feat: relocate review chunks and embed review in git panel (#1590)

* feat: relocate review chunks and embed review in git panel

Move the AI-sorted review chunk navigator out of the global left sidebar
and into the review page's own main panel (sidebar keeps the thread list),
and surface the review main body inside the agent thread's Git > Review
sub-tab with an expand link to the full review page.

The review main body + side panel + helpers are extracted into a shared
ReviewMainBody component with "full" and "embedded" variants.

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>

* chore: move ReviewTab into its own file

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
Caroline di Vittorio 2026-06-23 12:27:51 -07:00 • committed by GitHub
parent 3992d3ef5d
commit 63e4c2baac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 2087 additions and 2008 deletions

View file

@ -24,6 +24,7 @@ import type { AgentThread, Message } from "@/lib/agents/types"
import type { ThreadPrDiffFile } from "@/lib/agents/api"
import type { ChangedFileSummaryItem } from "@/components/agents/messages"
import { useAgentThreadPrDiff } from "@/lib/agents/queries"
import { ReviewTab } from "@/components/agents/ReviewTab"
import { buttonVariants } from "@/components/ui/button"
import {
DIFF_VIRTUALIZER_CONFIG,
@ -554,7 +555,9 @@ export function AgentGitPanel({ thread, messages }: AgentGitPanelProps) {
</div>
<div className="flex min-h-0 flex-1">
{tab === "diff" && files.length > 0 ? (
{tab === "review" ? (
<ReviewTab thread={thread} />
) : tab === "diff" && files.length > 0 ? (
<WorkerPoolContextProvider
poolOptions={DIFF_WORKER_POOL_OPTIONS}
highlighterOptions={DIFF_WORKER_HIGHLIGHTER_OPTIONS}
@ -585,15 +588,18 @@ export function AgentGitPanel({ thread, messages }: AgentGitPanelProps) {
</div>
)}
{fullScreen && !isMobile && files.length > 0 && (
<div className="w-72 shrink-0 border-l border-[var(--ui-border)] bg-[var(--ui-surface)]">
<FileTreeExplorer
files={files}
selectedTreePath={selectedTreePath}
onSelect={setSelectedTreePath}
/>
</div>
)}
{tab === "diff" &&
fullScreen &&
!isMobile &&
files.length > 0 && (
<div className="w-72 shrink-0 border-l border-[var(--ui-border)] bg-[var(--ui-surface)]">
<FileTreeExplorer
files={files}
selectedTreePath={selectedTreePath}
onSelect={setSelectedTreePath}
/>
</div>
)}
</div>
</>
)}

View file

@ -27,11 +27,6 @@ import type { SessionUser } from "@/lib/api"
import type { AgentSource, AgentThread } from "@/lib/agents/types"
import type { SidebarLayout } from "@/components/sidebar-layout"
import { SidebarUserMenu } from "@/components/SidebarUserMenu"
import {
ReviewSidebarPanel,
ReviewSidebarProvider,
useReviewSidebarData,
} from "@/components/agents/ReviewSidebar"
import { Button } from "@/components/ui/button"
import {
SidebarCollapseButton,
@ -114,7 +109,6 @@ export function AgentsSidebar({
useSeedAgentThreadDetails(visibleThreads, activeThreadId)
useRunCompletionNotifier(visibleThreads, activeThreadId)
const groups = groupThreads(activeThreads)
const reviewSidebar = useReviewSidebarData()
return (
<SidebarFrame
@ -164,44 +158,40 @@ export function AgentsSidebar({
})}
</nav>
{reviewSidebar ? (
<ReviewSidebarPanel data={reviewSidebar} />
) : (
<div className="min-h-0 flex-1 overflow-y-auto px-2 pb-2">
<ThreadGroup
label="Today"
threads={groups.today}
activeThreadId={activeThreadId}
onNavigate={layout.closeOnMobile}
/>
<ThreadGroup
label="Last 7 days"
threads={groups.last7}
activeThreadId={activeThreadId}
onNavigate={layout.closeOnMobile}
defaultCollapsed
/>
<ThreadGroup
label="Last 30 days"
threads={groups.last30}
activeThreadId={activeThreadId}
onNavigate={layout.closeOnMobile}
defaultCollapsed
/>
<ThreadGroup
label="Older"
threads={groups.older}
activeThreadId={activeThreadId}
onNavigate={layout.closeOnMobile}
/>
<ResolvedThreadGroup
threads={resolvedThreads}
hasMore={resolvedHasMore}
activeThreadId={activeThreadId}
onNavigate={layout.closeOnMobile}
/>
</div>
)}
<div className="min-h-0 flex-1 overflow-y-auto px-2 pb-2">
<ThreadGroup
label="Today"
threads={groups.today}
activeThreadId={activeThreadId}
onNavigate={layout.closeOnMobile}
/>
<ThreadGroup
label="Last 7 days"
threads={groups.last7}
activeThreadId={activeThreadId}
onNavigate={layout.closeOnMobile}
defaultCollapsed
/>
<ThreadGroup
label="Last 30 days"
threads={groups.last30}
activeThreadId={activeThreadId}
onNavigate={layout.closeOnMobile}
defaultCollapsed
/>
<ThreadGroup
label="Older"
threads={groups.older}
activeThreadId={activeThreadId}
onNavigate={layout.closeOnMobile}
/>
<ResolvedThreadGroup
threads={resolvedThreads}
hasMore={resolvedHasMore}
activeThreadId={activeThreadId}
onNavigate={layout.closeOnMobile}
/>
</div>
<div className="p-2">
<SidebarUserMenu user={user} showSettingsLink />
@ -546,17 +536,15 @@ export function AgentsShell({
}) {
const layout = useSidebarLayout()
return (
<ReviewSidebarProvider>
<SidebarLayoutProvider value={layout}>
<div className="agents-ui flex h-svh overflow-hidden bg-[var(--ui-bg)]">
<AgentsSidebar
user={user}
activeThreadId={activeThreadId}
layout={layout}
/>
<div className="flex min-w-0 flex-1">{children}</div>
</div>
</SidebarLayoutProvider>
</ReviewSidebarProvider>
<SidebarLayoutProvider value={layout}>
<div className="agents-ui flex h-svh overflow-hidden bg-[var(--ui-bg)]">
<AgentsSidebar
user={user}
activeThreadId={activeThreadId}
layout={layout}
/>
<div className="flex min-w-0 flex-1">{children}</div>
</div>
</SidebarLayoutProvider>
)
}

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,4 @@
import {
createContext,
memo,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from "react"
import { memo, useCallback, useEffect, useMemo, useState } from "react"
import {
FileTree,
useFileTree,
@ -27,7 +19,10 @@ import type {
import type { ReviewDiffFile } from "@/lib/api"
import { Markdown } from "@/components/agents/ported"
import { Skeleton } from "@/components/ui/skeleton"
import { TREE_UNSAFE_CSS, treeThemeStyle } from "@/components/agents/AgentGitPanel"
import {
TREE_UNSAFE_CSS,
treeThemeStyle,
} from "@/components/agents/AgentGitPanel"
import { cn } from "@/lib/utils"
function reviewFileGitStatus(status: ReviewDiffFile["status"]): GitStatus {
@ -61,38 +56,6 @@ export interface ReviewSidebarData {
onSelectGroup: (index: 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])
}
export function ReviewSidebarPanel({ data }: { data: ReviewSidebarData }) {
const hasGroups = data.groups !== null && data.groups.length > 0
const showAi = data.view === "ai" && hasGroups

View file

@ -0,0 +1,72 @@
import { useNavigate } from "@tanstack/react-router"
import { useQuery } from "@tanstack/react-query"
import type { AgentThread } from "@/lib/agents/types"
import { ReviewMainBody } from "@/components/agents/ReviewMainBody"
import { api } from "@/lib/api"
// The git panel's "Review" sub-tab: the PR's Open SWE review rendered inline (no
// side panel / chat), with an expand affordance that opens the full review page.
export function ReviewTab({ thread }: { thread: AgentThread }) {
const navigate = useNavigate()
const pr = thread.pr
const [owner, repo] = thread.repoFullName.split("/")
const number = pr?.number ?? null
const enabled = Boolean(owner && repo && number !== null)
const detail = useQuery({
queryKey: ["review", owner, repo, number],
queryFn: () =>
api.getReview(owner as string, repo as string, number as number),
enabled,
refetchInterval: (query) =>
query.state.data?.status === "running" ? 5000 : false,
})
const diff = useQuery({
queryKey: ["reviewDiff", owner, repo, number],
queryFn: () =>
api.getReviewDiff(owner as string, repo as string, number as number),
enabled,
})
if (!enabled) {
return (
<div className="min-h-0 flex-1 overflow-y-auto p-6 text-center text-xs text-[var(--ui-text-dim)]">
Open a pull request to see its review here.
</div>
)
}
if (detail.isLoading) {
return (
<div className="min-h-0 flex-1 overflow-y-auto p-6 text-center text-xs text-[var(--ui-text-dim)]">
Loading review…
</div>
)
}
if (detail.error || !detail.data) {
return (
<div className="min-h-0 flex-1 overflow-y-auto p-6 text-center text-xs text-[var(--ui-text-dim)]">
No review for this pull request yet.
</div>
)
}
return (
<ReviewMainBody
key={detail.data.head_sha}
detail={detail.data}
diffFiles={diff.data?.files ?? null}
variant="embedded"
onExpand={() =>
navigate({
to: "/agents/reviews/$owner/$repo/$number",
params: {
owner: owner as string,
repo: repo as string,
number: String(number),
},
})
}
/>
)
}

File diff suppressed because it is too large Load diff