From 0bfec13583ac77844fd5fdba9f469a0dde8a3a90 Mon Sep 17 00:00:00 2001 From: Adolanium <94890352+Adolanium@users.noreply.github.com> Date: Thu, 27 Aug 2026 20:08:23 +0300 Subject: [PATCH] fix(web): do not persist terminal context chips Drafts saved chip metadata and dropped the selected text. Reload filled empty text, so the chip still showed and send skipped the snippet. Do not write those chips. Drop old ones on load and strip the leftover prompt markers. --- apps/web/src/composerDraftStore.test.ts | 46 +++++-------- apps/web/src/composerDraftStore.ts | 87 ++----------------------- 2 files changed, 24 insertions(+), 109 deletions(-) diff --git a/apps/web/src/composerDraftStore.test.ts b/apps/web/src/composerDraftStore.test.ts index 20c6603f773b..69b4eb4c1e25 100644 --- a/apps/web/src/composerDraftStore.test.ts +++ b/apps/web/src/composerDraftStore.test.ts @@ -470,7 +470,10 @@ describe("composerDraftStore terminal contexts", () => { expect(draft?.terminalContexts.map((context) => context.id)).toEqual(["ctx-2", "ctx-1"]); }); - it("omits terminal context text from persisted drafts", () => { + it("does not persist terminal context chips", () => { + useComposerDraftStore + .getState() + .setPrompt(threadRef, `${INLINE_TERMINAL_CONTEXT_PLACEHOLDER} keep this`); useComposerDraftStore .getState() .addTerminalContext(threadRef, makeTerminalContext({ id: "ctx-persist" })); @@ -481,27 +484,19 @@ describe("composerDraftStore terminal contexts", () => { }; }; const persistedState = persistApi.getOptions().partialize(useComposerDraftStore.getState()) as { - draftsByThreadKey?: Record> }>; + draftsByThreadKey?: Record< + string, + { prompt?: string; terminalContexts?: Array> } + >; }; + const persistedDraft = + persistedState.draftsByThreadKey?.[threadKeyFor(threadId, TEST_ENVIRONMENT_ID)]; - expect( - persistedState.draftsByThreadKey?.[threadKeyFor(threadId, TEST_ENVIRONMENT_ID)] - ?.terminalContexts?.[0], - "Expected terminal context metadata to be persisted.", - ).toMatchObject({ - id: "ctx-persist", - terminalId: "default", - terminalLabel: "Terminal 1", - lineStart: 4, - lineEnd: 5, - }); - expect( - persistedState.draftsByThreadKey?.[threadKeyFor(threadId, TEST_ENVIRONMENT_ID)] - ?.terminalContexts?.[0]?.text, - ).toBeUndefined(); + expect(persistedDraft?.terminalContexts).toBeUndefined(); + expect(persistedDraft?.prompt).toBe(" keep this"); }); - it("hydrates persisted terminal contexts without in-memory snapshot text", () => { + it("drops persisted terminal context chips on hydrate", () => { const persistApi = useComposerDraftStore.persist as unknown as { getOptions: () => { merge: ( @@ -514,7 +509,7 @@ describe("composerDraftStore terminal contexts", () => { { draftsByThreadId: { [threadId]: { - prompt: INLINE_TERMINAL_CONTEXT_PLACEHOLDER, + prompt: `${INLINE_TERMINAL_CONTEXT_PLACEHOLDER} keep this`, attachments: [], terminalContexts: [ { @@ -535,16 +530,9 @@ describe("composerDraftStore terminal contexts", () => { useComposerDraftStore.getInitialState(), ); - expect(mergedState.draftsByThreadKey[threadKeyFor(threadId)]?.terminalContexts).toMatchObject([ - { - id: "ctx-rehydrated", - terminalId: "default", - terminalLabel: "Terminal 1", - lineStart: 4, - lineEnd: 5, - text: "", - }, - ]); + const hydrated = mergedState.draftsByThreadKey[threadKeyFor(threadId)]; + expect(hydrated?.terminalContexts).toEqual([]); + expect(hydrated?.prompt).toBe(" keep this"); }); it("sanitizes malformed persisted drafts during merge", () => { diff --git a/apps/web/src/composerDraftStore.ts b/apps/web/src/composerDraftStore.ts index f20385ee04f4..e2af27bbb621 100644 --- a/apps/web/src/composerDraftStore.ts +++ b/apps/web/src/composerDraftStore.ts @@ -1177,51 +1177,6 @@ function normalizePersistedElementContextDraft( }; } -function normalizePersistedTerminalContextDraft( - value: unknown, -): PersistedTerminalContextDraft | null { - if (!value || typeof value !== "object") { - return null; - } - const candidate = value as Record; - const id = candidate.id; - const threadId = candidate.threadId; - const createdAt = candidate.createdAt; - const lineStart = candidate.lineStart; - const lineEnd = candidate.lineEnd; - if ( - typeof id !== "string" || - id.length === 0 || - typeof threadId !== "string" || - threadId.length === 0 || - typeof createdAt !== "string" || - createdAt.length === 0 || - typeof lineStart !== "number" || - !Number.isFinite(lineStart) || - typeof lineEnd !== "number" || - !Number.isFinite(lineEnd) - ) { - return null; - } - const terminalId = typeof candidate.terminalId === "string" ? candidate.terminalId.trim() : ""; - const terminalLabel = - typeof candidate.terminalLabel === "string" ? candidate.terminalLabel.trim() : ""; - if (terminalId.length === 0 || terminalLabel.length === 0) { - return null; - } - const normalizedLineStart = Math.max(1, Math.floor(lineStart)); - const normalizedLineEnd = Math.max(normalizedLineStart, Math.floor(lineEnd)); - return { - id, - threadId: threadId as ThreadId, - createdAt, - terminalId, - terminalLabel, - lineStart: normalizedLineStart, - lineEnd: normalizedLineEnd, - }; -} - function normalizeDraftThreadEnvMode( value: unknown, fallbackWorktreePath: string | null, @@ -1694,12 +1649,6 @@ function normalizePersistedDraftsByThreadId( return normalized ? [normalized] : []; }) : []; - const terminalContexts = Array.isArray(draftCandidate.terminalContexts) - ? draftCandidate.terminalContexts.flatMap((entry) => { - const normalized = normalizePersistedTerminalContextDraft(entry); - return normalized ? [normalized] : []; - }) - : []; const elementContexts = Array.isArray(draftCandidate.elementContexts) ? draftCandidate.elementContexts.flatMap((entry) => { const normalized = normalizePersistedElementContextDraft(entry); @@ -1716,10 +1665,7 @@ function normalizePersistedDraftsByThreadId( draftCandidate.interactionMode === "plan" || draftCandidate.interactionMode === "default" ? draftCandidate.interactionMode : null; - const prompt = ensureInlineTerminalContextPlaceholders( - promptCandidate, - terminalContexts.length, - ); + const prompt = stripInlineTerminalContextPlaceholders(promptCandidate); // If the draft already has the v3 shape, use it directly const legacyDraftCandidate = draftValue as LegacyPersistedComposerThreadDraftState; let modelSelectionByProvider: Partial> = {}; @@ -1769,9 +1715,8 @@ function normalizePersistedDraftsByThreadId( const hasModelData = Object.keys(modelSelectionByProvider).length > 0 || activeProvider !== null; if ( - promptCandidate.length === 0 && + prompt.length === 0 && attachments.length === 0 && - terminalContexts.length === 0 && elementContexts.length === 0 && reviewComments.length === 0 && !hasModelData && @@ -1795,7 +1740,6 @@ function normalizePersistedDraftsByThreadId( nextDraftsByThreadKey[normalizedThreadKey] = { prompt, attachments, - ...(terminalContexts.length > 0 ? { terminalContexts } : {}), ...(elementContexts.length > 0 ? { elementContexts } : {}), ...(reviewComments.length > 0 ? { reviewComments } : {}), ...(hasModelData @@ -1899,10 +1843,10 @@ function partializeComposerDraftStoreState( } const hasModelData = Object.keys(draft.modelSelectionByProvider).length > 0 || draft.activeProvider !== null; + const persistablePrompt = stripInlineTerminalContextPlaceholders(draft.prompt); if ( - draft.prompt.length === 0 && + persistablePrompt.length === 0 && draft.persistedAttachments.length === 0 && - draft.terminalContexts.length === 0 && draft.elementContexts.length === 0 && draft.previewAnnotations.length === 0 && draft.reviewComments.length === 0 && @@ -1913,21 +1857,8 @@ function partializeComposerDraftStoreState( continue; } const persistedDraft: DeepMutable = { - prompt: draft.prompt, + prompt: persistablePrompt, attachments: draft.persistedAttachments, - ...(draft.terminalContexts.length > 0 - ? { - terminalContexts: draft.terminalContexts.map((context) => ({ - id: context.id, - threadId: context.threadId, - createdAt: context.createdAt, - terminalId: context.terminalId, - terminalLabel: context.terminalLabel, - lineStart: context.lineStart, - lineEnd: context.lineEnd, - })), - } - : {}), ...(draft.elementContexts.length > 0 ? { elementContexts: draft.elementContexts.map((context) => ({ @@ -2193,15 +2124,11 @@ function toHydratedThreadDraft( const activeProvider = normalizeProviderInstanceId(persistedDraft.activeProvider) ?? null; return { - prompt: persistedDraft.prompt, + prompt: stripInlineTerminalContextPlaceholders(persistedDraft.prompt), images: hydrateImagesFromPersisted(persistedDraft.attachments), nonPersistedImageIds: [], persistedAttachments: [...persistedDraft.attachments], - terminalContexts: - persistedDraft.terminalContexts?.map((context) => ({ - ...context, - text: "", - })) ?? [], + terminalContexts: [], elementContexts: persistedDraft.elementContexts?.map((context) => ({ ...context,