T3.2 - C3/S5: one source of truth for colour; page sheets alias only

theme-light.css is now the only file in html/ that contains a colour literal.
The five page stylesheets and all four inline <style> blocks declare names and
nothing else.

  theme-light.css                 191 declarations, 175 with a literal value
  console.css                      28 declarations,   0
  work-package-suite-styles.css    16 declarations,   0
  wp-chrome.css                    14 declarations,   0
  wp-creation-styles.css           24 declarations,   0
  wp-sidenav.css                    0 declarations,   0

#0f62fe is declared in one sheet, down from five. The eleven occurrences left
inside theme-light.css are Carbon's own v10-to-v11 alias layer, which the
inventory records as deliberate and not the S5 defect.

Names were kept, because 111 var() references live in .js files across 23 token
names and a rename there fails silently - no build error, no console warning,
just an unstyled element.

The rule the refactor was built on: consolidation is not unification. Where two
sheets declared the same value, they collapse. Where they declared DIFFERENT
values for one role - the two shadows, the eight status borders doing four jobs,
the three mono stacks - each value got its own canonical name and the pair is
recorded for T3.5. Picking a winner between two near-identical greys is a
rendered change, which this task forbids. The console's zebra stripe is the one
that would have bitten: #fafafa is six points from #f4f4f4, and merging them
erases the striping on the nine-column user table.

