A title per page, scroll that resets between pages, and the player-owner's line (#796) #799

Merged
claude-bot merged 3 commits from build/796-titles-scroll into main 2026-09-24 03:18:27 +00:00
Collaborator

Closes #796 (R4, from the B-questions).

Titles (WCAG 2.4.2)

  • Inside the frame, AppFrame sets document.title once per navigation from the bar's own breadcrumb (titleFor in useContents.js). The breadcrumb's root moves to just before the brand:
    • "This week · The Amberwake Accord · Quest Board"
    • "Settings · Invites & joining · The Amberwake Accord · Quest Board"
    • "Home · Quest Board"
  • Outside the frame, each of the nine public pages sets its own title with a small useDocumentTitle hook: help (index and each guide), sign in, privacy, licences, accessibility, setup (per step), the invitation (with the campaign's name once it's known), and not found. The front door is just "Quest Board".
  • pageTitle() in vocabulary.js drops empty segments, so a page whose data is still loading never shows "undefined".

Scroll

  • A forward navigation puts .book__main back at the top. If the address has a fragment, it scrolls to the element the fragment names instead, which is the same target useRouteFocus resolves (fragmentTarget, now exported). If the target arrives late with a lazy page, a bounded MutationObserver keeps looking.
  • Back and forward restore the position you left the page at. This uses a per-tab pathname → scrollTop map. React Router's ScrollRestoration only restores the window, and in this app the window never scrolls.

The player-owner's line (D93)

A campaign owner who isn't its GM now reads "You're a player, and you own this campaign." The GM's line is unchanged.

Checks

  • Full frontend suite on the merged tree: 143 files, 2011 tests passing. Lint: 0 errors (228 warnings, none new).
  • New tests:
    • useScrollRestoration.test.jsx, which uses the test-controlled arrive() gate rather than timers.
    • Title and meta-line blocks in AppFrame.test.jsx.
    • Title assertions for Front, Login, NotFound and JoinCampaign.

🤖 Generated with Claude Code

Closes #796 (R4, from the B-questions). ## Titles (WCAG 2.4.2) - **Inside the frame**, `AppFrame` sets `document.title` once per navigation from the bar's own breadcrumb (`titleFor` in `useContents.js`). The breadcrumb's root moves to just before the brand: - "This week · The Amberwake Accord · Quest Board" - "Settings · Invites & joining · The Amberwake Accord · Quest Board" - "Home · Quest Board" - **Outside the frame**, each of the nine public pages sets its own title with a small `useDocumentTitle` hook: help (index and each guide), sign in, privacy, licences, accessibility, setup (per step), the invitation (with the campaign's name once it's known), and not found. The front door is just "Quest Board". - `pageTitle()` in `vocabulary.js` drops empty segments, so a page whose data is still loading never shows "undefined". ## Scroll - A forward navigation puts `.book__main` back at the top. If the address has a fragment, it scrolls to the element the fragment names instead, which is the same target `useRouteFocus` resolves (`fragmentTarget`, now exported). If the target arrives late with a lazy page, a bounded `MutationObserver` keeps looking. - Back and forward restore the position you left the page at. This uses a per-tab `pathname → scrollTop` map. React Router's `ScrollRestoration` only restores the window, and in this app the window never scrolls. ## The player-owner's line (D93) A campaign owner who isn't its GM now reads "You're a player, and you own this campaign." The GM's line is unchanged. ## Checks - Full frontend suite on the merged tree: 143 files, 2011 tests passing. Lint: 0 errors (228 warnings, none new). - New tests: - `useScrollRestoration.test.jsx`, which uses the test-controlled `arrive()` gate rather than timers. - Title and meta-line blocks in `AppFrame.test.jsx`. - Title assertions for Front, Login, NotFound and JoinCampaign. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
document.title now changes with the address: the signed-in frame builds it
once, per navigation, from the Contents' own breadcrumb (`titleFor` in
useContents.js), most specific first and Quest Board last. The dozen pages
outside the frame each set their own with a small useDocumentTitle hook and
vocabulary.js's pageTitle builder; a title that depends on data still loading
falls back to a sensible one rather than ever reading "undefined".

A forward navigation now resets .book__main to the top, or to the element a
fragment names (useScrollRestoration.js), instead of opening wherever the
last page left the scrollbar; a browser back or forward restores the
position it was left at, from an in-tab pathname -> scrollTop map. It waits
for a fragment's target the same way useRouteFocus does, for pages still
behind a lazy chunk, and runs before useRouteFocus so the two land on the
same place rather than fighting over it.

Also: a reader who both plays in and owns a campaign now reads "You're a
player, and you own this campaign." in the Contents (D93, #789), instead of
the plain player's line that did not say so.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
fix(frontend): the front door's tab is the brand alone (#796)
All checks were successful
CI / Does this change touch the recording path? (pull_request) Successful in 22s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 54s
CI / Bot tests and audit (pull_request) Successful in 1m49s
CI / Feedback site tests (pull_request) Successful in 1m53s
CI / Docker image build (pull_request) Successful in 1m5s
CI / Bot/backend version sync (pull_request) Successful in 1m9s
CI / Backend lint (ruff) (pull_request) Successful in 1m19s
CI / Frontend tests, audit, and build (pull_request) Successful in 5m10s
CI / Backend migration, tests, and audit (pull_request) Successful in 11m55s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 9m59s
6d6ae6bd47
"The front door" is the design's name for the page, not a title a
reader would recognise. pageTitle()'s own docstring already says the
front door's tab is the brand alone; this makes it so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign in to join this conversation.
No description provided.