Merge development into the gospel handoff branch
This commit is contained in:
commit
35faf8cbf0
2 changed files with 43 additions and 12 deletions
|
|
@ -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).`)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue