composer: persist the draft on input, keep unconfirmed sends in a durable outbox #122

Merged
void merged 3 commits from void/draft-persist into dev 2026-11-12 21:17:30 +00:00
Member

Cause: the draft lived in a memory-only jotai atom (roomIdToMsgDraftAtomFamily) written only when RoomInput unmounted, so a reload lost it; a sent message left the editor at once and was held nowhere durable (js-sdk pending events are not persisted).

Fix: composerStore.ts (localStorage, per account/room): draft written debounced on input and flushed on pagehide/hidden, restored on load; outbox entry written at send under a txn id, removed only when sendMessage resolves, marked failed on rejection. RoomOutbox shows pending/failed entries above the composer with Resend (same txn id, so a send that did land is deduplicated) and Discard.

Not covered: file uploads, reply draft, edit drafts; a message sent from a tab that dies before the localStorage write.

🤖 Generated with Claude Code

Cause: the draft lived in a memory-only jotai atom (`roomIdToMsgDraftAtomFamily`) written only when RoomInput unmounted, so a reload lost it; a sent message left the editor at once and was held nowhere durable (js-sdk pending events are not persisted). Fix: `composerStore.ts` (localStorage, per account/room): draft written debounced on input and flushed on pagehide/hidden, restored on load; outbox entry written at send under a txn id, removed only when sendMessage resolves, marked failed on rejection. `RoomOutbox` shows pending/failed entries above the composer with Resend (same txn id, so a send that did land is deduplicated) and Discard. Not covered: file uploads, reply draft, edit drafts; a message sent from a tab that dies before the localStorage write. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
composer: persist the draft on input, keep unconfirmed sends in a durable outbox
Some checks failed
Build pull request / Build pull request (pull_request) Has been cancelled
7316c92ff3
The draft lived in a memory-only jotai atom written only when RoomInput
unmounted, so a reload lost it. A sent message was cleared from the editor
at once and held nowhere durable. The draft is now written to localStorage
(debounced, flushed on pagehide/hidden) and restored on load; a send stays
in a per-account outbox under its txn id until the server confirms, shown
above the composer with Resend and Discard.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
outbox: survive a refused storage write, mark reload-interrupted sends, hide Resend on live sends
Some checks failed
Build pull request / Build pull request (pull_request) Has been cancelled
350ec987e0
Soul pass findings: a failed localStorage write no longer loses the entry
(memory copy wins until a write succeeds), a stale draft is dropped when
its rewrite fails, storage reads/removes cannot throw, and pending entries
older than the page load read as interrupted with Resend available.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
lint: import order, explicit no-op test stubs
All checks were successful
Build pull request / Build pull request (pull_request) Successful in 3m9s
a531976675
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
void merged commit 92a98664c9 into dev 2026-11-12 21:17:30 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
GameCult/Ratatoskr!122
No description provided.