| author | |
| committer | |
| log | ac0d1f52a087c63819b2b032ac4535d18d3c23af |
| tree | 3d4e795d6eb01d3ff96d5093e21d0e61b3f8e8a1 |
| parent | d0ae0adc9923e51f7027ee16afedb14b3ae7779c |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Julius Marminge <julius0216@outlook.com>4 files changed, 148 insertions(+), 55 deletions(-)
apps/web/src/components/DiffPanel.tsx+13-31| ... | @@ -19,7 +19,6 @@ import { cn } from "~/lib/utils"; | ... | @@ -19,7 +19,6 @@ import { cn } from "~/lib/utils"; |
| 19 | import { readNativeApi } from "../nativeApi"; | 19 | import { readNativeApi } from "../nativeApi"; |
| 20 | import { resolvePathLinkTarget } from "../terminal-links"; | 20 | import { resolvePathLinkTarget } from "../terminal-links"; |
| 21 | import { parseDiffRouteSearch, stripDiffSearchParams } from "../diffRouteSearch"; | 21 | import { parseDiffRouteSearch, stripDiffSearchParams } from "../diffRouteSearch"; |
| 22 | import { isElectron } from "../env"; | ||
| 23 | import { useTheme } from "../hooks/useTheme"; | 22 | import { useTheme } from "../hooks/useTheme"; |
| 24 | import { buildPatchCacheKey } from "../lib/diffRendering"; | 23 | import { buildPatchCacheKey } from "../lib/diffRendering"; |
| 25 | import { resolveDiffThemeName } from "../lib/diffRendering"; | 24 | import { resolveDiffThemeName } from "../lib/diffRendering"; |
| ... | @@ -27,6 +26,7 @@ import { useTurnDiffSummaries } from "../hooks/useTurnDiffSummaries"; | ... | @@ -27,6 +26,7 @@ import { useTurnDiffSummaries } from "../hooks/useTurnDiffSummaries"; |
| 27 | import { useStore } from "../store"; | 26 | import { useStore } from "../store"; |
| 28 | import { useAppSettings } from "../appSettings"; | 27 | import { useAppSettings } from "../appSettings"; |
| 29 | import { formatShortTimestamp } from "../timestampFormat"; | 28 | import { formatShortTimestamp } from "../timestampFormat"; |
| 29 | import { DiffPanelLoadingState, DiffPanelShell, type DiffPanelMode } from "./DiffPanelShell"; | ||
| 30 | import { ToggleGroup, Toggle } from "./ui/toggle-group"; | 30 | import { ToggleGroup, Toggle } from "./ui/toggle-group"; |
| 31 | 31 | ||
| 32 | type DiffRenderMode = "stacked" | "split"; | 32 | type DiffRenderMode = "stacked" | "split"; |
| ... | @@ -152,7 +152,7 @@ function buildFileDiffRenderKey(fileDiff: FileDiffMetadata): string { | ... | @@ -152,7 +152,7 @@ function buildFileDiffRenderKey(fileDiff: FileDiffMetadata): string { |
| 152 | } | 152 | } |
| 153 | 153 | ||
| 154 | interface DiffPanelProps { | 154 | interface DiffPanelProps { |
| 155 | mode?: "inline" | "sheet" | "sidebar"; | 155 | mode?: DiffPanelMode; |
| 156 | } | 156 | } |
| 157 | 157 | ||
| 158 | export { DiffWorkerPoolProvider } from "./DiffWorkerPoolProvider"; | 158 | export { DiffWorkerPoolProvider } from "./DiffWorkerPoolProvider"; |
| ... | @@ -398,7 +398,6 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { | ... | @@ -398,7 +398,6 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 398 | selectedChip?.scrollIntoView({ block: "nearest", inline: "nearest", behavior: "smooth" }); | 398 | selectedChip?.scrollIntoView({ block: "nearest", inline: "nearest", behavior: "smooth" }); |
| 399 | }, [selectedTurn?.turnId, selectedTurnId]); | 399 | }, [selectedTurn?.turnId, selectedTurnId]); |
| 400 | 400 | ||
| 401 | const shouldUseDragRegion = isElectron && mode !== "sheet"; | ||
| 402 | const headerRow = ( | 401 | const headerRow = ( |
| 403 | <> | 402 | <> |
| 404 | <div className="relative min-w-0 flex-1 [-webkit-app-region:no-drag]"> | 403 | <div className="relative min-w-0 flex-1 [-webkit-app-region:no-drag]"> |
| ... | @@ -512,28 +511,9 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { | ... | @@ -512,28 +511,9 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 512 | </ToggleGroup> | 511 | </ToggleGroup> |
| 513 | </> | 512 | </> |
| 514 | ); | 513 | ); |
| 515 | const headerRowClassName = cn( | ||
| 516 | "flex items-center justify-between gap-2 px-4", | ||
| 517 | shouldUseDragRegion ? "drag-region h-[52px] border-b border-border" : "h-12", | ||
| 518 | ); | ||
| 519 | 514 | ||
| 520 | return ( | 515 | return ( |
| 521 | <div | 516 | <DiffPanelShell mode={mode} header={headerRow}> |
| 522 | className={cn( | ||
| 523 | "flex h-full min-w-0 flex-col bg-background", | ||
| 524 | mode === "inline" | ||
| 525 | ? "w-[42vw] min-w-[360px] max-w-[560px] shrink-0 border-l border-border" | ||
| 526 | : "w-full", | ||
| 527 | )} | ||
| 528 | > | ||
| 529 | {shouldUseDragRegion ? ( | ||
| 530 | <div className={headerRowClassName}>{headerRow}</div> | ||
| 531 | ) : ( | ||
| 532 | <div className="border-b border-border"> | ||
| 533 | <div className={headerRowClassName}>{headerRow}</div> | ||
| 534 | </div> | ||
| 535 | )} | ||
| 536 | |||
| 537 | {!activeThread ? ( | 517 | {!activeThread ? ( |
| 538 | <div className="flex flex-1 items-center justify-center px-5 text-center text-xs text-muted-foreground/70"> | 518 | <div className="flex flex-1 items-center justify-center px-5 text-center text-xs text-muted-foreground/70"> |
| 539 | Select a thread to inspect turn diffs. | 519 | Select a thread to inspect turn diffs. |
| ... | @@ -558,15 +538,17 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { | ... | @@ -558,15 +538,17 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 558 | </div> | 538 | </div> |
| 559 | )} | 539 | )} |
| 560 | {!renderablePatch ? ( | 540 | {!renderablePatch ? ( |
| 561 | <div className="flex h-full items-center justify-center px-3 py-2 text-xs text-muted-foreground/70"> | 541 | isLoadingCheckpointDiff ? ( |
| 562 | <p> | 542 | <DiffPanelLoadingState label="Loading checkpoint diff..." /> |
| 563 | {isLoadingCheckpointDiff | 543 | ) : ( |
| 564 | ? "Loading checkpoint diff..." | 544 | <div className="flex h-full items-center justify-center px-3 py-2 text-xs text-muted-foreground/70"> |
| 565 | : hasNoNetChanges | 545 | <p> |
| 546 | {hasNoNetChanges | ||
| 566 | ? "No net changes in this selection." | 547 | ? "No net changes in this selection." |
| 567 | : "No patch available for this selection."} | 548 | : "No patch available for this selection."} |
| 568 | </p> | 549 | </p> |
| 569 | </div> | 550 | </div> |
| 551 | ) | ||
| 570 | ) : renderablePatch.kind === "files" ? ( | 552 | ) : renderablePatch.kind === "files" ? ( |
| 571 | <Virtualizer | 553 | <Virtualizer |
| 572 | className="diff-render-surface h-full min-h-0 overflow-auto px-2 pb-2" | 554 | className="diff-render-surface h-full min-h-0 overflow-auto px-2 pb-2" |
| ... | @@ -622,6 +604,6 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { | ... | @@ -622,6 +604,6 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 622 | </div> | 604 | </div> |
| 623 | </> | 605 | </> |
| 624 | )} | 606 | )} |
| 625 | </div> | 607 | </DiffPanelShell> |
| 626 | ); | 608 | ); |
| 627 | } | 609 | } |
apps/web/src/components/DiffPanelShell.tsx created+92| ... | @@ -0,0 +1,92 @@ | ||
| 1 | import type { ReactNode } from "react"; | ||
| 2 | |||
| 3 | import { isElectron } from "~/env"; | ||
| 4 | import { cn } from "~/lib/utils"; | ||
| 5 | |||
| 6 | import { Skeleton } from "./ui/skeleton"; | ||
| 7 | |||
| 8 | export type DiffPanelMode = "inline" | "sheet" | "sidebar"; | ||
| 9 | |||
| 10 | function getDiffPanelHeaderRowClassName(mode: DiffPanelMode) { | ||
| 11 | const shouldUseDragRegion = isElectron && mode !== "sheet"; | ||
| 12 | return cn( | ||
| 13 | "flex items-center justify-between gap-2 px-4", | ||
| 14 | shouldUseDragRegion ? "drag-region h-[52px] border-b border-border" : "h-12", | ||
| 15 | ); | ||
| 16 | } | ||
| 17 | |||
| 18 | export function DiffPanelShell(props: { | ||
| 19 | mode: DiffPanelMode; | ||
| 20 | header: ReactNode; | ||
| 21 | children: ReactNode; | ||
| 22 | }) { | ||
| 23 | const shouldUseDragRegion = isElectron && props.mode !== "sheet"; | ||
| 24 | |||
| 25 | return ( | ||
| 26 | <div | ||
| 27 | className={cn( | ||
| 28 | "flex h-full min-w-0 flex-col bg-background", | ||
| 29 | props.mode === "inline" | ||
| 30 | ? "w-[42vw] min-w-[360px] max-w-[560px] shrink-0 border-l border-border" | ||
| 31 | : "w-full", | ||
| 32 | )} | ||
| 33 | > | ||
| 34 | {shouldUseDragRegion ? ( | ||
| 35 | <div className={getDiffPanelHeaderRowClassName(props.mode)}>{props.header}</div> | ||
| 36 | ) : ( | ||
| 37 | <div className="border-b border-border"> | ||
| 38 | <div className={getDiffPanelHeaderRowClassName(props.mode)}>{props.header}</div> | ||
| 39 | </div> | ||
| 40 | )} | ||
| 41 | {props.children} | ||
| 42 | </div> | ||
| 43 | ); | ||
| 44 | } | ||
| 45 | |||
| 46 | export function DiffPanelHeaderSkeleton() { | ||
| 47 | return ( | ||
| 48 | <> | ||
| 49 | <div className="relative min-w-0 flex-1"> | ||
| 50 | <Skeleton className="absolute left-0 top-1/2 size-6 -translate-y-1/2 rounded-md border border-border/50" /> | ||
| 51 | <Skeleton className="absolute right-0 top-1/2 size-6 -translate-y-1/2 rounded-md border border-border/50" /> | ||
| 52 | <div className="flex gap-1 overflow-hidden px-8 py-0.5"> | ||
| 53 | <Skeleton className="h-6 w-16 shrink-0 rounded-md" /> | ||
| 54 | <Skeleton className="h-6 w-24 shrink-0 rounded-md" /> | ||
| 55 | <Skeleton className="h-6 w-24 shrink-0 rounded-md max-sm:hidden" /> | ||
| 56 | </div> | ||
| 57 | </div> | ||
| 58 | <div className="flex shrink-0 gap-1"> | ||
| 59 | <Skeleton className="size-7 rounded-md" /> | ||
| 60 | <Skeleton className="size-7 rounded-md" /> | ||
| 61 | </div> | ||
| 62 | </> | ||
| 63 | ); | ||
| 64 | } | ||
| 65 | |||
| 66 | export function DiffPanelLoadingState(props: { label: string }) { | ||
| 67 | return ( | ||
| 68 | <div className="flex min-h-0 flex-1 flex-col p-2"> | ||
| 69 | <div | ||
| 70 | className="flex min-h-0 flex-1 flex-col overflow-hidden rounded-md border border-border/60 bg-card/25" | ||
| 71 | role="status" | ||
| 72 | aria-live="polite" | ||
| 73 | aria-label={props.label} | ||
| 74 | > | ||
| 75 | <div className="flex items-center gap-2 border-b border-border/50 px-3 py-2"> | ||
| 76 | <Skeleton className="h-4 w-32 rounded-full" /> | ||
| 77 | <Skeleton className="ml-auto h-4 w-20 rounded-full" /> | ||
| 78 | </div> | ||
| 79 | <div className="flex min-h-0 flex-1 flex-col gap-4 px-3 py-4"> | ||
| 80 | <div className="space-y-2"> | ||
| 81 | <Skeleton className="h-3 w-full rounded-full" /> | ||
| 82 | <Skeleton className="h-3 w-full rounded-full" /> | ||
| 83 | <Skeleton className="h-3 w-10/12 rounded-full" /> | ||
| 84 | <Skeleton className="h-3 w-11/12 rounded-full" /> | ||
| 85 | <Skeleton className="h-3 w-9/12 rounded-full" /> | ||
| 86 | </div> | ||
| 87 | <span className="sr-only">{props.label}</span> | ||
| 88 | </div> | ||
| 89 | </div> | ||
| 90 | </div> | ||
| 91 | ); | ||
| 92 | } | ||
apps/web/src/routes/_chat.$threadId.tsx+42-20| ... | @@ -1,8 +1,15 @@ | ... | @@ -1,8 +1,15 @@ |
| 1 | import { ThreadId } from "@t3tools/contracts"; | 1 | import { ThreadId } from "@t3tools/contracts"; |
| 2 | import { createFileRoute, retainSearchParams, useNavigate } from "@tanstack/react-router"; | 2 | import { createFileRoute, retainSearchParams, useNavigate } from "@tanstack/react-router"; |
| 3 | import { Suspense, lazy, type ReactNode, useCallback, useEffect } from "react"; | 3 | import { Suspense, lazy, type ReactNode, useCallback, useEffect, useState } from "react"; |
| 4 | 4 | ||
| 5 | import ChatView from "../components/ChatView"; | 5 | import ChatView from "../components/ChatView"; |
| 6 | import { DiffWorkerPoolProvider } from "../components/DiffWorkerPoolProvider"; | ||
| 7 | import { | ||
| 8 | DiffPanelHeaderSkeleton, | ||
| 9 | DiffPanelLoadingState, | ||
| 10 | DiffPanelShell, | ||
| 11 | type DiffPanelMode, | ||
| 12 | } from "../components/DiffPanelShell"; | ||
| 6 | import { useComposerDraftStore } from "../composerDraftStore"; | 13 | import { useComposerDraftStore } from "../composerDraftStore"; |
| 7 | import { | 14 | import { |
| 8 | type DiffRouteSearch, | 15 | type DiffRouteSearch, |
| ... | @@ -47,19 +54,21 @@ const DiffPanelSheet = (props: { | ... | @@ -47,19 +54,21 @@ const DiffPanelSheet = (props: { |
| 47 | ); | 54 | ); |
| 48 | }; | 55 | }; |
| 49 | 56 | ||
| 50 | const DiffLoadingFallback = (props: { inline: boolean }) => { | 57 | const DiffLoadingFallback = (props: { mode: DiffPanelMode }) => { |
| 51 | if (props.inline) { | 58 | return ( |
| 52 | return ( | 59 | <DiffPanelShell mode={props.mode} header={<DiffPanelHeaderSkeleton />}> |
| 53 | <div className="flex h-full min-h-0 items-center justify-center px-4 text-center text-xs text-muted-foreground/70"> | 60 | <DiffPanelLoadingState label="Loading diff viewer..." /> |
| 54 | Loading diff viewer... | 61 | </DiffPanelShell> |
| 55 | </div> | 62 | ); |
| 56 | ); | 63 | }; |
| 57 | } | ||
| 58 | 64 | ||
| 65 | const LazyDiffPanel = (props: { mode: DiffPanelMode }) => { | ||
| 59 | return ( | 66 | return ( |
| 60 | <aside className="flex h-full w-[560px] shrink-0 items-center justify-center border-l border-border bg-card px-4 text-center text-xs text-muted-foreground/70"> | 67 | <DiffWorkerPoolProvider> |
| 61 | Loading diff viewer... | 68 | <Suspense fallback={<DiffLoadingFallback mode={props.mode} />}> |
| 62 | </aside> | 69 | <DiffPanel mode={props.mode} /> |
| 70 | </Suspense> | ||
| 71 | </DiffWorkerPoolProvider> | ||
| 63 | ); | 72 | ); |
| 64 | }; | 73 | }; |
| 65 | 74 | ||
| ... | @@ -67,8 +76,9 @@ const DiffPanelInlineSidebar = (props: { | ... | @@ -67,8 +76,9 @@ const DiffPanelInlineSidebar = (props: { |
| 67 | diffOpen: boolean; | 76 | diffOpen: boolean; |
| 68 | onCloseDiff: () => void; | 77 | onCloseDiff: () => void; |
| 69 | onOpenDiff: () => void; | 78 | onOpenDiff: () => void; |
| 79 | renderDiffContent: boolean; | ||
| 70 | }) => { | 80 | }) => { |
| 71 | const { diffOpen, onCloseDiff, onOpenDiff } = props; | 81 | const { diffOpen, onCloseDiff, onOpenDiff, renderDiffContent } = props; |
| 72 | const onOpenChange = useCallback( | 82 | const onOpenChange = useCallback( |
| 73 | (open: boolean) => { | 83 | (open: boolean) => { |
| 74 | if (open) { | 84 | if (open) { |
| ... | @@ -143,9 +153,7 @@ const DiffPanelInlineSidebar = (props: { | ... | @@ -143,9 +153,7 @@ const DiffPanelInlineSidebar = (props: { |
| 143 | storageKey: DIFF_INLINE_SIDEBAR_WIDTH_STORAGE_KEY, | 153 | storageKey: DIFF_INLINE_SIDEBAR_WIDTH_STORAGE_KEY, |
| 144 | }} | 154 | }} |
| 145 | > | 155 | > |
| 146 | <Suspense fallback={<DiffLoadingFallback inline />}> | 156 | {renderDiffContent ? <LazyDiffPanel mode="sidebar" /> : null} |
| 147 | <DiffPanel mode="sidebar" /> | ||
| 148 | </Suspense> | ||
| 149 | <SidebarRail /> | 157 | <SidebarRail /> |
| 150 | </Sidebar> | 158 | </Sidebar> |
| 151 | </SidebarProvider> | 159 | </SidebarProvider> |
| ... | @@ -166,6 +174,9 @@ function ChatThreadRouteView() { | ... | @@ -166,6 +174,9 @@ function ChatThreadRouteView() { |
| 166 | const routeThreadExists = threadExists || draftThreadExists; | 174 | const routeThreadExists = threadExists || draftThreadExists; |
| 167 | const diffOpen = search.diff === "1"; | 175 | const diffOpen = search.diff === "1"; |
| 168 | const shouldUseDiffSheet = useMediaQuery(DIFF_INLINE_LAYOUT_MEDIA_QUERY); | 176 | const shouldUseDiffSheet = useMediaQuery(DIFF_INLINE_LAYOUT_MEDIA_QUERY); |
| 177 | // TanStack Router keeps active route components mounted across param-only navigations | ||
| 178 | // unless remountDeps are configured, so this stays warm across thread switches. | ||
| 179 | const [hasOpenedDiff, setHasOpenedDiff] = useState(diffOpen); | ||
| 169 | const closeDiff = useCallback(() => { | 180 | const closeDiff = useCallback(() => { |
| 170 | void navigate({ | 181 | void navigate({ |
| 171 | to: "/$threadId", | 182 | to: "/$threadId", |
| ... | @@ -184,6 +195,12 @@ function ChatThreadRouteView() { | ... | @@ -184,6 +195,12 @@ function ChatThreadRouteView() { |
| 184 | }); | 195 | }); |
| 185 | }, [navigate, threadId]); | 196 | }, [navigate, threadId]); |
| 186 | 197 | ||
| 198 | useEffect(() => { | ||
| 199 | if (diffOpen) { | ||
| 200 | setHasOpenedDiff(true); | ||
| 201 | } | ||
| 202 | }, [diffOpen]); | ||
| 203 | |||
| 187 | useEffect(() => { | 204 | useEffect(() => { |
| 188 | if (!threadsHydrated) { | 205 | if (!threadsHydrated) { |
| 189 | return; | 206 | return; |
| ... | @@ -199,13 +216,20 @@ function ChatThreadRouteView() { | ... | @@ -199,13 +216,20 @@ function ChatThreadRouteView() { |
| 199 | return null; | 216 | return null; |
| 200 | } | 217 | } |
| 201 | 218 | ||
| 219 | const shouldRenderDiffContent = diffOpen || hasOpenedDiff; | ||
| 220 | |||
| 202 | if (!shouldUseDiffSheet) { | 221 | if (!shouldUseDiffSheet) { |
| 203 | return ( | 222 | return ( |
| 204 | <> | 223 | <> |
| 205 | <SidebarInset className="h-dvh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground"> | 224 | <SidebarInset className="h-dvh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground"> |
| 206 | <ChatView key={threadId} threadId={threadId} /> | 225 | <ChatView key={threadId} threadId={threadId} /> |
| 207 | </SidebarInset> | 226 | </SidebarInset> |
| 208 | <DiffPanelInlineSidebar diffOpen={diffOpen} onCloseDiff={closeDiff} onOpenDiff={openDiff} /> | 227 | <DiffPanelInlineSidebar |
| 228 | diffOpen={diffOpen} | ||
| 229 | onCloseDiff={closeDiff} | ||
| 230 | onOpenDiff={openDiff} | ||
| 231 | renderDiffContent={shouldRenderDiffContent} | ||
| 232 | /> | ||
| 209 | </> | 233 | </> |
| 210 | ); | 234 | ); |
| 211 | } | 235 | } |
| ... | @@ -216,9 +240,7 @@ function ChatThreadRouteView() { | ... | @@ -216,9 +240,7 @@ function ChatThreadRouteView() { |
| 216 | <ChatView key={threadId} threadId={threadId} /> | 240 | <ChatView key={threadId} threadId={threadId} /> |
| 217 | </SidebarInset> | 241 | </SidebarInset> |
| 218 | <DiffPanelSheet diffOpen={diffOpen} onCloseDiff={closeDiff}> | 242 | <DiffPanelSheet diffOpen={diffOpen} onCloseDiff={closeDiff}> |
| 219 | <Suspense fallback={<DiffLoadingFallback inline={false} />}> | 243 | {shouldRenderDiffContent ? <LazyDiffPanel mode="sheet" /> : null} |
| 220 | <DiffPanel mode="sheet" /> | ||
| 221 | </Suspense> | ||
| 222 | </DiffPanelSheet> | 244 | </DiffPanelSheet> |
| 223 | </> | 245 | </> |
| 224 | ); | 246 | ); |
apps/web/src/routes/_chat.tsx+1-4| ... | @@ -3,7 +3,6 @@ import { useQuery } from "@tanstack/react-query"; | ... | @@ -3,7 +3,6 @@ import { useQuery } from "@tanstack/react-query"; |
| 3 | import { Outlet, createFileRoute, useNavigate } from "@tanstack/react-router"; | 3 | import { Outlet, createFileRoute, useNavigate } from "@tanstack/react-router"; |
| 4 | import { useEffect } from "react"; | 4 | import { useEffect } from "react"; |
| 5 | 5 | ||
| 6 | import { DiffWorkerPoolProvider } from "../components/DiffWorkerPoolProvider"; | ||
| 7 | import ThreadSidebar from "../components/Sidebar"; | 6 | import ThreadSidebar from "../components/Sidebar"; |
| 8 | import { useHandleNewThread } from "../hooks/useHandleNewThread"; | 7 | import { useHandleNewThread } from "../hooks/useHandleNewThread"; |
| 9 | import { isTerminalFocused } from "../lib/terminalFocus"; | 8 | import { isTerminalFocused } from "../lib/terminalFocus"; |
| ... | @@ -120,9 +119,7 @@ function ChatRouteLayout() { | ... | @@ -120,9 +119,7 @@ function ChatRouteLayout() { |
| 120 | > | 119 | > |
| 121 | <ThreadSidebar /> | 120 | <ThreadSidebar /> |
| 122 | </Sidebar> | 121 | </Sidebar> |
| 123 | <DiffWorkerPoolProvider> | 122 | <Outlet /> |
| 124 | <Outlet /> | ||
| 125 | </DiffWorkerPoolProvider> | ||
| 126 | </SidebarProvider> | 123 | </SidebarProvider> |
| 127 | ); | 124 | ); |
| 128 | } | 125 | } |