authorgravatar for jakeleventhal@me.comJake Leventhal <jakeleventhal@me.com> 2026-07-18 07:17:30-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-18 13:17:30+02:00
log38e0bb07bb24894524f1502ac0487c5ea4ff85de
tree2ebd44463832745f9fb133fadf816b9bc1e01463
parent765e1b5fc044b899741a11dcba141bb2c49c6c37
signature Signed by PGP key B5690EEEBB952194

Default diffs to working changes (#3974)

Co-authored-by: Julius Marminge <julius0216@outlook.com>

4 files changed, 47 insertions(+), 9 deletions(-)

apps/web/src/components/ChatView.tsx+12-3
......@@ -2252,12 +2252,13 @@ function ChatViewContent(props: ChatViewProps) {
22522252 worktreePath: activeThread?.worktreePath ?? null,
22532253 })
22542254 : null;
2255 const gitStatusCwd = activeThread?.worktreePath ?? gitCwd;
22552256 const gitStatusQuery = useEnvironmentQuery(
2256 gitCwd === null
2257 gitStatusCwd === null
22572258 ? null
22582259 : vcsEnvironment.status({
22592260 environmentId,
2260 input: { cwd: gitCwd },
2261 input: { cwd: gitStatusCwd },
22612262 }),
22622263 );
22632264 const keybindings = useAtomValue(primaryServerKeybindingsAtom);
......@@ -2291,6 +2292,9 @@ function ChatViewContent(props: ChatViewProps) {
22912292 terminalUiLaunchContext?.threadId === activeThreadId ? terminalUiLaunchContext : null;
22922293 // Default true while loading to avoid toolbar flicker.
22932294 const isGitRepo = gitStatusQuery.data?.isRepo ?? true;
2295 const initialDiffPanelGitScope =
2296 gitStatusQuery.data?.hasWorkingTreeChanges === true ? "unstaged" : "branch";
2297 const diffPanelGitStatusResolutionKey = gitStatusQuery.data ? "resolved" : "pending";
22942298 const terminalShortcutLabelOptions = useMemo(
22952299 () => ({
22962300 context: {
......@@ -5136,7 +5140,12 @@ function ChatViewContent(props: ChatViewProps) {
51365140 />
51375141 ) : activeRightPanelSurface?.kind === "diff" ? (
51385142 <Suspense fallback={null}>
5139 <DiffPanel mode="embedded" composerDraftTarget={composerDraftTarget} />
5143 <DiffPanel
5144 key={`${activeThreadKey}:${diffPanelGitStatusResolutionKey}`}
5145 mode="embedded"
5146 composerDraftTarget={composerDraftTarget}
5147 initialGitScope={initialDiffPanelGitScope}
5148 />
51405149 </Suspense>
51415150 ) : activeRightPanelSurface?.kind === "plan" ? (
51425151 <PlanSidebar
apps/web/src/components/DiffPanel.tsx+14-4
......@@ -178,13 +178,19 @@ const DIFF_PANEL_UNSAFE_CSS = `
178178interface DiffPanelProps {
179179 mode?: DiffPanelMode;
180180 composerDraftTarget: ScopedThreadRef | DraftId;
181 initialGitScope: "branch" | "unstaged";
181182}
182183
183184export { DiffWorkerPoolProvider } from "./DiffWorkerPoolProvider";
184185
185export default function DiffPanel({ mode = "inline", composerDraftTarget }: DiffPanelProps) {
186export default function DiffPanel({
187 mode = "inline",
188 composerDraftTarget,
189 initialGitScope: initialGitScopeProp,
190}: DiffPanelProps) {
186191 const { resolvedTheme } = useTheme();
187192 const settings = useClientSettings();
193 const [initialGitScope] = useState(initialGitScopeProp);
188194 const [diffRenderMode, setDiffRenderMode] = useState<DiffRenderMode>("stacked");
189195 const [wordWrap, setWordWrap] = useState(settings.wordWrap);
190196 const [diffIgnoreWhitespace, setDiffIgnoreWhitespace] = useState(settings.diffIgnoreWhitespace);
......@@ -199,9 +205,6 @@ export default function DiffPanel({ mode = "inline", composerDraftTarget }: Diff
199205 strict: false,
200206 select: (params) => resolveThreadRouteRef(params),
201207 });
202 const diffSelection = useDiffPanelStore((state) =>
203 selectThreadDiffPanelSelection(state.byThreadKey, routeThreadRef),
204 );
205208 const activeThreadId = routeThreadRef?.threadId ?? null;
206209 const activeThread = useThread(routeThreadRef);
207210 const activeProjectId = activeThread?.projectId ?? null;
......@@ -229,6 +232,13 @@ export default function DiffPanel({ mode = "inline", composerDraftTarget }: Diff
229232 })
230233 : null,
231234 );
235 const diffSelection = useDiffPanelStore((state) =>
236 selectThreadDiffPanelSelection(
237 state.byThreadKey,
238 routeThreadRef,
239 initialGitScope === "unstaged",
240 ),
241 );
232242 const isGitRepo = gitStatusQuery.data?.isRepo ?? true;
233243 const { turnDiffSummaries, inferredCheckpointTurnCountByTurnId } =
234244 useTurnDiffSummaries(activeThread);
apps/web/src/diffPanelStore.test.ts+15-1
......@@ -9,12 +9,26 @@ const THREAD_REF = scopeThreadRef(EnvironmentId.make("environment-1"), ThreadId.
99describe("diffPanelStore", () => {
1010 beforeEach(() => useDiffPanelStore.setState({ byThreadKey: {}, branchBaseRefByThreadKey: {} }));
1111
12 it("defaults each thread to branch changes with automatic base selection", () => {
12 it("defaults each thread to branch changes when the working tree is clean", () => {
1313 expect(
1414 selectThreadDiffPanelSelection(useDiffPanelStore.getState().byThreadKey, THREAD_REF),
1515 ).toEqual({ kind: "branch", baseRef: null });
1616 });
1717
18 it("defaults each thread to working changes when the working tree is dirty", () => {
19 expect(
20 selectThreadDiffPanelSelection(useDiffPanelStore.getState().byThreadKey, THREAD_REF, true),
21 ).toEqual({ kind: "unstaged" });
22 });
23
24 it("preserves an explicit scope selection when the working tree state changes", () => {
25 useDiffPanelStore.getState().selectGitScope(THREAD_REF, "branch");
26
27 expect(
28 selectThreadDiffPanelSelection(useDiffPanelStore.getState().byThreadKey, THREAD_REF, true),
29 ).toEqual({ kind: "branch", baseRef: null });
30 });
31
1832 it("clears incompatible selection fields when changing scopes", () => {
1933 const store = useDiffPanelStore.getState();
2034 store.selectTurn(THREAD_REF, TurnId.make("turn-1"), "src/app.ts");
apps/web/src/diffPanelStore.ts+6-1
......@@ -11,6 +11,7 @@ export type DiffPanelSelection =
1111 | { kind: "turn"; turnId: TurnId; filePath: string | null; revealRequestId: number };
1212
1313const DEFAULT_SELECTION: DiffPanelSelection = { kind: "branch", baseRef: null };
14const DEFAULT_WORKING_TREE_SELECTION: DiffPanelSelection = { kind: "unstaged" };
1415
1516interface DiffPanelStoreState {
1617 byThreadKey: Record<string, DiffPanelSelection>;
......@@ -133,7 +134,11 @@ export const useDiffPanelStore = create<DiffPanelStoreState>()(
133134export function selectThreadDiffPanelSelection(
134135 byThreadKey: Record<string, DiffPanelSelection>,
135136 ref: ScopedThreadRef | null | undefined,
137 hasWorkingTreeChanges = false,
136138): DiffPanelSelection {
137139 if (!ref) return DEFAULT_SELECTION;
138 return byThreadKey[scopedThreadKey(ref)] ?? DEFAULT_SELECTION;
140 return (
141 byThreadKey[scopedThreadKey(ref)] ??
142 (hasWorkingTreeChanges ? DEFAULT_WORKING_TREE_SELECTION : DEFAULT_SELECTION)
143 );
139144}