Skip to content

fix(ui): keep unsent composer drafts across restart - #165

Merged
iamnbutler merged 1 commit into
mainfrom
codex/durable-composer-drafts
Oct 6, 2026
Merged

iamnbutler merged 1 commit into
mainfrom
codex/durable-composer-drafts

Conversation

@iamnbutler

@iamnbutler iamnbutler commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Fixes the browser-side cause of #77 (dogfooding, #5). Composer drafts were kept only in sessionStorage with a 500 ms debounce and an unmount flush, so a restart lost them and a quick quit could lose recent edits.

useDraft now stores editor document JSON in localStorage under the existing ace:draft:<scope> keys, writing synchronously on every edit. A live legacy sessionStorage draft migrates unless a durable draft already exists. The legacy entry is removed only once a durable copy exists, so clear cannot bring it back. Storage is best-effort: if it fails, the editor keeps working. Send, failed-admission, scope, and mode behavior are unchanged.

Validation used real headless Chromium with a persistent profile against an isolated host running the old and changed bundles (no mocks):

  • After profile restart, settled and rapid-close drafts were restored with identical JSON, Unicode, and hard breaks. Close was requested immediately after the last keystroke and the process closed 27 ms later. The old bundle lost both.
  • Two channels and the dashboard stayed isolated.
  • Old-editor legacy drafts migrated. A durable draft won when both existed.
  • A successful human send cleared the draft, which did not return after restart. A rejected agent admission (no provider) kept it. Discard cleared it.
  • The quota-failure path was reviewed only: the quota smoke never filled storage.
  • bun types, bun run ci, and React Doctor (100/100) passed.

Native WKWebView quit/relaunch is still pending while the macOS session is locked.

Built in Ace

Implementation, checks, commit, and this PR were done in an Ace channel. Codex coordinated and reviewed outside Ace (#9, #108) and viewed screenshots outside Ace (#51). Headless Playwright ran from Ace's shell because the native session was unavailable and Ace's browser tooling is incomplete (#50).

Store composer drafts in localStorage under the existing ace:draft scope keys and write each edit synchronously, so a browser or app restart, including a quit right after typing, keeps the draft. Live sessionStorage drafts migrate unless a durable draft already exists; the legacy copy is removed once a durable one exists. Storage failures never break editor input.

Refs #77
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant