cmux-next composer: a chat's unsent pictures and files come back when it is reopened or restored
Leo asked that a chat's unsent draft (its text, pictures and attached files) survive reopening the chat, and archiving or deleting it and then restoring it, not only a quit (#22999).
acpmux already keeps the draft's text in the session's meta, so this adds the chips:
- Native (
AgentPaneDraftAttachments):- Each chat's chips are kept in app support under
agent-draft-attachments/<sha256 of session id>/. Each chip's bytes are their own file, listed indraft.json. The file helpers are shared with the relaunch wake (AgentPaneAttachmentFiles). - Writes for one chat run in order on a per-session queue.
- Limits: 100 chats (the oldest are pruned) and 64 MB per chat.
- The page reaches it through two new ops on
AgentPageProvider:chat.draftAttachments(write; answers the ids whose bytes the host holds) andchat.draftAttachments.get.
- Each chat's chips are kept in app support under
- Page (
keptAttachments.ts):- Reads the chat's chips when the chat is shown.
- Writes them 300 ms after they stop changing. A chip's bytes go once; after that only its id goes.
- Flushes on
pagehide, on hidden, and on unmount. - Sending clears the kept chips, and a write still pending is dropped then, so sent chips never come back.
- A harness switch carries the chips over.
- Quick chat keeps nothing.
Tests:
webviews/test/composer-kept-attachments.test.tsxcovers:- reopen;
- archive/restore;
- send clearing;
- paste-then-send inside the wait;
- a refusal putting chips back;
- stale held ids;
- flush on hide.
ChatDraftAttachmentsTestscovers:- a round trip through a fresh store on the same folder (relaunch);
- clearing on send;
- the byte cap.
- The fleet ran
ChatDraftAttachmentsTests,AgentPaneRelaunchWakeTestsandChatScrollRestoreTestsat this head.
Known limits:
- A brand-new chat with no session yet keeps nothing on tab close. Its text isn't kept either.
- After a harness switch, the old chat keeps a copy of the chips that moved.
- Two pages on one chat are last-write-wins.
Independent subagent review: PASS (the five findings from round one are fixed; two nits are fixed here: a refused write goes again, and the page's JSON is checked before encoding).
🤖 Generated with Claude Code
Agent session: cosmic-gray
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Keeps a chat's unsent pictures and files (its composer chips) across reopening the chat, archiving/deleting and restoring it, and relaunches. Previously only the draft text survived; now the chips are stored per chat on disk and restored when the chat is shown again.
- Host side (
AgentPaneDraftAttachments): stores each chat's chips underagent-draft-attachments/<hashed session id>/; chip contents are separate files listed indraft.json. - Page side (
keptAttachments.ts): writes chips 300 ms after they settle, flushes on page close/hide, and clears them on send so sent chips never return. - Adds
chat.draftAttachmentsandchat.draftAttachments.getops onAgentPageProvider. - Shares the file-moving logic with the relaunch wake (
AgentPaneAttachmentFiles). - Refactors
AgentPaneRelaunchWaketo use the shared attachment file helpers. - Limits: 100 chats kept (oldest pruned), 64 MB per chat. Quick chats keep nothing.
Known limits
- A brand-new chat with no session keeps nothing on tab close.
- After a harness switch, both old and new chats hold copies.
- Two pages open on the same chat conflict (last write wins).