diff --git a/ui/src/components/agents/AgentGitPanel.tsx b/ui/src/components/agents/AgentGitPanel.tsx index 270ed420..6b27004c 100644 --- a/ui/src/components/agents/AgentGitPanel.tsx +++ b/ui/src/components/agents/AgentGitPanel.tsx @@ -165,22 +165,50 @@ function PanelResizeHandle({ ) } +// Neutral filename foreground from the pierre Shiki themes (pierre-light / +// pierre-dark sidebar foreground). The tree tints filename text by git status, +// so feeding this keeps names neutral grey/white instead of accent-blue. +const TREE_FILE_FG = "light-dark(#525252, #a3a3a3)" + +// Selected rows must read as high-contrast (white in dark, near-black in light) +// while the rest stay neutral. The built-in git-status content color outranks +// the selection color by specificity, so override it from the `unsafe` layer. +export const TREE_UNSAFE_CSS = ` + [data-item-selected="true"] [data-item-section="content"] { + color: var(--trees-selected-fg); + } + + /* On click a row is focus-ringed a frame before it's marked selected, which + * flashes the accent outline. Pointer focus doesn't match :focus-visible, so + * drop the ring there; keyboard navigation keeps it. */ + [data-item-focused="true"]:not(:focus-visible)::before { + outline-color: transparent; + } +` + export function treeThemeStyle(): React.CSSProperties { return { "--trees-theme-sidebar-bg": "var(--ui-surface)", "--trees-theme-sidebar-fg": "var(--ui-text)", "--trees-theme-sidebar-border": "var(--ui-border)", "--trees-theme-sidebar-header-fg": "var(--ui-text-dim)", - "--trees-theme-list-hover-bg": "var(--ui-panel-2)", - "--trees-theme-list-active-selection-bg": "var(--ui-accent-bubble)", + "--trees-theme-list-hover-bg": + "color-mix(in oklab, var(--ui-accent) 10%, transparent)", + "--trees-theme-list-active-selection-bg": + "color-mix(in oklab, var(--ui-accent) 22%, transparent)", "--trees-theme-list-active-selection-fg": "var(--ui-text)", + "--trees-selected-focused-border-color-override": "transparent", "--trees-theme-input-bg": "var(--ui-panel)", + "--trees-theme-input-fg": "var(--ui-text)", "--trees-theme-input-border": "var(--ui-border)", "--trees-theme-focus-ring": "var(--ui-accent)", "--trees-theme-scrollbar-thumb": "var(--ui-border)", - "--trees-theme-git-added-fg": "var(--ui-success)", - "--trees-theme-git-deleted-fg": "var(--ui-danger)", - "--trees-theme-git-modified-fg": "var(--ui-accent)", + "--trees-theme-git-added-fg": TREE_FILE_FG, + "--trees-theme-git-modified-fg": TREE_FILE_FG, + "--trees-theme-git-deleted-fg": TREE_FILE_FG, + "--trees-theme-git-renamed-fg": TREE_FILE_FG, + "--trees-theme-git-untracked-fg": TREE_FILE_FG, + "--trees-theme-git-ignored-fg": "var(--ui-text-dim)", } as React.CSSProperties } @@ -557,7 +585,8 @@ function FileTreeExplorer({ initialExpansion: "open", flattenEmptyDirectories: true, search: true, - icons: "standard", + icons: "complete", + unsafeCSS: TREE_UNSAFE_CSS, }) useEffect(() => { diff --git a/ui/src/components/agents/ReviewSidebar.tsx b/ui/src/components/agents/ReviewSidebar.tsx index edc11abf..fbe4508e 100644 --- a/ui/src/components/agents/ReviewSidebar.tsx +++ b/ui/src/components/agents/ReviewSidebar.tsx @@ -5,10 +5,14 @@ import { useFileTreeSelection, } from "@pierre/trees/react" -import type { GitStatus, GitStatusEntry } from "@pierre/trees" +import type { + FileTreeDirectoryHandle, + GitStatus, + GitStatusEntry, +} from "@pierre/trees" import type { ReviewDiffFile } from "@/lib/api" import { Skeleton } from "@/components/ui/skeleton" -import { treeThemeStyle } from "@/components/agents/AgentGitPanel" +import { TREE_UNSAFE_CSS, treeThemeStyle } from "@/components/agents/AgentGitPanel" function reviewFileGitStatus(status: ReviewDiffFile["status"]): GitStatus { if (status === "removed") return "deleted" @@ -100,9 +104,10 @@ function ReviewFileTreeExplorer({ const { model } = useFileTree({ paths, gitStatus, - initialExpansion: "open", flattenEmptyDirectories: true, - icons: "standard", + density: "default", + icons: "complete", + unsafeCSS: TREE_UNSAFE_CSS, }) useEffect(() => { @@ -120,9 +125,13 @@ function ReviewFileTreeExplorer({ }, [selection, onSelect]) useEffect(() => { - if (selected) { - model.scrollToPath(selected, { focus: false }) + if (!selected) return + const segments = selected.split("/") + for (let depth = 1; depth < segments.length; depth += 1) { + const item = model.getItem(segments.slice(0, depth).join("/")) + if (item?.isDirectory()) (item as FileTreeDirectoryHandle).expand() } + model.scrollToPath(selected, { focus: false }) }, [model, selected]) return (