From e44a1b3b9f1f13842f0897828142d4727759a485 Mon Sep 17 00:00:00 2001 From: maria-rcks <254055478+maria-rcks@users.noreply.github.com> Date: Wed, 16 Sep 2026 20:49:42 +0000 Subject: [PATCH 1/3] fix(web): default diff panel to working tree --- apps/web/src/components/ChatView.tsx | 6 +----- apps/web/src/components/DiffPanel.tsx | 9 +-------- apps/web/src/diffPanelStore.test.ts | 12 ++++++------ apps/web/src/diffPanelStore.ts | 9 ++------- 4 files changed, 10 insertions(+), 26 deletions(-) diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 6b372437f2ae..3636d5d5f7ef 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -3712,9 +3712,6 @@ export default function ChatView(props: ChatViewProps) { showEnvironmentIndicator: showComposerEnvironmentIndicator, hostsRestingComposerControls: routeKind === "server" && restingComposerControlsVisible, }); - const initialDiffPanelGitScope = - gitStatusQuery.data?.hasWorkingTreeChanges === true ? "unstaged" : "branch"; - const diffPanelGitStatusResolutionKey = gitStatusQuery.data ? "resolved" : "pending"; const terminalShortcutLabelOptions = useMemo( () => ({ context: { @@ -9179,10 +9176,9 @@ export default function ChatView(props: ChatViewProps) { ) : renderedRightPanelSurface?.kind === "diff" ? ( diff --git a/apps/web/src/components/DiffPanel.tsx b/apps/web/src/components/DiffPanel.tsx index ca0bdaa7b4d5..9194e36d83bd 100644 --- a/apps/web/src/components/DiffPanel.tsx +++ b/apps/web/src/components/DiffPanel.tsx @@ -100,19 +100,16 @@ const EMPTY_COLLAPSED_DIFF_FILE_KEYS: ReadonlySet = new Set(); interface DiffPanelProps { mode?: DiffPanelMode; composerDraftTarget: ScopedThreadRef | DraftId; - initialGitScope: "branch" | "unstaged"; workspaceMutationId: string | null; } export default function DiffPanel({ mode = "inline", composerDraftTarget, - initialGitScope: initialGitScopeProp, workspaceMutationId, }: DiffPanelProps) { const { resolvedTheme } = useTheme(); const settings = useClientSettings(); - const [initialGitScope] = useState(initialGitScopeProp); const diffLayout = settings.diffLayout; const updateClientSettings = useUpdateClientSettings(); const [wordWrap, setWordWrap] = useState(settings.wordWrap); @@ -166,11 +163,7 @@ export default function DiffPanel({ : null, ); const diffSelection = useDiffPanelStore((state) => - selectThreadDiffPanelSelection( - state.byThreadKey, - routeThreadRef, - initialGitScope === "unstaged", - ), + selectThreadDiffPanelSelection(state.byThreadKey, routeThreadRef), ); const isGitRepo = gitStatusQuery.data?.isRepo ?? true; const { turnDiffSummaries, inferredCheckpointTurnCountByTurnId } = diff --git a/apps/web/src/diffPanelStore.test.ts b/apps/web/src/diffPanelStore.test.ts index f1d774f9cbfc..919793cad882 100644 --- a/apps/web/src/diffPanelStore.test.ts +++ b/apps/web/src/diffPanelStore.test.ts @@ -14,23 +14,23 @@ describe("diffPanelStore", () => { }), ); - it("defaults each thread to branch changes when the working tree is clean", () => { + it("defaults each thread to working tree changes without requiring git status", () => { expect( selectThreadDiffPanelSelection(useDiffPanelStore.getState().byThreadKey, THREAD_REF), - ).toEqual({ kind: "branch", baseRef: null }); + ).toEqual({ kind: "unstaged" }); }); - it("defaults each thread to working changes when the working tree is dirty", () => { + it("defaults to working tree changes before a thread is selected", () => { expect( - selectThreadDiffPanelSelection(useDiffPanelStore.getState().byThreadKey, THREAD_REF, true), + selectThreadDiffPanelSelection(useDiffPanelStore.getState().byThreadKey, null), ).toEqual({ kind: "unstaged" }); }); - it("preserves an explicit scope selection when the working tree state changes", () => { + it("preserves an explicit branch selection", () => { useDiffPanelStore.getState().selectGitScope(THREAD_REF, "branch"); expect( - selectThreadDiffPanelSelection(useDiffPanelStore.getState().byThreadKey, THREAD_REF, true), + selectThreadDiffPanelSelection(useDiffPanelStore.getState().byThreadKey, THREAD_REF), ).toEqual({ kind: "branch", baseRef: null }); }); diff --git a/apps/web/src/diffPanelStore.ts b/apps/web/src/diffPanelStore.ts index 56b5ad23fecc..08b0323a4cec 100644 --- a/apps/web/src/diffPanelStore.ts +++ b/apps/web/src/diffPanelStore.ts @@ -10,8 +10,7 @@ export type DiffPanelSelection = | { kind: "unstaged" } | { kind: "turn"; turnId: TurnId; filePath: string | null; revealRequestId: number }; -const DEFAULT_SELECTION: DiffPanelSelection = { kind: "branch", baseRef: null }; -const DEFAULT_WORKING_TREE_SELECTION: DiffPanelSelection = { kind: "unstaged" }; +const DEFAULT_SELECTION: DiffPanelSelection = { kind: "unstaged" }; interface DiffPanelStoreState { byThreadKey: Record; @@ -134,11 +133,7 @@ export const useDiffPanelStore = create()( export function selectThreadDiffPanelSelection( byThreadKey: Record, ref: ScopedThreadRef | null | undefined, - hasWorkingTreeChanges = false, ): DiffPanelSelection { if (!ref) return DEFAULT_SELECTION; - return ( - byThreadKey[scopedThreadKey(ref)] ?? - (hasWorkingTreeChanges ? DEFAULT_WORKING_TREE_SELECTION : DEFAULT_SELECTION) - ); + return byThreadKey[scopedThreadKey(ref)] ?? DEFAULT_SELECTION; } From 4cac861cf03a9ef3be57d29232cc751ef813e68e Mon Sep 17 00:00:00 2001 From: maria-rcks <254055478+maria-rcks@users.noreply.github.com> Date: Wed, 16 Sep 2026 20:52:57 +0000 Subject: [PATCH 2/3] style(web): format diff selection assertion --- apps/web/src/diffPanelStore.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/web/src/diffPanelStore.test.ts b/apps/web/src/diffPanelStore.test.ts index 919793cad882..886e0594495e 100644 --- a/apps/web/src/diffPanelStore.test.ts +++ b/apps/web/src/diffPanelStore.test.ts @@ -21,9 +21,9 @@ describe("diffPanelStore", () => { }); it("defaults to working tree changes before a thread is selected", () => { - expect( - selectThreadDiffPanelSelection(useDiffPanelStore.getState().byThreadKey, null), - ).toEqual({ kind: "unstaged" }); + expect(selectThreadDiffPanelSelection(useDiffPanelStore.getState().byThreadKey, null)).toEqual({ + kind: "unstaged", + }); }); it("preserves an explicit branch selection", () => { From 38e7e1503a5a47b24bc43a0a1ad79da967fcb7e9 Mon Sep 17 00:00:00 2001 From: maria-rcks <254055478+maria-rcks@users.noreply.github.com> Date: Wed, 16 Sep 2026 22:04:37 +0000 Subject: [PATCH 3/3] fix(web): open proactive and reopened diffs on working tree --- apps/web/src/components/ChatView.tsx | 16 ++++++++++-- .../components/settings/SettingsPanels.tsx | 2 +- apps/web/src/diffPanelStore.test.ts | 25 +++++++++++++++++++ 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 3636d5d5f7ef..53423d6e1363 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -1961,6 +1961,16 @@ export default function ChatView(props: ChatViewProps) { selectActiveRightPanel(state.byThreadKey, activeThreadRef), ); const diffOpen = activeRightPanelKind === "diff"; + const explicitDiffOpenRef = useRef(null); + useLayoutEffect(() => { + const explicitThreadRef = explicitDiffOpenRef.current; + explicitDiffOpenRef.current = null; + // Generic openings always show the checkout, including tab fallbacks and thread changes. + // A timeline click instead opens the specific turn/file the user requested. + if (diffOpen && activeThreadRef && explicitThreadRef !== activeThreadRef) { + useDiffPanelStore.getState().selectGitScope(activeThreadRef, "unstaged"); + } + }, [activeThreadRef, diffOpen]); const rightPanelState = useRightPanelStore((state) => selectThreadRightPanelState(state.byThreadKey, activeThreadRef), ); @@ -4498,6 +4508,7 @@ export default function ChatView(props: ChatViewProps) { ); const addDiffSurface = useCallback(() => { if (!activeThreadRef || !isServerThread || !isGitRepo) return; + useDiffPanelStore.getState().selectGitScope(activeThreadRef, "unstaged"); useRightPanelStore.getState().open(activeThreadRef, "diff"); onDiffPanelOpen?.(); }, [activeThreadRef, isGitRepo, isServerThread, onDiffPanelOpen]); @@ -4739,7 +4750,7 @@ export default function ChatView(props: ChatViewProps) { if (!panels.openProactive(activeThreadRef, { id: "diff", kind: "diff" }, userActionRevision)) { return; } - useDiffPanelStore.getState().selectTurn(activeThreadRef, newlyCompletedTurnId); + useDiffPanelStore.getState().selectGitScope(activeThreadRef, "unstaged"); onDiffPanelOpen?.(); }, [ activeThread?.checkpoints, @@ -9028,11 +9039,12 @@ export default function ChatView(props: ChatViewProps) { const onOpenTurnDiff = useCallback( (turnId: TurnId, filePath?: string) => { if (!isServerThread || !activeThreadRef) return; + explicitDiffOpenRef.current = diffOpen ? null : activeThreadRef; useDiffPanelStore.getState().selectTurn(activeThreadRef, turnId, filePath); useRightPanelStore.getState().open(activeThreadRef, "diff"); onDiffPanelOpen?.(); }, - [activeThreadRef, isServerThread, onDiffPanelOpen], + [activeThreadRef, diffOpen, isServerThread, onDiffPanelOpen], ); // The revert handler is read from a ref at call-time so the callback // reference is fully stable and never busts TimelineRowCtx identity. diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx index d2306e02a85f..735ff226fc68 100644 --- a/apps/web/src/components/settings/SettingsPanels.tsx +++ b/apps/web/src/components/settings/SettingsPanels.tsx @@ -2528,7 +2528,7 @@ export function GeneralSettingsPanel() { { ).toEqual({ kind: "branch", baseRef: "origin/main" }); }); + it("clears a thread's turn and file when selecting working tree without changing another thread's branch base", () => { + const otherThreadRef = scopeThreadRef( + EnvironmentId.make("environment-1"), + ThreadId.make("thread-2"), + ); + const store = useDiffPanelStore.getState(); + store.selectBranchBaseRef(THREAD_REF, "origin/release"); + store.selectTurn(THREAD_REF, TurnId.make("turn-1"), "src/app.ts"); + store.selectBranchBaseRef(otherThreadRef, "origin/main"); + + store.selectGitScope(THREAD_REF, "unstaged"); + + const { byThreadKey } = useDiffPanelStore.getState(); + expect(selectThreadDiffPanelSelection(byThreadKey, THREAD_REF)).toEqual({ kind: "unstaged" }); + expect(selectThreadDiffPanelSelection(byThreadKey, otherThreadRef)).toEqual({ + kind: "branch", + baseRef: "origin/main", + }); + + store.selectGitScope(THREAD_REF, "branch"); + expect( + selectThreadDiffPanelSelection(useDiffPanelStore.getState().byThreadKey, THREAD_REF), + ).toEqual({ kind: "branch", baseRef: "origin/release" }); + }); + it("increments the reveal request when opening the same turn file again", () => { const turnId = TurnId.make("turn-1"); useDiffPanelStore.getState().selectTurn(THREAD_REF, turnId, "src/app.ts");