Harden MapLibre style readiness
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-06-17 00:59:46 +02:00
parent 8c3813d442
commit c6c8a54931
5 changed files with 19 additions and 10 deletions
+1
View File
@@ -11,6 +11,7 @@
- Added a compact frontend status strip for project, AOI, datasets, active map layer, QA/QC and exports.
- The strip is driven by existing App state and suggests the next operator action in the V1 loop.
- Hardened the MapLibre component so GeoJSON sources/layers wait for the map style to finish loading before updates run.
- Added regression coverage to ensure the strip remains wired without introducing new API calls.
- No backend behavior, migrations, API contracts, provider downloads, AI inference or new dependencies were introduced.
@@ -13,6 +13,8 @@ def test_geomap_exposes_v1_layer_controls_and_feature_inspection_contract() -> N
assert "opacity?: number" in geomap
assert "onFeatureSelect?: (feature: GeoJSON.Feature | null) => void" in geomap
assert "queryRenderedFeatures" in geomap
assert "mapStyleReady" in geomap
assert "map.isStyleLoaded()" in geomap
assert "setLayoutProperty('dataset-fill', 'visibility'" in geomap
assert "setPaintProperty('dataset-fill', 'fill-opacity', opacity)" in geomap
+1 -1
View File
@@ -3,7 +3,7 @@
Changed:
- Added `frontend/src/components/WorkbenchStatusStrip.tsx` to summarize existing V1 state for project, AOI, datasets, active map layer, QA/QC and exports.
- Wired the status strip into `frontend/src/App.tsx` using existing orchestration state only.
- Added compact status-strip styling and regression tests for the frontend wiring contract.
- Added compact status-strip styling and regression tests for the frontend wiring contract.`n- Hardened `frontend/src/components/GeoMap.tsx` so MapLibre source/layer updates wait for style readiness before adding sources.
- Updated frontend README, TODO and changelog.
Tested:
+1
View File
@@ -162,6 +162,7 @@ React + TypeScript + MapLibre foundation for project/area/dataset workflow.
- The strip summarizes existing connected state for project, AOI, datasets, active map layer, persisted QA/QC results and exports.
- It suggests the next operator action based on missing V1 loop state without calling new APIs or adding backend behavior.
- The status strip is implemented in `src/components/WorkbenchStatusStrip.tsx` and remains driven by `App.tsx` orchestration state.
- MapLibre source/layer updates now wait for style readiness to avoid runtime blank-screen failures during first render.
## Release hardening updates
+14 -9
View File
@@ -1,4 +1,4 @@
import { useEffect, useRef } from 'react'
import { useEffect, useRef, useState } from 'react'
import maplibregl from 'maplibre-gl'
import 'maplibre-gl/dist/maplibre-gl.css'
@@ -63,6 +63,7 @@ function GeoMap({
const containerRef = useRef<HTMLDivElement | null>(null)
const mapRef = useRef<maplibregl.Map | null>(null)
const onFeatureSelectRef = useRef<GeoMapProps['onFeatureSelect']>(onFeatureSelect)
const [mapStyleReady, setMapStyleReady] = useState(false)
useEffect(() => {
onFeatureSelectRef.current = onFeatureSelect
@@ -80,6 +81,9 @@ function GeoMap({
zoom: 9,
})
map.addControl(new maplibregl.NavigationControl(), 'top-right')
map.on('load', () => {
setMapStyleReady(true)
})
map.on('click', (event) => {
const layers = ['dataset-fill', 'dataset-line', 'area-fill', 'area-line'].filter((layerId) => map.getLayer(layerId))
if (layers.length === 0) {
@@ -101,12 +105,13 @@ function GeoMap({
return () => {
map.remove()
mapRef.current = null
setMapStyleReady(false)
}
}, [])
useEffect(() => {
const map = mapRef.current
if (!map) {
if (!map || !mapStyleReady || !map.isStyleLoaded()) {
return
}
@@ -174,11 +179,11 @@ function GeoMap({
}
}
}
}, [data])
}, [data, mapStyleReady])
useEffect(() => {
const map = mapRef.current
if (!map) {
if (!map || !mapStyleReady || !map.isStyleLoaded()) {
return
}
@@ -231,11 +236,11 @@ function GeoMap({
map.fitBounds(bounds, { padding: 40 })
}
}
}, [areaData, data])
}, [areaData, data, mapStyleReady])
useEffect(() => {
const map = mapRef.current
if (!map) {
if (!map || !mapStyleReady || !map.isStyleLoaded()) {
return
}
const visibility = visible ? 'visible' : 'none'
@@ -247,11 +252,11 @@ function GeoMap({
map.setLayoutProperty('dataset-line', 'visibility', visibility)
map.setPaintProperty('dataset-line', 'line-opacity', visible ? 1 : 0)
}
}, [visible, opacity, data])
}, [visible, opacity, data, mapStyleReady])
useEffect(() => {
const map = mapRef.current
if (!map) {
if (!map || !mapStyleReady || !map.isStyleLoaded()) {
return
}
const visibility = areaVisible ? 'visible' : 'none'
@@ -263,7 +268,7 @@ function GeoMap({
map.setLayoutProperty('area-line', 'visibility', visibility)
map.setPaintProperty('area-line', 'line-opacity', areaVisible ? 1 : 0)
}
}, [areaVisible, areaOpacity, areaData])
}, [areaVisible, areaOpacity, areaData, mapStyleReady])
return <div className="map-container" ref={containerRef} />
}