summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorShoubhit Dash <[email protected]>2026-04-03 19:02:53 +0530
committerGitHub <[email protected]>2026-04-03 08:32:53 -0500
commit9d57f21f9fedddc753908d2727f0422408eaa878 (patch)
tree84b76f250c98c8b180f10148274df9b9aa0d43cf /packages
parent3deee3a02b26bc9fd73f9ed86f0f3d49cd168203 (diff)
downloadopencode-9d57f21f9fedddc753908d2727f0422408eaa878.tar.gz
opencode-9d57f21f9fedddc753908d2727f0422408eaa878.zip
feat(ui): redesign modified files section in session turn (#20348)
Co-authored-by: David Hill <[email protected]>
Diffstat (limited to 'packages')
-rw-r--r--packages/ui/src/components/session-turn.css64
-rw-r--r--packages/ui/src/components/session-turn.tsx209
-rw-r--r--packages/ui/src/i18n/en.ts4
3 files changed, 134 insertions, 143 deletions
diff --git a/packages/ui/src/components/session-turn.css b/packages/ui/src/components/session-turn.css
index c4a8726ac..53e2b58f4 100644
--- a/packages/ui/src/components/session-turn.css
+++ b/packages/ui/src/components/session-turn.css
@@ -92,33 +92,15 @@
min-width: 0;
}
- [data-slot="session-turn-diffs"]
- > [data-component="collapsible"]
- > [data-slot="collapsible-trigger"][aria-expanded="true"] {
- position: sticky;
- top: var(--sticky-accordion-top, 0px);
- z-index: 20;
- height: 40px;
- padding-bottom: 8px;
- background-color: var(--background-stronger);
- }
-
- [data-component="session-turn-diffs-trigger"] {
- width: 100%;
+ [data-slot="session-turn-diffs-header"] {
display: flex;
- align-items: center;
- justify-content: flex-start;
- gap: 8px;
- padding: 0;
- }
-
- [data-slot="session-turn-diffs-title"] {
- display: inline-flex;
align-items: baseline;
gap: 8px;
+ padding-bottom: 12px;
}
[data-slot="session-turn-diffs-label"] {
+ font-variant-numeric: tabular-nums;
color: var(--text-strong);
font-family: var(--font-family-sans);
font-size: var(--font-size-base);
@@ -126,28 +108,38 @@
line-height: var(--line-height-large);
}
- [data-slot="session-turn-diffs-count"] {
- color: var(--text-base);
+ [data-slot="session-turn-diffs-toggle"] {
+ color: var(--text-interactive-base);
font-family: var(--font-family-sans);
- font-variant-numeric: tabular-nums;
font-size: var(--font-size-base);
font-weight: var(--font-weight-regular);
- line-height: var(--line-height-x-large);
+ line-height: var(--line-height-large);
+ cursor: pointer;
+ opacity: 0;
+ transition: opacity 0.15s ease;
+ margin-left: 4px;
}
- [data-slot="session-turn-diffs-meta"] {
- display: inline-flex;
- align-items: center;
- gap: 8px;
- flex-shrink: 0;
+ [data-component="session-turn-diffs-group"]:hover [data-slot="session-turn-diffs-toggle"] {
+ opacity: 1;
+ }
- [data-slot="collapsible-arrow"] {
- margin-left: -6px;
- transform: translateY(2px);
- }
+ [data-component="session-turn-diffs-group"][data-show-all] [data-slot="session-turn-diffs-toggle"] {
+ opacity: 1;
+ }
+
+ [data-slot="session-turn-diffs-more"] {
+ color: var(--text-weak);
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-small);
+ line-height: var(--line-height-large);
+ margin-top: 12px;
+ padding: 0 0 6px;
+ cursor: pointer;
+ transition: color 0.15s ease;
- [data-component="diff-changes"][data-variant="bars"] {
- transform: translateY(1px);
+ &:hover {
+ color: var(--text-link-base);
}
}
diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx
index fe029485a..c20e5fb1c 100644
--- a/packages/ui/src/components/session-turn.tsx
+++ b/packages/ui/src/components/session-turn.tsx
@@ -12,7 +12,6 @@ import { AssistantParts, Message, MessageDivider, PART_MAPPING, type UserActions
import { Card } from "./card"
import { Accordion } from "./accordion"
import { StickyAccordionHeader } from "./sticky-accordion-header"
-import { Collapsible } from "./collapsible"
import { DiffChanges } from "./diff-changes"
import { Icon } from "./icon"
import { TextShimmer } from "./text-shimmer"
@@ -241,23 +240,20 @@ export function SessionTurn(
}, [])
.reverse()
})
+ const MAX_FILES = 10
const edited = createMemo(() => diffs().length)
const [state, setState] = createStore({
- open: false,
+ showAll: false,
expanded: [] as string[],
})
- const open = () => state.open
+ const showAll = () => state.showAll
const expanded = () => state.expanded
-
- createEffect(
- on(
- open,
- (value, prev) => {
- if (!value && prev) setState("expanded", [])
- },
- { defer: true },
- ),
- )
+ const overflow = createMemo(() => Math.max(0, edited() - MAX_FILES))
+ const visible = createMemo(() => (showAll() ? diffs() : diffs().slice(0, MAX_FILES)))
+ const toggleAll = () => {
+ autoScroll.pause()
+ setState("showAll", !showAll())
+ }
const assistantMessages = createMemo(
() => {
@@ -425,101 +421,100 @@ export function SessionTurn(
</Show>
<SessionRetry status={status()} show={active()} />
<Show when={edited() > 0 && !working()}>
- <div data-slot="session-turn-diffs">
- <Collapsible open={open()} onOpenChange={(value) => setState("open", value)} variant="ghost">
- <Collapsible.Trigger>
- <div data-component="session-turn-diffs-trigger">
- <div data-slot="session-turn-diffs-title">
- <span data-slot="session-turn-diffs-label">{i18n.t("ui.sessionReview.change.modified")}</span>
- <span data-slot="session-turn-diffs-count">
- {edited()} {i18n.t(edited() === 1 ? "ui.common.file.one" : "ui.common.file.other")}
- </span>
- <div data-slot="session-turn-diffs-meta">
- <DiffChanges changes={diffs()} variant="bars" />
- <Collapsible.Arrow />
- </div>
- </div>
- </div>
- </Collapsible.Trigger>
- <Collapsible.Content>
- <Show when={open()}>
- <div data-component="session-turn-diffs-content">
- <Accordion
- multiple
- style={{ "--sticky-accordion-offset": "40px" }}
- value={expanded()}
- onChange={(value) =>
- setState("expanded", Array.isArray(value) ? value : value ? [value] : [])
- }
- >
- <For each={diffs()}>
- {(diff) => {
- const active = createMemo(() => expanded().includes(diff.file))
- const [visible, setVisible] = createSignal(false)
-
- createEffect(
- on(
- active,
- (value) => {
- if (!value) {
- setVisible(false)
- return
- }
-
- requestAnimationFrame(() => {
- if (!active()) return
- setVisible(true)
- })
- },
- { defer: true },
- ),
- )
-
- return (
- <Accordion.Item value={diff.file}>
- <StickyAccordionHeader>
- <Accordion.Trigger>
- <div data-slot="session-turn-diff-trigger">
- <span data-slot="session-turn-diff-path">
- <Show when={diff.file.includes("/")}>
- <span data-slot="session-turn-diff-directory">
- {`\u202A${getDirectory(diff.file)}\u202C`}
- </span>
- </Show>
- <span data-slot="session-turn-diff-filename">{getFilename(diff.file)}</span>
- </span>
- <div data-slot="session-turn-diff-meta">
- <span data-slot="session-turn-diff-changes">
- <DiffChanges changes={diff} />
- </span>
- <span data-slot="session-turn-diff-chevron">
- <Icon name="chevron-down" size="small" />
- </span>
- </div>
- </div>
- </Accordion.Trigger>
- </StickyAccordionHeader>
- <Accordion.Content>
- <Show when={visible()}>
- <div data-slot="session-turn-diff-view" data-scrollable>
- <Dynamic
- component={fileComponent}
- mode="diff"
- before={{ name: diff.file, contents: diff.before }}
- after={{ name: diff.file, contents: diff.after }}
- />
- </div>
+ <div
+ data-slot="session-turn-diffs"
+ data-component="session-turn-diffs-group"
+ data-show-all={showAll() || undefined}
+ >
+ <div data-slot="session-turn-diffs-header">
+ <span data-slot="session-turn-diffs-label">
+ {edited()} {i18n.t("ui.sessionTurn.diffs.changed")}{" "}
+ {i18n.t(edited() === 1 ? "ui.common.file.one" : "ui.common.file.other")}
+ </span>
+ <DiffChanges changes={diffs()} />
+ <Show when={overflow() > 0}>
+ <span data-slot="session-turn-diffs-toggle" onClick={toggleAll}>
+ {showAll() ? i18n.t("ui.sessionTurn.diffs.showLess") : i18n.t("ui.sessionTurn.diffs.showAll")}
+ </span>
+ </Show>
+ </div>
+ <div data-component="session-turn-diffs-content">
+ <Accordion
+ multiple
+ style={{ "--sticky-accordion-offset": "40px" }}
+ value={expanded()}
+ onChange={(value) => setState("expanded", Array.isArray(value) ? value : value ? [value] : [])}
+ >
+ <For each={visible()}>
+ {(diff) => {
+ const active = createMemo(() => expanded().includes(diff.file))
+ const [shown, setShown] = createSignal(false)
+
+ createEffect(
+ on(
+ active,
+ (value) => {
+ if (!value) {
+ setShown(false)
+ return
+ }
+
+ requestAnimationFrame(() => {
+ if (!active()) return
+ setShown(true)
+ })
+ },
+ { defer: true },
+ ),
+ )
+
+ return (
+ <Accordion.Item value={diff.file}>
+ <StickyAccordionHeader>
+ <Accordion.Trigger>
+ <div data-slot="session-turn-diff-trigger">
+ <span data-slot="session-turn-diff-path">
+ <Show when={diff.file.includes("/")}>
+ <span data-slot="session-turn-diff-directory">
+ {`\u202A${getDirectory(diff.file)}\u202C`}
+ </span>
</Show>
- </Accordion.Content>
- </Accordion.Item>
- )
- }}
- </For>
- </Accordion>
- </div>
- </Show>
- </Collapsible.Content>
- </Collapsible>
+ <span data-slot="session-turn-diff-filename">{getFilename(diff.file)}</span>
+ </span>
+ <div data-slot="session-turn-diff-meta">
+ <span data-slot="session-turn-diff-changes">
+ <DiffChanges changes={diff} />
+ </span>
+ <span data-slot="session-turn-diff-chevron">
+ <Icon name="chevron-down" size="small" />
+ </span>
+ </div>
+ </div>
+ </Accordion.Trigger>
+ </StickyAccordionHeader>
+ <Accordion.Content>
+ <Show when={shown()}>
+ <div data-slot="session-turn-diff-view" data-scrollable>
+ <Dynamic
+ component={fileComponent}
+ mode="diff"
+ before={{ name: diff.file, contents: diff.before }}
+ after={{ name: diff.file, contents: diff.after }}
+ />
+ </div>
+ </Show>
+ </Accordion.Content>
+ </Accordion.Item>
+ )
+ }}
+ </For>
+ </Accordion>
+ <Show when={!showAll() && overflow() > 0}>
+ <div data-slot="session-turn-diffs-more" onClick={toggleAll}>
+ {i18n.t("ui.sessionTurn.diffs.more", { count: String(overflow()) })}
+ </div>
+ </Show>
+ </div>
</div>
</Show>
<Show when={error()}>
diff --git a/packages/ui/src/i18n/en.ts b/packages/ui/src/i18n/en.ts
index 18823aeaa..e66b55092 100644
--- a/packages/ui/src/i18n/en.ts
+++ b/packages/ui/src/i18n/en.ts
@@ -38,6 +38,10 @@ export const dict: Record<string, string> = {
"ui.sessionTurn.steps.hide": "Hide steps",
"ui.sessionTurn.summary.response": "Response",
"ui.sessionTurn.diff.showMore": "Show more changes ({{count}})",
+ "ui.sessionTurn.diffs.changed": "Changed",
+ "ui.sessionTurn.diffs.showAll": "Show all",
+ "ui.sessionTurn.diffs.showLess": "Show less",
+ "ui.sessionTurn.diffs.more": "+{{count}} more files",
"ui.sessionTurn.retry.retrying": "retrying",
"ui.sessionTurn.retry.inSeconds": "in {{seconds}}s",