diff --git a/library/composables/useCountryOutline.js b/library/composables/useCountryOutline.js index 129e9731e..6f514ebad 100644 --- a/library/composables/useCountryOutline.js +++ b/library/composables/useCountryOutline.js @@ -73,10 +73,28 @@ export function resolveCountryIso(evt, pgs) { // 'all', 'US', 'CN', 'IN'); without a worldview filter an outline would be // drawn 2-3× over itself. Mirror the homepage CountryMap.vue filter: keep the // 'all' worldview plus the US-recognized worldview. -function countryFilter(code) { +// The ENGINE is read off the live map rather than guessed from config: the container carries +// maplibregl-map or mapboxgl-map, which is a fact about the instance in hand. Consumers call +// useCountryOutline(() => map.value) with no options, so there is no provider to pass in. +function isMaplibreMap(m) { + try { return !!m?.getContainer?.()?.classList?.contains('maplibregl-map') } catch (_) { return false } +} + +function countryFilter(code, isMaplibre) { + const byCode = ['==', ['get', 'iso_3166_1_alpha_3'], code] + // ★ THE WORLDVIEW CLAUSE IS MAPBOX-ONLY, AND ANDING IT ON MAPLIBRE EMPTIES THE LAYER. + // The clause exists for the tileset above; world-adm0.geojson, the MapLibre source added below, + // has no worldview property and no per-worldview duplicates. So ['get','worldview'] is null, + // both sides of the `any` are false on every feature, and the `all` admits NOTHING — the outline + // is absent with no error, exactly as if the boundary data had failed to load. Unlike the ISO + // field, this cannot be bridged in the loader: there is no worldview to invent. It is a fact + // about the Mapbox tileset's shape, so only the Mapbox path asks for it. + // MEASURED: against the real 162 features, the clause admits 0 and the branch below admits its + // country — _qa-cycle/02-builder-cycle/_chk-worldview-clause-guarded-per-engine.mjs. + if (isMaplibre) return byCode return [ 'all', - ['==', ['get', 'iso_3166_1_alpha_3'], code], + byCode, ['any', ['==', ['get', 'worldview'], 'all'], ['in', 'US', ['get', 'worldview']]] ] } @@ -116,7 +134,9 @@ export function useCountryOutline(getMap, opts = {}) { const m = typeof getMap === 'function' ? getMap() : null if (!m) return - const filter = countryFilter(code) + // Engine FIRST: the filter's shape depends on it, and the fast path below reuses this filter. + const isMaplibre = isMaplibreMap(m) + const filter = countryFilter(code, isMaplibre) // Fast path: source/layer already present from a prior pick — just swap the // filter so the highlight moves to the new country with zero teardown. @@ -136,14 +156,7 @@ export function useCountryOutline(getMap, opts = {}) { // the console. THREE consumers share this composable — doxa-simple-map, countries-map and // research-map — so the fix belongs here, once, not three times. // Same shape already proven in DashboardMapPanel and RegionCountryMap. - // - // The ENGINE is read off the live map rather than guessed from config: the container carries - // maplibregl-map or mapboxgl-map, which is a fact about the instance in hand. Consumers call - // useCountryOutline(() => map.value) with no options, so there is no provider to pass in. - const isMaplibre = (() => { - try { return !!m.getContainer?.()?.classList?.contains('maplibregl-map') } catch (_) { return false } - })() - + // isMaplibre is resolved above, before the filter is built — see countryFilter(). if (isMaplibre) { // Add EMPTY first, then setData after the fetch, so addLayer below stays SYNCHRONOUS and // the outline keeps its place in the layer order.