Build lane 2: the public pages (PUB-1 to PUB-12) #728

Closed
opened 2026-09-22 00:12:19 +00:00 by claude-bot · 8 comments
Collaborator

Build lane 2 of the v4.5.0 redesign. The brief is docs/design/v4.5.0-brief.md (§9, the lanes in order); the build plan is docs/design/v4.5.0-build-plan.md (the rollout rule, the design-system mapping, the journeys as tests). Umbrella: #366. Goes after lane 1.

Scope

Surfaces: PUB-1 to PUB-12: the front door, sign in, an invitation, the shared analytics page, Help, Licences & notices, not found and the crash screens, first-run setup, the Discord link pages, Accessibility, Privacy.
The drawing: /feedback/prototype/lane1c — the page the build must match, still by still.
Decisions: the brief §7, and each surface's header in redesign-surfaces/sources/headers.md.

Server changes it needs (in this lane or an earlier one)

  • the front door's numbers (campaigns, sessions, hours recorded) and the operator's contact and policy words (About this server, ADMIN-15)
  • the privacy page reading the retention settings; a real 404; the setup wizard's skip list (IS-251 to IS-258)
  • Licences & notices from the loaded packs' licence fields (D64, D67)

Acceptance

  • Every placement coverage/lane1c.py classifies as drawn or replaced is built as the prototype draws it; the script is the checklist. Rows it marks behaviour are tested, not drawn.
  • J1 (first-run setup and sign in) and J2 (joining by link) pass on desktop, on a phone and keyboard-only in the journeys suite (#726), with the axe scan clean (WCAG 2.2 AA, #367).
  • The brief's vocabulary (§5) throughout, from vocabulary.js; no page-level error destroys rendered content; every async operation shows progress and a next action on failure; disabled controls say why.
  • Main stays deployable after every PR of the lane: the new shell carries the old pages until each is replaced (the rollout rule).
  • The surfaces' headers in redesign-surfaces/sources/headers.md (Decided lines and the owner's review notes) are honoured; a departure is a decision change on #659 first.

Notes

  • Nothing public may reveal campaign content (D64): the numbers are aggregates and the licences page names no content.

🤖 Generated with Claude Code

**Build lane 2 of the v4.5.0 redesign.** The brief is `docs/design/v4.5.0-brief.md` (§9, the lanes in order); the build plan is `docs/design/v4.5.0-build-plan.md` (the rollout rule, the design-system mapping, the journeys as tests). Umbrella: #366. Goes after lane 1. ## Scope **Surfaces:** PUB-1 to PUB-12: the front door, sign in, an invitation, the shared analytics page, Help, Licences & notices, not found and the crash screens, first-run setup, the Discord link pages, Accessibility, Privacy. **The drawing:** `/feedback/prototype/lane1c` — the page the build must match, still by still. **Decisions:** the brief §7, and each surface's header in `redesign-surfaces/sources/headers.md`. ## Server changes it needs (in this lane or an earlier one) - the front door's numbers (campaigns, sessions, hours recorded) and the operator's contact and policy words (About this server, ADMIN-15) - the privacy page reading the retention settings; a real 404; the setup wizard's skip list (`IS-251` to `IS-258`) - Licences & notices from the loaded packs' licence fields (D64, D67) ## Acceptance - [ ] Every placement `coverage/lane1c.py` classifies as **drawn** or **replaced** is built as the prototype draws it; the script is the checklist. Rows it marks **behaviour** are tested, not drawn. - [ ] J1 (first-run setup and sign in) and J2 (joining by link) pass on desktop, on a phone and keyboard-only in the journeys suite (#726), with the axe scan clean (WCAG 2.2 AA, #367). - [ ] The brief's vocabulary (§5) throughout, from `vocabulary.js`; no page-level error destroys rendered content; every async operation shows progress and a next action on failure; disabled controls say why. - [ ] Main stays deployable after every PR of the lane: the new shell carries the old pages until each is replaced (the rollout rule). - [ ] The surfaces' headers in `redesign-surfaces/sources/headers.md` (Decided lines and the owner's review notes) are honoured; a departure is a decision change on #659 first. ## Notes - Nothing public may reveal campaign content (D64): the numbers are aggregates and the licences page names no content. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Collaborator

