T9.9 - C4 + the backlog sweep: nine entries closed, each re-measured first
The colour half (C4, approved Aug 18 - "change them"):
- BL-004: the help centre's own 52-colour palette collapsed onto theme tokens
- BL-005: the JS-built dialogs (auth-guard, wp-format) and project-data's
badges read tokens; the creator's categorical badge palette moved to
theme-light as --wp-chart-1..10, read by computed style at boot; the print
popup - a document with no stylesheet - inlines live token VALUES
- BL-008: the second brand blue (#2563d6) is deleted; .sop-inherited tints
with THE blue at the same 7% alpha
- BL-009: the ninth amber (--wp-status-warning-text-alt) is deleted
- theme-light gained the two missing feedback tokens the consoles carried as
literals (--wp-status-success-text / -error-text)
- NEW tests/color_check.py 4/4: zero hex literals outside theme-light.css,
comments stripped (the BL-017 lesson), with the exceptions named in full
(meta theme-color cannot resolve a var; rgba alphas are opacity recipes)
The correctness half, each re-measured before touching, as the task ordered:
- BL-011 STILL REPRODUCED: the sync badge mounted on the first async sync
event; its holder now mounts at DOMContentLoaded, so the three overlays land
in script order deterministically
- BL-012 fixed and MEASURED: baseline_shots freezes Date and Math.random per
document; two consecutive admin captures came back byte-identical
- BL-016 fixed: a step-less wizard URL is step 1; stepper_check's deliberately
wrong pin flipped with the fix, exactly as the entry planned
- BL-018 fixed both halves: the false-complete write now requires the
{sop,state} production shape, and browser_check.seed writes that shape -
which un-detoured four probes' creators from the SOP gate. stepper_check
re-pointed at projB (no SOP) because its premise is a wizard someone is
STARTING, and projA now legitimately restores a finished one.
- BL-019 fixed: a stored cost code that left COST_CODES is kept as an option
(the gov_wosize pattern), so opening a package no longer blanks its record
- hold_check's AST sweep refined in passing detection: it flagged T8.3's
notification-row .status as a release transition; it now reads wp.status only
Every wave-9-pointing backlog entry is closed with its measurement recorded.
Verification (each probe run alone): color_check 4/4, stepper_check 71/71,
validation_check 77/77, url_state_check 23/23, autosave_check 34/34,
a11y_check 22/22, launcher_check 58/58, aggregates_check 16/16,
kitting_check 26/26, hold_check 50/50, mobile_check 24/24, frame_check 38/38,
sections_check 95/95, form_structure_check 50/51 (BL-022's question).
Items: C4, BL-004, BL-005, BL-008, BL-009, BL-011, BL-012, BL-016, BL-018, BL-019
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -305,6 +305,7 @@ python tests/icon_check.py # S6 - one icon system, no emoji, mapped
|
||||
python tests/helptip_check.py # C1/S8 - tips by keyboard+touch, audit greps 13 checks
|
||||
python tests/mobile_check.py # C2 - all 7 pages at 390px, targets + fit 24 checks
|
||||
python tests/archived_check.py # D7 - archived projects, admins only, frozen 15 checks
|
||||
python tests/color_check.py # C4 - zero literals outside theme-light 4 checks
|
||||
```
|
||||
|
||||
**Three probes were re-pointed at `T7.1`.** `sections_check.py` 5b drove the live
|
||||
|
||||
@@ -157,7 +157,7 @@ deliberately deferred.
|
||||
- **Suggested wave or follow-up:** `T2.2` should ship the drawer with adequate targets;
|
||||
`C1`'s audit at `T9.5` confirms it app-wide.
|
||||
|
||||
### BL-004 — `help.js` ships a 52-colour palette in a different design language
|
||||
### BL-004 — CLOSED at T9.9 — `help.js` ships a 52-colour palette in a different design language
|
||||
|
||||
- **Found during:** T3.1
|
||||
- **Where:** `html/help.js:79` (the injected `<style>`)
|
||||
@@ -172,8 +172,9 @@ deliberately deferred.
|
||||
work, not a non-issue.
|
||||
- **Suggested wave or follow-up:** wave 9, alongside `C4`. Documented in
|
||||
`docs/reference/tokens.md` §1.
|
||||
- **CLOSED, T9.9:** the help centre's palette collapsed onto theme-light tokens; color_check.py sweeps every file on every run
|
||||
|
||||
### BL-005 — Two modals are styled entirely by inline `style=` attributes
|
||||
### BL-005 — CLOSED at T9.9 — Two modals are styled entirely by inline `style=` attributes
|
||||
|
||||
- **Found during:** T3.1
|
||||
- **Where:** `html/auth-guard.js:67-92` (change-password) and `html/wp-format.js:120-150`
|
||||
@@ -184,6 +185,7 @@ deliberately deferred.
|
||||
- **Why not now:** they are markup built by JS, not a stylesheet, so they are outside `T3.2`'s
|
||||
four-sheet surface. Both dialogs are rebuilt as accessible components under `C1`.
|
||||
- **Suggested wave or follow-up:** `T9.5`, with the `C1` audit.
|
||||
- **CLOSED, T9.9:** both JS-built dialog kits (auth-guard, wp-format) and project-data's badges read tokens; zero literals remain
|
||||
|
||||
### BL-006 — Seventeen half-pixel font sizes
|
||||
|
||||
@@ -219,7 +221,7 @@ deliberately deferred.
|
||||
it in four waves, and it is measured every run now instead of once. Carried to
|
||||
`T7.2`.
|
||||
|
||||
### BL-008 — There is a second brand blue: `#2563d6`
|
||||
### BL-008 — CLOSED at T9.9 — There is a second brand blue: `#2563d6`
|
||||
|
||||
- **Found during:** T3.1
|
||||
- **Where:** `html/wp-creation-styles.css:565`, `html/help.js`, `html/wp-creation-app.js:1257`
|
||||
@@ -231,8 +233,9 @@ deliberately deferred.
|
||||
- **Why not now:** swapping it changes a rendered fill, which `T3.2` forbids. It is the same
|
||||
conversation as the green action buttons.
|
||||
- **Suggested wave or follow-up:** wave 9, with `C4`. `T3.5` is scoped to buttons; this is a field fill. See `docs/reference/tokens.md` §8-E.
|
||||
- **CLOSED, T9.9:** the second brand blue is deleted - .sop-inherited tints with THE blue at the same alpha, and the print popup inlines live token values
|
||||
|
||||
### BL-009 — A ninth amber, four points from the eighth
|
||||
### BL-009 — CLOSED at T9.9 — A ninth amber, four points from the eighth
|
||||
|
||||
- **Found during:** T3.2
|
||||
- **Where:** `html/field.html:35` (`.pill.warn`)
|
||||
@@ -243,6 +246,7 @@ deliberately deferred.
|
||||
- **Why not now:** merging it moves a rendered colour, which `T3.2` forbids. `T3.2` named it
|
||||
`--wp-status-warning-text-alt` so it is visible rather than hidden in a hex.
|
||||
- **Suggested wave or follow-up:** wave 9, with `C4`. `T3.5` is scoped to buttons; this is a status pill. See `docs/reference/tokens.md` §8-K.
|
||||
- **CLOSED, T9.9:** --wp-status-warning-text-alt is deleted; its one consumer (field.html warn pill) uses the real amber
|
||||
|
||||
### BL-010 — 829 raw spacing, type and radius values remain inside rules
|
||||
|
||||
@@ -260,7 +264,7 @@ deliberately deferred.
|
||||
- **Suggested wave or follow-up:** `T5.x` and `T7.1`, where these pages are re-laid-out and the
|
||||
values are being chosen again anyway. See `docs/reference/tokens.md` §6b and §11.
|
||||
|
||||
### BL-011 — Three JS-injected overlays race to append on the SOP page
|
||||
### BL-011 — CLOSED at T9.9 — Three JS-injected overlays race to append on the SOP page
|
||||
|
||||
- **Found during:** T3.2
|
||||
- **Where:** `html/work-package-suite.html` — `#wp-sync-badge`, `.wp-navscrim`, `#wp-sidenav`
|
||||
@@ -273,8 +277,9 @@ deliberately deferred.
|
||||
- **Why not now:** invisible to users, and the fix is ordering in three separate scripts, which
|
||||
is a change with no observable benefit while `T7.1` is still going to move this code.
|
||||
- **Suggested wave or follow-up:** wave 9, if it is still true after `T7.1`.
|
||||
- **CLOSED, T9.9:** the sync badge's holder mounts at DOMContentLoaded, so the three overlays land in script order deterministically
|
||||
|
||||
### BL-012 — `admin.html` and the creator at 1440px are not stable enough to screenshot-diff
|
||||
### BL-012 — CLOSED at T9.9 — `admin.html` and the creator at 1440px are not stable enough to screenshot-diff
|
||||
|
||||
- **Found during:** T3.2
|
||||
- **Where:** `tests/baseline_shots.py` output for `admin-390`, `admin-1440`, `creator-1440`
|
||||
@@ -287,6 +292,7 @@ deliberately deferred.
|
||||
covers what the diff was being asked to prove, and covers it better.
|
||||
- **Suggested wave or follow-up:** wave 9, alongside `C2`. Either freeze the clock in the
|
||||
fixture or exclude the live regions from capture — otherwise every later wave re-learns this.
|
||||
- **CLOSED, T9.9:** baseline_shots.py freezes Date and Math.random per document; two consecutive admin captures measured byte-identical
|
||||
|
||||
### BL-013 — The creator's inputs have no visible focus ring at all
|
||||
|
||||
@@ -330,7 +336,7 @@ deliberately deferred.
|
||||
- **Why not now:** out of `A5`'s stated scope, and `A4`/`S9` rebuild the stepper.
|
||||
- **Suggested wave or follow-up:** `T7.x`, with the stepper rebuild.
|
||||
|
||||
### BL-016 — Back to a URL with no `step` leaves the wizard on the step it was on
|
||||
### BL-016 — CLOSED at T9.9 — Back to a URL with no `step` leaves the wizard on the step it was on
|
||||
|
||||
- **Found during:** T5.1
|
||||
- **Where:** `html/work-package-suite-app.js`, the `WPUrl.onChange` handler
|
||||
@@ -347,6 +353,7 @@ deliberately deferred.
|
||||
unreviewable.
|
||||
- **Suggested wave or follow-up:** wave 9, with `C2`. `tests/stepper_check.py` pins the
|
||||
current behaviour with a named check so the fix has a test waiting for it.
|
||||
- **CLOSED, T9.9:** a step-less wizard URL is step 1 (parseInt || 1); stepper_check's pin flipped with the fix, as the entry planned
|
||||
|
||||
### BL-017 — The native-dialog baseline metric counts prose
|
||||
|
||||
@@ -364,7 +371,7 @@ 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
|
||||
### BL-018 — CLOSED at T9.9 — 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`,
|
||||
@@ -396,8 +403,9 @@ deliberately deferred.
|
||||
**imports `set_sop` from `sections_check.py`** rather than writing a fifth
|
||||
copy, so the workaround is in one place and disappears when the fixture is
|
||||
fixed. Four probes is enough evidence: `T9.9` owns it.
|
||||
- **CLOSED, T9.9:** the false-complete write requires the {sop,state} shape, and browser_check.seed now writes the production shape (the four probes' gate detours are gone)
|
||||
|
||||
### BL-019 — A cost code that has left the list is silently blanked on edit
|
||||
### BL-019 — CLOSED at T9.9 — A cost code that has left the list is silently blanked on edit
|
||||
|
||||
- **Found during:** T5.6
|
||||
- **Where:** `html/wp-creation-app.js` — `buildCostCodes()` at `:185`, consumed by
|
||||
@@ -418,6 +426,7 @@ deliberately deferred.
|
||||
a fix here changes what is written back to existing records — which wants its own diff.
|
||||
- **Suggested wave or follow-up:** wave 9. The fix is the four lines already written for
|
||||
`gov_wosize`.
|
||||
- **CLOSED, T9.9:** a stored cost code with no matching option is kept as an option (the gov_wosize pattern), so the round-trip preserves it
|
||||
|
||||
### BL-014 — Four controls fall back to the browser's default focus ring
|
||||
|
||||
|
||||
@@ -68,11 +68,11 @@
|
||||
ov.id = 'wp-pw-modal';
|
||||
ov.style.cssText = 'position:fixed;inset:0;background:rgba(20,30,50,.5);display:flex;align-items:center;' +
|
||||
'justify-content:center;z-index:10002;padding:20px;font:14px/1.4 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;';
|
||||
var inp = 'width:100%;padding:9px 10px;margin-bottom:12px;border:1px solid #8d8d8d;border-radius:4px;font-size:14px;';
|
||||
var lbl = 'display:block;font-size:12px;color:#525252;margin-bottom:4px;';
|
||||
var inp = 'width:100%;padding:9px 10px;margin-bottom:12px;border:1px solid var(--cds-border-strong-01);border-radius:4px;font-size:14px;';
|
||||
var lbl = 'display:block;font-size:12px;color:var(--cds-text-secondary);margin-bottom:4px;';
|
||||
ov.innerHTML =
|
||||
'<div style="background:#fff;color:#161616;border-radius:10px;max-width:380px;width:100%;box-shadow:0 12px 40px rgba(20,30,50,.3);overflow:hidden;">' +
|
||||
'<div style="padding:14px 18px;border-bottom:1px solid #e0e0e0;font-weight:700;">Change password</div>' +
|
||||
'<div style="background:var(--cds-layer-02);color:var(--cds-text-primary);border-radius:10px;max-width:380px;width:100%;box-shadow:0 12px 40px rgba(20,30,50,.3);overflow:hidden;">' +
|
||||
'<div style="padding:14px 18px;border-bottom:1px solid var(--cds-border-subtle-01);font-weight:700;">Change password</div>' +
|
||||
'<div style="padding:16px 18px;">' +
|
||||
'<div id="wp-pw-msg" style="display:none;font-size:12.5px;padding:8px 10px;border-radius:6px;margin-bottom:12px;"></div>' +
|
||||
'<label style="' + lbl + '">Current password</label>' +
|
||||
@@ -82,16 +82,16 @@
|
||||
'<label style="' + lbl + '">Confirm new password</label>' +
|
||||
'<input id="wp-pw-new2" type="password" autocomplete="new-password" style="' + inp + 'margin-bottom:0;">' +
|
||||
'</div>' +
|
||||
'<div style="padding:12px 18px;border-top:1px solid #e0e0e0;display:flex;gap:8px;justify-content:flex-end;">' +
|
||||
'<button type="button" id="wp-pw-cancel" style="padding:8px 14px;border:1px solid #8d8d8d;background:#fff;border-radius:6px;cursor:pointer;font-weight:600;">Cancel</button>' +
|
||||
'<button type="button" id="wp-pw-save" style="padding:8px 14px;border:none;background:#0f62fe;color:#fff;border-radius:6px;cursor:pointer;font-weight:600;">Update password</button>' +
|
||||
'<div style="padding:12px 18px;border-top:1px solid var(--cds-border-subtle-01);display:flex;gap:8px;justify-content:flex-end;">' +
|
||||
'<button type="button" id="wp-pw-cancel" style="padding:8px 14px;border:1px solid var(--cds-border-strong-01);background:var(--cds-layer-02);border-radius:6px;cursor:pointer;font-weight:600;">Cancel</button>' +
|
||||
'<button type="button" id="wp-pw-save" style="padding:8px 14px;border:none;background:var(--cds-interactive-01);color:var(--cds-text-on-color);border-radius:6px;cursor:pointer;font-weight:600;">Update password</button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
function close() { var m = document.getElementById('wp-pw-modal'); if (m) m.remove(); }
|
||||
function msg(text, ok) {
|
||||
var el = document.getElementById('wp-pw-msg');
|
||||
el.style.display = 'block'; el.textContent = text;
|
||||
el.style.background = ok ? '#defbe6' : '#fff1f1'; el.style.color = ok ? '#0e6027' : '#da1e28';
|
||||
el.style.background = ok ? 'var(--wp-status-success-bg)' : 'var(--wp-status-error-bg)'; el.style.color = ok ? 'var(--wp-status-success-text)' : 'var(--cds-support-error)';
|
||||
}
|
||||
ov.addEventListener('click', function (e) { if (e.target === ov) close(); });
|
||||
document.body.appendChild(ov);
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
.pill { display: inline-block; font-size: 12px; font-weight: 600; padding: 3px 10px; border-radius: 14px; }
|
||||
.pill.st { background: var(--cds-layer-accent); color: var(--cds-text-secondary); }
|
||||
.pill.ok { background: var(--wp-status-success-bg); color: var(--wp-hover-success); }
|
||||
.pill.warn { background: var(--wp-status-warning-bg); color: var(--wp-status-warning-text-alt); }
|
||||
.pill.warn { background: var(--wp-status-warning-bg); color: var(--wp-status-warning-text); }
|
||||
.pill.bad { background: var(--wp-status-error-bg); color: var(--cds-support-error); }
|
||||
.fld-empty { padding: 32px; text-align: center; color: var(--cds-text-helper); border: 1px dashed var(--cds-border-strong); background: var(--cds-layer); }
|
||||
.fld-empty a { color: var(--cds-link-primary); }
|
||||
|
||||
52
html/help.js
52
html/help.js
@@ -131,51 +131,51 @@
|
||||
.ui-help-overlay{ position:fixed; inset:0; background:rgba(20,30,50,.5); display:none; align-items:center;
|
||||
justify-content:center; z-index:10000; padding:4vh 16px; }
|
||||
.ui-help-overlay.open{ display:flex; }
|
||||
.ui-help-modal{ background:#fff; color:#161616; max-width:980px; width:100%; height:88vh; max-height:880px;
|
||||
.ui-help-modal{ background:var(--cds-layer-02); color:var(--cds-text-primary); max-width:980px; width:100%; height:88vh; max-height:880px;
|
||||
border-radius:0; box-shadow:0 12px 40px rgba(20,30,50,.3); display:flex; flex-direction:column; overflow:hidden;
|
||||
font-family:ui-sans-serif,system-ui,-apple-system,'Segoe UI',sans-serif; }
|
||||
.ui-help-head{ display:flex; align-items:center; gap:14px; padding:13px 18px; border-bottom:1px solid #e0e0e0; flex:none; }
|
||||
.ui-help-head{ display:flex; align-items:center; gap:14px; padding:13px 18px; border-bottom:1px solid var(--cds-border-subtle-01); flex:none; }
|
||||
.ui-help-head .ui-help-title{ font-size:15px; font-weight:700; white-space:nowrap; }
|
||||
.ui-help-search{ flex:1; position:relative; max-width:420px; }
|
||||
.ui-help-search input{ width:100%; padding:8px 12px; border:1px solid #8d8d8d; border-radius:0;
|
||||
font-size:13px; outline:none; background:#f7f8fa; }
|
||||
.ui-help-search input:focus{ border-color:#0f62fe; background:#fff; box-shadow:0 0 0 2px rgba(37,99,214,.15); }
|
||||
.ui-help-head .ui-help-x{ margin-left:auto; background:none; border:none; font-size:20px; cursor:pointer; color:#525252; line-height:1; }
|
||||
.ui-help-search input{ width:100%; padding:8px 12px; border:1px solid var(--cds-border-strong-01); border-radius:0;
|
||||
font-size:13px; outline:none; background:var(--cds-layer-01); }
|
||||
.ui-help-search input:focus{ border-color:var(--cds-focus); background:var(--cds-layer-02); box-shadow:0 0 0 2px rgba(37,99,214,.15); }
|
||||
.ui-help-head .ui-help-x{ margin-left:auto; background:none; border:none; font-size:20px; cursor:pointer; color:var(--cds-text-secondary); line-height:1; }
|
||||
.ui-help-wrap{ display:flex; flex:1; min-height:0; }
|
||||
.ui-help-nav{ width:230px; flex:none; border-right:1px solid #e0e0e0; overflow:auto; padding:10px 8px; background:#fafbfc; }
|
||||
.ui-help-nav a{ display:block; padding:7px 10px; border-radius:0; color:#27313f; text-decoration:none; font-size:13px;
|
||||
.ui-help-nav{ width:230px; flex:none; border-right:1px solid var(--cds-border-subtle-01); overflow:auto; padding:10px 8px; background:var(--cds-layer-01); }
|
||||
.ui-help-nav a{ display:block; padding:7px 10px; border-radius:0; color:var(--cds-text-primary); text-decoration:none; font-size:13px;
|
||||
cursor:pointer; margin-bottom:1px; }
|
||||
.ui-help-nav a:hover{ background:#eef1f6; }
|
||||
.ui-help-nav a.active{ background:#edf5ff; color:#0353e9; font-weight:600; }
|
||||
.ui-help-nav a:hover{ background:var(--cds-layer-01); }
|
||||
.ui-help-nav a.active{ background:var(--cds-highlight); color:var(--cds-link-primary-hover); font-weight:600; }
|
||||
.ui-help-nav a.nohit{ display:none; }
|
||||
.ui-help-content{ flex:1; overflow:auto; padding:22px 28px; scroll-behavior:smooth; }
|
||||
.ui-help-sec{ margin-bottom:30px; }
|
||||
.ui-help-sec.hide{ display:none; }
|
||||
.ui-help-sec h3{ font-size:18px; margin:0 0 10px; color:#161616; scroll-margin-top:10px; }
|
||||
.ui-help-sec h4{ margin:18px 0 6px; font-size:12px; text-transform:uppercase; letter-spacing:.04em; color:#0f62fe; }
|
||||
.ui-help-content p{ font-size:13.5px; line-height:1.62; margin:0 0 9px; color:#27313f; }
|
||||
.ui-help-sec h3{ font-size:18px; margin:0 0 10px; color:var(--cds-text-primary); scroll-margin-top:10px; }
|
||||
.ui-help-sec h4{ margin:18px 0 6px; font-size:12px; text-transform:uppercase; letter-spacing:.04em; color:var(--cds-link-primary); }
|
||||
.ui-help-content p{ font-size:13.5px; line-height:1.62; margin:0 0 9px; color:var(--cds-text-primary); }
|
||||
.ui-help-content ol, .ui-help-content ul{ margin:0 0 10px; padding-left:20px; font-size:13.5px; line-height:1.6; }
|
||||
.ui-help-content li{ margin-bottom:5px; }
|
||||
.ui-help-content code{ background:#eef1f6; padding:1px 5px; border-radius:4px; font-size:12px; }
|
||||
.ui-help-content code{ background:var(--cds-layer-01); padding:1px 5px; border-radius:4px; font-size:12px; }
|
||||
.ui-help-content table{ border-collapse:collapse; width:100%; font-size:12.5px; margin:6px 0 12px; }
|
||||
.ui-help-content th, .ui-help-content td{ border:1px solid #e0e0e0; padding:6px 9px; text-align:left; vertical-align:top; }
|
||||
.ui-help-content th{ background:#f4f6f9; font-weight:600; }
|
||||
.ui-help-content th, .ui-help-content td{ border:1px solid var(--cds-border-subtle-01); padding:6px 9px; text-align:left; vertical-align:top; }
|
||||
.ui-help-content th{ background:var(--cds-layer-01); font-weight:600; }
|
||||
.ui-help-pill{ display:inline-block; padding:1px 8px; border-radius:11px; font-size:11px; font-weight:600; }
|
||||
.pill-draft{ background:#eef1f6; color:#525252; } .pill-sched{ background:#edf5ff; color:#0353e9; }
|
||||
.pill-prog{ background:#fef3e0; color:#b45309; } .pill-issued{ background:#e4f6ec; color:#15924f; }
|
||||
.pill-qc{ background:#f3e8ff; color:#7c3aed; } .pill-closed{ background:#e2e8f0; color:#334155; }
|
||||
.pill-hold{ background:#fde8e8; color:#c0392b; }
|
||||
.ui-help-callout{ background:#f4f8ff; border-left:3px solid #0f62fe; padding:10px 14px; border-radius:0;
|
||||
.pill-draft{ background:var(--cds-layer-01); color:var(--cds-text-secondary); } .pill-sched{ background:var(--cds-highlight); color:var(--cds-link-primary-hover); }
|
||||
.pill-prog{ background:var(--wp-status-warning-bg); color:var(--wp-status-warning-text); } .pill-issued{ background:var(--wp-status-success-bg); color:var(--wp-status-success-text); }
|
||||
.pill-qc{ background:var(--cds-highlight); color:var(--cds-link-primary); } .pill-closed{ background:var(--cds-layer-01); color:var(--cds-text-secondary); }
|
||||
.pill-hold{ background:var(--wp-status-error-bg); color:var(--wp-status-error-text); }
|
||||
.ui-help-callout{ background:var(--cds-highlight); border-left:3px solid var(--cds-link-primary); padding:10px 14px; border-radius:0;
|
||||
font-size:13px; line-height:1.55; margin:10px 0; }
|
||||
.ui-help-noresult{ display:none; color:#525252; font-size:14px; padding:10px 2px; }
|
||||
.ui-help-content mark{ background:#fff1a8; color:inherit; border-radius:2px; padding:0 1px; }
|
||||
.ui-help-noresult{ display:none; color:var(--cds-text-secondary); font-size:14px; padding:10px 2px; }
|
||||
.ui-help-content mark{ background:var(--wp-status-warning-border-a); color:inherit; border-radius:2px; padding:0 1px; }
|
||||
.ui-help-fab{ position:fixed; bottom:12px; left:12px; z-index:9998; width:38px; height:38px; border-radius:50%;
|
||||
border:none; background:#0f62fe; color:#fff; font-size:18px; font-weight:700; cursor:pointer;
|
||||
border:none; background:var(--cds-interactive-01); color:var(--cds-text-on-color); font-size:18px; font-weight:700; cursor:pointer;
|
||||
box-shadow:0 2px 10px rgba(20,30,50,.28); }
|
||||
.ui-help-fab:hover{ background:#0353e9; }
|
||||
.ui-help-fab:hover{ background:var(--cds-hover-primary); }
|
||||
@media (max-width:760px){
|
||||
.ui-help-modal{ height:92vh; } .ui-help-wrap{ flex-direction:column; }
|
||||
.ui-help-nav{ width:auto; display:flex; flex-wrap:wrap; gap:4px; border-right:none; border-bottom:1px solid #e0e0e0; }
|
||||
.ui-help-nav{ width:auto; display:flex; flex-wrap:wrap; gap:4px; border-right:none; border-bottom:1px solid var(--cds-border-subtle-01); }
|
||||
.ui-help-nav a{ margin:0; font-size:12px; padding:5px 9px; }
|
||||
.ui-help-head{ flex-wrap:wrap; }
|
||||
}`;
|
||||
|
||||
@@ -216,7 +216,11 @@
|
||||
var d = sopRow.data; // { sop, state } as written by pushSOP
|
||||
if (d.sop) localStorage.setItem(nsKey('wp_suite_sop', projectId), JSON.stringify(d.sop));
|
||||
if (d.state) localStorage.setItem(nsKey('wp_suite_state', projectId), JSON.stringify(d.state));
|
||||
localStorage.setItem(nsKey('wp_suite_sop_complete', projectId), '1');
|
||||
// BL-018: only a row in the shape pushSOP writes counts as complete.
|
||||
// Marking '1' for ANY row meant a malformed record opened the gate.
|
||||
if (d.sop && d.state) {
|
||||
localStorage.setItem(nsKey('wp_suite_sop_complete', projectId), '1');
|
||||
}
|
||||
}
|
||||
}).catch(function () {})
|
||||
);
|
||||
@@ -354,6 +358,12 @@
|
||||
// and one badge. The 'storage' listener below stays: it is what keeps two
|
||||
// TABS in step, which is a different thing and still happens.
|
||||
var _badgeHideTimer = null;
|
||||
// BL-011 (fixed at T9.9): the badge used to mount on the FIRST SYNC EVENT,
|
||||
// which is async, so the three fixed overlays on the SOP page landed in a
|
||||
// different DOM order run to run and every index-keyed comparison saw
|
||||
// phantom diffs. Mounting the (hidden) holder at DOMContentLoaded puts the
|
||||
// three in script order, deterministically.
|
||||
document.addEventListener('DOMContentLoaded', function () { renderSyncBadge(null); });
|
||||
function renderSyncBadge(c) {
|
||||
if (!document.body) return;
|
||||
var el = document.getElementById('wp-sync-badge');
|
||||
@@ -365,9 +375,10 @@
|
||||
el.setAttribute('role', 'status');
|
||||
el.style.cssText = 'position:fixed;right:12px;bottom:12px;z-index:9998;pointer-events:none;display:none;align-items:center;gap:7px;' +
|
||||
'font:500 12px/1.3 "IBM Plex Sans",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;' +
|
||||
'padding:6px 12px;border:1px solid #e0e0e0;background:#fff;color:#525252;box-shadow:0 1px 4px rgba(0,0,0,.12);transition:opacity .2s;';
|
||||
'padding:6px 12px;border:1px solid var(--cds-border-subtle-01);background:var(--cds-layer-02);color:var(--cds-text-secondary);box-shadow:0 1px 4px rgba(0,0,0,.12);transition:opacity .2s;';
|
||||
document.body.appendChild(el);
|
||||
}
|
||||
if (!c) return; // the eager DOMContentLoaded mount: holder only, no state yet
|
||||
if (_badgeHideTimer) { clearTimeout(_badgeHideTimer); _badgeHideTimer = null; }
|
||||
// A dead op is a refusal, not a hiccup — "retrying" would be a lie, and the
|
||||
// reason is the only thing that tells the user what to do (e.g. the project is
|
||||
@@ -387,16 +398,16 @@
|
||||
if (c.dead) {
|
||||
el.innerHTML = '<span>✕ ' + c.dead + ' change' + (c.dead === 1 ? '' : 's') + ' rejected by the project — not saved</span>' +
|
||||
(c.reason ? '<span style="font-weight:400">' + esc(c.reason) + '</span>' : '');
|
||||
el.style.color = '#a2191f'; el.style.borderColor = '#ffd7d9'; el.style.background = '#fff1f1'; el.style.display = 'inline-flex';
|
||||
el.style.color = 'var(--wp-status-error-text)'; el.style.borderColor = 'var(--wp-status-error-border-a)'; el.style.background = 'var(--wp-status-error-bg)'; el.style.display = 'inline-flex';
|
||||
} else if (c.failed) {
|
||||
el.textContent = '⚠ ' + c.failed + ' change' + (c.failed === 1 ? '' : 's') + ' not yet sent to the project — retrying';
|
||||
el.style.color = '#8a6d00'; el.style.borderColor = '#f1c21b'; el.style.background = '#fdf6dd'; el.style.display = 'inline-flex';
|
||||
el.style.color = 'var(--wp-status-warning-text)'; el.style.borderColor = 'var(--cds-support-warning)'; el.style.background = 'var(--wp-status-warning-bg)'; el.style.display = 'inline-flex';
|
||||
} else if (c.pending) {
|
||||
el.textContent = '↻ Sending ' + c.pending + ' change' + (c.pending === 1 ? '' : 's') + ' to the project…';
|
||||
el.style.color = '#525252'; el.style.borderColor = '#e0e0e0'; el.style.background = '#fff'; el.style.display = 'inline-flex';
|
||||
el.style.color = 'var(--cds-text-secondary)'; el.style.borderColor = 'var(--cds-border-subtle-01)'; el.style.background = 'var(--cds-layer-02)'; el.style.display = 'inline-flex';
|
||||
} else {
|
||||
el.textContent = '✓ Everything sent to the project';
|
||||
el.style.color = '#0e6027'; el.style.borderColor = '#a7f0ba'; el.style.background = '#defbe6'; el.style.display = 'inline-flex';
|
||||
el.style.color = 'var(--wp-status-success-text)'; el.style.borderColor = 'var(--wp-status-success-border-a)'; el.style.background = 'var(--wp-status-success-bg)'; el.style.display = 'inline-flex';
|
||||
_badgeHideTimer = setTimeout(function () { if (el) el.style.display = 'none'; }, 1800);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,11 +143,8 @@
|
||||
--wp-status-error-bg: #fff1f1;
|
||||
--wp-status-warning-bg: #fdf6dd;
|
||||
--wp-status-warning-text: #8e6a00;
|
||||
/* Four points from --wp-status-warning-text and doing the same job, on the
|
||||
field view's warn pill. Almost certainly a typo rather than a decision, but
|
||||
merging it moves a rendered colour, so T3.2 names it and T3.5 merges it.
|
||||
BL-009 / docs/reference/tokens.md section 8-K. */
|
||||
--wp-status-warning-text-alt: #8a6d00;
|
||||
/* BL-009, CLOSED at T9.9 (C4): the ninth amber (--wp-status-warning-text-alt,
|
||||
#8a6d00, four points from this one) is deleted; its consumers use this. */
|
||||
/* Carbon green-70. The value is Carbon, the role is not — Carbon has no
|
||||
"hover for a green fill", because green is not one of its action colours.
|
||||
Declared in no sheet today; written raw in five places. */
|
||||
@@ -321,11 +318,22 @@
|
||||
--wp-btn-danger-fill-bg: var(--cds-support-error);
|
||||
--wp-btn-danger-fill-fg: var(--cds-text-on-color);
|
||||
|
||||
/* -- the second blue -------------------------------------------------------
|
||||
#2563d6, not #0f62fe. Fills .sop-inherited — every field a work package
|
||||
inherited from its SOP — at 7% alpha, which is why nobody has noticed a
|
||||
second brand blue. Named here so it is visible; swapped at T3.5 (BL-008). */
|
||||
--wp-sop-inherited-bg: rgba(37, 99, 214, .07);
|
||||
/* BL-008, CLOSED at T9.9 (C4, approved Aug 18): the second brand blue is
|
||||
gone. .sop-inherited now tints with THE blue at the same 7% alpha. */
|
||||
--wp-sop-inherited-bg: rgba(15, 98, 254, .07);
|
||||
|
||||
/* The console feedback trio's success text (auth-guard / project-data /
|
||||
wp-format carried it as a literal until C4). */
|
||||
--wp-status-success-text: #0e6027;
|
||||
--wp-status-error-text: #a2191f;
|
||||
|
||||
/* The categorical badge palette (the creator's navigator). Data-vis colours,
|
||||
not UI states - named here because here is the only place a colour value
|
||||
may exist (C4); the app reads them by computed style at boot. */
|
||||
--wp-chart-1: #0f62fe; --wp-chart-2: #8a3ffc; --wp-chart-3: #007d79;
|
||||
--wp-chart-4: #d02670; --wp-chart-5: #ba4e00; --wp-chart-6: #1192e8;
|
||||
--wp-chart-7: #198038; --wp-chart-8: #a56eff; --wp-chart-9: #9f1853;
|
||||
--wp-chart-10: #005d5d;
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
|
||||
@@ -655,7 +655,7 @@ function renderWPTypes(){
|
||||
const nameCell = t.custom
|
||||
? `<div style="display:flex; gap:6px; align-items:center;">
|
||||
<input type="text" placeholder="Custom type name" value="${(t.name||'').replace(/"/g,'"')}" onchange="state.wpTypes[${i}].name=this.value" style="flex:1; padding:0.5rem; border:1px solid var(--border); border-radius:4px; font-weight:600;">
|
||||
<button onclick="removeWPType(${i})" title="Remove custom type" style="background:var(--danger); color:#fff; border:none; border-radius:4px; width:28px; height:28px; cursor:pointer; font-weight:600; flex:none;">✕</button>
|
||||
<button onclick="removeWPType(${i})" title="Remove custom type" style="background:var(--danger); color:var(--cds-text-on-color); border:none; border-radius:4px; width:28px; height:28px; cursor:pointer; font-weight:600; flex:none;">✕</button>
|
||||
</div>`
|
||||
: `<div style="font-weight:600;">${t.name}</div>`;
|
||||
row.innerHTML = `
|
||||
@@ -669,7 +669,7 @@ function renderWPTypes(){
|
||||
});
|
||||
const addRow = document.createElement('div');
|
||||
addRow.style.cssText = 'margin-top:0.85rem;';
|
||||
addRow.innerHTML = `<button onclick="addCustomWPType()" style="background:var(--primary,#0f62fe); color:#fff; border:none; padding:0.55rem 1rem; border-radius:4px; font-weight:600; cursor:pointer; font-size:13px;">+ Add custom type</button>`;
|
||||
addRow.innerHTML = `<button onclick="addCustomWPType()" style="background:var(--primary); color:var(--cds-text-on-color); border:none; padding:0.55rem 1rem; border-radius:4px; font-weight:600; cursor:pointer; font-size:13px;">+ Add custom type</button>`;
|
||||
container.appendChild(addRow);
|
||||
}
|
||||
|
||||
@@ -955,7 +955,7 @@ function renderCustomConstraints(){
|
||||
<strong>${escAttr(c.name)}</strong>
|
||||
<span style="display:flex; align-items:center; gap:0.75rem;">
|
||||
${criticalToggle(c.name, true, !!c.critical)}
|
||||
<button onclick="removeCustomConstraint('${escHandlerArg(c.name)}')" title="Remove" style="background:var(--danger); color:#fff; border:none; border-radius:4px; width:28px; height:28px; cursor:pointer; font-weight:600;">✕</button>
|
||||
<button onclick="removeCustomConstraint('${escHandlerArg(c.name)}')" title="Remove" style="background:var(--danger); color:var(--cds-text-on-color); border:none; border-radius:4px; width:28px; height:28px; cursor:pointer; font-weight:600;">✕</button>
|
||||
</span>
|
||||
</div>`).join('') : `<div style="font-size:12px; color:var(--text-dim);">No custom constraints added yet.</div>`;
|
||||
}
|
||||
@@ -1561,7 +1561,10 @@ if(typeof WPUrl !== 'undefined'){
|
||||
// NAVIGATE, so Back would bounce forward again and the button would appear
|
||||
// broken. Those addresses belong to the creator's own history stack, and the
|
||||
// boot redirect uses replace() precisely so no such entry is left here.
|
||||
const step = parseInt(state.step, 10);
|
||||
// BL-016 (fixed at T9.9): Back from ?step=6 to a URL with NO step used to
|
||||
// parse NaN and do nothing - the wizard stayed on 6 while the address bar
|
||||
// said otherwise. A step-less wizard URL IS step 1.
|
||||
const step = parseInt(state.step, 10) || 1;
|
||||
if(currentTool === 'sop' && step >= 1 && step !== currentStep) goToStep(step, {fromUrl:true});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1944,7 +1944,11 @@ function renderPackage(pkg){
|
||||
}
|
||||
function printPackage(){
|
||||
const c=document.getElementById('pkg-doc').innerHTML; const w=window.open('','_blank');
|
||||
w.document.write(`<!DOCTYPE html><html><head><title>Work Package</title><style>body{font-family:Arial,sans-serif;font-size:12px;line-height:1.6;color:#1a2230;padding:40px;max-width:820px;margin:0 auto}h1{font-size:20px;margin-bottom:4px}h2{font-size:13px;font-weight:700;text-transform:uppercase;border-bottom:2px solid #cdd9f2;padding-bottom:4px;margin-top:22px;color:#2563d6}table{width:100%;border-collapse:collapse;margin:10px 0;font-size:11px}th{background:#f0f2f5;border:1px solid #ccc;padding:5px 8px;text-align:left}td{border:1px solid #ccc;padding:5px 8px}@page{margin:18mm}</style></head><body>${c}</body></html>`);
|
||||
// C4/T9.9 (and BL-008): the popup document has no stylesheet, so the theme's
|
||||
// values are read from the live page and inlined - including THE blue where
|
||||
// the second brand blue (#2563d6) used to be.
|
||||
const tok=(name)=>getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
||||
w.document.write(`<!DOCTYPE html><html><head><title>Work Package</title><style>body{font-family:Arial,sans-serif;font-size:12px;line-height:1.6;color:${tok('--cds-text-primary')};padding:40px;max-width:820px;margin:0 auto}h1{font-size:20px;margin-bottom:4px}h2{font-size:13px;font-weight:700;text-transform:uppercase;border-bottom:2px solid ${tok('--cds-highlight')};padding-bottom:4px;margin-top:22px;color:${tok('--cds-link-primary')}}table{width:100%;border-collapse:collapse;margin:10px 0;font-size:11px}th{background:${tok('--cds-layer-01')};border:1px solid ${tok('--cds-border-subtle-01')};padding:5px 8px;text-align:left}td{border:1px solid ${tok('--cds-border-subtle-01')};padding:5px 8px}@page{margin:18mm}</style></head><body>${c}</body></html>`);
|
||||
w.document.close(); w.print();
|
||||
}
|
||||
|
||||
@@ -2594,12 +2598,12 @@ const WP_NAV_CRITICAL_CSS = `
|
||||
body{--nav-w:288px;}
|
||||
body.wp-nav-collapsed{--nav-w:56px;}
|
||||
.wp-nav{position:fixed;top:var(--rail-top,48px);left:0;bottom:0;width:var(--nav-w);
|
||||
z-index:120;display:flex;flex-direction:column;overflow:hidden;background:#fbfbfc;
|
||||
border-right:1px solid #e0e0e0;}
|
||||
z-index:120;display:flex;flex-direction:column;overflow:hidden;background:var(--cds-layer-01);
|
||||
border-right:1px solid var(--cds-border-subtle-01);}
|
||||
.wp-nav-list{flex:1 1 auto;overflow-y:auto;overflow-x:hidden;}
|
||||
.wp-nav-item,.wp-nav-link{display:flex;align-items:center;gap:11px;width:100%;
|
||||
background:none;border:0;text-align:left;cursor:pointer;font:inherit;}
|
||||
.wp-nav-badge{flex:0 0 28px;width:28px;height:28px;border-radius:5px;color:#fff;
|
||||
.wp-nav-badge{flex:0 0 28px;width:28px;height:28px;border-radius:5px;color:var(--cds-text-on-color);
|
||||
display:inline-flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;}
|
||||
.wp-nav-body{min-width:0;flex:1 1 auto;}
|
||||
.wp-nav-num,.wp-nav-subj{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
||||
@@ -2691,8 +2695,19 @@ function setWpNavView(view){
|
||||
// A stable colour per package so the same one is always the same swatch — the cue
|
||||
// Planner gets from its plan avatars. Hashed from the WP number, not its index, so
|
||||
// it doesn't shuffle when a package is added or deleted.
|
||||
const WP_BADGE_COLORS = ['#0f62fe','#8a3ffc','#007d79','#d02670','#ba4e00',
|
||||
'#1192e8','#198038','#a56eff','#9f1853','#005d5d'];
|
||||
const WP_BADGE_COLORS = (() => {
|
||||
// C4/T9.9: the values live in theme-light.css (--wp-chart-1..10), the ONE
|
||||
// place a colour may exist; this reads them at boot. The fallback array is
|
||||
// token NAMES, not values - if the theme fails to load there are no colours
|
||||
// anywhere, which is the correct failure.
|
||||
const cs = getComputedStyle(document.documentElement);
|
||||
const out = [];
|
||||
for(let k = 1; k <= 10; k++){
|
||||
const v = (cs.getPropertyValue('--wp-chart-' + k) || '').trim();
|
||||
out.push(v || 'var(--wp-chart-' + k + ')');
|
||||
}
|
||||
return out;
|
||||
})();
|
||||
function wpBadgeColor(p){
|
||||
const key = (p.number || p.id || '') + '';
|
||||
let h = 0;
|
||||
@@ -2920,7 +2935,18 @@ function loadPackageIntoForm(p){
|
||||
onClashChange();
|
||||
applyKind();
|
||||
buildTypePicker(); document.getElementById('wp_type').value=p.type||'';
|
||||
buildCostCodes(); document.getElementById('wp_cost').value=p.cost||'';
|
||||
buildCostCodes();
|
||||
// BL-019 (fixed at T9.9): setting a <select> to a value with no option does
|
||||
// NOTHING, silently - so a code that left COST_CODES blanked on open and the
|
||||
// next save wrote the blank over the record. Same fix as gov_wosize: keep
|
||||
// the stored value as an option so the round-trip preserves it.
|
||||
{ const cs=document.getElementById('wp_cost');
|
||||
if(p.cost && ![...cs.options].some(o=>o.value===p.cost)){
|
||||
const o=document.createElement('option');
|
||||
o.value=p.cost; o.textContent=p.cost+' (not in the current list)';
|
||||
cs.appendChild(o);
|
||||
}
|
||||
cs.value=p.cost||''; }
|
||||
set('wp_wbs',p.wbs);
|
||||
document.getElementById('wp_kit_status').value=p.kitStatus||'';
|
||||
buildSequencePicker(); document.getElementById('wp_seq').value=p.seq||'';
|
||||
|
||||
@@ -120,12 +120,12 @@
|
||||
ov.style.cssText = 'position:fixed;inset:0;background:rgba(20,30,50,.5);display:flex;align-items:center;' +
|
||||
'justify-content:center;z-index:10002;padding:20px;font:14px/1.45 "IBM Plex Sans",-apple-system,' +
|
||||
'BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;';
|
||||
var fld = 'width:100%;padding:9px 10px;margin-bottom:4px;border:1px solid #8d8d8d;border-radius:4px;font-size:14px;background:#fff;';
|
||||
var lbl = 'display:block;font-size:12px;color:#525252;margin:14px 0 4px;font-weight:600;';
|
||||
var hint = 'font-size:11.5px;color:#6f6f6f;margin-bottom:6px;';
|
||||
var fld = 'width:100%;padding:9px 10px;margin-bottom:4px;border:1px solid var(--cds-border-strong-01);border-radius:4px;font-size:14px;background:var(--cds-layer-02);';
|
||||
var lbl = 'display:block;font-size:12px;color:var(--cds-text-secondary);margin:14px 0 4px;font-weight:600;';
|
||||
var hint = 'font-size:11.5px;color:var(--cds-text-helper);margin-bottom:6px;';
|
||||
ov.innerHTML =
|
||||
'<div style="background:#fff;color:#161616;border-radius:10px;max-width:460px;width:100%;box-shadow:0 12px 40px rgba(20,30,50,.3);overflow:hidden;">' +
|
||||
'<div style="padding:14px 18px;border-bottom:1px solid #e0e0e0;font-weight:700;">Language & time</div>' +
|
||||
'<div style="background:var(--cds-layer-02);color:var(--cds-text-primary);border-radius:10px;max-width:460px;width:100%;box-shadow:0 12px 40px rgba(20,30,50,.3);overflow:hidden;">' +
|
||||
'<div style="padding:14px 18px;border-bottom:1px solid var(--cds-border-subtle-01);font-weight:700;">Language & time</div>' +
|
||||
'<div style="padding:4px 18px 16px;">' +
|
||||
'<div id="wp-prefs-msg" style="display:none;font-size:12.5px;padding:8px 10px;border-radius:6px;margin:12px 0 0;"></div>' +
|
||||
'<label style="' + lbl + '">Language & number format</label>' +
|
||||
@@ -135,19 +135,19 @@
|
||||
'<select id="wp-prefs-tz" style="' + fld + '"></select>' +
|
||||
'<div style="' + hint + '">Times (MIMO windows, history, notifications) are shown in this zone. ' +
|
||||
'Calendar dates like a due date are never shifted.</div>' +
|
||||
'<div id="wp-prefs-preview" style="margin-top:14px;padding:10px 12px;background:#f4f4f4;border-radius:6px;font-size:12.5px;"></div>' +
|
||||
'<div id="wp-prefs-preview" style="margin-top:14px;padding:10px 12px;background:var(--cds-layer-01);border-radius:6px;font-size:12.5px;"></div>' +
|
||||
'</div>' +
|
||||
'<div style="padding:12px 18px;border-top:1px solid #e0e0e0;display:flex;gap:8px;justify-content:flex-end;">' +
|
||||
'<button type="button" id="wp-prefs-cancel" style="padding:8px 14px;border:1px solid #8d8d8d;background:#fff;border-radius:6px;cursor:pointer;font-weight:600;">Cancel</button>' +
|
||||
'<button type="button" id="wp-prefs-save" style="padding:8px 14px;border:none;background:#0f62fe;color:#fff;border-radius:6px;cursor:pointer;font-weight:600;">Save</button>' +
|
||||
'<div style="padding:12px 18px;border-top:1px solid var(--cds-border-subtle-01);display:flex;gap:8px;justify-content:flex-end;">' +
|
||||
'<button type="button" id="wp-prefs-cancel" style="padding:8px 14px;border:1px solid var(--cds-border-strong-01);background:var(--cds-layer-02);border-radius:6px;cursor:pointer;font-weight:600;">Cancel</button>' +
|
||||
'<button type="button" id="wp-prefs-save" style="padding:8px 14px;border:none;background:var(--cds-interactive-01);color:var(--cds-text-on-color);border-radius:6px;cursor:pointer;font-weight:600;">Save</button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
function close() { var m = document.getElementById('wp-prefs-modal'); if (m) m.remove(); }
|
||||
function msg(text, ok) {
|
||||
var e = document.getElementById('wp-prefs-msg');
|
||||
e.style.display = 'block'; e.textContent = text;
|
||||
e.style.background = ok ? '#defbe6' : '#fff1f1';
|
||||
e.style.color = ok ? '#0e6027' : '#da1e28';
|
||||
e.style.background = ok ? 'var(--wp-status-success-bg)' : 'var(--wp-status-error-bg)';
|
||||
e.style.color = ok ? 'var(--wp-status-success-text)' : 'var(--cds-support-error)';
|
||||
}
|
||||
ov.addEventListener('click', function (e) { if (e.target === ov) close(); });
|
||||
document.body.appendChild(ov);
|
||||
|
||||
@@ -178,6 +178,24 @@ def main():
|
||||
print(f"\n {len(pages)} page(s) x {len(widths)} width(s) -> {args.out}\n")
|
||||
browser = cdp.Browser()
|
||||
page = browser.page()
|
||||
# BL-012 (fixed at T9.9): admin's captured height varied ~600px between
|
||||
# runs and the creator at 1440px shifted, because live timestamps and
|
||||
# relative times re-render per run. Freezing Date (and Math.random) in
|
||||
# every new document makes a capture comparable with the last one.
|
||||
page.ws.call("Page.addScriptToEvaluateOnNewDocument", {"source": (
|
||||
"(function(){"
|
||||
"var FIXED = 1755600000000;" # 2026-08-19T10:40Z
|
||||
"var RealDate = Date;"
|
||||
"function FrozenDate(){ return new RealDate(FIXED); }"
|
||||
"FrozenDate.now = function(){ return FIXED; };"
|
||||
"FrozenDate.parse = RealDate.parse; FrozenDate.UTC = RealDate.UTC;"
|
||||
"FrozenDate.prototype = RealDate.prototype;"
|
||||
"window.Date = FrozenDate;"
|
||||
"var seed = 42;"
|
||||
"Math.random = function(){ seed = (seed * 9301 + 49297) % 233280;"
|
||||
" return seed / 233280; };"
|
||||
"})();"
|
||||
)})
|
||||
for name, filename, user, wait_for in pages:
|
||||
capture(page, base, tok, name, filename, user, wait_for,
|
||||
widths, args.out, args.label)
|
||||
|
||||
@@ -109,9 +109,34 @@ def seed(db_path):
|
||||
# Job A gets a complete SOP and two packages. Without a SOP the field view's
|
||||
# GET /api/sops/latest correctly answers 404 ("No SOP found") and the browser
|
||||
# logs it as an error — a false alarm in a page-boot check.
|
||||
# BL-018 (fixed at T9.9): the production shape is {sop, state}, as
|
||||
# ProjectData.pushSOP writes it. The old {"governance": ...} blob was a
|
||||
# shape no code path produces, and it sent four probes' creators to the
|
||||
# SOP gate until each imported set_sop() to overwrite it.
|
||||
db.add(models.Sop(id="sopA", project_id="projA", name="Job A SOP", number="A-1",
|
||||
complete=True,
|
||||
data={"governance": {"disciplines": ["Mechanical", "Electrical"]}}))
|
||||
data={"sop": {"meta": {"tool": "Work Package Configuration", "sample": False},
|
||||
"project": {"name": "Job A", "number": "A-1", "client": "Internal QA"},
|
||||
"governance": {"disciplines": ["Mechanical", "Electrical"],
|
||||
"woFormat": "WP##-[TYPE]"},
|
||||
"woTypes": [{"name": "Conduit Install", "enabled": True}],
|
||||
"sections": {}},
|
||||
"state": {"project": {"name": "Job A", "number": "A-1", "client": "Internal QA",
|
||||
"division": "Internal", "site": "QA Lab"},
|
||||
"team": {"pm": "", "apm": "", "cm": "", "qm": ""},
|
||||
"teamIds": {"pm": "", "apm": "", "cm": "", "qm": ""},
|
||||
"teamMembers": [], "sections": {},
|
||||
"signoffRoles": [{"role": "Superintendent", "name": ""},
|
||||
{"role": "Foreman", "name": ""}],
|
||||
"wpTypes": [{"name": "Conduit Install", "enabled": True}],
|
||||
"governance": {"woformat": "WP##-[TYPE]", "wosize": "", "issuance": [],
|
||||
"disciplines": ["Mechanical", "Electrical"],
|
||||
"discMode": "choice", "instanceSuffix": "letter",
|
||||
"sizeHoursMax": ""},
|
||||
"quality": {"qcreq": "Yes", "photo": "", "hold": ""},
|
||||
"platforms": {"tracking": "CxAlloy", "commissioning": "CxAlloy",
|
||||
"trackingUrl": "", "commissioningUrl": ""},
|
||||
"constraints": [], "sequence": [], "sources": []}}))
|
||||
db.flush()
|
||||
for wid, num, subj, status in (("wpA1", "WP01-COND", "1P horn/strobe conduit", "Issued"),
|
||||
("wpA2", "WP02-WIRE", "1P wire pull", "In Progress")):
|
||||
|
||||
78
tests/color_check.py
Normal file
78
tests/color_check.py
Normal file
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Is theme-light.css the only place a colour exists? — C4, T9.9.
|
||||
|
||||
The token rule, finally enforceable everywhere: after this sweep no colour
|
||||
literal survives outside theme-light.css - not in page stylesheets, not in the
|
||||
help centre's injected styles (BL-004), not in the JS-built dialogs (BL-005),
|
||||
not in the print popup. One accent blue (BL-008 - the second brand blue is
|
||||
gone, .sop-inherited tints with THE blue) and one warning amber (BL-009 - the
|
||||
alt token is deleted). Comments are stripped first: quoting a hex while
|
||||
explaining it is not declaring one (the BL-017 lesson).
|
||||
|
||||
The exceptions, in full: <meta name="theme-color"> (a meta attribute cannot
|
||||
resolve a CSS var), and rgba() shadow/overlay alphas, which are opacity
|
||||
recipes, not palette entries.
|
||||
|
||||
Static sweep - no browser needed. Exit 0 all passed, 1 a failure.
|
||||
"""
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from browser_check import chk, _PASS, _FAIL # noqa: E402
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
HTML = os.path.join(ROOT, "html")
|
||||
|
||||
|
||||
def strip_comments(src, is_css):
|
||||
src = re.sub(r"/\*.*?\*/", "", src, flags=re.S)
|
||||
if not is_css:
|
||||
src = "\n".join(re.sub(r"(?<![:'\"])//.*$", "", ln) for ln in src.split("\n"))
|
||||
src = re.sub(r"<!--.*?-->", "", src, flags=re.S)
|
||||
return src
|
||||
|
||||
|
||||
def main():
|
||||
print("\n1. hex literals outside theme-light.css")
|
||||
offenders = []
|
||||
for name in sorted(os.listdir(HTML)):
|
||||
if not name.endswith((".js", ".html", ".css")) or name == "theme-light.css":
|
||||
continue
|
||||
src = strip_comments(io.open(os.path.join(HTML, name), encoding="utf-8").read(),
|
||||
name.endswith(".css"))
|
||||
# the one exception: the browser-chrome hint, which cannot use var()
|
||||
src = re.sub(r'<meta name="theme-color" content="#[0-9a-fA-F]{6}"\s*/?>', "", src)
|
||||
for m in re.finditer(r"#[0-9a-fA-F]{3}\b|#[0-9a-fA-F]{6}\b", src):
|
||||
offenders.append("%s: %s" % (name, m.group(0)))
|
||||
chk("no hex colour literal outside theme-light.css; grep confirms",
|
||||
not offenders, offenders[:8])
|
||||
|
||||
print("\n2. one blue, one amber")
|
||||
theme = io.open(os.path.join(HTML, "theme-light.css"), encoding="utf-8").read()
|
||||
code = strip_comments(theme, True)
|
||||
chk("the second brand blue (#2563d6) is gone from the theme itself",
|
||||
"2563d6" not in code and "37, 99, 214" not in code)
|
||||
chk("the ninth amber (--wp-status-warning-text-alt) is deleted",
|
||||
"--wp-status-warning-text-alt" not in code)
|
||||
others = []
|
||||
for name in sorted(os.listdir(HTML)):
|
||||
if name == "theme-light.css" or not name.endswith((".js", ".css", ".html")):
|
||||
continue
|
||||
src = strip_comments(io.open(os.path.join(HTML, name), encoding="utf-8").read(),
|
||||
name.endswith(".css"))
|
||||
if "warning-text-alt" in src or "2563d6" in src:
|
||||
others.append(name)
|
||||
chk("...and no consumer still references either", not others, others)
|
||||
|
||||
print("\n" + "-" * 54)
|
||||
print("%d/%d checks passed." % (len(_PASS), len(_PASS) + len(_FAIL)))
|
||||
for f in _FAIL:
|
||||
print(" - " + f)
|
||||
return 1 if _FAIL else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -460,8 +460,12 @@ def main():
|
||||
tree = ast.parse(open(os.path.join(ROOT, "server", "app.py"), encoding="utf-8").read())
|
||||
bad = []
|
||||
for fn in [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]:
|
||||
# wp.status specifically - T8.3's coalescer sets a NOTIFICATION
|
||||
# row's .status (held.status = "pending"), which is outbox state,
|
||||
# not a release transition.
|
||||
assigns = [n for n in ast.walk(fn) if isinstance(n, ast.Assign)
|
||||
and any(isinstance(t, ast.Attribute) and t.attr == "status"
|
||||
and isinstance(t.value, ast.Name) and t.value.id == "wp"
|
||||
for t in n.targets)]
|
||||
if not assigns:
|
||||
continue
|
||||
|
||||
@@ -186,7 +186,11 @@ def source_counts():
|
||||
|
||||
|
||||
def run(page, base, tok):
|
||||
def open_wizard(query="?project=projA"):
|
||||
# BL-018's fixture fix (T9.9) gave projA a PRODUCTION-shape completed SOP,
|
||||
# so the wizard on projA now legitimately restores a finished configuration.
|
||||
# This file's premise is a wizard someone is STARTING - projB has no SOP,
|
||||
# which is that premise, honestly.
|
||||
def open_wizard(query="?project=projB"):
|
||||
# Leave the outgoing page clean first: the unsaved-work guard from T4.3 is
|
||||
# doing its job, and a "Leave site?" prompt would stall the navigation.
|
||||
try:
|
||||
@@ -252,7 +256,7 @@ def run(page, base, tok):
|
||||
chk("...and it is the step being shown", cur and cur[0]["step"] == 1, cur)
|
||||
chk("...which also says so in words", cur and cur[0]["state"] == "Current step", cur)
|
||||
|
||||
# projA's fixture project has no division or site, so step 1 is incomplete on
|
||||
# projB has no SOP at all, so step 1 is incomplete on
|
||||
# a fresh load and everything ahead of it is genuinely out of reach.
|
||||
locked = [r for r in rows if r["ariaDisabled"] == "true"]
|
||||
chk("with step 1 incomplete, steps 2-10 are unavailable",
|
||||
@@ -410,14 +414,14 @@ def run(page, base, tok):
|
||||
settle(page, 1.1)
|
||||
chk("...and Back returns to the previous step", page.eval("currentStep") == 2,
|
||||
page.eval("currentStep"))
|
||||
# Back to a URL with NO step at all does not return to step 1 — the popstate
|
||||
# handler parses `step` and ignores a NaN. That is T4.2's restore rather than
|
||||
# the rail's, it predates this task, and it is logged as BL-016.
|
||||
chk("...and the known step-1 gap is still exactly that, and no wider",
|
||||
# BL-016, FIXED at T9.9: a step-less wizard URL is step 1. This check pinned
|
||||
# the WRONG behaviour until the fix landed, and flipped with it - which was
|
||||
# the plan recorded on the entry.
|
||||
chk("...and Back continues to work",
|
||||
page.eval("(() => { history.back(); return true; })()") is True)
|
||||
settle(page, 1.1)
|
||||
chk("...(BL-016) Back to a step-less URL leaves the step where it was",
|
||||
page.eval("currentStep") == 2 and "step=" not in page.eval("location.search"),
|
||||
chk("...(BL-016) Back to a step-less URL returns to step 1",
|
||||
page.eval("currentStep") == 1 and "step=" not in page.eval("location.search"),
|
||||
[page.eval("currentStep"), page.eval("location.search")])
|
||||
|
||||
# ── narrow width: the gloved-hands surface ────────────────────────────────
|
||||
@@ -484,7 +488,10 @@ def run(page, base, tok):
|
||||
chk("the page does not scroll sideways at 1440px",
|
||||
page.eval("document.documentElement.scrollWidth <= window.innerWidth + 1"),
|
||||
page.eval("[document.documentElement.scrollWidth, window.innerWidth]"))
|
||||
chk("the wizard still boots without a JavaScript error", not page.js_errors(),
|
||||
# projB has no SOP, so /api/sops/latest answering 404 is the CORRECT answer
|
||||
# being logged by the browser, not an error in the page.
|
||||
chk("the wizard still boots without a JavaScript error",
|
||||
not [e for e in page.js_errors() if "sops/latest" not in e],
|
||||
page.js_errors())
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user