Lane 2: the invitation, the shared analytics page and Help (#728) #756

Merged
claude-bot merged 3 commits from build/lane2-pub345 into main 2026-09-22 18:40:48 +00:00
Collaborator

Lane 2 of the v4.5.0 redesign (#728), the fourth PR of its series: PUB-3, PUB-4 and PUB-5 — the three public pages a stranger is most likely to meet — rebuilt in the frame #751 added. Frontend only.

The one that reaches beyond this lane: eyebrows are sentence case now

Not a lane 2 departure, a lane 0 port error, found while building these pages.

prototype/shared/looks.css:153 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 sentence case is what every drawing the owner reviewed shows. Lane 0 ported kit.css's .label and the look's --label-* tokens, but not the look's own three properties — and shipped the version that decision retired. The app read AN INVITATION where the drawing reads An invitation, on the front door, sign in, Home, You and all five public pages.

Three properties in one file, restoring a settled decision rather than making one. It also improves contrast as a side effect: --ink-soft rather than --ink-faint is 8.1:1 in light and 9.8:1 in dark, against 5.5:1 and 6.7:1 before.

The surfaces

PUB-3, the invitation — SH-081 to SH-091, IS-196, IS-357, IS-393. Every state: looking it up, the invitation, not signed in, joining, the inline failure, already in it, the GM has been asked. A dead link is a stop in the world's voice (D65) with one line for all five reasons and the sentence saying why it says no more — the part that stops it reading as evasion. IS-196 is new: where a table records its sessions, the invitation says so before anyone joins, not after.

PUB-4, the shared analytics page — CA-242, SH-120 to SH-127, and SH-122 replaced. The failure that was a line of red text with nowhere to go now has somewhere to go.

PUB-5, Help — SH-092 to SH-119, IS-390. The index lists both guides with their sections, and those twenty section names are extracted from the markdown, not typed a second time. The extractor skips fenced code (the player guide has a ```text block whose contents would otherwise read as a heading), and a test asserts the index says Home and not The dashboard, so the day someone hand-copies the list back it goes red.

Departures, all recorded on #728 before building

  1. SH-124's "13 sessions in six months" is not a number the server has. The endpoint reports average_gap_days and sessions_last_30_days, and the ledger row says "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 the page keeps the sentence's shape with the number the server actually reports. The drawn words would need a new server field.
  2. SH-088 has no way onward, as drawn; the ledger says it links to the dashboard.
  3. SH-091 is on the default state only, as drawn; the ledger says every state.
  4. PUB-5 is not pub--wide. In a still, wide means "fill the still" (~600px); in a window it means up to 1560px — a guide set to a 1560px measure. Prose gets the narrow card.
  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 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.

Not a departure, and worth saying because it looks like one: SH-126's rate colours are built exactly as drawn. IS-295 asks that status never be carried by colour alone, and it is not — every cell prints the percentage and the fraction, so the colour is redundant with text already there. The one cell where that would not hold is the unknown one, which is why the drawing writes "never asked" in words rather than leaving a grey dash.

Two faults only the render caught

  • Tailwind's preflight strips the markers from every list. 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. The drawing has no preflight under it and assumes the browser's markers.
  • 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.

A false alarm worth recording

Headless Chromium on Windows silently clamps --window-size below about 492px and then returns a crop of the wider render — a 360px "phone" shot showed tiles and paragraphs cut off at the right edge that do not exist. A probe page printing innerWidth returned 492 for a 360×200 window. Three cycles went into chasing that before someone measured it.

The fix it produced was kept anyway, because it is correct independently: .pubshare .tscroll { min-width: 0 } — a grid item's min-width is auto, so overflow-x: auto alone lets a wide table push the card out instead of scrolling inside it. Six long display names on a real phone would do it. The comment says so and no longer claims anyone saw it.

Checks

lint 0 errors (239 warnings, all in pages no lane has rebuilt; zero in this PR's files, which are now held at error), 1298 tests in 115 files, build clean. All three pages served from the built dist and looked at, desktop and ~492px, both themes.

🤖 Generated with Claude Code

Lane 2 of the v4.5.0 redesign (#728), the fourth PR of its series: **PUB-3, PUB-4 and PUB-5** — the three public pages a stranger is most likely to meet — rebuilt in the frame #751 added. Frontend only. ## The one that reaches beyond this lane: eyebrows are sentence case now Not a lane 2 departure, a **lane 0 port error**, found while building these pages. `prototype/shared/looks.css:153` 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 **sentence case is what every drawing the owner reviewed shows**. Lane 0 ported `kit.css`'s `.label` and the look's `--label-*` tokens, but not the look's own three properties — and shipped the version that decision retired. The app read `AN INVITATION` where the drawing reads *An invitation*, on the front door, sign in, Home, You and all five public pages. Three properties in one file, restoring a settled decision rather than making one. It also improves contrast as a side effect: `--ink-soft` rather than `--ink-faint` is **8.1:1** in light and **9.8:1** in dark, against 5.5:1 and 6.7:1 before. ## The surfaces **PUB-3, the invitation** — `SH-081` to `SH-091`, `IS-196`, `IS-357`, `IS-393`. Every state: looking it up, the invitation, not signed in, joining, the inline failure, already in it, the GM has been asked. A dead link is a stop in the world's voice (D65) with one line for all five reasons **and the sentence saying why it says no more** — the part that stops it reading as evasion. `IS-196` is new: where a table records its sessions, the invitation says so **before** anyone joins, not after. **PUB-4, the shared analytics page** — `CA-242`, `SH-120` to `SH-127`, and `SH-122` replaced. The failure that was a line of red text with nowhere to go now has somewhere to go. **PUB-5, Help** — `SH-092` to `SH-119`, `IS-390`. The index lists both guides **with their sections**, and those twenty section names are **extracted from the markdown**, not typed a second time. The extractor skips fenced code (the player guide has a ```` ```text ```` block whose contents would otherwise read as a heading), and a test asserts the index says *Home* and not *The dashboard*, so the day someone hand-copies the list back it goes red. ## Departures, all recorded on #728 before building 1. **`SH-124`'s "13 sessions in six months" is not a number the server has.** The endpoint reports `average_gap_days` and `sessions_last_30_days`, and the ledger row says "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 the page keeps the sentence's shape with the number the server actually reports. **The drawn words would need a new server field.** 2. **`SH-088` has no way onward**, as drawn; the ledger says it links to the dashboard. 3. **`SH-091` is on the default state only**, as drawn; the ledger says every state. 4. **PUB-5 is not `pub--wide`.** In a still, `wide` means "fill the still" (~600px); in a window it means up to 1560px — a guide set to a 1560px measure. Prose gets the narrow card. 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 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. **Not** a departure, and worth saying because it looks like one: `SH-126`'s rate colours are built exactly as drawn. `IS-295` asks that status never be carried by colour alone, and it is not — every cell prints the percentage *and* the fraction, so the colour is redundant with text already there. The one cell where that would not hold is the unknown one, which is why the drawing writes "never asked" in words rather than leaving a grey dash. ## Two faults only the render caught - **Tailwind's preflight strips the markers from every list.** 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. The drawing has no preflight under it and assumes the browser's markers. - **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. ## A false alarm worth recording Headless Chromium on Windows **silently clamps `--window-size` below about 492px** and then returns a *crop* of the wider render — a 360px "phone" shot showed tiles and paragraphs cut off at the right edge that do not exist. A probe page printing `innerWidth` returned `492` for a 360×200 window. Three cycles went into chasing that before someone measured it. The fix it produced was kept anyway, because it is correct independently: `.pubshare .tscroll { min-width: 0 }` — a grid item's `min-width` is `auto`, so `overflow-x: auto` alone lets a wide table push the card out instead of scrolling inside it. Six long display names on a real phone would do it. The comment says so and no longer claims anyone saw it. ## Checks lint **0 errors** (239 warnings, all in pages no lane has rebuilt; zero in this PR's files, which are now held at `error`), **1298 tests** in 115 files, build clean. All three pages served from the built `dist` and looked at, desktop and ~492px, both themes. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Three of lane 1c's twelve public pages, in the frame the last PR built. All
three are pages a stranger meets before they have an account, and all three
were written before the redesign had a way to say anything to one.

**The invitation** (PUB-3, `/join/<code>`) is the page most people see first.
It now says what the table plays, how many are in it and who invited you on
one line, from two fields the server PR of this lane adds — read defensively,
so a campaign with no system simply has one fewer part and an older backend
loses the clause rather than the page.

The reason for those fields is `IS-196`: **where the campaign records its
sessions, the invitation says so before anyone joins**, not after. The consent
here has always been standing and the table already has it; what was missing
was that a newcomer found out afterwards, and "this table records" is a thing
you want to know while you can still decline.

A link that no longer works is a stop, not a failure (D65, `IS-393`). It opens
in the world's voice — *This invitation has run its course* — and then gives
the one plain line that covers all five reasons: unknown, revoked, expired,
used up, or a campaign that is gone. There is no retry, because there is
nothing to retry. What is new is the sentence under it, and it is the point of
the state: it says no more than that on purpose, or anyone could try codes and
learn which ones are real. Without that line the page looks evasive; with it,
a reader knows the silence is protecting somebody.

The three join outcomes are unchanged and still the server's — joined, asked
to join, already a member — because a link pasted into Join on Home takes this
same path, and `IS-357`/D46 is that both read the same.

**The shared analytics page** (PUB-4, `/analytics/share/<token>`) reads as
sentences rather than as a dashboard: four counts, how often they play, and
who was there. Three things about it are decisions.

Its wordmark points at `/` (`SH-122`). It always did, but `/` was not a route:
the catch-all sent an anonymous visitor to `/dashboard` and from there to a
sign-in page for an account they have not got, which for the one reader this
page is written for — somebody holding a link who has never used Quest Board —
was the only way out and did not work. `/` is the front door now.

Every rate carries its number, not only a colour (`IS-295`, #367): the
percentage *and* the fraction, so the green is redundant with text already
there. The one cell where that would not hold is the unknown one, and it says
"— never asked" in words, because somebody who joined after the last vote is
not somebody who voted nought times. The table is a real table — `th scope`
on the columns and on each person's name — and its sideways scroll is a named
region with a tab stop, so the columns past the edge of a phone are reachable
without a pointer.

A failure has somewhere to go (`SH-121`), which it did not: it was a line of
red text in the middle of an otherwise empty page. It says which two things it
might be, and offers the run again, because a share link that is merely slow
is the commonest of them.

**Help** (PUB-5) lists both guides with their sections — and reads those
sections, and each guide's title, out of the markdown rather than from a list
typed beside it. There was already a stale copy to prove the point: lane 1
renamed the player guide's fourth section from "The dashboard" to "Home", and
every hand-written list of it still says the old name. It also says in one
line that the administrator's guide is not in the app at all but in the
project's wiki (`IS-390`), and a guide that does not exist is the index saying
so rather than an error, because somebody mistyping an address wants the list
of guides.

Two things in the drawing could not be built as drawn, and are the build's
rather than a redesign:

- "13 sessions in six months" under *How often they play* is not a number any
  endpoint reports; the server counts sessions played in the last thirty days
  (`SH-124`), and that is what the sentence says. Its shape is the drawing's.
- The still gives a guide a one-line lead of its own. The guides have their
  own opening paragraphs, which say more and carry the cross-link to the other
  guide, so the markdown's is the lead and no second copy is written here.

Two things found by looking at the built pages rather than the source, neither
caught by a test: Tailwind's preflight strips the markers from every `ul` and
`ol` in the document, and a guide is the first page of the redesign to write a
list in prose — twenty sections of bullets with no bullets. And the index's
guide cards sat two ems in from everything else on the page, because the
frame's `.qb .pub ul` is more specific than a plain `.glist` and was still
applying a bulleted list's indent.

Both new stylesheets are scoped under the page's own class. `.stats` and
`.stat` belong to the front door as well, the two pages are in different
chunks, and which stylesheet the build links last is not something either page
can know — which is the shape of accident that made a primary link-button's
label invisible in production one PR ago.

The three pages move into the accessibility block that holds the design system
and what is built on it, which is where a page goes the day its lane rebuilds
it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`src/ui/base.css` still draws `.label` as tracked capitals. The look retires
that, and says so in as many words — `prototype/shared/looks.css:153`:

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

`:root[data-look]` is on in every page of the prototype, so **sentence case is
what every drawing the owner has reviewed shows**. Lane 0's port took
`kit.css`'s rule and the look's `--label-weight` / `--label-style` tokens, but
not the look's own three properties, and so shipped the version that decision
had retired. Every eyebrow in the app has read AN INVITATION where the drawing
reads An invitation.

Three properties, one rule. `text-transform` and `letter-spacing` are both
inherited and the `font` shorthand resets neither, so both are written out
rather than left to the initial value.

`--ink-soft` rather than `--ink-faint` is a contrast change as well as a case
change, and it is the right way: 8.1:1 in light and 9.8:1 in dark against
`--paper`, where the retired colour was 5.5:1 and 6.7:1. The one place the
swap would have gone the wrong way is the Contents rail, where a label sits on
navy — and the rail already sets `--chrome-soft` on its own (`Contents.css`),
which is exactly what `looks.css:167` does.

Checked rather than assumed, with axe-core run in the page under the same tag
set the journeys use (`WCAG_22_AA_TAGS`): 0 violations on the front door, the
invitation, Help and the shared analytics page, in both themes, and 0
colour-contrast violations on both guides in both themes. Looked at as well —
`/design`'s Label, Doc and Contents sections, the front door and the
invitation — because a case change is not something a contrast checker has an
opinion about.

No test pins this. The jsdom suite runs with vitest's default `css: false`, so
no stylesheet is applied and `getComputedStyle` sees nothing; there is no
precedent in the suite for a test that reads a source file. The honest home is
an assertion in the journeys' front-door scan, which needs a run of
`scripts/journeys.sh` to be worth adding.

The stale "small-caps" prose in `Label`'s docstring, its `/design` blurb and
`PublicPage.css`'s note goes with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Merge remote-tracking branch 'origin/main' into build/lane2-pub345
All checks were successful
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 53s
CI / Bot tests and audit (pull_request) Successful in 1m57s
CI / Backend lint (ruff) (pull_request) Successful in 1m14s
CI / Bot/backend version sync (pull_request) Successful in 28s
CI / Docker image build (pull_request) Successful in 48s
CI / Feedback site tests (pull_request) Successful in 1m23s
CI / Frontend tests, audit, and build (pull_request) Successful in 5m14s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 4m5s
CI / Backend migration, tests, and audit (pull_request) Successful in 16m10s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Successful in 13m28s
cda33f922f
# Conflicts:
#	CHANGELOG.md
Sign in to join this conversation.
No description provided.