ensureLayers and addPinLayers guard the layers, not just the source

`if (map.getSource('tapestry')) return` returns having added NOTHING whenever the
source outlives the layers — and that state is real, not hypothetical: setStyle()
wipes LAYERS, which is why paintAll is re-armed on 'style.load' at :1625. The
re-add is a genuine code path (the light/dark toggle) and this guard could silently
no-op it, leaving a map with a source, no polygons and no pins.
addPinLayers had the identical bug on getSource('upg'), which is why territories
and pins would disappear together: one mistake, made twice.

ensureSelectionLayers():652 ALREADY HAD THE RIGHT SHAPE — `if (!map ||
!map.getSource('tapestry')) return; if (map.getLayer('upg-sel-fill')) return`. The
correct pattern was sitting in this file and its two siblings did not copy it.
Presence of a source and presence of the layers drawn from it are two properties;
a function whose job is the second must not test the first.

EACH LAYER IS GUARDED SEPARATELY because addLayer THROWS on an id that already
exists — a half-built style is exactly the state this fix is for, so re-adding a
survivor would abort the function and skip everything after it.

⚠ THIS IS NOT THE CAUSE OF THE BLANK ENGLISH README SHOT, and I am recording that
because I diagnosed it as such and was wrong. I had a confident chain — source
survives layers, half-built style, hence en blank while ar painted — and started
fixing it as the cause. rega settled it with the measurement I did not have: the
leaf paints 9.5-16.5 s after load and the screenshot tool waited 7 s. A timeout,
not a defect. The chain explained the symptom without being true of it, and
claiming this commit fixes that shot would be exactly the false attribution I have
been correcting in other people's reports today.

Kept on its own merits. BUNDLE=my-upg-100-list vite build clean.
This commit is contained in:
Doxa Maps 2026-09-26 08:21:48 +00:00
parent 0952006343
commit ea90e9b288
2 changed files with 95 additions and 65 deletions

View file

@ -604,8 +604,24 @@ function buildMatchColorExpr(zones) {
}
function ensureLayers() {
if (map.getSource('tapestry')) return
map.addSource('tapestry', { type: 'geojson', data: tapestry })
// ⚠ GUARD ON THE LAYERS, NOT ONLY THE SOURCE — "the source exists" and "the layers exist" are two
// properties, and this function's job is the second one. The old guard was
// `if (map.getSource('tapestry')) return`, which returns and adds NOTHING whenever the source
// survives while the layers are gone. That state is real: a style reload wipes LAYERS, and a source
// can be present without them.
// ⚠ THIS IS NOT THE CAUSE OF THE BLANK ENGLISH README SHOT, and I want that on record because I
// diagnosed it as such and was wrong. rega settled that one with the measurement I did not have:
// the leaf paints 9.5-16.5 s after load and the screenshot tool waited 7 s. A timeout, not a defect.
// I had built a confident chain — source-survives-layers, half-built style, en/ar divergence — that
// explained the symptom without being true of it.
// THE GUARD BUG IS STILL REAL, on its own merits and on a different path: setStyle() (the
// light/dark toggle) wipes LAYERS, and a source can outlive them, so the old guard returns having
// added nothing. That is why paintAll is re-armed on 'style.load' at :1625 — the re-add is a real
// code path, and this guard could silently no-op it.
// ensureSelectionLayers():652 already had the right shape (source AND getLayer) — the correct
// pattern was in this file and these two functions did not copy it. addPinLayers had the same bug.
if (map.getSource('tapestry') && map.getLayer('tapestry-fill') && map.getLayer('tapestry-line')) return
if (!map.getSource('tapestry')) map.addSource('tapestry', { type: 'geojson', data: tapestry })
// 09b light-default L-D2: the neon cyber palette runs hot on white. ensureLayers
// is called on the initial draw AND on every post-setStyle theme re-add, so we
// read isDark.value HERE (JS state, not a map expression) to pick per-theme
@ -614,22 +630,30 @@ function ensureLayers() {
// the bright tapestry-LINE outline carries the glow on light either way.
const deafOpacity = isDark.value ? 0.45 : 0.30
const othersOpacity = isDark.value ? 0.65 : 0.45
map.addLayer({
id: 'tapestry-fill',
type: 'fill',
source: 'tapestry',
paint: {
'fill-color': matchColorExpr,
'fill-opacity': ['case', ['==', ['get', 'zoneKey'], 'deaf'], deafOpacity, othersOpacity],
},
})
map.addLayer({
id: 'tapestry-line',
type: 'line',
source: 'tapestry',
// F2-D2: brighter, thicker outline gives contrast on dark without washing out light.
paint: { 'line-color': matchColorExpr, 'line-width': 1.2, 'line-opacity': 0.9 },
})
// ⚠ EACH LAYER IS GUARDED SEPARATELY, because the two can be lost independently — and addLayer
// THROWS on an id that already exists, which would abort this function and skip everything after it.
// A half-built style is exactly the state this fix exists for, so it must not be made worse by
// re-adding a survivor.
if (!map.getLayer('tapestry-fill')) {
map.addLayer({
id: 'tapestry-fill',
type: 'fill',
source: 'tapestry',
paint: {
'fill-color': matchColorExpr,
'fill-opacity': ['case', ['==', ['get', 'zoneKey'], 'deaf'], deafOpacity, othersOpacity],
},
})
}
if (!map.getLayer('tapestry-line')) {
map.addLayer({
id: 'tapestry-line',
type: 'line',
source: 'tapestry',
// F2-D2: brighter, thicker outline gives contrast on dark without washing out light.
paint: { 'line-color': matchColorExpr, 'line-width': 1.2, 'line-opacity': 0.9 },
})
}
// ★ THE REAL READINESS SIGNAL. onLegendSelect() returns early unless map.getLayer('tapestry-fill')
// exists, so a "Show on map" request that arrives before this function runs cannot hold a
@ -2100,8 +2124,14 @@ onMounted(async () => {
// Pin layer add, factored out so BOTH the initial draw() and the post-setStyle
// theme re-add (handleToggleTheme) can call it. Idempotent: guards getSource('upg').
addPinLayers = () => {
if (map.getSource('upg')) return
map.addSource('upg', { type: 'geojson', data: pinFC })
// ⚠ SAME BUG, SAME FIX as ensureLayers():606 — guard on the LAYER, not only the source. The old
// `if (map.getSource('upg')) return` adds nothing whenever the source survives a style reload
// while 'upg-pins' does not. NOT the cause of the blank English README shot (that was a 7 s
// screenshot wait against a 9.5-16.5 s first paint — rega measured it); this is the
// setStyle/theme-toggle path, where the re-add is real and could silently no-op.
if (map.getSource('upg') && map.getLayer('upg-pins')) return
if (!map.getSource('upg')) map.addSource('upg', { type: 'geojson', data: pinFC })
if (map.getLayer('upg-pins')) { attachPinHandlers(); return }
map.addLayer({
id: 'upg-pins', type: 'circle', source: 'upg', paint: {
// BUG-UPG100-DOTS-HARD-TO-SEE (2026-09-19): the previous floor (2.5px @ low zoom,

File diff suppressed because one or more lines are too long