Import the R2 implementation spec into the repo

The plan was delivered as wp-suite-implementation-spec.zip and lived only in
Downloads, so every "read CLAUDE.md first" instruction in it pointed at a file
the repo did not have. Bring it in unchanged: CLAUDE.md, IMPLEMENTATION.md, and
docs/waves/wave-0 through wave-9 plus backlog.md.

UX-REVIEW-2026-08-14.md is committed alongside it. It is the review that
produced F1-F6, S1-S13 and the A/B/C assessments, and item IDs throughout the
wave files cite it, so it belongs under version control rather than sitting
untracked in the working tree.

No application code changes here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-14 17:42:56 -05:00
parent 9459e76a6c
commit 3d99d4b9d0
14 changed files with 2254 additions and 0 deletions

102
CLAUDE.md Normal file
View File

@@ -0,0 +1,102 @@
# Working rules — Work Package Suite
This repo is being changed against a fixed spec. Read `IMPLEMENTATION.md` before starting
any task, and read the wave file for the task you are on. Do not work from this file alone.
## The spec is the source of truth
Every change traces to an item ID (`CR-001`, `F1`, `S1`, `A1`, `B1`, `C1`). If you are about
to make a change that has no ID, stop. Either it belongs to an existing item and you should
say which, or it is out of scope and should be logged in `docs/waves/backlog.md` instead of
built.
Do not renumber, merge, or reinterpret item IDs. They are referenced in documents outside
this repo that other people are reading.
## Scope discipline
- **One task per PR.** Task IDs are `T<wave>.<n>`. Reference the task ID and the item IDs in
the commit message and PR title.
- **Do not fix things you notice in passing.** The codebase has known problems documented as
S1 through S13, all scheduled. Fixing S6 while doing T3.2 makes the diff unreviewable and
breaks the wave ordering. Log it, move on.
- **Do not reorder waves.** The ordering is dependency-driven and documented in
`IMPLEMENTATION.md` section 4. Waves 1 through 4 are prerequisites: they produce almost no
visible change and every later wave assumes them.
- **Do not start a wave until the previous wave is merged**, unless the task explicitly says
it is independent.
## Frontend and backend boundary
The UX review that produced F1-F6 and S1-S13 covered `html/` only. Several change requests
need server work and will be silently half-built if you treat them as frontend-only:
| Item | Needs server work |
|---|---|
| CR-004, CR-018 | Structured location storage and aggregate endpoints. Not localStorage. |
| CR-007 | File upload, storage, and retrieval for drawing attachments. |
| CR-011, CR-014 | Outbound email and a durable link target per work package. |
| CR-013 | Material request persistence. |
| B4 | Aggregate endpoints replacing localStorage-derived counts. |
If a task touches one of these and you find yourself writing to `localStorage`, you are
building the wrong thing. Say so and stop.
## Things that must not change
These are recorded decisions, not oversights. Do not "clean them up":
- **Actual Hours stays in Closeout (CR-017).** Its removal was proposed and rejected.
- **Localization stays (A7).** `admin.js` language and time handling is a shipped feature.
- **Uppercase card headers in `console.css` stay (A5).** The sentence-case rule applies to
buttons and field labels only. The uppercase header idiom is deliberate.
- **The logged-override path for predecessors stays (A1).** It is an audited business rule,
not a bug. See `wp-creation-app.js:1962-1972`.
- **Removed fields are hidden, not deleted (CR-002, CR-016).** Retain the data and the model.
Removal is expressed through the CR-006 section toggles.
## The token rule
After wave 3 there is exactly one place a color, spacing or type value is defined. Page
stylesheets alias that source and declare nothing new.
Adding a raw hex value to a page stylesheet is a defect regardless of what the task asked
for. Four parallel token systems is what produced S5, and the `.field-hint` comment at
`work-package-suite-styles.css:336` is the bug that resulted. Do not recreate it.
## Accessibility is in scope
Approved Aug 14, 2026 (C1). Any component you rebuild ships accessible or it is not done:
- Interactive elements are `<button>`, `<a>`, or an input. Never a `<div>` with `onclick`.
There are currently 12 `<div>` and 2 `<span>` click handlers app-wide; do not add a 15th.
- Anything conveying instructions is reachable by keyboard and by touch. Hover-only is not
acceptable — Field View runs on tablets.
- Status changes and toasts announce through an `aria-live` region. `login.html` already does
this correctly with `role="alert"` and `role="status"`. Copy that pattern.
- Focus is always visible. Do not use `outline: none` without a replacement of at least equal
visibility.
- Text meets 4.5:1 against its background, 3:1 for large text.
## Verification
A task is not done because the code is written. Every task file lists its own done-when
checks. In addition, for any task touching the frontend:
1. Run the app locally: `uvicorn server.app:app` against a throwaway SQLite database.
2. Exercise the affected flow at **390px** and at **1440px**. Field View at 390px is the
gloved-hands surface and is where the worst rendering was found.
3. Capture before and after screenshots into the PR.
4. Run the existing smoke test. It signs in; `server/seed_demo.py` does not, which is S13.
If a done-when check cannot be verified, do not mark the task complete. Say which check
failed and why.
## Asking versus assuming
The four gating decisions are closed and recorded in `IMPLEMENTATION.md` section 2. Nothing
else in the spec is a decision waiting to be made.
Where a task says "confirm with Nick", that is a product question, not an implementation
blocker: build to the written acceptance criteria, and raise the question in the PR
description. Do not invent a different behavior because the written one seems incomplete.

163
IMPLEMENTATION.md Normal file
View File

@@ -0,0 +1,163 @@
# Work Package Suite — Implementation Plan
Derived from **Consolidated Change Request R2**, August 14, 2026.
Companion document for humans: `WorkPackageSuite_Consolidated_Change_Request_R2.docx`.
55 items, 9 waves, dependency-ordered. Read `CLAUDE.md` first.
---
## 1. Where this came from
Three inputs were merged to produce this plan:
| Source | Produced | IDs |
|---|---|---|
| 13 in-app feedback comments, Aug 14 2026 (01:1101:32 PM) | Functional change requests | `CR-001``CR-018` |
| Micron EUV review meeting, Aug 14 2026 | Functional change requests and context | included above |
| UX review of `html/`, Aug 14 2026, branch `users/directory-super-user` | Rendering defects, structural findings, proposal assessment | `F1``F6`, `S1``S13`, `A1``A7`, `B1``B7`, `C1``C4` |
`F1``F6` were verified in a browser and are reproducible. `S1``S13` were read from source
and carry file and line references. `A`/`B`/`C` items assess a separately proposed UX change
list; their IDs are carried over unchanged so the two documents line up.
## 2. Decisions (all closed)
Approved by Nick Siegfried, August 14 2026. Nothing in this plan is blocked pending a
decision. Do not reopen these without a spec revision.
| ID | Decision |
|---|---|
| `CR-009` | **Keep and extend Kitting.** Build `CR-010`, `CR-011`, `CR-012`. Ship it toggled **off** for the Micron EUV SOP via the `CR-006` section toggles. Kitting is not happening at Micron today; the team expects to get there. |
| `CR-013` | **Build the lightweight material request.** Line items (description, quantity, unit), needed-by date, requestor, delivery location, status. **Defer** the parts catalog, live inventory, and warehouse integration. Sequence after the creator has real section structure (see `X6`). |
| `B7` | **Dissolve the creator iframe.** Scheduled as `T7.1`, the first task of wave 7. This is the largest engineering item in the plan and most of wave 7 depends on it. |
| `C1` | **Accessibility is in scope.** Fold it into components already being rebuilt rather than treating it as a separate pass. Rules are in `CLAUDE.md`. |
> If the `B7` decision was meant as "schedule the decision" rather than "dissolve the
> iframe", that is the one line in this plan to correct before wave 7 starts. Everything
> before wave 7 is unaffected either way.
## 3. Cross-track dependencies
These are the reason the wave order is what it is. Each row is a change request that cannot
be built as written, or cannot be built once, until something else lands.
| ID | Item | Depends on | Type | Why |
|---|---|---|---|---|
| `X1` | `CR-011`, `CR-014` | `S3` | **Blocking** | Both promise an email containing a direct link to the work package. There is no `pushState` anywhere in the app, so no work package has an addressable URL. The emails cannot be built until URL state exists. |
| `X2` | `CR-015` | `A1` | **Blocking** | The hold-not-clearing bug and the transition model are the same code. `wp-creation-app.js:1962-1972` shows a deliberate logged-override path, so hold state is not purely derived from open constraints. Fixing them separately means fixing it twice. |
| `X3` | `CR-014` | `A1` | Sequencing | `Ready for QA` is a new state and must land inside the guarded transition model, not beside it. |
| `X4` | `CR-006` | `B7` | Sequencing | Section toggles must suppress sections inside the creator, which is an iframe child until `T7.1`. |
| `X5` | `CR-004`, `CR-018` | `B4` | **Blocking** | Rollup by building, floor and sector cannot come from `localStorage`. Same data dependency that blocks the pipeline strip. |
| `X6` | `CR-013` | `F6`, `A4` | Sequencing | A material request card is a 12th card on a form already ~4,700px tall. Build it after the form has section structure. |
| `X7` | `CR-003` | `C3` | Sequencing | Priority color coding needs one source of truth for color, or Normal/High/Urgent get four implementations. |
| `X8` | `CR-007` | `F2`, `S2` | **Blocking** | Offline drawing access is a tablet feature and the app bar breaks at 390px. Uploading into a form with no autosave risks losing the upload. |
| `X9` | `CR-005` | `A4`, `F5`, `S9` | Sequencing | A new wizard step should be built during the stepper rebuild, with the read-only-looking fields and keyboard access fixed at the same time. |
| `X10` | `CR-002`, `CR-016` | `CR-006`, `C3` | Sequencing | Both removals are expressed as toggles, not deletions. Without token consolidation the toggled styling drifts. |
## 4. Wave order
| Wave | Focus | File | Items |
|---|---|---|---|
| 0 | Orientation and file map | `docs/waves/wave-0.md` | — |
| 1 | Rendering defects and tooling | `docs/waves/wave-1.md` | `F1` `F3` `F4` `F5`(interim) `S13` |
| 2 | Drawer everywhere, flat strip deleted | `docs/waves/wave-2.md` | `B1` `S4` `F2` |
| 3 | Token consolidation and button system | `docs/waves/wave-3.md` | `C3` `S5` `A3` `A5` |
| 4 | Platform prerequisites | `docs/waves/wave-4.md` | `B4` `S3` `S2` `B5` `S10` `S11` `S12` |
| 5 | Launcher and SOP wizard | `docs/waves/wave-5.md` | `A4` `B3` `S9` `CR-005` `CR-006` `CR-002` `CR-016` `S1`(wizard) |
| 6 | Work package general information | `docs/waves/wave-6.md` | `CR-001` `CR-003` `CR-004` `CR-018` `A7` |
| 7 | The creator | `docs/waves/wave-7.md` | `B7` `A1` `CR-015` `A2` `A6` `CR-014` `CR-007` `B6` `S1`(creator) `F6` |
| 8 | Kitting and material | `docs/waves/wave-8.md` | `CR-009` `CR-010` `CR-011` `CR-012` `CR-013` |
| 9 | Verification and cleanup | `docs/waves/wave-9.md` | `CR-008` `CR-017` `S6` `S7` `C1` `C2` `C4` |
**Waves 1 through 4 produce almost no field-visible change.** That is deliberate and it is
roughly the first third of the effort. It is called out here because the Micron team is
already skeptical about time spent, and silence during these waves reads as nothing
happening. The trade is that waves 5 through 8 land once instead of being rebuilt, which is
the failure mode the review documented at `work-package-suite-styles.css:336`.
### Deltas from the R2 sequence
The R2 document's nine-wave table did not place `A5`, `A7`, or `F6`. This plan places `A5`
in wave 3 (it depends on token consolidation), `A7` in wave 6 (it is general-information
surface work), and `F6` in wave 7 (its actual fix is section tabs, not a CSS change). `F2`
moves fully to wave 2 because `B1` resolves it; wave 1 only stops the clipping.
## 5. Task format
Every task in a wave file follows this shape. Do not start a task that is missing a
done-when list; flag it instead.
```markdown
### T5.4 — CR-005: Upload location lists at SOP setup
- **Items:** CR-005
- **Depends on:** T5.1
- **Blocks:** T6.3
- **Surface:** html/ + server/
- **Files:** <paths from docs/reference/file-map.md>
- **Do:** ...
- **Do not:** ...
- **Done when:**
- [ ] verifiable check
- [ ] verifiable check
```
`Done when` entries must be checkable by running or looking at something. "Confirm with
Nick" is never a done-when; it goes in the PR description.
## 6. Traceability
Every one of the 55 items appears in exactly one wave, except where noted.
| Item | Wave | Item | Wave | Item | Wave |
|---|---|---|---|---|---|
| CR-001 | 6 | F1 | 1 | A1 | 7 |
| CR-002 | 5 | F2 | 1 interim, 2 full | A2 | 7 |
| CR-003 | 6 | F3 | 1 | A3 | 1 interim, 3 full |
| CR-004 | 6 | F4 | 1 | A4 | 5 |
| CR-005 | 5 | F5 | 1 interim, 3 full | A5 | 3 |
| CR-006 | 5 | F6 | 7 | A6 | 7 |
| CR-007 | 7 | S1 | 5 wizard, 7 creator | A7 | 6 |
| CR-008 | 9 | S2 | 4 | B1 | 2 |
| CR-009 | 8 | S3 | 4 | B2 | 2 |
| CR-010 | 8 | S4 | 2 | B3 | 5 |
| CR-011 | 8 | S5 | 3 | B4 | 4 |
| CR-012 | 8 | S6 | 9 | B5 | 4 |
| CR-013 | 8 | S7 | 9 | B6 | 7 |
| CR-014 | 7 | S8 | 9 | B7 | 7 |
| CR-015 | 7 | S9 | 5 | C1 | all, audited in 9 |
| CR-016 | 5 | S10 | 4 | C2 | all, audited in 9 |
| CR-017 | 9 (guard only) | S11 | 4 | C3 | 3 |
| CR-018 | 6 | S12 | 4 | C4 | 4 and 9 |
| | | S13 | 1 | | |
`F5` and `A3` are the same defect from two sources. `C1` and `C2` are standing requirements
enforced per task under `CLAUDE.md`, with a dedicated audit in wave 9. `CR-017` is a
do-not-remove guard rather than a build.
## 7. Out of scope
Deferred by decision. Do not build these, and do not treat them as gaps:
- Full material request and inventory system, including the parts catalog and warehouse
integration. Revisit once Paul Coonrod is engaged.
- Asset database integration. Clinton's team owns that database; the Assets section is
toggled off in the interim (`CR-016`).
- CxAlloy or GC QA system integration. The QA gate is notification-only until CxAlloy lands.
- P6 activity list import and validated lookup. `CR-001` uses free text.
## 8. Inputs the plan is waiting on
These do not block code, but they block the data that makes several features real. Track
them; do not invent placeholder values in their absence.
| Input | Owner | Blocks |
|---|---|---|
| B100 floor and area list | David Leal | `CR-004`, `CR-005` real data |
| Material spreadsheet | Nate, via David Leal | `CR-013` field set |
| Master material workbook, preferred request format | Paul Coonrod | `CR-013` scope |
| QA process of record (Excel questionnaire moving into CxAlloy) | David Ramstorf, Lupe, Cody Schaefer | `CR-014` beyond notification |
Where a list is required and not yet supplied, build the upload path (`CR-005`) and seed with
an obviously-fake sample. Do not hardcode guessed floor names.

