# Story 1.3: Add Layers Registry as the Ordering Source of Truth Status: in-progress ## Story As a map maintainer, I want a centralized Layers registry, so that visibility, order, and surface ownership are managed consistently instead of inferred from DOM position. ## Acceptance Criteria 1. Given logical map layers currently rely on DOM placement and ad hoc lookups, when a layer is registered, then the registry stores its `id`, `kind`, `order`, `visible` state, and surface handle, and callers can query layer state without inferring it from DOM order. 2. Given runtime layer order changes, when reorder operations are applied through the registry, then all registered layer surfaces receive the updated ordering atomically, and the registry remains the single source of truth for visibility and order. ## Tasks / Subtasks - [x] Create the Layers registry module. - [x] Define the minimum record shape: `id`, `kind`, `order`, `visible`, `surface`. - [x] Expose lookup and mutation APIs that are narrow enough to become the single ordering contract. - [x] Bootstrap the current layer stack into the registry. - [x] Register the existing logical SVG layers in their current order. - [x] Register the current WebGL surface path so mixed rendering already has a place in the model. - [x] Move order and visibility mutations behind the registry. - [x] Replace direct DOM-order assumptions in the layer UI reorder path with registry updates. - [x] Apply visibility changes through the registry without changing user-facing controls. - [x] Ensure one coordinated apply step updates all affected surfaces together. - [x] Preserve compatibility for migration-era callers. - [x] Keep existing layer IDs and toggle IDs stable. - [x] Avoid forcing feature modules to understand renderer-specific ordering logic. - [ ] Perform manual smoke verification. - [ ] Reordering through the existing Layers UI still changes the visible stack correctly. - [ ] Visibility toggles still map to the correct runtime surface. ## Dev Notes ### Context Summary - The current reorder path is still DOM-driven. `public/modules/ui/layers.js` maps toggle IDs to concrete DOM groups in `getLayer(id)` and then calls `insertAfter` or `insertBefore` directly in `moveLayer`. - The initial stack order is created in `public/main.js` by appending groups to `viewbox` in sequence. Older-map migration code in `public/modules/dynamic/auto-update.js` also inserts groups relative to concrete siblings such as `#terrain` and `#borders`. - The registry has to become the source of truth without breaking those legacy IDs immediately. This story is about authoritative state ownership, not yet about fully removing all DOM-based insertion helpers. ### Technical Requirements - Implement the registry in `src/` as a global module, likely `src/modules/layers.ts`. - Keep the public contract minimal. Do not add export metadata yet; that belongs to Epic 4. - Store actual surface handles, not just selectors, so later stories can mount independent SVG shells and WebGL surfaces under one contract. - Ensure reorder application is atomic from the user perspective. Avoid partial states where one surface has moved and another has not. - Keep the registry boring. No factory layers, schema systems, or speculative metadata beyond `id`, `kind`, `order`, `visible`, and `surface`. ### Architecture Compliance - This story implements Decision 1 from the architecture: the Layers registry is the single source of truth for ordering and visibility. - The runtime must support arbitrary future ordering of SVG and WebGL layers. Do not hard-code separate ordering buckets. - The registry should own state; individual layer modules should not infer state from the DOM. ### Previous Story Intelligence - Story 1.1 introduces stable runtime hosts; the registry should refer to those hosts rather than embedding DOM queries in each layer record. - Story 1.2 centralizes shared transform state; do not mix camera ownership into the registry API. - The current sortable UI already maps toggle IDs to concrete layer IDs in `getLayer(id)`. Preserve those IDs so Story 1.5 can bridge legacy callers cleanly. ### Project Structure Notes - Expected touch points: - `src/modules/layers.ts` - `src/modules/index.ts` - `src/types/global.ts` - `public/modules/ui/layers.js` - `public/main.js` - `public/modules/dynamic/auto-update.js` - Keep migration focused. Do not rewrite every legacy caller in this story. - Keep the registry implementation in `src/` and expose only the smallest legacy-facing hooks needed in `public/modules/ui/layers.js`. ### Testing Notes - Manual verification is sufficient for this tranche. - Verify the existing sortable Layers UI still works and that no layer disappears from the visible stack after a reorder. - Do not add Playwright coverage or new automated test infrastructure in this story. ### Dependencies - Story 1.1 provides runtime host references. - Story 1.2 provides the shared Scene contract that registry-managed surfaces will consume. ### References - [Source: _bmad-output/planning-artifacts/epics.md, Epic 1, Story 1.3] - [Source: _bmad-output/planning-artifacts/architecture-layered-map-dom-split.md, Decision 1, Decision 2, Migration Plan] - [Source: public/modules/ui/layers.js, `moveLayer` and `getLayer`] - [Source: public/main.js, layer append order] - [Source: public/modules/dynamic/auto-update.js, compatibility inserts relative to existing siblings] ## Dev Agent Record ### Agent Model Used GitHub Copilot (Claude Sonnet 4.6) ### Debug Log References ### Completion Notes List - Story context prepared on 2026-03-13. - Implementation completed 2026-03-13. - Created `src/modules/layers.ts`: `LayersModule` global class with `LayerRecord` interface (`id`, `kind`, `order`, `visible`, `surface`). Exports `register()`, `get()`, `getAll()`, `moveAfter()`, `moveBefore()`, `setVisible()`. DOM sync is atomic — one element move per call. Registered on `window.Layers`. - Added `import "./layers"` to `src/modules/index.ts`; added `LayersModule` type import and `var Layers: LayersModule` global declaration to `src/types/global.ts`. - In `public/main.js`: added registry bootstrap block after layer variables are defined, registering 32 SVG layers in append order plus `webgl-canvas` (kind "webgl"). Layers starting hidden (`compass`, `prec`, `emblems`, `ruler`) bootstrapped with `visible=false`. - In `public/modules/ui/layers.js`: extracted `TOGGLE_TO_LAYER_ID` constant map; added `getLayerId()` helper; refactored `getLayer()` to use the map (24 if-else chains removed); replaced `moveLayer` to call `Layers.moveAfter()`/`Layers.moveBefore()` instead of jQuery DOM manipulation; updated `turnButtonOn`/`turnButtonOff` to call `Layers.setVisible()`; updated `applyLayersPreset` to call `Layers.setVisible()` per layer. - `auto-update.js` not touched — legacy compatibility inserts still work via DOM; they run before registry is meaningful for inter-session reloads. - Automated tests skipped per project instruction. Manual smoke verification pending. ### File List - src/modules/layers.ts (new) - src/modules/index.ts (modified) - src/types/global.ts (modified) - public/main.js (modified) - public/modules/ui/layers.js (modified)