Migrate the regions pages onto portedGlobals, and fix a Cyrillic rule that was inert
upg100's harness recorded my two files as NOT MEASURED because its stubs threw on
them. Both were FIDELITY gaps — the stub modelled states that cannot occur — so
they are fixed by making the stub more like the browser, never by relaxing a
comparison:
· `.value` was undefined, so a component reading an INJECTED ref threw
"Cannot read properties of undefined" (region-dashboards.vue:199,
cfg.value.regionSlug — ProfileLoader always provides that object).
· the stub threw on ANY string coercion, because a Proxy over a function has no
primitive conversion. That alone produced regions-list's "Cannot convert
object to primitive value".
`.value` is now the EMPTY STRING, which is the one shape that serves both real
cases honestly: a locale ref's .value IS a string, so `${locale.value || 'en'}`
correctly yields 'en'; and reading a property off '' returns undefined instead of
throwing, so an injected-config read falls through its own `|| ''` exactly as in
the browser. Symbol.toPrimitive makes any remaining stub render empty.
Measured components: 2 -> 4, with no NOT MEASURED left.
★ AND THE MIGRATION FOUND A REAL BUG, which is the part worth reading.
regions-list's Cyrillic heading rule ended with a trailing comma and NO
declaration block, so the concatenation emitted:
[lang="ru"] .rl-title, [lang="ru-RU"] .rl-title,regions-list
A selector list with nothing to apply — the Russian heading font was silently
never applied — AND it swallowed useShadowStyles' style-id argument as CSS,
leaving that sheet unlabelled. Braces stayed balanced, so
check:shadow-css-parses could not see it, and it passed 80/80 both before and
after. cyrillicHeading('.rl-title') replaces it and emits a real rule.
SO THE BYTE-COMPARE SPLITS, deliberately, and I did not paper over it:
region-dashboards.vue IDENTICAL — cyrillicHeading('.rd-title','.rd-h2')
emits exactly the literal it replaced, verified
character-for-character before the swap. A pure
de-duplication.
regions-list.vue CHANGED, and the change IS the fix. The old bytes were
the bug, so its baseline is re-taken rather than
preserved. Preserving it would have pinned a broken
rule forever, which is the one thing a byte-identical
gate must not be used for.
NOT MIGRATED, on purpose: strongMedium(). The helper emits bare `strong{...}`
while region-dashboards emits `.rd-root strong{...}` — a narrower selector.
Swapping it would widen the rule to every <strong> in the shadow root, a
behaviour change disguised as a de-duplication. scrollbarGutter() is likewise
left inline: both files carry it inside a larger .rd-scroll/.rl-scroll rule, not
as a standalone declaration the helper could replace.
Both files now gated (owned:true) instead of recorded-only, so a future edit that
changes their emitted CSS fails rather than being noted.
This commit is contained in:
parent
65810f872f
commit
fc3b64bbe6
4 changed files with 40 additions and 9 deletions
|
|
@ -60,8 +60,8 @@ const BASELINE = join(HERE, 'fixtures', 'ported-globals-baseline.json')
|
|||
const FILES = [
|
||||
{ path: 'library/components/regions/GoalCard.vue', owned: true },
|
||||
{ path: 'library/components/regions/RegionSectionCard.vue', owned: true },
|
||||
{ path: 'plugin-designs/regions-dashboard/region-dashboards.vue', owned: false },
|
||||
{ path: 'plugin-designs/regions-dashboard/regions-list.vue', owned: false },
|
||||
{ path: 'plugin-designs/regions-dashboard/region-dashboards.vue', owned: true },
|
||||
{ path: 'plugin-designs/regions-dashboard/regions-list.vue', owned: true },
|
||||
]
|
||||
const OWNED = new Set(FILES.filter((f) => f.owned).map((f) => f.path))
|
||||
|
||||
|
|
@ -106,8 +106,27 @@ async function emittedCss(src, rel) {
|
|||
// A stub that tolerates any shape a component might use it in: called, constructed, or
|
||||
// property-accessed. defineProps/defineEmits are compiler macros, so they must return something
|
||||
// indexable rather than undefined.
|
||||
// ★ TWO STUB GAPS CLOSED, and both were FIDELITY problems rather than assertions to weaken.
|
||||
// The stub modelled states that cannot occur in real use, so two real components could not be
|
||||
// measured at all and were recorded as NOT MEASURED:
|
||||
// · `.value` was undefined. A component reading an INJECTED ref — cfg.value.regionSlug on
|
||||
// region-dashboards.vue:199, where ProfileLoader always provides a profile-config object —
|
||||
// threw "Cannot read properties of undefined".
|
||||
// · the stub itself threw on any STRING COERCION (template interpolation, String(), + ''),
|
||||
// because a Proxy over a function has no primitive conversion. regions-list threw "Cannot
|
||||
// convert object to primitive value" for that reason alone.
|
||||
// `.value` is now the EMPTY STRING, which is the one shape that serves both real cases honestly:
|
||||
// a locale ref's .value IS a string, so `${locale.value || 'en'}` correctly yields 'en'; and
|
||||
// reading a property off '' returns undefined instead of throwing, so an injected-config read
|
||||
// falls through its own `|| ''` exactly as it does in the browser. Symbol.toPrimitive makes any
|
||||
// remaining stub render as empty rather than explode. Nothing here relaxes a comparison — the
|
||||
// emitted CSS is still hashed byte-for-byte.
|
||||
const anyFn = () => new Proxy(function () {}, {
|
||||
get: (t, k) => (k === 'value' ? undefined : anyFn()),
|
||||
get: (t, k) => {
|
||||
if (k === 'value') return ''
|
||||
if (k === Symbol.toPrimitive) return () => ''
|
||||
return anyFn()
|
||||
},
|
||||
apply: () => anyFn(),
|
||||
})
|
||||
const stubModule = (spec) => new Proxy({}, {
|
||||
|
|
|
|||
|
|
@ -1,8 +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.",
|
||||
"_source": "git ref 45e31f102",
|
||||
"_source": "working tree",
|
||||
"hashes": {
|
||||
"library/components/regions/GoalCard.vue": "987d94b1cd66110d",
|
||||
"library/components/regions/RegionSectionCard.vue": "897d948b17b21efe"
|
||||
"library/components/regions/RegionSectionCard.vue": "897d948b17b21efe",
|
||||
"plugin-designs/regions-dashboard/region-dashboards.vue": "ea1d2047307b1efd",
|
||||
"plugin-designs/regions-dashboard/regions-list.vue": "ab4877b933e5c8a3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ import BackToTopButton from '@map/components/BackToTopButton.vue'
|
|||
import { SHADOW_BOX_SIZING_RESET } from './src/shadowReset.js'
|
||||
import { summarizeRegions, emptyStats, toNumber, PEOPLE_GROUP_LIST_FIELDS } from '@map/utils/countries-meta.js'
|
||||
import { peopleGroupPoints, PEOPLE_GROUP_POINT_FIELDS } from '@map/utils/peopleGroupPoints.js'
|
||||
import { cyrillicHeading } from '@map/styles/portedGlobals.js'
|
||||
|
||||
const props = defineProps({
|
||||
profileConfig: { type: String, default: '' },
|
||||
|
|
@ -1004,9 +1005,11 @@ useShadowStyles(
|
|||
// BOTH TAG FORMS, deliberately: their site tags the document ru-RU while our i18n locale is `ru`,
|
||||
// and a host page embedding this element may set either. A selector that matched only one would
|
||||
// work on their site and fail on ours, or the reverse, depending on who set the attribute.
|
||||
'[lang="ru"] .rd-title, [lang="ru-RU"] .rd-title,' +
|
||||
'[lang="ru"] .rd-h2, [lang="ru-RU"] .rd-h2{ font-family:"Oswald","Arial Narrow",BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Arial,sans-serif;' +
|
||||
' letter-spacing:-0.02em; }' +
|
||||
// The Cyrillic heading face now comes from the shared module rather than a third copy of the
|
||||
// same rule — its output is byte-identical to what this file emitted before (verified), so this
|
||||
// is a de-duplication and not a style change. See library/styles/portedGlobals.js for the
|
||||
// measurement history and for the one global it DELIBERATELY does not port.
|
||||
cyrillicHeading('.rd-title', '.rd-h2') +
|
||||
// .rd-big / .rd-big-label / .rd-goal and the .stack--xs that separates a bar from its legend are
|
||||
// in library/components/regions/GoalCard.vue and library/components/charts/ProgressBar.vue now,
|
||||
// together with the measurement history that fixed each value. The `strong` rule below STAYS:
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ import RegionSectionCard from '@map/components/regions/RegionSectionCard.vue'
|
|||
import posterData from '@map/data/country-posters.json'
|
||||
import { resolveAssetsBase } from '@map/utils/embedBase.js'
|
||||
import { onBeforeUnmount } from 'vue'
|
||||
import { cyrillicHeading } from '@map/styles/portedGlobals.js'
|
||||
|
||||
const props = defineProps({
|
||||
profileConfig: { type: String, default: '' },
|
||||
|
|
@ -447,7 +448,13 @@ useShadowStyles(
|
|||
// BOTH TAG FORMS, deliberately: their site tags the document ru-RU while our i18n locale is `ru`,
|
||||
// and a host page embedding this element may set either. A selector that matched only one would
|
||||
// work on their site and fail on ours, or the reverse, depending on who set the attribute.
|
||||
'[lang="ru"] .rl-title, [lang="ru-RU"] .rl-title,' +
|
||||
// ★ THIS RULE WAS INERT, AND THE LABEL WAS BEING EATEN. The line above used to end with a
|
||||
// trailing comma and no declaration block, so the concatenation produced
|
||||
// [lang="ru"] .rl-title, [lang="ru-RU"] .rl-title,regions-list
|
||||
// — a selector list with nothing to apply, which silently dropped the Cyrillic heading font AND
|
||||
// consumed useShadowStyles' style-id argument as CSS, leaving the sheet unlabelled. Braces stayed
|
||||
// balanced, so check:shadow-css-parses could not see it. Found while migrating to portedGlobals.
|
||||
cyrillicHeading('.rl-title'),
|
||||
'regions-list'
|
||||
)
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in a new issue