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");