Parent: #164 (LayerFilter plugin). Depends on: filter foundation (merged).
Activation-selection side effects (default-on / fly-to / time) are out of
scope — pending decisions tracked in SCHEMA.md §8; this UI ships without them.
Problem
With the machinery in place, the visible product remains: all three theme
panels running on the engine. Need and Hazard still run the old flat
matcher with hand-enumerated dropdown options (a second copy of the
taxonomy that drifts from the data). The Event theme — facets over the
activation catalogue, an adapting activation picker, results = the picked
activation's layers — doesn't exist at all. And its Location filter is
defined as a geocode search over activation geometries (no location field
exists anywhere), following the same external-geocoder pattern already
used by the map-control search.
Outcome
Need & Hazard on the engine:
- Dropdowns populated from distinct values found on the mission's layers
(display-ready values, arrays), each option with its matching-layer
count; zero options in configuration; the old per-layer theme/location/
year fields are gone and ignored.
- Selections narrow the layer panel via the listed flag; persist per theme
across switches; result counts match the design's "N Results".
Event theme:
- Hazard-type and Year facets plus the activation picker, all mutually
adapting per the cascade; "N activations" reflects current facets; each
option carries a year badge; the selected activation shows its date
range and an ACTIVE pill when ongoing.
- Picking an activation narrows the layer panel to its layers; narrowing
facets past the selection auto-clears it and un-narrows.
- Side effects (auto-toggle default-on layers, fly-to, time) are out of
scope, pending the SCHEMA.md §8 decisions — pairing attributes flow through
untouched.
Location (geocode) facet:
- Typeahead search against the configured geocoder; chosen place shown as
a clearable chip; activations pass by geometric intersection (polygon
results intersect properly; point-geometry activations match containing
regions). No geocoder configured → the control hides; theme still works.
- Outward-only cascade accepted and documented: other facets don't narrow
free text, but counts always reflect the full selection.
Acceptance
Est. PR size: ~800–1000 substantive lines excluding tests (~250
Need/Hazard rewire + ~350 Event + ~250 geocode) — at the agreed upper
bound. Pre-agreed split seam if it overruns: the geocode facet lifts out
cleanly as a follow-up PR (the predicate seam ships regardless).
Implementation sketch — written as of 61849d9d on 2026-07-23; rough guide, re-verify against latest
- Replace
lib/utils/matchLayers.ts usage with engine calls; retire it
and the hardcoded options arrays from config.json themes.
- Rail (
LayerFilterThemes) untouched — already pulls themes over the bus
(layerFilter:getThemes).
- Listed writes stay in
emitFilterChange → layers:setListed
(landed by the foundation PR), still gated on first interaction.
- Event theme config:
catalog: "activations", facets from: "catalog",
picker facet isEntry: true, year facet derived: "datetimes"
(SCHEMA.md §5). Results list reuses the existing checkbox-list
components; picker rows (year chip + title) per the Figma designs the
current panels already follow.
- Geocode: check how MapControl invokes its geocoder and reuse the
service/config pattern (possibly extract a Tools/_shared/ helper).
Nominatim-style polygon_geojson=1 gives admin polygons; fall back to
bbox. Intersection via turf booleanIntersects (existing dependency),
computed on selection only. Engine-side it's a predicate facet:
selection { label, geometry }.
- Do NOT wire
defaultOn/view/time even though rows carry the edge
attributes — the SCHEMA.md §8 decisions gate that.
Parent: #164 (LayerFilter plugin). Depends on: filter foundation (merged).
Activation-selection side effects (default-on / fly-to / time) are out of
scope — pending decisions tracked in SCHEMA.md §8; this UI ships without them.
Problem
With the machinery in place, the visible product remains: all three theme
panels running on the engine. Need and Hazard still run the old flat
matcher with hand-enumerated dropdown options (a second copy of the
taxonomy that drifts from the data). The Event theme — facets over the
activation catalogue, an adapting activation picker, results = the picked
activation's layers — doesn't exist at all. And its Location filter is
defined as a geocode search over activation geometries (no location field
exists anywhere), following the same external-geocoder pattern already
used by the map-control search.
Outcome
Need & Hazard on the engine:
(display-ready values, arrays), each option with its matching-layer
count; zero options in configuration; the old per-layer theme/location/
year fields are gone and ignored.
across switches; result counts match the design's "N Results".
Event theme:
adapting per the cascade; "N activations" reflects current facets; each
option carries a year badge; the selected activation shows its date
range and an ACTIVE pill when ongoing.
facets past the selection auto-clears it and un-narrows.
scope, pending the SCHEMA.md §8 decisions — pairing attributes flow through
untouched.
Location (geocode) facet:
a clearable chip; activations pass by geometric intersection (polygon
results intersect properly; point-geometry activations match containing
regions). No geocoder configured → the control hides; theme still works.
free text, but counts always reflect the full selection.
Acceptance
correct counts and update the panel live; a two-sector layer appears
under both without duplication; adding a tag to a layer config and
reloading adds the option with no plugin config change.
values while Year itself keeps all years listed.
ongoing activation shows ACTIVE; flipping Year to a non-matching
value resets the picker to placeholder.
(statewide polygon matches, out-of-state point doesn't); clearing
the chip restores; endpoint from configuration only
(grep-verifiable); unreachable geocoder degrades with a message.
--theme-*) consistent across all threethemes; intersection logic unit-tested (polygon/polygon,
point/polygon, disjoint).
Est. PR size: ~800–1000 substantive lines excluding tests (~250
Need/Hazard rewire + ~350 Event + ~250 geocode) — at the agreed upper
bound. Pre-agreed split seam if it overruns: the geocode facet lifts out
cleanly as a follow-up PR (the predicate seam ships regardless).
Implementation sketch — written as of
61849d9don 2026-07-23; rough guide, re-verify against latestlib/utils/matchLayers.tsusage with engine calls; retire itand the hardcoded
optionsarrays fromconfig.jsonthemes.LayerFilterThemes) untouched — already pulls themes over the bus(
layerFilter:getThemes).emitFilterChange→layers:setListed(landed by the foundation PR), still gated on first interaction.
catalog: "activations", facetsfrom: "catalog",picker facet
isEntry: true, year facetderived: "datetimes"(SCHEMA.md §5). Results list reuses the existing checkbox-list
components; picker rows (year chip + title) per the Figma designs the
current panels already follow.
service/config pattern (possibly extract a
Tools/_shared/helper).Nominatim-style
polygon_geojson=1gives admin polygons; fall back tobbox. Intersection via turf
booleanIntersects(existing dependency),computed on selection only. Engine-side it's a predicate facet:
selection
{ label, geometry }.defaultOn/view/time even though rows carry the edgeattributes — the SCHEMA.md §8 decisions gate that.