Picking this up. Lane 1 (#727) merged this morning — PRs #742, #744, #747, #748, #749, #750 — so lane 2 is next in the build plan's order.

Read first: the drawing (/feedback/prototype/lane1c, the twelve PUB sections and its "Reviewed, settled, and still open" list), the checklist (coverage/lane1c.py — 109 placements: 89 drawn, 4 replaced, 10 behaviour, 6 belonging to another surface), the surfaces' headers in sources/headers.md, and the decisions these pages turn on: D4, D13, D15-as-amended-by-D63, D46, D62, D63, D64, D65, D66, D68, D70.

The PR series

Working agreements §7: the lane's server change first, as its own PR; then UI only; every PR names the coverage rows it builds.

1 — Server: what the public pages read. Backend only, one migration. The public pages are the only part of the app that reads the server rather than a campaign, and nothing today exposes any of it without a session:

  • the front door's three numbers (campaigns running, sessions played, running since) — confirmed by the owner on 2026-09-21 — with About this server able to turn them off (IS-385);
  • who runs this server and how to reach them, and the administrator's own words (IS-388, ADMIN-15's storage; lane 9 draws the Admin form that writes them);
  • the retention lines the privacy page reads rather than types (IS-387), so the page cannot drift from the settings the day they change;
  • the notices address, publicly readable, for Licences & notices (API-016, D64);
  • whether an invitation's campaign records its sessions (IS-196), which the invite preview cannot say today.

2 — Server: first-run setup. Backend only, one migration: the setup state, the seven steps, and — the point of the decision — which steps were skipped, so Admin can list them afterwards (IS-251–IS-258, D70: it reminds, never blocks). The step checks are Instance health's, not a second set (#439).

3 — UI: the public frame, the front door, sign in. The pub frame the twelve pages share (wordmark or crumb, the page, and the six-part footer D63 settled), then PUB-11, PUB-1 and PUB-2. Carries the footer's three new doors — Accessibility, Privacy, Licences & notices — into the signed-in shell too, where lane 1 left a comment saying they arrive with this lane.

4 — UI: the invitation, the shared analytics page, Help. PUB-3, PUB-4, PUB-5.

5 — UI: the stops, and the four written pages. PUB-7 (a real 404 at last — SH-009), PUB-6, PUB-10, PUB-12.

6 — UI: setting up a new server. PUB-8, the wizard and the list it leaves in Admin, and J1's setup steps in the journeys suite.

J1 and J2 grow with the PRs that build what they walk through, rather than in a PR of their own; the axe gate is on every page they visit.

Departures

None decided in advance. Anything the build finds it cannot draw as the prototype draws it gets recorded here and in the PR body before it is built, per §7 — the build does not redesign.

🤖 Generated with Claude Code

