🤖 fix: persist chat draft images #1187
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Persist pasted image attachments in ChatInput drafts so they survive workspace switches and app restarts.
Key points:
inputImages:<scopeId>localStorage key and included it in workspace storage copy/delete logic.Tests:
make static-check📋 Implementation Plan
Persist pasted image attachments in ChatInput drafts
Why images are currently lost
usePersistedState(getInputKey(...))insrc/browser/components/ChatInput/index.tsx.useState<ImageAttachment[]>([])), so they reset whenever:Goal
Make pasted/drag-dropped image attachments persist the same way draft text does:
Recommended approach (minimal + safe)
Persist image attachments best-effort to localStorage under a new key, while keeping the authoritative in-memory state in React.
Rationale:
usePersistedStateas the source of truth).Implementation plan
1) Add a workspace-scoped storage key for draft images
Files:
src/common/constants/storage.tsActions:
getInputImagesKey(scopeId: string): string→inputImages:${scopeId}scopeIdinputs asgetInputKey: workspaceId for workspace variant;getPendingScopeId(projectPath)for creation variant.)getInputImagesKeytoPERSISTENT_WORKSPACE_KEY_FUNCTIONSso images are:copyWorkspaceStorage)deleteWorkspaceStorage)migrateWorkspaceStorage)2) Load persisted images when ChatInput mounts
Files:
src/browser/components/ChatInput/index.tsxActions:
storageKeysto includeimagesKey:getInputImagesKey(props.workspaceId)getInputImagesKey(getPendingScopeId(props.projectPath))imageAttachmentsfrom localStorage:useState(() => readPersistedState<ImageAttachment[]>(storageKeys.imagesKey, [])){id,url,mediaType}strings, log and fall back to[](and consider clearing the bad key).3) Persist image changes back to localStorage (best-effort)
Files:
src/browser/components/ChatInput/index.tsxActions:
imageAttachmentsas a normaluseState.updatePersistedState(storageKeys.imagesKey, nextImages.length ? nextImages : undefined).handlePaste)handleDrop)handleRemoveImage)setDraftOptional but recommended (quota UX):
JSON.stringify(nextImages).lengthimageAttachmentsin memory so the user can send the message.4) Clear pending image drafts on successful workspace creation
Files:
src/browser/components/ChatInput/useCreationWorkspace.tsActions:
pendingInputKey), also clear:updatePersistedState(getInputImagesKey(getPendingScopeId(projectPath)), undefined)5) Tests
Goal: prevent regressions in the future.
Recommended tests:
src/common/constants/storage.test.tsgetInputImagesKey()formatcopyWorkspaceStorage()copies the image key (using a fake localStorage)deleteWorkspaceStorage()removes the image keysrc/browser/components/ChatInputtest (new):inputImages:<workspaceId>containing oneImageAttachmentChatInputfor that workspace and assert the thumbnail<img src=...>is present6) Manual QA checklist
Alternative: store draft images on disk (more robust, more work)
If localStorage size limits or sync write jank are a concern, store draft images under
~/.mux/sessions/<workspaceId>/draft-images.jsonusingSessionFileManager(server-side) and exposeworkspace.getDraft()/workspace.setDraft()ORPC endpoints.Pros:
Cons:
Generated with
mux• Model:openai:gpt-5.2• Thinking:xhigh