A named person owns fulfillment of the kit - today that is Paul Coonrod, informally, and everything bottlenecks through him. The package now records it explicitly: - The Warehouse owner control is a dropdown of project members (the same roster the Owner picker reads). Picking someone stores BOTH the display name (kitOwner - exports and old renderers keep working) and the account id (kitOwnerId - the routing CR-011's notifications will read at T8.3). - Confirmed Aug 18: the field lives ON the work package, not the project - a package retargeted to a different warehouse notifies the right person without touching the project. The wizard gets no field. - A stored name with no matching account - typed before the field was account-backed, or someone since removed from the project - is KEPT as a selected "(no account)" option and round-trips unchanged. Removing someone from the project breaks nothing. - The dashboard filters by warehouse owner, options drawn from the owners actually present in the data - a filter offering people with nothing to fulfill is noise. Verification (each probe run alone): kitting_check.py extended to 21/21 (T8.2 section: picker, id+name persistence, orphan survival, board filter). Regression: generalinfo_check 49/49. Items: CR-010 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
551 lines
32 KiB
Markdown
551 lines
32 KiB
Markdown
# 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 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>`.
|
||
|
||
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 2–10 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 16–312) — 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-*` (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.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: **`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: 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/CR-010 - statuses, owner, filter 21 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 1–3 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 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.
|
||
|
||
### 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.
|