| author | |
| committer | |
| log | 4ab5fca4ca9d8fb12b1442ed46107e15c4275bf2 |
| tree | 9fce1dc02d018a4326cdc5f7d3392fb1da46d1de |
| parent | 69a4fa82155f05637f73b3d2f61a5cdf9366b4b9 |
- Track and persist `envMode` in composer draft thread state
- Keep worktree mode for local drafts even when `worktreePath` is null
- Update branch toolbar/chat/sidebar to use effective draft env mode
- Add tests for env mode persistence and undefined option merges5 files changed, 111 insertions(+), 27 deletions(-)
apps/web/src/components/BranchToolbar.tsx+14-7| ... | ... | @@ -59,6 +59,10 @@ export default function BranchToolbar({ |
| 59 | 59 | const activeWorktreePath = serverThread?.worktreePath ?? draftThread?.worktreePath ?? null; |
| 60 | 60 | const branchCwd = activeWorktreePath ?? activeProject?.cwd ?? null; |
| 61 | 61 | const hasServerThread = serverThread !== undefined; |
| 62 | const effectiveEnvMode: "local" | "worktree" = | |
| 63 | activeWorktreePath || (!hasServerThread && draftThread?.envMode === "worktree") | |
| 64 | ? "worktree" | |
| 65 | : envMode; | |
| 62 | 66 | |
| 63 | 67 | // ── Queries ─────────────────────────────────────────────────────────── |
| 64 | 68 | |
| ... | ... | @@ -68,7 +72,7 @@ export default function BranchToolbar({ |
| 68 | 72 | const branchNames = branches.map((branch) => branch.name); |
| 69 | 73 | const branchByName = new Map(branches.map((branch) => [branch.name, branch])); |
| 70 | 74 | const trimmedBranchQuery = branchQuery.trim(); |
| 71 | const canCreateBranch = envMode === "local" && trimmedBranchQuery.length > 0; | |
| 75 | const canCreateBranch = effectiveEnvMode === "local" && trimmedBranchQuery.length > 0; | |
| 72 | 76 | const hasExactBranchMatch = branchByName.has(trimmedBranchQuery); |
| 73 | 77 | const createBranchItemValue = canCreateBranch |
| 74 | 78 | ? `__create_new_branch__:${trimmedBranchQuery}` |
| ... | ... | @@ -93,7 +97,7 @@ export default function BranchToolbar({ |
| 93 | 97 | const syncedBranch = deriveSyncedLocalBranch({ |
| 94 | 98 | activeThreadId, |
| 95 | 99 | activeWorktreePath, |
| 96 | envMode, | |
| 100 | envMode: effectiveEnvMode, | |
| 97 | 101 | activeThreadBranch, |
| 98 | 102 | queryBranches, |
| 99 | 103 | }); |
| ... | ... | @@ -127,7 +131,7 @@ export default function BranchToolbar({ |
| 127 | 131 | activeWorktreePath, |
| 128 | 132 | activeThreadBranch, |
| 129 | 133 | queryBranches, |
| 130 | envMode, | |
| 134 | effectiveEnvMode, | |
| 131 | 135 | dispatch, |
| 132 | 136 | hasServerThread, |
| 133 | 137 | setDraftThreadContext, |
| ... | ... | @@ -178,6 +182,7 @@ export default function BranchToolbar({ |
| 178 | 182 | setDraftThreadContext(threadId, { |
| 179 | 183 | branch, |
| 180 | 184 | worktreePath, |
| 185 | envMode: effectiveEnvMode, | |
| 181 | 186 | }); |
| 182 | 187 | }; |
| 183 | 188 | |
| ... | ... | @@ -186,7 +191,7 @@ export default function BranchToolbar({ |
| 186 | 191 | if (!api || !activeThreadId || !branchCwd) return; |
| 187 | 192 | |
| 188 | 193 | // For new worktree mode, selecting a branch picks the base branch. |
| 189 | if (envMode === "worktree" && !envLocked && !activeWorktreePath) { | |
| 194 | if (effectiveEnvMode === "worktree" && !envLocked && !activeWorktreePath) { | |
| 190 | 195 | setThreadError(null); |
| 191 | 196 | setThreadBranch(branch.name, null); |
| 192 | 197 | setIsBranchMenuOpen(false); |
| ... | ... | @@ -265,9 +270,11 @@ export default function BranchToolbar({ |
| 265 | 270 | variant="ghost" |
| 266 | 271 | className="text-muted-foreground/70 hover:text-foreground/80" |
| 267 | 272 | size="xs" |
| 268 | onClick={() => onEnvModeChange(envMode === "local" ? "worktree" : "local")} | |
| 273 | onClick={() => | |
| 274 | onEnvModeChange(effectiveEnvMode === "local" ? "worktree" : "local") | |
| 275 | } | |
| 269 | 276 | > |
| 270 | {envMode === "worktree" ? "New worktree" : "Local"} | |
| 277 | {effectiveEnvMode === "worktree" ? "New worktree" : "Local"} | |
| 271 | 278 | </Button> |
| 272 | 279 | )} |
| 273 | 280 | </div> |
| ... | ... | @@ -286,7 +293,7 @@ export default function BranchToolbar({ |
| 286 | 293 | > |
| 287 | 294 | <span className="max-w-[240px] truncate"> |
| 288 | 295 | {activeThreadBranch |
| 289 | ? envMode === "worktree" && !activeWorktreePath | |
| 296 | ? effectiveEnvMode === "worktree" && !activeWorktreePath | |
| 290 | 297 | ? `From ${activeThreadBranch}` |
| 291 | 298 | : activeThreadBranch |
| 292 | 299 | : "Select branch"} |
apps/web/src/components/ChatView.tsx+18-5| ... | ... | @@ -114,6 +114,7 @@ import { readNativeApi } from "~/nativeApi"; |
| 114 | 114 | import { getAppModelOptions, useAppSettings } from "../appSettings"; |
| 115 | 115 | import { |
| 116 | 116 | type ComposerImageAttachment, |
| 117 | type DraftThreadEnvMode, | |
| 117 | 118 | type DraftThreadState, |
| 118 | 119 | type PersistedComposerImageAttachment, |
| 119 | 120 | useComposerDraftStore, |
| ... | ... | @@ -465,6 +466,7 @@ export default function ChatView({ threadId }: ChatViewProps) { |
| 465 | 466 | ); |
| 466 | 467 | const clearComposerDraftContent = useComposerDraftStore((store) => store.clearComposerContent); |
| 467 | 468 | const clearDraftThread = useComposerDraftStore((store) => store.clearDraftThread); |
| 469 | const setDraftThreadContext = useComposerDraftStore((store) => store.setDraftThreadContext); | |
| 468 | 470 | const draftThread = useComposerDraftStore((store) => store.draftThreadsByThreadId[threadId] ?? null); |
| 469 | 471 | const promptRef = useRef(prompt); |
| 470 | 472 | const [isDragOverComposer, setIsDragOverComposer] = useState(false); |
| ... | ... | @@ -476,7 +478,7 @@ export default function ChatView({ threadId }: ChatViewProps) { |
| 476 | 478 | const [sendPhase, setSendPhase] = useState<SendPhase>("idle"); |
| 477 | 479 | const [isConnecting, _setIsConnecting] = useState(false); |
| 478 | 480 | const [isRevertingCheckpoint, setIsRevertingCheckpoint] = useState(false); |
| 479 | const [envMode, setEnvMode] = useState<"local" | "worktree">("local"); | |
| 481 | const [envMode, setEnvMode] = useState<DraftThreadEnvMode>("local"); | |
| 480 | 482 | const [isSwitchingRuntimeMode, setIsSwitchingRuntimeMode] = useState(false); |
| 481 | 483 | const [respondingRequestIds, setRespondingRequestIds] = useState<ApprovalRequestId[]>([]); |
| 482 | 484 | const [expandedWorkGroups, setExpandedWorkGroups] = useState<Record<string, boolean>>({}); |
| ... | ... | @@ -1386,8 +1388,16 @@ export default function ChatView({ threadId }: ChatViewProps) { |
| 1386 | 1388 | |
| 1387 | 1389 | useEffect(() => { |
| 1388 | 1390 | if (!activeThread?.id) return; |
| 1389 | setEnvMode(activeWorktreePath ? "worktree" : "local"); | |
| 1390 | }, [activeThread?.id, activeWorktreePath]); | |
| 1391 | if (activeWorktreePath) { | |
| 1392 | setEnvMode("worktree"); | |
| 1393 | return; | |
| 1394 | } | |
| 1395 | if (isLocalDraftThread) { | |
| 1396 | setEnvMode(draftThread?.envMode ?? "local"); | |
| 1397 | return; | |
| 1398 | } | |
| 1399 | setEnvMode("local"); | |
| 1400 | }, [activeThread?.id, activeWorktreePath, draftThread?.envMode, isLocalDraftThread]); | |
| 1391 | 1401 | |
| 1392 | 1402 | // Auto-resize textarea |
| 1393 | 1403 | useEffect(() => { |
| ... | ... | @@ -1973,11 +1983,14 @@ export default function ChatView({ threadId }: ChatViewProps) { |
| 1973 | 1983 | [scheduleComposerFocus, setComposerDraftEffort, threadId], |
| 1974 | 1984 | ); |
| 1975 | 1985 | const onEnvModeChange = useCallback( |
| 1976 | (mode: "local" | "worktree") => { | |
| 1986 | (mode: DraftThreadEnvMode) => { | |
| 1977 | 1987 | setEnvMode(mode); |
| 1988 | if (isLocalDraftThread) { | |
| 1989 | setDraftThreadContext(threadId, { envMode: mode }); | |
| 1990 | } | |
| 1978 | 1991 | scheduleComposerFocus(); |
| 1979 | 1992 | }, |
| 1980 | [scheduleComposerFocus], | |
| 1993 | [isLocalDraftThread, scheduleComposerFocus, setDraftThreadContext, threadId], | |
| 1981 | 1994 | ); |
| 1982 | 1995 | |
| 1983 | 1996 | const applyPromptReplacement = useCallback( |
apps/web/src/components/Sidebar.tsx+12-4| ... | ... | @@ -20,7 +20,7 @@ import { derivePendingApprovals } from "../session-logic"; |
| 20 | 20 | import { gitRemoveWorktreeMutationOptions, gitStatusQueryOptions } from "../lib/gitReactQuery"; |
| 21 | 21 | import { serverConfigQueryOptions } from "../lib/serverReactQuery"; |
| 22 | 22 | import { readNativeApi } from "../nativeApi"; |
| 23 | import { useComposerDraftStore } from "../composerDraftStore"; | |
| 23 | import { type DraftThreadEnvMode, useComposerDraftStore } from "../composerDraftStore"; | |
| 24 | 24 | import { toastManager } from "./ui/toast"; |
| 25 | 25 | import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "./ui/collapsible"; |
| 26 | 26 | import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; |
| ... | ... | @@ -345,17 +345,20 @@ export default function Sidebar() { |
| 345 | 345 | options?: { |
| 346 | 346 | branch?: string | null; |
| 347 | 347 | worktreePath?: string | null; |
| 348 | envMode?: DraftThreadEnvMode; | |
| 348 | 349 | }, |
| 349 | 350 | ): Promise<void> => { |
| 350 | 351 | const hasBranchOption = options?.branch !== undefined; |
| 351 | 352 | const hasWorktreePathOption = options?.worktreePath !== undefined; |
| 353 | const hasEnvModeOption = options?.envMode !== undefined; | |
| 352 | 354 | const storedDraftThread = getDraftThreadByProjectId(projectId); |
| 353 | 355 | if (storedDraftThread) { |
| 354 | 356 | return (async () => { |
| 355 | if (hasBranchOption || hasWorktreePathOption) { | |
| 357 | if (hasBranchOption || hasWorktreePathOption || hasEnvModeOption) { | |
| 356 | 358 | setDraftThreadContext(storedDraftThread.threadId, { |
| 357 | 359 | ...(hasBranchOption ? { branch: options?.branch ?? null } : {}), |
| 358 | 360 | ...(hasWorktreePathOption ? { worktreePath: options?.worktreePath ?? null } : {}), |
| 361 | ...(hasEnvModeOption ? { envMode: options?.envMode } : {}), | |
| 359 | 362 | }); |
| 360 | 363 | } |
| 361 | 364 | setProjectDraftThreadId(projectId, storedDraftThread.threadId); |
| ... | ... | @@ -372,10 +375,11 @@ export default function Sidebar() { |
| 372 | 375 | |
| 373 | 376 | const activeDraftThread = routeThreadId ? getDraftThread(routeThreadId) : null; |
| 374 | 377 | if (activeDraftThread && routeThreadId && activeDraftThread.projectId === projectId) { |
| 375 | if (hasBranchOption || hasWorktreePathOption) { | |
| 378 | if (hasBranchOption || hasWorktreePathOption || hasEnvModeOption) { | |
| 376 | 379 | setDraftThreadContext(routeThreadId, { |
| 377 | 380 | ...(hasBranchOption ? { branch: options?.branch ?? null } : {}), |
| 378 | 381 | ...(hasWorktreePathOption ? { worktreePath: options?.worktreePath ?? null } : {}), |
| 382 | ...(hasEnvModeOption ? { envMode: options?.envMode } : {}), | |
| 379 | 383 | }); |
| 380 | 384 | } |
| 381 | 385 | setProjectDraftThreadId(projectId, routeThreadId); |
| ... | ... | @@ -388,6 +392,7 @@ export default function Sidebar() { |
| 388 | 392 | createdAt, |
| 389 | 393 | branch: options?.branch ?? null, |
| 390 | 394 | worktreePath: options?.worktreePath ?? null, |
| 395 | envMode: options?.envMode ?? "local", | |
| 391 | 396 | }); |
| 392 | 397 | |
| 393 | 398 | await navigate({ |
| ... | ... | @@ -684,6 +689,9 @@ export default function Sidebar() { |
| 684 | 689 | void handleNewThread(projectId, { |
| 685 | 690 | branch: activeThread?.branch ?? activeDraftThread?.branch ?? null, |
| 686 | 691 | worktreePath: activeThread?.worktreePath ?? activeDraftThread?.worktreePath ?? null, |
| 692 | envMode: | |
| 693 | activeDraftThread?.envMode ?? | |
| 694 | (activeThread?.worktreePath ? "worktree" : "local"), | |
| 687 | 695 | }); |
| 688 | 696 | }; |
| 689 | 697 | |
| ... | ... | @@ -691,7 +699,7 @@ export default function Sidebar() { |
| 691 | 699 | return () => { |
| 692 | 700 | window.removeEventListener("keydown", onWindowKeyDown); |
| 693 | 701 | }; |
| 694 | }, [handleNewThread, keybindings, routeThreadId, state.projects, state.threads]); | |
| 702 | }, [getDraftThread, handleNewThread, keybindings, routeThreadId, state.projects, state.threads]); | |
| 695 | 703 | |
| 696 | 704 | const onCreateThreadClick = () => { |
| 697 | 705 | if (state.projects.length === 0) { |
apps/web/src/composerDraftStore.test.ts+30| ... | ... | @@ -183,12 +183,14 @@ describe("composerDraftStore project draft thread mapping", () => { |
| 183 | 183 | projectId, |
| 184 | 184 | branch: "feature/test", |
| 185 | 185 | worktreePath: "/tmp/worktree-test", |
| 186 | envMode: "worktree", | |
| 186 | 187 | createdAt: "2026-01-01T00:00:00.000Z", |
| 187 | 188 | }); |
| 188 | 189 | expect(useComposerDraftStore.getState().getDraftThread(threadId)).toEqual({ |
| 189 | 190 | projectId, |
| 190 | 191 | branch: "feature/test", |
| 191 | 192 | worktreePath: "/tmp/worktree-test", |
| 193 | envMode: "worktree", | |
| 192 | 194 | createdAt: "2026-01-01T00:00:00.000Z", |
| 193 | 195 | }); |
| 194 | 196 | }); |
| ... | ... | @@ -273,6 +275,7 @@ describe("composerDraftStore project draft thread mapping", () => { |
| 273 | 275 | projectId, |
| 274 | 276 | branch: "feature/next", |
| 275 | 277 | worktreePath: "/tmp/feature-next", |
| 278 | envMode: "worktree", | |
| 276 | 279 | }); |
| 277 | 280 | }); |
| 278 | 281 | |
| ... | ... | @@ -295,6 +298,33 @@ describe("composerDraftStore project draft thread mapping", () => { |
| 295 | 298 | projectId, |
| 296 | 299 | branch: "main", |
| 297 | 300 | worktreePath: "/tmp/main-worktree", |
| 301 | envMode: "worktree", | |
| 302 | }); | |
| 303 | }); | |
| 304 | ||
| 305 | it("preserves worktree env mode without a worktree path", () => { | |
| 306 | const store = useComposerDraftStore.getState(); | |
| 307 | store.setProjectDraftThreadId(projectId, threadId, { | |
| 308 | branch: "feature/base", | |
| 309 | worktreePath: null, | |
| 310 | envMode: "worktree", | |
| 311 | }); | |
| 312 | const runtimeUndefinedOptions = { | |
| 313 | branch: undefined, | |
| 314 | worktreePath: undefined, | |
| 315 | envMode: undefined, | |
| 316 | } as unknown as { | |
| 317 | branch?: string | null; | |
| 318 | worktreePath?: string | null; | |
| 319 | envMode?: "local" | "worktree"; | |
| 320 | }; | |
| 321 | store.setProjectDraftThreadId(projectId, threadId, runtimeUndefinedOptions); | |
| 322 | ||
| 323 | expect(useComposerDraftStore.getState().getDraftThread(threadId)).toMatchObject({ | |
| 324 | projectId, | |
| 325 | branch: "feature/base", | |
| 326 | worktreePath: null, | |
| 327 | envMode: "worktree", | |
| 298 | 328 | }); |
| 299 | 329 | }); |
| 300 | 330 | }); |
apps/web/src/composerDraftStore.ts+37-11| ... | ... | @@ -11,6 +11,7 @@ import { create } from "zustand"; |
| 11 | 11 | import { createJSONStorage, persist } from "zustand/middleware"; |
| 12 | 12 | |
| 13 | 13 | export const COMPOSER_DRAFT_STORAGE_KEY = "t3code:composer-drafts:v1"; |
| 14 | export type DraftThreadEnvMode = "local" | "worktree"; | |
| 14 | 15 | |
| 15 | 16 | export interface PersistedComposerImageAttachment { |
| 16 | 17 | id: string; |
| ... | ... | @@ -38,6 +39,7 @@ interface PersistedDraftThreadState { |
| 38 | 39 | createdAt: string; |
| 39 | 40 | branch: string | null; |
| 40 | 41 | worktreePath: string | null; |
| 42 | envMode: DraftThreadEnvMode; | |
| 41 | 43 | } |
| 42 | 44 | |
| 43 | 45 | interface PersistedComposerDraftStoreState { |
| ... | ... | @@ -61,6 +63,7 @@ export interface DraftThreadState { |
| 61 | 63 | createdAt: string; |
| 62 | 64 | branch: string | null; |
| 63 | 65 | worktreePath: string | null; |
| 66 | envMode: DraftThreadEnvMode; | |
| 64 | 67 | } |
| 65 | 68 | |
| 66 | 69 | interface ProjectDraftThread extends DraftThreadState { |
| ... | ... | @@ -80,6 +83,7 @@ interface ComposerDraftStoreState { |
| 80 | 83 | branch?: string | null; |
| 81 | 84 | worktreePath?: string | null; |
| 82 | 85 | createdAt?: string; |
| 86 | envMode?: DraftThreadEnvMode; | |
| 83 | 87 | }, |
| 84 | 88 | ) => void; |
| 85 | 89 | setDraftThreadContext: ( |
| ... | ... | @@ -89,6 +93,7 @@ interface ComposerDraftStoreState { |
| 89 | 93 | worktreePath?: string | null; |
| 90 | 94 | projectId?: ProjectId; |
| 91 | 95 | createdAt?: string; |
| 96 | envMode?: DraftThreadEnvMode; | |
| 92 | 97 | }, |
| 93 | 98 | ) => void; |
| 94 | 99 | clearProjectDraftThreadId: (projectId: ProjectId) => void; |
| ... | ... | @@ -203,6 +208,16 @@ function normalizePersistedAttachment(value: unknown): PersistedComposerImageAtt |
| 203 | 208 | }; |
| 204 | 209 | } |
| 205 | 210 | |
| 211 | function normalizeDraftThreadEnvMode( | |
| 212 | value: unknown, | |
| 213 | fallbackWorktreePath: string | null, | |
| 214 | ): DraftThreadEnvMode { | |
| 215 | if (value === "local" || value === "worktree") { | |
| 216 | return value; | |
| 217 | } | |
| 218 | return fallbackWorktreePath ? "worktree" : "local"; | |
| 219 | } | |
| 220 | ||
| 206 | 221 | function normalizePersistedComposerDraftState(value: unknown): PersistedComposerDraftStoreState { |
| 207 | 222 | if (!value || typeof value !== "object") { |
| 208 | 223 | return EMPTY_PERSISTED_DRAFT_STORE_STATE; |
| ... | ... | @@ -227,6 +242,7 @@ function normalizePersistedComposerDraftState(value: unknown): PersistedComposer |
| 227 | 242 | const createdAt = candidateDraftThread.createdAt; |
| 228 | 243 | const branch = candidateDraftThread.branch; |
| 229 | 244 | const worktreePath = candidateDraftThread.worktreePath; |
| 245 | const normalizedWorktreePath = typeof worktreePath === "string" ? worktreePath : null; | |
| 230 | 246 | if (typeof projectId !== "string" || projectId.length === 0) { |
| 231 | 247 | continue; |
| 232 | 248 | } |
| ... | ... | @@ -237,7 +253,8 @@ function normalizePersistedComposerDraftState(value: unknown): PersistedComposer |
| 237 | 253 | ? createdAt |
| 238 | 254 | : new Date().toISOString(), |
| 239 | 255 | branch: typeof branch === "string" ? branch : null, |
| 240 | worktreePath: typeof worktreePath === "string" ? worktreePath : null, | |
| 256 | worktreePath: normalizedWorktreePath, | |
| 257 | envMode: normalizeDraftThreadEnvMode(candidateDraftThread.envMode, normalizedWorktreePath), | |
| 241 | 258 | }; |
| 242 | 259 | } |
| 243 | 260 | } |
| ... | ... | @@ -263,6 +280,7 @@ function normalizePersistedComposerDraftState(value: unknown): PersistedComposer |
| 263 | 280 | createdAt: new Date().toISOString(), |
| 264 | 281 | branch: null, |
| 265 | 282 | worktreePath: null, |
| 283 | envMode: "local", | |
| 266 | 284 | }; |
| 267 | 285 | } else if (draftThreadsByThreadId[threadId as ThreadId]?.projectId !== projectId) { |
| 268 | 286 | draftThreadsByThreadId[threadId as ThreadId] = { |
| ... | ... | @@ -456,6 +474,10 @@ export const useComposerDraftStore = create<ComposerDraftStoreState>()( |
| 456 | 474 | set((state) => { |
| 457 | 475 | const existingThread = state.draftThreadsByThreadId[threadId]; |
| 458 | 476 | const previousThreadIdForProject = state.projectDraftThreadIdByProjectId[projectId]; |
| 477 | const nextWorktreePath = | |
| 478 | options?.worktreePath === undefined | |
| 479 | ? (existingThread?.worktreePath ?? null) | |
| 480 | : (options.worktreePath ?? null); | |
| 459 | 481 | const nextDraftThread: DraftThreadState = { |
| 460 | 482 | projectId, |
| 461 | 483 | createdAt: options?.createdAt ?? existingThread?.createdAt ?? new Date().toISOString(), |
| ... | ... | @@ -463,10 +485,10 @@ export const useComposerDraftStore = create<ComposerDraftStoreState>()( |
| 463 | 485 | options?.branch === undefined |
| 464 | 486 | ? (existingThread?.branch ?? null) |
| 465 | 487 | : (options.branch ?? null), |
| 466 | worktreePath: | |
| 467 | options?.worktreePath === undefined | |
| 468 | ? (existingThread?.worktreePath ?? null) | |
| 469 | : (options.worktreePath ?? null), | |
| 488 | worktreePath: nextWorktreePath, | |
| 489 | envMode: | |
| 490 | options?.envMode ?? | |
| 491 | (nextWorktreePath ? "worktree" : (existingThread?.envMode ?? "local")), | |
| 470 | 492 | }; |
| 471 | 493 | const hasSameProjectMapping = previousThreadIdForProject === threadId; |
| 472 | 494 | const hasSameDraftThread = |
| ... | ... | @@ -474,7 +496,8 @@ export const useComposerDraftStore = create<ComposerDraftStoreState>()( |
| 474 | 496 | existingThread.projectId === nextDraftThread.projectId && |
| 475 | 497 | existingThread.createdAt === nextDraftThread.createdAt && |
| 476 | 498 | existingThread.branch === nextDraftThread.branch && |
| 477 | existingThread.worktreePath === nextDraftThread.worktreePath; | |
| 499 | existingThread.worktreePath === nextDraftThread.worktreePath && | |
| 500 | existingThread.envMode === nextDraftThread.envMode; | |
| 478 | 501 | if (hasSameProjectMapping && hasSameDraftThread) { |
| 479 | 502 | return state; |
| 480 | 503 | } |
| ... | ... | @@ -518,6 +541,8 @@ export const useComposerDraftStore = create<ComposerDraftStoreState>()( |
| 518 | 541 | if (nextProjectId.length === 0) { |
| 519 | 542 | return state; |
| 520 | 543 | } |
| 544 | const nextWorktreePath = | |
| 545 | options.worktreePath === undefined ? existing.worktreePath : (options.worktreePath ?? null); | |
| 521 | 546 | const nextDraftThread: DraftThreadState = { |
| 522 | 547 | projectId: nextProjectId, |
| 523 | 548 | createdAt: |
| ... | ... | @@ -525,16 +550,17 @@ export const useComposerDraftStore = create<ComposerDraftStoreState>()( |
| 525 | 550 | ? existing.createdAt |
| 526 | 551 | : options.createdAt || existing.createdAt, |
| 527 | 552 | branch: options.branch === undefined ? existing.branch : (options.branch ?? null), |
| 528 | worktreePath: | |
| 529 | options.worktreePath === undefined | |
| 530 | ? existing.worktreePath | |
| 531 | : (options.worktreePath ?? null), | |
| 553 | worktreePath: nextWorktreePath, | |
| 554 | envMode: | |
| 555 | options.envMode ?? | |
| 556 | (nextWorktreePath ? "worktree" : (existing.envMode ?? "local")), | |
| 532 | 557 | }; |
| 533 | 558 | const isUnchanged = |
| 534 | 559 | nextDraftThread.projectId === existing.projectId && |
| 535 | 560 | nextDraftThread.createdAt === existing.createdAt && |
| 536 | 561 | nextDraftThread.branch === existing.branch && |
| 537 | nextDraftThread.worktreePath === existing.worktreePath; | |
| 562 | nextDraftThread.worktreePath === existing.worktreePath && | |
| 563 | nextDraftThread.envMode === existing.envMode; | |
| 538 | 564 | if (isUnchanged) { |
| 539 | 565 | return state; |
| 540 | 566 | } |