Merge remote-tracking branch 'origin/development' into regions/handoff-integration

This commit is contained in:
Doxa Maps 2026-09-29 15:24:53 +00:00
commit 0450da266c
2 changed files with 43 additions and 12 deletions

View file

@ -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 <script setup> far enough to capture its useShadowStyles argument.
*
@ -180,6 +203,7 @@ async function emittedCss(src, rel) {
}
const now = {}
const blocks = {}
const unmeasured = [] // owned files that failed to evaluate — these BLOCK
const unmeasuredOther = [] // recorded-only files — these are reported, never blocking
for (const { path: f } of FILES) {
@ -190,7 +214,8 @@ for (const { path: f } of FILES) {
;(OWNED.has(f) ? unmeasured : unmeasuredOther).push(`${f}: ${r.error}`)
continue
}
now[f] = createHash('sha256').update(r.css).digest('hex').slice(0, 16)
blocks[f] = portedBlock(r.css)
now[f] = blocks[f].length ? createHash('sha256').update(blocks[f].join('\n')).digest('hex').slice(0, 16) : '(no ported rule)'
}
if (args.includes('--write')) {
@ -199,15 +224,21 @@ if (args.includes('--write')) {
for (const u of unmeasured) console.error(` ! ${u}`)
process.exit(75)
}
const empty = FILES.filter((x) => x.owned && blocks[x.path] && !blocks[x.path].length).map((x) => x.path)
if (empty.length) {
console.error('PORTED-GLOBALS: refusing to write a baseline — these files emit no ported rule, so a hash would guard nothing:')
for (const e of empty) console.error(` ! ${e}`)
process.exit(1)
}
writeFileSync(BASELINE, JSON.stringify({
_why: 'Emitted-CSS hashes, captured by EXECUTING each component\'s setup and hashing the string it hands useShadowStyles. Whitespace-normalised; declarations and rule grouping are not. A ported-globals extraction must not change these.',
_why: 'Hashes of the PORTED BLOCK of each component\'s emitted CSS (captured by EXECUTING its setup): every rule or declaration library/styles/portedGlobals.js emits, with its selectors. Unrelated CSS in these files does not move them; a changed, dropped or re-targeted ported rule does.',
_source: fromRef ? `git ref ${fromRef}` : 'working tree',
hashes: now,
}, null, 2) + '\n')
console.log(`PORTED-GLOBALS: baseline written (${fromRef ? 'from ' + fromRef : 'working tree'})`)
for (const { path: f, owned } of FILES) {
if (now[f] === undefined) continue
console.log(` ${now[f] || '(absent)'} ${f}${owned ? '' : ' [recorded only — regions owns it]'}`)
console.log(` ${now[f] || '(absent)'} ${f}${blocks[f] ? ` (${blocks[f].length} ported rule(s))` : ''}${owned ? '' : ' [recorded only — regions owns it]'}`)
}
for (const u of unmeasuredOther) console.log(` (not measured) ${u}`)
process.exit(0)
@ -235,7 +266,7 @@ for (const { path: f } of FILES) {
if (now[f] === undefined) continue // recorded-only and unevaluated — reported above, not enforced
compared++
if (base[f] !== now[f]) {
fails.push(`${f}\n before: ${base[f]}\n after: ${now[f]}\n the emitted CSS CHANGED — a ported-globals extraction must be byte-identical, so either the\n module's output differs from the literal it replaced, or a declaration was dropped`)
fails.push(`${f}\n before: ${base[f]}\n after: ${now[f]}\n the PORTED BLOCK changed: a portedGlobals.js rule was edited, dropped, added or re-targeted\n (the rest of the stylesheet is not compared). What this run found:\n${blocks[f].map((b) => ` ${b}`).join('\n') || ' (nothing — every ported rule is gone)'}`)
}
}
if (fails.length) {
@ -244,4 +275,4 @@ if (fails.length) {
process.exit(1)
}
for (const u of unmeasuredOther) console.log(`PORTED-GLOBALS: note — not measured (recorded only, not gated): ${u}`)
console.log(`PORTED-GLOBALS: PASS — the emitted CSS of ${compared} component(s) is byte-identical to the pre-refactor baseline.`)
console.log(`PORTED-GLOBALS: PASS — the ported block of ${compared} component(s) is identical to the baseline (only portedGlobals.js output is compared; the rest of each stylesheet is free to change).`)

View file

@ -1,10 +1,10 @@
{
"_why": "Emitted-CSS hashes, captured by EXECUTING each component's setup and hashing the string it hands useShadowStyles. Whitespace-normalised; declarations and rule grouping are not. A ported-globals extraction must not change these.",
"_why": "Hashes of the PORTED BLOCK of each component's emitted CSS (captured by EXECUTING its setup): every rule or declaration library/styles/portedGlobals.js emits, with its selectors. Unrelated CSS in these files does not move them; a changed, dropped or re-targeted ported rule does.",
"_source": "working tree",
"hashes": {
"library/components/regions/GoalCard.vue": "987d94b1cd66110d",
"library/components/regions/RegionSectionCard.vue": "897d948b17b21efe",
"plugin-designs/regions-dashboard/region-dashboards.vue": "ea1d2047307b1efd",
"plugin-designs/regions-dashboard/regions-list.vue": "ab4877b933e5c8a3"
"library/components/regions/GoalCard.vue": "95ef1b19fdc658ff",
"library/components/regions/RegionSectionCard.vue": "fb65f7a1f7f25409",
"plugin-designs/regions-dashboard/region-dashboards.vue": "7d3971c379ad31c1",
"plugin-designs/regions-dashboard/regions-list.vue": "db6628e0a4506c1b"
}
}