Unified · Split
| @@ -2252,12 +2252,13 @@ function ChatViewContent(props: ChatViewProps) { · 2251 unmodified lines | ||
| 2252 | 2252 | worktreePath: activeThread?.worktreePath ?? null, |
| 2253 | 2253 | }) |
| 2254 | 2254 | : null; |
| 2255 | + const gitStatusCwd = activeThread?.worktreePath ?? gitCwd; | |
| 2255 | 2256 | const gitStatusQuery = useEnvironmentQuery( |
| 2256 | − gitCwd === null | |
| 2257 | + gitStatusCwd === null | |
| 2257 | 2258 | ? null |
| 2258 | 2259 | : vcsEnvironment.status({ |
| 2259 | 2260 | environmentId, |
| 2260 | − input: { cwd: gitCwd }, | |
| 2261 | + input: { cwd: gitStatusCwd }, | |
| 2261 | 2262 | }), |
| 2262 | 2263 | ); |
| 2263 | 2264 | const keybindings = useAtomValue(primaryServerKeybindingsAtom); |
| @@ -2291,6 +2292,9 @@ function ChatViewContent(props: ChatViewProps) { · 27 unmodified lines | ||
| 2291 | 2292 | terminalUiLaunchContext?.threadId === activeThreadId ? terminalUiLaunchContext : null; |
| 2292 | 2293 | // Default true while loading to avoid toolbar flicker. |
| 2293 | 2294 | const isGitRepo = gitStatusQuery.data?.isRepo ?? true; |
| 2295 | + const initialDiffPanelGitScope = | |
| 2296 | + gitStatusQuery.data?.hasWorkingTreeChanges === true ? "unstaged" : "branch"; | |
| 2297 | + const diffPanelGitStatusResolutionKey = gitStatusQuery.data ? "resolved" : "pending"; | |
| 2294 | 2298 | const terminalShortcutLabelOptions = useMemo( |
| 2295 | 2299 | () => ({ |
| 2296 | 2300 | context: { |
| @@ -5136,7 +5140,12 @@ function ChatViewContent(props: ChatViewProps) { · 2839 unmodified lines | ||
| 5136 | 5140 | /> |
| 5137 | 5141 | ) : activeRightPanelSurface?.kind === "diff" ? ( |
| 5138 | 5142 | <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 | + /> | |
| 5140 | 5149 | </Suspense> |
| 5141 | 5150 | ) : activeRightPanelSurface?.kind === "plan" ? ( |
| 5142 | 5151 | <PlanSidebar |
| @@ -178,13 +178,19 @@ const DIFF_PANEL_UNSAFE_CSS = ` · 177 unmodified lines | ||
| 178 | 178 | interface DiffPanelProps { |
| 179 | 179 | mode?: DiffPanelMode; |
| 180 | 180 | composerDraftTarget: ScopedThreadRef | DraftId; |
| 181 | + initialGitScope: "branch" | "unstaged"; | |
| 181 | 182 | } |
| 182 | 183 | |
| 183 | 184 | export { DiffWorkerPoolProvider } from "./DiffWorkerPoolProvider"; |
| 184 | 185 | |
| 185 | −export default function DiffPanel({ mode = "inline", composerDraftTarget }: DiffPanelProps) { | |
| 186 | +export default function DiffPanel({ | |
| 187 | + mode = "inline", | |
| 188 | + composerDraftTarget, | |
| 189 | + initialGitScope: initialGitScopeProp, | |
| 190 | +}: DiffPanelProps) { | |
| 186 | 191 | const { resolvedTheme } = useTheme(); |
| 187 | 192 | const settings = useClientSettings(); |
| 193 | + const [initialGitScope] = useState(initialGitScopeProp); | |
| 188 | 194 | const [diffRenderMode, setDiffRenderMode] = useState<DiffRenderMode>("stacked"); |
| 189 | 195 | const [wordWrap, setWordWrap] = useState(settings.wordWrap); |
| 190 | 196 | const [diffIgnoreWhitespace, setDiffIgnoreWhitespace] = useState(settings.diffIgnoreWhitespace); |
| @@ -199,9 +205,6 @@ export default function DiffPanel({ mode = "inline", composerDraftTarget }: Diff · 8 unmodified lines | ||
| 199 | 205 | strict: false, |
| 200 | 206 | select: (params) => resolveThreadRouteRef(params), |
| 201 | 207 | }); |
| 202 | − const diffSelection = useDiffPanelStore((state) => | |
| 203 | − selectThreadDiffPanelSelection(state.byThreadKey, routeThreadRef), | |
| 204 | − ); | |
| 205 | 208 | const activeThreadId = routeThreadRef?.threadId ?? null; |
| 206 | 209 | const activeThread = useThread(routeThreadRef); |
| 207 | 210 | const activeProjectId = activeThread?.projectId ?? null; |
| @@ -229,6 +232,13 @@ export default function DiffPanel({ mode = "inline", composerDraftTarget }: Diff · 21 unmodified lines | ||
| 229 | 232 | }) |
| 230 | 233 | : null, |
| 231 | 234 | ); |
| 235 | + const diffSelection = useDiffPanelStore((state) => | |
| 236 | + selectThreadDiffPanelSelection( | |
| 237 | + state.byThreadKey, | |
| 238 | + routeThreadRef, | |
| 239 | + initialGitScope === "unstaged", | |
| 240 | + ), | |
| 241 | + ); | |
| 232 | 242 | const isGitRepo = gitStatusQuery.data?.isRepo ?? true; |
| 233 | 243 | const { turnDiffSummaries, inferredCheckpointTurnCountByTurnId } = |
| 234 | 244 | useTurnDiffSummaries(activeThread); |
| @@ -9,12 +9,26 @@ const THREAD_REF = scopeThreadRef(EnvironmentId.make("environment-1"), ThreadId. · 8 unmodified lines | ||
| 9 | 9 | describe("diffPanelStore", () => { |
| 10 | 10 | beforeEach(() => useDiffPanelStore.setState({ byThreadKey: {}, branchBaseRefByThreadKey: {} })); |
| 11 | 11 | |
| 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", () => { | |
| 13 | 13 | expect( |
| 14 | 14 | selectThreadDiffPanelSelection(useDiffPanelStore.getState().byThreadKey, THREAD_REF), |
| 15 | 15 | ).toEqual({ kind: "branch", baseRef: null }); |
| 16 | 16 | }); |
| 17 | 17 | |
| 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 | + | |
| 18 | 32 | it("clears incompatible selection fields when changing scopes", () => { |
| 19 | 33 | const store = useDiffPanelStore.getState(); |
| 20 | 34 | store.selectTurn(THREAD_REF, TurnId.make("turn-1"), "src/app.ts"); |
| @@ -11,6 +11,7 @@ export type DiffPanelSelection = · 10 unmodified lines | ||
| 11 | 11 | | { kind: "turn"; turnId: TurnId; filePath: string | null; revealRequestId: number }; |
| 12 | 12 | |
| 13 | 13 | const DEFAULT_SELECTION: DiffPanelSelection = { kind: "branch", baseRef: null }; |
| 14 | +const DEFAULT_WORKING_TREE_SELECTION: DiffPanelSelection = { kind: "unstaged" }; | |
| 14 | 15 | |
| 15 | 16 | interface DiffPanelStoreState { |
| 16 | 17 | byThreadKey: Record<string, DiffPanelSelection>; |
| @@ -133,7 +134,11 @@ export const useDiffPanelStore = create<DiffPanelStoreState>()( · 116 unmodified lines | ||
| 133 | 134 | export function selectThreadDiffPanelSelection( |
| 134 | 135 | byThreadKey: Record<string, DiffPanelSelection>, |
| 135 | 136 | ref: ScopedThreadRef | null | undefined, |
| 137 | + hasWorkingTreeChanges = false, | |
| 136 | 138 | ): DiffPanelSelection { |
| 137 | 139 | 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 | + ); | |
| 139 | 144 | } |