Lane 2: the public frame, the front door and sign in (#728) #751
Loading…
Reference in a new issue
No description provided.
Delete branch "build/lane2-public-frame"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Lane 2 of the v4.5.0 redesign (#728), the third PR of its series and the first UI one. The drawing is
/feedback/prototype/lane1c, sectionspub11,pub1,pub2.The cascade, which is the real story here
The journeys went red on this branch — 18 failed, 82 axe colour-contrast violations across the app bar, the Contents rail and the account links — and it turned out to be one bug with three faces.
The tie.
base.cssscopes the type to.qb, andAppFrameputs.qbon each chrome piece rather than on the frame's root (thesrc/ui/README.mdrule, so pages not yet rebuilt keep their own look). So.qb { background: var(--paper) }and.appbar { background: var(--chrome-bg) }are equally specific and land on the same element, as are.rail's, and.qb aagainst each ofa.btn--primary,.foot a,.appbar a,.toc--sub a. Those ties are meant to go to the component, and they did — on source order alone.main.jsxarranges that order deliberately and says so in a comment, because lane 1 hit it once already.Chunking defeats source order. Measured, by building
main's frontend beside this branch's and reading byte offsets in the emitted CSS:mainindex-*.css).qb{88317 →a.btn--primary{94880 →.rail{110463 →.appbar{110483 →.foot a{115582ui-*.css, thenindex-*.css)Adding a second eager consumer of the library — the front door, through
PublicPage— tipped the bundler into emitting the kit's CSS as a shared chunk, and a shared chunk's stylesheet is linked before the entry's. So.qbstarted winning: the bar and the rail lost--chrome-bg, while everything inside them kept the near-white--chrome-inkthat is correct on navy and unreadable on paper.The fix is one line, in
src/ui/index.css:An un-layered rule beats a layered one whatever order the files load in, so "a component wins over the base" becomes a fact about the stylesheet instead of a fact about the bundler's output. Nothing else is layered, deliberately — the only rule being written down is that the base sits underneath.
Two alternatives were tried and rejected on measurement, not on taste.
build.cssCodeSplit: falsegives one stylesheet but concatenates with the entry last (.qb{at 117364 against.appbar{at 17741), making the inversion permanent. Hand-weakening every.qb-level rule with:where()is the same idea with a far wider blast radius, and unnecessary once the layer covers the class.The surfaces
PUB-11, the front door, at
/—IS-384,IS-385,IS-386,IS-388. Until now/was not a route at all: the catch-all sent whoever typed the bare address to/dashboard, which bounced them to a sign-in page for an account they have not got. D62 makes it a page about Quest Board — the hero, how a night runs, what you get, this server's numbers, and who runs it.The numbers are the server's, never a campaign's (D64). Turned off means the block is not drawn, not zeroes; a server too new says so in a line, because three noughts read as a broken page rather than a new one.
/public/sitefailing is not an error to show a stranger — the page is complete without it.PUB-1, sign in —
SH-074,SH-075,SH-076,SH-077,SH-087,SH-152,IS-386,SH-032. The tagline moves to the front door. Two states that were silent now say which they are: a session that ran out, and the goodbye after deleting an account.PUB-2, sign-in failed —
SH-078,SH-079,SH-080,IS-393. OnStop: The gate stayed shut, the provider's words, one way back (D65).Behaviour rows, tested rather than drawn:
SH-001,SH-002,SH-014,SH-031,SH-073.The journeys, and the baseline
/auth/login(its comment explains that this is the tripwire for a test-only endpoint), and signed out is still outside the frame. The keyboard test now asserts its locator resolves before asking anything of it — the pointer guard refuses before a locator is resolved, so one matching nothing would pass everytoThrowand certify nothing.A11Y_BASELINEis now{}./loginwas its last entry, named to lane 2 by owner; this lane rebuilt the page, so the entry goes. Verified rather than assumed —--grep J0green with nothing left to tolerate.A product bug found by writing J1
The front door's Sign in went straight to
/auth/login. D62 is that sign in is a page behind the front door, reached from it — jumping to the provider took away the two states PUB-1 exists to carry. Both Sign in controls go to/loginnow.Departures, and what the drawing did not settle
blurbonGET /public/site(#752), and the paragraph is composed from whatever the server has actually said, each clause drawn only when there is something true to put in it.<h2>s. The drawing writes them as<span class="label">; a named region says where you are once inside it, but a heading is how a screen-reader user gets between sections at all. They keep the label's look, anddisplay: inlinekeeps the span's box — the built page is a byte-identical screenshot before and after..stop__voicegoes back tovar(--serif), aslane1c.html:181has it: the only heading in the app that opts out of the display face, so the sentence spoken in the world's voice is softer than the furniture. Lane 0's port flattened it..pub--widegot a measure (the drawing's own 1560px page width;max-width: nonewas the still supplying the bound).SH-152usessessionStorage, because/auth/logoutis a server redirect to a bare/loginwith nothing to put a parameter on. One tab, one use, cleared on read.SH-032's promise is made true by #752, which widens_safe_next.Also found by looking at the built output
With a short
?message=, the sign-in-failed stop drew narrow, centred and indented inside its card. A grid item with anautomargin does not stretch — the margins eat the free space and the item shrinks to its content — soStop's ownmargin: 0 auto, right when it is the whole window, made the box as wide as its longest sentence. A long message clamped to the track and looked fine, which is how it survived the first look.Checks
--grep J012 passed and--grep J16 passed (3 skipped, lane 3's placeholders) on desktop, phone and keyboard, locally. Lint 0 errors (239 warnings, the unchanged baseline), 1269 unit tests, build clean. An independent axe run of the three public pages against the built bundle, two viewport shapes × both colour schemes: 12/12 clean.🤖 Generated with Claude Code
`/` was not a route. The catch-all sent whoever typed the bare address to `/dashboard`, which bounced them to a sign-in button for an account they have not got — so the first thing a stranger saw of Quest Board was a door with no handle. D62 makes `/` a page about the product, and sign in a page behind it. Three of lane 1c's twelve public pages, and the frame all twelve share: - **PublicPage** (`src/shell/`) is the head, the page and the footer, and nothing else — a signed-out visitor has no Contents, no bell, no search and no live bar, so this is not `AppFrame`. The head is the wordmark or a crumb back to the front door; `footer={false}` is for the screens that are only a wait. Its links are in one exported constant so a later PR of this lane can extend it, and today it carries only the doors that exist: Accessibility, Privacy and Licences & notices are in the drawing but their pages are not built yet, and three dead links is worse than three absent ones. - **The front door** (PUB-11, IS-384/385/388) says what Quest Board is, how a night runs, what you get, and — from `GET /public/site` — a few numbers and who runs this server. The numbers are the server's, never a campaign's or a person's; turned off means no block at all, and a server too new says so in a line rather than showing three noughts. A server that has not said how to reach whoever runs it says that. The endpoint failing is not an error to show: the page is complete without those two sections, which is also how it behaves on an instance whose backend has not caught up. - **Sign in** (PUB-1) loses the tagline to the front door and gains the reason you are here: *Your session ran out* (SH-032) and *Your account is gone* (SH-152) both used to land on the page a first visit lands on, with nothing said. The 401 handler says why in the query; deleting an account cannot, because the server's own sign-out redirect is not ours to write, so it leaves the reason where the page will find it. - **Sign-in failed** (PUB-2) is the library's `Stop`: the world's voice, then the provider's own words or the plain line, and one way back (D65). The catch-all is untouched — an address that does not exist saying so is SH-009, later in this lane — and no existing path moves. One thing found by looking at the built page rather than the source: a primary link-button's label was invisible in production. `base.css`'s `.qb a` and a component's own link colour are equally specific and land in different chunks, with the entry chunk's linked last, so every rule a component wrote to claim a link's colour won in `vite dev` and lost in the build. The base rule is `:where()`-wrapped now and carries no weight of its own. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>The 18 red journeys were one bug, and it is not the one I reported yesterday. **The mechanism, measured.** Half of what a component's stylesheet claims, it claims at the same specificity as `src/ui/base.css` and *on the same element*: the base scopes the type to `.qb`, `AppFrame` puts `.qb` on each chrome piece, so `.qb { background: var(--paper) }` ties with `.appbar { background: var(--chrome-bg) }` and `.rail`'s, and `.qb a` ties with `a.btn--primary`, `.foot a`, `.appbar a`, `.toc--sub a`. Those ties are meant to go to the component and do, while base.css is emitted first — which `main.jsx` arranges deliberately and explains in a comment. Chunking defeats that. Building `main`'s frontend beside this branch's and reading the byte offsets: main one stylesheet: .qb{ 88317 → a.btn--primary{ 94880 → .rail{ 110463 → .appbar{ 110483 → .foot a{ 115582 this PR two: ui-*.css, then index-*.css — base.css last Adding a second eager consumer of the library (`Front`, through `PublicPage`) tipped the bundler into emitting the kit's CSS as a shared chunk, and a shared chunk's stylesheet is linked before the entry's. So `.qb` began winning: the app bar and the Contents rail lost `--chrome-bg`, while everything inside them kept the near-white `--chrome-ink` that is correct on navy and unreadable on paper. 82 contrast violations across the frame. **So yesterday's "find" was this bug's first symptom, not a second bug.** The unlabelled primary button was `a.btn--primary` losing the same tie; there was never an invisible button on `main`. Worse, the `:where()` I put on `.qb a` handed `.appbar a` and `.rail .toc a` their win too, which is why the crumb and the Contents links went from legible accent to white-on-white. That change is reverted. **The fix** is one line: `@import "./base.css" layer(qb-base)`. An un-layered rule beats a layered one whatever order the files load in, so "a component wins over the base" stops being a property of the bundler's output. Nothing else is layered — the rule being written down is only that the base sits underneath. I stopped short of weakening every `.qb`-level rule by hand, which would have been the sweeping version of the same idea with a much wider blast radius. Also here, from the review: - **The journeys walk the pages this lane rebuilt.** J0's signed-out test asserts the page as it is now — heading *Sign in*, the wordmark a crumb back to `/` — and keeps every guarantee it was making: the sign-in control is still an anchor to the backend's own `/auth/login`, and signed out is still outside the frame. The keyboard test now asserts its locator resolves before it asks anything of it, because the pointer guard refuses *before* a locator is resolved and a locator matching nothing would pass every `toThrow`. J1 starts at the front door, which is the first page a GM meets, and scans it — it was the one page in the app with no axe scan on it. - **The a11y baseline is empty.** `/login` was its last entry and this lane rebuilt the page, so the entry goes and the header prose goes with it. Verified rather than assumed: `--grep J0` green with nothing left to tolerate. - **The front door's Sign in goes to `/login`, not to the provider.** D62 is that sign in is a page behind the front door, "reached from it"; jumping straight to the provider took away the two states that page exists to carry. Locally: `--grep J0` 12 passed, `--grep J1` 6 passed (3 skipped, lane 3's), on desktop, phone and keyboard; lint 0 errors; 1269 unit tests; build green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>