Files
Project-SDE-WP-Suite/docs/reference/file-map.md
n.siegfried b9d5f8ef92 T8.4 - CR-012: the delivery location is the shared vocabulary plus fifty feet
Staging is not the pain; the last fifty feet are - the correct floor lay-down,
shark cage or conduit tree instead of material picked at will by whoever is
closest. The Kitting & MIMO section gains:

- Delivery Building / Floor / Sector: the SAME dependent pickers CR-004 built,
  through the same fillLocSelect (which learned an optional field-map instead
  of being copied), reading the same project location lists, storing PATHS.
  A parallel free-text location vocabulary is exactly what CR-004 removed;
  none was added.
- A free-text detail field for the specifics ("Shark cage 7, conduit tree C"),
  persisted as delivDetail.
- deliveryLoc, the composed display string (labels off the shared lists, then
  the detail after a dash) - which is what the CR-011 email already reads
  (kitting_body preferred deliveryLoc from day one, with mimoLoc as the
  pre-CR-012 fallback) and what the package printout now carries as its own
  Delivery Location row.

Verification (each probe run alone): kitting_check.py extended to 26/26 (the
delivery selects are asserted to offer the SAME option list as the CR-004
trio, values persist as paths, the printout carries the composed value);
kitting_notify_check 17/17 now asserting the mail carries CR-012's composed
value, not the fallback. Regression: locations_check 58/58.

Items: CR-012

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 12:11:05 -07:00

32 KiB
Raw Blame History

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 418700), 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>.

Wave 5 added two <head> scripts, both shared: wp-sections.js (the CR-006 section list, read by the wizard and the creator, so the two cannot disagree about what "Assets is off" means) joins wp-url.js and wp-autosave.js.

The SOP wizard has 12 steps, not 10, since wave 5. T5.4 appended Locations (11) and T5.5 appended Sections (12). Both were appended rather than inserted where they belong by subject: renumbering 210 would touch every sop-step-N id, every collectStepData case, every gate key and the analytics history, for an ordering change. The count lives in one place — LAST_STEP in work-package-suite-app.js — and reordering is cheap once nothing depends on the numbers. tests/stepper_check.py names it STEP_COUNT for the same reason.

The iframe boundary - dissolved at T7.1, August 18 2026

work-package-suite.html declared <iframe id="wp-frame"> with no src, and work-package-suite-app.js set it at runtime to wp-creation-index.html?embedded=1&project=<id>. The creator was the only page loading neither wp-chrome.css nor wp-chrome.js, which is why it had no app bar and read as part of the wizard rather than as a page.

There is no iframe in html/ any more. The creator is a top-level document with the same app bar and the same tab strip as the wizard; the Work Package Creation and Dashboard tabs are links between the two. docs/reference/creator-frame.md section 5 records what was done and why that route was taken over merging the two documents.

What a task touching this area needs to know now:

Was Is
#wp-frame, applyEmbedLayout, sizeWPFrame, --wp-chrome-h, .embed-full gone; the window sizes the page
?embedded=1, body.embedded, .embed-hide gone; an old link carrying the param is ignored, not half-obeyed
four cross-frame calls (openWpById, showDashboard/showForm, dashApplyFlag, applySopSections) the URL: ?project=, ?view=, ?wp=, ?flag=, all read at the creator's own boot
.main-nav / .nav-tab in work-package-suite-styles.css wp-chrome.css, because both tool pages draw the strip
inIframe branches in auth-guard.js, wp-chrome.js, wp-sidenav.js, help.js, and _isTop in project-data.js gone; help.js uses the explicit WP_HELP_NO_FAB flag both tool pages set

Old addresses still work and must keep working. work-package-suite.html?tab=wp, ?view=dashboard and ?wp=<id> are in bookmarks, in wp-sidenav.js's link map, and they are the shape CR-011 and CR-014 emails were specified against (X1). The wizard forwards them to the creator with replace(), so Back does not bounce. tests/frame_check.py section 4 pins all three.

Pages that are not what the plan implies

  • field.html is 93 lines. It is a shell; field.js (178 lines) builds it. A task that says "edit the field view page" almost always means field.js.
  • index.html carries ~296 lines of inline <style> (lines 16312) — the bulk of the launcher's CSS is not in any stylesheet. T3.x token work has to reach inside it.
  • admin.html and users.html are near-identical shells over console.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-* (2117) and --wp-appbar-* (169176) 114 + 6
