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