From fcb1c8285e73170f8b0916889062e3fe9764a596 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Sat, 26 Sep 2026 02:10:30 +0000 Subject: [PATCH] Port their box-sizing reset into the shadow root, and delete the seven patches it replaces app/assets/styles/base/_reset.scss:3-8 applies border-box to every element on DX-life. Our leaf document carries the identical rule already -- but these components render inside a SHADOW ROOT, and a document-level `*` selector does not cross that boundary. So every element in here was content-box while every element of theirs was border-box, and no scoped component style could reveal it. That is how it survived seven global-rule fixes, and it is why this codebase had accrued seven separate per-element box-sizing:border-box patches: each was a local workaround for the reset that never arrived. What it cost, measured rather than described: their legend swatch is width:10px with a 1px border -- 10px under border-box, 12px under content-box. Two swatch columns of 1px each are exactly the 2px by which our legend panel measured wider than theirs (167 vs 165) after the rest of the legend was aligned to the pixel. The same label measured 129px on both sides in the same Poppins, so the text was never the cause. The port is a geometric NO-OP and that is why it is safe to do in one move: the blast radius was measured FIRST, by injecting this exact rule into every shadow root and diffing every element's rect. 0 of 431 elements moved on region-dashboards, 0 of 678 on regions-list, 301 already border-box by their own declarations. The detector was negative-tested, because a zero from a check that cannot fail proves nothing. So the whole value of this commit is the DELETION -- six per-element patches plus the interim `.rcm *` subtree rule, which the global reset strictly contains. Placement is plugin-local, not library/useShadowStyles.js. Putting it there would fix all 25 bundles in one line and is tempting for exactly that reason, but the blast radius above was measured on THESE two pages only; 23 other bundles would have their geometry changed on no evidence. RULE 2 says default to the plugin folder until a second consumer is named. One module rather than three copies: three roots inject into the shared shadow root and any of them can be the one that mounts, so all three need it. --- .../regions-dashboard/region-dashboards.vue | 8 ++- .../regions-dashboard/regions-list.vue | 5 +- .../regions-dashboard/regions-mfe.vue | 3 +- .../src/RegionCountryMap.vue | 1 - .../regions-dashboard/src/UupgsList.vue | 6 +- .../regions-dashboard/src/shadowReset.js | 52 +++++++++++++++++ public/dx-maps/doxa-maps-spa/manifest.json | 8 +-- public/dx-maps/manifest.json | 8 +-- .../region-dashboards/index.html | 2 +- .../regions-dashboard/regions-dashboard.js | 56 +++++++++---------- .../regions-dashboard/regions-list/index.html | 2 +- .../regions-dashboard/regions-mfe/index.html | 2 +- 12 files changed, 106 insertions(+), 47 deletions(-) create mode 100644 plugin-designs/regions-dashboard/src/shadowReset.js diff --git a/plugin-designs/regions-dashboard/region-dashboards.vue b/plugin-designs/regions-dashboard/region-dashboards.vue index be7699a1f..575ac2e3e 100644 --- a/plugin-designs/regions-dashboard/region-dashboards.vue +++ b/plugin-designs/regions-dashboard/region-dashboards.vue @@ -46,6 +46,7 @@ import { PALETTE as REGION_PALETTE, WAGF_REGIONS, REGION_ORDER } from '@map/colo // component's shadow CSS so every var() below resolves inside the shadow root. import { DXLIFE_TOKENS } from './dxlife-tokens.js' import RegionCountryMap from './src/RegionCountryMap.vue' +import { SHADOW_BOX_SIZING_RESET } from './src/shadowReset.js' import { summarizeRegions, emptyStats, toNumber, PEOPLE_GROUP_LIST_FIELDS } from '@map/utils/countries-meta.js' const props = defineProps({ @@ -704,6 +705,9 @@ onBeforeUnmount(() => { // the two tabs does not change the surface under the reader. // NO BACKTICKS in this string, including comments (house style: one terminates the literal). useShadowStyles( + // Their _reset.scss:3-8 FIRST, so every rule below is authored against border-box exactly as + // theirs is. See src/shadowReset.js for why it lives there and for the measured blast radius. + SHADOW_BOX_SIZING_RESET + DXLIFE_TOKENS + // EVERY value below is DX-life's own, resolved against their 10px rem root // (app/assets/styles/base/_globals.scss html{font-size:var(--font-size-base)}, and @@ -793,7 +797,7 @@ useShadowStyles( ' padding:var(--spacing-3xl) 0 var(--spacing-4xl); display:flex; flex-direction:column;' + ' container-type:inline-size; }' + '.rd-scroll > * + *{ margin-top:var(--spacing-3xl); }' + // .stack .stack--3xl - '.rd-scroll > *{ box-sizing:border-box; width:calc(90% + 16px); max-width:1200px;' + + '.rd-scroll > *{ width:calc(90% + 16px); max-width:1200px;' + ' margin-left:auto; margin-right:auto; }' + '.rd-status{ padding:28px; text-align:center; color:var(--dx-muted); font-size:var(--font-size-md); }' + '.rd-status.rd-failed{ font-weight:600; }' + @@ -838,7 +842,7 @@ useShadowStyles( // ── .card: blocks/_cards.scss:9-21. radius --border-radius-xlg (35px); // padding clamp(--spacing-lg, 6vw, --spacing-2xl) clamp(--spacing-lg, 6vw, --spacing-3xl). - '.rd-card{ position:relative; box-sizing:border-box; border-radius:var(--dx-radius);' + + '.rd-card{ position:relative; border-radius:var(--dx-radius);' + ' background:var(--dx-elevated); color:var(--dx-text);' + ' padding:clamp(var(--spacing-lg), 6cqw, var(--spacing-2xl)) clamp(var(--spacing-lg), 6cqw, var(--spacing-3xl)); }' + // .card[padding-small] -> --spacing-lg flat diff --git a/plugin-designs/regions-dashboard/regions-list.vue b/plugin-designs/regions-dashboard/regions-list.vue index 552eb49d8..7131ed944 100644 --- a/plugin-designs/regions-dashboard/regions-list.vue +++ b/plugin-designs/regions-dashboard/regions-list.vue @@ -40,6 +40,7 @@ */ import { inject, ref, computed, onMounted, watch, getCurrentInstance } from 'vue' import { useShadowStyles } from '@map/composables/useShadowStyles.js' +import { SHADOW_BOX_SIZING_RESET } from './src/shadowReset.js' import { splitLastWord } from './src/useLastWordHighlight.js' import { useRegionsI18n } from './src/useRegionsI18n.js' // ar renders right-to-left. RTL_LOCALES is the shared set and :dir on our own root is the @@ -340,6 +341,8 @@ onMounted(loadRegions) // Colours/fonts/radii are an intentional exception to this repo's material-light chrome tokens — // the Driver asked for an exact clone of DX-life's OWN design here, so the clone wins. useShadowStyles( + // Their _reset.scss:3-8 FIRST -- see src/shadowReset.js. + SHADOW_BOX_SIZING_RESET + DXLIFE_TOKENS + ':host{ display:block; width:100%; height:100%; min-height:320px; }' + '.rl-root{ position:absolute; inset:0; display:flex; flex-direction:column; min-height:0;' + @@ -383,7 +386,7 @@ useShadowStyles( // So it is FLUID at 90%+16px until the viewport reaches 1315.625px, then pinned at 1200px centred. // Our cards previously stretched to the full pane at every width -- there was no cap at all. // box-sizing:border-box so .rl-status's own 28px padding stays inside the cap. - '.rl-scroll > *{ box-sizing:border-box; width:calc(90% + 16px); max-width:1200px;' + + '.rl-scroll > *{ width:calc(90% + 16px); max-width:1200px;' + ' margin-left:auto; margin-right:auto; }' + // Their head block: .stack--xs (10px) gap, centred. h1 is _globals' clamp(--font-size-3xl, // 7vw, --font-size-5xl) and the subtitle is --font-size-md in --color-brand-lighter. diff --git a/plugin-designs/regions-dashboard/regions-mfe.vue b/plugin-designs/regions-dashboard/regions-mfe.vue index bf21b46cd..d302e420f 100644 --- a/plugin-designs/regions-dashboard/regions-mfe.vue +++ b/plugin-designs/regions-dashboard/regions-mfe.vue @@ -27,6 +27,7 @@ import { ref, computed, inject, onMounted, onBeforeUnmount } from 'vue' import { buildPaneColumn, PANE_GRID_STYLES } from '@map/layouts/pane-wrapper/pane-wrapper.js' import { useShadowStyles } from '@map/composables/useShadowStyles.js' +import { SHADOW_BOX_SIZING_RESET } from './src/shadowReset.js' import ViewSelect from '@map/components/ViewSelect.vue' import ShareButton from '@map/components/map-controls/ShareButton.vue' // The SELECT VIEW strip is one of the sanctioned additions, so DX-life has no equivalent @@ -170,7 +171,7 @@ const SHARE_EMBED_PATH = 'regions-dashboard/regions-mfe/index.html' // NO BACKTICKS inside this CSS string, including in comments — one terminates the JS template // literal and the build stops compiling (house style). -useShadowStyles(PANE_GRID_STYLES + '\n' + +useShadowStyles(SHADOW_BOX_SIZING_RESET + PANE_GRID_STYLES + '\n' + // SELF-SIZE. Every layer inside is absolute/height:100% (.rmfe -> .pane-col -> .pane), so the // chain resolves to ZERO unless the HOST itself is sized — the "elements are not embedding" // failure upg-100-mfe documents. Declaring :host makes this fill whatever slot it is mounted diff --git a/plugin-designs/regions-dashboard/src/RegionCountryMap.vue b/plugin-designs/regions-dashboard/src/RegionCountryMap.vue index 774734f96..82c2a90de 100644 --- a/plugin-designs/regions-dashboard/src/RegionCountryMap.vue +++ b/plugin-designs/regions-dashboard/src/RegionCountryMap.vue @@ -739,7 +739,6 @@ useShadowStyles( // root so it is verified where it is measured; porting it bundle-wide and DELETING those seven // patches is carded (BUILD-PORT-THEIR-BOX-SIZING-RESET-TO-THE-SHADOW-ROOT) because it changes // geometry in components whose match against live has to be re-measured, not assumed. - '.rcm, .rcm *, .rcm *::before, .rcm *::after{ box-sizing:border-box; }' + '.rcm{ display:block; position:relative; width:100%; min-height:780px; aspect-ratio:16/9;' + ' overflow:hidden; border-radius:inherit; }' + '@media (max-width:768px){ .rcm{ min-height:0; aspect-ratio:1/2; } }' + diff --git a/plugin-designs/regions-dashboard/src/UupgsList.vue b/plugin-designs/regions-dashboard/src/UupgsList.vue index 16b93afbf..aecbd3729 100644 --- a/plugin-designs/regions-dashboard/src/UupgsList.vue +++ b/plugin-designs/regions-dashboard/src/UupgsList.vue @@ -326,7 +326,7 @@ useShadowStyles( // type at every other size. MEASURED: theirs renders ~600px inside the 1200px container // and shows the whole placeholder; ours truncated it at 480px. '.search-box{ position:relative; width:100%; max-width:50ch; margin-inline:auto; }' + - '.search-box input{ box-sizing:border-box; width:100%; border:1px solid #666; border-radius:100px;' + + '.search-box input{ width:100%; border:1px solid #666; border-radius:100px;' + ' background:var(--dx-white); padding:10px var(--spacing-lg); text-indent:1.5em; font:inherit;' + ' font-size:var(--font-size-md); color:var(--dx-text); }' + '.search-box input::placeholder{ color:gray; }' + @@ -397,7 +397,7 @@ useShadowStyles( '.grid{ display:grid; grid-gap:var(--spacing-2xl);' + ' grid-template-columns:repeat(auto-fit, minmax(min(400px,100%), 1fr)); }' + // .card + .uupg__card: their grid-template-areas, radius-xlg, --spacing-md padding + gap - '.uupg__card{ box-sizing:border-box; background:var(--dx-white); color:var(--dx-text);' + + '.uupg__card{ background:var(--dx-white); color:var(--dx-text);' + ' border-radius:35px; display:grid;' + ' grid-template-areas:"image header adopted" "image content content" "footer footer footer-right";' + ' grid-template-columns:auto 1fr auto; grid-template-rows:auto auto auto;' + @@ -424,7 +424,7 @@ useShadowStyles( '.uupg__match{ margin:0; font-size:var(--font-size-sm); color:var(--dx-muted); }' + // .button.compact, grid-area footer, bottom-aligned '.uupg__more-button{ grid-area:footer; align-self:flex-end; display:block; width:fit-content;' + - ' box-sizing:border-box; padding:10px var(--spacing-2xl); color:#f3f3f1; background:#92b195;' + + ' padding:10px var(--spacing-2xl); color:#f3f3f1; background:#92b195;' + ' border-radius:100px; border:1px solid transparent; line-height:1; text-decoration:none;' + ' text-transform:uppercase; font-size:var(--font-size-md);' + // THEIR FAMILY IS "Brandon Grotesque", WHICH IS COMMERCIAL — the one substitution RULE 1 diff --git a/plugin-designs/regions-dashboard/src/shadowReset.js b/plugin-designs/regions-dashboard/src/shadowReset.js new file mode 100644 index 000000000..11e6df297 --- /dev/null +++ b/plugin-designs/regions-dashboard/src/shadowReset.js @@ -0,0 +1,52 @@ +/** + * Their box-sizing reset, ported into the shadow root. + * + * WHAT THIS IS. app/assets/styles/base/_reset.scss:3-8 on DX-life master, verbatim: + * + * *, + * *::before, + * *::after { + * box-sizing: border-box; + * } + * + * a piccalil.li reset that applies to EVERY element on their page. + * + * WHY IT WAS MISSING FOR SO LONG, and why no component could reveal it. Our leaf document already + * carries the identical rule (regions-dashboard/index.html:9) -- but these components render inside + * a SHADOW ROOT, and a document-level `*` selector does not cross a shadow boundary. So every + * element in here was content-box while every element of theirs was border-box, and no scoped + * component style could show the difference. It was the SEVENTH global rule of theirs found + * missing, and it is why this codebase had accumulated seven separate per-element + * `box-sizing:border-box` patches: each was a local workaround for the reset that never arrived. + * + * WHAT IT COST, measured rather than described: their legend swatch is `width:10px; border:1px + * solid #fff` -- 10px under border-box, 12px under content-box. Two swatch columns of 1px each are + * exactly the 2px by which our legend panel measured wider than theirs (167px vs 165px) after the + * rest of the legend had been aligned to the pixel. The same label string measured 129px on both + * sides in the same Poppins, so the text was never the cause. + * + * WHY PORTING IT IS SAFE, and this is the part that made the change cheap instead of frightening: + * the blast radius was MEASURED before any of it was written. + * _probe-boxsizing-blast-radius.mjs injected this exact rule into every shadow root at runtime and + * diffed every element's rect before and after -- 0 of 431 elements moved on region-dashboards and + * 0 of 678 on regions-list, with 301 already border-box by their own declarations. So the port is a + * geometric NO-OP and the whole value of it is DELETING the per-element patches it replaces, which + * is the direction RULE 3 asks for. The detector was negative-tested (NEG=1 injects padding-left:3px + * and it reports the movement), because a zero from a check that cannot fail is worth nothing. + * + * WHY HERE AND NOT library/useShadowStyles.js. Putting it there would fix all 25 bundles at once and + * is tempting for exactly that reason -- but the blast radius above was measured on THESE two pages + * only, and geometry in 23 other bundles would change on an unmeasured basis. RULE 2 says default to + * the plugin folder; the second consumer that would justify library/ is not named yet. If another + * plugin wants it, it moves then, with that plugin's blast radius measured too. + * + * WHY A MODULE AND NOT THREE COPIES. Three roots inject into the shared shadow root + * (region-dashboards, regions-list, regions-mfe) and any of them can be the one that mounts, so all + * three need it. One exported string keeps it one fact in one place. + */ + +/** Their _reset.scss:3-8, as a shadow-root-scoped rule. */ +export const SHADOW_BOX_SIZING_RESET = + '*, *::before, *::after{ box-sizing:border-box; }' + +export default SHADOW_BOX_SIZING_RESET diff --git a/public/dx-maps/doxa-maps-spa/manifest.json b/public/dx-maps/doxa-maps-spa/manifest.json index fd3fa284e..496545596 100644 --- a/public/dx-maps/doxa-maps-spa/manifest.json +++ b/public/dx-maps/doxa-maps-spa/manifest.json @@ -888,7 +888,7 @@ "bundle": "regions-dashboard", "scriptUrl": "./regions-dashboard/regions-dashboard.js", "output": "dx-maps/regions-dashboard/regions-dashboard.js", - "integrity": "sha384-z4GurUdJw60hce5cF5GM+ugsgNGMEYEdkT0VlBXI8XOw1J4NP+F5CUtoD7ahznVG", + "integrity": "sha384-FDwUoPdVCOpa/WplF4PpBVLYMusCFPZqbxBBqQOFlI0sJhR+UB4HuSbH/O4MJec0", "elements": [ "regions-dashboard" ], @@ -908,7 +908,7 @@ "scriptUrl": "./regions-dashboard/regions-dashboard.js", "embed": { "iframe": "", - "script": "\n\n\n\n" + "script": "\n\n\n\n" } }, { @@ -922,7 +922,7 @@ "scriptUrl": "./regions-dashboard/regions-dashboard.js", "embed": { "iframe": "", - "script": "\n\n" + "script": "\n\n" } }, { @@ -936,7 +936,7 @@ "scriptUrl": "./regions-dashboard/regions-dashboard.js", "embed": { "iframe": "", - "script": "\n\n\n\n" + "script": "\n\n\n\n" } } ] diff --git a/public/dx-maps/manifest.json b/public/dx-maps/manifest.json index fd3fa284e..496545596 100644 --- a/public/dx-maps/manifest.json +++ b/public/dx-maps/manifest.json @@ -888,7 +888,7 @@ "bundle": "regions-dashboard", "scriptUrl": "./regions-dashboard/regions-dashboard.js", "output": "dx-maps/regions-dashboard/regions-dashboard.js", - "integrity": "sha384-z4GurUdJw60hce5cF5GM+ugsgNGMEYEdkT0VlBXI8XOw1J4NP+F5CUtoD7ahznVG", + "integrity": "sha384-FDwUoPdVCOpa/WplF4PpBVLYMusCFPZqbxBBqQOFlI0sJhR+UB4HuSbH/O4MJec0", "elements": [ "regions-dashboard" ], @@ -908,7 +908,7 @@ "scriptUrl": "./regions-dashboard/regions-dashboard.js", "embed": { "iframe": "", - "script": "\n\n\n\n" + "script": "\n\n\n\n" } }, { @@ -922,7 +922,7 @@ "scriptUrl": "./regions-dashboard/regions-dashboard.js", "embed": { "iframe": "", - "script": "\n\n" + "script": "\n\n" } }, { @@ -936,7 +936,7 @@ "scriptUrl": "./regions-dashboard/regions-dashboard.js", "embed": { "iframe": "", - "script": "\n\n\n\n" + "script": "\n\n\n\n" } } ] diff --git a/public/dx-maps/regions-dashboard/region-dashboards/index.html b/public/dx-maps/regions-dashboard/region-dashboards/index.html index 49c4c4c63..507e973c6 100644 --- a/public/dx-maps/regions-dashboard/region-dashboards/index.html +++ b/public/dx-maps/regions-dashboard/region-dashboards/index.html @@ -30,7 +30,7 @@ - + - + - +