120
UX-REVIEW-2026-08-14.md Normal file
View File

@@ -0,0 +1,120 @@
# UX Review — Work Package Suite
**Date:** 2026-08-14 · **Branch:** `users/directory-super-user` · **Scope:** `html/` — 6 pages, 4 stylesheets, ~11.9k lines
Full write-up with screenshots: <https://claude.ai/code/artifact/09d89aa1-54b9-41db-9e09-e1fc8fb55852>
**Method.** Two passes. A full read of the frontend, then the app run locally
(`uvicorn server.app:app`, throwaway SQLite DB) and driven in Chrome through the real
first-run flow: sign in → create project → SOP wizard → creator → directory → field view
at 390px. Section 1 below is browser-verified; section 2 is read from source.
> Incidental find: `server/seed_demo.py` no longer authenticates, so it can't seed a
> running instance — every `/api/` route requires a session and the script sends none.
> It predates the commit that taught the smoke test to sign in.
---
## 1. Rendering defects (browser-verified, reproducible today)
No design decisions needed for any of these.
| # | Defect | Severity |
|---|---|---|
| F1 | **Header project switcher is stale on every page.** Hero, picker and create-user card show the active project; the app bar still reads "Select a project". Two sources of truth, global one loses. | High |
| F2 | **App bar breaks at 390px — on Field View.** Nav wraps to 3 rows and clips: "Sign out" cut in half, search truncated. This is the gloved-hands surface. | High |
| F3 | **Logo/project-name collision in the SOP header.** `.header-left` and the injected chrome fight for the same run of the bar. | Medium |
| F4 | **Comments drawer renders off-screen over the header** in the standalone creator. | Medium |
| F5 | **SOP wizard fields read as read-only**`#f4f4f4` fill, `#e0e0e0` border. Cause: the wizard redeclares its own tokens and never sees `--cds-field: #ffffff`. | Medium |
| F6 | **The creator is one ~4,700px form** — 11 cards rendered at once, jump links standing in for structure. | Medium |
F1 and F2 are prerequisites for the proposed chrome work.
---
## 2. Structural findings (from source)
### Costs users work
- **Validation is `alert()`-only, covering 3 of 10 wizard steps.** 79 native dialogs app-wide,
43 in the creator. `wp-creation-app.js:1144` says "Subject and WP Type are required" without
naming, highlighting or scrolling to the field — on a form ten cards deep.
`validateStep` guards only steps 1, 5, 6 while the markup marks required fields throughout.
- **No autosave, no unsaved-work guard.** The only `beforeunload` listener is analytics dwell
tracking (`work-package-suite-app.js:326`).
- **Nothing is linkable.** No `pushState` anywhere. Tab switches, dashboard views and package
selections change no URL — can't send someone a link to WP07; refresh loses your place.
- **No global nav on the two pages people live in.** Drawer + app bar ship on `admin`, `field`,
`users`. The SOP wizard has its own header and no drawer; the creator has neither, plus a
different brand treatment (mono wordmark vs. logo chip).
### Costs consistency
- **Four parallel token systems.** `#0f62fe` is declared 4× (`--cds-interactive-01`, `--primary`,
`--accent` twice). Already caused a bug — see the comment at
`work-package-suite-styles.css:336` about `.field-hint` rendering unstyled.
- **Icon set is mixed emoji + dingbats** (⤓ ⤒ 👁 ★ 📊 ⧉ 🕘 💬 ▤ ⚙ 🔒 ⚡ ⎙ ◔ ▦). `▤` is used for
two different things on the same page.
- **Four sample-data affordances under three names.** One sits a click from live project data
with no confirm and no undo.
### Costs accessibility (and the field case)
- **Hover-only tooltips carry load-bearing instructions.** 15 `.help-tip` badges, `<span>`s with
no `tabindex` — so the `:focus` half of their CSS rule is dead code and there's no touch path.
- **SOP step rail not keyboard-reachable.** All 10 steps are `<div onclick>`; 12 `<div>` + 2
`<span>` click handlers app-wide.
- **Zero `aria-live` regions.** Both toast systems and every admin banner announce nothing.
`login.html` gets this right with `role="alert"`/`role="status"`; the pattern never spread.
- **Helper text at ~2.9:1** (`--text-dim: #8d8d8d` at 12px). `console.css:85-87` already
diagnosed and fixed this; the other two sheets didn't follow.
- **Wizard removes the focus ring** — `outline: none` plus a pale 3px glow
(`work-package-suite-styles.css:322-328`).
---
## 3. Assessment of the proposed change list
Strong work — nearly every item maps to something verifiable, and two rank above anything in
this review's own findings. 9 endorsed, 7 need adjustment, 6 gaps.
### Endorse as proposed
| ID | Item | Note |
|---|---|---|
| A1 | **Guarded status transitions** | Best item on the list. But: Issue (Hold) is a *branch* from several states, and there's a deliberate **logged-override** path for predecessors (`wp-creation-app.js:1962-1972`). "One legal next action" is too narrow — model as primary-next **+ audited override**. |
| A2 | De-duplicate constraint warning | Triplication confirmed. Caveat: the top banner is the only one visible without scrolling, so the tab count badge must carry that weight. |
| A3 | Fields that look editable | Confirmed (F5). Pair with token consolidation or it drifts back. |
| A4 | Vertical stepper + progress | Also retires the orphaned "1 / 10" counter. Make the steps real `<button>`s while rebuilding. |
| A5 | One button system, no green/blue mix | Real: green `Save & View` + green `SOP Complete` vs blue elsewhere. **Scope "sentence case" to buttons/field labels** — the uppercase card-header idiom in `console.css` is deliberate and works. |
| A6 | Sidebar triage data + inline hold reason | Cheap — hold reason is already captured by the hold modal. |
| A7 | Card status lines · footer clarity · Language & time | Localization is a real shipped feature (`admin.js:484-517`), so keeping it is correct. |
### Right diagnosis, adjust the prescription
| ID | Item | Adjustment |
|---|---|---|
| B1 | Collapse the 8-item nav | **Don't build a new avatar menu — ship the drawer you already have.** It's the best chrome in the app and already holds the whole nav; it's just on 3 pages of 6 while a flat strip duplicates it. Put it everywhere, delete the strip. Fixes F2. |
| B2 | Un-truncate the switcher | Fix **correctness (F1) first**. "Micron EUV Cleanroom Enable · 2667008" won't fit 280px, and existing rules narrow it to 150px under 900px — needs a breakpoint plan. |
| B3 | Remove the project-picker card | The first-run empty state lives *inside* that card. A header dropdown is the wrong home for "create your first project" — the launcher needs an explicit empty state. |
| B4 | 4-cell pipeline strip | Hard data dependency: launcher SOP status reads `localStorage`, and the creator's dashboard admits it "reads local data now; wires to SQL in Phase 2". Per-browser numbers that look authoritative are worse than none. **Server aggregates first.** |
| B5 | Autosave status text | Must ship with **actual autosave + dirty guard**. A "✓ All changes saved" toast already appears (that's the sync outbox, not your draft), so the app arguably implies this today. |
| B6 | Wizard actions beside the fields | On Constraints/Sequence steps that means scrolling to save. **Prefer sticky** — the creator's sticky save bar already solves it. |
| B7 | Toolbar into the tab row | Unstated prerequisite: tab row is in the parent, toolbar in the iframe child. This **requires dissolving the iframe** — the largest engineering item on the list. Name and schedule it. |
### Not addressed
| ID | Gap |
|---|---|
| C1 | **Accessibility** — and the redesign touches every broken part. The step rail being rebuilt is 10 `<div onclick>`s; the status pills being replaced are the moment for a real radio group; the tooltips are unreachable on the tablet Field View targets. Skipping these means paying for the same components twice. |
| C2 | **Mobile** — no item on the list, and it's where the worst rendering is (F2). |
| C3 | **Token consolidation** — without it, "one button system" gets implemented four times and drifts, exactly as `.field-hint` did. |
| C4 | **URL state · icon system · unsaved-changes guard · sample-data sprawl** — none addressed. |
---
## 4. Recommended sequence
Ordered by dependency, not preference.
1. **The six rendering defects.** No design decisions; F1/F2 unblock the chrome work.
2. **Drawer everywhere, flat strip deleted.** Reuses the best existing component, fixes phone-width collapse, settles global nav before anything is redrawn.
3. **Token consolidation.** One source of truth, page sheets aliasing to it. Every later visual item lands once instead of four times.
4. **Launcher + SOP wizard.** Pipeline strip (after server aggregates), empty-state ownership, vertical stepper, editable fields, autosave + guard. Make the 10 steps buttons while rebuilding.
5. **The creator.** Guarded transitions, de-duplicated warnings, section tabs, triage data. **Decide the iframe question up front** — most of this depends on it.
**Dependencies:** B1 resolves F2 · B2 depends on F1 · A3 depends on C3 · B7 gates most of step 5.

53
docs/waves/backlog.md Normal file
View File

@@ -0,0 +1,53 @@
# Backlog
Anything noticed during implementation that is real but not in the plan goes here instead of
into the current PR. `CLAUDE.md` requires this: every change traces to an item ID, so
unplanned work gets logged rather than built.
Add an entry, do not fix it inline. This file is reviewed at `T9.7` and feeds the next spec
revision.
## Format
```markdown
### BL-001 — Short title
- **Found during:** T3.2
- **Where:** path/to/file.js:120
- **What:** one or two sentences on the problem
- **Why not now:** out of scope for the current wave / needs a product decision / larger than the task
- **Suggested wave or follow-up:** wave 9 / next revision / needs Nick
```
## Known follow-ups already identified in the spec
These are logged from the source documents, not discovered in code. They are real but
deliberately deferred.
### BL-000a — Validated P6 activity lookup
- **From:** `CR-001`
- **What:** `CR-001` accepts free text for the P6 Activity ID. A validated lookup against an imported P6 activity list was identified as the eventual want.
- **Why not now:** the Micron schedule is actively being reworked, so importing an activity list now would import churn.
- **Suggested:** next revision, once the schedule stabilizes.
### BL-000b — Field-level toggles in General Information
- **From:** `CR-006`
- **What:** `CR-006` toggles whole sections. General Information may need per-field toggles, since projects differ in which identifiers they use.
- **Why not now:** section-level toggles cover every removal request currently on the list.
- **Suggested:** next revision, if a second project needs a different field set.
### BL-000c — Estimated versus actual hours productivity factor
- **From:** `CR-017`
- **What:** Actual Hours is retained and rolls up. Comparing it against estimated hours would produce a productivity factor, which was the stated reason for wanting the field.
- **Why not now:** estimated hours capture is not in scope this round.
- **Suggested:** next revision.
### BL-000d — Attachment merge versus list on export
- **From:** `CR-008` / `T9.1`
- **What:** whether the PDF export merges attachments into one package or lists them separately.
- **Why not now:** product decision, raised in the `T9.1` PR.
- **Suggested:** needs Nick.

91
docs/waves/wave-0.md Normal file
View File

@@ -0,0 +1,91 @@
# Wave 0 — Orientation
**Do this before wave 1.** Nothing else in the plan should be started until `T0.1` is merged.
The item IDs and file references throughout this plan come from a UX review conducted on
branch `users/directory-super-user`, not from a fresh read of the repo you are in. The line
numbers were accurate at the time of the review. Confirm them before relying on them.
---
### T0.1 — Build the file map
- **Items:** none (prerequisite)
- **Depends on:** nothing
- **Blocks:** every other task
- **Surface:** documentation only
- **Files:** creates `docs/reference/file-map.md`
**Do:**
Inventory the frontend and record it. The review described `html/` as 6 pages, 4
stylesheets, roughly 11,900 lines. Confirm that and write down what is actually there.
For each of the 6 pages record: file path, what it is called in this plan (launcher, SOP
wizard, creator, admin, field view, directory/users, login), which stylesheets it loads,
which scripts it loads, and whether it renders inside an iframe or hosts one.
For each stylesheet record: path, and which pages consume it.
Then verify these specific references from the review and record the current line number
next to each, or mark it `NOT FOUND`:
| Reference | Claim |
|---|---|
| `wp-creation-app.js:1144` | `alert()` reading "Subject and WP Type are required" |
| `wp-creation-app.js:1962-1972` | logged-override path for predecessor constraints |
| `work-package-suite-app.js:326` | the only `beforeunload` listener, analytics dwell tracking |
| `work-package-suite-styles.css:322-328` | `outline: none` plus a pale 3px focus glow |
| `work-package-suite-styles.css:336` | comment about `.field-hint` rendering unstyled |
| `console.css:85-87` | the contrast fix that was not propagated |
| `admin.js:484-517` | language and time localization |
| `server/seed_demo.py` | seeding script that does not authenticate |
Also record counts, so later waves can prove they went down:
- occurrences of `alert(`, `confirm(`, `prompt(` — review found 79 app-wide, 43 in the creator
- `<div` and `<span` elements carrying `onclick` — review found 12 and 2
- declarations of `#0f62fe` — review found 4
- `.help-tip` elements — review found 15
- `aria-live` regions — review found 0
- `pushState` calls — review found 0
**Do not:** change any application code in this task. It is a read and a document.
**Done when:**
- [ ] `docs/reference/file-map.md` exists and lists all pages, stylesheets and scripts
- [ ] every reference in the table above is confirmed with a current line number or marked `NOT FOUND`
- [ ] the seven counts are recorded with the command used to produce each
- [ ] any file path used in a later wave file that does not exist is listed under a `Discrepancies` heading
---
### T0.2 — Confirm the app runs and the baseline is captured
- **Items:** none (prerequisite)
- **Depends on:** T0.1
- **Blocks:** every task with a visual done-when
- **Surface:** local environment
- **Files:** creates `docs/reference/baseline/`
**Do:**
Run the app locally (`uvicorn server.app:app` against a throwaway SQLite database) and drive
the first-run flow: sign in, create a project, SOP wizard, creator, directory, field view.
Capture baseline screenshots at **390px** and **1440px** for each of the 6 pages into
`docs/reference/baseline/`. Name them `<page>-<width>.png`. These are the before images
every later PR compares against.
While doing this, confirm each of `F1` through `F6` still reproduces and note it. If one does
not reproduce, say so in the PR rather than deleting the task from wave 1.
**Do not:** fix anything you see. Wave 1 does that.
**Done when:**
- [ ] the app runs locally from a clean database
- [ ] 12 baseline screenshots exist (6 pages x 2 widths)
- [ ] each of `F1``F6` is marked reproduces / does not reproduce, with a screenshot reference
- [ ] the exact run command and any setup steps are recorded in `docs/reference/file-map.md`

185
docs/waves/wave-1.md Normal file
View File

@@ -0,0 +1,185 @@
# Wave 1 — Rendering defects and tooling
**Items:** `F1`, `F2` (interim), `F3`, `F4`, `F5` (interim), `S13`
**Depends on:** wave 0
**Why first:** none of these need a design decision, and `F1` and `F2` block all chrome work
in wave 2.
Two tasks here are deliberately partial. `F2` gets a containment fix only because `B1` in
wave 2 resolves it properly, and `F5` gets a one-line fix because `C3` in wave 3 removes the
underlying cause. Do not over-build either.
---
### T1.1 — F1: Header project switcher is stale on every page
- **Items:** `F1`
- **Depends on:** T0.1
- **Blocks:** T2.1, T2.2
- **Surface:** `html/`
- **Files:** per file-map — the app bar/header component and whatever holds active-project state
**Problem:** The hero, the project picker and the create-user card all show the active
project. The app bar still reads "Select a project". Two sources of truth for the same value,
and the global one loses.
**Do:**
Find every place the active project is read or stored. There is more than one. Collapse them
to a single source, and have the app bar subscribe to it rather than holding its own copy.
Set the value on project selection and on page load, including a hard refresh and a
deep-linked page.
**Do not:** introduce a state library. This is a single value with a small number of readers.
**Done when:**
- [ ] the app bar shows the active project on all 6 pages
- [ ] value survives a hard refresh on every page
- [ ] switching projects updates the app bar and the hero in the same interaction, with no reload
- [ ] with no project selected, the app bar reads "Select a project" and the picker is reachable
- [ ] exactly one code path writes active-project state; note its location in the PR
---
### T1.2 — F2 (interim): stop the app bar clipping at 390px
- **Items:** `F2` partial
- **Depends on:** T1.1
- **Blocks:** nothing (superseded by T2.2)
- **Surface:** `html/`
- **Files:** app bar styles
**Problem:** At 390px the nav wraps to three rows and clips. "Sign out" is cut in half and
search is truncated. Field View runs at this width on tablets with gloved hands.
**Do:** The smallest change that guarantees no control is unreachable or visually cut. Allow
the bar to scroll or stack rather than clip. This is triage, not a redesign — `T2.2` deletes
this markup.
**Do not:** build a responsive menu, a hamburger, or an avatar dropdown. Wave 2 ships the
existing drawer instead, and anything built here would be thrown away.
**Done when:**
- [ ] at 390px no nav control is clipped or unreachable on any of the 6 pages
- [ ] "Sign out" is fully visible and tappable at 390px
- [ ] search is either fully usable or deliberately hidden at this width, not truncated
- [ ] no layout change at 1440px — screenshot diff against baseline is empty
- [ ] the PR states this is interim and names `T2.2` as the real fix
---
### T1.3 — F3: Logo and project name collide in the SOP header
- **Items:** `F3`
- **Depends on:** T1.1
- **Blocks:** nothing
- **Surface:** `html/`
- **Files:** SOP wizard page and its header styles, `.header-left`
**Problem:** `.header-left` and the injected chrome compete for the same run of the bar and
overlap.
**Do:** Give the two elements a defined relationship rather than letting both claim the same
space. Verify with a long project name — "Micron EUV Cleanroom Enable 2667008" is the real
one and it is what breaks things.
**Do not:** solve it by truncating the project name. `B2` in wave 2 owns truncation and
needs a breakpoint plan, not a silent ellipsis.
**Done when:**
- [ ] no overlap at 390px, 768px, 1024px and 1440px
- [ ] tested with the longest real project name
- [ ] the logo remains fully visible at all four widths
---
### T1.4 — F4: Comments drawer renders off-screen over the header
- **Items:** `F4`
- **Depends on:** T0.1
- **Blocks:** nothing
- **Surface:** `html/`
- **Files:** standalone creator page and comments drawer styles
**Problem:** In the standalone creator, the comments drawer renders off-screen and over the
header.
**Do:** Fix the positioning context and stacking. Check whether the drawer is positioned
relative to the iframe or the parent — if the iframe boundary is implicated, note it in the
PR because `T7.1` removes that boundary.
**Do not:** raise `z-index` until something happens to work. Identify the containing block
first and say what it was in the PR.
**Done when:**
- [ ] the drawer opens fully on screen in the standalone creator at 390px and 1440px
- [ ] the drawer does not cover the header
- [ ] the drawer closes and reopens correctly, and scroll position is not lost
- [ ] behavior is identical in the embedded and standalone creator
---
### T1.5 — F5 (interim): SOP wizard fields read as read-only
- **Items:** `F5` partial, `A3` partial
- **Depends on:** T0.1
- **Blocks:** nothing (completed by T3.4)
- **Surface:** `html/`
- **Files:** SOP wizard stylesheet
**Problem:** Wizard inputs render with a `#f4f4f4` fill and `#e0e0e0` border, which reads as
disabled. Users do not type in them. The cause is that the wizard redeclares its own tokens
and never sees `--cds-field: #ffffff`.
**Do:** Make the fields look editable. The minimal correct fix is to have the wizard consume
the existing field token instead of its local override.
**Do not:** start token consolidation here. That is `T3.1``T3.3` and it is a separate,
mechanical, whole-app change. Fix the field appearance only.
**Done when:**
- [ ] wizard inputs render with a white fill and a border visually consistent with inputs elsewhere
- [ ] genuinely disabled inputs still look disabled and are distinguishable from enabled ones
- [ ] no other component changes appearance — screenshot diff limited to the wizard
- [ ] the PR notes that `T3.4` removes the underlying duplicate token
---
### T1.6 — S13: `server/seed_demo.py` cannot seed a running instance
- **Items:** `S13`
- **Depends on:** T0.1
- **Blocks:** nothing, but makes every later task's setup faster
- **Surface:** `server/`
- **Files:** `server/seed_demo.py`, and the smoke test for reference
**Problem:** Every `/api/` route requires a session and the script sends none, so it cannot
seed a running instance. It predates the commit that taught the smoke test to sign in.
**Do:** Give the script the same authentication the smoke test uses. Reuse that code path
rather than writing a second one.
**Do not:** add a bypass, a debug flag, or an unauthenticated seeding route to the server.
The script authenticates like a client; the server does not get weaker.
**Done when:**
- [ ] `seed_demo.py` runs against a freshly started instance and exits 0
- [ ] the seeded data is visible in the UI after running it
- [ ] running it twice does not error or duplicate data, or it fails with a clear message
- [ ] no new unauthenticated route exists — confirm by diff
---
## Wave 1 exit criteria
- [ ] `F1`, `F3`, `F4` fully resolved and confirmed against the wave 0 baseline screenshots
- [ ] `F2` and `F5` contained, with their real fixes referenced (`T2.2`, `T3.4`)
- [ ] `S13` fixed and seeding works
- [ ] no new `<div onclick>`, no new raw hex values, no new `alert()` calls introduced
- [ ] `F6` untouched — it is a structural problem fixed by section tabs in `T7.2`

108
docs/waves/wave-2.md Normal file
View File

@@ -0,0 +1,108 @@
# Wave 2 — Drawer everywhere, flat strip deleted
**Items:** `B1`, `B2`, `S4`, `F2` (full)
**Depends on:** wave 1 merged
**Why here:** this settles global navigation before anything else is redrawn, and it resolves
the 390px collapse properly.
**The correction that defines this wave.** The proposed UX change list called for building a
new avatar menu to collapse the eight-item nav. Do not build it. The app already has a
drawer that holds the entire nav, and the review's judgment was that it is the best chrome in
the app. It ships on 3 of 6 pages while a flat strip duplicates it. Shipping the existing
drawer everywhere and deleting the strip is less work than building the menu, and it fixes
`F2` as a side effect.
---
### T2.1 — Extract the drawer into a shared component
- **Items:** `B1` part 1
- **Depends on:** T1.1
- **Blocks:** T2.2
- **Surface:** `html/`
- **Files:** the drawer markup/styles/script currently used by admin, field view and directory
**Do:** Lift the drawer into one shared implementation the pages include, rather than three
near-copies. Keep its current behavior and appearance — this is extraction, not redesign.
Where the three copies differ, take the admin version unless the PR explains otherwise.
**Do not:** change the drawer's visual design, its animation, or its contents in this task.
**Done when:**
- [ ] one drawer implementation exists; no page defines its own
- [ ] admin, field view and directory look and behave identically to the wave 0 baseline
- [ ] screenshot diff against baseline for those three pages is empty at both widths
- [ ] the drawer reads active-project state from the single source established in `T1.1`
---
### T2.2 — Mount the drawer on all six pages and delete the flat strip
- **Items:** `B1` part 2, `S4`, `F2` full
- **Depends on:** T2.1
- **Blocks:** T5.1, T7.2
- **Surface:** `html/`
- **Files:** all 6 pages, the flat nav strip markup and styles
**Problem:** The SOP wizard has its own header and no drawer. The creator has neither. These
are the two pages people actually live in, so global nav is missing exactly where it is most
needed (`S4`).
**Do:** Mount the shared drawer on all six pages, including the SOP wizard and the creator.
Delete the flat nav strip and its styles entirely. Reconcile the SOP wizard's bespoke header
against the shared chrome — the wizard keeps its step context, it does not keep its own
global nav.
Unify the brand treatment while you are here: the creator uses a mono wordmark and everything
else uses a logo chip. Pick the logo chip.
**Do not:** leave the flat strip in place behind a media query "just in case". Delete it. Its
existence is what produced the duplication.
**Done when:**
- [ ] all 6 pages render the same drawer with the same contents
- [ ] the flat nav strip is deleted; grep confirms no remaining references
- [ ] at 390px on every page, no nav control is clipped and every destination is reachable
- [ ] Field View at 390px passes with tap targets of at least 44px
- [ ] the creator and the SOP wizard show the same brand treatment as the other four pages
- [ ] the interim fix from `T1.2` is removed rather than layered on top
---
### T2.3 — B2: Project switcher truncation and breakpoints
- **Items:** `B2`
- **Depends on:** T2.2
- **Blocks:** nothing
- **Surface:** `html/`
- **Files:** app bar / drawer header styles
**Problem:** "Micron EUV Cleanroom Enable 2667008" does not fit 280px, and existing rules
narrow the switcher to 150px below 900px. This needs a breakpoint plan, not a width bump.
**Do:** Decide what the switcher shows at each breakpoint and implement that. A workable
plan: full name where it fits, project code alone at narrow widths, full name always
available in the drawer or on hover/focus. Whatever you choose, write it into
`docs/reference/file-map.md` so it is not re-litigated.
**Do not:** apply a plain ellipsis at every width. A project name truncated to "Micron EUV
Clean..." on the page where someone confirms which project they are in is the failure this
item exists to prevent.
**Done when:**
- [ ] the active project is identifiable at 390px, 768px, 1024px and 1440px
- [ ] the full name is reachable at every width by some means, and that means is documented
- [ ] correctness from `T1.1` is preserved — the switcher never reads "Select a project" while a project is active
- [ ] tested with the longest real project name
---
## Wave 2 exit criteria
- [ ] one drawer, six pages, no flat strip
- [ ] `F2` resolved for real; the `T1.2` interim is gone
- [ ] `S4` closed: global nav exists on the SOP wizard and the creator
- [ ] no visual regression on admin, field view or directory versus baseline

147
docs/waves/wave-3.md Normal file
View File

@@ -0,0 +1,147 @@
# Wave 3 — Token consolidation and one button system
**Items:** `C3`, `S5`, `A3`/`F5` (full), `A5`
**Depends on:** wave 2 merged
**Why here:** every remaining visual task lands once instead of four times. Skipping this
means "one button system" gets implemented in four token systems and drifts, which is exactly
how `.field-hint` broke.
This wave should produce **no intentional visual change** except the two known defects it
fixes. Treat any other diff in the screenshot comparison as a bug in your refactor.
---
### T3.1 — Inventory the token systems
- **Items:** `C3` part 1, `S5` part 1
- **Depends on:** T2.3
- **Blocks:** T3.2
- **Surface:** documentation
- **Files:** creates `docs/reference/tokens.md`
**Problem:** Four parallel token systems. `#0f62fe` alone is declared four times, as
`--cds-interactive-01`, `--primary`, and `--accent` twice.
**Do:** Produce a mapping table: every custom property and every raw hex, hsl or rgb value in
the four stylesheets, where it is declared, what consumes it, and which canonical token it
maps to. Include spacing and type scales, not just color. Mark genuine duplicates,
near-duplicates (values one or two hex digits apart, which are usually accidents), and
one-offs.
**Do not:** change any stylesheet in this task.
**Done when:**
- [ ] `docs/reference/tokens.md` lists every declaration with file and line
- [ ] every value is mapped to a proposed canonical token or flagged as a one-off with a reason
- [ ] near-duplicates are called out explicitly, since those are where unintended visual change will come from
- [ ] the `#0f62fe` count matches or corrects the wave 0 baseline count
---
### T3.2 — Establish one source of truth
- **Items:** `C3` part 2, `S5` part 2
- **Depends on:** T3.1
- **Blocks:** T3.3, T3.4, T4.6, T4.7
- **Surface:** `html/`
- **Files:** all 4 stylesheets
**Do:** Create the canonical token set in one place. Convert the other stylesheets to alias
it. Page stylesheets declare no new values. Keep existing custom property names as aliases
where deleting them would touch large amounts of markup — an alias pointing at the canonical
token is fine; a second declaration of the same literal is not.
**Do not:** change any rendered value in this task, including the two known defects. Fix
those in `T3.3` and `T3.4` so their diffs are reviewable separately from a 4-stylesheet
refactor.
**Done when:**
- [ ] exactly one declaration exists per canonical value; grep for `#0f62fe` returns 1
- [ ] no page stylesheet declares a raw color, spacing or type value
- [ ] screenshot diff against the wave 2 state is empty on all 6 pages at both widths
- [ ] `docs/reference/tokens.md` is updated to reflect what was actually built
---
### T3.3 — Fix `.field-hint` and the drift it documents
- **Items:** `S5` part 3
- **Depends on:** T3.2
- **Blocks:** nothing
- **Surface:** `html/`
- **Files:** `work-package-suite-styles.css` around line 336
**Problem:** There is a comment at `work-package-suite-styles.css:336` describing
`.field-hint` rendering unstyled. It is the documented consequence of the duplicate token
systems.
**Do:** Fix `.field-hint` properly now that the tokens are consolidated, and delete the
comment. Then check for the same class of failure elsewhere: any selector that silently
renders unstyled because it referenced a token the page never saw.
**Done when:**
- [ ] `.field-hint` renders correctly everywhere it appears
- [ ] the stale comment is deleted
- [ ] any other silently-unstyled selectors found are listed in the PR and either fixed or logged
---
### T3.4 — A3/F5 full fix: fields look editable
- **Items:** `A3`, `F5` full
- **Depends on:** T3.2
- **Blocks:** T5.1
- **Surface:** `html/`
- **Files:** SOP wizard stylesheet
**Do:** Remove the wizard's local field token override entirely, so it consumes the canonical
field token. Remove the interim fix from `T1.5` rather than leaving both.
**Done when:**
- [ ] the wizard declares no field-specific color of its own
- [ ] enabled inputs are visually identical to enabled inputs on every other page
- [ ] disabled inputs remain clearly distinguishable from enabled ones
- [ ] the `T1.5` interim change is gone; grep confirms it
---
### T3.5 — A5: one button system
- **Items:** `A5`
- **Depends on:** T3.2
- **Blocks:** T5.1, T7.2
- **Surface:** `html/`
- **Files:** button styles, and any page overriding them
**Problem:** Green `Save & View` and green `SOP Complete` against blue everywhere else. Two
button languages competing.
**Do:** One button system: primary, secondary, tertiary, destructive. Map every existing
button to one of those roles. Green becomes a status color, not an action color.
Apply sentence case **to buttons and field labels only**.
**Do not:** touch the uppercase card-header idiom in `console.css`. It is deliberate, it
works, and the review specifically scoped the sentence-case rule to exclude it. Changing it
is out of scope.
**Done when:**
- [ ] every button in the app maps to one of four documented roles
- [ ] no green action buttons remain; `Save & View` and `SOP Complete` use the primary role
- [ ] buttons and field labels are sentence case
- [ ] uppercase card headers in `console.css` are unchanged — confirm by diff
- [ ] button roles are documented in `docs/reference/tokens.md`
---
## Wave 3 exit criteria
- [ ] one token source of truth; page sheets alias only
- [ ] `#0f62fe` declared once
- [ ] no unintended visual change on any page versus wave 2
- [ ] fields look editable, buttons speak one language

215
docs/waves/wave-4.md Normal file
View File

@@ -0,0 +1,215 @@
# Wave 4 — Platform prerequisites
**Items:** `B4`, `S3`, `S2`, `B5`, `S10`, `S11`, `S12`, part of `C4`
**Depends on:** wave 3 merged
**Why here:** `X1` and `X5` are blocking dependencies that resolve in this wave. Two of the
most valuable field requests — the QA gate (`CR-014`) and the kitting notification
(`CR-011`) — promise emails containing a direct link to a work package. Nothing in this app
has a URL. Until `T4.2` lands, those emails cannot be built.
`T4.5` through `T4.7` are small and could be done any time. They are here because every
component built in waves 5 through 8 inherits them, and retrofitting is what `C1` warns
about.
---
### T4.1 — B4: Server aggregates replace localStorage-derived counts
- **Items:** `B4`
- **Depends on:** T3.2
- **Blocks:** T5.3, T6.3, T6.5
- **Surface:** `server/` + `html/`
- **Files:** server API, launcher, creator dashboard
**Problem:** Launcher SOP status reads `localStorage`, and the creator's dashboard carries a
comment admitting it reads local data and wires to SQL in Phase 2. Per-browser numbers that
look authoritative are worse than no numbers: two people looking at the same project see
different counts and neither is told.
**Do:** Add server endpoints returning the aggregate counts the launcher and dashboard need,
computed from the database. Point both surfaces at them. Delete the `localStorage` reads for
anything presented as a count or a status rollup.
Design the aggregate shape to support grouping by building, floor and sector, because
`CR-018` needs exactly that in wave 6. You do not have to expose the grouping yet, but do not
build a shape that cannot.
**Do not:** leave a `localStorage` fallback for counts. A silently-stale fallback is the
failure mode being fixed. If the endpoint fails, show an error state.
**Done when:**
- [ ] launcher and dashboard counts come from the server
- [ ] two different browsers signed in as different users show identical counts for the same project
- [ ] no `localStorage` read remains behind any displayed count; grep confirms
- [ ] endpoint failure shows an explicit error state, not a zero and not a stale number
- [ ] the aggregate response can be grouped by location without a schema change
---
### T4.2 — S3: URL state
- **Items:** `S3`, part of `C4`
- **Depends on:** T2.2
- **Blocks:** T7.6, T8.3 (blocking dependency `X1`)
- **Surface:** `html/`
- **Files:** all 6 pages, routing/state handling
**Problem:** No `pushState` anywhere. Tab switches, dashboard views and package selections
change no URL. You cannot send someone a link to WP07, and a refresh loses your place.
**Do:** Give addressable state a URL. At minimum: the selected project, the selected work
package, the active tab or section, and the active dashboard view or filter. Restore state
from the URL on load. Make browser back and forward behave.
Deep links must work for a signed-out user: land on login, then continue to the requested
target after signing in. An email link that dumps the recipient on a dashboard is the
failure this task exists to prevent.
**Do not:** use hash-only routing if the server can serve paths. Do not put anything secret
in the URL.
**Done when:**
- [ ] a URL identifying a specific work package opens that work package directly
- [ ] the same URL works for a signed-out user via login, landing on the requested target
- [ ] refresh preserves project, package, tab and view
- [ ] browser back and forward move through states without a full reload or a broken view
- [ ] copying the URL and opening it in a second browser reaches the same view
- [ ] `pushState` count is greater than 0; record the new count
---
### T4.3 — S2: Autosave and unsaved-work guard
- **Items:** `S2`, part of `C4`
- **Depends on:** T4.2
- **Blocks:** T7.7 (blocking dependency `X8`)
- **Surface:** `server/` + `html/`
- **Files:** creator, SOP wizard, `work-package-suite-app.js:326`
**Problem:** No autosave and no unsaved-work guard. The only `beforeunload` listener is
analytics dwell tracking. On a form roughly 4,700px tall, a mis-click loses everything.
**Do:** Autosave drafts on a debounce and on step or section change. Add a real
`beforeunload` guard for genuinely unsaved changes, alongside the analytics listener rather
than replacing it. Add draft recovery: if a session ends with unsaved work, offer it back on
return.
**Do not:** fire the guard when nothing has changed. A dialog that appears on every exit
gets clicked through and is worse than none.
**Done when:**
- [ ] typing then navigating away and returning restores the work
- [ ] typing then closing the tab prompts; not typing then closing does not
- [ ] autosave survives a browser crash simulation (kill the tab, reopen)
- [ ] the analytics dwell listener still functions
- [ ] autosave failure is surfaced to the user, not swallowed
---
### T4.4 — B5: Honest save status
- **Items:** `B5`
- **Depends on:** T4.3
- **Blocks:** nothing
- **Surface:** `html/`
- **Files:** creator, SOP wizard, existing toast system
**Problem:** An "All changes saved" toast already appears, but it belongs to the sync outbox,
not the draft. The app already implies a promise it does not keep.
**Do:** A persistent save-state indicator tied to the actual draft: saving, saved with a
timestamp, or failed with a retry. Distinguish it clearly from the sync outbox message, or
rename the outbox message so the two are not confused.
**Done when:**
- [ ] the indicator reflects real draft state, verified by watching it during a save failure
- [ ] a failed save is visually distinct from a successful one and offers a retry
- [ ] the sync outbox message no longer reads as a draft-save confirmation
- [ ] the indicator is announced via `aria-live` (see `T4.5`)
---
### T4.5 — S10: `aria-live` regions
- **Items:** `S10`
- **Depends on:** T3.2
- **Blocks:** nothing, but every later task depends on the pattern existing
- **Surface:** `html/`
- **Files:** both toast systems, admin banners, `login.html` as the reference
**Problem:** Zero `aria-live` regions app-wide. Both toast systems and every admin banner
announce nothing. `login.html` already does this correctly with `role="alert"` and
`role="status"`; the pattern never spread.
**Do:** Copy the `login.html` pattern into both toast systems and the admin banners. Errors
use `role="alert"` (assertive), confirmations use `role="status"` (polite).
**Done when:**
- [ ] every toast and banner announces
- [ ] errors interrupt, confirmations do not
- [ ] verified with a screen reader, or with an automated check plus a manual read of the markup
- [ ] `aria-live` region count is greater than 0; record it
---
### T4.6 — S11: Helper text contrast
- **Items:** `S11`
- **Depends on:** T3.2
- **Blocks:** nothing
- **Surface:** `html/`
- **Files:** canonical tokens, `console.css:85-87` as the reference
**Problem:** `--text-dim: #8d8d8d` at 12px is about 2.9:1. `console.css:85-87` already
diagnosed and fixed this; the other two sheets did not follow. Now that tokens are
consolidated, fix it once.
**Do:** Raise the dim-text token to meet 4.5:1 against its actual backgrounds. Check it on
every surface it lands on, including shaded cards and table rows, not just white.
**Done when:**
- [ ] all helper and hint text measures at least 4.5:1 against its real background
- [ ] the fix lives in the canonical token, not per-sheet
- [ ] `console.css` no longer needs its local override; remove it
- [ ] measured values are recorded in the PR
---
### T4.7 — S12: Restore the focus ring
- **Items:** `S12`
- **Depends on:** T3.2
- **Blocks:** nothing
- **Surface:** `html/`
- **Files:** `work-package-suite-styles.css:322-328`
**Problem:** `outline: none` plus a pale 3px glow. Keyboard users cannot see where they are.
**Do:** A visible focus indicator meeting at least 3:1 against adjacent colors, applied
consistently to every interactive element. Use `:focus-visible` so mouse users do not see
rings on click.
**Do not:** ship `outline: none` without a replacement anywhere in the app.
**Done when:**
- [ ] tabbing through every page shows a clearly visible indicator at each stop
- [ ] the indicator meets 3:1 against adjacent colors on every background it appears on
- [ ] no `outline: none` remains without an accompanying visible replacement; grep confirms
- [ ] mouse clicks do not leave a persistent ring
---
## Wave 4 exit criteria
- [ ] counts come from the server and match across browsers
- [ ] work packages are addressable by URL, including from a signed-out state
- [ ] drafts autosave and unsaved work is guarded
- [ ] toasts announce, helper text is legible, focus is visible
- [ ] `X1` and `X5` are cleared, unblocking `CR-011`, `CR-014`, `CR-004` and `CR-018`

239
docs/waves/wave-5.md Normal file
View File

@@ -0,0 +1,239 @@
# Wave 5 — Launcher and SOP wizard
**Items:** `A4`, `B3`, `S9`, `S1` (wizard), `CR-005`, `CR-006`, `CR-002`, `CR-016`
**Depends on:** wave 4 merged
**Why here:** `CR-006` is the enabler for every removal request in the plan. Building it
before `CR-002` and `CR-016` means nothing gets deleted that another project may need.
First field-visible wave. Everything before this was prerequisites.
---
### T5.1 — A4 + S9: Vertical stepper with real buttons
- **Items:** `A4`, `S9`
- **Depends on:** T3.4, T3.5, T2.2
- **Blocks:** T5.4, T5.5
- **Surface:** `html/`
- **Files:** SOP wizard step rail
**Problem:** The step rail is 10 `<div>` elements with `onclick`, so it is unreachable by
keyboard. There is also an orphaned "1 / 10" counter doing the job a stepper should do.
**Do:** Rebuild the rail as a vertical stepper showing all 10 steps, which is complete, which
is current, and which are not yet reachable. Every step is a real `<button>`. Retire the
"1 / 10" counter.
Per `C1`, this is the moment to make it accessible — it is being rebuilt anyway.
**Do not:** ship a single `<div onclick>` in the replacement. The app-wide count from wave 0
must go **down** by 10.
**Done when:**
- [ ] all 10 steps are `<button>` elements
- [ ] the rail is fully operable by keyboard: tab, arrow keys, enter and space
- [ ] current step is exposed with `aria-current`
- [ ] completed, current and unavailable states are distinguishable without relying on color alone
- [ ] the "1 / 10" counter is gone
- [ ] app-wide `<div onclick>` count dropped by 10 versus the wave 0 baseline
---
### T5.2 — B3: Launcher empty state, then remove the picker card
- **Items:** `B3`
- **Depends on:** T4.1
- **Blocks:** T5.3
- **Surface:** `html/`
- **Files:** launcher, project picker card
**Problem:** The proposal removes the project-picker card, but the first-run empty state
lives inside it. A header dropdown is the wrong home for "create your first project".
**Do:** Build an explicit first-run empty state on the launcher: no projects yet, here is how
to create one, here is the sample project. Only once that exists, remove the picker card.
Order matters — removing the card first strands new users.
**Done when:**
- [ ] a brand-new account with zero projects sees a clear path to create one
- [ ] the sample project remains discoverable from the empty state
- [ ] the picker card is removed only after the empty state ships
- [ ] switching projects still works from the header for users who have projects
---
### T5.3 — B4 surface: pipeline strip
- **Items:** `B4` UI half
- **Depends on:** T4.1, T5.2
- **Blocks:** nothing
- **Surface:** `html/`
- **Files:** launcher
**Do:** Build the four-cell pipeline strip against the server aggregates from `T4.1`. Each
cell links through to the filtered list, using the URL state from `T4.2`.
**Do not:** ship it against `localStorage`. That is the specific thing `B4` warns about.
**Done when:**
- [ ] every number comes from a server endpoint
- [ ] each cell links to a filtered view via a shareable URL
- [ ] a project with zero work packages renders a sensible empty state, not four zeros that look broken
- [ ] the strip announces updates via `aria-live` if it refreshes in place
---
### T5.4 — CR-005: Upload building / floor / sector lists at SOP setup
- **Items:** `CR-005`
- **Depends on:** T5.1
- **Blocks:** T6.3
- **Surface:** `server/` + `html/`
- **Files:** SOP wizard, server API, schema
**Intent:** The location taxonomy differs per project. On Micron, floors within B100 behave
like separate buildings, so floor and sector are the unit of both execution and cost
tracking. Configure it once per SOP rather than hard-coding it.
**Do:** A wizard step accepting a bulk upload (CSV or paste) of Building / Floor / Sector
values, with the hierarchy preserved. Editable after import: add, rename, deactivate. Store
as structured codes, not display strings — `CR-018` needs to group by them.
Deactivating a value hides it from new work packages without breaking existing ones that
reference it.
**Do not:** hardcode floor or building names. The B100 list has not been supplied yet (see
`IMPLEMENTATION.md` section 8). Seed with obviously-fake sample values.
**Done when:**
- [ ] CSV upload and paste both work and report rejected rows with reasons
- [ ] duplicates are detected and reported rather than silently merged
- [ ] values are editable after import
- [ ] deactivating a value hides it from new work packages; an existing work package referencing it still renders
- [ ] values are stored as codes suitable for grouping
- [ ] no guessed real-world floor names exist anywhere in the code
---
### T5.5 — CR-006: Section toggles
- **Items:** `CR-006`
- **Depends on:** T5.1
- **Blocks:** T5.6, T5.7, T7.2
- **Surface:** `server/` + `html/`
- **Files:** SOP wizard, work package renderer, PDF export, creator
**Intent:** This is the structural fix behind most removal requests in the plan. Rather than
deleting fields globally, each project turns on only the sections it uses. It is what lets
Micron drop Kitting and Assets while another project keeps them.
**Do:** In the SOP creation tool, list every work package section with an on/off toggle:
General Information, Location, Scope of Work, Assets, Materials, Kitting, Drawings and
Attachments, Constraints, QA/QC, Closeout. Default all on for a new SOP.
A section toggled off does not render in the creation form, the detail view, or the PDF
export. Data already captured is retained, and toggling back on restores it intact.
**Do not:** delete data on toggle-off. Ever. And note `X4`: the creator is an iframe child
until `T7.1`, so toggle propagation into the creator may need revisiting in wave 7 — test it
and say what you found.
**Done when:**
- [ ] all 10 sections are toggleable
- [ ] a section toggled off is absent from the form, the detail view and the PDF export
- [ ] toggling off then on restores prior data with no loss
- [ ] new SOPs default to all sections on
- [ ] toggle state propagates into the creator, or the PR documents exactly where it does not and why
---
### T5.6 — CR-002: Remove Acumatica Cost Code and Task
- **Items:** `CR-002`
- **Depends on:** T5.5
- **Blocks:** nothing
- **Surface:** `html/` + `server/`
- **Files:** work package form, detail view, PDF export
**Intent:** The team concluded these are noise on a field work package. Cost codes are
effectively constant and the Acumatica task mapping is a PM concern. The cost visibility they
actually want is by building and floor, which is `CR-004` and `CR-018`.
**Do:** Hide both fields from the form, the detail view and the PDF export, using the section
or field toggle mechanism from `T5.5`. Retain the data and the model.
**Do not:** drop the columns or delete the data. Another project may need these fields, and
that is what the toggles are for.
**Done when:**
- [ ] neither field appears in the form, detail view or PDF export for the Micron EUV SOP
- [ ] existing records still hold their values; confirm by querying
- [ ] the fields can be re-enabled for another SOP without a code change
- [ ] no schema migration drops data
---
### T5.7 — CR-016: Assets section off for Micron EUV
- **Items:** `CR-016`
- **Depends on:** T5.5
- **Blocks:** nothing
- **Surface:** configuration
- **Files:** SOP configuration for Micron EUV
**Intent:** Asset content duplicates the database Clinton's team maintains. Integration is
deferred, so the section comes out in the interim.
**Do:** Toggle Assets off for the Micron EUV SOP. Leave the section and its data model in the
application so the eventual database integration has somewhere to land.
**Done when:**
- [ ] Assets does not render for Micron EUV in the form, detail view or export
- [ ] the section and model remain in the codebase
- [ ] another SOP can enable Assets and it works
---
### T5.8 — S1 (wizard): Inline validation
- **Items:** `S1` wizard half
- **Depends on:** T5.1
- **Blocks:** nothing
- **Surface:** `html/`
- **Files:** SOP wizard validation, `wp-creation-app.js:1144` for the pattern to replace
**Problem:** Validation is `alert()`-only and `validateStep` guards only steps 1, 5 and 6,
while the markup marks required fields throughout. The message "Subject and WP Type are
required" names no field, highlights nothing and scrolls nowhere.
**Do:** Inline validation on every step that has required fields. Errors appear at the field,
the first invalid field receives focus, and the message says what is wrong. Announce via the
`aria-live` region from `T4.5`. Remove the wizard's `alert()` calls.
**Do not:** add a native `alert()`, `confirm()` or `prompt()` anywhere. The app-wide count
from wave 0 must go down.
**Done when:**
- [ ] every step with required fields validates them
- [ ] each error renders at its field and is associated via `aria-describedby`
- [ ] submitting an invalid step focuses and scrolls to the first error
- [ ] errors announce to screen readers
- [ ] the wizard's native dialog count is 0; record the new app-wide count
---
## Wave 5 exit criteria
- [ ] the stepper is keyboard operable and `<div onclick>` count dropped by at least 10
- [ ] section toggles work end to end, including the export
- [ ] Acumatica fields and Assets are hidden by configuration, with no data deleted
- [ ] the wizard has no `alert()` calls left
- [ ] first-run users can create a project

153
docs/waves/wave-6.md Normal file
View File

@@ -0,0 +1,153 @@
# Wave 6 — Work package general information
**Items:** `CR-001`, `CR-003`, `CR-004`, `CR-018`, `A7`
**Depends on:** wave 5 merged
**Why here:** these are the field's most-requested changes and they need the location lists
(`T5.4`), the server aggregates (`T4.1`) and the token system (`T3.2`) to already exist.
---
### T6.1 — CR-001: P6 Activity ID and Description
- **Items:** `CR-001`
- **Depends on:** T5.5
- **Blocks:** nothing
- **Surface:** `server/` + `html/`
- **Files:** work package model, General Information section, list/dashboard, PDF export
**Intent:** Every work package traces back to the P6 schedule activity that drives it, so a
date on the work package is anchored to a schedule activity rather than floating on its own.
The meeting placed this at the top of the work package, next to the due date.
**Do:** Add `P6 Activity ID` and `P6 Activity Description` to General Information. Free text
is acceptable — a validated lookup against an imported activity list is explicitly deferred,
partly because the Micron schedule is actively being reworked.
Display the Activity ID next to the Due Date so the schedule driver is visible without
scrolling. Add it to the list/dashboard as a sortable column and to the PDF export.
**Done when:**
- [ ] both fields exist, persist, and survive a reload
- [ ] Activity ID renders next to Due Date on the detail view
- [ ] the list/dashboard column sorts correctly, including with empty values
- [ ] both fields appear on the PDF export
- [ ] the fields respect the `CR-006` section toggles
---
### T6.2 — CR-003: Priority
- **Items:** `CR-003`
- **Depends on:** T3.5
- **Blocks:** nothing
- **Surface:** `server/` + `html/`
- **Files:** work package model, General Information, list/dashboard, PDF export
**Intent:** Due date alone does not convey urgency. Exactly three levels, agreed live in the
meeting: Normal, High, Urgent. Normal is the baseline default.
**Do:** A required dropdown with those three values, defaulting to Normal. Filterable and
sortable on the dashboard. Color-coded using canonical tokens only (`X7`). Printed on the
PDF export.
Priority is independent of status: it can change after issue without a status change.
**Do not:** add a fourth level. Do not introduce a new color outside the token system.
**Done when:**
- [ ] exactly three values exist; Normal is the default on a new work package
- [ ] the dashboard filters and sorts by priority
- [ ] priority colors come from canonical tokens; no raw hex added
- [ ] color is not the only signal — the label is always present
- [ ] priority prints on the PDF export
- [ ] changing priority does not alter status
---
### T6.3 — CR-004: Building / Floor / Sector dropdowns
- **Items:** `CR-004`
- **Depends on:** T5.4, T4.1
- **Blocks:** T6.4, T8.4
- **Surface:** `server/` + `html/`
- **Files:** Location section, work package model, dashboard filters
**Intent:** Users pick location from a controlled list rather than typing it. Floors within
B100 behave like separate buildings and are the unit of both execution and cost tracking.
**Do:** Three dependent dropdowns in the Location section, sourced from the project
configuration built in `T5.4`. Selecting a Building filters Floors; selecting a Floor filters
Sectors. Store codes, not display strings. Add dashboard filters for all three.
**Do not:** free-text these fields, and do not read the option lists from `localStorage`
(`X5`).
**Done when:**
- [ ] all three render as dropdowns populated from project configuration
- [ ] dependent filtering works, and clearing a parent clears its children
- [ ] values persist as codes; confirm by inspecting stored data
- [ ] the dashboard filters by each of the three
- [ ] a work package referencing a deactivated value still renders correctly
- [ ] all option data comes from the server
---
### T6.4 — CR-018: Rollup by floor and area
- **Items:** `CR-018`
- **Depends on:** T6.3, T4.1
- **Blocks:** nothing
- **Surface:** `server/` + `html/`
- **Files:** aggregate endpoints, dashboard
**Intent:** This is why the Acumatica cost code was removed rather than relabeled. The
tracking dimension the team wants is floor and area, not an accounting code. For Phase 2 the
intent is to stop treating B100 as one building and monitor by floor, since each floor
carries its own instrumentation and material load.
**Do:** Extend the `T4.1` aggregates to group and total by Building, Floor and Sector. Surface
grouped totals on the dashboard. Where hours are captured (Closeout `Actual Hours`, retained
per `CR-017`), roll them up along the same dimensions.
**Done when:**
- [ ] the dashboard groups and totals by Building, Floor and Sector
- [ ] totals reconcile against an unfiltered count; verify with a known data set
- [ ] Actual Hours rolls up along the same dimensions
- [ ] grouping is computed server-side
- [ ] work packages with no location assigned appear in an explicit "unassigned" group rather than vanishing
---
### T6.5 — A7: Card status lines, footer clarity, language and time
- **Items:** `A7`
- **Depends on:** T3.5
- **Blocks:** nothing
- **Surface:** `html/`
- **Files:** card components, footer, `admin.js:484-517`
**Do:** Clarify status lines on cards and tidy the footer as proposed.
**Do not:** remove localization. `admin.js:484-517` handles language and time and it is a
shipped feature. The review specifically endorsed keeping it. If the proposal reads as
removing it, that reading is wrong.
**Done when:**
- [ ] card status lines read clearly and use the canonical status colors
- [ ] the footer is unambiguous about what it is showing
- [ ] localization still functions; verify by switching language and time format
- [ ] `admin.js:484-517` behavior is unchanged
---
## Wave 6 exit criteria
- [ ] P6 activity, priority and structured location all persist and export
- [ ] the dashboard filters by priority and by all three location levels
- [ ] rollups are server-computed and reconcile
- [ ] localization intact

297
docs/waves/wave-7.md Normal file
View File

@@ -0,0 +1,297 @@
# Wave 7 — The creator
**Items:** `B7`, `A1`, `CR-015`, `A2`, `A6`, `CR-014`, `CR-007`, `B6`, `S1` (creator), `F6`
**Depends on:** wave 6 merged, and `T4.2` (URL state) merged
**Why last among the build waves:** most of it depends on the iframe decision, and `CR-014`
depends on `X1`.
`T7.1` is the largest engineering item in the plan. Decided and approved: the iframe gets
dissolved. Do it first — everything after it is cheaper on the far side.
---
### T7.1 — B7: Dissolve the creator iframe
- **Items:** `B7`
- **Depends on:** T6.5
- **Blocks:** T7.2 through T7.9
- **Surface:** `html/`
- **Files:** parent page hosting the creator, the creator itself, cross-frame messaging
**Problem:** The tab row lives in the parent and the toolbar lives in the iframe child, so
folding one into the other is impossible while the boundary exists. The boundary is also
implicated in `F4` and possibly in `CR-006` toggle propagation (`X4`).
**Do:** Remove the iframe boundary so the creator renders in the parent document. Expect to
untangle: cross-frame messaging, duplicated styling, focus and scroll management, and
anything relying on separate document contexts.
Do this as its own PR with no feature work attached. It will be large and it needs to be
reviewable as a structural change.
**Do not:** bundle any other item into this task, and do not leave a dormant iframe path
behind a flag.
**Done when:**
- [ ] no iframe remains in the creator; grep confirms
- [ ] every creator feature works as it did before: verify against the wave 0 baseline screenshots and a functional pass
- [ ] `F4` no longer reproduces
- [ ] `CR-006` section toggles propagate into the creator with no special-casing
- [ ] browser back and forward still behave with the `T4.2` URL state
- [ ] no cross-frame messaging code remains
---
### T7.2 — F6 + A4 pattern: Section tabs replace the 4,700px form
- **Items:** `F6`
- **Depends on:** T7.1, T5.5, T3.5
- **Blocks:** T7.9
- **Surface:** `html/`
- **Files:** creator layout, jump links
**Problem:** The creator is one form roughly 4,700px tall with 11 cards rendered at once and
jump links standing in for structure.
**Do:** Give the form real structure — section tabs or an equivalent that shows one coherent
group at a time. Respect the `CR-006` toggles: a section toggled off has no tab. Retire the
jump links. Section identity belongs in the URL (`T4.2`) so a section is linkable.
**Done when:**
- [ ] no single view exceeds roughly two screen heights at 1440px
- [ ] toggled-off sections have no tab
- [ ] each section is addressable by URL and survives refresh
- [ ] jump links are gone
- [ ] tabs are keyboard operable and follow the tab pattern with `aria-selected` and arrow keys
- [ ] unsaved work is preserved when moving between sections (`T4.3`)
---
### T7.3 — A1 + CR-015: Guarded transitions and the hold bug
- **Items:** `A1`, `CR-015`
- **Depends on:** T7.1
- **Blocks:** T7.4, T7.6
- **Surface:** `server/` + `html/`
- **Files:** status handling, constraint handling, `wp-creation-app.js:1962-1972`
**This is one task, not two (`X2`).** The hold-not-clearing bug and the transition model are
the same code. `CR-015` is the highest-severity item in the plan and it was reproduced live
in front of the Micron team.
**Problem:** Clearing all constraints does not clear the hold. Hold state is not purely
derived from open constraints, which is why it goes stale.
**Do:** Model transitions explicitly. For each state, define the primary next action **plus
an audited override**. "One legal next action" is too narrow — `wp-creation-app.js:1962-1972`
contains a deliberate logged-override path for predecessors, and Issue (Hold) is a branch
from several states, not a single linear step.
Derive hold state from open constraints rather than storing it independently, or if it must
be stored, recalculate it on every constraint change. Write every hold and release to the
work package history with timestamp, user and reason.
**Do not:** remove the logged-override path. It is an audited business rule. And do not fix
the symptom by recalculating on page load only.
**Done when:**
- [ ] clearing the last open constraint clears the hold and returns the work package to its prior status, with no refresh
- [ ] logging a new constraint on an in-progress work package places it back on hold
- [ ] clearing that constraint releases it again
- [ ] the override path still works and still logs
- [ ] Issue (Hold) is reachable from every state it should branch from
- [ ] every hold and release is in the history with timestamp, user and reason
- [ ] a regression test covers the clear-last-constraint path specifically
- [ ] the exact root cause is stated in the PR
**Why the history matters:** the delay-documentation use case is the business justification
for the whole constraint workflow. If a constraint log can be shown to have been wrong, it
stops supporting a notice of delay or a change order claim.
---
### T7.4 — A2: De-duplicate the constraint warning
- **Items:** `A2`
- **Depends on:** T7.3
- **Blocks:** nothing
- **Surface:** `html/`
- **Files:** creator constraint warnings
**Problem:** The same warning appears three times.
**Do:** Keep one. The top banner is the only one visible without scrolling, so it stays. The
tab count badge then has to carry the weight the removed duplicates were carrying — make the
count prominent enough to notice from any section.
**Done when:**
- [ ] the warning appears once
- [ ] the tab badge shows the open constraint count and is legible at 390px
- [ ] with the constraints section scrolled out of view, a user can still tell constraints are open
- [ ] the banner announces via `aria-live` when the count changes
---
### T7.5 — A6: Sidebar triage data and inline hold reason
- **Items:** `A6`
- **Depends on:** T7.3
- **Blocks:** nothing
- **Surface:** `html/`
- **Files:** creator sidebar
**Do:** Surface triage data in the sidebar, including the hold reason inline. The hold reason
is already captured by the hold modal, so this is display work.
**Use case to test against:** someone in a stand-up is asked why a package has not moved.
They open it on a phone and need the answer without scrolling or clicking.
**Done when:**
- [ ] the hold reason is visible in the sidebar without opening the modal
- [ ] status, priority, due date, P6 activity and open constraint count are all visible together
- [ ] usable at 390px
- [ ] a package with no hold shows a sensible state rather than an empty slot
---
### T7.6 — CR-014: Ready for QA gate
- **Items:** `CR-014`
- **Depends on:** T7.3, T4.2 (blocking dependency `X1`)
- **Blocks:** nothing
- **Surface:** `server/` + `html/`
- **Files:** status model, notification service, QA section
**Intent:** Looping QA in has historically been the failure point. Marlena raised this
directly: QA needs to see inbound work and organize their schedule ahead of time rather than
being told after the fact.
**Do:** Add `Ready for QA` between In Progress and Complete/Closed, inside the `T7.3`
transition model (`X3`). Entering that state emails the assigned QA representative and the
QA/QC members named on the work package.
The email includes the work package number and title, location, a scope summary, and **a
direct link that opens that work package** — this is why `T4.2` is a hard prerequisite. A
dashboard view lists everything sitting in Ready for QA. The QA representative can accept or
reject; rejection returns the package to In Progress with a required comment.
**Do not:** build the email with a link to the app root. That is the failure `X1` exists to
prevent.
**Done when:**
- [ ] the new state exists in the transition model, not beside it
- [ ] entering it sends email to the QA rep and named QA/QC members
- [ ] the link in the email opens that specific work package, including for a signed-out recipient via login
- [ ] a dashboard view or filter lists all packages in Ready for QA
- [ ] rejection returns to In Progress and requires a comment
- [ ] the transition is written to history
- [ ] email failure is logged and surfaced, not silent
**Note:** QA currently runs off a comprehensive Excel questionnaire being migrated into
CxAlloy. Integration with CxAlloy is explicitly out of scope. This gate is notification only.
---
### T7.7 — CR-007: Upload drawings for offline access
- **Items:** `CR-007`
- **Depends on:** T7.2, T4.3 (blocking dependency `X8`)
- **Blocks:** nothing
- **Surface:** `server/` + `html/`
- **Files:** Drawings and Attachments section, file storage, export
**Intent:** Today the section holds links, typically to a Bluebeam session. The field wants
the specific PDF attached instead: a general foreman opens the package and sees exactly the
sheet and the highlighted section relevant to their scope, with no navigation and no
connectivity dependency. Links stay supported for those who want them.
**Do:** File upload alongside the existing link support. Store files with the work package.
Include them when the package is exported or downloaded. Make them available offline on a
tablet. Each attachment gets a short description field so the creator can note the focus
area, for example "Tray section, Level 3 east only".
Show file size limits and supported types in the UI before upload, not after failure.
**Do not:** build this before `T4.3`. Uploading into a form with no autosave risks losing
both the upload and the surrounding work.
**Done when:**
- [ ] PDF upload works and the file is retrievable
- [ ] links still work alongside uploads
- [ ] attachments are included in export or download
- [ ] an uploaded drawing opens on a tablet with the network disabled
- [ ] the description field persists and prints on the export
- [ ] limits and supported types are stated before upload
- [ ] upload failure is clear and recoverable, with no loss of surrounding form work
- [ ] the whole flow works at 390px
**Accepted tradeoff:** this shifts effort onto the package creator to isolate the right
sheet. That was raised in the meeting and accepted.
---
### T7.8 — B6: Sticky action bar
- **Items:** `B6`
- **Depends on:** T7.2
- **Blocks:** nothing
- **Surface:** `html/`
- **Files:** creator and wizard action bars
**Problem:** The proposal placed actions beside the fields, but on the Constraints and
Sequence steps that means scrolling to save.
**Do:** A sticky action bar instead. The creator already solves this; reuse that pattern in
the wizard rather than inventing a second one.
**Done when:**
- [ ] save and primary actions are reachable without scrolling on every step and section
- [ ] verified specifically on Constraints and Sequence
- [ ] the bar does not obscure fields at 390px
- [ ] it shows the save state from `T4.4`
---
### T7.9 — S1 (creator): Inline validation
- **Items:** `S1` creator half
- **Depends on:** T7.2
- **Blocks:** nothing
- **Surface:** `html/`
- **Files:** creator validation, `wp-creation-app.js:1144`
**Problem:** 43 native dialogs in the creator alone. `wp-creation-app.js:1144` says "Subject
and WP Type are required" without naming, highlighting or scrolling to the field, on a form
ten cards deep.
**Do:** Inline validation matching the wizard pattern from `T5.8`. Errors at the field, focus
and scroll to the first invalid one, and with section tabs now in place, the tab containing
errors is marked. Remove the creator's native dialogs.
**Done when:**
- [ ] no `alert()`, `confirm()` or `prompt()` remains in the creator; count is 0
- [ ] every required field validates inline
- [ ] submitting focuses and scrolls to the first error, switching sections if needed
- [ ] sections containing errors are marked in the tab row
- [ ] errors announce via `aria-live`
- [ ] the app-wide native dialog count is recorded against the wave 0 baseline of 79
---
## Wave 7 exit criteria
- [ ] no iframe; `F4` resolved structurally
- [ ] `CR-015` fixed with a regression test and a stated root cause
- [ ] hold state is derived, and history records every transition
- [ ] the QA gate emails a working deep link
- [ ] drawings upload and open offline on a tablet
- [ ] creator native dialog count is 0

164
docs/waves/wave-8.md Normal file
View File

@@ -0,0 +1,164 @@
# Wave 8 — Kitting and material
**Items:** `CR-009`, `CR-010`, `CR-011`, `CR-012`, `CR-013`
**Depends on:** wave 7 merged
**Decision, approved Aug 14 2026:** keep and extend Kitting, then ship it **toggled off** for
the Micron EUV SOP. Kitting is not happening at Micron today — it is complicated by
journeyman ratio and warehouse cost — but the team expects to get there, and prefab arguably
already qualifies.
That means this wave builds functionality that will not be visible on the pilot project. That
is intentional. Do not shortcut it on the grounds that nobody is using it, and do not switch
it on for Micron.
---
### T8.1 — CR-009: Kitting section retained and structured
- **Items:** `CR-009`
- **Depends on:** T7.9
- **Blocks:** T8.2, T8.3, T8.4
- **Surface:** `server/` + `html/`
- **Files:** Kitting section, SOP configuration
**Do:** Confirm the Kitting section is intact and structured to receive `T8.2` through
`T8.4`. Define kitting statuses explicitly. Proposed set, adjust only with a stated reason:
`Not Started`, `Picking`, `Staged`, `In Transit`, `Delivered`.
Then set the section off for the Micron EUV SOP via the `CR-006` toggles.
**Done when:**
- [ ] kitting statuses are defined as an explicit set, not free text
- [ ] the section is off for Micron EUV and absent from its form, detail view and export
- [ ] a different SOP can enable it and it works fully
- [ ] no kitting data is deleted anywhere
---
### T8.2 — CR-010: Warehouse Owner dropdown
- **Items:** `CR-010`
- **Depends on:** T8.1
- **Blocks:** T8.3
- **Surface:** `server/` + `html/`
- **Files:** Kitting section, dashboard filters
**Intent:** A named person owns fulfillment of the kit. Today that is Paul Coonrod and
everything bottlenecks through him informally, so the work package should record it
explicitly.
**Do:** A Warehouse Owner dropdown populated from project members. That person receives
kitting notifications by default. Filterable on the dashboard so a warehouse owner can see
everything assigned to them.
**Done when:**
- [ ] the dropdown lists project members and persists a selection
- [ ] the selected owner is a default recipient for `T8.3` notifications
- [ ] the dashboard filters by warehouse owner
- [ ] removing someone from the project does not break existing work packages referencing them
---
### T8.3 — CR-011: Kitting status change notification
- **Items:** `CR-011`
- **Depends on:** T8.2, T4.2 (blocking dependency `X1`)
- **Blocks:** nothing
- **Surface:** `server/` + `html/`
- **Files:** notification service, kitting status handling
**Intent:** Extends the existing status-change notification pattern to kitting so the field
knows when material moves from requested to staged to delivered without chasing it in Teams.
**Do:** A kitting status change emails the work package distribution list. The email states
the work package, the old and new status, who changed it, the delivery location, and a direct
link to the work package. Match the existing status-change email pattern so users see one
convention, not two.
**Do not:** send a link to the app root (`X1`).
**Done when:**
- [ ] every kitting status change sends to the distribution list
- [ ] the email includes old status, new status, actor, delivery location and a working deep link
- [ ] the link opens that specific work package, including via login for a signed-out recipient
- [ ] formatting and tone match the existing status-change email
- [ ] rapid consecutive changes do not produce a burst of near-identical emails
- [ ] send failures are logged and surfaced
---
### T8.4 — CR-012: Delivery location on Kitting / Material
- **Items:** `CR-012`
- **Depends on:** T8.1, T6.3
- **Blocks:** nothing
- **Surface:** `server/` + `html/`
- **Files:** Kitting section, print/pick output
**Intent:** Delivery logistics are the real pain. Staging at the warehouse, getting it on a
truck, then making sure material reaches the correct floor lay-down, shark cage or conduit
tree rather than being picked at will by whoever is closest.
**Do:** Capture delivery location using the same Building / Floor / Sector lists as `CR-004`,
plus a free-text detail field for specifics like lay-down area, shark cage or conduit tree.
Include the delivery location on the kitting notification email and on any pick or delivery
printout.
**Done when:**
- [ ] delivery location uses the shared location lists, not a parallel free-text field
- [ ] the detail field persists and prints
- [ ] the location appears on the `T8.3` email
- [ ] it appears on any pick or delivery printout
---
### T8.5 — CR-013: Lightweight material request
- **Items:** `CR-013`
- **Depends on:** T8.4, T7.2 (sequencing dependency `X6`)
- **Blocks:** nothing
- **Surface:** `server/` + `html/`
- **Files:** Materials section, request model, dashboard
**Decision, approved Aug 14 2026:** build the lightweight version. Defer the parts catalog,
live inventory and warehouse integration.
**Intent:** Material requests run today through per-floor Teams chats and a spreadsheet, all
funneling to Paul Coonrod. The comparison drawn in the meeting was a project where requests
went into a OneNote and became, in the words used at the time, word vomit. The point of this
item is structure, not features.
**In scope:** line items (description, quantity, unit), needed-by date, requestor, delivery
location (reuse `T8.4`), and status.
**Out of scope, do not build:** master parts catalog dropdown, live inventory levels,
warehouse system integration.
**Do:** Build the in-scope set. Requests are visible on the dashboard and filterable by
delivery location and status.
**Do not:** build this before `T7.2`. A material card added to the pre-tab creator is a 12th
card on a 4,700px form. And do not invent a parts list — Nate's spreadsheet and the master
material workbook have not been supplied yet (see `IMPLEMENTATION.md` section 8).
**Done when:**
- [ ] line items can be added, edited and removed, with quantity and unit
- [ ] needed-by date, requestor, delivery location and status all persist
- [ ] requests appear on the dashboard, filterable by location and status
- [ ] no parts catalog, inventory count or warehouse integration exists in the diff
- [ ] the section respects `CR-006` toggles
- [ ] the whole flow works at 390px, since requests originate in the field
---
## Wave 8 exit criteria
- [ ] Kitting is fully built and toggled off for Micron EUV
- [ ] notifications carry working deep links and a delivery location
- [ ] the material request is the lightweight scope, nothing more
- [ ] no guessed part numbers or floor names anywhere in the code

217
docs/waves/wave-9.md Normal file
View File

@@ -0,0 +1,217 @@
# Wave 9 — Verification and cleanup
**Items:** `CR-008`, `CR-017` (guard), `S6`, `S7`, `S8`, `C1` audit, `C2` audit, part of `C4`
**Depends on:** wave 8 merged
**Why last:** `CR-008` reviews the PDF export against the final structure rather than
reviewing it twice. The audits confirm the standing requirements actually held across eight
waves rather than being asserted per PR.
---
### T9.1 — CR-008: PDF export review
- **Items:** `CR-008`
- **Depends on:** T8.5
- **Blocks:** nothing
- **Surface:** `server/` + `html/`
- **Files:** PDF export
**Problem:** The export was never walked through in the meeting. It needs a pass to confirm
it prints what the field actually needs and nothing it does not, now that the work package
structure has changed substantially.
**Do:** Review the export section by section against the final structure. Sections toggled
off under `CR-006` must be omitted. The export must include: P6 Activity ID and Description,
Priority, Building / Floor / Sector, scope of work, materials, constraints with status,
QA/QC, and an attachment index. Removed fields — ACU Cost Code, Acumatica Task — must not
appear.
Open question to raise in the PR, not to decide alone: should attachments merge into a single
PDF package, or be listed as separate files? The field preference expressed in the meeting
was to hand someone exactly the sheet they need and nothing more, which argues for a merged
package with only the attached sheets. Propose, do not assume.
**Done when:**
- [ ] every listed field appears in the export
- [ ] toggled-off sections are absent
- [ ] ACU Cost Code and Acumatica Task appear nowhere
- [ ] the export is legible printed in black and white
- [ ] the export is legible opened on a tablet
- [ ] the attachment index lists every attachment with its description
- [ ] the merge-versus-list question is raised in the PR with a recommendation
---
### T9.2 — CR-017: Actual Hours guard
- **Items:** `CR-017`
- **Depends on:** T9.1
- **Blocks:** nothing
- **Surface:** verification only
- **Files:** Closeout section
**This is a guard, not a build.** Removing Actual Hours was floated in the meeting and
rejected — Marlena said she likes tracking actual hours spent so it can be measured. It is
listed here so a later simplification pass does not quietly drop it.
**Do:** Confirm Actual Hours still exists in Closeout, is optional rather than required, and
survived eight waves of change.
**Done when:**
- [ ] Actual Hours exists in Closeout and persists
- [ ] it is optional, and a work package can be closed without it
- [ ] it rolls up per `T6.4`
- [ ] a follow-up item is logged for comparing actual against estimated hours to produce a productivity factor
---
### T9.3 — S6: One icon system
- **Items:** `S6`
- **Depends on:** T9.1
- **Blocks:** nothing
- **Surface:** `html/`
- **Files:** every page
**Problem:** The icon set mixes emoji and dingbats, and at least one glyph is used for two
different meanings on the same page.
**Do:** Pick one icon system and convert everything to it. Build a mapping of meaning to icon
and record it in `docs/reference/tokens.md`. Resolve every duplicate meaning.
**Do not:** leave emoji in the UI. They render differently per platform, which is part of why
the same glyph reads as two things.
**Done when:**
- [ ] one icon system across all 6 pages
- [ ] no glyph carries two meanings anywhere
- [ ] every icon has a text label or an accessible name
- [ ] the meaning-to-icon mapping is documented
- [ ] icons render identically on Windows, macOS and a tablet
---
### T9.4 — S7: Sample-data cleanup
- **Items:** `S7`
- **Depends on:** T9.1
- **Blocks:** nothing
- **Surface:** `html/` + `server/`
- **Files:** every sample-data affordance
**Problem:** Four sample-data affordances under three names. One sits a single click from
live project data with no confirm and no undo.
**Do:** Reduce to one affordance with one name. Move it away from live project actions. Add a
confirmation stating exactly what will happen, and make it reversible or clearly scoped to a
sample project only.
**Done when:**
- [ ] exactly one sample-data affordance exists, under one name
- [ ] it is not adjacent to any live project action
- [ ] it confirms before acting, naming what it will do
- [ ] it cannot touch live project data; verify by attempting it against a real project
- [ ] the other three affordances are removed; grep confirms
---
### T9.5 — C1 + S8: Accessibility audit
- **Items:** `C1`, `S8`
- **Depends on:** T9.3
- **Blocks:** nothing
- **Surface:** `html/`
- **Files:** all pages, `.help-tip` elements
**`S8` is finished here** because tooltips are the one accessibility item with no natural
host component earlier in the plan. 15 `.help-tip` badges are `<span>` elements with no
`tabindex`, so the `:focus` half of their CSS rule is dead code and there is no touch path at
all — on tablets, which is exactly what Field View targets.
**Do:** Make every `.help-tip` reachable by keyboard and by touch. Then audit the whole app
against the `CLAUDE.md` rules and record results in `docs/reference/accessibility-audit.md`.
Compare against the wave 0 baseline counts:
| Metric | Baseline | Target |
|---|---|---|
| `<div>` / `<span>` with `onclick` | 12 / 2 | 0 |
| `.help-tip` unreachable by keyboard | 15 | 0 |
| `aria-live` regions | 0 | at least 1 per toast system and banner |
| Text below 4.5:1 | present | 0 |
| `outline: none` without replacement | present | 0 |
| Native dialogs (`alert`/`confirm`/`prompt`) | 79 | 0, or documented exceptions |
**Done when:**
- [ ] every `.help-tip` works by keyboard and by touch
- [ ] every metric above meets target, or the gap is documented with a reason and a follow-up item
- [ ] a full keyboard-only pass completes the primary flow: sign in, create project, SOP wizard, create work package, issue it
- [ ] the audit document exists with per-page results
---
### T9.6 — C2: Mobile pass
- **Items:** `C2`
- **Depends on:** T9.5
- **Blocks:** nothing
- **Surface:** `html/`
- **Files:** all pages
**Problem:** Nothing in the original proposal touched mobile, and it is where the worst
rendering was found. Field View at 390px is the gloved-hands surface.
**Do:** A deliberate pass at 390px across all 6 pages and the full primary flow. Capture
after screenshots and compare against the wave 0 baseline.
**Done when:**
- [ ] the full primary flow completes at 390px without horizontal scrolling
- [ ] tap targets are at least 44px throughout
- [ ] no control is clipped, overlapped or unreachable on any page
- [ ] `CR-007` attachments open at 390px, offline
- [ ] `T8.5` material requests can be raised at 390px
- [ ] after screenshots for all 6 pages at 390px are committed alongside the baseline
---
### T9.7 — Final reconciliation
- **Items:** all
- **Depends on:** T9.6
- **Blocks:** nothing
- **Surface:** documentation
- **Files:** creates `docs/reference/completion.md`
**Do:** Walk all 55 items from `IMPLEMENTATION.md` section 6 and record, for each: built /
deferred / not applicable, the PR that delivered it, and any deviation from its written
acceptance criteria.
Explicitly confirm the four out-of-scope items in section 7 were not built: no parts catalog
or inventory, no asset database integration, no CxAlloy integration, no P6 activity import.
Then list what the plan learned that the next revision should carry: items whose acceptance
criteria turned out wrong, inputs still outstanding, and follow-ups logged along the way.
**Done when:**
- [ ] all 55 items are accounted for with a PR reference or a stated reason
- [ ] every deviation from written acceptance criteria is documented
- [ ] the four out-of-scope items are confirmed unbuilt
- [ ] outstanding inputs from `IMPLEMENTATION.md` section 8 are restated with current status
- [ ] follow-ups are collected in one place for the next revision
---
## Wave 9 exit criteria
- [ ] the export matches the final structure
- [ ] one icon system, one sample-data affordance
- [ ] accessibility metrics hit target or are documented
- [ ] the primary flow works at 390px
- [ ] every item is reconciled