From f2bf6202061228584663eebb2fad95f9f494f3e6 Mon Sep 17 00:00:00 2001 From: David Hill Date: Tue, 27 Jan 2026 17:16:01 +0000 Subject: [PATCH] fix(app): highlight selected change Track clicked file in the Changes tree and apply selection styling to the matching review diff. --- packages/app/src/components/file-tree.tsx | 5 +++- packages/app/src/pages/session.tsx | 29 ++++++++++++++++++- packages/ui/src/components/button.css | 9 +++++- packages/ui/src/components/session-review.css | 7 +++++ packages/ui/src/components/session-review.tsx | 2 ++ packages/ui/src/styles/theme.css | 2 ++ packages/ui/src/theme/resolve.ts | 1 + 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: { <> - Ignored + Ignored @@ -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() 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() { +
@@ -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)} /> 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} > 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)"