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 #88

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

`src/pages/oops.ts` is new: a status and a sentence in, a page of this shop out — header, stylesheet, what happened in a person's words, and a way out of it. Every refusal `src/server.ts` makes now goes through it (cross-site `403`, stale-form `403`, `415`, `400`, `413`, `408`, `500`), and `shop.tooMany`'s `429`, which had a page of its own, shares it. The refusal sentences live in one table at th…

shipped production is serving it deploy ab962da

opened this

src/pages/oops.ts is new: a status and a sentence in, a page of this shop out — header, stylesheet, what happened in a person's words, and a way out of it. Every refusal src/server.ts makes now goes through it (cross-site 403, stale-form 403, 415, 400, 413, 408, 500), and shop.tooMany's 429, which had a page of its own, shares it. The refusal sentences live in one table at the top of src/server.ts so all seven can be read at once.

Machine callers are untouched: src/http.ts gains wantsHtml(), and a caller whose accept does not mention text/html gets exactly the line it got before (forbidden, expected a form, that body was too large, …), while POST /webhooks/stripe — the one raw route — answers plain text however it asks. The stale-form page links to /cart and / and still sets the session cookie, so going back and sending the form again succeeds rather than failing the same way twice; there is a test that does exactly that. The 500 page says the shop failed and nothing else — no message, no path, no stack — and console.error keeps the exception whole. Everything still leaves through finish(), so the security floor and cache-control: no-store are on a refusal like any other response.

Worth a look in the preview: POST a form with a stale token (or open a tab, wait for the session to roll, and submit) — the page should carry your cart count in the header and both links. Six new tests in src/server.test.ts cover each refusal; all six fail on main and the suite is 142 green, including --randomize. The one test seam worth reviewing: the 500 test lends /search a handler that throws and gives it back in a finally, because a 500 has no other door. What I did not do: /admin/events's own 403 and the webhook's 400s are handler answers to machine callers and stay plain text, and no dependency was added.

🤖 Generated with Claude Code

Closes #45

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs