Ensure the engine stylesheet the library's way, and localize the selection cover
TWO ITEMS, both found by checking claims rather than accepting them.
★ 1. THE MAPBOX "missing CSS declarations" WARNING (rega END-REGA-SRV / dx-shepherd END-DXS-U100C). The
routed hypothesis was "the leaf doesn't load mapbox-gl.css". It does not — and neither does
cities-internet's leaf, which does NOT warn, so that was not the difference. The real difference, measured:
connectivity-map.vue:398-399 calls `engineStylesheetUrl(provider)` then `await ensureEngineCSS(...)`. This
profile called NEITHER; it hand-rolled its own injection, which failed two ways:
(a) it ran only when the map element was inside a ShadowRoot, so on a standalone BUILT leaf — light DOM
— no stylesheet was ensured at all; and
(b) even in the shadow case it fired and forgot, so `new gl.Map` could run before the sheet parsed.
Mapbox checks its declarations AT CONSTRUCTION, which is exactly when the warning appears.
ensureEngineCSS covers both DOM cases and AWAITS load/error with a 2s cap. It also takes the PROVIDER's own
URL, where the old line asked for mapbox's stylesheet even under MapLibre behind an !isMaplibre guard — so
a maplibre page now gets maplibre's sheet or none, and never touches api.mapbox.com, which is the keyless
contract the maplibre-embed gate enforces. The shadow injection stays as a belt for a partner embedding the
custom element with no wrapper <link>; both paths check by href, so neither duplicates the other.
★ 2. THE SELECTION PACKET'S COVER WAS HARDCODED ENGLISH. Found while checking whether batch selection was
already done (END-DXS-U100B — it is, and render-verified). selectionAsList built its label with
`names.join(' + ')` and `` `+${n} more` ``, printed onto page 1 and into the print dialog title. So a reader
choosing three territories under ?lang=ar got their names in Arabic joined by an English tail.
zone_join / zone_join_more already exist in all eleven packet-strings blocks — translated once for exactly
this shape when the zone-title generator landed — so this is reuse, not new strings. The block is resolved
with per-KEY fallback to English, as packet.mjs and packet-render already do it, so a half-translated locale
cannot blank a key that en has.
check:sfc-parse PASS, check:no-free-identifiers PASS.
⚠ THE 0×0 CONTAINER IS NOT MINE AND NOT FIXED HERE — diagnosed and routed to studio. It is
doxa-maps-spa's HEAVY_ELEMENTS set: the gallery computes `heavy: HEAVY_ELEMENTS.has(tag) ||
HEAVY_ELEMENTS.has(id)`, the set contains `my-upg-100-list-map` (the inner MAP element) and the gallery
mounts the COMPOSITE, so neither the mfe tag nor the bundle id `my-upg-100-list` matches. A non-heavy tab
mounts under `v-show` + `:hidden`, i.e. display:none, and a map built into a display:none container is 0×0
by construction. The gallery's own comment says heavy means "anything that owns OR NESTS a map" — the
composite nests one, so it qualifies and is simply missing from the list. regions-dashboard/regions-mfe has
the same shape and the same gap.
NOT caused by my inert change, despite the timing pointing there (absent at a1c8f3f6, present at 5d83febd):
`inert` sets no layout at all and nothing in the tree keys CSS on [inert], so it cannot collapse a box. The
window contained seven candidate commits and the timing alone could not distinguish them.
This commit is contained in:
parent
278bdde885
commit
4d2b8509ab
1 changed files with 52 additions and 3 deletions
|
|
@ -77,7 +77,7 @@ import ThemeToggleButton from '@map/components/map-controls/ThemeToggleButton.vu
|
|||
// edits src/ (zero blast radius — md5 unchanged). (research-map.vue:141,1815.)
|
||||
import GeocoderComponent from '@map/components/map-controls/GeocoderComponent.vue'
|
||||
import { resolveMapProvider } from '@map/composables/useMapProvider.js'
|
||||
import { ensureEngineScript, engineGlobal, engineStylesheetUrl } from '@map/composables/useMapInstance.js'
|
||||
import { ensureEngineScript, engineGlobal, ensureEngineCSS, engineStylesheetUrl } from '@map/composables/useMapInstance.js'
|
||||
// Shapes Arabic / Persian / Hebrew BASEMAP labels. A raw map inherits nothing from the bundle
|
||||
// entry, so this profile has to install it itself — see the call site below for why the entry's
|
||||
// registration only ever worked by accident.
|
||||
|
|
@ -1567,6 +1567,22 @@ function selectedLists() {
|
|||
// HERE — once, against one fetch of the people-groups list — and handed on as the type every
|
||||
// consumer already handles. Resolving per zone would refetch the whole catalogue per zone and
|
||||
// still leave the caller to merge.
|
||||
// The packet's own string block for the reader's locale, per-KEY fallback to English exactly as
|
||||
// packet.mjs and packet-render do it — a half-translated block must not blank a key that en has.
|
||||
let _packetStrings = null;
|
||||
async function packetStrings() {
|
||||
if (!_packetStrings) {
|
||||
const all = await import('@map/scripts/posters/packet-strings.json').then((m) => m.default || m);
|
||||
_packetStrings = { ...(all.en || {}), ...(all[packetLang()] || {}) };
|
||||
}
|
||||
return _packetStrings;
|
||||
}
|
||||
/** `{a}` / `{b}` / `{n}` interpolation; null when the pattern is missing so the caller can default. */
|
||||
function fillJoin(pattern, params) {
|
||||
if (!pattern) return null;
|
||||
return String(pattern).replace(/\{(\w+)\}/g, (m, k) => (params[k] != null ? String(params[k]) : m));
|
||||
}
|
||||
|
||||
async function selectionAsList() {
|
||||
const lists = selectedLists();
|
||||
if (!lists.length) throw new Error('nothing selected');
|
||||
|
|
@ -1587,10 +1603,20 @@ async function selectionAsList() {
|
|||
}
|
||||
// The label is what the reader sees on the cover and in the print dialog's title. Name the
|
||||
// territories they actually chose rather than inventing a title for their selection.
|
||||
// ⚠ THE JOINERS WERE HARDCODED ENGLISH on a cover an Arabic reader sees: ' + ' and ' +N more', printed
|
||||
// straight onto the packet's first page and into the print dialog's title. Found while checking whether
|
||||
// batch selection was already done (dx-shepherd END-DXS-U100B) — the SELECTION path was, but its label
|
||||
// was not localized, so a reader choosing three territories in Arabic got their names in Arabic joined
|
||||
// by an English tail.
|
||||
// zone_join / zone_join_more already exist in all eleven packet-strings blocks (landed with the zone
|
||||
// title generator), translated once for exactly this shape, so this is a reuse rather than new strings.
|
||||
const names = lists.map((l) => l.label || l.id);
|
||||
const S = await packetStrings();
|
||||
const join2 = (a, b) => fillJoin(S.zone_join, { a, b }) ?? `${a} + ${b}`;
|
||||
const label = names.length <= 3
|
||||
? names.join(' + ')
|
||||
: `${names.slice(0, 2).join(' + ')} +${names.length - 2} more`;
|
||||
? names.reduce(join2)
|
||||
: fillJoin(S.zone_join_more, { a: names.slice(0, 2).reduce(join2), n: names.length - 2 })
|
||||
?? `${names.slice(0, 2).join(' + ')} +${names.length - 2} more`;
|
||||
return { id: 'selection', label, count: slugs.size,
|
||||
selector: { type: 'pg_slugs', values: [...slugs] } };
|
||||
}
|
||||
|
|
@ -2042,6 +2068,29 @@ onMounted(async () => {
|
|||
// mapbox-under-maplibre reference in this profile, carried as a named exemption in the
|
||||
// maplibre-embed gate; guarding it lets that exemption be deleted and enforced instead.
|
||||
// MapLibre ships its own ctrl CSS via ensureEngineScript, so nothing is lost.
|
||||
// ★ THE LIBRARY'S ensureEngineCSS, NOT A HAND-ROLLED COPY (rega END-REGA-SRV / END-DXS-U100C,
|
||||
// 2026-09-27). Mapbox logged "missing CSS declarations" on the BUILT leaf and in the gallery, and the
|
||||
// cause was this block, not a missing <link>:
|
||||
// (a) it only ran when the map element was inside a ShadowRoot — `root instanceof ShadowRoot` — so on
|
||||
// a standalone leaf, where the element is in LIGHT DOM, no stylesheet was ensured at all; and
|
||||
// (b) even in the shadow case it FIRED AND FORGOT, so `new gl.Map` could run before the sheet parsed.
|
||||
// Mapbox checks its declarations AT CONSTRUCTION, which is exactly when the warning appears.
|
||||
// ensureEngineCSS handles both DOM cases (light DOM returns early because the wrapper <link> covers it)
|
||||
// and AWAITS the load/error event with a 2s cap, so construction happens after the sheet is live.
|
||||
// ⚠ AND IT TAKES THE PROVIDER'S OWN URL rather than 'mapbox' hardcoded — the old line asked the registry
|
||||
// for mapbox's stylesheet even under MapLibre, guarded by !isMaplibre. Passing upgProvider means the
|
||||
// guard is unnecessary: a maplibre page gets maplibre's sheet (or none) and never touches
|
||||
// api.mapbox.com, which is the keyless contract the maplibre-embed gate enforces.
|
||||
// MEASURED as the difference from cities-internet, which does not warn: connectivity-map.vue:398-399
|
||||
// calls exactly this pair and this profile called neither.
|
||||
try {
|
||||
const css = engineStylesheetUrl(upgProvider)
|
||||
if (css) await ensureEngineCSS(mapEl.value, css)
|
||||
} catch (_) { /* a stylesheet must never stop the map painting */ }
|
||||
// The legacy shadow-root injection stays ONLY as the belt to that braces: ensureEngineCSS returns early
|
||||
// for light DOM on the assumption a wrapper <link> exists, and a partner embedding the custom element
|
||||
// directly may have no wrapper at all. Harmless when ensureEngineCSS already added the same href — both
|
||||
// paths check for it by href before inserting.
|
||||
try {
|
||||
const shadow = !isMaplibre && mapEl.value && mapEl.value.getRootNode()
|
||||
if (shadow instanceof ShadowRoot) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue