From 38e0bb07bb24894524f1502ac0487c5ea4ff85de Mon Sep 17 00:00:00 2001 From: Jake Leventhal Date: Sat, 18 Jul 2026 07:17:30 -0400 Subject: [PATCH] Default diffs to working changes (#3974) Co-authored-by: Julius Marminge --- apps/web/src/components/ChatView.tsx | 15 ++++++++++++--- apps/web/src/components/DiffPanel.tsx | 18 ++++++++++++++---- apps/web/src/diffPanelStore.test.ts | 16 +++++++++++++++- apps/web/src/diffPanelStore.ts | 7 ++++++- 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 77951e7079adfc140e7087285a3ef02b4ce53af7..69292e7e6aa3bd7fbf0c3c738443f14bbf72343a 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -2252,12 +2252,13 @@ function ChatViewContent(props: ChatViewProps) { worktreePath: activeThread?.worktreePath ?? null, }) : null; + const gitStatusCwd = activeThread?.worktreePath ?? gitCwd; const gitStatusQuery = useEnvironmentQuery( - gitCwd === null + gitStatusCwd === null ? null : vcsEnvironment.status({ environmentId, - input: { cwd: gitCwd }, + input: { cwd: gitStatusCwd }, }), ); const keybindings = useAtomValue(primaryServerKeybindingsAtom); @@ -2291,6 +2292,9 @@ function ChatViewContent(props: ChatViewProps) { terminalUiLaunchContext?.threadId === activeThreadId ? terminalUiLaunchContext : null; // Default true while loading to avoid toolbar flicker. const isGitRepo = gitStatusQuery.data?.isRepo ?? true; + const initialDiffPanelGitScope = + gitStatusQuery.data?.hasWorkingTreeChanges === true ? "unstaged" : "branch"; + const diffPanelGitStatusResolutionKey = gitStatusQuery.data ? "resolved" : "pending"; const terminalShortcutLabelOptions = useMemo( () => ({ context: { @@ -5136,7 +5140,12 @@ function ChatViewContent(props: ChatViewProps) { /> ) : activeRightPanelSurface?.kind === "diff" ? ( - + ) : activeRightPanelSurface?.kind === "plan" ? ( ("stacked"); const [wordWrap, setWordWrap] = useState(settings.wordWrap); const [diffIgnoreWhitespace, setDiffIgnoreWhitespace] = useState(settings.diffIgnoreWhitespace); @@ -199,9 +205,6 @@ export default function DiffPanel({ mode = "inline", composerDraftTarget }: Diff strict: false, select: (params) => resolveThreadRouteRef(params), }); - const diffSelection = useDiffPanelStore((state) => - selectThreadDiffPanelSelection(state.byThreadKey, routeThreadRef), - ); const activeThreadId = routeThreadRef?.threadId ?? null; const activeThread = useThread(routeThreadRef); const activeProjectId = activeThread?.projectId ?? null; @@ -229,6 +232,13 @@ export default function DiffPanel({ mode = "inline", composerDraftTarget }: Diff }) : null, ); + const diffSelection = useDiffPanelStore((state) => + selectThreadDiffPanelSelection( + state.byThreadKey, + routeThreadRef, + initialGitScope === "unstaged", + ), + ); const isGitRepo = gitStatusQuery.data?.isRepo ?? true; const { turnDiffSummaries, inferredCheckpointTurnCountByTurnId } = useTurnDiffSummaries(activeThread); diff --git a/apps/web/src/diffPanelStore.test.ts b/apps/web/src/diffPanelStore.test.ts index 64846e8e9f143759da27530b695d9f91e53116a0..607b7c8d5801627b0cab4d68516342467d732357 100644 --- a/apps/web/src/diffPanelStore.test.ts +++ b/apps/web/src/diffPanelStore.test.ts @@ -9,12 +9,26 @@ const THREAD_REF = scopeThreadRef(EnvironmentId.make("environment-1"), ThreadId. describe("diffPanelStore", () => { beforeEach(() => useDiffPanelStore.setState({ byThreadKey: {}, branchBaseRefByThreadKey: {} })); - it("defaults each thread to branch changes with automatic base selection", () => { + it("defaults each thread to branch changes when the working tree is clean", () => { expect( selectThreadDiffPanelSelection(useDiffPanelStore.getState().byThreadKey, THREAD_REF), ).toEqual({ kind: "branch", baseRef: null }); }); + it("defaults each thread to working changes when the working tree is dirty", () => { + expect( + selectThreadDiffPanelSelection(useDiffPanelStore.getState().byThreadKey, THREAD_REF, true), + ).toEqual({ kind: "unstaged" }); + }); + + it("preserves an explicit scope selection when the working tree state changes", () => { + useDiffPanelStore.getState().selectGitScope(THREAD_REF, "branch"); + + expect( + selectThreadDiffPanelSelection(useDiffPanelStore.getState().byThreadKey, THREAD_REF, true), + ).toEqual({ kind: "branch", baseRef: null }); + }); + it("clears incompatible selection fields when changing scopes", () => { const store = useDiffPanelStore.getState(); store.selectTurn(THREAD_REF, TurnId.make("turn-1"), "src/app.ts"); diff --git a/apps/web/src/diffPanelStore.ts b/apps/web/src/diffPanelStore.ts index c946b286d1b9cc70dd11aff2d7f207fc3aa5160c..56b5ad23feccc739d5717cd5a6794f800e9987e3 100644 --- a/apps/web/src/diffPanelStore.ts +++ b/apps/web/src/diffPanelStore.ts @@ -11,6 +11,7 @@ export type DiffPanelSelection = | { 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" }; interface DiffPanelStoreState { byThreadKey: Record; @@ -133,7 +134,11 @@ 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)] ?? DEFAULT_SELECTION; + return ( + byThreadKey[scopedThreadKey(ref)] ?? + (hasWorkingTreeChanges ? DEFAULT_WORKING_TREE_SELECTION : DEFAULT_SELECTION) + ); } -- 2.54.0