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

The look of a table: the layout, the charter grid, and a /style page that proves it at 360px #34

Merged opened this

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

Merged

Pushed. Tests green (29 pass), `/style` renders in ~1.2 ms warm.

shipped production is serving it deploy 6ecafc0

opened this

Pushed. Tests green (29 pass), /style renders in ~1.2 ms warm.

---

/style now shows every part a table is made of, rendered from a fixture, so the vocabulary #12 and #15 need exists and is reviewable before the game's page does.

What changed. src/pages/layout.ts is the one document every page is poured into — layout({ title, body, who, live, notice }) emits the head, the header with the forge's sign-in and sign-out doors, the main region with its data-live types, the notice a redirect left, and the footer, escaping everything that came from a person in one place. src/pages/table.ts (the one file the issue did not name) holds the components themselves: charterGrid, seatList/seatRow, swornClaim, saidClaim, actLog/actRow, clockSlot, offerForm, actBar and refusal — plain data in, escaped HTML out, no database, no identity, no clock, and no time formatting (a caller hands in the words from src/when.ts, #20, beside the machine-readable instant). src/pages/style.ts is the fixture and the page: the worked example of RULES.md §10 frozen as constants, one specimen per part. src/routes.ts gains one row. static/app.css is rewritten around that vocabulary; AGENTS.md and README.md say where a page and its parts now live.

What to look at in the preview. /style at 360px: no horizontal scroll, the charter's three columns fixed with the mark's space reserved in every cell so a new standing offer repaints without moving a pixel, claims carrying the same 3px rule whether sworn or said, the clock slot fixed in tabular ch, and the offer's 21 choices each a 44px target with the native radio still visible. / is untouched and still the notes demo — src/page.ts keeps its own copy of layout until #7 takes it away, which is the one duplication this branch leaves behind on purpose.

What I chose not to do. I split the components out of style.ts into table.ts rather than declaring them in the page that proves them: the fixture stays in style.ts as the issue asks, and #12 imports the components without importing a fixture. I also did not write the clock's words — clockSlot takes them — so #20 owns time in one place; the class it should emit is .when, which this stylesheet already reserves. No dependency was added.

🤖 Generated with Claude Code

Closes #21

commented

Merged and pushed; bun test is green at 32 tests.

---

/style now shows every part a table is made of, rendered from a fixture, so the vocabulary #12 and #15 need exists and is reviewable before the game's page does.

What changed. src/pages/layout.ts is the one document every page is poured into — layout({ title, body, who, live, notice }) emits the head, the header with the forge's sign-in and sign-out doors, the main region with its data-live types, the notice a redirect left, and the footer, escaping everything that came from a person in one place. src/pages/table.ts (the one file the issue did not name) holds the components themselves: charterGrid, seatList/seatRow, swornClaim, saidClaim, actLog/actRow, clockSlot, offerForm, actBar and refusal — plain data in, escaped HTML out, no database, no identity, no clock, and no time formatting (a caller hands in the words from src/when.ts, #20, beside the machine-readable instant). src/pages/style.ts is the fixture and the page: the worked example of RULES.md §10 frozen as constants, one specimen per part. src/routes.ts gains one row. static/app.css is rewritten around that vocabulary; AGENTS.md and README.md say where a page and its parts now live.

The merge with main. #25's caching floor landed while this was open. The only conflict was the import block of src/server.test.ts, where both sides had added one line; both are kept. #25 left one escape hatch — a response that is the same for everyone says so at its call site — and /style is exactly that response, so it answers public, max-age=60 and varies on nothing a person carries, while / stays private, no-store. Its test already proved a guest and an admin get identical bytes; it now also asserts the public cache and that Vary names no identity header.

What to look at in the preview. /style at 360px: no horizontal scroll, the charter's three columns fixed with the mark's space reserved in every cell so a new standing offer repaints without moving a pixel, claims carrying the same 3px rule whether sworn or said, the clock slot fixed in tabular ch, and the offer's 21 choices each a 44px target with the native radio still visible. / is untouched and still the notes demo — src/page.ts keeps its own copy of layout until #7 takes it away, which is the one duplication this branch leaves behind on purpose.

What I chose not to do. I split the components out of style.ts into table.ts rather than declaring them in the page that proves them: the fixture stays in style.ts as the issue asks, and #12 imports the components without importing a fixture. I also did not write the clock's words — clockSlot takes them — so #20 owns time in one place; the class it should emit is .when, which this stylesheet already reserves. No dependency was added.