**Picking this up.** Lane 1 (#727) merged this morning — PRs #742, #744, #747, #748, #749, #750 — so lane 2 is next in the build plan's order. Read first: the drawing (`/feedback/prototype/lane1c`, the twelve PUB sections and its "Reviewed, settled, and still open" list), the checklist (`coverage/lane1c.py` — **109 placements: 89 drawn, 4 replaced, 10 behaviour, 6 belonging to another surface**), the surfaces' headers in `sources/headers.md`, and the decisions these pages turn on: D4, D13, D15-as-amended-by-D63, D46, D62, D63, D64, D65, D66, D68, D70. ## The PR series Working agreements §7: the lane's server change first, as its own PR; then UI only; every PR names the coverage rows it builds. **1 — Server: what the public pages read.** Backend only, one migration. The public pages are the only part of the app that reads the *server* rather than a campaign, and nothing today exposes any of it without a session: - the front door's three numbers (campaigns running, sessions played, running since) — confirmed by the owner on 2026-09-21 — with About this server able to turn them off (`IS-385`); - who runs this server and how to reach them, and the administrator's own words (`IS-388`, ADMIN-15's storage; lane 9 draws the Admin form that writes them); - the retention lines the privacy page reads rather than types (`IS-387`), so the page cannot drift from the settings the day they change; - the notices address, publicly readable, for Licences & notices (`API-016`, D64); - whether an invitation's campaign records its sessions (`IS-196`), which the invite preview cannot say today. **2 — Server: first-run setup.** Backend only, one migration: the setup state, the seven steps, and — the point of the decision — **which steps were skipped**, so Admin can list them afterwards (`IS-251`–`IS-258`, D70: it reminds, never blocks). The step checks are Instance health's, not a second set (#439). **3 — UI: the public frame, the front door, sign in.** The `pub` frame the twelve pages share (wordmark or crumb, the page, and the six-part footer D63 settled), then PUB-11, PUB-1 and PUB-2. Carries the footer's three new doors — Accessibility, Privacy, Licences & notices — into the signed-in shell too, where lane 1 left a comment saying they arrive with this lane. **4 — UI: the invitation, the shared analytics page, Help.** PUB-3, PUB-4, PUB-5. **5 — UI: the stops, and the four written pages.** PUB-7 (a real 404 at last — `SH-009`), PUB-6, PUB-10, PUB-12. **6 — UI: setting up a new server.** PUB-8, the wizard and the list it leaves in Admin, and J1's setup steps in the journeys suite. J1 and J2 grow with the PRs that build what they walk through, rather than in a PR of their own; the axe gate is on every page they visit. ## Departures None decided in advance. Anything the build finds it cannot draw as the prototype draws it gets recorded here and in the PR body before it is built, per §7 — the build does not redesign. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Collaborator

A departure to record before it is built, on IS-252 (the setup wizard's backup step) — per §7, on the issue first.

The drawing says "Take one now, and check it restores", and reports back "Took a backup and read it back: 1.1 GB, 4 minutes ago." The owner's review note is explicit about why: "Taking a backup is not enough: the step reads it back."

What exists to build that on:

  • Backups are real and fully built — settings_service.BackupConfig (destination, retention count, rclone remote, include-audio), scheduled and on by default since #414, with POST /api/admin/backup/run, GET /api/admin/backup/logs and a backup_log table recording status, size and destination.
  • backup_client_service (#429) already refuses to write a backup this server's pg_restore could not read — its own docstring says "a backup nobody has restored is not a backup".
  • What does not exist is an actual restore. Nothing anywhere restores a dump, and a true verification would need a scratch database, the time to load a full dump into it, and a story for what happens when the dump is large.

So the step will read the archive back rather than restore it: after taking the backup it runs pg_restore --list over the file it just produced and reports what it found — the archive is readable by this server's own pg_restore, and how big it is and when. That catches the failures that actually happen in the wild (a truncated file, an unreadable archive, a version mismatch, a destination that silently wrote nothing) and it is honest about being a read rather than a restore, so the wording on the step will say "read it back", not "checked it restores".

A real restore-into-a-scratch-database check is a worthwhile thing and belongs with the backup feature (#414's line of work), not inside a first-run wizard. If you would rather the step claim less, or rather it wait for the real thing, say so and I will change it — it is the last PR of the lane, so there is time.

Nothing else in the lane is blocked; PRs 1 and 3 are being built now.

🤖 Generated with Claude Code

**A departure to record before it is built, on `IS-252`** (the setup wizard's backup step) — per §7, on the issue first. The drawing says *"Take one now, and check it restores"*, and reports back *"Took a backup and read it back: 1.1 GB, 4 minutes ago."* The owner's review note is explicit about why: **"Taking a backup is not enough: the step reads it back."** What exists to build that on: - Backups are real and fully built — `settings_service.BackupConfig` (destination, retention count, rclone remote, include-audio), scheduled and on by default since #414, with `POST /api/admin/backup/run`, `GET /api/admin/backup/logs` and a `backup_log` table recording status, size and destination. - `backup_client_service` (#429) already refuses to *write* a backup this server's `pg_restore` could not read — its own docstring says "a backup nobody has restored is not a backup". - **What does not exist is an actual restore.** Nothing anywhere restores a dump, and a true verification would need a scratch database, the time to load a full dump into it, and a story for what happens when the dump is large. So the step will **read the archive back rather than restore it**: after taking the backup it runs `pg_restore --list` over the file it just produced and reports what it found — the archive is readable by this server's own `pg_restore`, and how big it is and when. That catches the failures that actually happen in the wild (a truncated file, an unreadable archive, a version mismatch, a destination that silently wrote nothing) and it is honest about being a read rather than a restore, so the wording on the step will say "read it back", not "checked it restores". A real restore-into-a-scratch-database check is a worthwhile thing and belongs with the backup feature (#414's line of work), not inside a first-run wizard. If you would rather the step claim less, or rather it wait for the real thing, say so and I will change it — it is the last PR of the lane, so there is time. Nothing else in the lane is blocked; PRs 1 and 3 are being built now. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Collaborator

A second departure to record before building, on IS-253 (the setup wizard's key step) — and some good news about the rest of PUB-8.

The good news first

The seven steps are a path through endpoints the product already has, not a new settings system. Having gone through them one by one:

Step What it writes through
You (IS-251) nothing — the first account is already made an administrator implicitly at first sign-in
Backups (IS-252) PUT /api/admin/backup/config, POST /api/admin/backup/run, GET /api/admin/backup/logs
The key (IS-253) nothing — GET /api/admin/settings/secrets-health already answers the question
What is kept (IS-254) PUT /api/admin/settings/retention
The models (IS-255) PUT /api/admin/settings/bot, then POST /api/admin/settings/whisper/test and .../llm/test — which are Instance health's own tests, as the surface header asked for, not a second set
Discord (IS-256) PUT /api/admin/settings/bot, POST /api/admin/settings/bot/regenerate-key, GET /api/admin/settings/bot/ping

regenerate-key already returns the key once and its docstring already says so — which is the drawing's "Made for you. Put it in the bot's environment; it is not shown again", word for word. The drawing got that one exactly right.

So the only genuinely new server thing is the state: nothing today records that a server has been set up at all. One new instance setting (setup_state — a key in the existing JSON store, so still no migration) holding done, skipped and completed_at, with GET /api/setup/state, POST /api/setup/steps/{step} and POST /api/setup/finish. Re-doing a skipped step clears it from the list, because D70's list has to be able to empty. finish never refuses over a skipped step — D70: it reminds, never blocks.

The departure: the key step will show no key

The drawing shows QB_SECRET_KEY=2f9c…a41e with a Copy button. I do not think the build should, and I would rather say so than quietly draw it.

GET /api/admin/settings/bot states the rule the whole settings layer follows — "Secrets are never returned — only presence flags" — and SECRET_KEY is the key every other secret is encrypted with, so it is the last one to make an exception for. It also buys the operator nothing: they put SECRET_KEY into the server's environment themselves, so showing it back teaches them nothing and adds one more place it can leak from — a screenshot of the setup wizard, a support thread, a shoulder.

What the step does instead is the thing the step is actually for, which is custody rather than disclosure:

  • it says where the key lives — SECRET_KEY, in this server's environment — and that a backup restored without it cannot read the bot token, the AI keys or the mail password, which is the sentence that matters and is the drawing's own;
  • it confirms the key is working, from GET /api/admin/settings/secrets-health: how many encrypted settings are readable with it, and names any that are not. #428 and #430 built that endpoint for precisely this question, after a restore onto a host with a different SECRET_KEY produced a system that looked restored and was not;
  • it keeps the drawing's checkbox, "I have kept a copy somewhere safe", and it can still be skipped.

The generated BOT_API_KEY on the Discord step is untouched by this — that one is made by the server, has to be shown once to be usable, and already is.

Say the word if you would rather have the fingerprint shown as drawn and I will put it back; it is the last PR of the lane.

🤖 Generated with Claude Code

**A second departure to record before building, on `IS-253`** (the setup wizard's key step) — and some good news about the rest of PUB-8. ## The good news first The seven steps are a path through endpoints the product **already has**, not a new settings system. Having gone through them one by one: | Step | What it writes through | |---|---| | You (`IS-251`) | nothing — the first account is already made an administrator implicitly at first sign-in | | Backups (`IS-252`) | `PUT /api/admin/backup/config`, `POST /api/admin/backup/run`, `GET /api/admin/backup/logs` | | The key (`IS-253`) | nothing — `GET /api/admin/settings/secrets-health` already answers the question | | What is kept (`IS-254`) | `PUT /api/admin/settings/retention` | | The models (`IS-255`) | `PUT /api/admin/settings/bot`, then `POST /api/admin/settings/whisper/test` and `.../llm/test` — **which are Instance health's own tests**, as the surface header asked for, not a second set | | Discord (`IS-256`) | `PUT /api/admin/settings/bot`, `POST /api/admin/settings/bot/regenerate-key`, `GET /api/admin/settings/bot/ping` | `regenerate-key` already returns the key **once** and its docstring already says so — which is the drawing's "Made for you. Put it in the bot's environment; it is not shown again", word for word. The drawing got that one exactly right. So the only genuinely new server thing is **the state**: nothing today records that a server has been set up at all. One new instance setting (`setup_state` — a key in the existing JSON store, so still no migration) holding `done`, `skipped` and `completed_at`, with `GET /api/setup/state`, `POST /api/setup/steps/{step}` and `POST /api/setup/finish`. Re-doing a skipped step clears it from the list, because D70's list has to be able to empty. `finish` never refuses over a skipped step — D70: it reminds, never blocks. ## The departure: the key step will show no key The drawing shows `QB_SECRET_KEY=2f9c…a41e` with a Copy button. I do not think the build should, and I would rather say so than quietly draw it. `GET /api/admin/settings/bot` states the rule the whole settings layer follows — *"Secrets are never returned — only presence flags"* — and `SECRET_KEY` is the key every other secret is encrypted **with**, so it is the last one to make an exception for. It also buys the operator nothing: they put `SECRET_KEY` into the server's environment themselves, so showing it back teaches them nothing and adds one more place it can leak from — a screenshot of the setup wizard, a support thread, a shoulder. What the step does instead is the thing the step is actually *for*, which is custody rather than disclosure: - it says where the key lives — `SECRET_KEY`, in this server's environment — and that **a backup restored without it cannot read the bot token, the AI keys or the mail password**, which is the sentence that matters and is the drawing's own; - it confirms the key is working, from `GET /api/admin/settings/secrets-health`: how many encrypted settings are readable with it, and names any that are not. #428 and #430 built that endpoint for precisely this question, after a restore onto a host with a different `SECRET_KEY` produced a system that looked restored and was not; - it keeps the drawing's checkbox, "I have kept a copy somewhere safe", and it can still be skipped. The generated `BOT_API_KEY` on the Discord step is untouched by this — that one is made by the server, has to be shown once to be usable, and already is. Say the word if you would rather have the fingerprint shown as drawn and I will put it back; it is the last PR of the lane. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Collaborator

Departures on PUB-3, PUB-4 and PUB-5, recorded before the PR goes up (§7). Five small ones, and one thing that is not lane 2's but is being fixed here anyway.

The one worth your attention: every eyebrow in the app is the wrong case

Not a lane 2 departure — a lane 0 port error, found while building these pages and verified on both sides.

shared/looks.css:153 in the prototype says it outright:

/* Labels: sentence case everywhere; the kit's tracked capitals are retired. */
:root[data-look] .label { text-transform: none; letter-spacing: 0; … color: var(--ink-soft); }

:root[data-look] is on in every prototype page, so every drawing you reviewed and signed off shows sentence case. Lane 0 ported kit.css's .label and not the reset, so the app ships the thing that decision retired: AN INVITATION where the drawing reads An invitation. It is on the front door, sign in, Home, You and all five public pages — everything built so far.

Three properties in one file, and restoring a decision rather than making one, so it is fixed in this PR rather than filed. Flagging it against #364 as well, since that is where it came from.

The five departures

  1. SH-124 — "13 sessions in six months" is not a number the server has. GET /public/analytics/{token} reports average_gap_days and sessions_last_30_days; the ledger row for SH-124 says "sessions completed in the last 30 days". The drawn figures are also arithmetically impossible together — thirteen sessions at a thirteen-day gap is not six months — so I read "six months" as fiction rather than specification. The page keeps the drawn sentence's shape with the number the server actually reports: Every 13 days on average · 2 sessions in the last 30 days. If you want the drawn words, it needs a new server field — say so and I will add it.

  2. SH-088 has no way onward. The ledger row says the request-sent state "explains the wait and links to the dashboard"; the drawing's banner has no action, and I built it as drawn. It is a real if small loss for a signed-in reader who has just asked to join and is now looking at a page with nowhere to go. Easy to add if you would rather have the ledger's version.

  3. SH-091 is on the default state only, as drawn. The ledger says "shown on every state"; the drawing replaces it with "You will come straight back here afterwards" when signed out, and drops it while busy or failed. Drawing wins, difference noted.

  4. PUB-5 is not pub--wide. The drawing passes it, but in a still the box is about 600px so it never shows; in a real window pub--wide is max-width: none inside a 1560px page — a guide set to a 1560px measure, which is unreadable prose. It uses the narrow card the other public pages use, which is also almost exactly the still's proportions. PUB-3 and PUB-4 keep their drawn widths.

  5. SH-122's wordmark is a link. The decision is that "the header's Quest Board link goes somewhere that exists"; the drawing's PUB-4 head is the shared non-link wordmark, so building it literally would leave the page with no door into Quest Board at all — worse for exactly the stranger the row is about. The wordmark is drawn as the wordmark and carries href="/". Not the crumb, which would swallow the "Campaign analytics" eyebrow over the campaign name.

Two faults the render check caught, for the record

Tailwind's preflight strips the markers from every list, and the guides are the first pages in the redesign to render a list as prose — so a twenty-section guide drew its bullets with no bullets. And the Help index's guide cards sat an indent in from everything else, because .pubhelp .glist loses to the frame's .qb .pub ul and inherited a bullet list's padding. Neither is visible in the markup; both are obvious in a picture.

🤖 Generated with Claude Code

**Departures on PUB-3, PUB-4 and PUB-5**, recorded before the PR goes up (§7). Five small ones, and one thing that is not lane 2's but is being fixed here anyway. ## The one worth your attention: every eyebrow in the app is the wrong case Not a lane 2 departure — a **lane 0 port error**, found while building these pages and verified on both sides. `shared/looks.css:153` in the prototype says it outright: ```css /* Labels: sentence case everywhere; the kit's tracked capitals are retired. */ :root[data-look] .label { text-transform: none; letter-spacing: 0; … color: var(--ink-soft); } ``` `:root[data-look]` is on in every prototype page, so **every drawing you reviewed and signed off shows sentence case**. Lane 0 ported `kit.css`'s `.label` and not the reset, so the app ships the thing that decision retired: `AN INVITATION` where the drawing reads `An invitation`. It is on the front door, sign in, Home, You and all five public pages — everything built so far. Three properties in one file, and restoring a decision rather than making one, so it is fixed in this PR rather than filed. Flagging it against **#364** as well, since that is where it came from. ## The five departures 1. **`SH-124` — "13 sessions in six months" is not a number the server has.** `GET /public/analytics/{token}` reports `average_gap_days` and `sessions_last_30_days`; the ledger row for `SH-124` says "sessions completed in the last 30 days". The drawn figures are also arithmetically impossible together — thirteen sessions at a thirteen-day gap is not six months — so I read "six months" as fiction rather than specification. The page keeps the drawn sentence's shape with the number the server actually reports: *Every 13 days on average · 2 sessions in the last 30 days.* **If you want the drawn words, it needs a new server field — say so and I will add it.** 2. **`SH-088` has no way onward.** The ledger row says the request-sent state "explains the wait and links to the dashboard"; the drawing's banner has no action, and I built it as drawn. It is a real if small loss for a signed-in reader who has just asked to join and is now looking at a page with nowhere to go. Easy to add if you would rather have the ledger's version. 3. **`SH-091` is on the default state only**, as drawn. The ledger says "shown on every state"; the drawing replaces it with "You will come straight back here afterwards" when signed out, and drops it while busy or failed. Drawing wins, difference noted. 4. **PUB-5 is not `pub--wide`.** The drawing passes it, but in a still the box is about 600px so it never shows; in a real window `pub--wide` is `max-width: none` inside a 1560px page — a guide set to a 1560px measure, which is unreadable prose. It uses the narrow card the other public pages use, which is also almost exactly the still's proportions. PUB-3 and PUB-4 keep their drawn widths. 5. **`SH-122`'s wordmark is a link.** The decision is that "the header's Quest Board link goes somewhere that exists"; the drawing's PUB-4 head is the shared *non-link* wordmark, so building it literally would leave the page with no door into Quest Board at all — worse for exactly the stranger the row is about. The wordmark is drawn as the wordmark and carries `href="/"`. Not the crumb, which would swallow the "Campaign analytics" eyebrow over the campaign name. ## Two faults the render check caught, for the record Tailwind's preflight strips the markers from every list, and the guides are the first pages in the redesign to render a list *as prose* — so a twenty-section guide drew its bullets with no bullets. And the Help index's guide cards sat an indent in from everything else, because `.pubhelp .glist` loses to the frame's `.qb .pub ul` and inherited a bullet list's padding. Neither is visible in the markup; both are obvious in a picture. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Collaborator

A new catalogue row, IS-438, and one small question for you at the end of it.

The drawing's front door reads "Amberwake is run by Dana, for a few groups of friends." Nothing stored that last clause — the build found it by hard-coding it, which would have had every self-hosted Quest Board telling its visitors that its operator runs it for a few groups of friends. #752 made it a real field (blurb on GET /public/site), written in About this server.

IS-389 names the four things About this server holds — the name, who runs it and how to reach them, the privacy policy's own words, and the numbers switch. This is a fifth. Without a row of its own, lane 9 builds that form from IS-389 and no real server ever gets a line on its front door.

So, per §7's "the catalogue is kept live": IS-438 added to ledger-IS.md, placed on ADMIN-15 (primary) and PUB-11, catalogue rebuilt — 1,707 rows — and every coverage script re-run, which is how the second surface turned up.

The question

lane1c.py counts it drawn on PUB-11: #751 draws that sentence both with the line and without it.

lane7.py counts it deferred on ADMIN-15, and that is the honest answer rather than a comfortable one. Lane 7's About this server form draws Its name, Run by, How to reach them and In your words — and has no field for this, because the row did not exist when that lane was drawn.

I have deliberately not added it to the drawing. It would be one input in a form that already has four, and it follows directly from D62, so it is a small change — but lane 7 is a page you have already reviewed and signed off, and quietly editing a reviewed drawing is not a thing I should do on my own initiative.

So: would you rather the drawing gained the field now, or that lane 9 (#735) adds it when it builds that form? Either is fine and neither blocks lane 2; if you say nothing, lane 9 will find it as a deferred row in its own coverage script, which is the mechanism working as designed.

🤖 Generated with Claude Code

**A new catalogue row, `IS-438`, and one small question for you at the end of it.** The drawing's front door reads *"**Amberwake** is run by Dana, **for a few groups of friends**."* Nothing stored that last clause — the build found it by hard-coding it, which would have had every self-hosted Quest Board telling its visitors that its operator runs it for a few groups of friends. #752 made it a real field (`blurb` on `GET /public/site`), written in About this server. `IS-389` names the four things About this server holds — the name, who runs it and how to reach them, the privacy policy's own words, and the numbers switch. **This is a fifth.** Without a row of its own, lane 9 builds that form from `IS-389` and no real server ever gets a line on its front door. So, per §7's "the catalogue is kept live": `IS-438` added to `ledger-IS.md`, placed on **ADMIN-15** (primary) and **PUB-11**, catalogue rebuilt — 1,707 rows — and **every** coverage script re-run, which is how the second surface turned up. ## The question `lane1c.py` counts it **drawn** on PUB-11: #751 draws that sentence both with the line and without it. `lane7.py` counts it **deferred** on ADMIN-15, and that is the honest answer rather than a comfortable one. Lane 7's About this server form draws *Its name*, *Run by*, *How to reach them* and *In your words* — and has no field for this, because the row did not exist when that lane was drawn. **I have deliberately not added it to the drawing.** It would be one input in a form that already has four, and it follows directly from D62, so it is a small change — but lane 7 is a page you have already reviewed and signed off, and quietly editing a reviewed drawing is not a thing I should do on my own initiative. So: **would you rather the drawing gained the field now, or that lane 9 (#735) adds it when it builds that form?** Either is fine and neither blocks lane 2; if you say nothing, lane 9 will find it as a deferred row in its own coverage script, which is the mechanism working as designed. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Collaborator

Lane 2's build is complete, and all four open questions are answered (owner, 2026-09-22).

The six PRs, all merged

#752 the server the public pages read — /public/site, /public/policies, the invitation's two new fields, ?next= widened, the Discord link pages restyled
#754 first-run setup's own state, and the backup read-back
#751 the public frame, the front door at /, sign in, sign-in failed
#756 the invitation, the shared analytics page, Help
#758 the stops, the waits, and the four written pages
#759 setting up a new server, and what it leaves waiting

Plus two fixes this lane found in other people's work: #755 (a Home test asserting a countdown against the wall clock) and #757 (the journeys job's Compose project name, hard-coded, so two overlapping CI runs tore each other's stacks down).

The answers

IS-252 — the backup step reads the archive back. Confirmed, and now D87. pg_restore --list over the file just written, reported as "read it back", claiming no more than it does. A real restore-into-a-scratch-database check belongs with the backup feature (#414), not a first-run wizard.

IS-253 — the key step shows no key material. Confirmed, and now D88. SECRET_KEY is the key the other secrets are encrypted with; secrets-health answers "is this the right key" by decrypting rather than by eye.

SH-124 — twelve months, and now D89. And a correction to what I told you: I said the drawing's figures were "arithmetically impossible together". They are not — thirteen sessions at a thirteen-day average gap spans about 169 days, which is roughly six months, so the drawn sentence is internally consistent. What was impossible was mapping it onto the endpoint's sessions_last_30_days. That was a missing field, not a broken drawing, and the build took the lesser sentence when the drawing was right. Being corrected to twelve months rather than six: a year tells a reader about a campaign's rhythm and survives a table that plays fortnightly through a quiet summer. In progress on fix/owner-answers-code.

IS-438 — the drawing gains the field. Done: ADMIN-15's "About this server" now has What it is for, the line that finishes the front door's sentence, with its empty state on a fresh server. Rendered and read in both themes. lane7.py counts it drawn rather than deferred, the catalogue is rebuilt, and all sixteen coverage scripts pass.

Also settled from #759: the first-run redirect keeps both conditions (pristine setup state and nothing on Home), because pristine alone is equally true of every instance that predates the wizard and would have bounced a year-old server's administrator into "Setting up this server" on upgrade day. SH-088 gains the way onward the ledger asked for. And D90 writes down the pub--wide rule for every later lane: a still's box is about 600px, a window's is 1560px, and prose at that measure is unreadable — so a page that is mostly words keeps the narrow card whatever the still passed.

Against the acceptance list

  • Every placement lane1c.py classifies as drawn or replaced is built; the script passes with 110 placements and nothing unclassified.
  • J1 and J2 pass on desktop, on a phone and keyboard-only, with the axe scan clean at WCAG 2.2 AA — and the baseline file is now empty, /login having been its last entry and this lane having rebuilt that page.
  • The vocabulary is vocabulary.js's throughout; no page-level error destroys rendered content; every async call shows progress and a next action; disabled controls say why.
  • Main stayed deployable after every PR.
  • The surfaces' headers are honoured, with every departure recorded on this issue before it was built.

Left open only for your review on the dev server, per the rollout rule. Next in order is lane 3 (#729, the campaign core and Settings).

🤖 Generated with Claude Code

**Lane 2's build is complete, and all four open questions are answered (owner, 2026-09-22).** ## The six PRs, all merged | | | |---|---| | **#752** | the server the public pages read — `/public/site`, `/public/policies`, the invitation's two new fields, `?next=` widened, the Discord link pages restyled | | **#754** | first-run setup's own state, and the backup read-back | | **#751** | the public frame, the front door at `/`, sign in, sign-in failed | | **#756** | the invitation, the shared analytics page, Help | | **#758** | the stops, the waits, and the four written pages | | **#759** | setting up a new server, and what it leaves waiting | Plus two fixes this lane found in other people's work: **#755** (a Home test asserting a countdown against the wall clock) and **#757** (the journeys job's Compose project name, hard-coded, so two overlapping CI runs tore each other's stacks down). ## The answers **`IS-252` — the backup step reads the archive back. Confirmed, and now D87.** `pg_restore --list` over the file just written, reported as "read it back", claiming no more than it does. A real restore-into-a-scratch-database check belongs with the backup feature (#414), not a first-run wizard. **`IS-253` — the key step shows no key material. Confirmed, and now D88.** `SECRET_KEY` is the key the other secrets are encrypted with; `secrets-health` answers "is this the right key" by decrypting rather than by eye. **`SH-124` — twelve months, and now D89.** And a **correction to what I told you**: I said the drawing's figures were "arithmetically impossible together". They are not — thirteen sessions at a thirteen-day average gap spans about 169 days, which is roughly six months, so the drawn sentence is internally consistent. What was impossible was mapping it onto the endpoint's `sessions_last_30_days`. That was a missing field, not a broken drawing, and the build took the lesser sentence when the drawing was right. Being corrected to twelve months rather than six: a year tells a reader about a campaign's rhythm and survives a table that plays fortnightly through a quiet summer. In progress on `fix/owner-answers-code`. **`IS-438` — the drawing gains the field.** Done: ADMIN-15's "About this server" now has **What it is for**, the line that finishes the front door's sentence, with its empty state on a fresh server. Rendered and read in both themes. `lane7.py` counts it **drawn** rather than deferred, the catalogue is rebuilt, and all sixteen coverage scripts pass. Also settled from #759: the first-run redirect keeps **both** conditions (pristine setup state *and* nothing on Home), because pristine alone is equally true of every instance that predates the wizard and would have bounced a year-old server's administrator into "Setting up this server" on upgrade day. `SH-088` gains the way onward the ledger asked for. And **D90** writes down the `pub--wide` rule for every later lane: a still's box is about 600px, a window's is 1560px, and prose at that measure is unreadable — so a page that is mostly words keeps the narrow card whatever the still passed. ## Against the acceptance list - Every placement `lane1c.py` classifies as **drawn** or **replaced** is built; the script passes with 110 placements and nothing unclassified. - **J1 and J2 pass** on desktop, on a phone and keyboard-only, with the axe scan clean at WCAG 2.2 AA — and the baseline file is now **empty**, `/login` having been its last entry and this lane having rebuilt that page. - The vocabulary is `vocabulary.js`'s throughout; no page-level error destroys rendered content; every async call shows progress and a next action; disabled controls say why. - Main stayed deployable after every PR. - The surfaces' headers are honoured, with every departure recorded on this issue before it was built. **Left open only for your review on the dev server**, per the rollout rule. Next in order is lane 3 (#729, the campaign core and Settings). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Collaborator

The owner's review of lane 2 on dev, 2026-09-25. Looks right: the front door, and the 404 and the waits.

The owner's notes:

  • Front door: the owner wants pictures and screenshots here once the redesign is done. That's a later task.
  • Skipped: sign-in failed, the invitation, and shared analytics.

Fixes in batch R1 (fix/review-r1):

  • R1-3: an issue number leaked into the Accessibility page's text. Also a guard test: no #nn or Dnn in any public page's visible text.
  • R1-4 (D109): there's no way back to Quest Board from Accessibility, Privacy, or Licences & notices. The wordmark becomes a home link on every public page, reversing the analytics-only departure.
  • R1-8 (D108): Setup stays reachable after the server is set up. It closes on completion, on both the client and the server, and the skipped steps are finished in Admin.

Layout: "the pages are too narrow on high-res screens. Let's take advantage of screen space." Help: "The text column is too narrow… we should have a navigation section along the top." Both are part of the wide-screen mock-ups (revising D90). Help's top navigation is built in every option.

**The owner's review of lane 2 on dev, 2026-09-25.** Looks right: the front door, and the 404 and the waits. **The owner's notes:** - **Front door:** the owner wants pictures and screenshots here once the redesign is done. That's a later task. - **Skipped:** sign-in failed, the invitation, and shared analytics. **Fixes in batch R1 (`fix/review-r1`):** - **R1-3:** an issue number leaked into the Accessibility page's text. Also a guard test: no `#nn` or `Dnn` in any public page's visible text. - **R1-4 (D109):** there's no way back to Quest Board from Accessibility, Privacy, or Licences & notices. The wordmark becomes a home link on every public page, reversing the analytics-only departure. - **R1-8 (D108):** Setup stays reachable after the server is set up. It closes on completion, on both the client and the server, and the skipped steps are finished in Admin. **Layout:** "the pages are too narrow on high-res screens. Let's take advantage of screen space." Help: "The text column is too narrow… we should have a navigation section along the top." Both are part of the wide-screen mock-ups (revising D90). Help's top navigation is built in every option.
Author
Collaborator

Signed off by the owner on the Redesign Review page, 2026-10-01 ("Much better after the pass, thank you."). The R3 re-check for this lane (setup going to Admin once finished) is marked OK. No changes asked for. Closing.

**Signed off by the owner on the Redesign Review page, 2026-10-01** ("Much better after the pass, thank you."). The R3 re-check for this lane (setup going to Admin once finished) is marked OK. No changes asked for. Closing.
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/Quest-Board#728
No description provided.