Four cells on the launcher - Work packages, Release ready, On hold, Overdue -
every number from /api/wps/metrics, which T4.1 built. There is deliberately no
cache fallback anywhere in renderPipeline(): a remembered number sitting beside
three live ones is the failure B4 exists to remove, wearing a different hat.
The cells are the four the endpoint already computes and the dashboard already
filters on, so they map 1:1 onto its flags rather than inventing a fifth slice
nothing downstream understands. wp-creation-app.js now names them (DASH_FLAGS)
for the same reason: a cell linking to a filter the board does not recognise is
a dead link that still looks live.
"Links to a filtered view via a shareable URL" needed the filter to BE URL state,
which it was not - the dashboard kept its flag in a variable. So:
- dashToggleFlag pushes ?flag=<f>, and clears it on the way out of the board
- the creator applies ?flag= before its first render, not after (applying it
after paints the whole board and throws it away)
- Back and Forward move through filters like any other state
- work-package-suite-app.js forwards the flag ACROSS the iframe boundary, since
the creator's src carries only the project. B7/T7.1 dissolves that frame and
this hand-off goes with it; it is commented as such.
Zero is a real answer for one slice of a project that has work in it. Four zeros
on a project with none is not a reading, it is a strip that looks broken - that
case gets a sentence and a way into the creator instead. A failed request gets an
explicit error naming the failure, and no cells at all.
html/index.html the strip, its states, PIPE_CELLS
html/wp-creation-app.js flag as URL state; DASH_FLAGS; dashApplyFlag
html/work-package-suite-app.js forward the flag into the frame; clear on exit
tests/pipeline_check.py new - 43 checks
Done when
[x] every number comes from a server endpoint - proved by poisoning localStorage
with 99 fake packages and demanding the strip still read the server's 4
[x] each cell links to a filtered view via a shareable URL - and the probe
FOLLOWS the link and reads the filter inside the frame rather than trusting
that a correct-looking URL was built
[x] a project with zero work packages renders a sensible empty state
[x] the strip announces updates via aria-live (polite - a count is not an
interruption) and reports aria-busy while it is counting
What the probe caught
The link landed on "Complete the SOP Configuration first". Not the strip's
fault: browser_check.py's fixture stores a bare {governance: …} blob as the SOP
data, where production stores {sop, state}. restoreSavedSOP() needs `state` and
bails without it, so sopComplete stays false and the WP tab shows its gate.
pipeline_check seeds the production shape. The underlying wart is real and is
logged rather than fixed - see BL-018.
Verified one at a time
pipeline_check 43/43 new
launcher_check 58/58
stepper_check 70/70
url_state 23/23 the dashboard's new flag state did not disturb it
aggregates 16/16
browser_check 71/71
a11y 22/22
autosave 34/34
f_items F1-F5 FIXED, F6 REPRODUCES (T7.2)
No colour literal added: still 0 across all page sheets and inline blocks. The
four cells are told apart by a label, a sentence and an accent - three channels,
so colour is not carrying it alone (C1).
Raised, not fixed
BL-018 The WP tab's gate is the last localStorage-derived status in the app.
T4.1 moved the launcher's card to the server; the wizard page still
decides gate-or-creator from wp_suite_sop_complete plus a state blob.
pullProject refreshes both on load so a connected user is fine, but the
two answers come from different places and the fallback is silent.
Includes a second, sharper edge: project-data.js:210 writes that flag
for ANY row returned, including one with no `state` to restore - so the
flag is written and never read consistently. T7.1 owns it.
Question for the PR, per CLAUDE.md: the strip counts Overdue against `data.due`,
which is free text today. CR-004/CR-018 restructure location but not dates. If
"overdue" is going to drive anything beyond a launcher tile, that field needs a
type.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
449 lines
25 KiB
Markdown
449 lines
25 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>`.
|
||
|
||
### The iframe boundary (matters for `B7` / `T7.1`)
|
||
|
||
`work-package-suite.html:383` declares `<iframe id="wp-frame">` with **no `src`**.
|
||
`work-package-suite-app.js:581` sets it at runtime to
|
||
`wp-creation-index.html?embedded=1&project=<id>`.
|
||
|
||
The creator is the only page that loads **neither `wp-chrome.css` nor `wp-chrome.js`** — that is
|
||
why it has no app bar of its own and why it looks like part of the wizard. `T7.1` has to give it
|
||
that chrome back, or deliberately not.
|
||
|
||
Three scripts branch on `window.top !== window.self` and will change behaviour when the frame
|
||
goes away:
|
||
|
||
| Script | Line | Framed behaviour |
|
||
|---|---|---|
|
||
| `auth-guard.js` | 15 | redirects `window.top` to `login.html` |
|
||
| `wp-chrome.js` | 17 | returns early, renders no chrome |
|
||
| `help.js` | — | suppresses the Help FAB in the child |
|
||
|
||
### Pages that are not what the plan implies
|
||
|
||
- **`field.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` |
|
||
|
||
Dialogs by file: `wp-creation-app.js` 43 · `work-package-suite-app.js` 14 · `users.js` 10 ·
|
||
`admin.js` 6 · `index.html` 6.
|
||
`<div onclick>` by file: `work-package-suite.html` 10 · `wp-creation-index.html` 1 ·
|
||
`work-package-suite-app.js` 1. Both `<span onclick>` are in `wp-creation-app.js`.
|
||
|
||
**Metric 5 needs its definition stated**, or wave 9 will measure a different thing.
|
||
`#0f62fe` appears **31** times across `html/`, and **14** of those are custom-property
|
||
declarations. The review's "4" is the number of **stylesheets that declare their own accent
|
||
token** — the four parallel systems:
|
||
|
||
```
|
||
grep -nE '^\s*--[a-zA-Z0-9-]+\s*:\s*#0f62fe' *.css # 14 declarations, in 4 sheets
|
||
```
|
||
|
||
| Sheet | Token |
|
||
|---|---|
|
||
| `theme-light.css` | `--cds-interactive-01` (+ 6 Carbon aliases) |
|
||
| `work-package-suite-styles.css:2` | `--primary` |
|
||
| `wp-chrome.css:25` | `--wpc-accent` |
|
||
| `wp-creation-styles.css:18` | `--accent` |
|
||
|
||
**The wave 9 target is 1.** Track the sheet count, not the raw occurrence count.
|
||
|
||
**Metric 6:** 15 in page markup (`work-package-suite.html` 3, `wp-creation-index.html` 12).
|
||
A 16th lives in `help.js:261` inside the help centre's own copy, demonstrating the component —
|
||
it is not a page badge. None carries `tabindex`, and `help.js:26` styles only `:hover`/`:focus`,
|
||
so a `<span>` with no tabindex is **keyboard-unreachable**. That is the `C1` defect, and it
|
||
confirms "unreachable" literally.
|
||
|
||
**Metric 7:** `login.html:99-100` uses `role="alert"` and `role="status"`, which are implicit
|
||
live regions. The count of the literal `aria-live` attribute is 0, matching the review. CLAUDE.md
|
||
points at these two lines as the pattern to copy — they are real and correct.
|
||
|
||
**Metric 8:** `pushState` is 0. There is **one** `history.replaceState` at `login.js:196`,
|
||
cleaning the URL after sign-in. It is not routing, so `X1`'s premise holds: no work package has
|
||
an addressable URL.
|
||
|
||
---
|
||
|
||
## 5. Running it
|
||
|
||
```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
|
||
```
|
||
|
||
`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 | **77** | `T5.1` (2), `T5.2` (1), wave 4 (+1) |
|
||
|
||
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 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.
|