From 62afbdcdf33cbc14424027daf02c89ba8f9807c5 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Tue, 29 Sep 2026 14:25:28 +0000 Subject: [PATCH] check:ported-globals-identical compares only the ported rules, not the whole stylesheet The gate hashed each component's entire emitted CSS, so any unrelated style change in these four files (Regions giving three :host rules position:relative) failed it, and could pass only by re-baselining a guard that exists to prove something else. It now hashes what library/styles/portedGlobals.js emits, found in the executed CSS: each cyrillicHeading and strongMedium rule with the selectors in front of it, and each scrollbar-gutter declaration with its rule's selector. Re-baselined on the tree the old gate passed on. Proven both ways: re-scoping GoalCard's strong rule, changing the module's Cyrillic letter-spacing and dropping a file's ported rules each fail it; --write refuses to baseline an empty block. --- internal/check-ported-globals-identical.mjs | 45 ++++++++++++++++--- .../fixtures/ported-globals-baseline.json | 10 ++--- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/internal/check-ported-globals-identical.mjs b/internal/check-ported-globals-identical.mjs index 0d9168458..c155fa9d3 100644 --- a/internal/check-ported-globals-identical.mjs +++ b/internal/check-ported-globals-identical.mjs @@ -6,8 +6,9 @@ * page CSS cannot reach into a shadow root. The copies were MEASURED as byte-identical where they * overlap (the Cyrillic stack 3x, strong{font-weight:500} 2x, scrollbar-gutter:stable 2x), so * extracting them into library/styles/portedGlobals.js is DEDUPLICATION, not a fix — and the proof - * obligation is therefore unusual: the emitted CSS must be IDENTICAL before and after, or the - * refactor is wrong. + * obligation is therefore unusual: the ported rules must be emitted IDENTICALLY before and after, or + * the refactor is wrong. Only those rules are compared (see portedBlock); the rest of each stylesheet + * belongs to its component and is free to change. * * ★ IT EXECUTES THE COMPONENT'S SETUP AND CAPTURES THE REAL ARGUMENT. It does NOT read source text. * The first version of this harness hashed the SOURCE of the useShadowStyles(...) call, and that was @@ -84,6 +85,28 @@ function readSource(rel) { } } +// ★ ONLY THE PORTED BLOCK IS HASHED (2026-09-29). The contract is that the PORTED rules reach the browser +// unchanged. The gate used to hash each component's WHOLE stylesheet, so unrelated CSS work in these files +// (regions adding position:relative to a :host rule) failed it and could pass only by re-baselining someone +// else's guard. The hash now covers exactly what library/styles/portedGlobals.js emits, found in the EXECUTED +// CSS: every cyrillicHeading rule and every strongMedium rule WITH the selectors in front of it (GoalCard +// scopes strong to '.rd-goal '), and every scrollbar-gutter declaration with the selector of the rule holding +// it. A changed, dropped or re-targeted ported rule still fails; nothing else in the stylesheet can. A file +// whose block comes out EMPTY fails (its ported rules were dropped), and --write refuses to baseline one: +// a hash of nothing guards nothing. +const PORTED = await import('../library/styles/portedGlobals.js') +const norm = (x) => String(x).replace(/\s+/g, ' ').trim() +function portedBlock(css) { + const frags = [] + const cy = norm(PORTED.cyrillicHeading('.x')); const cyBody = cy.slice(cy.indexOf('{')) + const strong = norm(PORTED.strongMedium()); const gutter = norm(PORTED.scrollbarGutter()) + const selectorOf = (open) => css.slice(css.lastIndexOf('}', open) + 1, open).trim() + for (let i = css.indexOf(cyBody); i >= 0; i = css.indexOf(cyBody, i + 1)) frags.push(`cyrillic ${selectorOf(i)}${cyBody}`) + for (let i = css.indexOf(strong); i >= 0; i = css.indexOf(strong, i + 1)) frags.push(`strong ${selectorOf(i)} ${strong}`) + for (let i = css.indexOf(gutter); i >= 0; i = css.indexOf(gutter, i + 1)) frags.push(`gutter ${selectorOf(css.lastIndexOf('{', i))} { ${gutter} }`) + return frags +} + /** * Execute a component's