authorgravatar for maria@kuuro.netmaria <maria@kuuro.net> 2026-09-02 05:03:47-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-02 05:03:47-04:00
logf14f41b894448298a86865c9114e6700245356e7
treedc9e369d5003c3e53371419ec40ac82b06ce0f8a
parent7e9d5a7efa70f92f91d960a4f50243ba44d805da
signature Signed by PGP key B5690EEEBB952194

fix(web): preserve composer draft during worktree setup (#9197)

Co-authored-by: maria-rcks <254055478+maria-rcks@users.noreply.github.com>

2 files changed, 12 insertions(+), 5 deletions(-)

apps/web/src/composerDraftStore.test.ts+4-2
......@@ -1458,17 +1458,18 @@ describe("composerDraftStore project draft thread mapping", () => {
14581458 expect(draftFor(threadId, TEST_ENVIRONMENT_ID)?.prompt).toBe("keep me");
14591459 });
14601460
1461 it("finalizes a promoted draft after the canonical thread route is active", () => {
1461 it("moves composer edits made during promotion to the canonical thread", () => {
14621462 const store = useComposerDraftStore.getState();
14631463 store.setProjectDraftThreadId(projectRef, draftId, { threadId });
1464 store.setPrompt(draftId, "promote me");
14651464 markPromotedDraftThread(threadId);
1465 store.setPrompt(draftId, "typed during setup");
14661466
14671467 finalizePromotedDraftThreadByRef(scopeThreadRef(TEST_ENVIRONMENT_ID, threadId));
14681468
14691469 expect(useComposerDraftStore.getState().getDraftThreadByProjectRef(projectRef)).toBeNull();
14701470 expect(useComposerDraftStore.getState().getDraftThread(draftId)).toBeNull();
14711471 expect(draftByKey(draftId)).toBeUndefined();
1472 expect(draftFor(threadId, TEST_ENVIRONMENT_ID)?.prompt).toBe("typed during setup");
14721473 });
14731474
14741475 it("finalizes a matching materialized draft even when promotion was not pre-marked", () => {
......@@ -1481,6 +1482,7 @@ describe("composerDraftStore project draft thread mapping", () => {
14811482 expect(useComposerDraftStore.getState().getDraftThreadByProjectRef(projectRef)).toBeNull();
14821483 expect(useComposerDraftStore.getState().getDraftThread(draftId)).toBeNull();
14831484 expect(draftByKey(draftId)).toBeUndefined();
1485 expect(draftFor(threadId, TEST_ENVIRONMENT_ID)?.prompt).toBe("promote me");
14841486 });
14851487
14861488 it("updates branch context on an existing draft thread", () => {
apps/web/src/composerDraftStore.ts+8-3
......@@ -1583,6 +1583,7 @@ function removeDraftThreadReferences(
15831583 | "logicalProjectDraftThreadKeyByLogicalProjectKey"
15841584 >,
15851585 threadKey: string,
1586 composerDestination?: ScopedThreadRef,
15861587): Pick<
15871588 ComposerDraftStoreState,
15881589 | "draftThreadsByThreadKey"
......@@ -1597,7 +1598,11 @@ function removeDraftThreadReferences(
15971598 const { [threadKey]: _removedDraftThread, ...restDraftThreadsByThreadKey } =
15981599 state.draftThreadsByThreadKey;
15991600 const { [threadKey]: removedComposerDraft, ...restDraftsByThreadKey } = state.draftsByThreadKey;
1600 revokeDraftThreadPreviewUrls(removedComposerDraft);
1601 if (composerDestination && removedComposerDraft) {
1602 restDraftsByThreadKey[composerTargetKey(composerDestination)] = removedComposerDraft;
1603 } else {
1604 revokeDraftThreadPreviewUrls(removedComposerDraft);
1605 }
16011606 return {
16021607 draftsByThreadKey: restDraftsByThreadKey,
16031608 draftThreadsByThreadKey: restDraftThreadsByThreadKey,
......@@ -2785,10 +2790,10 @@ const composerDraftStore = create<ComposerDraftStoreState>()(
27852790 }
27862791 set((state) => {
27872792 const existing = state.draftThreadsByThreadKey[threadKey];
2788 if (!isDraftThreadPromoting(existing)) {
2793 if (!existing || !isDraftThreadPromoting(existing)) {
27892794 return state;
27902795 }
2791 return removeDraftThreadReferences(state, threadKey);
2796 return removeDraftThreadReferences(state, threadKey, existing.promotedTo ?? undefined);
27922797 });
27932798 },
27942799 clearDraftThread: (threadRef) => {