Loading…
Reference in a new issue
No description provided.
Delete branch "build/lane3-j6"
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 3's acceptance gate, and the one piece of it nobody had written. #729 requires "J1 … and J6 (Highlights, Storyline) pass on desktop, on a phone and keyboard-only … with the axe scan clean";
e2e/had j0, j0b, j1 and j2.sh scripts/journeys.sh(whole suite, no grep): 24 passed, 6 skipped — the six are the two pre-existingtest.skipplaceholders × three projects. Frontend unit suite 1591 / 127 files.a11y-baseline.jsis still{}and no run printed atoleratedline.What it walks
One sign-in as Priya, four acts, read-only.
The recap. Home's card carries Last time and the first sentence; she opens the campaign by the card's own Open link rather than an address — that is the act's claim, that a player who missed the evening finds out what happened without being told where to look. On This week the h1 is the session's title (no numbering, #760) and "Previously on" carries the whole recap, asserted via its last sentence, which the card's one-liner does not contain.
SE-006's "would not load" state is walked for real —page.routeaborts the cold-open request, the page reloads, and the journey asserts the banner, the retry, the absence of Hide, and that the h1 and About the campaign survived. That is aSectionBoundarydoing its job in a browser rather than in jsdom, which nothing else in the suite has ever shown. The absent state is deliberately not walked and says so in the spec: it renders nothing, and reaching it would need a second campaign existing only to have no history.ThisWeek.test.jsxholds it.Highlights. Two groups, one per played evening; every line asserted under its own group and absent from the other. Then the rule the page exists for: the group's date is read out of the
<h2>and asserted to be non-empty, not today's (the quotes were filed minutes earlier by the seed — printingcreated_atis precisely the mistake) and different between groups. The filter is exercised on a speaker who spoke in both evenings (both groups must stand) and one who spoke in one, and the "Said by" choices must still number all four afterwards (CA-228).Storyline. D47 in one assertion: exactly two level-2 headings — the story and the timeline, not four things. The timeline's first row carries the same date string Highlights printed for that session, so two surfaces are pinned to agreeing about one evening, which they would not if either had reached for
created_at.The visibility leak is asserted three ways — the
gmandplayer_privatetitles and details absent from the page; a directGET /timelinewith Priya's own cookies asserted not to contain them while it does contain the two public ones, so the assertion cannot pass vacuously; and again on the wiki article.The wiki is walked, not stubbed — Contents → front → search → entry → its timeline — and marked in the spec as lane 8's seam with a note on what lane 8 must keep true.
Eight axe scans per project, 24 a run, all clean
Home is scanned even though J2 scans it in the same run: the rule is a scan on every page a journey reaches, and a journey leaning on another's coverage goes quiet the day that one stops looking.
Findings in already-merged code
1. A WCAG 2.2 AA 1.4.3 failure on the wiki front page — fixed here. "Last updated …" was
text-gray-400 dark:text-gray-600: #99a1af on white at 12px, 2.6:1 against a 4.5:1 minimum. Nowgray-500/gray-400(4.84:1 light).It had never fired because the line only draws when a storyline has a
generated_at, and until J6 seeded a story no e2e stack had ever had one. The journey's own fixture is what exposed it.The pattern is systemic: that same pair is on 16 more faint lines across 7 files, all on surfaces lanes 4, 5 and 8 still have to rebuild. Only the one a journey walks is fixed here; the rest is filed separately, because a sixteen-place sweep belongs in a deliberate commit and not inside a journeys PR.
2.
a11y-baseline.jscannot describe a campaign-scoped page at all — it looks entries up as an exactA11Y_BASELINE[pathname]key, and every address inside a campaign carries a per-run UUID. From lane 3 onwards the only key that can ever match is"*", which quietens a rule on every page in the suite. So tolerating the contrast failure above was not actually an option; fixing it was the only move available. Filed.3. The wiki is a dead end on a phone. The category rail is
hidden md:flex, and on an article it is both that page's only navigation surface and where its copy of the search lives. Below 768px, once a player is on an entry there is no wiki navigation and no wiki search — the only way onward is the browser's Back button. That is a step strictly harder on a phone than with a mouse, which is a regression against the brief's own bar. J6 passes on all three projects because it uses the front page's search, which #396 moved into the main column. Named on lane 8's issue.4. The generated story renders its chapter separators as literal
---paragraphs —update_campaign_storylinejoins chapters with"\n\n---\n\n"andchapters()treats anything that is not aChapter N —heading as a paragraph. J6 deliberately does not assert this: asserting current-but-wrong output cements it. Filed.5.
identities.jsoncarried the GM's retired surname in"sub": "e2e-dana-okafor"— in the file the README calls "the one place their names are written down", which is exactly why it must not be wrong there. Fixed in its own commit. Safe:make journeysstarts fromdown -vandauth.pymakes the first user admin regardless ofsub.6. The suite is now visibly paying the sign-in limiter. Eighteen sign-ins a run against 10/minute per IP.
signInAswaits the 429 out so every run is green, but one test per run takes ~23s instead of ~3s, and which test moves between runs — the signature of a window being waited out rather than of a slow page. Worth saying before somebody diagnoses it as a slow page. Noted on #726.7. The axe gate only ever sees light mode — Chromium's default
prefers-color-scheme. The brief makes both themes first-class on every page, so the dark half of the contrast fix above is computed, not measured. Noted on #726 as a decision for whoever owns the harness.Choices a reviewer should weigh
update_campaign_storyline, which rebuilds the body from every summarised session with no model involved, so the seed waits for the worker. A hand-written body would setmanually_edited_atand take that path out of the run.🤖 Generated with Claude Code
Lane 3's acceptance gate, and the sixth of the brief's seven journeys. Priya signs in once and reads her way through a week she missed, on a desktop, on an emulated Pixel 7 and with no pointer at all, with an axe scan on every page she reaches: 1. the recap — Home's card already carries last time's first line, the campaign's own page carries the whole of it under "Previously on" (SE-006), and the door to the session names the same evening. The second of that section's three states is walked for real by failing the cold-open request: it says so, offers a retry, and the rest of the page is untouched. The third renders nothing at all, so there is no surface to walk; ThisWeek.test.jsx holds it. 2. Highlights — two groups under the two evenings, each dated from its session rather than from the day the line was kept, and a speaker filter that leaves the grouping standing and whose choices do not shrink as it filters (CA-228). 3. Storyline — two things and not four (D47): the story in chapters, and one timeline over sessions, a milestone and the wiki's own events, filterable by kind, read-only for a player all the way down. **A player is handed neither a `gm` timeline event nor a `player_private` one**, asserted on the page, on the article the events belong to, and in the answer the server sends. 4. the wiki — lane 8's page, walked against what is there today rather than stubbed, and marked as the seam it is. The journey mutates nothing. What it reads is seeded as scenery, because the three projects share one database and run one after another: the seed gains a second played session (one group is always correctly grouped, so neither grouping nor ordering is testable against one), two recaps written with the GM's own endpoint, four highlights published by approving the sessions, a milestone, and a wiki entry carrying four timeline events — two of them seeded so that they can be absent. The story is not written by the seed at all: setting a summary fans out to update_campaign_storyline, which rebuilds it from every summarised session with no model involved, so the seed waits for the worker instead of hand-writing a body that would mark the storyline edited and take that path out of the run. The shell's Contents gains a fixture of its own — openContents/goVia — because it is a rail on a desktop and a sheet behind a button on a phone, and every lane from here on has to walk it. One defect fixed in a page this found: the wiki front page's "Last updated" line was #99a1af on white at 12px, 2.6:1 against WCAG 2.2 AA's 4.5:1, and about the same in dark. The journeys are the first thing ever to put a generated story in front of it. It could not have been baselined even if that were wanted — a11y-baseline.js keys entries by exact pathname and every address inside a campaign carries a UUID that changes with the run — so the only key that would have matched is "*". a11y-baseline.js stays {}. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>CI was right and the local runs were not. The session page (`SessionDetail.jsx`) fails `color-contrast` on **thirteen** nodes, and J6 had been certifying it clean twelve runs in a row. Not a race in the seed, and not the shell putting a dark chip on light paper: the chips are bare `bg-amber-900/50 text-amber-300` and `bg-green-900/50 text-green-300` with **no `dark:` prefix**, so they are dark-intent styling applied unconditionally and have been wrong in light mode since long before the redesign — axe prints `dark:` classes in its selectors when they are there, and for these it does not. Three scans of the settled page report the same thirteen nodes, so the page itself is deterministic. What was not deterministic was **what J6's scan was pointed at**. The two assertions before it — the session's title, and the recap text — are both satisfied by the session request alone, while every failing element belongs to a second wave (the roster's RSVP chips, and the post-session checklist, which renders at all only because this seed writes post-session task rows). A readout at the scan moment shows that second wave absent; axe reports it present, because it arrives inside axe's own injection window. Whether the scan sees it is therefore decided by how fast the machine is: green here, red on the runner. So: - **J6 stops at the door.** It asserts that "Read the full recap" and the aside's "Read the recap" name the same session, and goes no further. `/sessions/:id` is lane 4's to rebuild and J5's to walk — "after the session: the checklist, transcript and summary review" — and lane 3's acceptance gate should not be held on re-colouring a page lane 4 deletes. The thirteen nodes are measured and listed in the PR rather than tolerated; `a11y-baseline.js` stays `{}`. - **Every scan now waits for its page's *last* request**, not its first. This week waits for the campaign card at its foot; the wiki front waits for the story and the "Last updated" line — which is the element this branch fixed, so that scan was a coin-flip in the other direction. The rule is written into the harness README beside the scan rule it qualifies, because a scan whose target is a race certifies nothing and fails where it is least likely to be believed. Verified with `--repeat-each=3`: nine of nine, run times clustered within a second where they used to vary. Also folds in `ruff format` on the seed (the backend lint job's pin is 0.4.4; `ruff check` and `ruff format --check` both clean over `webapp/frontend/e2e/`). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>The red CI run: CI was right, the local runs were not
Pushed as
0d30b7c. A correction to this PR's description first, because it named the wrong cause and I would rather fix it at the top than quietly edit it:That was wrong on both halves, and it is the claim that cost the red run. The session page fails on 13 nodes, deterministically — reproduced three times, identical selectors and ratios each time, matching CI exactly. And scanning a page built from pre-redesign markup costs whatever that page's debt is, which is a reason to know the debt before adding the step, not a reason to add it and find out.
I also guessed above that the blended backgrounds might be dark-variant classes leaking into light mode, which would have made this a shell-integration fault and lane 3's. That is wrong too. The chips are bare and unconditional:
No
dark:prefix and no light counterpart — confirmed twice over: axe printsdark:classes in its selectors when they exist (it does for a different element on the same page) and prints none for these, and the computed style is a translucent dark green actually applied in light mode. The old app has supported light mode all along, so these have been wrong since before the shell existed. Filed as #775, and named on lane 4's issue.The actual bug was in the journey, and it is the interesting part
The page fails every time. What was intermittent was what the scan was pointed at.
The two assertions before
axe()were the session's title and the recap — and both are satisfied by the session request alone. Every failing element belongs to a second wave: the roster's RSVP chips and the post-session checklist. A readout at the exact scan moment:The second wave is absent when the assertions clear and present by the time axe finishes — it arrives inside axe's own injection window. So whether the scan saw it was decided by machine speed. Twelve green local runs (four full suites × three projects); the runner lost the coin-flip. That is the worst failure mode a gate can have, and it was mine, not CI's.
Worth saying how the wrong claim got made: from a throwaway probe that did
page.goto()thenaxe()with no wait at all, scanning a page that had barely begun rendering — and then twelve green runs "confirmed" it.Fixed in three places, not one
/sessions/:idwas never in J6's scope — the contract's recap act is This week'sSE-006— the markup is lane 4's to delete, and lane 3's acceptance gate should not be blocked on lane 4's debt. The spec carries the diagnosis in place so lane 4 inherits it rather than rediscovering it.Last updatedline — and that one matters:Last updatedis the element this branch fixed, and the searchbox the scan had been waiting on is static first-paint markup, so that scan was a coin-flip in the other direction and the violation it found was luck. Deterministic now.axe()rule it qualifies, with this failure as the worked example: wait for the slowest thing on the page, and pick it deliberately — a scan whose target is a race certifies nothing, and it fails in the place least likely to be believed.a11y-baseline.jsis untouched and still{}.Verification
--repeat-each=3: 9 of 9, run times now clustered within a second (9.6–10.6s) where they used to vary — consistent with deterministic waits rather than a race.eslint e2e src/pages/WikiFront.jsx: 0 errors.ruff checkandruff format --checkclean overe2e/with the pinned 0.4.4.main(four commits ahead, including #769's Settings, which touchesui/Doc/index.jsxthat J6'ssectionOf()helper reads) on a scratch branch: 24 passed. The branch is behind but will need no spec changes to merge.🤖 Generated with Claude Code