authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-07-30 03:48:33-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-30 03:48:33-07:00
logf0c6ba994670bbd9eeab97eab6701d5c3589178d
treee1f5c75380c74e109cee50e2591676c6a0a3c8ce
parent90f3913a81380af7aeafac62f8132aeb98345a12
signature Signed by PGP key B5690EEEBB952194

fix(web): keep worktree default when switching a draft's machine (#4964)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

3 files changed, 20 insertions(+), 24 deletions(-)

apps/web/src/composerDraftStore.test.ts+8-4
......@@ -1103,13 +1103,14 @@ describe("composerDraftStore project draft thread mapping", () => {
11031103 });
11041104 });
11051105
1106 it("clears branch and worktree context when remapping a draft to another environment", () => {
1106 it("clears branch and worktree but keeps env mode when remapping a draft to another environment", () => {
11071107 const store = useComposerDraftStore.getState();
11081108 store.setProjectDraftThreadId(projectRef, draftId, {
11091109 threadId,
11101110 branch: "feature/local-only",
11111111 worktreePath: "/tmp/local-worktree",
11121112 envMode: "worktree",
1113 startFromOrigin: true,
11131114 });
11141115
11151116 store.setLogicalProjectDraftThreadId(scopedProjectKey(projectRef), remoteProjectRef, draftId, {
......@@ -1121,17 +1122,19 @@ describe("composerDraftStore project draft thread mapping", () => {
11211122 projectId,
11221123 branch: null,
11231124 worktreePath: null,
1124 envMode: "local",
1125 envMode: "worktree",
1126 startFromOrigin: true,
11251127 });
11261128 });
11271129
1128 it("clears branch and worktree context when changing a draft thread project ref", () => {
1130 it("clears branch and worktree but keeps env mode when changing a draft thread project ref", () => {
11291131 const store = useComposerDraftStore.getState();
11301132 store.setProjectDraftThreadId(projectRef, draftId, {
11311133 threadId,
11321134 branch: "feature/local-only",
11331135 worktreePath: "/tmp/local-worktree",
11341136 envMode: "worktree",
1137 startFromOrigin: true,
11351138 });
11361139
11371140 store.setDraftThreadContext(draftId, {
......@@ -1143,7 +1146,8 @@ describe("composerDraftStore project draft thread mapping", () => {
11431146 projectId,
11441147 branch: null,
11451148 worktreePath: null,
1146 envMode: "local",
1149 envMode: "worktree",
1150 startFromOrigin: true,
11471151 });
11481152 });
11491153});
apps/web/src/composerDraftStore.ts+11-18
......@@ -1340,6 +1340,10 @@ function createDraftThreadState(
13401340 interactionMode?: ProviderInteractionMode;
13411341 },
13421342): DraftThreadState {
1343 // A project change (including switching environments within a logical
1344 // project) invalidates machine-specific context: the branch may not exist
1345 // there and the worktree path certainly doesn't. The user's *intent* —
1346 // env mode and start-from-origin — is machine-independent and carries.
13431347 const projectChanged =
13441348 existingThread !== undefined &&
13451349 (existingThread.environmentId !== projectRef.environmentId ||
......@@ -1358,9 +1362,7 @@ function createDraftThreadState(
13581362 : (options.branch ?? null);
13591363 const nextStartFromOrigin =
13601364 options?.startFromOrigin === undefined
1361 ? projectChanged
1362 ? false
1363 : (existingThread?.startFromOrigin ?? false)
1365 ? (existingThread?.startFromOrigin ?? false)
13641366 : options.startFromOrigin;
13651367 return {
13661368 threadId,
......@@ -1374,12 +1376,7 @@ function createDraftThreadState(
13741376 branch: nextBranch,
13751377 worktreePath: nextWorktreePath,
13761378 envMode:
1377 options?.envMode ??
1378 (nextWorktreePath
1379 ? "worktree"
1380 : projectChanged
1381 ? "local"
1382 : (existingThread?.envMode ?? "local")),
1379 options?.envMode ?? (nextWorktreePath ? "worktree" : (existingThread?.envMode ?? "local")),
13831380 startFromOrigin: nextStartFromOrigin,
13841381 promotedTo: null,
13851382 };
......@@ -2343,6 +2340,9 @@ const composerDraftStore = create<ComposerDraftStoreState>()(
23432340 ) {
23442341 return state;
23452342 }
2343 // Mirrors createDraftThreadState: a project/environment change
2344 // drops machine-specific context (branch, worktree path) but
2345 // keeps the user's env mode and start-from-origin intent.
23462346 const projectChanged =
23472347 nextProjectRef.environmentId !== existing.environmentId ||
23482348 nextProjectRef.projectId !== existing.projectId;
......@@ -2360,9 +2360,7 @@ const composerDraftStore = create<ComposerDraftStoreState>()(
23602360 : (options.branch ?? null);
23612361 const nextStartFromOrigin =
23622362 options.startFromOrigin === undefined
2363 ? projectChanged
2364 ? false
2365 : existing.startFromOrigin
2363 ? existing.startFromOrigin
23662364 : options.startFromOrigin;
23672365 const nextDraftThread: DraftThreadState = {
23682366 threadId: existing.threadId,
......@@ -2378,12 +2376,7 @@ const composerDraftStore = create<ComposerDraftStoreState>()(
23782376 branch: nextBranch,
23792377 worktreePath: nextWorktreePath,
23802378 envMode:
2381 options.envMode ??
2382 (nextWorktreePath
2383 ? "worktree"
2384 : projectChanged
2385 ? "local"
2386 : (existing.envMode ?? "local")),
2379 options.envMode ?? (nextWorktreePath ? "worktree" : (existing.envMode ?? "local")),
23872380 startFromOrigin: nextStartFromOrigin,
23882381 promotedTo: existing.promotedTo ?? null,
23892382 };
apps/web/src/hooks/useHandleNewThread.ts+1-2
......@@ -183,8 +183,7 @@ export function useNewThreadHandler() {
183183 // The workspace context must also ride along here: when projectRef
184184 // targets a different physical member of the logical project,
185185 // createDraftThreadState treats the remap as a project change and
186 // would otherwise wipe branch/worktree and force "local" mode,
187 // undoing the write above.
186 // would otherwise wipe branch/worktree, undoing the write above.
188187 setLogicalProjectDraftThreadId(
189188 logicalProjectKey,
190189 projectRef,