| author | |
| committer | |
| log | 38e0bb07bb24894524f1502ac0487c5ea4ff85de |
| tree | 2ebd44463832745f9fb133fadf816b9bc1e01463 |
| parent | 765e1b5fc044b899741a11dcba141bb2c49c6c37 |
| signature | Signed by PGP key B5690EEEBB952194 |
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) { |
| 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) { |
| 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) { |
| 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 |
apps/web/src/components/DiffPanel.tsx+14-4| ... | ... | @@ -178,13 +178,19 @@ const DIFF_PANEL_UNSAFE_CSS = ` |
| 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 |
| 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 |
| 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); |
apps/web/src/diffPanelStore.test.ts+15-1| ... | ... | @@ -9,12 +9,26 @@ const THREAD_REF = scopeThreadRef(EnvironmentId.make("environment-1"), ThreadId. |
| 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"); |
apps/web/src/diffPanelStore.ts+6-1| ... | ... | @@ -11,6 +11,7 @@ export type DiffPanelSelection = |
| 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>()( |
| 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 | } |