Files
Project-SDE-WP-Suite/docs/reference/file-map.md
n.siegfried 79c4a36c70 T8.1 - CR-009: kitting statuses are a set, and Micron EUV is not kitting
The statuses: the proposed five, adopted as proposed - Not Started, Picking,
Staged, In Transit, Delivered - as one named constant (KIT_STATUSES) building
the select. They describe fulfillment; the old four ('Open', 'In Progress',
'Kitted', 'Delivered') mixed fulfillment with workflow. A value stored before
the set existed is kept, selected, and shown as "(legacy)" - CR-016's rule
that renamed vocabularies must not orphan recorded data - and round-trips
through collect unchanged.

Micron EUV: the sample SOP is the Micron configuration on record (CR-016 /
T5.7), and it now names kitting:false beside assets:false - off by CR-006
TOGGLE, in both the creator sample and the wizard sample. The section leaves
the form, the rail and the export; its data and model stay exactly where they
are (the probe loads the example package under the Micron sample and finds
its kitting values intact through collect). Any other SOP turns the section
on and it works fully - driven against sopA with everything enabled.

sections_check re-pointed, not relaxed: its sample-map pin said "naming only
assets"; it now says "naming exactly assets (CR-016) and kitting (CR-009)" -
still refusing any section that goes off without a recorded item behind it.

Verification (each probe run alone): NEW tests/kitting_check.py 14/14.
Regression: sections_check 95/95.

Items: CR-009

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

551 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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](#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-lg``0 4px 16px rgba(0,0,0,0.16)` (wizard) vs a different blur in the creator
- `--mono``console.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.css`** — `outline: 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
```bash
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:
```bash
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:
```bash
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:
```bash
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:
```bash
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:
```bash
python tests/kitting_check.py # CR-009 - status set + off for Micron EUV 14 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:
```bash
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 `F1``F5` and `T7.x` rebuilds this page anyway.