From f14f41b894448298a86865c9114e6700245356e7 Mon Sep 17 00:00:00 2001 From: maria Date: Wed, 2 Sep 2026 05:03:47 -0400 Subject: [PATCH] fix(web): preserve composer draft during worktree setup (#9197) Co-authored-by: maria-rcks <254055478+maria-rcks@users.noreply.github.com> --- apps/web/src/composerDraftStore.test.ts | 6 ++++-- apps/web/src/composerDraftStore.ts | 11 ++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/web/src/composerDraftStore.test.ts b/apps/web/src/composerDraftStore.test.ts index e1d9d462a661682a4def287bfeb05db788c8978b..068bb1ff41f03177f5291bf455ad0e354d8c9bd7 100644 --- a/apps/web/src/composerDraftStore.test.ts +++ b/apps/web/src/composerDraftStore.test.ts @@ -1458,17 +1458,18 @@ describe("composerDraftStore project draft thread mapping", () => { expect(draftFor(threadId, TEST_ENVIRONMENT_ID)?.prompt).toBe("keep me"); }); - it("finalizes a promoted draft after the canonical thread route is active", () => { + it("moves composer edits made during promotion to the canonical thread", () => { const store = useComposerDraftStore.getState(); store.setProjectDraftThreadId(projectRef, draftId, { threadId }); - store.setPrompt(draftId, "promote me"); markPromotedDraftThread(threadId); + store.setPrompt(draftId, "typed during setup"); finalizePromotedDraftThreadByRef(scopeThreadRef(TEST_ENVIRONMENT_ID, threadId)); expect(useComposerDraftStore.getState().getDraftThreadByProjectRef(projectRef)).toBeNull(); expect(useComposerDraftStore.getState().getDraftThread(draftId)).toBeNull(); expect(draftByKey(draftId)).toBeUndefined(); + expect(draftFor(threadId, TEST_ENVIRONMENT_ID)?.prompt).toBe("typed during setup"); }); it("finalizes a matching materialized draft even when promotion was not pre-marked", () => { @@ -1481,6 +1482,7 @@ describe("composerDraftStore project draft thread mapping", () => { expect(useComposerDraftStore.getState().getDraftThreadByProjectRef(projectRef)).toBeNull(); expect(useComposerDraftStore.getState().getDraftThread(draftId)).toBeNull(); expect(draftByKey(draftId)).toBeUndefined(); + expect(draftFor(threadId, TEST_ENVIRONMENT_ID)?.prompt).toBe("promote me"); }); it("updates branch context on an existing draft thread", () => { diff --git a/apps/web/src/composerDraftStore.ts b/apps/web/src/composerDraftStore.ts index 6ef8c651885829e181196474d85884d0694a15f4..0032ababbae4c0a383547a1dca550923fc16b9c2 100644 --- a/apps/web/src/composerDraftStore.ts +++ b/apps/web/src/composerDraftStore.ts @@ -1583,6 +1583,7 @@ function removeDraftThreadReferences( | "logicalProjectDraftThreadKeyByLogicalProjectKey" >, threadKey: string, + composerDestination?: ScopedThreadRef, ): Pick< ComposerDraftStoreState, | "draftThreadsByThreadKey" @@ -1597,7 +1598,11 @@ function removeDraftThreadReferences( const { [threadKey]: _removedDraftThread, ...restDraftThreadsByThreadKey } = state.draftThreadsByThreadKey; const { [threadKey]: removedComposerDraft, ...restDraftsByThreadKey } = state.draftsByThreadKey; - revokeDraftThreadPreviewUrls(removedComposerDraft); + if (composerDestination && removedComposerDraft) { + restDraftsByThreadKey[composerTargetKey(composerDestination)] = removedComposerDraft; + } else { + revokeDraftThreadPreviewUrls(removedComposerDraft); + } return { draftsByThreadKey: restDraftsByThreadKey, draftThreadsByThreadKey: restDraftThreadsByThreadKey, @@ -2785,10 +2790,10 @@ const composerDraftStore = create()( } set((state) => { const existing = state.draftThreadsByThreadKey[threadKey]; - if (!isDraftThreadPromoting(existing)) { + if (!existing || !isDraftThreadPromoting(existing)) { return state; } - return removeDraftThreadReferences(state, threadKey); + return removeDraftThreadReferences(state, threadKey, existing.promotedTo ?? undefined); }); }, clearDraftThread: (threadRef) => { -- 2.54.0