chore: merge dev into v2 (#36144)

Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: usrnk1 <7547651+usrnk1@users.noreply.github.com>
Co-authored-by: James Long <longster@gmail.com>
Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com>
Co-authored-by: Jack <jack@anoma.ly>
Co-authored-by: opencode <opencode@sst.dev>
Co-authored-by: Frank <frank@anoma.ly>
Co-authored-by: Jay <53023+jayair@users.noreply.github.com>
Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
Co-authored-by: Aarav Sareen <96787824+arvsrn@users.noreply.github.com>
Co-authored-by: Julian Coy <julian@ex-machina.co>
Co-authored-by: Brendan Allan <git@brendonovich.dev>
Co-authored-by: Vladimir Glafirov <vglafirov@gitlab.com>
Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com>
Co-authored-by: Dustin Deus <deusdustin@gmail.com>
Co-authored-by: Kit Langton <kit.langton@gmail.com>
Co-authored-by: Simon Klee <hello@simonklee.dk>
Co-authored-by: Jay <air@live.ca>
Co-authored-by: David Hill <1879069+iamdavidhill@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
Co-authored-by: James Long <jlongster@users.noreply.github.com>
Co-authored-by: 冯基魁 <56265583+fengjikui@users.noreply.github.com>
Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com>
This commit is contained in:
opencode-agent[bot]
2026-07-09 16:32:44 -05:00
committed by GitHub
co-authored by opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com> usrnk1 James Long opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com> Aiden Cline Brendan Allan Jack opencode Frank Jay Luke Parker Aarav Sareen Julian Coy Brendan Allan Vladimir Glafirov Adam Dustin Deus Kit Langton Simon Klee Jay David Hill Aiden Cline James Long 冯基魁 Aiden Cline
parent a72992e00f
commit a7746379d5
129 changed files with 3734 additions and 945 deletions
@@ -185,6 +185,14 @@
background-color 0.15s ease,
color 0.15s ease;
[data-component="task-tool-surface"] {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
flex: 1 1 auto;
}
[data-slot="basic-tool-tool-info-structured"] {
flex: 1 1 auto;
min-width: 0;
@@ -250,16 +258,30 @@
body[data-new-layout] {
[data-component="task-tool-card"] {
padding: 8px 12px 8px 10px;
border-radius: 8px;
gap: 8px;
padding: 0;
border-radius: 0;
border: 0;
border-color: transparent;
background: var(--task-agent-background, light-dark(#fafafa, rgba(250, 250, 250, 0.15)));
box-shadow: inset 0 0 0 0.5px var(--task-agent-border, var(--v2-border-border-base));
background: transparent;
box-shadow: none;
[data-component="task-tool-surface"] {
padding: 8px 12px;
border-radius: 8px;
background: var(--v2-background-bg-layer-01);
}
[data-component="task-tool-spinner"],
[data-component="task-tool-title"] {
color: var(--task-agent-color, var(--text-strong));
[data-component="task-tool-icon"] {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
[data-component="icon"] {
color: var(--task-agent-color, var(--v2-icon-icon-base));
}
}
[data-component="task-tool-title"] {
@@ -270,6 +292,7 @@ body[data-new-layout] {
font-size: 13px;
line-height: 20px;
letter-spacing: -0.04px;
color: var(--v2-text-text-base);
flex: none;
flex-grow: 0;
}
@@ -281,7 +304,7 @@ body[data-new-layout] {
font-size: 13px;
line-height: 20px;
letter-spacing: -0.04px;
color: light-dark(var(--v2-text-text-base), #fafafa);
color: var(--v2-text-text-base);
font-variation-settings: "slnt" 0;
flex: none;
flex-grow: 0;
@@ -294,7 +317,11 @@ body[data-new-layout] {
&:hover,
&:focus-visible {
border-color: transparent;
background: var(--task-agent-background, light-dark(#fafafa, rgba(250, 250, 250, 0.15)));
background: transparent;
[data-component="task-tool-surface"] {
background: var(--v2-background-bg-layer-01);
}
}
}
}
@@ -369,22 +369,12 @@ const agentTones: Record<string, string> = {
plan: "var(--icon-agent-plan-base)",
}
const v2AgentTones: Record<string, { color: string; border: string; background: string }> = {
build: {
color: "var(--v2-agent-build-solid)",
border: "var(--v2-agent-build-border)",
background: "var(--v2-agent-build-background)",
},
explore: {
color: "var(--v2-agent-explore-solid)",
border: "var(--v2-agent-explore-border)",
background: "var(--v2-agent-explore-background)",
},
plan: {
color: "var(--v2-agent-plan-solid)",
border: "var(--v2-agent-plan-border)",
background: "var(--v2-agent-plan-background)",
},
const v2AgentTones: Record<string, string> = {
build: "var(--v2-agent-build-solid)",
explore: "var(--v2-agent-explore-solid)",
plan: "var(--v2-agent-plan-solid)",
review: "var(--v2-agent-review-solid)",
writer: "var(--v2-agent-writer-solid)",
}
const agentThemeColors: Record<string, string> = {
@@ -431,19 +421,17 @@ function tone(name: string) {
function taskAgent(
raw: unknown,
list?: readonly { name: string; color?: string }[],
): { name?: string; color?: string; v2Color?: string; border?: string; background?: string } {
): { name?: string; color?: string; v2Color?: string } {
if (typeof raw !== "string" || !raw) return {}
const key = raw.toLowerCase()
const item = list?.find((entry) => entry.name === raw || entry.name.toLowerCase() === key)
const v2Tone = item?.color ? undefined : v2AgentTones[key]
const color = agentColor(item?.color, agentThemeColors) ?? agentTones[key] ?? tone(key)
const v2Color = agentColor(item?.color, v2AgentThemeColors) ?? v2Tone?.color ?? color
const v2Color = agentColor(item?.color, v2AgentThemeColors) ?? v2Tone ?? color
return {
name: item?.name ?? `${raw[0]!.toUpperCase()}${raw.slice(1)}`,
color,
v2Color,
border: v2Tone?.border ?? `color-mix(in srgb, ${v2Color} 48%, transparent)`,
background: v2Tone?.background ?? `color-mix(in srgb, ${v2Color} 12%, transparent)`,
}
}
@@ -1934,8 +1922,6 @@ ToolRegistry.register({
const title = createMemo(() => agent().name ?? i18n.t("ui.tool.agent.default"))
const tone = createMemo(() => agent().color)
const v2Tone = createMemo(() => agent().v2Color)
const border = createMemo(() => agent().border)
const background = createMemo(() => agent().background)
const subtitle = createMemo(() => {
const value =
typeof props.input.description === "string" && props.input.description
@@ -1980,25 +1966,34 @@ ToolRegistry.register({
style={{
"--task-agent-color": v2Tone(),
"--task-agent-legacy-color": tone(),
"--task-agent-border": border(),
"--task-agent-background": background(),
}}
>
<div data-slot="basic-tool-tool-info-structured">
<div data-slot="basic-tool-tool-info-main">
<Show when={running()}>
<span data-component="task-tool-spinner" style={{ color: tone() ?? "var(--icon-interactive-base)" }}>
<Show when={newLayout()} fallback={<Spinner />}>
<SessionProgressIndicatorV2
style={{ color: v2Tone() ?? "light-dark(var(--v2-text-text-base), #ffffff)" }}
/>
</Show>
</span>
</Show>
<span data-component="task-tool-title">{title()}</span>
<Show when={subtitle()}>
<span data-slot="basic-tool-tool-subtitle">{subtitle()}</span>
</Show>
<div data-component="task-tool-surface">
<div data-slot="basic-tool-tool-info-structured">
<div data-slot="basic-tool-tool-info-main">
<Show
when={running()}
fallback={
<Show when={newLayout()}>
<span data-component="task-tool-icon">
<Icon name="subagent" size="small" />
</span>
</Show>
}
>
<span data-component="task-tool-spinner" style={{ color: tone() ?? "var(--icon-interactive-base)" }}>
<Show when={newLayout()} fallback={<Spinner />}>
<SessionProgressIndicatorV2
style={{ color: v2Tone() ?? "light-dark(var(--v2-text-text-base), #ffffff)" }}
/>
</Show>
</span>
</Show>
<span data-component="task-tool-title">{title()}</span>
<Show when={subtitle()}>
<span data-slot="basic-tool-tool-subtitle">{subtitle()}</span>
</Show>
</div>
</div>
</div>
<Show when={clickable()}>
@@ -359,6 +359,34 @@ const TOOL_SAMPLES = {
title: "Agent (Explore)",
metadata: { sessionId: "sub-session-1" },
},
"task build": {
tool: "task",
input: { description: "Implement the settings page", subagent_type: "build", prompt: "Build it" },
output: "Implemented the settings page.",
title: "Agent (Build)",
metadata: { sessionId: "sub-session-2" },
},
"task plan": {
tool: "task",
input: { description: "Plan the migration strategy", subagent_type: "plan", prompt: "Plan it" },
output: "Drafted a 4-step migration plan.",
title: "Agent (Plan)",
metadata: { sessionId: "sub-session-3" },
},
"task themed": {
tool: "task",
input: { description: "Review the diff for regressions", subagent_type: "review", prompt: "Review it" },
output: "No regressions found.",
title: "Agent (Review)",
metadata: { sessionId: "sub-session-4" },
},
"task fallback": {
tool: "task",
input: { description: "Write release notes", subagent_type: "writer", prompt: "Write them" },
output: "Release notes drafted.",
title: "Agent (Writer)",
metadata: { sessionId: "sub-session-5" },
},
webfetch: {
tool: "webfetch",
input: { url: "https://solidjs.com/docs/latest/api" },
@@ -1197,6 +1225,7 @@ function Playground() {
const data = createMemo(() => ({
session: [session()],
agent: [{ name: "build" }, { name: "plan" }, { name: "explore" }, { name: "review" }],
session_status: {},
session_diff: {},
message: { [session().id]: state.messages },
@@ -1323,6 +1352,19 @@ function Playground() {
])
}
const addAgentTasksTurn = () => {
addFullTurn("Run subagents with every agent type", [
toolPart(TOOL_SAMPLES.task),
toolPart(TOOL_SAMPLES["task build"]),
toolPart(TOOL_SAMPLES["task plan"]),
toolPart(TOOL_SAMPLES["task themed"]),
toolPart(TOOL_SAMPLES["task fallback"]),
toolPart(TOOL_SAMPLES.task, "running"),
toolPart(TOOL_SAMPLES["task build"], "running"),
toolPart(TOOL_SAMPLES["task plan"], "running"),
])
}
const addKitchenSink = () => {
// User message variants
addUser("short")
@@ -1708,6 +1750,9 @@ function Playground() {
<button style={btnStyle} onClick={addReasoningFullTurn}>
full turn
</button>
<button style={btnStyle} onClick={addAgentTasksTurn}>
agent tasks
</button>
<button style={btnAccent} onClick={addKitchenSink}>
kitchen sink
</button>
@@ -1947,7 +1992,7 @@ function Playground() {
{/* Main area: timeline preview */}
<div
ref={previewRef!}
style={{ flex: "1", overflow: "auto", "min-width": "0", "background-color": "var(--background-stronger)" }}
style={{ flex: "1", overflow: "auto", "min-width": "0", "background-color": "var(--v2-background-bg-base)" }}
>
<DataProvider data={data()} directory="/project">
<FileComponentProvider component={FileStub}>
@@ -0,0 +1,43 @@
import { Show, type JSX, type ParentProps } from "solid-js"
import "./session-review-v2.css"
export function SessionFilePanelV2(props: {
sidebar?: JSX.Element
toolbar: boolean
toolbarStart?: JSX.Element
toolbarEnd?: JSX.Element
children?: JSX.Element
}) {
return (
<div data-component="session-review-v2">
<div data-slot="session-review-v2-body">
{props.sidebar}
<div data-slot="session-review-v2-preview">
<Show when={props.toolbar}>
<div data-slot="session-review-v2-toolbar">
<div data-slot="session-review-v2-toolbar-group" class="session-review-v2-toolbar-group--start">
{props.toolbarStart}
</div>
<Show when={props.toolbarEnd}>
{(toolbar) => (
<div data-slot="session-review-v2-toolbar-group" class="session-review-v2-toolbar-group--segments">
{toolbar()}
</div>
)}
</Show>
</div>
</Show>
{props.children}
</div>
</div>
</div>
)
}
export function SessionFilePanelV2Title(props: ParentProps) {
return <div data-slot="session-review-v2-toolbar-title">{props.children}</div>
}
export function SessionFilePanelV2Empty(props: ParentProps) {
return <div data-slot="session-review-v2-empty">{props.children}</div>
}
@@ -12,7 +12,7 @@ import { ScrollView } from "@opencode-ai/ui/scroll-view"
import { makeEventListener } from "@solid-primitives/event-listener"
import { Show, createEffect, createMemo, createSignal, type JSX } from "solid-js"
import { getWorkerPool } from "../../pierre/worker"
import "./session-review-v2.css"
import { SessionFilePanelV2, SessionFilePanelV2Empty } from "./session-file-panel-v2"
export const SESSION_REVIEW_V2_SIDEBAR_WIDTH_DEFAULT = 240
export const SESSION_REVIEW_V2_SIDEBAR_WIDTH_MIN = 200
@@ -45,6 +45,11 @@ export type SessionReviewV2SidebarProps = {
filter: string
onFilterChange: (value: string) => void
onFilterKeyDown?: JSX.EventHandlerUnion<HTMLInputElement, KeyboardEvent>
filterAutofocus?: boolean
filterRef?: (element: HTMLInputElement) => void
filterControls?: string
filterActiveDescendant?: string
filterExpanded?: boolean
width?: number
onWidthChange?: (width: number) => void
minWidth?: number
@@ -69,55 +74,62 @@ export function SessionReviewV2Sidebar(props: SessionReviewV2SidebarProps) {
return (
<div data-component="session-review-v2-sidebar-root">
<aside
data-slot="session-review-v2-sidebar"
data-resizing={resizing() ? "" : undefined}
aria-hidden={!props.open}
inert={!props.open}
style={{ width: props.open ? `${width()}px` : "0px" }}
>
<div data-slot="session-review-v2-sidebar-header">
<div data-slot="session-review-v2-sidebar-title">{props.title}</div>
{props.stats}
</div>
<div data-slot="session-review-v2-sidebar-filter">
<TextInputV2
type="search"
value={props.filter}
onInput={(event) => props.onFilterChange(event.currentTarget.value)}
onKeyDown={props.onFilterKeyDown}
showClearButton={props.filter.length > 0}
clearLabel={i18n.t("ui.list.clearFilter")}
onClearClick={() => props.onFilterChange("")}
placeholder={i18n.t("ui.sessionReviewV2.filterFiles")}
aria-label={i18n.t("ui.sessionReviewV2.filterFiles")}
leadingIcon={
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
d="M12.25 12.25L10.0625 10.0625M11.0833 6.41667C11.0833 8.994 8.994 11.0833 6.41667 11.0833C3.83934 11.0833 1.75 8.994 1.75 6.41667C1.75 3.83934 3.83934 1.75 6.41667 1.75C8.994 1.75 11.0833 3.83934 11.0833 6.41667Z"
stroke="currentColor"
stroke-linecap="square"
/>
</svg>
}
/>
</div>
<ScrollView
data-slot="session-review-v2-sidebar-tree"
class="group/file-tree-v2"
thumbVisibility="scroll"
viewportRef={props.viewportRef}
<Show when={props.open}>
<aside
data-slot="session-review-v2-sidebar"
data-resizing={resizing() ? "" : undefined}
style={{ width: `${width()}px` }}
>
{props.children}
</ScrollView>
</aside>
<div data-slot="session-review-v2-sidebar-header">
<div data-slot="session-review-v2-sidebar-title">{props.title}</div>
{props.stats}
</div>
<div data-slot="session-review-v2-sidebar-filter">
<TextInputV2
type="search"
value={props.filter}
onInput={(event) => props.onFilterChange(event.currentTarget.value)}
onKeyDown={props.onFilterKeyDown}
autofocus={props.filterAutofocus}
ref={props.filterRef}
role={props.filterControls ? "combobox" : undefined}
aria-autocomplete={props.filterControls ? "list" : undefined}
aria-controls={props.filterControls}
aria-activedescendant={props.filterActiveDescendant}
aria-expanded={props.filterControls ? props.filterExpanded : undefined}
showClearButton={props.filter.length > 0}
clearLabel={i18n.t("ui.list.clearFilter")}
onClearClick={() => props.onFilterChange("")}
placeholder={i18n.t("ui.sessionReviewV2.filterFiles")}
aria-label={i18n.t("ui.sessionReviewV2.filterFiles")}
leadingIcon={
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
d="M12.25 12.25L10.0625 10.0625M11.0833 6.41667C11.0833 8.994 8.994 11.0833 6.41667 11.0833C3.83934 11.0833 1.75 8.994 1.75 6.41667C1.75 3.83934 3.83934 1.75 6.41667 1.75C8.994 1.75 11.0833 3.83934 11.0833 6.41667Z"
stroke="currentColor"
stroke-linecap="square"
/>
</svg>
}
/>
</div>
<ScrollView
data-slot="session-review-v2-sidebar-tree"
class="group/file-tree-v2"
thumbVisibility="scroll"
viewportRef={props.viewportRef}
>
{props.children}
</ScrollView>
</aside>
</Show>
<Show when={props.open && props.onWidthChange}>
<div data-slot="session-review-v2-sidebar-resize" onPointerDown={() => setResizing(true)}>
<ResizeHandle
@@ -185,130 +197,125 @@ export function SessionReviewV2(props: SessionReviewV2Props) {
cycle(event.key === "<" ? prev() : next())
})
return (
<div data-component="session-review-v2">
<div data-slot="session-review-v2-body">
{props.sidebar}
<div data-slot="session-review-v2-preview">
<Show when={props.hasDiffs} fallback={props.empty}>
<div data-slot="session-review-v2-toolbar">
<div data-slot="session-review-v2-toolbar-group" class="session-review-v2-toolbar-group--start">
{props.sidebarToggle}
<Show when={showCollapsedMeta()}>
<div data-slot="session-review-v2-toolbar-collapsed-meta">
<Show when={title()}>
<div data-slot="session-review-v2-toolbar-title">{title()}</div>
</Show>
{stats()}
<Show when={canCycle()}>
<span data-slot="session-review-v2-file-position">
{fileIndex() + 1}/{props.files.length}
</span>
</Show>
</div>
</Show>
<div data-slot="session-review-v2-toolbar-group">
<TooltipV2
openDelay={2000}
value={
<>
{i18n.t("ui.sessionReviewV2.previousFile")}
<KeybindV2 keys={["<"]} variant="neutral" />
</>
}
>
<IconButton
icon="arrow-left"
variant="ghost"
size="small"
class="session-review-v2-file-nav-button"
disabled={!canCycle()}
onClick={() => {
const file = prev()
if (!file) return
props.onSelectFile(file)
}}
aria-label={i18n.t("ui.sessionReviewV2.previousFile")}
/>
</TooltipV2>
<TooltipV2
openDelay={2000}
value={
<>
{i18n.t("ui.sessionReviewV2.nextFile")}
<KeybindV2 keys={[">"]} variant="neutral" />
</>
}
>
<IconButton
icon="arrow-right"
variant="ghost"
size="small"
class="session-review-v2-file-nav-button"
disabled={!canCycle()}
onClick={() => {
const file = next()
if (!file) return
props.onSelectFile(file)
}}
aria-label={i18n.t("ui.sessionReviewV2.nextFile")}
/>
</TooltipV2>
</div>
</div>
<div data-slot="session-review-v2-toolbar-group" class="session-review-v2-toolbar-group--segments">
<SegmentedControlV2
value={props.expandMode}
onChange={(value) => {
if (value !== "expand" && value !== "collapse") return
props.onExpandModeChange(value)
}}
class="session-review-v2-segmented-control session-review-v2-segmented-control--icon"
aria-label={i18n.t("ui.sessionReviewV2.expandMode")}
>
<TooltipV2 openDelay={2000} value={i18n.t("ui.sessionReviewV2.showAllLines")}>
<SegmentedControlItemV2 value="expand" aria-label={i18n.t("ui.sessionReviewV2.showAllLines")}>
<Icon name="expand" />
</SegmentedControlItemV2>
</TooltipV2>
<TooltipV2 openDelay={2000} value={i18n.t("ui.sessionReviewV2.hideNonDiffLines")}>
<SegmentedControlItemV2 value="collapse" aria-label={i18n.t("ui.sessionReviewV2.hideNonDiffLines")}>
<Icon name="collapse" />
</SegmentedControlItemV2>
</TooltipV2>
</SegmentedControlV2>
<Show when={props.onDiffStyleChange}>
<SegmentedControlV2
value={props.diffStyle}
onChange={(value) => {
if (value !== "unified" && value !== "split") return
props.onDiffStyleChange?.(value)
}}
class="session-review-v2-segmented-control session-review-v2-segmented-control--icon"
aria-label={i18n.t("ui.sessionReviewV2.diffView")}
>
<TooltipV2 openDelay={2000} value={i18n.t("ui.sessionReviewV2.unifiedDiff")}>
<SegmentedControlItemV2 value="unified" aria-label={i18n.t("ui.sessionReviewV2.unifiedDiff")}>
<Icon name="unified" />
</SegmentedControlItemV2>
</TooltipV2>
<TooltipV2 openDelay={2000} value={i18n.t("ui.sessionReviewV2.splitDiff")}>
<SegmentedControlItemV2 value="split" aria-label={i18n.t("ui.sessionReviewV2.splitDiff")}>
<Icon name="split" />
</SegmentedControlItemV2>
</TooltipV2>
</SegmentedControlV2>
</Show>
</div>
</div>
<Show when={props.activeFile} fallback={<div data-slot="session-review-v2-empty">{props.empty}</div>}>
{props.preview}
</Show>
const toolbarStart = () => (
<>
{props.sidebarToggle}
<Show when={showCollapsedMeta()}>
<div data-slot="session-review-v2-toolbar-collapsed-meta">
<Show when={title()}>
<div data-slot="session-review-v2-toolbar-title">{title()}</div>
</Show>
{stats()}
<Show when={canCycle()}>
<span data-slot="session-review-v2-file-position">
{fileIndex() + 1}/{props.files.length}
</span>
</Show>
</div>
</Show>
<div class="flex items-center">
<TooltipV2
openDelay={2000}
value={
<>
{i18n.t("ui.sessionReviewV2.previousFile")}
<KeybindV2 keys={["<"]} variant="neutral" />
</>
}
>
<IconButton
icon="arrow-left"
variant="ghost"
size="small"
class="session-review-v2-file-nav-button"
disabled={!canCycle()}
onClick={() => cycle(prev())}
aria-label={i18n.t("ui.sessionReviewV2.previousFile")}
/>
</TooltipV2>
<TooltipV2
openDelay={2000}
value={
<>
{i18n.t("ui.sessionReviewV2.nextFile")}
<KeybindV2 keys={[">"]} variant="neutral" />
</>
}
>
<IconButton
icon="arrow-right"
variant="ghost"
size="small"
class="session-review-v2-file-nav-button"
disabled={!canCycle()}
onClick={() => cycle(next())}
aria-label={i18n.t("ui.sessionReviewV2.nextFile")}
/>
</TooltipV2>
</div>
</div>
</>
)
const toolbarEnd = () => (
<>
<SegmentedControlV2
value={props.expandMode}
onChange={(value) => {
if (value !== "expand" && value !== "collapse") return
props.onExpandModeChange(value)
}}
class="session-review-v2-segmented-control session-review-v2-segmented-control--icon"
aria-label={i18n.t("ui.sessionReviewV2.expandMode")}
>
<TooltipV2 openDelay={2000} value={i18n.t("ui.sessionReviewV2.showAllLines")}>
<SegmentedControlItemV2 value="expand" aria-label={i18n.t("ui.sessionReviewV2.showAllLines")}>
<Icon name="expand" />
</SegmentedControlItemV2>
</TooltipV2>
<TooltipV2 openDelay={2000} value={i18n.t("ui.sessionReviewV2.hideNonDiffLines")}>
<SegmentedControlItemV2 value="collapse" aria-label={i18n.t("ui.sessionReviewV2.hideNonDiffLines")}>
<Icon name="collapse" />
</SegmentedControlItemV2>
</TooltipV2>
</SegmentedControlV2>
<Show when={props.onDiffStyleChange}>
<SegmentedControlV2
value={props.diffStyle}
onChange={(value) => {
if (value !== "unified" && value !== "split") return
props.onDiffStyleChange?.(value)
}}
class="session-review-v2-segmented-control session-review-v2-segmented-control--icon"
aria-label={i18n.t("ui.sessionReviewV2.diffView")}
>
<TooltipV2 openDelay={2000} value={i18n.t("ui.sessionReviewV2.unifiedDiff")}>
<SegmentedControlItemV2 value="unified" aria-label={i18n.t("ui.sessionReviewV2.unifiedDiff")}>
<Icon name="unified" />
</SegmentedControlItemV2>
</TooltipV2>
<TooltipV2 openDelay={2000} value={i18n.t("ui.sessionReviewV2.splitDiff")}>
<SegmentedControlItemV2 value="split" aria-label={i18n.t("ui.sessionReviewV2.splitDiff")}>
<Icon name="split" />
</SegmentedControlItemV2>
</TooltipV2>
</SegmentedControlV2>
</Show>
</>
)
return (
<SessionFilePanelV2
sidebar={props.sidebar}
toolbar={props.hasDiffs}
toolbarStart={toolbarStart()}
toolbarEnd={toolbarEnd()}
>
<Show when={props.hasDiffs} fallback={props.empty}>
<Show when={props.activeFile} fallback={<SessionFilePanelV2Empty>{props.empty}</SessionFilePanelV2Empty>}>
{props.preview}
</Show>
</Show>
</SessionFilePanelV2>
)
}