diff --git a/apps/desktop/src/app/session/hooks/use-prompt-actions/submit.ts b/apps/desktop/src/app/session/hooks/use-prompt-actions/submit.ts index 33909016e7..cfa405bf23 100644 --- a/apps/desktop/src/app/session/hooks/use-prompt-actions/submit.ts +++ b/apps/desktop/src/app/session/hooks/use-prompt-actions/submit.ts @@ -18,6 +18,7 @@ import { $composerAttachments, type ComposerAttachment, mainComposerScope, + revokeDiscardedAttachmentPreviews, terminalContextBlocksFromDraft } from '@/store/composer' import { $hudMode } from '@/store/hud' @@ -487,7 +488,11 @@ export function useSubmitPrompt(deps: SubmitPromptDeps) { // After sync rewrites refs, refresh the optimistic message in place so the // transcript shows the resolved @file: ref rather than the local path. - const rewriteOptimistic = (sid: string) => + // Sync replaces blob: previews with workspace-resolvable refs, so any + // blob: URL the rewritten refs no longer retain is this consumer's last + // reference — release it (#63682 ownership handoff). + const rewriteOptimistic = (sid: string, syncedAttachments: ComposerAttachment[] = attachments) => { + revokeDiscardedAttachmentPreviews(attachments, syncedAttachments) updateSessionState( sid, state => ({ @@ -496,8 +501,13 @@ export function useSubmitPrompt(deps: SubmitPromptDeps) { }), targetStoredSessionId ) + } const dropOptimistic = (sid: null | string) => { + // The optimistic bubble is gone, so its blob: previews die with it — + // unless a rejected-submit restore already re-loaded the attachments + // into the composer, which re-owns those URLs (#63682 handoff). + revokeDiscardedAttachmentPreviews(attachments, usingComposerAttachments ? $composerAttachments.get() : []) if (!sid) { if (targetIsCurrentView()) { scope.setMessages(current => current.filter(m => m.id !== optimisticId)) @@ -811,7 +821,7 @@ export function useSubmitPrompt(deps: SubmitPromptDeps) { // the gateway receives @file: paths that resolve in its workspace. // Images keep their inline bounded thumbnail — see optimisticAttachmentRef. attachmentRefs = syncedAttachments.map(optimisticAttachmentRef).filter((r): r is string => Boolean(r)) - rewriteOptimistic(liveSessionId) + rewriteOptimistic(liveSessionId, syncedAttachments) const text = buildContextText(syncedAttachments) // Another Desktop window may own a newer transcript while this one