From fe59dc2dacf7c27e41c2c0cc3af33ae3924cdf40 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Fri, 25 Sep 2026 15:27:42 -0500 Subject: [PATCH] fix(desktop): revoke optimistic blob previews when the consumer is discarded or replaced Completes the #63682 ownership handoff flagged in review on #66546: the queued-prompt side revoked discarded blob: previews, but the optimistic bubble's previews were never released. Revoke when the optimistic message is dropped (unless a rejected-submit restore re-loaded the attachments into the composer, which re-owns them) and when the post-sync rewrite replaces blob: refs with workspace-resolvable ones. Co-authored-by: stantheman0128 --- .../app/session/hooks/use-prompt-actions/submit.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) 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