Files
Project-SDE-WP-Suite/docs/waves/wave-7.md
n.siegfried b1a7fe04bc Wave 7 exit - the creator is a page, the hold clears, QA is in the loop
Nine of nine exit criteria verified, eight ticked. The ninth is recorded
open, deliberately: at rest the creator reads 1,954px / 900px = 2.17 screens
against the strict 2.0 encoding of D3's "roughly two screen heights" (down
from 5,399px). The criterion was amended once already; whether 2.17 satisfies
"roughly" is a product judgment - BL-022 carries the number, the remaining
~154px of chrome, and the question. form_structure_check keeps the strict
check red until it is answered.

Wave totals: 10 tasks (T7.1-T7.10), 8 new probe suites (frame, form
structure, hold, warning, triage, QA gate + capture sink, files + offline,
sticky bar, creator dialogs, usage - 305 new checks), one Alembic migration
(wp_files), no real email sent anywhere on this branch.

Items: B7, F6, D1-D10, CR-015, A1, A2, A6, CR-014, CR-007, B6, S1(creator), D5

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

23 KiB

Wave 7 — The creator

Items: B7, A1, CR-015, A2, A6, CR-014, CR-007, B6, S1 (creator), F6, and from August 18: D1, D2, D3, D4, D5, D8, D9, D10 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.

Read docs/waves/decisions-2026-08-18.md before starting. Eight of its ten items land in this wave, four of them as changes to done-when lists written below. Where a task carries an Amended block, that block wins over the text above it.

Read docs/reference/creator-frame.md before T7.1. It is the measurement of the boundary: 21 colliding stylesheet selectors, 9 colliding script globals, 0 colliding markup ids, 28 cross-frame call sites, and the three questions no count can answer.


T7.1 — B7: Dissolve the creator iframe

  • Items: B7, D1
  • Depends on: T6.5
  • Blocks: T7.2 through T7.10
  • 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.

Amended August 18 (D1, D5). creator-frame.md section 4 lists three questions this task has to settle. Two are now answered:

  • Does the creator get the app bar back? Yes. It is the only page in html/ that loads neither wp-chrome.css nor wp-chrome.js, and a page without the app bar is not a page. Expect to redo the wizard's layout arithmetic - chromeHeight(), --wp-chrome-h, the embed-full sizing.
  • What happens to body.embedded? It goes, and with it .embed-hide. The three controls it was hiding: the creator's own header stays hidden because the app bar replaces it; the sample-data control becomes visible (D1); the analytics button stays visible here and moves to the admin console at T7.10 (D5). Do not delete it in this task - a task that both dissolves the frame and deletes a feature is two diffs.
  • One sequence component or two? Still open, and still this task's call. Nine of the 21 colliding selectors are the sequence editor. The wizard authors the sequence and the creator renders it; confirmed August 18 that those stay two adjustable things, which argues for scoping rather than merging. If you scope, say so and leave BL-015 open for wave 9.

Do not fold BL-001, BL-006, BL-007 or BL-013 into this diff. They are logged against this file and they are cheaper now than ever, and they are still separate items. What this task owes them is a measurement: record whether each still reproduces after the rebuild, so T7.2 and T9.5 inherit a fact rather than an assumption.

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
  • the creator renders the standard app bar, and the wizard's chrome arithmetic still holds at 390px and 1440px
  • body.embedded and .embed-hide are gone; grep confirms
  • the sample-data control is visible and reachable on the creator (D1)
  • BL-001, BL-006, BL-007 and BL-013 are each re-measured after the rebuild and their entries updated with the result

T7.2 — F6 + D3: Side navigation and collapsible sections replace the 4,700px form

  • Items: F6, D3
  • 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.

Amended August 18 (D3) - the answer is not tabs. Asked to choose, the decision was one page with navigation down the side; asked about the height conflict that creates, the call was delegated. Adopted: one page, persistent side navigation, sections collapsible, only the current one open by default, plus an Expand all. Tabs were rejected because they hide sections a first-time author does not know exist. An uncollapsed long form was rejected because it is the 4,700px page F6 exists to fix. The done-when list below is the amended one; it replaces four of the six entries as written.