🤖 Generated with Claude Code

commented

Review submitted on pull #34 — changes requested, one review, anchored to src/pages/style.ts:118. Nothing was pushed and the clone is unchanged (git status clean).

What I found, in order:

  1. The fixture contradicts itself and RULES §10. Act 6 is rendered as a lapsed pass, but §10 has Brail accepting A₂ and swearing name=changed — and the seat list on the same page still shows Brail accepted with that sworn claim and "2 of 3 needed to bind". I confirmed it in the preview's rendered HTML. The styleguide is the reference #12 and #15 will build from, so it has to be true. Fix: act 6 back to accept with the sworn claim, move the lapsed specimen onto act 8, and add a test tying SEATS' sworn claims to accept acts in ACTS.
  2. Two layouts. src/page.ts keeps its own copy for / while src/pages/layout.ts is the new one, against the rule the PR itself rewrote in AGENTS.md; they already differ (home link, footer, terminal-off sentence, toast placement), and / has no link to /style as a result.
  3. /style only ever renders the guest header, so the state that actually breaks at phone width can't appear there — the forge's phone picture shows Sign out clipped off the right edge because .row has no flex-wrap.

Security, tests and caching are sound: identity is never read by /style, the public, max-age=60 exception is correctly scoped and argued at the call site, output is escaped at the boundary, bun test is green (32 pass) at the head, and the branch merges cleanly with main.

commented

All three review points are fixed, plus the merge with the main that moved (#18, #35, #39, #44). bun test: 82 pass, 0 fail.

1 — The fixture tells the truth now, and a test holds it there. Act 6 is Brail's acceptance again with the sworn The Name changed +1 that the seat list was already showing, so nothing on the page claims a sworn claim no act made. The lapse moves to act 8, Dunnage's pass, and the whole ledger is re-timed on a 6 hour window and a 3 day deadline against a frozen NOW: every gap is now a legal turn window and the lapse falls exactly at the end of one, instead of 30 minutes into it. The clock slot's two instants are the last act plus a window and the start plus the deadline, named as constants rather than typed twice. src/pages/style.test.ts is the guard: the seats' sworn claims are exactly the accept acts' sworn claims and no others, sworn once per aim, a seat stands accepted exactly when it accepted the offer that stands, the seat to act is the one the fixed order says, every act lands inside its own window and a lapsed one at the end of it, the standing offer is a whole accord over this charter, and every claim names a clause of it at a setting it holds. Putting act 6 back as a lapsed pass fails three of those tests.

2 — One layout. src/page.ts no longer renders a document: home() hands a body, a notice and who to the one layout, and the copy is deleted. The merge landed this on top of #18, so it is the lobby that is now a body, and the shared footer keeps the /api/tables line the lobby's own footer had. / reaches /style because there is only one footer to link it from, and a test asserts / and /style share the document. AGENTS.md says there is one layout and never a second copy rather than blessing the copy.

3 — The header wraps, and /style can show the state that broke. .row wraps, so a pill, a commit, a name and Sign out fall onto a second line instead of off the right edge. I exported headerNav rather than header: it is the only part of the header that varies with who is asking, and three whole headers on one page would mean three <h1>s. It is the first specimen, for a guest, a person and an admin.

Smaller ones, all taken. The specimen forms post to POST /style, which redirects back and writes nothing — and carries no notice, because a page a shared cache may keep cannot hold a word meant for one person; actBar now takes whole paths instead of building ${action}/accept, since a component that concatenates a URL has to know the route table, and back takes the path to return to (default /, as before). The charter is built by a lookup that throws at boot if the pool drops a clause, not a ! that would 500. The timing assertion takes the fastest of ten warm requests — a floor on what the code does (~1 ms locally) rather than a bet on an unloaded box.

The merge, and what I left. Conflicts were src/page.ts (resolved above) and static/app.css: #39 landed the real .when, so src/when.ts owns that class — its tabular figures and its 12ch/10ch widths per kind stay, and only where a time sits is this branch's (.clock .when, .act-head .when). The demo's .note rules are gone in favour of main's .item. What I did not do: wire timeTag into clockSlot and actRow. They still take the words as data, which keeps /style's frozen fixture from being rewritten in the browser against a real clock; passing when.ts's tag through is #12's call when it renders a live table, and the slot reserves its width either way.

🤖 Generated with Claude Code

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs