Skip to content

LayerFilter foundation: listed-flag pipeline + faceted engine + activation catalogue loader #219

Description

@BhattaraiSijan

Parent: #164 (LayerFilter plugin). First PR of the revamp; no UI changes.

Problem

Three missing pieces stand between the current flat filter and the
Disasters designs, and they form one coherent machinery layer:

  1. Filtering has no visible effect — the plugin only announces matches
    on the bus. The agreed mechanism is a runtime "shown in layer lists"
    flag in core state: the filter writes it in bulk, the layer panel reads
    it reactively, and what's drawn on the map is never touched.
  2. The filter model can't express the Event theme. The agreed model:
    the working set is the join of layers and activations (one row per
    pairing, plus a row per unpaired layer), and every filter — sector,
    hazard, year, and the activation picker itself — is a facet over those
    rows, all adapting to each other by one cascade rule.
  3. Nothing produces those rows. Activations are authored as a GeoJSON
    FeatureCollection (STAC datetime conventions) listing each event's
    metadata and layer associations; a loader must parse it, join it to the
    mission's layers via stable per-layer keys, and derive the computed
    values (year spans, active status).

Full data contract: src/essence/Tools/LayerFilter/SCHEMA.md.

Outcome

Listed-flag pipeline (implementation already recovered, tests green —
review-and-land as part of this PR):

  • Filtering narrows the layer panel immediately; clearing restores it; map
    visibility is user-owned and never modified; the flag is runtime-only
    and survives config re-parses; boot never narrows (filter engages on
    first user interaction); removed layers clean up their flag.

Faceted engine (pure, app-agnostic, no MMGIS imports, no UI):

  • Per facet: available options with counts, computed from rows passing
    every other facet (never itself) — narrowing year adapts hazard and
    the activation list, and vice versa, without trapping the user.
  • Selected values that disappear from availability auto-clear.
  • Selection state is per theme and survives theme switches.
  • Facets are enumerated (dropdowns) or predicate (arbitrary row test —
    the seam a later geocode facet plugs into).
  • Multi- vs single-select per facet; case-insensitive value matching.

Catalogue loader + Configure split:

  • Two separate configuration fields (themes/filters vs activation
    catalogue — different authors, different cadence; catalogue swappable
    for a hosted URL later without schema change).
  • Catalogue parsed and joined to layers; per-pairing attributes
    (default-on, opacity, view override) flow onto rows untouched.
  • Derived: year(s) from the full date-range span (a Dec–Feb event matches
    both years); active status from end-date absence.
  • Graceful degradation: dangling layer keys warn and skip; malformed
    catalogue JSON fails soft leaving layer-only themes functional.

Acceptance

  • Demo config: select a sector → panel narrows; clear → restores;
    toggled-on layers stay drawn throughout; fresh load shows the full
    panel.
  • Table-driven engine tests: two-way cascade adaptation, own-facet
    exemption, auto-clear invalidation, multi-select semantics,
    year-boundary span, unpaired-layer rows, empty selection = no
    narrowing.
  • Loader tests: schema-doc example catalogue → expected row set;
    dangling key warns and skips; end_datetime: null ⇒ active;
    malformed input fails soft.
  • Engine package contains no MMGIS import and no Disasters-domain
    word (grep-verifiable in review).
  • Both theme styles (layer-scoped and catalogue-scoped) expressible
    purely as configuration in tests.

Est. PR size: ~600 substantive lines excluding tests (130 recovered +
~250 engine + ~200 loader) — within the ~800 cohesive-unit bound. If it
threatens 1000 during implementation, the loader is the natural split.

Implementation sketch — written as of 61849d9d on 2026-07-23; rough guide, re-verify against latest

Listed flag (recovered, uncommitted on feat/filter-plugin):

  • Layers_.js: L_.layers.listed (uuid → bool, absent = listed; kept
    outside configData because resetConfig re-parses that), providers
    layers:getListed / layers:setListed({ updates, source }) (source
    reserved for multi-writer arbitration), emits layer:listedChange;
    cleanup in removeLayerFromLayersData.
  • Gotcha: layer:listedChange singular naming is deliberate — kept
    distinct from the existing layers:listChanged (add/remove). Don't
    "fix" it.
  • LayerManager/adapters/getVisibleLayersWithLegends.ts skips
    listed === false; LayerFilter writes the complement map via
    buildListedUpdates in emitFilterChange, gated on first interaction.
  • Exclude configure/public/toolConfigs.json from the commit (not ours).

Engine: lib/engine/types.ts (Row, FacetDef enumerated|predicate,
ThemeDef, SelectionState), facets.ts (options/counts with own-facet
exemption), derive.ts (yearsFromDatetimes, isActive, case-fold),
engine.ts (apply + invalidate). Row = (layer, activationEdge|null, edgeAttrs); results = distinct layers of surviving rows. Supersedes
lib/utils/matchLayers.ts — keep it until the UI PR migrates, then retire.
Open call: multi-valued tag vs multi-select filter — any-overlap vs
contains-all is undecided; implement any-overlap default, alternative
behind per-facet config, state it in the PR description.

Loader: config fields declared in the plugin config.json variables
(mirror the existing themes-JSON field). Loader in adapters/ (needs
layers:getAllConfigs over the bus — bus-only rule). Pure parsing/
derivation in lib/ for tests. Gotcha: never reference MMGIS layer
name (ingest-generated UUID) from the catalogue — stable key is
layer.properties.key; display_name is NOT unique (nameToUUID is an
array). Keep the loader input a plain FeatureCollection so a future
hosted-URL/STAC source is a swap, not a migration.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions