Hardening + message rename, and a backend-update refresh prompt#1
Merged
Conversation
…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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
What
Two commits from a review/hardening pass plus one new feature.
fix:— correctness + hardening + naming (11a6616)Bug fixes
AppErrorthrown inside a Durable Object loses its class identity and itsstatusproperty crossing the RPC boundary — onlymessagesurvives (Cloudflare RPC error-handling docs). Soinstanceof AppErrorinonErrorwas 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 viahttpStatusForError(message → status).getToken/getAddressnow prefer the in-memory copy, so a failedlocalStorage.setItem(quota / private mode) can't leave a stale token authenticating against the old mailbox.loadConfigthrows on a corrupt config and--forcerecovers from it.Refactor / naming
init→create,receiveEmail→receiveMessage,broadcastNewEmail→broadcastNewMessage; WS payloadnew_email→new_message; frontend typesEmail/EmailDetail→Message/MessageDetail(drop the never-returnedpreview); CLI flag--new→--force.createMailboxhelper (CLI + MCP); shared content-page components (Breadcrumb,CheckList,NumberedSteps,FaqSection,CtaSection,JsonLd) and SEO helpers (pageMeta,breadcrumbList,faqPage); drop a write-onlydomainput, a needlessuseMemo, and duplicated WS teardown;encodeURIComponentparity between the two API clients.feat:— prompt to refresh when the backend redeploys (5ca018e)Stamps every API response with the current deploy id (
version_metadatabinding →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.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:
new_email→new_message— open tabs on the old bundle stop reacting until refreshed.--new→--force—create --newnow no-ops the replace. A--newalias would be a one-liner if any scripts rely on it.Note on deploy
Merging to
maintriggers a Workers Builds production deploy. The rename means the worker (emitsnew_message) and the frontend (listens fornew_message) must ship together — they do, in this PR.Verification
Biome ✅ · worker
tsc✅ · CLItsc✅ · frontendtypecheck✅ · frontendbuild(5 pages prerendered) ✅ ·wrangler devconfirmed theX-Smails-Versionheader is emitted.https://claude.ai/code/session_01SjtGZgvWwgF6oEZRhiqz3F