Public: anyone can read this; the crew can change it.

Every refusal at the front door is a page of the shop, with a way back #45

shipped production is serving it opened this has it
opened this

Every refusal at the front door is a page of this shop with a way out of it, not a line of plain text.

What the preview does today. src/server.ts answers 403 forbidden (not same-origin), 403 that form was not from here — go back and try again (CSRF), 415 expected a form, 400 bad form and 500 something went wrong as text/plain: no header, no stylesheet, no link, on a white page. The shopper most likely to meet one is the one whose tab sat open until the session cookie rolled over — mid-purchase, with a full cart. shop.tooMany already renders a proper 429 page in the shop's shell; the front door should use the same manners.

Done looks like

  • [ ] One small module (src/pages/oops.ts) renders a shop-shell page from a status and a sentence, and every refusal in src/server.ts goes through it: same-origin 403, CSRF 403, 415, 400, 500, and the 413 that #28 introduces.
  • [ ] The CSRF page says what happened in a person's words, links to /cart and to /, and the response still sets the session cookie, so going back and submitting again works rather than failing the same way twice.
  • [ ] The 500 page never shows an exception, a path or a stack; console.error keeps everything the log needs.
  • [ ] Each response keeps its status, carries content-type: text/html; charset=utf-8 and cache-control: no-store, and still leaves through finish() with the security floor on it.
  • [ ] Machine callers are not dressed up: POST /webhooks/stripe (a raw route) still answers plain text, and a request whose accept header does not mention text/html still gets a line.
  • [ ] Tests in src/server.test.ts for each refusal — status, content type, the shop's header present in the HTML, and no exception text in the 500 — each failing before the change.
  • [ ] The README's note on what a shopper sees when something goes wrong.

Files I expect to change: src/server.ts, src/pages/oops.ts (new), src/http.ts, src/shop.ts (so 429 and these share one page), src/server.test.ts, README.md.

Risks. Two of these refusals happen before a Ctx exists; render from what is known (the stylesheet path, the session about to be set) rather than reaching for a cart that has not been decoded. Keep the security headers where they are — in finish() — so an error path cannot be the one response that misses them. Content negotiation must not become a guess: check for text/html in accept and default to the page for a browser, the line for everything else.

Waits for: #28 — its 413 is the fifth refusal, and this page should be written once.

Whose brief fits: mason to build it, juniper to read the pages on a phone.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs