The regions idle paint expression moves to the library that already painted with it

useLegendMapBridge took the idle paint helper as an injected dependency and
called it five times, while each consuming map defined its own copy of the
one-line body and handed it over. The library owned the paint logic and not the
expression it painted with, so the copies could drift apart silently and the
two maps would render the same idle state differently.

Export it from the composable instead, and take it from there in this map. The
injected parameter is deliberately KEPT for one round: inside the composable
the parameter shadows the module export, so what the bridge paints with is
still exactly what its callers pass, and this change cannot move behaviour. It
is a deletion in the plugin and an addition in the library, nothing more. The
parameter goes when the second map has adopted the export too.

This map held a THIRD copy under a different name -- a function-scoped helper
in the paint prewarm, byte-identical body, used once. It now refers to the
library helper.

Verified rather than assumed: the library version and both original plugin
versions produce identical output for every value the maps actually pass,
including the dynamic one. check:region-paint-single-source is green for this
map and stays red for the other until it adopts the export, which is the state
the gate is meant to report.

The second map is the other plugin's file and is not touched here; its owner
takes that half.
This commit is contained in:
Doxa Maps 2026-09-29 12:52:34 +00:00
parent 4ae1bbf4f5
commit 1fde4b9cc4
2 changed files with 29 additions and 3 deletions

View file

@ -89,6 +89,29 @@ export function buildFlatLegendMatchExpr(node, legendType) {
return null
}
/**
* regionIdle — the doxa-regions "idle" paint expression.
*
* ★ WHY THIS LIVES HERE. Both maps that paint the regions layers — research-map.vue and
* countries-map.vue — defined their own copy of this one-liner while ALREADY passing it into this
* composable, which then called it to paint the idle state. So the library owned the paint logic and
* not the expression it painted with, and the two copies could be edited apart with nothing failing:
* the same "idle" would render differently on the two maps. check:region-paint-single-source is the
* gate that now fails if either plugin re-introduces a local copy.
*
* ⚠ THE SAME VALUE ON BOTH BRANCHES IS THE POINT, not a mistake. ['case', cond, v, v] returns v
* whichever way the condition falls — it exists so the expression is shaped like the HIGHLIGHT
* expressions it sits beside (which do differ per branch), and so a region with no
* iso_3166_1_alpha_3 is painted rather than skipped. Collapsing it to a bare `v` would change what
* MapLibre does with featureless regions.
*
* @param {*} v the paint value to apply in both branches
* @returns {Array} a MapLibre 'case' expression
*/
export function regionIdle(v) {
return ['case', ['has', 'iso_3166_1_alpha_3'], v, v]
}
export function useLegendMapBridge({ map, activeTab, countryOutline, regionIdle, mapFly, pplrInstance, mapId }) {
/**
* Drop every highlight the legend may have painted on the country/region layers.

View file

@ -199,7 +199,7 @@ import { useLanguageFamilyLegendData } from '@map/composables/legends/useLanguag
// SemanticTreeLegend instance can render either depending on activeLegendType.
import { useAffinityBlockLegendData } from '@map/composables/legends/useAffinityBlockLegendData.js'
import { useWagfRegionsLegendData } from '@map/composables/legends/useWagfRegionsLegendData.js'
import { useLegendMapBridge, findNodeInTree, buildFlatLegendMatchExpr } from '@map/composables/legends/useLegendMapBridge.js'
import { useLegendMapBridge, findNodeInTree, buildFlatLegendMatchExpr, regionIdle } from '@map/composables/legends/useLegendMapBridge.js'
import { useLegendData as useFlatLegendData } from '@map/composables/legends/useLegendData.js'
import { RESEARCH_LEGEND_OPTIONS } from '@map/composables/legends/researchLegendOptions.js'
import { findScrollportAncestor } from '@map/utils/scrollport.js'
@ -761,7 +761,8 @@ const mapData = useMapData({
// property constant again, so the next highlight recompiles the shader variant the pre-warm exists to
// have already compiled. rega measured 0 ms on a fresh tab; a reader who clears a selection first was
// paying it again, which no run so far has exercised.
const regionIdle = (v) => ['case', ['has', 'iso_3166_1_alpha_3'], v, v]
// regionIdle now comes from the library (useLegendMapBridge), which already called it — this file
// was defining a copy and then passing it in. See check:region-paint-single-source.
async function highlightCountryByIso(iso3) {
const code = String(iso3 || '').trim().toUpperCase()
@ -1107,7 +1108,9 @@ const REGION_IDLE_WIDTH = ['case', ['has', 'iso_3166_1_alpha_3'], 0.6, 0.6]
// does not define is skipped rather than invented, and one already wrapped is left alone.
function prewarmRegionPaint(m) {
if (!m || !m.getLayer('regions-fill')) return
const wrap = (v) => ['case', ['has', 'iso_3166_1_alpha_3'], v, v]
// Was a THIRD local copy of the idle expression, under a different name — byte-identical body.
// The library's regionIdle replaces it exactly.
const wrap = regionIdle
// ★ SKIP WHAT IS ALREADY DATA-DRIVEN, and note the test is not "is it an array". exp-shepherd wrote
// their copy to skip any array, reasoning that an expression is already data-driven so there is no
// variant to warm. That is right for this layer — line-color arrives as a match on a feature property