BL-013 is folded in here and only here: the creator's inputs have outline: none with a 1.05:1 glow as their only focus cue, which is a standing CLAUDE.md violation on the page with the most form controls in the app. A rebuilt form ships the T3.4 ring or it is not done.

Done when:

  • no single view exceeds roughly two screen heights at 1440px at rest - that is, in the default collapse state the page loads in
  • toggled-off sections have no entry in the side navigation
  • each section is addressable by URL and survives refresh
  • jump links are gone
  • the side navigation is keyboard operable and marks the current section with aria-current
  • each section header is a real <button> with aria-expanded, not a <div> with a click handler
  • Expand all and its inverse exist and are keyboard reachable
  • deep-linking to a collapsed section expands it
  • unsaved work is preserved when moving between sections (T4.3)
  • every focusable control on the page draws a ring of at least 3:1 against its background (BL-013)

T7.3 — A1 + CR-015: Guarded transitions and the hold bug

  • Items: A1, CR-015, D4
  • Depends on: T7.1
  • Blocks: T7.4, T7.6
  • Surface: server/ + html/
  • Files: status handling, constraint handling, confirmEarlyRelease() in wp-creation-app.js

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 - confirmEarlyRelease() in wp-creation-app.js is a deliberate logged-override path for predecessors, and Issue (Hold) is a branch from several states, not a single linear step.

Corrected August 18. This task's file list, IMPLEMENTATION.md X2 and CLAUDE.md all cited wp-creation-app.js:1962-1972 as the protected override path. Those lines are deletePackage() and clearSaved() - a different rule entirely. The path to preserve is confirmEarlyRelease(), called from the issue and release paths. All three documents now name it by function so the reference survives this rebuild.

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

Amended August 18 (D4) - what Urgent does. Asked whether an Urgent package should be issuable with an open constraint, the first answer was "maybe". The concern raised back was that a silent bypass destroys the delay-documentation use case that justifies the whole constraint workflow: a constraint log that can be shown to have been skipped stops supporting a notice of delay. Confirmed: surface the audited path, do not add a new one.

  • an Urgent package with an open constraint offers confirmEarlyRelease() in the primary action position, not behind a secondary control
  • taking it still writes actor, timestamp and reason to history
  • no code path releases a constrained package without writing that entry; verify by grep and by driving it
  • a Normal or High package is unchanged - the override is no more prominent than it is today

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, D2, D9, D10
  • Depends on: T7.3, T4.2 (blocking dependency X1), and the wave 5 SOP wizard for the D2 field
  • 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.

Amended August 18 (D2, D9, D10). Three answers change this task:

  • Who receives it. Not "the QA rep and the QA/QC members named on the work package" - the QA group, configured once during SOP setup as a dropdown of project members. That makes the recipient list SOP configuration, so this task acquires a wizard field and a dependency on wave 5 that it did not have.
  • Who hears about a rejection. The work package owner and the same distribution list, not only the submitter.
  • Where the state is visible. The dashboard and Field View.
  • Who turns email on. An administrator, from admin.html. The gate is a stored setting, not an environment variable. It ships off and is verified against a capture sink; no real mail is sent from this branch.

Done when:

  • the new state exists in the transition model, not beside it
  • the QA distribution list is chosen in the SOP wizard from project members and persists
  • entering Ready for QA emails that list and nobody else
  • rejection emails the work package owner and the same list
  • 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
  • Field View shows which packages are in Ready for QA, legibly at 390px, with something other than colour carrying the state (D9)
  • rejection returns to In Progress and requires a comment
  • the transition is written to history
  • email failure is logged and surfaced, not silent
  • the send path is exercised end to end against a capture sink, with the count of captured messages asserted
  • the default is off; an administrator can turn it on from admin.html and a non-administrator cannot (D10)
  • changing the setting is written to the audit log
  • no SMTP password appears anywhere outside the environment, and no customer IP appears in a message body

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, D8
  • 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.