Collecting the one-offs in one place made two things countable that were not
before: twelve distinct shadows, and a ninth amber (#8a6d00 on the field view,
four points from #8e6a00 and doing the same job - BL-009).

VERIFICATION - the screenshot done-when could not do the job, so it was replaced.

Captured against wave 2, 11 of 14 shots were pixel-identical and 3 were not.
Capturing wave 2 against ITSELF produced the same 3 differences at the same
bounding box, so those shots cannot distinguish a regression from the clock.
Trap 2 in the brief is half wrong: users.html is stable at both widths; the
unstable third is the creator at 1440px, and admin's captured page height varies
by ~600px between runs (BL-012).

So tests/token_check.py was added. It checks what wave 3 actually claims: that
every custom property resolves to the same literal, and every element computes
the same colours, shadows and type. That is stronger than a screenshot - it
covers the hover, focus and disabled rules a screenshot never exercises, and it
is deterministic.

  wave 2 vs T3.2, all 7 pages:
  178/178 wave-2 token names resolve identically, +213 new
  3,500 elements compute identically, zero added, zero removed
  16 tokens differ in notation only (#fff -> #ffffff), which is the duplicate
  class this task existed to collapse

Two detours worth not repeating: the element walk was first keyed by sibling
index and reported 55 phantom differences on the SOP page, where three
JS-injected overlays append in whichever order their async work finishes
(BL-011); and the comparator now normalises notation before reporting, because
otherwise it fails on its own success.

f_items 5 FIXED / F6 REPRODUCES as expected. browser_check 71/71.

ONE DONE-WHEN NOT MET, recorded rather than skipped: "no page stylesheet
declares a raw color, spacing or type value". The colour half is met in full.
483 raw spacing values, 281 font-sizes and 65 radii remain inside rules, 492 of
them in the creator. That is arithmetic, not effort: the creator's spacing is
every integer from 1px to 14px, so no token exists that padding:9px 11px maps to
without changing one of the numbers - and this task forbids changing a rendered
value. The two requirements are mutually exclusive. Logged as BL-010 for T5.x
and T7.1, where those pages are re-laid-out and the values get chosen again.

New backlog: BL-009 (ninth amber), BL-010 (raw spacing/type in rules),
BL-011 (overlay append race), BL-012 (unstable screenshot targets).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-15 18:50:30 -05:00
parent 0e40e967a0
commit 9ab7b48de2
14 changed files with 964 additions and 266 deletions

View File

@@ -8,18 +8,36 @@
sheet should invent its own. Page-specific rules (per-ID scroll boxes, column
exceptions) stay in the page that owns them.
══ TOKENS ══════════════════════════════════════════════════════════════════ */
:root{ --bg:#f4f4f4; --surface:#fff; --border:#e0e0e0; --border-strong:#8d8d8d; --text:#161616;
--muted:#525252; --dim:#8d8d8d; --accent:#0f62fe; --accent-hover:#0353e9; --accent-soft:#edf5ff;
--green:#198038; --green-bg:#defbe6;
--red:#da1e28; --red-bg:#fff1f1; --amber:#8e6a00; --amber-bg:#fdf6dd;
--head-bg:#f4f4f4; --zebra:#fafafa; --row-hover:#eef0f2;
--mono:'IBM Plex Mono','Cascadia Mono',Consolas,monospace;
--s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:20px; --s6:28px;
--ctl:32px; /* every button / input / select that sits in a form row */
--ctl-sm:26px; } /* every control that sits inside a table cell */
══ TOKENS ══════════════════════════════════════════════════════════════════
Names only. Every value comes from theme-light.css, which every page loads
first — this sheet declares nothing of its own (T3.2 / S5 / C3). The names
stay because admin.js and users.js read 21 of them from JavaScript, where a
rename fails silently: no build error, no console warning, just an unstyled
element. See docs/reference/tokens.md section 9. */
:root{ --bg:var(--cds-background); --surface:var(--cds-layer); --border:var(--cds-border-subtle);
--border-strong:var(--cds-border-strong); --text:var(--cds-text-primary);
--muted:var(--cds-text-secondary); --dim:var(--cds-ui-04); --accent:var(--cds-interactive-01);
--accent-hover:var(--cds-hover-primary); --accent-soft:var(--cds-highlight);
--green:var(--cds-support-success); --green-bg:var(--wp-status-success-bg);
--red:var(--cds-support-error); --red-bg:var(--wp-status-error-bg);
--amber:var(--wp-status-warning-text); --amber-bg:var(--wp-status-warning-bg);
--head-bg:var(--cds-layer-accent);
/* --zebra is six points lighter than --head-bg, not equal to it: collapsing
the two erases the striping on a nine-column table, which is the one place
striping is load-bearing. --row-hover is deliberately NOT --accent-soft;
see the note above table.grid tbody tr:hover. Both are one-offs and both
are named in docs/reference/tokens.md section 8-A/8-B. */
--zebra:var(--wp-table-zebra); --row-hover:var(--wp-table-row-hover);
/* The narrow mono stack, kept as-is: widening it changes the rendered face on
any machine without IBM Plex Mono installed, and this sheet's two pages are
the ones a font change would show up on. T3.5 picks one stack (8-H). */
--mono:var(--wp-font-mono-2);
--s1:var(--wp-s1); --s2:var(--wp-s2); --s3:var(--wp-s3);
--s4:var(--wp-s4); --s5:var(--wp-s5); --s6:var(--wp-s6);
--ctl:var(--wp-ctl); /* every button / input / select that sits in a form row */
--ctl-sm:var(--wp-ctl-sm); } /* every control that sits inside a table cell */
*{ box-sizing:border-box; }
body{ margin:0; font-family:'IBM Plex Sans',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; background:var(--bg); color:var(--text); }
body{ margin:0; font-family:var(--wp-font-sans-2); background:var(--bg); color:var(--text); }
/* ══ PAGE ══════════════════════════════════════════════════════════════════════
1240px, not 860: the user table is nine columns wide and at 860 it spilled
@@ -46,12 +64,12 @@ a.home:hover{ text-decoration:underline; }
toolbar is one clean band instead of a ragged one. */
button{ font:inherit; font-size:13px; font-weight:600; line-height:1; white-space:nowrap;
height:var(--ctl); padding:0 var(--s3); border-radius:0; cursor:pointer;
border:1px solid var(--border-strong); background:#fff; color:var(--text); }
border:1px solid var(--border-strong); background:var(--surface); color:var(--text); }
button:hover{ border-color:var(--accent); color:var(--accent); }
button:focus-visible{ outline:2px solid var(--accent); outline-offset:-3px; }
button:disabled, button:disabled:hover{ color:var(--dim); border-color:var(--border); background:#fff; cursor:default; }
button.primary{ background:var(--accent); border-color:var(--accent); color:#fff; }
button.primary:hover{ background:var(--accent-hover); border-color:var(--accent-hover); color:#fff; }
button:disabled, button:disabled:hover{ color:var(--dim); border-color:var(--border); background:var(--surface); cursor:default; }
button.primary{ background:var(--accent); border-color:var(--accent); color:var(--cds-text-on-color); }
button.primary:hover{ background:var(--accent-hover); border-color:var(--accent-hover); color:var(--cds-text-on-color); }
button.danger{ border-color:var(--red); color:var(--red); }
button.danger:hover{ background:var(--red-bg); border-color:var(--red); color:var(--red); }
.row{ display:flex; gap:var(--s2); flex-wrap:wrap; align-items:center; }
@@ -64,7 +82,7 @@ button.danger:hover{ background:var(--red-bg); border-color:var(--red); color:va
.toolbar input:not([type=checkbox]), .toolbar select,
.urow input:not([type=checkbox]), .urow select{
height:var(--ctl); padding:0 var(--s2); font:inherit; font-size:13px; line-height:normal;
border:1px solid var(--border-strong); border-radius:0; background:#fff; color:var(--text); }
border:1px solid var(--border-strong); border-radius:0; background:var(--surface); color:var(--text); }
.toolbar select, .urow select{ cursor:pointer; padding-right:var(--s1); }
.toolbar input:focus-visible, .toolbar select:focus-visible,
.urow input:focus-visible, .urow select:focus-visible{ outline:2px solid var(--accent); outline-offset:-2px; }
@@ -79,17 +97,17 @@ button.danger:hover{ background:var(--red-bg); border-color:var(--red); color:va
.banner{ margin:var(--s3) 0 0; padding:9px var(--s3); border-radius:0; font-size:13px; font-weight:600;
line-height:1.4; border:1px solid var(--border); border-left:3px solid var(--border-strong);
background:var(--surface); color:var(--text); }
.banner.ok{ background:var(--green-bg); color:var(--green); border-color:#a7f0ba; border-left-color:var(--green); }
.banner.bad{ background:var(--red-bg); color:var(--red); border-color:#ffd7d9; border-left-color:var(--red); }
.banner.warn{ background:var(--amber-bg); color:var(--amber); border-color:#fddc69; border-left-color:var(--amber); }
.banner.ok{ background:var(--green-bg); color:var(--green); border-color:var(--wp-status-success-border-a); border-left-color:var(--green); }
.banner.bad{ background:var(--red-bg); color:var(--red); border-color:var(--wp-status-error-border-a); border-left-color:var(--red); }
.banner.warn{ background:var(--amber-bg); color:var(--amber); border-color:var(--wp-status-warning-border-a); border-left-color:var(--amber); }
/* --muted, not --dim: #8d8d8d on white is 3.3:1, under the 4.5:1 floor at 12px,
and the boxes the scripts fill are themselves .note — their primary toggle
labels inherit this colour. */
.note{ font-size:12px; line-height:1.55; color:var(--muted); margin-top:var(--s2); }
.note strong, .note em{ color:var(--text); }
pre.out{ background:#0f1525; color:#d7e0f5; border-radius:0; padding:var(--s3) var(--s4); font-family:var(--mono);
pre.out{ background:var(--wp-term-bg); color:var(--wp-term-fg); border-radius:0; padding:var(--s3) var(--s4); font-family:var(--mono);
font-size:12px; line-height:1.55; white-space:pre-wrap; max-height:340px; overflow:auto; margin:var(--s3) 0 0; }
pre.out .p{ color:#56d364; font-weight:700; } pre.out .f{ color:#ff7b72; font-weight:700; }
pre.out .p{ color:var(--wp-term-pass); font-weight:700; } pre.out .f{ color:var(--wp-term-fail); font-weight:700; }
table.kv{ border-collapse:collapse; font-size:13px; margin-top:var(--s2); }
table.kv th{ text-align:left; padding:var(--s1) var(--s5) var(--s1) 0; color:var(--muted); font-weight:600; white-space:nowrap; }
table.kv td{ padding:var(--s1) 0; font-variant-numeric:tabular-nums; font-weight:700; color:var(--text); }
@@ -135,15 +153,15 @@ button.mini{ height:var(--ctl-sm); padding:0 var(--s2); font-size:12px; }
table.grid td .chk, table.users td .chk{ height:var(--ctl-sm); }
select.role-select{ height:var(--ctl-sm); max-width:170px; padding:0 var(--s1) 0 var(--s2);
font:inherit; font-size:12px; border:1px solid var(--border-strong); border-radius:0;
background:#fff; color:var(--text); cursor:pointer; }
background:var(--surface); color:var(--text); cursor:pointer; }
select.role-select:hover{ border-color:var(--accent); }
select.role-select.is-admin{ color:var(--accent); border-color:var(--accent); font-weight:600; }
select.role-select:disabled{ color:var(--dim); border-color:var(--border); background:var(--bg); cursor:default; }
.tag{ display:inline-block; padding:1px 8px; border-radius:11px; font-size:11px; font-weight:600;
line-height:1.55; white-space:nowrap; vertical-align:middle; }
.tag.admin{ background:var(--accent-soft); color:var(--accent); }
.tag.super{ background:#e8daff; color:#6929c4; }
.tag.user{ background:#e8e8e8; color:var(--muted); }
.tag.super{ background:var(--wp-tag-super-bg); color:var(--wp-tag-super-text); }
.tag.user{ background:var(--cds-layer-hover); color:var(--muted); }
.tag.on{ background:var(--green-bg); color:var(--green); }
.tag.off{ background:var(--red-bg); color:var(--red); }
.tag.archived{ background:var(--amber-bg); color:var(--amber); }
@@ -155,10 +173,10 @@ select.role-select:disabled{ color:var(--dim); border-color:var(--border); backg
/* ══ MODALS ════════════════════════════════════════════════════════════════════
The project-access dialog, shared by both pages. */
.modal-ov{ position:fixed; inset:0; background:rgba(20,30,50,.5); display:flex; align-items:center;
.modal-ov{ position:fixed; inset:0; background:var(--wp-scrim-cool-modal); display:flex; align-items:center;
justify-content:center; z-index:10002; padding:var(--s5); }
.modal-box{ background:var(--surface); border-radius:0; max-width:660px; width:100%; max-height:82vh;
display:flex; flex-direction:column; overflow:hidden; box-shadow:0 12px 40px rgba(20,30,50,.3); }
display:flex; flex-direction:column; overflow:hidden; box-shadow:var(--wp-shadow-modal); }
.modal-head{ padding:var(--s3) var(--s4); border-bottom:1px solid var(--border); font-weight:700; }
.modal-body{ padding:var(--s3) var(--s4); overflow:auto; }
.modal-foot{ padding:var(--s3) var(--s4); border-top:1px solid var(--border);
@@ -171,7 +189,7 @@ select.role-select:disabled{ color:var(--dim); border-color:var(--border); backg
/* ══ GATES & WARNINGS ══════════════════════════════════════════════════════════ */
.gate-overlay{ position:fixed; inset:0; background:var(--bg); display:flex; align-items:center; justify-content:center; padding:var(--s5); z-index:9999; }
.gate-box{ background:var(--surface); border:1px solid var(--border); border-radius:0; padding:var(--s6); max-width:380px; width:100%; box-shadow:0 8px 30px rgba(20,30,50,.12); }
.gate-box{ background:var(--surface); border:1px solid var(--border); border-radius:0; padding:var(--s6); max-width:380px; width:100%; box-shadow:var(--wp-shadow-gate); }
.gate-box h2{ margin:0 0 var(--s1); padding:0; border:0; font-size:17px; text-transform:none; letter-spacing:0; color:var(--text); }
.gate-box p{ color:var(--muted); font-size:13px; margin:0 0 var(--s4); }
.gate-msg{ color:var(--red); font-size:12px; min-height:16px; margin-bottom:var(--s2); }