From 6088ef17e8efc5389dda0a8672d1b34acf715f8c Mon Sep 17 00:00:00 2001 From: "n.siegfried" Date: Sun, 16 Aug 2026 10:38:58 -0500 Subject: [PATCH] T5.3 - B4 surface: a pipeline strip, four server counts, four shareable links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four cells on the launcher - Work packages, Release ready, On hold, Overdue - every number from /api/wps/metrics, which T4.1 built. There is deliberately no cache fallback anywhere in renderPipeline(): a remembered number sitting beside three live ones is the failure B4 exists to remove, wearing a different hat. The cells are the four the endpoint already computes and the dashboard already filters on, so they map 1:1 onto its flags rather than inventing a fifth slice nothing downstream understands. wp-creation-app.js now names them (DASH_FLAGS) for the same reason: a cell linking to a filter the board does not recognise is a dead link that still looks live. "Links to a filtered view via a shareable URL" needed the filter to BE URL state, which it was not - the dashboard kept its flag in a variable. So: - dashToggleFlag pushes ?flag=, and clears it on the way out of the board - the creator applies ?flag= before its first render, not after (applying it after paints the whole board and throws it away) - Back and Forward move through filters like any other state - work-package-suite-app.js forwards the flag ACROSS the iframe boundary, since the creator's src carries only the project. B7/T7.1 dissolves that frame and this hand-off goes with it; it is commented as such. Zero is a real answer for one slice of a project that has work in it. Four zeros on a project with none is not a reading, it is a strip that looks broken - that case gets a sentence and a way into the creator instead. A failed request gets an explicit error naming the failure, and no cells at all. html/index.html the strip, its states, PIPE_CELLS html/wp-creation-app.js flag as URL state; DASH_FLAGS; dashApplyFlag html/work-package-suite-app.js forward the flag into the frame; clear on exit tests/pipeline_check.py new - 43 checks Done when [x] every number comes from a server endpoint - proved by poisoning localStorage with 99 fake packages and demanding the strip still read the server's 4 [x] each cell links to a filtered view via a shareable URL - and the probe FOLLOWS the link and reads the filter inside the frame rather than trusting that a correct-looking URL was built [x] a project with zero work packages renders a sensible empty state [x] the strip announces updates via aria-live (polite - a count is not an interruption) and reports aria-busy while it is counting What the probe caught The link landed on "Complete the SOP Configuration first". Not the strip's fault: browser_check.py's fixture stores a bare {governance: …} blob as the SOP data, where production stores {sop, state}. restoreSavedSOP() needs `state` and bails without it, so sopComplete stays false and the WP tab shows its gate. pipeline_check seeds the production shape. The underlying wart is real and is logged rather than fixed - see BL-018. Verified one at a time pipeline_check 43/43 new launcher_check 58/58 stepper_check 70/70 url_state 23/23 the dashboard's new flag state did not disturb it aggregates 16/16 browser_check 71/71 a11y 22/22 autosave 34/34 f_items F1-F5 FIXED, F6 REPRODUCES (T7.2) No colour literal added: still 0 across all page sheets and inline blocks. The four cells are told apart by a label, a sentence and an accent - three channels, so colour is not carrying it alone (C1). Raised, not fixed BL-018 The WP tab's gate is the last localStorage-derived status in the app. T4.1 moved the launcher's card to the server; the wizard page still decides gate-or-creator from wp_suite_sop_complete plus a state blob. pullProject refreshes both on load so a connected user is fine, but the two answers come from different places and the fallback is silent. Includes a second, sharper edge: project-data.js:210 writes that flag for ANY row returned, including one with no `state` to restore - so the flag is written and never read consistently. T7.1 owns it. Question for the PR, per CLAUDE.md: the strip counts Overdue against `data.due`, which is free text today. CR-004/CR-018 restructure location but not dates. If "overdue" is going to drive anything beyond a launcher tile, that field needs a type. Co-Authored-By: Claude Opus 5 (1M context) --- docs/reference/file-map.md | 6 + docs/waves/backlog.md | 26 +++ html/index.html | 108 ++++++++++ html/work-package-suite-app.js | 17 +- html/wp-creation-app.js | 47 ++++- tests/pipeline_check.py | 371 +++++++++++++++++++++++++++++++++ 6 files changed, 569 insertions(+), 6 deletions(-) create mode 100644 tests/pipeline_check.py diff --git a/docs/reference/file-map.md b/docs/reference/file-map.md index ae0da09..a2d7149 100644 --- a/docs/reference/file-map.md +++ b/docs/reference/file-map.md @@ -225,8 +225,14 @@ Wave 5 added more, for the same reason: ```bash python tests/stepper_check.py # A4/S9 — ten real buttons, keyboard operable 70 checks python tests/launcher_check.py # B3 — can a brand-new account get started? 58 checks +python tests/pipeline_check.py # B4 surface — server counts, shareable links 43 checks ``` +`pipeline_check.py` reads the dashboard's state out of the **iframe's DOM**, not its +globals: `dashFilter` and `currentView` are declared with `let` in a classic script, so +they are not properties of `window` and a cross-frame read of either comes back +`undefined` — which is indistinguishable from a filter that never applied. + `launcher_check.py` is the only probe that runs itself in **two subprocesses**, and both reasons are worth knowing before writing a third: diff --git a/docs/waves/backlog.md b/docs/waves/backlog.md index c607536..bbb5969 100644 --- a/docs/waves/backlog.md +++ b/docs/waves/backlog.md @@ -298,6 +298,32 @@ deliberately deferred. a comment-stripped figure alongside the raw one and state both. Wave 9 sets the target against the stripped figure. +### BL-018 — The Work Package tab's gate is the last localStorage-derived status + +- **Found during:** T5.3 +- **Where:** `html/work-package-suite-app.js` — `restoreSavedSOP()` sets `sopComplete`, + `renderWPTab()` shows the gate or the creator on it +- **What:** `T4.1` moved the *launcher's* SOP status onto `/api/projects/{id}/summary`, and + `aggregates_check.py` proves the card reports the server's answer over a lying cache. The + SOP **wizard page** still decides whether to show the creator or the "complete the SOP + Configuration first" gate from `localStorage.wp_suite_sop_complete` plus a `wp_suite_state` + blob. `ProjectData.pullProject()` refreshes both from the server on load, so a signed-in + user with a working connection is fine — but the two answers come from different places, + and the fallback is silent rather than an error state, which is the shape `B4` objects to. +- **Also:** `project-data.js:210` writes `wp_suite_sop_complete = '1'` whenever + `/api/sops/latest` returns any row, including one whose `data` carries no `state`. The + wizard then holds a browser that believes the SOP is complete and has nothing to restore, + so `restoreSavedSOP()` bails and `sopComplete` stays false — the flag is written and never + read consistently. Found because `browser_check.py`'s fixture seeds exactly that shape, and + a pipeline-strip link consequently landed on the gate. `pipeline_check.py` seeds the + production shape (`{sop, state}`) instead. +- **Why not now:** `T5.3` is the strip. Changing which source the WP gate trusts changes what + the SOP wizard does when offline, and `B7`/`T7.1` dissolves that iframe and rewrites this + hand-off wholesale. +- **Suggested wave or follow-up:** `T7.1`, or wave 9 with `C2` if the gate survives the + rebuild unchanged. Either way `browser_check.py`'s fixture should adopt the `{sop, state}` + shape so it stops being the only place this discrepancy is visible. + ### BL-014 — Four controls fall back to the browser's default focus ring - **Found during:** T3.4 diff --git a/html/index.html b/html/index.html index 742fceb..96ba243 100644 --- a/html/index.html +++ b/html/index.html @@ -317,6 +317,39 @@ .field-error { color: var(--cds-text-error); font-size: 12px; font-weight: 600; margin-top: 0.3rem; } .field-error:empty { display: none; } + /* PIPELINE STRIP — B4 surface (T5.3) + Four counts, every one of them from /api/wps/metrics. There is deliberately + no localStorage fallback anywhere in here: a per-browser number that looks + authoritative is the thing B4 removes, and a stale four beside a live four + is worse than an error. */ + .pipeline { margin-bottom: 1.5rem; } + .pipeline-head { font-size: 13px; font-weight: 600; text-transform: none; + color: var(--cds-text-secondary); margin-bottom: 0.6rem; } + .pipeline-strip { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 1px; + background: var(--cds-border-subtle); border: 1px solid var(--cds-border-subtle); } + .pipe-cell { display: flex; flex-direction: column; gap: 0.15rem; padding: 1rem 1.1rem; + background: var(--cds-layer); text-decoration: none; color: var(--cds-text-primary); + border-left: 3px solid transparent; transition: background 0.15s; } + .pipe-cell:hover { background: var(--cds-layer-hover); } + .pipe-num { font-size: 1.75rem; font-weight: 300; line-height: 1.1; } + .pipe-label { font-size: 13px; font-weight: 600; } + .pipe-sub { font-size: 12px; color: var(--cds-text-secondary); } + /* Each cell's accent says which slice it is, and the label says it in words — + the number alone is the same shape in all four (C1). */ + .pipe-cell.is-total { border-left-color: var(--cds-interactive-01); } + .pipe-cell.is-ready { border-left-color: var(--cds-support-success); } + .pipe-cell.is-onhold { border-left-color: var(--wp-status-warning-text); } + .pipe-cell.is-overdue { border-left-color: var(--cds-support-error); } + /* Zero is a real answer for one slice of a project that has work in it. Four + zeros on a project with no work packages at all is not a reading, it is a + broken-looking strip, so that case gets a sentence instead. */ + .pipe-empty, .pipe-error { grid-column: 1 / -1; background: var(--cds-layer); + padding: 1.1rem 1.2rem; font-size: 13px; color: var(--cds-text-secondary); } + .pipe-error { color: var(--wp-status-warning-text); } + .pipe-empty a { color: var(--cds-link-primary); } + .pipe-loading { grid-column: 1 / -1; background: var(--cds-layer); padding: 1.1rem 1.2rem; + font-size: 13px; color: var(--cds-text-secondary); font-style: italic; } + /* FIRST RUN A brand-new account has no projects, so it has no tool cards either — this is the whole page for that person, and it has to say what to do next. */ @@ -437,6 +470,16 @@ + + +