From fc3b64bbe65f2a56e76a50c2669e46efd12ddfa2 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Tue, 29 Sep 2026 02:15:00 +0000 Subject: [PATCH] Migrate the regions pages onto portedGlobals, and fix a Cyrillic rule that was inert MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 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. --- internal/check-ported-globals-identical.mjs | 25 ++++++++++++++++--- .../fixtures/ported-globals-baseline.json | 6 +++-- .../regions-dashboard/region-dashboards.vue | 9 ++++--- .../regions-dashboard/regions-list.vue | 9 ++++++- 4 files changed, 40 insertions(+), 9 deletions(-) diff --git a/internal/check-ported-globals-identical.mjs b/internal/check-ported-globals-identical.mjs index 5b3683443..0d9168458 100644 --- a/internal/check-ported-globals-identical.mjs +++ b/internal/check-ported-globals-identical.mjs @@ -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({}, { diff --git a/internal/fixtures/ported-globals-baseline.json b/internal/fixtures/ported-globals-baseline.json index 47884c42b..080da13b1 100644 --- a/internal/fixtures/ported-globals-baseline.json +++ b/internal/fixtures/ported-globals-baseline.json @@ -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" } } diff --git a/plugin-designs/regions-dashboard/region-dashboards.vue b/plugin-designs/regions-dashboard/region-dashboards.vue index d2b0a34ba..a61e8f310 100644 --- a/plugin-designs/regions-dashboard/region-dashboards.vue +++ b/plugin-designs/regions-dashboard/region-dashboards.vue @@ -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: diff --git a/plugin-designs/regions-dashboard/regions-list.vue b/plugin-designs/regions-dashboard/regions-list.vue index f2e0bbcd7..80525cab3 100644 --- a/plugin-designs/regions-dashboard/regions-list.vue +++ b/plugin-designs/regions-dashboard/regions-list.vue @@ -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' )