Skip to content

Hardening + message rename, and a backend-update refresh prompt#1

Merged
akazwz merged 2 commits into
mainfrom
chore/hardening-and-version-prompt
Jun 26, 2026
Merged

Hardening + message rename, and a backend-update refresh prompt#1
akazwz merged 2 commits into
mainfrom
chore/hardening-and-version-prompt

Conversation

@akazwz

@akazwz akazwz commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

What

Two commits from a review/hardening pass plus one new feature.

fix: — correctness + hardening + naming (11a6616)

Bug fixes

  • DO RPC error status (the big one): an AppError thrown inside a Durable Object loses its class identity and its status property crossing the RPC boundary — only message survives (Cloudflare RPC error-handling docs). So instanceof AppError in onError was always false and every DO error returned 500. An expired/invalid mailbox therefore returned 500, the frontend's 401/403 recovery never fired, and a returning user got stuck on a dead inbox. Recovered via httpStatusForError (message → status).
  • Stale auth: getToken/getAddress now prefer the in-memory copy, so a failed localStorage.setItem (quota / private mode) can't leave a stale token authenticating against the old mailbox.
  • Silent mail loss: re-throw on a failed message store instead of accepting-and-dropping; bounce oversized mail below the SQLite row limit.
  • CLI: surface a timeout (not status text) when an error-body read aborts; loadConfig throws on a corrupt config and --force recovers from it.
  • Frontend: roll back the optimistic "read" on load failure; clear the saved mailbox only on 401/403, not transient errors; detach socket handlers before close.

Refactor / naming

  • Standardize the data/RPC/storage layer on "message": DO init→create, receiveEmail→receiveMessage, broadcastNewEmail→broadcastNewMessage; WS payload new_email→new_message; frontend types Email/EmailDetail→Message/MessageDetail (drop the never-returned preview); CLI flag --new→--force.
  • Dedupe: shared CLI createMailbox helper (CLI + MCP); shared content-page components (Breadcrumb, CheckList, NumberedSteps, FaqSection, CtaSection, JsonLd) and SEO helpers (pageMeta, breadcrumbList, faqPage); drop a write-only domain put, a needless useMemo, and duplicated WS teardown; encodeURIComponent parity between the two API clients.

feat: — prompt to refresh when the backend redeploys (5ca018e)

Stamps every API response with the current deploy id (version_metadata binding → X-Smails-Version). The frontend compares it against the id first seen this session; on a change it shows a persistent "refresh" toast. Piggybacks on existing API traffic — no polling, no new endpoint.

⚠️ Decision for review: two breaking renames

These are intentional (pre-1.0, all first-party consumers updated in lockstep), but they break previously-documented public contracts. The version-prompt feature mitigates the WS one (stale tabs get prompted to refresh rather than silently failing). Flagging in case you'd rather keep back-compat aliases:

  • WS new_emailnew_message — open tabs on the old bundle stop reacting until refreshed.
  • CLI --new--forcecreate --new now no-ops the replace. A --new alias would be a one-liner if any scripts rely on it.

Note on deploy

Merging to main triggers a Workers Builds production deploy. The rename means the worker (emits new_message) and the frontend (listens for new_message) must ship together — they do, in this PR.

Verification

Biome ✅ · worker tsc ✅ · CLI tsc ✅ · frontend typecheck ✅ · frontend build (5 pages prerendered) ✅ · wrangler dev confirmed the X-Smails-Version header is emitted.

https://claude.ai/code/session_01SjtGZgvWwgF6oEZRhiqz3F

akazwz added 2 commits June 26, 2026 04:19
…on "message"

Bug fixes:
- worker: an AppError thrown inside a Durable Object loses its class identity and
  status across the RPC boundary, so every DO error surfaced as 500. Recover the
  status from the (surviving) message via httpStatusForError, so an expired or
  invalid mailbox returns 401 and the frontend can re-provision instead of getting
  stuck on a dead inbox.
- frontend: prefer the in-memory auth copy over localStorage so a failed setItem
  (quota / private mode) can't leave a stale token in use.
- worker: re-throw on a failed message store instead of silently accepting and
  dropping the mail; bounce oversized mail below the SQLite row limit.
- cli: surface a timeout (not the status text) when an error-body read is aborted;
  loadConfig now throws on a corrupt/unreadable config and --force recovers from it.
- frontend: roll back the optimistic "read" when a message fails to load; only
  clear the saved mailbox on 401/403, not on transient errors; detach socket
  handlers before close so a stale onclose can't mutate the live connection.

Refactor / naming:
- Standardize the data/RPC/storage layer on "message": DO methods init->create,
  receiveEmail->receiveMessage, broadcastNewEmail->broadcastNewMessage; WebSocket
  payload new_email->new_message; frontend types Email/EmailDetail->Message/
  MessageDetail (drop the never-returned preview field); CLI flag --new->--force.
- Dedupe: a shared cli createMailbox helper for the CLI and MCP surfaces; shared
  content-page components (Breadcrumb, CheckList, NumberedSteps, FaqSection,
  CtaSection, JsonLd) and SEO helpers (pageMeta, breadcrumbList, faqPage); drop a
  write-only domain put, an unnecessary useMemo, and duplicated WebSocket teardown;
  encodeURIComponent parity between the two API clients.

Claude-Session: https://claude.ai/code/session_01SjtGZgvWwgF6oEZRhiqz3F
Stamp every API response with the current deploy id (via the Cloudflare
version_metadata binding) in an X-Smails-Version header. The frontend records the
id seen on the first response and compares it on every subsequent one; when it
changes the backend has been redeployed, so a persistent toast invites the user
to refresh and load the new bundle.

Piggybacks on existing API traffic — no polling, no new endpoint — and closes the
gap where a stale tab on an old bundle would silently miss WebSocket
notifications after a deploy.

Claude-Session: https://claude.ai/code/session_01SjtGZgvWwgF6oEZRhiqz3F
@akazwz akazwz merged commit 1ee04f5 into main Jun 26, 2026
1 check passed
@akazwz akazwz deleted the chore/hardening-and-version-prompt branch June 26, 2026 11:32
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