The country outline never drew on MapLibre: the worldview clause emptied its layer

Second site of the defect rega found on regions-mfe, and this one is shared by three
plugins — country-posters, doxa-simple-map and doxa-research-map.

The source asymmetry here was already handled carefully: MapLibre gets a geojson
source instead of the mapbox:// tileset, the loader bridges iso3 -> iso_3166_1_alpha_3,
and the layer drops 'source-layer' because MapLibre rejects it on geojson. What was
missed is one field deeper. countryFilter() also ANDs a worldview clause, which exists
because the tileset repeats a country once per worldview and the outline would otherwise
draw two or three times over. world-adm0.geojson has no such duplication and carries no
worldview property, 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, which
looks exactly like boundary data that 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 tileset's shape, so only the Mapbox path asks for it.

The engine sniff already in the file moves UP, above the filter. It sat inside the
try block, below the line that built the filter, so the filter could not have consulted
it; the fast path at the top (setFilter on an existing layer) reuses the same filter and
is now covered by the same branch.

MEASURED, fail-first, against the real 162 features with MapLibre's own style-spec parser:
  the worldview clause admits    0 / 162
  the MapLibre branch admits     1 / 1   (== one country, this composable's shape)
  the Mapbox path still collapses a duplicate worldview copy to 1
The check also REGISTERS both worldview sites in the tree and fails on a new unlisted
one, so the class cannot come back quietly in a component nobody re-shoots.

Not landed here: three consumer bundles compile this, so it needs their rebuild.

(cherry picked from commit b483606054)
This commit is contained in:
Doxa Maps 2026-09-26 01:14:31 +00:00
parent 6e639923af
commit 730df6a69a

View file

@ -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.