console.css unprefixed (1220) 28
work-package-suite-styles.css unprefixed (118) 16
wp-creation-styles.css unprefixed (931) 21
wp-chrome.css --wpc-*, scoped to .wp-chrome, not :root (1836) 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-lg0 4px 16px rgba(0,0,0,0.16) (wizard) vs a different blur in the creator
  • --monoconsole.css drops ui-monospace and Segoe UI Mono from 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: 967984
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 321328, outline:none on 325
work-package-suite-styles.css:336 comment about .field-hint unstyled CONFIRMED comment 336338, rule on 339
console.css:85-87 contrast fix not propagated CONFIRMED comment 8587, 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.cssoutline: none appears 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/F5 should fix all three.
  • console.css:85-87 — the comment measures #8d8d8d on white at 3.3:1. Wave 4's T4.6 says "about 2.9:1". They disagree; T4.6 should 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.py fails loudly but confusingly: call() swallows the HTTPError and returns the error body, so line 101's proj["id"] raises KeyError instead of reporting a 401. Only /api/health is 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
9 <iframe> in html/ 1 0 ✓ (T7.1) grep -oiE '<iframe' *.html | 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.

At the T7.1 commit the same commands read: dialogs 64 (the wizard's 14 are gone, the creator's 43 are T7.9's), <div onclick> 2, <span onclick> 2, .help-tip 18, colour literals in rules outside theme-light.css 0 in all five page sheets, and iframes 0 - metric 9 above, added because B7 is the only item in the plan whose completion is a count of zero.

Measure colour literals with comments stripped. Four of the five sheets now carry prose about the hex values they used to contain, and BL-017 is the entry about a metric that counted its own explanation. <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; needs WP_SMOKE_USER / WP_SMOKE_PASSWORD
python tests/browser_check.py               # pages boot and render, self-contained
python tests/baseline_shots.py              # screenshots, self-contained
python tests/f_items.py                     # does each of F1-F6 still reproduce?
python tests/token_check.py --out a.json    # every resolved token + computed style
python tests/token_check.py --compare a.json b.json

tests/token_check.py was added by T3.2, because a screenshot cannot prove a token refactor: three of the fourteen shots are not stable capture-to-capture (see backlog.md BL-012), and no screenshot exercises a hover, focus or disabled rule, which is where half the tokens live. It snapshots every custom property's resolved value and every element's computed colours, shadows and type, on all 7 pages, and diffs two snapshots. Use it for any task that claims to change styling without changing appearance.

Wave 4 added three more, each written because its task's done-when could not be checked by anything that already existed:

python tests/aggregates_check.py    # B4  — do the counts come from the server?   16 checks
python tests/url_state_check.py     # S3  — does the app's state have an address? 23 checks
python tests/autosave_check.py      # S2/B5 — does unsaved work survive?          34 checks
python tests/a11y_check.py          # S10/S11/S12 — announce, legible, focus      22 checks

Wave 5 added more, for the same reason:

python tests/stepper_check.py       # A4/S9 — ten real buttons, keyboard operable  70 checks
python tests/launcher_check.py      # B3  — can a brand-new account get started?   58 checks
python tests/pipeline_check.py      # B4 surface — server counts, shareable links  43 checks
python tests/locations_check.py     # CR-005 — codes not labels, nothing deleted   58 checks
python tests/sections_check.py      # CR-006/CR-002/CR-016 — hidden, not deleted  95 checks
python tests/validation_check.py    # S1 wizard — errors at the field, no dialogs 81 checks

Wave 6 added three more:

python tests/generalinfo_check.py   # CR-001/CR-003 — P6 activity, priority     49 checks
python tests/rollup_check.py        # CR-004/CR-018 — codes, and totals that add 63 checks
python tests/cards_check.py         # A7 — card status, footer, localization    44 checks

Wave 7 adds these:

python tests/frame_check.py          # B7/T7.1/D1 - is the iframe actually gone?  39 checks
python tests/form_structure_check.py # F6/D3 - rail, disclosure, one open section 51 checks
python tests/hold_check.py           # CR-015/A1/D4 - the hold clears, gates hold 50 checks
python tests/warning_check.py        # A2 - one warning, a badge from anywhere    17 checks
python tests/triage_check.py         # A6 - the sidebar answers the stand-up      16 checks
python tests/qa_gate_check.py        # CR-014/D2/D9/D10 - QA gate + capture sink  40 checks
python tests/files_check.py          # CR-007/D8 - drawings upload + real offline 36 checks
python tests/sticky_bar_check.py     # B6 - save reachable on every wizard step   12 checks
python tests/usage_check.py          # D5 - one analytics core, admin report      15 checks
python tests/creator_dialogs_check.py # S1 creator - 0 natives, errors at fields  20 checks

Wave 8 adds these:

python tests/kitting_check.py        # CR-009/010/012 - statuses, owner, delivery 26 checks
python tests/kitting_notify_check.py # CR-011 - kitting mail, coalesced, gated    17 checks

Three probes were re-pointed at T7.1. sections_check.py 5b drove the live cross-frame toggle hand-off, which no longer exists; it now pins the surviving path and the fact that an UNSAVED toggle correctly does not travel - a behaviour change, recorded rather than smoothed over. pipeline_check.py read the filter through #wp-frame.contentDocument; it reads the page. f_items.py's F4 drove two modes, standalone and ?embedded=1; there is one mode now. validation_check.py lost its "loading the sample on the wrong tab" case for the same reason - there is no wrong tab on the wizard any more - and gained one for the SOP gate instead.

Two probes were re-pointed at T7.2, both for the General Information split. sections_check.py's section table carried one selector per section; the general row now carries two (#general-card, #assign-card) and every hidden/shown reading walks ALL of a row's cards, because the failure mode the split could have had is exactly "half the section hidden and half still rendering". Its chip-strip reading moved to the rail (#section-nav no longer exists). generalinfo_check.py asserted DOM containment in #general-card; it now asserts what that containment was FOR - the CR-006 general toggle governs the P6 fields - against whichever of the two cards holds them.

Two probes were re-pointed at T6.5, and the reason is worth carrying forward. aggregates_check.py matched the launcher card's exact wording ("SOP complete"), and it also waited for the status line to be non-empty — which stopped meaning "the answer has arrived" once the card gained a line for the in-flight state too. Both assertions now match the ANSWER rather than the sentence, which is what that check was ever about. A probe that breaks on wording is a probe somebody edits carelessly the next time wording changes.

Windows consoles are cp1252. Several probes print page text back in a failure message, and a glyph outside cp1252 (, , ) crashes the reporter instead of reporting the failure — losing the diagnosis, which is the only thing that run was for. The wave 5 and 6 probes route any page-derived text through an ascii_() helper.

pipeline_check.py reads the dashboard's state out of the iframe's DOM, not its globals: dashFilter and currentView are declared with let in a classic script, so they are not properties of window and a cross-frame read of either comes back undefined — which is indistinguishable from a filter that never applied.

launcher_check.py is the only probe that runs itself in two subprocesses, and both reasons are worth knowing before writing a third:

  • The two states it tests are database states — an account with no projects, and an account with two — and faking "no projects" in the browser would test the fake.
  • server/db.py builds its engine at import time from DATABASE_URL, so a second seed() in one interpreter still points at the first phase's database, which has been deleted by then. That surfaces as unable to open database file, which reads like a broken environment rather than what it is.

It drives the rail with real key events over Input.dispatchKeyEvent rather than page.key(), which dispatches a synthetic KeyboardEvent on document. That event never reaches a listener bound to the rail and never triggers a button's native Enter/Space activation, so a rail with no keyboard support at all would have reported a clean pass — the same class of false green as a11y_check.py's focus emulation.

Each tests what was broken rather than what is easy to assert. aggregates_check.py poisons localStorage and demands the dashboard still report the server's total; a test that only checked the totals were correct would have passed before B4 was built. a11y_check.py runs with CDP focus emulation on, without which :focus-visible never matches in headless and every element reports a clean pass.

Run them one at a time. Chained back to back they exhaust the headless browser's ports and all three abort with "browser would not start"; that failure looks like a code fault and is not one.

Baseline counts, updated

# Metric Wave 0 Now Changed by
5 sheets declaring their own #0f62fe 4 (really 5, see §4) 1 T3.2
7 aria-live / role="alert"|"status" sites 0 13 T4.4, T4.5
8 pushState 0 2 (6 call sites via wp-url.js) T4.2
outline: none in stylesheets 6 1, with its replacement one rule above T3.4, T4.7
helper-text contrast, worst case 3.01:1 4.56:1 T4.6
3 <div onclick> 12 2 T5.1
1 native dialogs app-wide 79 64 T5.1 (2), T5.2 (1), T5.8 (13)
2 …of those, in the creator 43 43 unchanged — T7.9
9 <iframe> in html/ 1 0 T7.1
6 .help-tip badges, none keyboard-reachable 15 18 went UP; T9.5

Metrics 2, 4 and 6 (creator dialogs, <span onclick>, .help-tip badges) are wave 9's to move and are unchanged.

Metric 3 after T5.1: the two survivors are wp-creation-index.html:383 (.cmt-overlay) and work-package-suite-app.js:1046 (the constraint-library row). Both are wave 7 / wave 9 work. Nothing in the SOP wizard's markup carries a click handler on a non-interactive element any more.

Metric 1 after T5.8: the SOP wizard has none, measured both raw and with comments stripped. What is left is wp-creation-app.js 43, users.js 10, admin.js 6 and index.html 5 — the creator's are wave 7's, the rest wave 9's.

Metric 1 is noisier than it looks — the command matches alert( inside a comment as readily as inside code, and waves 3 and 4 left several comments explaining dialogs they were removing. That is why the figure was 80 before T5.1 rather than the 79 wave 0 recorded. T5.1 removed two real calls (validateStep's three conditions collapsed to one) and reworded its own comments so they do not inflate it. Logged as BL-017; T5.8 records a comment-stripped figure alongside the raw one.

server/smoketest.py is the one that is not self-contained: it drives a server you point it at and aborts unless WP_SMOKE_USER and WP_SMOKE_PASSWORD are set, because every route but /api/health needs a session. Use an admin account — it creates and deletes a project.

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.

tests/f_items.py is both halves of the same measurement: it recorded that all six defects reproduce before wave 1, and it is how waves 13 prove each one stopped. An item is done when its probe flips from REPRODUCES to FIXED. It never reports a silent pass — a probe that cannot decide says INCONCLUSIVE.


The project switcher: what it shows at each width (B2, T2.3)

Written here because T2.3 says to, so it is not re-litigated. Test name is always "Micron EUV Cleanroom Enable 2667008" — the real one, and the one that breaks things.

Width The app bar shows Why
≥ 1024px the full project name There is room. The cap is raised to 400px (button) / 340px (name) so a real name fits without an ellipsis.
< 1024px the project number alone, e.g. 2667008 Short, stable, unambiguous. Dropping the name entirely beats shortening it to "Micron EUV Clean…" on the one control whose job is to tell you which job you are in.

The number is not prefixed to the name at wide widths. Real project names already end with their number, so 2667008 — Micron EUV Cleanroom Enable 2667008 printed it twice.

The full name is reachable at every width by three routes, none of them hover-only (Field View is a touch surface — C1):

  1. The drawer.wp-sidenav-proj, under the drawer head, on every page. Wraps onto as many lines as it needs and is never truncated. This is the guaranteed one.
  2. The switcher's title — the full number — name, so it surfaces on hover and on keyboard focus.
  3. The switcher popover — every project is listed with its full name and number.

The ellipsis is kept only as a backstop for a name longer than anything real. Verified at 390, 768, 1024 and 1440: no truncation at any of them, and the drawer carries the whole name at all four.

T1.1 correctness is preserved — with a project active the switcher never reads "Select a project"; with none active it reads exactly that at every width.

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 19621972 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 19621972 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.

D5 — T2.1's premise is already satisfied: there are no "three near-copies"

wave-2.md:25 asks to "lift the drawer into one shared implementation the pages include, rather than three near-copies". There are no copies. html/wp-sidenav.js (221 lines) and html/wp-sidenav.css (90 lines) are already one implementation, included by admin, field view and directory. Verified: .wp-sidenav, .wp-navscrim and .wp-navbtn are declared in exactly one file, and no page defines its own drawer.

The review said the drawer "ships on 3 of 6 pages while a flat strip duplicates it". The duplication is between the drawer and the flat strip, not among three drawers. The plan appears to have read "3 of 6 pages" as "3 copies".

It already meets every T2.1 done-when as it stands, including the last one: activeProjectId() (wp-sidenav.js:62-68) reads ?project= then falls back to ProjectData.getActiveId(), which is the single source T1.1 established. It also self-mounts into .wp-appbar or .header and returns early inside an iframe, so it is already built to go on the remaining pages.

Consequence: T2.1 is a no-op beyond this verification. The real work of wave 2 is T2.2. Nothing was refactored, because refactoring a single shared component into a single shared component would be churn with regression risk and no gain.

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 F1F5 and T7.x rebuilds this page anyway.