Public: anyone can read this; the crew can change it.
Build atelier #2
Check · Review · Land · Live done — Landed and live in production.
The site is built, tested (37 tests, all passing) and pushed.
shipped production is serving it for issue #1 deploy d62169e
The first-run /admin/set-up screen can be claimed by whoever POSTs first: on the public preview I took the studio account with a single unauthenticated request. Make the claim require a secret only the operator has — a one-time setup token printed to the run log (and shown in the app terminal, FORGE_APP_SSH), or an ADMIN_PASSWORD from the environment — and refuse set-up otherwise. Then reset the account on this preview so the one I created is gone.
You were right, and it was a real hole: /admin/set-up accepted any unauthenticated POST while the account was unclaimed.
Claiming the studio account now needs a secret only the operator has. ADMIN_PASSWORD in the environment claims it at boot and the claim screen never appears; otherwise the app mints a one-time token at every boot while the account is unclaimed and prints it as atelier: set-up token 1a2b3-… in the run log and on the first screen of the app's terminal, and /admin asks for that token beside the new password. The token is compared with timingSafeEqual, spent the moment the account is claimed, replaced at each boot until then, and claim attempts are rate-limited per address like sign-ins; once an account exists, /admin/set-up is refused outright.
The preview account is reset by the deploy of this push: prepareClaim() records how an account was claimed, and any account left by the previous version — which needed no secret — is deleted at boot along with its sessions, with a line in the log saying so. The one you created is gone, the cookie it issued opens nothing, and the fresh token is in the preview's run log or the terminal at FORGE_APP_SSH.
One thing to look at beyond the fix: bun test runs every file in a single process, so all four test files were sharing src/db.ts and its database — the isolation I thought they had was luck of ordering. Each file now spawns the app as a real subprocess with its own data directory (src/testing.ts), reads the token from its log and asserts against its database. That also let me test the boot path honestly: src/session.test.ts restarts the app to check a stale token is refused, that an old-scheme account is wiped, and that a short ADMIN_PASSWORD is ignored rather than trusted. 44 tests pass.
The site is built, tested (37 tests, all passing) and pushed.
atelier is now a design studio's website whose every word and picture lives in SQLite under
FORGE_APP_DATA, not in a template: a home page, a work gallery with case-study pages, an about page and a contact form, plus/admin, where the first visitor sets the one argon2id account and can then edit any of the site's ~40 text fields, write and reorder case studies, upload images and read contact messages. Uploads are decoded and resized to 1600/800/320 px with Bun's built-inBun.Image(so no dependency was needed at all) and served from/media/<hash>/…as WebP with a year-long immutable cache; the width and height in the markup are read back off the encoded bytes, so nothing shifts as a page loads.Worth looking at in the preview:
/and/work/museu-do-azulejofor the typographic scale and the seeded copy;/adminfor the first-run password screen and the four editing rooms; and an image upload, which is the quickest way to see the resize and thesrcsetappear on the gallery card. The public pages ship zero JavaScript and no web fonts — all motion (load rise, card hover, scroll reveal) sits inside@media (prefers-reduced-motion: no-preference), and the CSP was tightened to dropunsafe-inlinefor styles, which a test now enforces by checking the markup contains no inlinestyle=or<script.Chosen not to do: no email is sent when the contact form is submitted (messages are stored and read in the editor), there is no password reset or second account (delete the
adminandsessionrows to start over), and I did not run Lighthouse itself — I have no browser here, so the score is engineered rather than measured: server-rendered first paint, hashed immutable assets, sized images,robots.txt,sitemap.xml, per-page descriptions and canonical URLs, contrast-checked colour and focus rings.src/tty.tswas rewritten to edit the same data over ssh (text, publishing, reordering, messages) and now parses multi-key input, andFORGE_APP_SSHis printed verbatim on the studio dashboard, or it says the terminal is off when unset.Closes #1