Seven shadow-root components each carry their own copy of DX Life's
base/_globals.scss rules, because page CSS cannot reach into a shadow
root. Measured first: where the copies overlap they are byte-identical
(the Cyrillic stack 3x, strong{font-weight:500} 2x, scrollbar-gutter 2x),
so this is deduplication, not a fix.
The module exports FUNCTIONS, not strings, and the measurement forced
that: the six copies of the Cyrillic rule share one declaration but have
six different subjects (.rd-goal-card__title, .rd-h2, .rd-title,
.region-card__title, .rl-title x2). A fixed rule string would have to
pick one class and silently stop styling the other five. The caller owns
its selector; the module owns the declaration.
cyrillicHeading takes a selector LIST, not a selector. GoalCard styles
one heading class; RegionSectionCard groups two into a single rule
sharing one declaration block. A one-selector signature emits two rules
instead of one -- the same CSS to a browser, but not the same
stylesheet, and the bar here is unchanged, not equivalent.
Migrated GoalCard and RegionSectionCard. regions-dashboard's two files
port the same rules and stay with their owner; the harness records them
so that migration can run against this same baseline.
The img border-radius rule (_globals.scss:80) is documented as
deliberately NOT ported, because an invisible omission is one that gets
"fixed" later.
PROOF: internal/check-ported-globals-identical.mjs executes each
component's setup, captures the string handed to useShadowStyles and
hashes it. The emitted CSS is byte-identical across the refactor
(987d94b1cd66110d, 897d948b17b21efe, both captured from 45e31f102).
The harness's first version hashed the SOURCE of the call and was
wrong: this refactor replaces a literal with a function call, so the
source changes by construction -- a text-hash gate fails a perfect
refactor and cannot tell that from a broken one. It measured the
artifact when the property under test was the output. It failed exactly
that way on first run.
Shown capable of failing, four ways: dropping letter-spacing, a wrong
font name, 500->600, and splitting the grouped rule into two. All four
go red; restoring goes green. Components that cannot be evaluated are
reported NOT MEASURED (exit 75), never as a pass.
77 lines
4.2 KiB
JavaScript
77 lines
4.2 KiB
JavaScript
/**
|
|
* portedGlobals — DX Life's base/_globals.scss rules, once, for shadow-root components.
|
|
*
|
|
* Page CSS cannot reach into a shadow root, so every component that must look like DX Life carries
|
|
* its own copy of that site's element rules. Seven files did, each citing base/_globals.scss by
|
|
* line. MEASURED before extracting: where the copies overlap they are BYTE-IDENTICAL (the Cyrillic
|
|
* font stack 3x, strong{font-weight:500} 2x, scrollbar-gutter:stable 2x). So this module is
|
|
* DEDUPLICATION, not a fix, and its correctness test is that the emitted CSS does not change —
|
|
* internal/check-ported-globals-identical.mjs holds the before/after hashes.
|
|
*
|
|
* ★ IT EXPORTS FUNCTIONS, NOT STRINGS, and that is forced by the measurement. The six copies of the
|
|
* Cyrillic rule share ONE declaration but have SIX DIFFERENT SUBJECTS — .rd-goal-card__title,
|
|
* .rd-h2, .rd-title, .region-card__title, .rl-title (x2). A module exporting a fixed rule string
|
|
* would have to pick one class and would silently stop styling the other five. The caller owns its
|
|
* selector; this module owns the declaration.
|
|
*
|
|
* ★ AND IT TAKES A LIST, NOT A SELECTOR, which the first draft of this module got wrong. GoalCard
|
|
* styles ONE heading class; RegionSectionCard GROUPS TWO (.rl-title and .region-card__title) into a
|
|
* single rule sharing one declaration block. A one-selector signature cannot reproduce the grouped
|
|
* form — it would emit two separate rules. Same CSS to a browser, but this refactor's whole proof is
|
|
* that the emitted bytes do not change, so "equivalent" is not good enough. Caught by diffing the
|
|
* two call sites before writing the callers, not after the hashes disagreed.
|
|
*
|
|
* Plain JS returning CSS strings, zero imports, no build step — the same pure-module shape as
|
|
* packetSets.js, so a Node gate and a Vite bundle both consume it unchanged.
|
|
*/
|
|
|
|
/**
|
|
* base/_globals.scss:35-44 — h1,h2,h3 switch face under Cyrillic.
|
|
*
|
|
* ⚠ NOT DECORATION: Bebas Neue HAS NO CYRILLIC GLYPHS, so a Russian heading left in it falls out of
|
|
* the family entirely and lands on whatever condensed system face the browser picks.
|
|
* ⚠ BOTH TAG FORMS, deliberately: their site tags the document ru-RU while our i18n locale is `ru`,
|
|
* and a host page embedding one of these elements may set either. A selector matching only one
|
|
* works on their site and fails on ours, or the reverse, depending on who set the attribute. Do not
|
|
* "simplify" this to a single form.
|
|
*
|
|
* @param {...string} selectors the caller's own heading classes, e.g. '.rl-title', '.region-card__title'.
|
|
* Several are GROUPED into one rule sharing one declaration block, which is how RegionSectionCard
|
|
* already spells it — pass them together rather than calling this twice.
|
|
* @returns {string} one CSS rule
|
|
*/
|
|
export function cyrillicHeading(...selectors) {
|
|
if (!selectors.length) throw new Error('cyrillicHeading: needs at least one selector')
|
|
const subjects = selectors
|
|
.map((s) => `[lang="ru"] ${s}, [lang="ru-RU"] ${s}`)
|
|
.join(',')
|
|
return `${subjects}{ font-family:"Oswald","Arial Narrow",BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Arial,sans-serif;` +
|
|
' letter-spacing:-0.02em; }'
|
|
}
|
|
|
|
/** base/_globals.scss:200-202 — <strong> is --font-weight-medium (500) on their site, not the UA's bold. */
|
|
export function strongMedium() {
|
|
return 'strong{ font-weight:500; }'
|
|
}
|
|
|
|
/**
|
|
* base/_globals.scss html rule — reserve the scrollbar track so content does not shift by its width
|
|
* when a list grows past the viewport.
|
|
*/
|
|
export function scrollbarGutter() {
|
|
return 'scrollbar-gutter:stable;'
|
|
}
|
|
|
|
/**
|
|
* ⚠ DELIBERATELY NOT PORTED, and recorded here so nobody "completes" the set later:
|
|
*
|
|
* _globals.scss:80 `img { border-radius: var(--border-radius) }`
|
|
*
|
|
* region-dashboards.vue:805 declines it on purpose. Rounding every image inside these components is
|
|
* not wanted — their site applies it in a context ours does not share. If a future component needs
|
|
* it, it should opt in explicitly rather than inherit it from a shared module, because an omission
|
|
* that is invisible is an omission that gets "fixed".
|
|
*/
|
|
export const NOT_PORTED = Object.freeze({
|
|
'img border-radius': '_globals.scss:80 — declined at region-dashboards.vue:805; opt in explicitly if ever needed',
|
|
})
|