Review R1: the owner's findings on lanes 1–4 (D107–D109; #727, #728, #729, #730) #823

Merged
claude-bot merged 14 commits from fix/review-r1 into main 2026-09-26 04:15:21 +00:00
Collaborator

The owner reviewed lanes 1–4 on dev (2026-09-25). This batch fixes eight findings. Each has a regression test that failed before its fix.

  • R1-1: Needs you didn't update after voting (#727). The Contents count was fetched once, by a frame that outlives every page. It now refetches on every change of page, and after any write that can answer an item (changesAttention, through the shell's onApiWrite).
  • R1-2: the calendar feed was named by its URL (#727). Feeds now carry NAME/X-WR-CALNAME ("Quest Board", and " · Quest Board" for a single session's .ics), with values escaped and folded per RFC 5545.
  • R1-3: an issue number was in the Accessibility page's text (#728). Removed. There is also a new guard: every public page is rendered, and the test fails on #nn or Dnn in visible text.
  • R1-4 (D109): no way back from Accessibility, Privacy, or Licences & notices (#728). The wordmark is now a home link on every public page ("Quest Board, home"; /dashboard when signed in, / otherwise). This reverses lane 2's analytics-only departure. The auth context was split into hooks/authContext.js, so the crash screen can render it.
  • R1-5: Settings → Recording, retention & AI crashed on every campaign (#729). GET /transcription-languages returns {detect, languages}, and the page mapped over the whole object. The test stub was a bare array, which is how the bug got past the tests. Also, the "After a session" section rendered above its SectionBoundary, so a throw there took down the whole page; it's now its own component, so a failure stays in that section.
  • R1-6: the Read tab said "nobody has said who was there" above a list of everyone (#730). With nobody marked, only the sentence is drawn. The toggles stay on the GM's Review.
  • R1-7 (D107): a one-shot's "when" was free text (#727). It's now the lane 0 date-and-time picker, entered in the campaign's zone. A new OneShotSpec.day field: with a time, it creates a pencilled-in session with that slot; with a date alone, a pencilled-in session with no slot. No migration.
  • R1-8 (D108): Setup stayed reachable after setup (#728).
    • Server: the wizard's writes return 409 once completed_at is set; GET /state stays open for Admin.
    • Skipped steps: the Admin saves that do a step now clear it from the skipped list (settle_setup_step).
    • Web: /setup redirects to /admin, and every skipped-step link goes to its Admin tab (?tab=).
    • J1 is updated for a server that's already set up.

Also in this PR: D107–D109 in intro.md, the catalogue rebuilt, and the CHANGELOG.

Checked locally:

  • the full backend suite (exit 0);
  • the frontend suite: 172 files, 2340 tests;
  • lint: 0 errors;
  • ruff: clean.

Not run: the journeys (another branch had the stack), so CI's journeys job is J1's first run since this change.

One call for the owner to confirm: skipping "You" or the key check in the wizard isn't listed in Admin, since nothing in Admin does either step. Ready still states the key skip.

🤖 Generated with Claude Code

The owner reviewed lanes 1–4 on dev (2026-09-25). This batch fixes eight findings. Each has a regression test that failed before its fix. - **R1-1: Needs you didn't update after voting (#727).** The Contents count was fetched once, by a frame that outlives every page. It now refetches on every change of page, and after any write that can answer an item (`changesAttention`, through the shell's `onApiWrite`). - **R1-2: the calendar feed was named by its URL (#727).** Feeds now carry `NAME`/`X-WR-CALNAME` ("Quest Board", and "<campaign> · Quest Board" for a single session's `.ics`), with values escaped and folded per RFC 5545. - **R1-3: an issue number was in the Accessibility page's text (#728).** Removed. There is also a new guard: every public page is rendered, and the test fails on `#nn` or `Dnn` in visible text. - **R1-4 (D109): no way back from Accessibility, Privacy, or Licences & notices (#728).** The wordmark is now a home link on every public page ("Quest Board, home"; `/dashboard` when signed in, `/` otherwise). This reverses lane 2's analytics-only departure. The auth context was split into `hooks/authContext.js`, so the crash screen can render it. - **R1-5: Settings → Recording, retention & AI crashed on every campaign (#729).** `GET /transcription-languages` returns `{detect, languages}`, and the page mapped over the whole object. The test stub was a bare array, which is how the bug got past the tests. Also, the "After a session" section rendered above its `SectionBoundary`, so a throw there took down the whole page; it's now its own component, so a failure stays in that section. - **R1-6: the Read tab said "nobody has said who was there" above a list of everyone (#730).** With nobody marked, only the sentence is drawn. The toggles stay on the GM's Review. - **R1-7 (D107): a one-shot's "when" was free text (#727).** It's now the lane 0 date-and-time picker, entered in the campaign's zone. A new `OneShotSpec.day` field: with a time, it creates a pencilled-in session with that slot; with a date alone, a pencilled-in session with no slot. No migration. - **R1-8 (D108): Setup stayed reachable after setup (#728).** - **Server:** the wizard's writes return 409 once `completed_at` is set; `GET /state` stays open for Admin. - **Skipped steps:** the Admin saves that do a step now clear it from the skipped list (`settle_setup_step`). - **Web:** `/setup` redirects to `/admin`, and every skipped-step link goes to its Admin tab (`?tab=`). - **J1** is updated for a server that's already set up. **Also in this PR:** D107–D109 in `intro.md`, the catalogue rebuilt, and the CHANGELOG. **Checked locally:** - the full backend suite (exit 0); - the frontend suite: 172 files, 2340 tests; - lint: 0 errors; - ruff: clean. **Not run:** the journeys (another branch had the stack), so CI's journeys job is J1's first run since this change. **One call for the owner to confirm:** skipping "You" or the key check in the wizard isn't listed in Admin, since nothing in Admin does either step. Ready still states the key skip. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The frame read /api/me/attention once per app load, so voting, answering
"Are you in?" or approving a transcript left the Contents count where it
was however many pages you walked through; only a reload brought it
down. It now reads it again on every navigation and after any write that
can answer an item, through the same onApiWrite announcement the
campaign list already uses (B11). The rule is changesAttention in
api/users.js: every session write but the notes' autosave, a session
made, a wiki suggestion taken or dismissed, a join request settled, and
anything that changes the campaign list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Neither ICS document the app serves said what the calendar was called,
so Google Calendar, subscribing by URL, named it after the URL. The
subscribable feed now carries NAME and X-WR-CALNAME "Quest Board" with
a DESCRIPTION/X-WR-CALDESC; the one-session download is named
"<campaign> · Quest Board". Both are escaped as RFC 5545 TEXT and
folded to 75 octets on a character boundary. The iTIP invitations stay
unnamed: they are events for the recipient's own calendar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"K.map is not a function" on dev was langs.map: GET
/api/campaigns/transcription-languages answers { detect, languages },
and the page mapped over the whole object — on every campaign, the
moment the answer landed. The test stub had been a bare array, so the
suite never saw the server's shape. The page now reads the list from
`languages` and takes only an array (languageList); the stub is the
server's shape.

The second bug: "After a session" had a SectionBoundary, but its JSX
was written inline in RecordingForm, so it was evaluated in the form's
render and a throw in it went past the boundary to the route. The
section's body is its own component (AfterSession) now, so a throw in
it fails that section alone and the rest of the page carries on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"(#367)" sat in the lead paragraph a reader sees, beside WCAG 2.2 AA:
a working note that belonged in a comment. It is gone, and a guard
draws every public page — Front, Sign in, the Help index and each
guide, 404, a failed sign-in, Privacy, Licences & notices,
Accessibility, the invitation and shared analytics — with the server
answering, and fails if any of them shows an issue number or a
decision id.

The campaign Settings sections were checked by source rather than
drawn: every #n/Dn in them is inside a comment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Accessibility, Privacy, Licences & notices, the invitation and the
stops drew the wordmark as a plain div, so there was no way back into
Quest Board from them but the browser's button; lane 2 had made it a
link only on shared analytics and in the crumb heads. The frame now
draws one Wordmark, a link named "Quest Board, home", on every public
page: to /dashboard for somebody signed in, to / for anybody else.

The frame reads who is signed in from the auth context directly
(hooks/authContext.js, split out of useAuth.jsx) rather than through
useAuth, which throws outside a provider: PublicFrame is also drawn by
the crash screen, above every provider, where no provider means
nobody signed in.

D109 is recorded in intro.md, reversing lane 2's analytics-only
departure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With no attendance taken, Who was there on the Read tab drew the
sentence "Nobody has said who was there yet." and then the whole
membership beneath it, unmarked — which reads as the list of who was
there. "Who was there" on Read is attendance (SE-145; lane 3's
attendanceList), not RSVPs and not the members, so with nobody marked
there is nobody to list: the sentence alone is drawn. The unmarked
list with a toggle per row stays the GM's, on the Review tab.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New campaign asked "When, roughly" in free text and kept what was
typed on the session's description, so a pencilled-in one-shot
anchored no evening. It is lane 0's DateTime picker now, under the
session form's zone rules: the wall clock is in the campaign's zone,
the row names the zone, and a chosen time is read back with its
abbreviation. A new one-shot campaign takes the GM's own zone, sent as
`timezone` on create (Settings → General can change it).

Server: OneShotSpec gains `day`. A day and a time pencil the session
in at that time (unchanged path, `when`); a day alone pencils it in
with no slot and says the day on the description, because a guessed
hour would put a reminder on the calendar for a moment nobody chose.
`when_text` is still honoured for an older client. The session stays
pencilled in (tentative) rather than confirmed, as the choice
promises. No migration: `when` was already a datetime.

D107 recorded in intro.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/setup stayed reachable after it was finished, and Admin's "Finish
setting up" led back into it.

Server: POST /api/setup/steps/{step} and POST /api/setup/finish now
refuse with 409 {"detail": ...} once completed_at is set. GET
/api/setup/state stays open: Admin reads it. So that Admin's list of
what was skipped can still empty (D70), the Admin saves that do each
step's job take a skipped step off it (settle_setup_step): backup
config -> backups, the retention policy -> retention, bot settings
with a transcriber URL -> ai, with a bot token -> discord. Only a
skipped step moves, so a server nobody has set up stays pristine and
the first-run offer still reads it as such.

Web: /setup, at any step, goes to /admin when the server was already
set up on arrival; the visit that reaches Ready (and so finishes it)
stays on Ready. Ready's and Admin's skipped lists link each step to
the Admin tab that does it (Admin's tab is now the address's ?tab=),
never back into the wizard; after finishing, the step strip is a
record rather than links. "You" and the key's checkbox are only ever
asked by the wizard and nothing in Admin does them, so a skip of
either is not listed in Admin.

J1 (not run here): the first project walks the wizard as before, with
Ready's "Set them up" now /admin?tab=backup; later projects on the
same database find setup finished and assert /setup -> /admin and
Admin's "Set up backups" link instead.

D108 recorded in intro.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs(design): rebuild the catalogue for D107–D109 (#727, #728)
Some checks failed
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m4s
CI / Backend lint (ruff) (pull_request) Successful in 54s
CI / Bot/backend version sync (pull_request) Successful in 23s
CI / Feedback site tests (pull_request) Successful in 48s
CI / Does this change touch the recording path? (pull_request) Successful in 24s
CI / Docker image build (pull_request) Successful in 43s
CI / Bot tests and audit (pull_request) Successful in 2m46s
CI / Backend migration, tests, and audit (pull_request) Successful in 13m42s
CI / Frontend tests, audit, and build (pull_request) Failing after 17m47s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Has been cancelled
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been cancelled
2fb8a42ed5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test(frontend): wait for the invitation's title rather than racing its effect (#796)
Some checks failed
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m3s
CI / Bot/backend version sync (pull_request) Successful in 26s
CI / Backend lint (ruff) (pull_request) Successful in 1m36s
CI / Bot tests and audit (pull_request) Successful in 1m59s
CI / Does this change touch the recording path? (pull_request) Successful in 29s
CI / Feedback site tests (pull_request) Successful in 2m26s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Has been cancelled
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been cancelled
CI / Docker image build (pull_request) Has been cancelled
CI / Backend migration, tests, and audit (pull_request) Has been cancelled
CI / Frontend tests, audit, and build (pull_request) Has been cancelled
6d1a8f8f6f
The title is written by useDocumentTitle's effect, which can land a tick
after the heading findByRole resolves on; CI under load caught it (run 1401).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test(frontend): wait for Read's attendance fetch rather than racing its effect (#730)
Some checks failed
CI / Bot/backend version sync (pull_request) Successful in 31s
CI / Backend lint (ruff) (pull_request) Successful in 36s
CI / Feedback site tests (pull_request) Successful in 50s
CI / Does this change touch the recording path? (pull_request) Successful in 19s
CI / Docker image build (pull_request) Successful in 1m9s
CI / Bot tests and audit (pull_request) Successful in 1m54s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 3m4s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Successful in 11m54s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Failing after 14m6s
CI / Frontend tests, audit, and build (pull_request) Has been cancelled
CI / Backend migration, tests, and audit (pull_request) Has been cancelled
7dd03d0c62
The Who was there heading draws before the effect that fetches attendance;
main went red on it under CI load (run 1402).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test(e2e): J1 scans only the setup banner on Admin, which is still the legacy page (#728, #735)
Some checks failed
CI / Backend lint (ruff) (pull_request) Successful in 30s
CI / Does this change touch the recording path? (pull_request) Failing after 33s
CI / Docker image build (pull_request) Successful in 50s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 56s
CI / Bot/backend version sync (pull_request) Successful in 49s
CI / Feedback site tests (pull_request) Successful in 44s
CI / Bot tests and audit (pull_request) Successful in 1m48s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 12m32s
CI / Frontend tests, audit, and build (pull_request) Has been cancelled
CI / Backend migration, tests, and audit (pull_request) Has been cancelled
566bbe0336
Admin's Tailwind buttons fail contrast until lane 9 rebuilds the page; the
finished-setup branch scanned the whole page (run 1404, keyboard project).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ci: re-run; run 1405's checkout hit Forgejo 502s
All checks were successful
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 57s
CI / Backend lint (ruff) (pull_request) Successful in 59s
CI / Feedback site tests (pull_request) Successful in 39s
CI / Bot tests and audit (pull_request) Successful in 1m52s
CI / Bot/backend version sync (pull_request) Successful in 55s
CI / Does this change touch the recording path? (pull_request) Successful in 20s
CI / Docker image build (pull_request) Successful in 4m57s
CI / Backend migration, tests, and audit (pull_request) Successful in 13m6s
CI / Frontend tests, audit, and build (pull_request) Successful in 18m23s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Successful in 14m45s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 24m32s
612a37364a
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign in to join this conversation.
No description provided.