open-swe/ui/src/components/agents/ReviewSidebar.tsx

397 lines
11 KiB
TypeScript
Raw Normal View History

import { createContext, useContext, useEffect, useMemo, useState } from "react"
import {
FileTree,
useFileTree,
useFileTreeSelection,
} from "@pierre/trees/react"
feat: AI-sorted PR review view with diff grouping (#1544) * feat: AI-sorted PR review view with diff grouping Group a PR's changed files into a logical top-to-bottom walkthrough via a best-effort structured-output LLM pass, kicked off concurrently with the reviewer run (~0 added latency) and persisted on reviewer thread metadata. Results render in the review UI behind an AI sorted / file tree toggle that persists across PRs; the view falls back to the file tree when groups are absent or stale. Adds a grouping-model team default (inherits the Reviewer subagent model when unset) and the admin RolePicker for it. * feat(reviews): richer AI-sorted explanations + sidebar polish Sidebar group rows get Devin-style spacing (dividers, padding), a per-group file list (click a file to jump to its diff), inline-code chips in titles, and an accent Read explanation link. Group explanations are now rich markdown: the grouping prompt feeds per-hunk line ranges and asks for inline code, a short code block, and [path:line](#loc=...) references. The Markdown renderer turns those #loc= links into in-page buttons that scroll the diff to the hunk and highlight the range, reusing the existing selectedLines path. * fix(reviews): drop stale diff groups from the AI-sorted view When groups were generated for a previous head, a persisted "ai" view in localStorage still rendered the outdated walkthrough. groupedView now returns null on diff_groups_stale, so the file-tree fallback is used and the view toggle hides until fresh groups arrive. --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-16 13:59:37 -07:00
import {
CaretRightIcon,
ListBulletsIcon,
TreeViewIcon,
} from "@phosphor-icons/react"
import type { ReactNode } from "react"
import type {
FileTreeDirectoryHandle,
GitStatus,
GitStatusEntry,
} from "@pierre/trees"
import type { ReviewDiffFile } from "@/lib/api"
feat: AI-sorted PR review view with diff grouping (#1544) * feat: AI-sorted PR review view with diff grouping Group a PR's changed files into a logical top-to-bottom walkthrough via a best-effort structured-output LLM pass, kicked off concurrently with the reviewer run (~0 added latency) and persisted on reviewer thread metadata. Results render in the review UI behind an AI sorted / file tree toggle that persists across PRs; the view falls back to the file tree when groups are absent or stale. Adds a grouping-model team default (inherits the Reviewer subagent model when unset) and the admin RolePicker for it. * feat(reviews): richer AI-sorted explanations + sidebar polish Sidebar group rows get Devin-style spacing (dividers, padding), a per-group file list (click a file to jump to its diff), inline-code chips in titles, and an accent Read explanation link. Group explanations are now rich markdown: the grouping prompt feeds per-hunk line ranges and asks for inline code, a short code block, and [path:line](#loc=...) references. The Markdown renderer turns those #loc= links into in-page buttons that scroll the diff to the hunk and highlight the range, reusing the existing selectedLines path. * fix(reviews): drop stale diff groups from the AI-sorted view When groups were generated for a previous head, a persisted "ai" view in localStorage still rendered the outdated walkthrough. groupedView now returns null on diff_groups_stale, so the file-tree fallback is used and the view toggle hides until fresh groups arrive. --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-16 13:59:37 -07:00
import { Markdown } from "@/components/agents/ported"
import { Skeleton } from "@/components/ui/skeleton"
import { TREE_UNSAFE_CSS, treeThemeStyle } from "@/components/agents/AgentGitPanel"
feat: AI-sorted PR review view with diff grouping (#1544) * feat: AI-sorted PR review view with diff grouping Group a PR's changed files into a logical top-to-bottom walkthrough via a best-effort structured-output LLM pass, kicked off concurrently with the reviewer run (~0 added latency) and persisted on reviewer thread metadata. Results render in the review UI behind an AI sorted / file tree toggle that persists across PRs; the view falls back to the file tree when groups are absent or stale. Adds a grouping-model team default (inherits the Reviewer subagent model when unset) and the admin RolePicker for it. * feat(reviews): richer AI-sorted explanations + sidebar polish Sidebar group rows get Devin-style spacing (dividers, padding), a per-group file list (click a file to jump to its diff), inline-code chips in titles, and an accent Read explanation link. Group explanations are now rich markdown: the grouping prompt feeds per-hunk line ranges and asks for inline code, a short code block, and [path:line](#loc=...) references. The Markdown renderer turns those #loc= links into in-page buttons that scroll the diff to the hunk and highlight the range, reusing the existing selectedLines path. * fix(reviews): drop stale diff groups from the AI-sorted view When groups were generated for a previous head, a persisted "ai" view in localStorage still rendered the outdated walkthrough. groupedView now returns null on diff_groups_stale, so the file-tree fallback is used and the view toggle hides until fresh groups arrive. --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-16 13:59:37 -07:00
import { cn } from "@/lib/utils"
function reviewFileGitStatus(status: ReviewDiffFile["status"]): GitStatus {
if (status === "removed") return "deleted"
if (status === "added") return "added"
if (status === "renamed") return "renamed"
return "modified"
}
feat: AI-sorted PR review view with diff grouping (#1544) * feat: AI-sorted PR review view with diff grouping Group a PR's changed files into a logical top-to-bottom walkthrough via a best-effort structured-output LLM pass, kicked off concurrently with the reviewer run (~0 added latency) and persisted on reviewer thread metadata. Results render in the review UI behind an AI sorted / file tree toggle that persists across PRs; the view falls back to the file tree when groups are absent or stale. Adds a grouping-model team default (inherits the Reviewer subagent model when unset) and the admin RolePicker for it. * feat(reviews): richer AI-sorted explanations + sidebar polish Sidebar group rows get Devin-style spacing (dividers, padding), a per-group file list (click a file to jump to its diff), inline-code chips in titles, and an accent Read explanation link. Group explanations are now rich markdown: the grouping prompt feeds per-hunk line ranges and asks for inline code, a short code block, and [path:line](#loc=...) references. The Markdown renderer turns those #loc= links into in-page buttons that scroll the diff to the hunk and highlight the range, reusing the existing selectedLines path. * fix(reviews): drop stale diff groups from the AI-sorted view When groups were generated for a previous head, a persisted "ai" view in localStorage still rendered the outdated walkthrough. groupedView now returns null on diff_groups_stale, so the file-tree fallback is used and the view toggle hides until fresh groups arrive. --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-16 13:59:37 -07:00
export type ReviewSidebarView = "ai" | "files"
export interface ReviewSidebarGroup {
index: number
title: string
summary: string
additions: number
deletions: number
fileCount: number
files: Array<string>
}
export interface ReviewSidebarData {
title: string
files: Array<ReviewDiffFile> | null
selected: string | null
viewed: Set<string>
onSelect: (path: string) => void
feat: AI-sorted PR review view with diff grouping (#1544) * feat: AI-sorted PR review view with diff grouping Group a PR's changed files into a logical top-to-bottom walkthrough via a best-effort structured-output LLM pass, kicked off concurrently with the reviewer run (~0 added latency) and persisted on reviewer thread metadata. Results render in the review UI behind an AI sorted / file tree toggle that persists across PRs; the view falls back to the file tree when groups are absent or stale. Adds a grouping-model team default (inherits the Reviewer subagent model when unset) and the admin RolePicker for it. * feat(reviews): richer AI-sorted explanations + sidebar polish Sidebar group rows get Devin-style spacing (dividers, padding), a per-group file list (click a file to jump to its diff), inline-code chips in titles, and an accent Read explanation link. Group explanations are now rich markdown: the grouping prompt feeds per-hunk line ranges and asks for inline code, a short code block, and [path:line](#loc=...) references. The Markdown renderer turns those #loc= links into in-page buttons that scroll the diff to the hunk and highlight the range, reusing the existing selectedLines path. * fix(reviews): drop stale diff groups from the AI-sorted view When groups were generated for a previous head, a persisted "ai" view in localStorage still rendered the outdated walkthrough. groupedView now returns null on diff_groups_stale, so the file-tree fallback is used and the view toggle hides until fresh groups arrive. --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-16 13:59:37 -07:00
groups: Array<ReviewSidebarGroup> | null
view: ReviewSidebarView
onViewChange: (view: ReviewSidebarView) => void
onSelectGroup: (index: number) => void
onLocationClick?: (file: string, startLine: number, endLine: number) => void
}
const ReviewSidebarContext = createContext<{
data: ReviewSidebarData | null
setData: (data: ReviewSidebarData | null) => void
} | null>(null)
export function ReviewSidebarProvider({
children,
}: {
children: React.ReactNode
}) {
const [data, setData] = useState<ReviewSidebarData | null>(null)
const value = useMemo(() => ({ data, setData }), [data])
return (
<ReviewSidebarContext.Provider value={value}>
{children}
</ReviewSidebarContext.Provider>
)
}
export function useReviewSidebarData(): ReviewSidebarData | null {
return useContext(ReviewSidebarContext)?.data ?? null
}
export function useRegisterReviewSidebar(data: ReviewSidebarData) {
const setData = useContext(ReviewSidebarContext)?.setData
useEffect(() => {
if (!setData) return
setData(data)
return () => setData(null)
}, [setData, data])
}
feat: AI-sorted PR review view with diff grouping (#1544) * feat: AI-sorted PR review view with diff grouping Group a PR's changed files into a logical top-to-bottom walkthrough via a best-effort structured-output LLM pass, kicked off concurrently with the reviewer run (~0 added latency) and persisted on reviewer thread metadata. Results render in the review UI behind an AI sorted / file tree toggle that persists across PRs; the view falls back to the file tree when groups are absent or stale. Adds a grouping-model team default (inherits the Reviewer subagent model when unset) and the admin RolePicker for it. * feat(reviews): richer AI-sorted explanations + sidebar polish Sidebar group rows get Devin-style spacing (dividers, padding), a per-group file list (click a file to jump to its diff), inline-code chips in titles, and an accent Read explanation link. Group explanations are now rich markdown: the grouping prompt feeds per-hunk line ranges and asks for inline code, a short code block, and [path:line](#loc=...) references. The Markdown renderer turns those #loc= links into in-page buttons that scroll the diff to the hunk and highlight the range, reusing the existing selectedLines path. * fix(reviews): drop stale diff groups from the AI-sorted view When groups were generated for a previous head, a persisted "ai" view in localStorage still rendered the outdated walkthrough. groupedView now returns null on diff_groups_stale, so the file-tree fallback is used and the view toggle hides until fresh groups arrive. --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-16 13:59:37 -07:00
export function ReviewSidebarPanel({ data }: { data: ReviewSidebarData }) {
const hasGroups = data.groups !== null && data.groups.length > 0
const showAi = data.view === "ai" && hasGroups
return (
<div className="flex min-h-0 flex-1 flex-col pb-2">
feat: AI-sorted PR review view with diff grouping (#1544) * feat: AI-sorted PR review view with diff grouping Group a PR's changed files into a logical top-to-bottom walkthrough via a best-effort structured-output LLM pass, kicked off concurrently with the reviewer run (~0 added latency) and persisted on reviewer thread metadata. Results render in the review UI behind an AI sorted / file tree toggle that persists across PRs; the view falls back to the file tree when groups are absent or stale. Adds a grouping-model team default (inherits the Reviewer subagent model when unset) and the admin RolePicker for it. * feat(reviews): richer AI-sorted explanations + sidebar polish Sidebar group rows get Devin-style spacing (dividers, padding), a per-group file list (click a file to jump to its diff), inline-code chips in titles, and an accent Read explanation link. Group explanations are now rich markdown: the grouping prompt feeds per-hunk line ranges and asks for inline code, a short code block, and [path:line](#loc=...) references. The Markdown renderer turns those #loc= links into in-page buttons that scroll the diff to the hunk and highlight the range, reusing the existing selectedLines path. * fix(reviews): drop stale diff groups from the AI-sorted view When groups were generated for a previous head, a persisted "ai" view in localStorage still rendered the outdated walkthrough. groupedView now returns null on diff_groups_stale, so the file-tree fallback is used and the view toggle hides until fresh groups arrive. --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-16 13:59:37 -07:00
<div className="flex items-center justify-between gap-2 px-4 py-1">
<span className="text-[10px] font-medium tracking-wide text-[var(--ui-text-dim)] uppercase">
feat: AI-sorted PR review view with diff grouping (#1544) * feat: AI-sorted PR review view with diff grouping Group a PR's changed files into a logical top-to-bottom walkthrough via a best-effort structured-output LLM pass, kicked off concurrently with the reviewer run (~0 added latency) and persisted on reviewer thread metadata. Results render in the review UI behind an AI sorted / file tree toggle that persists across PRs; the view falls back to the file tree when groups are absent or stale. Adds a grouping-model team default (inherits the Reviewer subagent model when unset) and the admin RolePicker for it. * feat(reviews): richer AI-sorted explanations + sidebar polish Sidebar group rows get Devin-style spacing (dividers, padding), a per-group file list (click a file to jump to its diff), inline-code chips in titles, and an accent Read explanation link. Group explanations are now rich markdown: the grouping prompt feeds per-hunk line ranges and asks for inline code, a short code block, and [path:line](#loc=...) references. The Markdown renderer turns those #loc= links into in-page buttons that scroll the diff to the hunk and highlight the range, reusing the existing selectedLines path. * fix(reviews): drop stale diff groups from the AI-sorted view When groups were generated for a previous head, a persisted "ai" view in localStorage still rendered the outdated walkthrough. groupedView now returns null on diff_groups_stale, so the file-tree fallback is used and the view toggle hides until fresh groups arrive. --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-16 13:59:37 -07:00
{data.title}
</span>
{hasGroups && (
<ReviewViewToggle view={data.view} onChange={data.onViewChange} />
)}
</div>
feat: AI-sorted PR review view with diff grouping (#1544) * feat: AI-sorted PR review view with diff grouping Group a PR's changed files into a logical top-to-bottom walkthrough via a best-effort structured-output LLM pass, kicked off concurrently with the reviewer run (~0 added latency) and persisted on reviewer thread metadata. Results render in the review UI behind an AI sorted / file tree toggle that persists across PRs; the view falls back to the file tree when groups are absent or stale. Adds a grouping-model team default (inherits the Reviewer subagent model when unset) and the admin RolePicker for it. * feat(reviews): richer AI-sorted explanations + sidebar polish Sidebar group rows get Devin-style spacing (dividers, padding), a per-group file list (click a file to jump to its diff), inline-code chips in titles, and an accent Read explanation link. Group explanations are now rich markdown: the grouping prompt feeds per-hunk line ranges and asks for inline code, a short code block, and [path:line](#loc=...) references. The Markdown renderer turns those #loc= links into in-page buttons that scroll the diff to the hunk and highlight the range, reusing the existing selectedLines path. * fix(reviews): drop stale diff groups from the AI-sorted view When groups were generated for a previous head, a persisted "ai" view in localStorage still rendered the outdated walkthrough. groupedView now returns null on diff_groups_stale, so the file-tree fallback is used and the view toggle hides until fresh groups arrive. --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-16 13:59:37 -07:00
{showAi ? (
<ReviewGroupList
groups={data.groups ?? []}
onSelectGroup={data.onSelectGroup}
onSelectFile={data.onSelect}
onLocationClick={data.onLocationClick}
/>
) : !data.files ? (
<div className="px-4 pt-1">
<Skeleton className="h-40 w-full" />
</div>
) : (
<ReviewFileTreeExplorer
files={data.files}
selected={data.selected}
onSelect={data.onSelect}
/>
)}
</div>
)
}
feat: AI-sorted PR review view with diff grouping (#1544) * feat: AI-sorted PR review view with diff grouping Group a PR's changed files into a logical top-to-bottom walkthrough via a best-effort structured-output LLM pass, kicked off concurrently with the reviewer run (~0 added latency) and persisted on reviewer thread metadata. Results render in the review UI behind an AI sorted / file tree toggle that persists across PRs; the view falls back to the file tree when groups are absent or stale. Adds a grouping-model team default (inherits the Reviewer subagent model when unset) and the admin RolePicker for it. * feat(reviews): richer AI-sorted explanations + sidebar polish Sidebar group rows get Devin-style spacing (dividers, padding), a per-group file list (click a file to jump to its diff), inline-code chips in titles, and an accent Read explanation link. Group explanations are now rich markdown: the grouping prompt feeds per-hunk line ranges and asks for inline code, a short code block, and [path:line](#loc=...) references. The Markdown renderer turns those #loc= links into in-page buttons that scroll the diff to the hunk and highlight the range, reusing the existing selectedLines path. * fix(reviews): drop stale diff groups from the AI-sorted view When groups were generated for a previous head, a persisted "ai" view in localStorage still rendered the outdated walkthrough. groupedView now returns null on diff_groups_stale, so the file-tree fallback is used and the view toggle hides until fresh groups arrive. --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-16 13:59:37 -07:00
function ReviewViewToggle({
view,
onChange,
}: {
view: ReviewSidebarView
onChange: (view: ReviewSidebarView) => void
}) {
return (
<div className="flex items-center gap-0.5 rounded-md border border-[var(--ui-border)] p-0.5">
<ReviewViewToggleButton
active={view === "ai"}
label="AI sorted"
onClick={() => onChange("ai")}
>
<ListBulletsIcon className="size-3.5" />
</ReviewViewToggleButton>
<ReviewViewToggleButton
active={view === "files"}
label="File tree"
onClick={() => onChange("files")}
>
<TreeViewIcon className="size-3.5" />
</ReviewViewToggleButton>
</div>
)
}
function ReviewViewToggleButton({
active,
label,
onClick,
children,
}: {
active: boolean
label: string
onClick: () => void
children: React.ReactNode
}) {
return (
<button
type="button"
onClick={onClick}
aria-label={label}
aria-pressed={active}
title={label}
className={cn(
"flex size-5 items-center justify-center rounded text-[var(--ui-text-dim)] transition-colors",
active
? "bg-[var(--ui-sidebar-hover)] text-[var(--ui-text)]"
: "hover:text-[var(--ui-text)]"
)}
>
{children}
</button>
)
}
function ReviewGroupList({
groups,
onSelectGroup,
onSelectFile,
onLocationClick,
}: {
groups: Array<ReviewSidebarGroup>
onSelectGroup: (index: number) => void
onSelectFile: (path: string) => void
onLocationClick?: (file: string, startLine: number, endLine: number) => void
}) {
return (
<div className="min-h-0 flex-1 divide-y divide-[var(--ui-border-subtle)] overflow-y-auto">
{groups.map((group) => (
<ReviewGroupRow
key={group.index}
group={group}
onSelect={() => onSelectGroup(group.index)}
onSelectFile={onSelectFile}
onLocationClick={onLocationClick}
/>
))}
</div>
)
}
function splitPath(path: string): { dir: string; base: string } {
const idx = path.lastIndexOf("/")
if (idx === -1) return { dir: "", base: path }
return { dir: path.slice(0, idx), base: path.slice(idx + 1) }
}
// Render a title with `backtick`-delimited spans as inline code chips, matching
// the Markdown component's inline-code styling, without pulling in the full
// block renderer for a single line.
function renderInlineCode(text: string): Array<ReactNode> {
return text.split(/(`[^`]+`)/g).map((part, i) => {
if (part.length >= 2 && part.startsWith("`") && part.endsWith("`")) {
return (
<code
key={i}
className="rounded bg-[var(--ui-panel-2)] px-1 py-0.5 font-mono text-[0.9em] text-[var(--ui-accent)]"
>
{part.slice(1, -1)}
</code>
)
}
return <span key={i}>{part}</span>
})
}
function ReviewGroupRow({
group,
onSelect,
onSelectFile,
onLocationClick,
}: {
group: ReviewSidebarGroup
onSelect: () => void
onSelectFile: (path: string) => void
onLocationClick?: (file: string, startLine: number, endLine: number) => void
}) {
const [expanded, setExpanded] = useState(false)
return (
<div className="px-3 py-3 transition-colors hover:bg-[var(--ui-sidebar-hover)]">
<button
type="button"
onClick={onSelect}
className="flex w-full items-start gap-2 text-left"
>
<span className="mt-0.5 flex size-5 shrink-0 items-center justify-center rounded bg-[var(--ui-panel-2)] text-[11px] font-medium text-[var(--ui-text-dim)]">
{group.index}
</span>
<span className="min-w-0 flex-1">
<span className="block text-xs leading-5 font-medium text-[var(--ui-text)]">
{renderInlineCode(group.title)}
</span>
<span className="mt-1 flex flex-wrap items-center gap-1.5 text-[11px] text-[var(--ui-text-dim)]">
<span>
{group.fileCount} file{group.fileCount === 1 ? "" : "s"}
</span>
{group.additions > 0 && (
<span className="text-emerald-500">+{group.additions}</span>
)}
{group.deletions > 0 && (
<span className="text-red-500">-{group.deletions}</span>
)}
</span>
</span>
</button>
{group.files.length > 0 && (
<div className="mt-2 space-y-0.5 pl-7">
{group.files.map((path) => {
const { dir, base } = splitPath(path)
return (
<button
key={path}
type="button"
onClick={() => onSelectFile(path)}
title={path}
className="flex w-full items-baseline gap-1.5 text-left text-[11px] hover:text-[var(--ui-accent)]"
>
<span className="shrink-0 font-medium text-[var(--ui-text-muted)]">
{base}
</span>
{dir && (
<span className="min-w-0 truncate text-[var(--ui-text-dim)]">
{dir}
</span>
)}
</button>
)
})}
</div>
)}
{group.summary && (
<div className="mt-2 pl-7">
<button
type="button"
onClick={() => setExpanded((value) => !value)}
className="inline-flex items-center gap-1 text-[11px] font-medium text-[var(--ui-accent)]"
>
<CaretRightIcon
className={cn(
"size-3 transition-transform",
expanded && "rotate-90"
)}
/>
Read explanation
</button>
{expanded && (
<div className="mt-1.5">
<Markdown
content={group.summary}
onLocationClick={onLocationClick}
/>
</div>
)}
</div>
)}
</div>
)
}
function ReviewFileTreeExplorer({
files,
selected,
onSelect,
}: {
files: Array<ReviewDiffFile>
selected: string | null
onSelect: (path: string) => void
}) {
const paths = useMemo(() => files.map((file) => file.path), [files])
const gitStatus = useMemo<Array<GitStatusEntry>>(
() =>
files.map((file) => ({
path: file.path,
status: reviewFileGitStatus(file.status),
})),
[files]
)
const { model } = useFileTree({
paths,
gitStatus,
flattenEmptyDirectories: true,
density: "default",
icons: "complete",
unsafeCSS: TREE_UNSAFE_CSS,
})
useEffect(() => {
model.resetPaths(paths)
}, [model, paths])
useEffect(() => {
model.setGitStatus(gitStatus)
}, [model, gitStatus])
const selection = useFileTreeSelection(model)
useEffect(() => {
const path = selection[0]
if (path) onSelect(path)
}, [selection, onSelect])
useEffect(() => {
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 (
<div className="min-h-0 flex-1">
<FileTree
model={model}
style={
{
height: "100%",
...treeThemeStyle(),
fix: Reviews tab — anchored finding card, paginated list, file tree truncation (#1507) * fix: Reviews tab — anchored finding card, paginated list, file tree truncation - Finding card now tracks the diff anchor while scrolling instead of staying frozen in the viewport; auto-hides when its diff card collapses (including collapse via mark-as-viewed) and on click outside - Checks section capped with max height + scroll - /reviews paginated (page size 20) with has_more, filtered to the current user's PRs by default with an All toggle; PR author login now stored in reviewer thread metadata - File tree truncation marker overlapped filenames because the sidebar bg was transparent; use the opaque sidebar color * fix: finding card tracks anchor 1:1 while scrolling Drop the vertical viewport clamp — it pinned the card at the clamp boundary while the highlighted lines kept scrolling, breaking the attachment. * fix: anchor finding card with Base UI popover Replace manual fixed-position tracking (laggy: setState per scroll frame) with a Popover anchored to the finding's diff row. Floating UI tracks the anchor outside React renders, so the card moves 1:1 with the content and scrolls out of view with it. Unanchored findings keep the fixed top-right card. * fix: lock finding card to diff scroll Replace the Base UI popover (async repositioning, paints a frame behind native scroll) with a card absolutely positioned inside the scroll container, so it scrolls with the diff in the same compositor frame. Scroll moves to the ReviewBody root, side panel becomes sticky. Position recomputes only on layout shifts via ResizeObserver. --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-11 17:52:20 -07:00
// Must stay opaque: the tree's truncation marker ("…") paints
// this color behind itself to hide the overflowing filename.
"--trees-theme-sidebar-bg": "var(--ui-sidebar)",
} as React.CSSProperties
}
/>
</div>
)
}