Amended August 18 (D8) - the numbers. CR-007 says "show file size limits and supported types" without saying what they are. They are: 5MB a file, PDFs and images, stored in the same database as everything else. Offline caching covers only the packages assigned to the requesting user, not the whole project.

The consequence was raised and settled rather than discovered later: files in the database means every encrypted backup carries them. Splitting them out was rejected - a backup that excludes the drawings is a backup you cannot restore from - so a 2GB per-project ceiling was approved instead, with a warning at 80%.

Done when:

  • PDF upload works and the file is retrievable
  • image 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
  • offline caching covers the requesting user's assigned packages only; verify a package assigned to someone else is not cached
  • the description field persists and prints on the export
  • limits and supported types are stated before upload
  • a file over 5MB is refused before upload, naming the limit
  • a type outside PDF and image is refused before upload, naming the accepted types
  • a project at 80% of 2GB warns; at 2GB it refuses, naming the ceiling
  • the running project total is visible where uploads happen
  • both limits are enforced on the server, not only in the browser
  • 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

T7.10 — D5: Usage data moves to the admin console

  • Items: D5
  • Depends on: T7.1
  • Blocks: nothing
  • Surface: html/
  • Files: admin.html, admin.js, wp-creation-app.js, work-package-suite-app.js

Problem: Usage analytics exists twice. creator-frame.md section 2b counts five of the nine colliding script globals as this one feature - ANALYTICS_KEY, analyticsLoad, analyticsSave, downloadAnalytics, showAnalytics - and records that the wizard's copy has no caller, because the button that opens it lives on the creator. Once the frame is gone, two implementations sit in one document.

Do: Keep one implementation and put its entry point in the admin console, where an operator-facing report belongs. Remove the analytics control from the creator and from the wizard. This is not a rewrite of what analytics collects - the same data, reached from one place instead of a button on a package-authoring screen.

Do not: change what is tracked, and do not leave the second implementation behind unreferenced. An unreferenced duplicate is what produced this item.

Done when:

  • usage data is reachable from admin.html
  • no analytics control remains on the creator or the wizard; grep confirms
  • exactly one implementation of analyticsLoad/analyticsSave/downloadAnalytics survives; grep confirms
  • the data recorded before this task is still readable after it
  • the console entry point is behind the same role check as the rest of admin.html
  • the report is usable at 390px

Wave 7 exit criteria

  • no iframe; F4 resolved structurally (frame_check.py, 39 checks)
  • CR-015 fixed with a regression test and a stated root cause (hold_check.py; the cause is in the T7.3 commit, both halves - prevStatus destroyed client-side, _released('Issue') true server-side)
  • hold state is derived, and history records every transition (client data.holds, server hold_logged/hold_released audit rows on both write paths)
  • the QA gate emails a working deep link, to the SOP-configured list, with the gate off by default (qa_gate_check.py against the capture sink)
  • drawings upload and open offline on a tablet, inside the 5MB and 2GB limits (files_check.py; the offline half runs against a genuinely killed server)
  • creator native dialog count is 0 (creator_dialogs_check.py; app-wide count printed against the wave 0 baseline of 79)
  • the creator is a page: app bar, side navigation, collapsible sections, focus rings — all shipped — but at rest it reads 1,954px against a 900px viewport: 2.17 screens against the strict 2.0 encoding of "roughly two screen heights." Down from 5,399px. Whether 2.17 satisfies "roughly two" is a product judgment, deliberately not made here after the criterion was already amended once (D3); BL-022 carries the remaining ~154px of page chrome and the question. form_structure_check.py keeps the strict check red until it is answered.
  • D1 through D5, D8, D9 and D10 are each satisfied (D1 T7.1, D2/D9/D10 T7.6, D3 T7.2, D4 T7.3, D5 T7.10, D8 T7.7)
  • no real email left this branch; the capture sink count is recorded (every send in qa_gate_check.py terminates at an in-process SMTP sink; counts and recipients asserted)