Wave 0 exists because the plan's line numbers came from a review of users/directory-super-user rather than a fresh read. This is the fresh read: 7 pages, 6 stylesheets, 11,867 lines, with each page's stylesheets, scripts and iframe role recorded, and all seven baseline counts captured with the command that produced them. Four discrepancies, one of which matters a great deal: D2 - CLAUDE.md's "logged-override path for predecessors stays (A1). See wp-creation-app.js:1962-1972" cites the wrong function. That range is dashIssue(), which REFUSES to issue and says "open the package to release it early with a logged reason". The reviewer read that sentence and correctly inferred an override exists, but cited the mention rather than the code. The audited path is confirmEarlyRelease() at 967-984 plus seven satellites (state at 392, call sites at 998 and 1149, persisted at 1117, rendered at 1215, rehydrated at 1674, reset at 481/488/1744). A T7.3 that preserved only 1962-1972 would delete the business rule while believing it had protected it. D1 - "6 pages, 4 stylesheets" is 7 and 6; wave-0's own parenthetical lists seven names. Every "all 6 pages" done-when is off by one. D3 - four documents the plan reads from are deliverables not yet written. D4 - the creator overflows horizontally at 1440px, which no F item covers. Logged as BL-001 rather than fixed, since T7.1 rebuilds that layout anyway. BL-002 records that outline:none appears three times in the wizard sheet, not once, so T3.4 fixes all three. Counts confirmed against the review: 79 dialogs (43 in the creator), 12 div and 2 span onclick, 15 help-tip badges, 0 aria-live, 0 pushState. The "4 declarations of #0f62fe" needed a definition - there are 31 occurrences and 14 custom-property declarations; the 4 is the number of stylesheets declaring their own accent token, which is the number wave 9 should drive to 1. No application code changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
16 KiB
File map — html/ and the references the plan depends on
Task: T0.1 · Produced: August 14, 2026 · Branch: feat/wp-suite-r2-implementation
Wave 0 exists because the item IDs and line numbers in this plan came from a UX review of
branch users/directory-super-user, not from a fresh read. This document is the fresh read.
Everything below was verified against the working tree, not quoted from the review.
Read the Discrepancies section before starting any wave. Four things the
plan asserts are wrong, and one of them (A1) guards a code path CLAUDE.md says must not change.
1. Pages
html/ holds 7 pages, 6 stylesheets, 11,867 lines. The plan says "6 pages, 4 stylesheets,
roughly 11,900 lines" — the line count is right, the other two are not. See D1.
| Page | Called in this plan | Lines | Stylesheets (load order) | Scripts (load order) | Iframe |
|---|---|---|---|---|---|
html/login.html |
login | 163 | theme-light.css |
login.js |
neither |
html/index.html |
launcher | 703 | theme-light.css, wp-chrome.css |
auth-guard, wp-format, feedback-config, project-data, help, (inline 418–700), wp-chrome |
neither |
html/work-package-suite.html |
SOP wizard | 437 | theme-light.css, wp-chrome.css, work-package-suite-styles.css |
auth-guard, wp-format, feedback-config, project-data, help, work-package-suite-app, wp-chrome |
hosts |
html/wp-creation-index.html |
creator | 401 | theme-light.css, wp-creation-styles.css |
auth-guard, wp-format, feedback-config, project-data, help, wp-creation-app |
child |
html/admin.html |
admin | 219 | theme-light.css, wp-chrome.css, console.css, wp-sidenav.css |
auth-guard, wp-format, console-util, admin, wp-chrome, wp-sidenav |
neither |
html/users.html |
directory/users | 106 | theme-light.css, wp-chrome.css, console.css, wp-sidenav.css |
auth-guard, wp-format, console-util, users, wp-chrome, wp-sidenav |
neither |
html/field.html |
field view | 93 | theme-light.css, wp-chrome.css, wp-sidenav.css |
auth-guard, wp-format, project-data, help, field, wp-chrome, wp-sidenav |
neither |
No script anywhere in html/ uses defer, async, or type="module". Every one is a
render-blocking classic script. auth-guard.js and wp-format.js load in <head>; the rest
load at the end of <body>.
The iframe boundary (matters for B7 / T7.1)
work-package-suite.html:383 declares <iframe id="wp-frame"> with no src.
work-package-suite-app.js:581 sets it at runtime to
wp-creation-index.html?embedded=1&project=<id>.
The creator is the only page that loads neither wp-chrome.css nor wp-chrome.js — that is
why it has no app bar of its own and why it looks like part of the wizard. T7.1 has to give it
that chrome back, or deliberately not.
Three scripts branch on window.top !== window.self and will change behaviour when the frame
goes away:
| Script | Line | Framed behaviour |
|---|---|---|
auth-guard.js |
15 | redirects window.top to login.html |
wp-chrome.js |
17 | returns early, renders no chrome |
help.js |
— | suppresses the Help FAB in the child |
Pages that are not what the plan implies
field.htmlis 93 lines. It is a shell;field.js(178 lines) builds it. A task that says "edit the field view page" almost always meansfield.js.index.htmlcarries ~296 lines of inline<style>(lines 16–312) — the bulk of the launcher's CSS is not in any stylesheet.T3.xtoken work has to reach inside it.admin.htmlandusers.htmlare near-identical shells overconsole.css+console-util.js.
2. Stylesheets
| File | Lines | Purpose | Consumed by |
|---|---|---|---|
theme-light.css |
249 | Carbon light (g10) token set, base type, shared dark appbar | all 7 pages (always first) |
console.css |
191 | dense admin tables and toolbars | admin, users |
work-package-suite-styles.css |
610 | SOP wizard page sheet | SOP wizard |
wp-chrome.css |
237 | injected chrome: project switcher, global search | launcher, SOP wizard, admin, users, field |
wp-creation-styles.css |
884 | creator page sheet | creator |
wp-sidenav.css |
90 | off-canvas nav drawer | admin, users, field |
No @import exists in any stylesheet. Composition is entirely <link> order.
theme-light.css is linked first on all 7 pages.
The four parallel token systems (S5, consolidated in wave 3)
| Sheet | Prefix | Root tokens |
|---|---|---|
theme-light.css |
--cds-* (2–117) and --wp-appbar-* (169–176) |
114 + 6 |
console.css |
unprefixed (12–20) | 28 |
work-package-suite-styles.css |
unprefixed (1–18) | 16 |
wp-creation-styles.css |
unprefixed (9–31) | 21 |
wp-chrome.css |
--wpc-*, scoped to .wp-chrome, not :root (18–36) |
0 at root |
wp-sidenav.css |
none — every colour is a hardcoded hex | 0 |
The three unprefixed sheets are the collision risk. They are mutually exclusive per page today (console on admin+users, wizard sheet on the wizard, creator sheet on the creator), so nothing currently breaks — wave 3 must not assume that stays true once chrome is unified.
Redeclared across sheets with the same value: --bg, --border, --border-strong,
--text, --accent, --red, --shadow, --text-dim.
Redeclared with different values — these are the real defects:
--shadow-lg—0 4px 16px rgba(0,0,0,0.16)(wizard) vs a different blur in the creator--mono—console.cssdropsui-monospaceandSegoe UI Monofrom the stack--surface—#fff(console) vs#ffffff(creator); same colour, two notations--appbar: #161616(wizard) duplicates--wp-appbar-bg: #161616(theme) under another name
Set at runtime on documentElement, declared in no stylesheet:
--wp-chrome-h (work-package-suite-app.js:528), --rail-top (wp-creation-app.js).
Declared on body rather than :root: --nav-w (wp-creation-styles.css:673,674,805).
Only cross-file dependency: work-package-suite-styles.css consumes --cds-hover-primary,
which only theme-light.css declares.
3. Verified references
Every reference the plan relies on, checked against the working tree.
| Reference | Claim | Status | Actually |
|---|---|---|---|
wp-creation-app.js:1144 |
alert() "Subject and WP Type are required" |
CONFIRMED | exact |
wp-creation-app.js:1962-1972 |
logged-override path for predecessors | MOVED — see D2 | that range is dashIssue(), which refuses. Real path: 967–984 |
work-package-suite-app.js:326 |
only beforeunload, analytics dwell |
CONFIRMED | exact; sole beforeunload in all of html/ |
work-package-suite-styles.css:322-328 |
outline:none + pale 3px glow |
CONFIRMED | block runs 321–328, outline:none on 325 |
work-package-suite-styles.css:336 |
comment about .field-hint unstyled |
CONFIRMED | comment 336–338, rule on 339 |
console.css:85-87 |
contrast fix not propagated | CONFIRMED | comment 85–87, fix on 88 |
admin.js:484-517 |
language and time localization | CONFIRMED | exact — "Localization defaults" block |
server/seed_demo.py |
seeds without authenticating | CONFIRMED | zero auth code in 178 lines |
Notes that change what a later task should do:
work-package-suite-styles.css—outline: noneappears three times, not once: 325 (the cited block), 347 (.user-pick:focus, same pale glow), and 501 (.seq-step input.seq-label:focus, no replacement glow at all).A3/F5should fix all three.console.css:85-87— the comment measures#8d8d8don white at 3.3:1. Wave 4'sT4.6says "about 2.9:1". They disagree;T4.6should re-measure rather than quote either. Both other sheets still bind helper text to the rejected#8d8d8d(work-package-suite-styles.css:10,wp-creation-styles.css:17), and the creator applies it at 10px (wp-creation-styles.css:374), worse than the 12px the comment measures.seed_demo.pyfails loudly but confusingly:call()swallows theHTTPErrorand returns the error body, so line 101'sproj["id"]raisesKeyErrorinstead of reporting a 401. Only/api/healthis unauthenticated, so the health check passes and it dies immediately after.
4. Baseline counts
Recorded so wave 9 can prove they went down. Run from html/ unless stated.
| # | Metric | Review | Actual | Command |
|---|---|---|---|---|
| 1 | native dialogs app-wide | 79 | 79 ✓ | grep -ohE '\b(alert|confirm|prompt)\(' *.js *.html | wc -l |
| 2 | …of those, in the creator | 43 | 43 ✓ | same, over wp-creation-app.js |
| 3 | <div onclick> |
12 | 12 ✓ | grep -oE '<div[^>]*onclick' *.html *.js | wc -l |
| 4 | <span onclick> |
2 | 2 ✓ | grep -oE '<span[^>]*onclick' *.html *.js | wc -l |
| 5 | #0f62fe accent systems |
4 | 4 ✓ | see below |
| 6 | .help-tip badges |
15 | 15 ✓ | grep -oE 'class="help-tip"' *.html | wc -l |
| 7 | aria-live regions |
0 | 0 ✓ | grep -ohE 'aria-live' *.html *.js | wc -l |
| 8 | pushState calls |
0 | 0 ✓ | grep -ohE 'pushState' *.html *.js | wc -l |
Dialogs by file: wp-creation-app.js 43 · work-package-suite-app.js 14 · users.js 10 ·
admin.js 6 · index.html 6.
<div onclick> by file: work-package-suite.html 10 · wp-creation-index.html 1 ·
work-package-suite-app.js 1. Both <span onclick> are in wp-creation-app.js.
Metric 5 needs its definition stated, or wave 9 will measure a different thing.
#0f62fe appears 31 times across html/, and 14 of those are custom-property
declarations. The review's "4" is the number of stylesheets that declare their own accent
token — the four parallel systems:
grep -nE '^\s*--[a-zA-Z0-9-]+\s*:\s*#0f62fe' *.css # 14 declarations, in 4 sheets
| Sheet | Token |
|---|---|
theme-light.css |
--cds-interactive-01 (+ 6 Carbon aliases) |
work-package-suite-styles.css:2 |
--primary |
wp-chrome.css:25 |
--wpc-accent |
wp-creation-styles.css:18 |
--accent |
The wave 9 target is 1. Track the sheet count, not the raw occurrence count.
Metric 6: 15 in page markup (work-package-suite.html 3, wp-creation-index.html 12).
A 16th lives in help.js:261 inside the help centre's own copy, demonstrating the component —
it is not a page badge. None carries tabindex, and help.js:26 styles only :hover/:focus,
so a <span> with no tabindex is keyboard-unreachable. That is the C1 defect, and it
confirms "unreachable" literally.
Metric 7: login.html:99-100 uses role="alert" and role="status", which are implicit
live regions. The count of the literal aria-live attribute is 0, matching the review. CLAUDE.md
points at these two lines as the pattern to copy — they are real and correct.
Metric 8: pushState is 0. There is one history.replaceState at login.js:196,
cleaning the URL after sign-in. It is not routing, so X1's premise holds: no work package has
an addressable URL.
5. Running it
uvicorn server.app:app # against a throwaway SQLite database
python server/smoketest.py # API; signs in
python tests/browser_check.py # pages boot, self-contained
python tests/baseline_shots.py # screenshots, self-contained
tests/browser_check.py and tests/baseline_shots.py are self-contained: each creates a
throwaway SQLite database, seeds a fixture, starts its own uvicorn on a free port, drives
headless Edge or Chrome over CDP, and tears everything down. Your real wpsuite.db is never
touched. Both need Edge or Chrome on the machine; set WP_BROWSER to override discovery.
Screenshots for a before/after pair:
python tests/baseline_shots.py --out docs/reference/baseline # before (committed)
python tests/baseline_shots.py --out /tmp/after --label after # after
python tests/baseline_shots.py --pages creator --widths 390,768,1024,1440
tests/ is referenced by no wave file. T0.2 needs screenshots and T7.3 requires "a
regression test covers the clear-last-constraint path" without naming a home for it — both
belong here.
Discrepancies
Things the plan asserts that the repo contradicts. Listed per T0.1's fourth done-when.
D1 — "6 pages, 4 stylesheets" is wrong; it is 7 and 6
wave-0.md:21 says 6 pages and 4 stylesheets. There are 7 pages and 6 stylesheets. T0.1's
own parenthetical at wave-0.md:24-25 lists seven names, so the document contradicts itself
in the same task. The missing stylesheets are wp-chrome.css and wp-sidenav.css.
Consequence: every "all 6 pages" done-when in waves 2, 3, 5 and 9 is off by one, and T0.2
asks for "12 baseline screenshots (6 pages x 2 widths)" when the correct number is 14.
14 were captured. Treat "6 pages" as "all pages" wherever it appears.
D2 — A1's protected line reference points at the wrong function
This is the important one. CLAUDE.md, under Things that must not change, says:
The logged-override path for predecessors stays (A1). It is an audited business rule, not a bug. See
wp-creation-app.js:1962-1972.
IMPLEMENTATION.md's X2 cites the same range. Lines 1962–1972 are dashIssue(), which is
the opposite code: the dashboard guard that refuses to issue and tells you to
"Open the package to release it early with a logged reason". The reviewer read that sentence and
correctly concluded a logged override exists — but cited the mention, not the implementation.
The actual audited path is:
| Part | Location |
|---|---|
confirmEarlyRelease() — the override itself |
wp-creation-app.js:967-984 |
state pkgGateOverride |
:392, reset at :481, :488, :1744 |
| call site — status change | :998-1002 |
| call site — save | :1149-1150 |
| persisted onto the package | :1117 (gateOverride:) |
| rendered in the printed package | :1215 |
| rehydrated when loading a package | :1674 |
| the guard the plan actually cited | :1960-1974 (dashIssue) |
The comment at :967-969 states the rule plainly: "Releasing with an unclosed predecessor is
allowed but must be explained. The reason rides on the package (data.gateOverride) and the
server writes it to the audit log."
Consequence for T7.3: the "do not remove" instruction must be applied to 967-984 and
its seven satellites, not to 1962-1972. A task that preserved only 1962–1972 would delete the
audited business rule while believing it had protected it. dashIssue() must also survive — it
is what stops the dashboard becoming a quiet way around the gate — but it is a second thing to
protect, not the same thing.
X2's reasoning is unaffected: hold state genuinely is not purely derived from open
constraints, so CR-015 and A1 remain the same code and the same task.
D3 — four documents the plan reads from do not exist yet
Not errors; they are deliverables not yet produced. Recorded so no task treats one as a missing input and goes looking for a rename.
| Path | Created by | Also read by |
|---|---|---|
docs/reference/file-map.md |
T0.1 |
T0.2, T1.1, T2.1, T2.3 |
docs/reference/tokens.md |
T3.1 |
T3.2, T3.5, T9.3 |
docs/reference/accessibility-audit.md |
T9.5 |
— |
docs/reference/completion.md |
T9.7 |
— |
backlog.md:16 contains path/to/file.js:120 inside a fenced format template. It is a
placeholder, not a reference — do not resolve it.
D4 — the creator overflows horizontally at 1440px, not just at 390px
Measured during T0.2, not in the review. Content width against the viewport it was given:
| Page | 390px | 1440px |
|---|---|---|
| launcher | 425 | ok |
| SOP wizard | 429 | ok |
| creator | 485 | 1551 |
| field view | 432 | ok |
| login, admin, users | ok | ok |
Four pages overflow at 390px, which is F2 and its neighbours. The creator also overflows by
111px at desk width, which no F item covers. Logged to docs/waves/backlog.md; not fixed
here, since wave 1 is scoped to F1–F5 and T7.x rebuilds this page anyway.