summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Hill <[email protected]>2026-01-27 17:16:01 +0000
committerDavid Hill <[email protected]>2026-01-27 20:22:05 +0000
commitf2bf6202061228584663eebb2fad95f9f494f3e6 (patch)
tree112a242b62c12dd21e2ae064a2eb05fe59bc8702
parent00c7729658ffe705faae3bcca21b6b416e9f7fbe (diff)
downloadopencode-f2bf6202061228584663eebb2fad95f9f494f3e6.tar.gz
opencode-f2bf6202061228584663eebb2fad95f9f494f3e6.zip
fix(app): highlight selected change
Track clicked file in the Changes tree and apply selection styling to the matching review diff.
-rw-r--r--packages/app/src/components/file-tree.tsx5
-rw-r--r--packages/app/src/pages/session.tsx29
-rw-r--r--packages/ui/src/components/button.css9
-rw-r--r--packages/ui/src/components/session-review.css7
-rw-r--r--packages/ui/src/components/session-review.tsx2
-rw-r--r--packages/ui/src/styles/theme.css2
-rw-r--r--packages/ui/src/theme/resolve.ts1
7 files changed, 52 insertions, 3 deletions
diff --git a/packages/app/src/components/file-tree.tsx b/packages/app/src/components/file-tree.tsx
index 3f5abfbbf..d43310b19 100644
--- a/packages/app/src/components/file-tree.tsx
+++ b/packages/app/src/components/file-tree.tsx
@@ -29,6 +29,7 @@ export default function FileTree(props: {
path: string
class?: string
nodeClass?: string
+ active?: string
level?: number
allowed?: readonly string[]
modified?: readonly string[]
@@ -149,6 +150,7 @@ export default function FileTree(props: {
component={local.as ?? "div"}
classList={{
"w-full min-w-0 h-6 flex items-center justify-start gap-x-1.5 rounded-md px-1.5 py-0 text-left hover:bg-surface-raised-base-hover active:bg-surface-base-active transition-colors cursor-pointer": true,
+ "bg-surface-base-active": local.node.path === props.active,
...(local.classList ?? {}),
[local.class ?? ""]: !!local.class,
[props.nodeClass ?? ""]: !!props.nodeClass,
@@ -297,7 +299,7 @@ export default function FileTree(props: {
<Show when={ignored()}>
<>
<span class="mx-1 font-bold text-text-invert-strong">•</span>
- <span class="shrink-0 text-text-invert-weak">Ignored</span>
+ <span class="shrink-0 text-text-invert-strong">Ignored</span>
</>
</Show>
</div>
@@ -343,6 +345,7 @@ export default function FileTree(props: {
allowed={props.allowed}
modified={props.modified}
kinds={props.kinds}
+ active={props.active}
draggable={props.draggable}
tooltip={props.tooltip}
onFileClick={props.onFileClick}
diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx
index 80a1ec703..e75bd2fed 100644
--- a/packages/app/src/pages/session.tsx
+++ b/packages/app/src/pages/session.tsx
@@ -89,6 +89,7 @@ interface SessionReviewTabProps {
comments?: LineComment[]
focusedComment?: { file: string; id: string } | null
onFocusedCommentChange?: (focus: { file: string; id: string } | null) => void
+ focusedFile?: string
onScrollRef?: (el: HTMLDivElement) => void
classes?: {
root?: string
@@ -213,6 +214,7 @@ function SessionReviewTab(props: SessionReviewTabProps) {
diffStyle={props.diffStyle}
onDiffStyleChange={props.onDiffStyleChange}
onViewFile={props.onViewFile}
+ focusedFile={props.focusedFile}
readFile={readFile}
onLineComment={props.onLineComment}
comments={props.comments}
@@ -480,12 +482,29 @@ export default function Page() {
}
const kinds = createMemo(() => {
+ const merge = (a: "add" | "del" | "mix" | undefined, b: "add" | "del" | "mix") => {
+ if (!a) return b
+ if (a === b) return a
+ return "mix" as const
+ }
+
+ const normalize = (p: string) => p.replaceAll("\\\\", "/").replace(/\/+$/, "")
+
const out = new Map<string, "add" | "del" | "mix">()
for (const diff of diffs()) {
+ const file = normalize(diff.file)
const add = diff.additions > 0
const del = diff.deletions > 0
const kind = add && del ? "mix" : add ? "add" : del ? "del" : "mix"
- out.set(diff.file, kind)
+
+ out.set(file, kind)
+
+ const parts = file.split("/")
+ for (const [idx] of parts.slice(0, -1).entries()) {
+ const dir = parts.slice(0, idx + 1).join("/")
+ if (!dir) continue
+ out.set(dir, merge(out.get(dir), kind))
+ }
}
return out
})
@@ -1084,12 +1103,15 @@ export default function Page() {
const [tree, setTree] = createStore({
reviewScroll: undefined as HTMLDivElement | undefined,
pendingDiff: undefined as string | undefined,
+ activeDiff: undefined as string | undefined,
})
const reviewScroll = () => tree.reviewScroll
const setReviewScroll = (value: HTMLDivElement | undefined) => setTree("reviewScroll", value)
const pendingDiff = () => tree.pendingDiff
const setPendingDiff = (value: string | undefined) => setTree("pendingDiff", value)
+ const activeDiff = () => tree.activeDiff
+ const setActiveDiff = (value: string | undefined) => setTree("activeDiff", value)
const showAllFiles = () => {
if (fileTreeTab() !== "changes") return
@@ -1151,6 +1173,7 @@ export default function Page() {
const focusReviewDiff = (path: string) => {
const current = view().review.open() ?? []
if (!current.includes(path)) view().review.setOpen([...current, path])
+ setActiveDiff(path)
setPendingDiff(path)
}
@@ -1697,6 +1720,7 @@ export default function Page() {
diffs={diffs}
view={view}
diffStyle="unified"
+ focusedFile={activeDiff()}
onLineComment={(comment) => addCommentToContext({ ...comment, origin: "review" })}
comments={comments.all()}
focusedComment={comments.focus()}
@@ -2046,6 +2070,7 @@ export default function Page() {
</StickyAddButton>
</Tabs.List>
</div>
+
<Tabs.Content value="empty" class="flex flex-col h-full overflow-hidden contain-strict">
<Show when={activeTab() === "empty"}>
<div class="relative pt-2 flex-1 min-h-0 overflow-hidden">
@@ -2597,6 +2622,7 @@ export default function Page() {
diffStyle={layout.review.diffStyle()}
onDiffStyleChange={layout.review.setDiffStyle}
onScrollRef={setReviewScroll}
+ focusedFile={activeDiff()}
onLineComment={(comment) => addCommentToContext({ ...comment, origin: "review" })}
comments={comments.all()}
focusedComment={comments.focus()}
@@ -2664,6 +2690,7 @@ export default function Page() {
allowed={diffFiles()}
kinds={kinds()}
draggable={false}
+ active={activeDiff()}
onFileClick={(node) => focusReviewDiff(node.path)}
/>
</Show>
diff --git a/packages/ui/src/components/button.css b/packages/ui/src/components/button.css
index 933c7ce5f..d9b345923 100644
--- a/packages/ui/src/components/button.css
+++ b/packages/ui/src/components/button.css
@@ -43,6 +43,10 @@
background-color: transparent;
color: var(--text-strong);
+ [data-slot="icon-svg"] {
+ color: var(--icon-base);
+ }
+
&:hover:not(:disabled) {
background-color: var(--surface-raised-base-hover);
}
@@ -54,8 +58,11 @@
}
&:disabled {
color: var(--text-weak);
- opacity: 0.7;
cursor: not-allowed;
+
+ [data-slot="icon-svg"] {
+ color: var(--icon-disabled);
+ }
}
&[data-selected="true"]:not(:disabled) {
background-color: var(--surface-raised-base-hover);
diff --git a/packages/ui/src/components/session-review.css b/packages/ui/src/components/session-review.css
index dd75b1905..20d2fef15 100644
--- a/packages/ui/src/components/session-review.css
+++ b/packages/ui/src/components/session-review.css
@@ -54,6 +54,13 @@
background-color: var(--background-stronger) !important;
}
+ [data-slot="session-review-accordion-item"][data-selected] {
+ [data-slot="session-review-accordion-content"] {
+ box-shadow: var(--shadow-xs-border-select);
+ border-radius: var(--radius-lg);
+ }
+ }
+
[data-slot="accordion-item"] {
[data-slot="accordion-content"] {
display: none;
diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx
index 8dfbbb1ca..84ec934e2 100644
--- a/packages/ui/src/components/session-review.tsx
+++ b/packages/ui/src/components/session-review.tsx
@@ -44,6 +44,7 @@ export interface SessionReviewProps {
comments?: SessionReviewComment[]
focusedComment?: SessionReviewFocus | null
onFocusedCommentChange?: (focus: SessionReviewFocus | null) => void
+ focusedFile?: string
open?: string[]
onOpenChange?: (open: string[]) => void
scrollRef?: (el: HTMLDivElement) => void
@@ -501,6 +502,7 @@ export const SessionReview = (props: SessionReviewProps) => {
id={diffId(diff.file)}
data-file={diff.file}
data-slot="session-review-accordion-item"
+ data-selected={props.focusedFile === diff.file ? "" : undefined}
>
<StickyAccordionHeader>
<Accordion.Trigger>
diff --git a/packages/ui/src/styles/theme.css b/packages/ui/src/styles/theme.css
index 04a9837fd..951450d54 100644
--- a/packages/ui/src/styles/theme.css
+++ b/packages/ui/src/styles/theme.css
@@ -286,6 +286,7 @@
--icon-diff-add-active: var(--mint-light-12);
--icon-diff-delete-base: var(--ember-light-10);
--icon-diff-delete-hover: var(--ember-light-11);
+ --icon-diff-modified-base: var(--icon-warning-base);
--syntax-comment: var(--text-weak);
--syntax-regexp: var(--text-base);
--syntax-string: #006656;
@@ -543,6 +544,7 @@
--icon-diff-add-active: var(--mint-dark-11);
--icon-diff-delete-base: var(--ember-dark-9);
--icon-diff-delete-hover: var(--ember-dark-10);
+ --icon-diff-modified-base: var(--icon-warning-base);
--syntax-comment: var(--text-weak);
--syntax-regexp: var(--text-base);
--syntax-string: #00ceb9;
diff --git a/packages/ui/src/theme/resolve.ts b/packages/ui/src/theme/resolve.ts
index 8c25aefd7..2e9c3a521 100644
--- a/packages/ui/src/theme/resolve.ts
+++ b/packages/ui/src/theme/resolve.ts
@@ -240,6 +240,7 @@ export function resolveThemeVariant(variant: ThemeVariant, isDark: boolean): Res
tokens["icon-diff-add-active"] = diffAdd[isDark ? 10 : 11]
tokens["icon-diff-delete-base"] = diffDelete[isDark ? 8 : 9]
tokens["icon-diff-delete-hover"] = diffDelete[isDark ? 9 : 10]
+ tokens["icon-diff-modified-base"] = tokens["icon-warning-base"]
tokens["syntax-comment"] = "var(--text-weak)"
tokens["syntax-regexp"] = "var(--text-base)"