// Re-entrancy contract for DataLayerManager.toggle() (audit M1 ⊗). // // The bug: toggle() awaits the layer's init() + first update() before arming the // polling interval. A second toggle during that window used to interleave — the // disable branch ran mid-enable, the interval was armed AFTER the user turned the // layer off, and a subsequent enable armed a SECOND interval → 2× poll → OpenSky // 429. The fix serializes toggles per-entry and re-checks enabled after the awaits. // // Pure test: the manager only calls the layer module's lifecycle methods and (when // a toggle container is present) DOM refresh. We pass no container, so it stays // headless. Run with: npm test import { test } from 'node:test'; import assert from 'node:assert/strict'; import { DataLayerManager, layerFeedState } from './manager.js'; import { contextSnapshotLayerIds, shouldCaptureContextSession, } from '../contextModePolicy.js'; /** Build a mock layer whose init/update resolve on the next microtask, so a * second toggle can land while the first is awaiting. */ function makeSlowLayer(id, { updateInterval = 1000 } = {}) { const calls = { enable: 0, disable: 0, update: 0, init: 0, presentation: [] }; return { calls, module: { id, name: id, icon: '', source: 'test', updateInterval, async init() { calls.init++; await Promise.resolve(); }, enable() { calls.enable++; }, disable() { calls.disable++; }, async update() { calls.update++; await Promise.resolve(); }, setLifecyclePresentation(state) { calls.presentation.push({ ...state }); }, getStats() { return { count: 0, lastUpdate: null }; }, }, }; } test('keeps panel-hidden coordinator layers registered and addressable', () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('military-awareness', { updateInterval: -1 }); layer.module.showInTogglePanel = false; mgr.register(layer.module); assert.deepEqual(mgr.getAll().map(({ id, showInTogglePanel }) => ({ id, showInTogglePanel })), [ { id: 'military-awareness', showInTogglePanel: false }, ]); assert.equal(mgr.isEnabled('military-awareness'), false); }); test('adopts direct layer params without re-entering the layer setter', () => { let params = { selectedFlightsTrackingId: 'flight-a' }; let setterCalls = 0; const manager = new DataLayerManager({}); manager.register({ id: 'flights', name: 'Flights', icon: '', source: 'test', setParams() { setterCalls += 1; return true; }, getParams() { return { ...params }; }, }); const events = []; manager.subscribe((event) => events.push(event)); assert.equal(manager.adoptLayerParams('flights', { selectedFlightsTrackingId: 'flight-a', }, { origin: 'user' }), true); assert.equal(setterCalls, 0); assert.equal(events.at(-1)?.type, 'params'); assert.equal(events.at(-1)?.params.selectedFlightsTrackingId, 'flight-a'); params = { selectedFlightsTrackingId: 'flight-b' }; assert.equal(manager.adoptLayerParams('flights', { selectedFlightsTrackingId: 'flight-a', }, { origin: 'user' }), false, 'changed live params reject stale adoption'); }); test('adopts settled visibility without re-running lifecycle work', async () => { const manager = new DataLayerManager({}); const layer = makeSlowLayer('flights', { updateInterval: -1 }); manager.register(layer.module); await manager.setEnabled('flights', true, { origin: 'programmatic' }); const events = []; manager.subscribe((event) => events.push(event)); assert.equal(manager.adoptLayerVisibility('flights', true, { origin: 'user', adoptedFromSelection: true, }), true); assert.equal(layer.calls.enable, 1); assert.equal(events.at(-1)?.type, 'visibility'); assert.equal(events.at(-1)?.adoptedFromSelection, true); assert.equal(manager.adoptLayerVisibility('flights', false, { origin: 'user' }), false); }); test('renders ordinary layer rows without recreating a panel-hidden coordinator', async () => { const originalDocument = globalThis.document; const makeElement = () => { const element = { children: [], className: '', dataset: {}, textContent: '', disabled: false, attributes: {}, classList: { toggle() {}, }, appendChild(child) { this.children.push(child); return child; }, addEventListener() {}, removeEventListener() {}, setAttribute(name, value) { this.attributes[name] = String(value); }, querySelector(selector) { if (selector.startsWith('[data-layer-id="')) { const id = selector.slice(16, -2); return this.children.find((child) => child.dataset.layerId === id) || null; } const className = selector.startsWith('.') ? selector.slice(1) : ''; const visit = (node) => { if (String(node.className).split(/\s+/).includes(className)) return node; for (const child of node.children || []) { const found = visit(child); if (found) return found; } return null; }; return visit(this); }, set innerHTML(value) { if (value === '') this.children = []; }, get innerHTML() { return ''; }, }; return element; }; globalThis.document = { createElement: makeElement }; const mgr = new DataLayerManager({}); const ordinary = makeSlowLayer('flights', { updateInterval: -1 }); const coordinator = makeSlowLayer('military-awareness', { updateInterval: -1 }); coordinator.module.showInTogglePanel = false; mgr.register(ordinary.module); mgr.register(coordinator.module); const container = makeElement(); try { mgr.buildTogglePanel(container); assert.ok(container.querySelector('[data-layer-id="flights"]')); assert.equal(container.querySelector('[data-layer-id="military-awareness"]'), null); assert.equal(await mgr.setEnabled('military-awareness', true), true); mgr._refreshTogglePanel(); assert.equal(container.querySelector('[data-layer-id="military-awareness"]'), null); assert.equal(mgr.getAll().find(({ id }) => id === 'military-awareness').enabled, true); } finally { await mgr.destroyAll(); if (originalDocument === undefined) delete globalThis.document; else globalThis.document = originalDocument; } }); test('clearSelectedLayers includes hidden coordinators and preserves newer dependency restoration', async () => { const mgr = new DataLayerManager({}); const order = []; const satellites = makeSlowLayer('satellites', { updateInterval: -1 }); const missions = makeSlowLayer('rocket-launches', { updateInterval: -1 }); const context = makeSlowLayer('military-awareness', { updateInterval: -1 }); context.module.showInTogglePanel = false; satellites.module.disable = () => { order.push('satellites'); }; missions.module.disable = async () => { order.push('rocket-launches'); await mgr.setEnabled('satellites', true, { origin: 'dependency-restore' }); }; context.module.disable = () => { order.push('military-awareness'); }; for (const layer of [satellites, missions, context]) mgr.register(layer.module); await mgr.setEnabled('satellites', true); await mgr.setEnabled('rocket-launches', true); await mgr.setEnabled('military-awareness', true); const result = await mgr.clearSelectedLayers({ origin: 'user' }); assert.deepEqual(result.targetIds, [ 'military-awareness', 'rocket-launches', 'satellites', ]); assert.deepEqual(order, ['military-awareness', 'rocket-launches']); assert.deepEqual(result.clearedIds, ['military-awareness', 'rocket-launches']); assert.deepEqual(result.notClearedIds, ['satellites']); assert.equal(result.items.find(({ id }) => id === 'satellites')?.superseded, true); assert.deepEqual([...mgr.getEnabledLayerIds()], ['satellites']); await mgr.destroyAll(); }); test('clearSelectedLayers absorbs a hidden coordinator fire-and-forget dependency release', async () => { const mgr = new DataLayerManager({}); const flights = makeSlowLayer('flights', { updateInterval: -1 }); const context = makeSlowLayer('military-awareness', { updateInterval: -1 }); context.module.showInTogglePanel = false; let releaseFinished; const finished = new Promise((resolve) => { releaseFinished = resolve; }); context.module.disable = () => { void mgr.setEnabled('flights', false, { origin: 'dependency-release' }) .finally(releaseFinished); }; mgr.register(flights.module); mgr.register(context.module); await mgr.setEnabled('flights', true); await mgr.setEnabled('military-awareness', true); const result = await mgr.clearSelectedLayers({ origin: 'user' }); await finished; assert.deepEqual(result.targetIds, ['military-awareness', 'flights']); assert.deepEqual(result.notClearedIds, []); assert.deepEqual([...mgr.getEnabledLayerIds()], []); }); test('clearSelectedLayers continues after failures and reports final lifecycle truth', async () => { const mgr = new DataLayerManager({}); const ordinary = makeSlowLayer('flights', { updateInterval: -1 }); const failing = makeSlowLayer('traffic', { updateInterval: -1 }); failing.module.disable = () => false; mgr.register(ordinary.module); mgr.register(failing.module); await mgr.setEnabled('flights', true); await mgr.setEnabled('traffic', true); const result = await mgr.clearSelectedLayers(); assert.deepEqual(result.clearedIds, ['flights']); assert.deepEqual(result.notClearedIds, ['traffic']); const failure = result.items.find(({ id }) => id === 'traffic'); assert.equal(failure.enabled, true); assert.equal(failure.lifecycleState, 'enabled'); assert.equal(failure.uncertain, true); assert.equal(await mgr.setEnabled('traffic', false), false); }); test('newer direct layer intent supersedes clearSelectedLayers without a blind retry', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('flights', { updateInterval: -1 }); let releaseDisable; let announceDisable; const disableStarted = new Promise((resolve) => { announceDisable = resolve; }); layer.module.disable = async () => { announceDisable(); await new Promise((resolve) => { releaseDisable = resolve; }); }; mgr.register(layer.module); await mgr.setEnabled('flights', true); const clearing = mgr.clearSelectedLayers({ origin: 'user' }); await disableStarted; const newerEnable = mgr.setEnabled('flights', true, { origin: 'voice' }); releaseDisable(); const result = await clearing; await newerEnable; assert.deepEqual(result.notClearedIds, ['flights']); assert.equal(mgr.isEnabled('flights'), true); assert.deepEqual(mgr.getLayerLifecycleState('flights'), { enabled: true, lifecycleState: 'enabled', uncertain: false, }); }); test('clearSelectedLayers does not issue a delayed OFF after a newer explicit ON', async () => { const mgr = new DataLayerManager({}); const flights = makeSlowLayer('flights', { updateInterval: -1 }); const blocker = makeSlowLayer('traffic', { updateInterval: -1 }); let releaseBlocker; let announceBlocker; const blockerStarted = new Promise((resolve) => { announceBlocker = resolve; }); blocker.module.disable = async () => { announceBlocker(); await new Promise((resolve) => { releaseBlocker = resolve; }); }; mgr.register(flights.module); mgr.register(blocker.module); await mgr.setEnabled('flights', true); await mgr.setEnabled('traffic', true); const clearing = mgr.clearSelectedLayers({ origin: 'user' }); await blockerStarted; await mgr.setEnabled('flights', true, { origin: 'voice' }); releaseBlocker(); const result = await clearing; const flightsResult = result.items.find(({ id }) => id === 'flights'); assert.equal(flightsResult.superseded, true); assert.equal(flightsResult.enabled, true); assert.deepEqual(result.notClearedIds, ['flights']); }); test('clearSelectedLayers skips delayed OFF for every newer absolute-intent origin', async (t) => { for (const origin of ['user', 'voice', 'programmatic', 'dependency-restore', 'context-restore']) { await t.test(origin, async () => { const mgr = new DataLayerManager({}); const flights = makeSlowLayer('flights', { updateInterval: -1 }); const blocker = makeSlowLayer('traffic', { updateInterval: -1 }); let releaseBlocker; let announceBlocker; const blockerStarted = new Promise((resolve) => { announceBlocker = resolve; }); blocker.module.disable = async () => { announceBlocker(); await new Promise((resolve) => { releaseBlocker = resolve; }); }; mgr.register(flights.module); mgr.register(blocker.module); await mgr.setEnabled('flights', true); await mgr.setEnabled('traffic', true); const clearing = mgr.clearSelectedLayers({ origin: 'user' }); await blockerStarted; await mgr.setEnabled('flights', true, { origin }); releaseBlocker(); const result = await clearing; const flightsResult = result.items.find(({ id }) => id === 'flights'); assert.equal(flightsResult.superseded, true); assert.equal(flightsResult.enabled, true); assert.deepEqual(result.notClearedIds, ['flights']); await mgr.destroyAll(); }); } }); test('Clear All reserves its complete OFF baseline before sequential teardown', async () => { const mgr = new DataLayerManager({}); const first = makeSlowLayer('flights', { updateInterval: -1 }); const blocker = makeSlowLayer('traffic', { updateInterval: -1 }); let releaseBlocker; let markBlockerStarted; const blockerStarted = new Promise((resolve) => { markBlockerStarted = resolve; }); blocker.module.disable = async () => { markBlockerStarted(); await new Promise((resolve) => { releaseBlocker = resolve; }); }; mgr.register(first.module); mgr.register(blocker.module); await mgr.setEnabled('flights', true); await mgr.setEnabled('traffic', true); const clearing = mgr.clearSelectedLayers({ origin: 'user' }); await blockerStarted; assert.deepEqual( [...mgr.getEnabledLayerIds()], [], 'all captured targets are effectively OFF before the first awaited teardown settles', ); await mgr.setEnabled('flights', true, { origin: 'voice' }); assert.deepEqual( [...mgr.getEnabledLayerIds()], ['flights'], 'a later layer intent supersedes only its own Clear reservation', ); releaseBlocker(); const result = await clearing; assert.equal(result.items.find(({ id }) => id === 'flights')?.superseded, true); assert.equal(mgr.isEnabled('flights'), true); await mgr.destroyAll(); }); test('a current queued request aborted before its turn publishes one exact cancellation', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('rocket-launches', { updateInterval: -1 }); let releaseToggle; let markToggleStarted; const toggleStarted = new Promise((resolve) => { markToggleStarted = resolve; }); layer.module.enable = async () => { markToggleStarted(); await new Promise((resolve) => { releaseToggle = resolve; }); }; mgr.register(layer.module); const changes = []; mgr.subscribe((change) => changes.push(change)); const occupyingTurn = mgr.toggle('rocket-launches', { origin: 'programmatic' }); await toggleStarted; const controller = new AbortController(); const queued = mgr._setEnabledWithIntent('rocket-launches', false, { origin: 'voice', signal: controller.signal, }); controller.abort(); releaseToggle(); await occupyingTurn; assert.equal(await queued.promise, false); const outcome = await mgr._waitForVisibilityIntent('rocket-launches', queued.intentEpoch); assert.equal(outcome.succeeded, false); assert.equal(outcome.cancellationReason, 'caller-abort'); assert.equal(outcome.phase, 'queued'); const terminal = changes.filter(({ intentEpoch }) => intentEpoch === queued.intentEpoch); assert.deepEqual(terminal.map(({ type }) => type), ['visibility-cancelled']); await mgr.destroyAll(); }); test('visibility lifecycle events retain the exact absolute-intent epoch', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('rocket-launches', { updateInterval: -1 }); const changes = []; mgr.register(layer.module); mgr.subscribe((change) => changes.push(change)); const request = mgr._setEnabledWithIntent('rocket-launches', true, { origin: 'voice' }); assert.equal(await request.promise, true); const ownedChanges = changes.filter(({ layerId }) => layerId === 'rocket-launches'); assert.ok(ownedChanges.length >= 3); assert.ok( ownedChanges.every(({ intentEpoch }) => intentEpoch === request.intentEpoch), 'pre-transition, lifecycle, and settled events remain correlated to one accepted intent', ); await mgr.destroyAll(); }); test('programmatic ON during Clear All active OFF owns final visibility and reporting', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('flights', { updateInterval: -1 }); let releaseDisable; let announceDisable; const disableStarted = new Promise((resolve) => { announceDisable = resolve; }); layer.module.disable = async () => { announceDisable(); await new Promise((resolve) => { releaseDisable = resolve; }); }; mgr.register(layer.module); await mgr.setEnabled('flights', true); const clearing = mgr.clearSelectedLayers({ origin: 'user' }); await disableStarted; const newerEnable = mgr.setEnabled('flights', true, { origin: 'programmatic' }); releaseDisable(); const result = await clearing; await newerEnable; const item = result.items.find(({ id }) => id === 'flights'); assert.equal(item.superseded, true); assert.equal(item.cleared, false); assert.equal(item.lifecycleState, 'enabling'); assert.deepEqual(result.notClearedIds, ['flights']); assert.deepEqual(mgr.getLayerLifecycleState('flights'), { enabled: true, lifecycleState: 'enabled', uncertain: false, }); // The test-owned slow disable is intentionally left installed; a destroy // would start a second unrelated gated disable and obscure this race. }); test('manager exposes enabling and disabling without changing settled visibility early', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('radio', { updateInterval: -1 }); let releaseInit; let announceInit; let releaseDisable; let announceDisable; const initStarted = new Promise((resolve) => { announceInit = resolve; }); const disableStarted = new Promise((resolve) => { announceDisable = resolve; }); layer.module.init = async () => { announceInit(); await new Promise((resolve) => { releaseInit = resolve; }); }; layer.module.disable = async () => { announceDisable(); await new Promise((resolve) => { releaseDisable = resolve; }); }; mgr.register(layer.module); const changes = []; mgr.subscribe((change) => changes.push(change)); const enabling = mgr.setEnabled('radio', true, { origin: 'user' }); await initStarted; assert.deepEqual(mgr.getLayerLifecycleState('radio'), { enabled: false, lifecycleState: 'enabling', uncertain: false, }); assert.equal(mgr.getAll()[0].lifecycleState, 'enabling'); assert.equal(changes.at(-1)?.type, 'visibility-transition'); assert.equal(changes.at(-1)?.settledEnabled, false); assert.deepEqual(layer.calls.presentation.at(-1), { lifecycleState: 'enabling', enabled: false, uncertain: false, }); releaseInit(); assert.equal(await enabling, true); assert.deepEqual(mgr.getLayerLifecycleState('radio'), { enabled: true, lifecycleState: 'enabled', uncertain: false, }); assert.deepEqual(layer.calls.presentation.at(-1), { lifecycleState: 'enabled', enabled: true, uncertain: false, }); const disabling = mgr.setEnabled('radio', false, { origin: 'user' }); await disableStarted; assert.deepEqual(mgr.getLayerLifecycleState('radio'), { enabled: true, lifecycleState: 'disabling', uncertain: false, }); assert.equal(changes.at(-1)?.type, 'visibility-transition'); assert.equal(changes.at(-1)?.settledEnabled, true); assert.deepEqual(layer.calls.presentation.at(-1), { lifecycleState: 'disabling', enabled: true, uncertain: false, }); releaseDisable(); assert.equal(await disabling, true); assert.deepEqual(mgr.getLayerLifecycleState('radio'), { enabled: false, lifecycleState: 'disabled', uncertain: false, }); assert.deepEqual(layer.calls.presentation.at(-1), { lifecycleState: 'disabled', enabled: false, uncertain: false, }); assert.deepEqual( changes.filter(({ type }) => type === 'visibility').map(({ enabled }) => enabled), [true, false], ); }); test('double-toggle during the awaited enable leaves the layer OFF with no leaked interval', async () => { const mgr = new DataLayerManager(/* viewer */ {}); const layer = makeSlowLayer('flights'); mgr.register(layer.module); // Fire two toggles back-to-back WITHOUT awaiting the first — the classic // voice+click / double-click race. const p1 = mgr.toggle('flights'); // enable const p2 = mgr.toggle('flights'); // should be treated as the disable await Promise.all([p1, p2]); // Net effect of enable-then-disable: layer is OFF... assert.equal(mgr.isEnabled('flights'), false, 'layer should end disabled'); // ...and NO interval is left running (the leak this fix prevents). const entry = mgr.layers.get('flights'); assert.equal(entry.intervalId, null, 'no polling interval should be armed'); }); test('serialized toggles never arm two intervals (2× poll → 429 guard)', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('military'); mgr.register(layer.module); // enable, disable, enable — rapid-fire, unawaited. const ps = [mgr.toggle('military'), mgr.toggle('military'), mgr.toggle('military')]; await Promise.all(ps); assert.equal(mgr.isEnabled('military'), true, 'odd number of toggles ends enabled'); const entry = mgr.layers.get('military'); assert.notEqual(entry.intervalId, null, 'exactly one interval should be armed'); // enable ran twice, disable once — and only one interval survives. assert.ok(layer.calls.enable >= 1, 'enable was called'); clearInterval(entry.intervalId); // don't leak the timer out of the test }); test('setEnabled is idempotent and serializes with toggle', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('satellites', { updateInterval: 0 }); mgr.register(layer.module); await mgr.setEnabled('satellites', true); assert.equal(mgr.isEnabled('satellites'), true); await mgr.setEnabled('satellites', true); // no-op, already enabled assert.equal(layer.calls.enable, 1, 'no redundant enable'); await mgr.setEnabled('satellites', false); assert.equal(mgr.isEnabled('satellites'), false); const entry = mgr.layers.get('satellites'); assert.equal(entry.intervalId, null, 'stats interval cleared on disable'); }); test('idempotent absolute intent publishes its newer origin without rerunning lifecycle', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('rocket-launches', { updateInterval: -1 }); const changes = []; mgr.register(layer.module); mgr.subscribe((change) => changes.push(change)); await mgr.setEnabled('rocket-launches', true, { origin: 'programmatic' }); changes.length = 0; assert.equal(await mgr.setEnabled('rocket-launches', true, { origin: 'voice' }), true); assert.equal(layer.calls.enable, 1, 'the module is not redundantly enabled'); assert.deepEqual(changes, [{ type: 'visibility', layerId: 'rocket-launches', enabled: true, origin: 'voice', intentEpoch: 2, }]); await mgr.destroyAll(); }); test('an aborted enable is transactionally cancelled without a settled visibility intent', async () => { const mgr = new DataLayerManager({}); const changes = []; let releaseUpdate; let announceUpdate; let updateShouldWait = true; let cleanupFails = true; let moduleActive = false; const updateStarted = new Promise((resolve) => { announceUpdate = resolve; }); const layer = makeSlowLayer('radio', { updateInterval: 0 }); layer.module.enable = () => { layer.calls.enable++; moduleActive = true; }; layer.module.update = async () => { layer.calls.update++; if (!updateShouldWait) return; updateShouldWait = false; announceUpdate(); await new Promise((resolve) => { releaseUpdate = resolve; }); }; // Cleanup failure means the partially enabled module cannot be proven OFF; // retain the conservative ON state without emitting settled visibility. layer.module.disable = () => { layer.calls.disable++; moduleActive = false; if (cleanupFails) throw new Error('cleanup fixture'); }; mgr.register(layer.module); mgr.subscribe((change) => changes.push(change)); const controller = new AbortController(); const enabling = mgr.setEnabled('radio', true, { origin: 'voice', signal: controller.signal, }); await updateStarted; controller.abort(); releaseUpdate(); const changed = await enabling; assert.equal(changed, false); assert.equal(mgr.isEnabled('radio'), true); assert.equal(moduleActive, false, 'module cleanup completed before reporting failure'); assert.equal(mgr.layers.get('radio').lifecycleUncertain, true); assert.equal(mgr.layers.get('radio').intervalId, null); assert.equal(changes.some(({ type }) => type === 'visibility'), false); assert.equal(changes.at(-1)?.type, 'visibility-failed'); assert.equal(changes.at(-1)?.phase, 'cancel-enable-cleanup'); cleanupFails = false; const visibilityBeforeRetry = changes.filter(({ type }) => type === 'visibility').length; assert.equal(await mgr.setEnabled('radio', true), true); assert.equal(moduleActive, true, 'same-state retry performs real enable work'); assert.equal(mgr.isEnabled('radio'), true); assert.equal(mgr.layers.get('radio').lifecycleUncertain, false); assert.notEqual(mgr.layers.get('radio').intervalId, null); assert.equal( changes.filter(({ type }) => type === 'visibility').length, visibilityBeforeRetry + 1, 'only the reconciled retry emits settled visibility', ); clearInterval(mgr.layers.get('radio').intervalId); }); test('failed enable cleanup leaves reconciliation debt instead of skipping a same-state retry', async (t) => { for (const phase of ['init', 'enable', 'update']) { for (const cleanupFailure of ['throw', 'false']) { await t.test(`${phase} / cleanup ${cleanupFailure}`, async () => { const mgr = new DataLayerManager({}); const changes = []; let failPhase = true; let failCleanup = true; let moduleActive = false; const layer = makeSlowLayer(`reconcile-${phase}-${cleanupFailure}`, { updateInterval: 1000 }); layer.module.init = async () => { layer.calls.init++; if (failPhase && phase === 'init') throw new Error('init fixture'); }; layer.module.enable = async () => { layer.calls.enable++; moduleActive = true; if (failPhase && phase === 'enable') throw new Error('enable fixture'); }; layer.module.update = async () => { layer.calls.update++; if (failPhase && phase === 'update') throw new Error('update fixture'); }; layer.module.disable = async () => { layer.calls.disable++; moduleActive = false; if (!failCleanup) return; if (cleanupFailure === 'false') return false; throw new Error('cleanup fixture'); }; mgr.register(layer.module); mgr.subscribe((change) => changes.push(change)); assert.equal(await mgr.setEnabled(layer.module.id, true), false); assert.equal(moduleActive, false, 'cleanup made the module inactive'); assert.equal(mgr.isEnabled(layer.module.id), true, 'manager remains conservatively ON'); assert.equal(mgr.layers.get(layer.module.id).lifecycleUncertain, true); assert.equal(mgr.layers.get(layer.module.id).intervalId, null); assert.equal(changes.some(({ type }) => type === 'visibility'), false); assert.equal(changes.at(-1)?.type, 'visibility-failed'); assert.equal(changes.at(-1)?.phase, phase); failPhase = false; failCleanup = false; const enablesBeforeRetry = layer.calls.enable; assert.equal(await mgr.setEnabled(layer.module.id, true), true); assert.equal(layer.calls.enable, enablesBeforeRetry + 1, 'retry does not take the same-state no-op'); assert.equal(moduleActive, true); assert.equal(mgr.isEnabled(layer.module.id), true); assert.equal(mgr.layers.get(layer.module.id).lifecycleUncertain, false); assert.notEqual(mgr.layers.get(layer.module.id).intervalId, null); assert.equal(changes.filter(({ type }) => type === 'visibility').length, 1); clearInterval(mgr.layers.get(layer.module.id).intervalId); }); } } }); test('failed disable is uncertain and same-state enable reconciles module authority', async () => { const mgr = new DataLayerManager({}); const changes = []; let rejectDisable = false; let moduleActive = false; const layer = makeSlowLayer('disable-reconcile', { updateInterval: 1000 }); layer.module.enable = async () => { layer.calls.enable++; moduleActive = true; }; layer.module.disable = async () => { layer.calls.disable++; moduleActive = false; if (rejectDisable) throw new Error('disable fixture'); }; mgr.register(layer.module); await mgr.setEnabled(layer.module.id, true); mgr.subscribe((change) => changes.push(change)); rejectDisable = true; assert.equal(await mgr.setEnabled(layer.module.id, false), false); assert.equal(moduleActive, false); assert.equal(mgr.isEnabled(layer.module.id), true); assert.equal(mgr.layers.get(layer.module.id).lifecycleUncertain, true); assert.equal(changes.some(({ type }) => type === 'visibility'), false); rejectDisable = false; const enablesBeforeRetry = layer.calls.enable; assert.equal(await mgr.setEnabled(layer.module.id, true), true); assert.equal(layer.calls.enable, enablesBeforeRetry + 1); assert.equal(moduleActive, true); assert.equal(mgr.layers.get(layer.module.id).lifecycleUncertain, false); assert.equal(changes.filter(({ type }) => type === 'visibility').length, 1); clearInterval(mgr.layers.get(layer.module.id).intervalId); }); test('abort rejections from init and enable are cancellations, not lifecycle failures', async (t) => { for (const phase of ['init', 'enable']) { await t.test(phase, async () => { const mgr = new DataLayerManager({}); const changes = []; let announcePhase; const phaseStarted = new Promise((resolve) => { announcePhase = resolve; }); const layer = makeSlowLayer(`radio-${phase}`, { updateInterval: -1 }); const rejectOnAbort = (_viewer, { signal } = {}) => new Promise((_resolve, reject) => { announcePhase(); signal.addEventListener('abort', () => { const error = new Error(`${phase} aborted`); error.name = 'AbortError'; reject(error); }, { once: true }); }); if (phase === 'init') layer.module.init = rejectOnAbort; else layer.module.enable = rejectOnAbort; mgr.register(layer.module); mgr.subscribe((change) => changes.push(change)); const controller = new AbortController(); const enabling = mgr.setEnabled(layer.module.id, true, { origin: 'voice', signal: controller.signal, }); await phaseStarted; controller.abort(); const changed = await enabling; assert.equal(changed, false); assert.equal(mgr.isEnabled(layer.module.id), false); assert.equal(changes.some(({ type }) => type === 'visibility-failed'), false); assert.equal(changes.some(({ type }) => type === 'visibility'), false); assert.equal(changes.at(-1)?.type, 'visibility-cancelled'); assert.equal(changes.at(-1)?.intentEpoch, 1); assert.equal(changes.at(-1)?.cancellationReason, 'caller-abort'); }); } }); test('failed cancelled-disable compensation preserves manager/module coherence and permits retry', async () => { const mgr = new DataLayerManager({}); const changes = []; let releaseDisable; let announceDisable; let failEnable = false; let disableAttempts = 0; const disableStarted = new Promise((resolve) => { announceDisable = resolve; }); const layer = makeSlowLayer('radio', { updateInterval: -1 }); layer.module.enable = () => { layer.calls.enable++; if (failEnable) throw new Error('cleanup enable fixture'); }; layer.module.disable = async () => { layer.calls.disable++; disableAttempts++; if (disableAttempts > 1) return; announceDisable(); await new Promise((resolve) => { releaseDisable = resolve; }); }; mgr.register(layer.module); await mgr.setEnabled('radio', true); failEnable = true; mgr.subscribe((change) => changes.push(change)); const controller = new AbortController(); const disabling = mgr.setEnabled('radio', false, { origin: 'voice', signal: controller.signal, }); await disableStarted; controller.abort(); releaseDisable(); const changed = await disabling; assert.equal(changed, false); assert.equal(mgr.isEnabled('radio'), false); assert.equal(changes.some(({ type }) => type === 'visibility'), false); assert.equal(changes.at(-1)?.type, 'visibility-failed'); assert.equal(changes.at(-1)?.phase, 'cancel-disable-compensation'); failEnable = false; assert.equal(await mgr.setEnabled('radio', true), true); assert.equal(mgr.isEnabled('radio'), true, 'same-state retry performs real lifecycle work'); }); test('cancelled Space Missions entry exposes the owning manager intent epoch', async () => { const mgr = new DataLayerManager({}); const changes = []; let releaseEnable; let announceEnable; const enableStarted = new Promise((resolve) => { announceEnable = resolve; }); const missions = makeSlowLayer('rocket-launches', { updateInterval: -1 }); missions.module.enable = async () => { announceEnable(); await new Promise((resolve) => { releaseEnable = resolve; }); }; mgr.register(missions.module); mgr.subscribe((change) => changes.push(change)); const controller = new AbortController(); const enabling = mgr.setEnabled('rocket-launches', true, { origin: 'user', signal: controller.signal, }); await enableStarted; controller.abort(); releaseEnable(); assert.equal(await enabling, false); const cancelled = changes.find(({ type }) => type === 'visibility-cancelled'); assert.equal(cancelled?.layerId, 'rocket-launches'); assert.equal(cancelled?.enabled, true); assert.equal(cancelled?.intentEpoch, 1); }); test('lifecycle methods returning false reject their transaction without settled visibility', async (t) => { for (const phase of ['init', 'enable', 'update', 'disable']) { await t.test(phase, async () => { const mgr = new DataLayerManager({}); const changes = []; const layer = makeSlowLayer(`semantic-${phase}`, { updateInterval: -1 }); layer.module[phase] = async () => false; mgr.register(layer.module); if (phase === 'disable') { layer.module.disable = async () => undefined; await mgr.setEnabled(layer.module.id, true); layer.module.disable = async () => false; } mgr.subscribe((change) => changes.push(change)); const changed = await mgr.setEnabled(layer.module.id, phase !== 'disable'); assert.equal(changed, false); assert.equal(mgr.isEnabled(layer.module.id), phase === 'disable'); assert.equal(changes.some(({ type }) => type === 'visibility'), false); assert.equal(changes.at(-1)?.type, 'visibility-failed'); assert.equal(changes.at(-1)?.phase, phase); assert.equal( mgr.getLayerLifecycleState(layer.module.id).lifecycleState, phase === 'disable' ? 'enabled' : 'disabled', ); }); } }); test('module-local AbortError is a cancellation while the caller signal remains live', async (t) => { for (const phase of ['init', 'enable', 'update', 'disable']) { await t.test(phase, async () => { const mgr = new DataLayerManager({}); const changes = []; const layer = makeSlowLayer(`resource-abort-${phase}`, { updateInterval: -1 }); const abortLocally = async () => { const error = new Error(`${phase} resource cancelled`); error.name = 'AbortError'; throw error; }; if (phase === 'disable') { mgr.register(layer.module); await mgr.setEnabled(layer.module.id, true); layer.module.disable = abortLocally; } else { layer.module[phase] = abortLocally; mgr.register(layer.module); } mgr.subscribe((change) => changes.push(change)); const changed = await mgr.setEnabled(layer.module.id, phase !== 'disable'); assert.equal(changed, false); assert.equal(mgr.isEnabled(layer.module.id), phase === 'disable'); assert.equal(changes.some(({ type }) => type === 'visibility-failed'), false); assert.equal(changes.some(({ type }) => type === 'visibility'), false); assert.equal(changes.at(-1)?.type, 'visibility-cancelled'); assert.equal(changes.at(-1)?.cancellationReason, 'resource-abort'); assert.equal(changes.at(-1)?.phase, phase); assert.equal( mgr.getLayerLifecycleState(layer.module.id).lifecycleState, phase === 'disable' ? 'enabled' : 'disabled', ); }); } }); test('a settled resource cancellation cannot disable a later successful retry', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('resource-abort-retry', { updateInterval: -1 }); const caller = new AbortController(); layer.module.enable = async () => { const error = new Error('resource request cancelled'); error.name = 'AbortError'; throw error; }; mgr.register(layer.module); assert.equal(await mgr.setEnabled(layer.module.id, true, { signal: caller.signal }), false); assert.equal(caller.signal.aborted, false); assert.equal(mgr.isEnabled(layer.module.id), false); layer.module.enable = async () => undefined; assert.equal(await mgr.setEnabled(layer.module.id, true), true); assert.equal(mgr.isEnabled(layer.module.id), true); const disablesAfterRetry = layer.calls.disable; caller.abort(); await Promise.resolve(); assert.equal(mgr.isEnabled(layer.module.id), true); assert.equal(layer.calls.disable, disablesAfterRetry); }); test('simultaneous absolute enable requests stay idempotent inside the toggle queue', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('vessels', { updateInterval: 0 }); mgr.register(layer.module); await Promise.all([ mgr.setEnabled('vessels', true), mgr.setEnabled('vessels', true), mgr.setEnabled('vessels', true), ]); assert.equal(mgr.isEnabled('vessels'), true, 'repeated absolute enables end enabled'); assert.equal(layer.calls.enable, 1, 'the queued desired-state check prevents a second enable'); assert.equal(layer.calls.disable, 0, 'an absolute enable never turns the layer off'); await Promise.all([ mgr.setEnabled('vessels', false), mgr.setEnabled('vessels', false), ]); assert.equal(mgr.isEnabled('vessels'), false, 'repeated absolute disables end disabled'); assert.equal(layer.calls.disable, 1, 'the queued desired-state check prevents a second disable'); }); test('enabled-layer snapshots restore the exact set through normal visibility events', async () => { const mgr = new DataLayerManager({}); const layers = ['flights', 'satellites', 'earthquakes'].map((id) => makeSlowLayer(id, { updateInterval: 0 })); const changes = []; for (const layer of layers) mgr.register(layer.module); mgr.subscribe((change) => changes.push(change)); await Promise.all([ mgr.setEnabled('flights', true), mgr.setEnabled('earthquakes', true), ]); const snapshot = mgr.getEnabledLayerIds(); assert.deepEqual([...snapshot], ['flights', 'earthquakes']); snapshot.add('unknown-layer'); await Promise.all([ mgr.setEnabled('flights', false), mgr.setEnabled('satellites', true), ]); const restoreStart = changes.length; await mgr.restoreEnabledLayerIds(snapshot, { origin: 'context-restore' }); assert.deepEqual([...mgr.getEnabledLayerIds()], ['flights', 'earthquakes']); assert.deepEqual( changes.slice(restoreStart) .filter(({ type }) => type === 'visibility') .map(({ layerId, enabled, origin }) => ({ layerId, enabled, origin })) .sort((a, b) => a.layerId.localeCompare(b.layerId)), [ { layerId: 'earthquakes', enabled: true, origin: 'context-restore' }, { layerId: 'flights', enabled: true, origin: 'context-restore' }, { layerId: 'satellites', enabled: false, origin: 'context-restore' }, ], 'every accepted registered absolute restore intent emits through the normal manager path', ); await mgr.destroyAll(); }); test('restore waits for every queued layer transition before rethrowing a failure', async () => { const mgr = new DataLayerManager({}); const failing = makeSlowLayer('failing', { updateInterval: 0 }); const radio = makeSlowLayer('radio', { updateInterval: 0 }); let releaseRadioDisable; const radioDisableGate = new Promise((resolve) => { releaseRadioDisable = resolve; }); radio.module.disable = async () => radioDisableGate; failing.module.enable = async () => { throw new Error('real lifecycle enable failure'); }; mgr.register(failing.module); mgr.register(radio.module); await mgr.setEnabled('radio', true); let settled = false; const restoring = mgr.restoreEnabledLayerIds(new Set(['failing']), { origin: 'context-restore' }) .finally(() => { settled = true; }); await Promise.resolve(); await Promise.resolve(); assert.equal(settled, false, 'a sibling semantic failure must not release the restore barrier early'); releaseRadioDisable(); await assert.rejects(restoring, (error) => { assert.match(error.message, /Failed to restore layer "failing" visibility/); assert.deepEqual(error.failedLayerIds, ['failing']); return true; }); assert.equal(settled, true); assert.equal(mgr.isEnabled('failing'), false, 'fulfilled false remains an honest failed state'); assert.equal(mgr.isEnabled('radio'), false); await mgr.destroyAll(); }); test('restore forwards caller cancellation to every visibility intent', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('flights', { updateInterval: 0 }); const controller = new AbortController(); const receivedSignals = []; const originalSetEnabledWithIntent = mgr._setEnabledWithIntent.bind(mgr); mgr._setEnabledWithIntent = (layerId, enabled, options) => { receivedSignals.push(options?.signal || null); return originalSetEnabledWithIntent(layerId, enabled, options); }; mgr.register(layer.module); controller.abort(); await assert.rejects( mgr.restoreEnabledLayerIds(new Set(['flights']), { origin: 'context-restore', signal: controller.signal, }), /Failed to restore layer "flights" visibility/, ); assert.deepEqual(receivedSignals, [controller.signal]); assert.equal(mgr.isEnabled('flights'), false); await mgr.destroyAll(); }); test('a partial caller-aborted restore can be compensated to its exact target', async () => { const mgr = new DataLayerManager({}); const flights = makeSlowLayer('flights', { updateInterval: 0 }); const military = makeSlowLayer('military', { updateInterval: 0 }); const radio = makeSlowLayer('radio', { updateInterval: 0 }); mgr.register(flights.module); mgr.register(military.module); mgr.register(radio.module); await mgr.setEnabled('flights', true); await mgr.setEnabled('military', true); const controller = new AbortController(); const originalDisable = flights.module.disable; flights.module.disable = async (...args) => { const result = await originalDisable(...args); controller.abort(); return result; }; await assert.rejects( mgr.restoreEnabledLayerIds(new Set(['radio']), { origin: 'context-restore', signal: controller.signal, }), /Failed to restore layer/, ); await mgr.restoreEnabledLayerIds(new Set(['radio']), { origin: 'context-restore' }); assert.deepEqual(new Set(mgr.getEnabledLayerIds()), new Set(['radio'])); await mgr.destroyAll(); }); test('restore follows superseding intents and requires their authoritative settled target', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('satellites', { updateInterval: 0 }); mgr.register(layer.module); await mgr.setEnabled('satellites', true); const originalSetEnabledWithIntent = mgr._setEnabledWithIntent.bind(mgr); let injected = false; mgr._setEnabledWithIntent = (layerId, enabled, options) => { const handle = originalSetEnabledWithIntent(layerId, enabled, options); if (!injected && options?.origin === 'context-restore') { injected = true; originalSetEnabledWithIntent(layerId, false, { origin: 'voice' }); } return handle; }; await assert.rejects( mgr.restoreEnabledLayerIds(new Set(['satellites']), { origin: 'context-restore' }), /Failed to restore layer "satellites" visibility/, ); assert.equal(mgr.isEnabled('satellites'), false); injected = false; mgr._setEnabledWithIntent = (layerId, enabled, options) => { const handle = originalSetEnabledWithIntent(layerId, enabled, options); if (!injected && options?.origin === 'context-restore') { injected = true; originalSetEnabledWithIntent(layerId, true, { origin: 'voice' }); } return handle; }; await mgr.restoreEnabledLayerIds(new Set(['satellites']), { origin: 'context-restore' }); assert.equal(mgr.isEnabled('satellites'), true); await mgr.destroyAll(); }); test('visibility notifications distinguish user toggles from dependencies', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('earthquakes', { updateInterval: 0 }); const changes = []; mgr.register(layer.module); mgr.subscribe((change) => changes.push(change)); await mgr.toggle('earthquakes', { origin: 'user' }); await mgr.setEnabled('earthquakes', false); const settled = changes.filter(({ type }) => type === 'visibility'); assert.equal(settled[0].origin, 'user'); assert.equal(settled[1].origin, 'programmatic'); }); test('newer absolute OFF supersedes a slow ON before settled publication', async () => { const mgr = new DataLayerManager({}); let releaseUpdate; let markUpdateStarted; let moduleActive = false; const updateGate = new Promise((resolve) => { releaseUpdate = resolve; }); const updateStarted = new Promise((resolve) => { markUpdateStarted = resolve; }); const radio = makeSlowLayer('radio', { updateInterval: 0 }); radio.module.enable = () => { radio.calls.enable++; moduleActive = true; }; radio.module.update = async () => { radio.calls.update++; markUpdateStarted(); await updateGate; }; radio.module.disable = () => { radio.calls.disable++; moduleActive = false; }; mgr.register(radio.module); const requests = []; const changes = []; const latestToken = Symbol('latest-off'); mgr.subscribeVisibilityRequests((change) => requests.push(change)); mgr.subscribe((change) => changes.push(change)); const enabling = mgr.setEnabled('radio', true, { origin: 'voice' }); await updateStarted; const disabling = mgr.setEnabled('radio', false, { origin: 'user', notificationToken: latestToken, }); assert.deepEqual(requests.map(({ enabled, origin }) => ({ enabled, origin })), [ { enabled: true, origin: 'voice' }, { enabled: false, origin: 'user' }, ]); releaseUpdate(); assert.deepEqual(await Promise.all([enabling, disabling]), [false, true]); assert.equal(mgr.isEnabled('radio'), false); assert.equal(moduleActive, false); assert.equal(mgr.layers.get('radio').intervalId, null); assert.deepEqual( changes.filter(({ type }) => type === 'visibility') .map(({ enabled, origin, notificationToken }) => ({ enabled, origin, notificationToken })), [{ enabled: false, origin: 'user', notificationToken: latestToken }], 'only the latest absolute request may publish settled visibility', ); assert.equal( radio.calls.presentation.some(({ lifecycleState, enabled, uncertain }) => ( lifecycleState === 'enabled' && enabled && !uncertain )), false, 'the superseded ON never exposes certain settled presentation', ); await mgr.destroyAll(); }); test('newer OFF cancels init and enable phases without obsolete settlement', async (t) => { for (const phase of ['init', 'enable']) { await t.test(phase, async () => { const mgr = new DataLayerManager({}); let releasePhase; let markPhaseStarted; const phaseGate = new Promise((resolve) => { releasePhase = resolve; }); const phaseStarted = new Promise((resolve) => { markPhaseStarted = resolve; }); const radio = makeSlowLayer(`radio-${phase}`, { updateInterval: -1 }); radio.module[phase] = async () => { radio.calls[phase]++; markPhaseStarted(); await phaseGate; }; mgr.register(radio.module); const changes = []; mgr.subscribe((change) => changes.push(change)); const enabling = mgr.setEnabled(radio.module.id, true, { origin: 'voice' }); await phaseStarted; const disabling = mgr.setEnabled(radio.module.id, false, { origin: 'user' }); releasePhase(); assert.deepEqual(await Promise.all([enabling, disabling]), [false, true]); assert.deepEqual( changes.filter(({ type }) => type === 'visibility') .map(({ enabled, origin }) => ({ enabled, origin })), [{ enabled: false, origin: 'user' }], ); assert.equal(mgr.isEnabled(radio.module.id), false); assert.equal( radio.calls.presentation.some(({ lifecycleState, enabled, uncertain }) => ( lifecycleState === 'enabled' && enabled && !uncertain )), false, ); await mgr.destroyAll(); }); } }); test('supersession during a visibility guard cancels the stale request before block publication', async () => { const mgr = new DataLayerManager({}); let releaseGuard; let markGuardStarted; const guardGate = new Promise((resolve) => { releaseGuard = resolve; }); const guardStarted = new Promise((resolve) => { markGuardStarted = resolve; }); const radio = makeSlowLayer('radio', { updateInterval: -1 }); mgr.register(radio.module); mgr.addVisibilityGuard(async (change) => { if (change.layerId !== 'radio' || !change.enabled) return null; markGuardStarted(); await guardGate; return 'stale guard result'; }); const changes = []; mgr.subscribe((change) => changes.push(change)); const enabling = mgr.setEnabled('radio', true, { origin: 'voice' }); await guardStarted; const disabling = mgr.setEnabled('radio', false, { origin: 'user' }); releaseGuard(); assert.deepEqual(await Promise.all([enabling, disabling]), [false, true]); assert.equal(changes.some(({ type }) => type === 'visibility-blocked'), false); assert.deepEqual( changes.filter(({ type }) => ['visibility-cancelled', 'visibility'].includes(type)) .map(({ type, enabled, origin }) => ({ type, enabled, origin })), [ { type: 'visibility-cancelled', enabled: true, origin: 'voice' }, { type: 'visibility', enabled: false, origin: 'user' }, ], ); assert.equal(mgr.isEnabled('radio'), false); await mgr.destroyAll(); }); test('newer same-target absolute intent owns the only settled publication', async () => { const mgr = new DataLayerManager({}); let releaseUpdate; let markUpdateStarted; let firstUpdate = true; const updateGate = new Promise((resolve) => { releaseUpdate = resolve; }); const updateStarted = new Promise((resolve) => { markUpdateStarted = resolve; }); const radio = makeSlowLayer('radio', { updateInterval: -1 }); radio.module.update = async () => { radio.calls.update++; if (!firstUpdate) return; firstUpdate = false; markUpdateStarted(); await updateGate; }; mgr.register(radio.module); const changes = []; mgr.subscribe((change) => changes.push(change)); const voiceEnable = mgr.setEnabled('radio', true, { origin: 'voice' }); await updateStarted; const userEnable = mgr.setEnabled('radio', true, { origin: 'user' }); releaseUpdate(); assert.deepEqual(await Promise.all([voiceEnable, userEnable]), [false, true]); assert.deepEqual( changes.filter(({ type }) => type === 'visibility') .map(({ enabled, origin }) => ({ enabled, origin })), [{ enabled: true, origin: 'user' }], ); assert.equal(mgr.isEnabled('radio'), true); await mgr.destroyAll(); }); test('cancelled visibility publishes an atomic successor handoff and exact intent outcomes', async () => { const mgr = new DataLayerManager({}); let releaseUpdate; let markUpdateStarted; let firstUpdate = true; const updateGate = new Promise((resolve) => { releaseUpdate = resolve; }); const updateStarted = new Promise((resolve) => { markUpdateStarted = resolve; }); const missions = makeSlowLayer('rocket-launches', { updateInterval: -1 }); missions.module.update = async () => { missions.calls.update++; if (!firstUpdate) return; firstUpdate = false; markUpdateStarted(); await updateGate; }; mgr.register(missions.module); const changes = []; mgr.subscribe((change) => changes.push(change)); const first = mgr._setEnabledWithIntent('rocket-launches', true, { origin: 'user' }); await updateStarted; const successor = mgr._setEnabledWithIntent('rocket-launches', true, { origin: 'programmatic' }); releaseUpdate(); assert.equal(await first.promise, false); assert.equal(await successor.promise, true); const cancelled = changes.find(({ type, intentEpoch }) => ( type === 'visibility-cancelled' && intentEpoch === first.intentEpoch )); assert.deepEqual({ reason: cancelled?.cancellationReason, phase: cancelled?.phase, successorIntentEpoch: cancelled?.successorIntentEpoch, successorEnabled: cancelled?.successorEnabled, successorOrigin: cancelled?.successorOrigin, }, { reason: 'superseded', phase: 'update', successorIntentEpoch: successor.intentEpoch, successorEnabled: true, successorOrigin: 'programmatic', }); assert.equal((await mgr._waitForVisibilityIntent('rocket-launches', successor.intentEpoch))?.succeeded, true); assert.deepEqual( changes.filter(({ type }) => type === 'visibility').map(({ enabled, origin }) => ({ enabled, origin })), [{ enabled: true, origin: 'programmatic' }], ); await mgr.destroyAll(); }); test('same-target uncertain retry retains authoritative visibility until abort cleanup settles', async () => { const mgr = new DataLayerManager({}); const radio = makeSlowLayer('radio', { updateInterval: -1 }); let enableAttempt = 0; let markRetryStarted; let releaseCleanup; const retryStarted = new Promise((resolve) => { markRetryStarted = resolve; }); const cleanupGate = new Promise((resolve) => { releaseCleanup = resolve; }); radio.module.enable = (_viewer, { signal } = {}) => { radio.calls.enable++; enableAttempt += 1; if (enableAttempt !== 2) return Promise.resolve(); markRetryStarted(); return new Promise((_resolve, reject) => { signal.addEventListener('abort', () => { const error = new Error('superseded retry'); error.name = 'AbortError'; reject(error); }, { once: true }); }); }; radio.module.disable = async () => { radio.calls.disable++; await cleanupGate; }; mgr.register(radio.module); assert.equal(await mgr.setEnabled('radio', true), true); const entry = mgr.layers.get('radio'); entry.lifecycleUncertain = true; const firstRetry = mgr.setEnabled('radio', true, { origin: 'voice' }); await retryStarted; const latestRetry = mgr.setEnabled('radio', true, { origin: 'user' }); assert.equal( mgr.isEnabled('radio'), true, 'supersession does not rewrite the last authoritative boolean before cleanup', ); releaseCleanup(); assert.deepEqual(await Promise.all([firstRetry, latestRetry]), [false, true]); assert.equal(mgr.isEnabled('radio'), true); assert.equal(entry.lifecycleUncertain, false); await mgr.destroyAll(); }); test('re-entrant absolute request during lifecycle presentation owns settlement and publication', async () => { const mgr = new DataLayerManager({}); const radio = makeSlowLayer('radio', { updateInterval: 1000 }); let moduleActive = false; let reentrantDisable = null; let triggered = false; radio.module.enable = () => { radio.calls.enable++; moduleActive = true; }; radio.module.disable = () => { radio.calls.disable++; moduleActive = false; }; radio.module.setLifecyclePresentation = (state) => { radio.calls.presentation.push({ ...state }); if (!triggered && state.lifecycleState === 'enabled' && state.enabled && !state.uncertain) { triggered = true; reentrantDisable = mgr.setEnabled('radio', false, { origin: 'user' }); } }; mgr.register(radio.module); const changes = []; mgr.subscribe((change) => changes.push(change)); const staleEnable = mgr.setEnabled('radio', true, { origin: 'voice' }); assert.equal(await staleEnable, false); assert.ok(reentrantDisable); assert.equal(await reentrantDisable, true); assert.equal(moduleActive, false); assert.equal(mgr.isEnabled('radio'), false); assert.equal(mgr.layers.get('radio').intervalId, null); assert.deepEqual( changes.filter(({ type }) => type === 'visibility') .map(({ enabled, origin }) => ({ enabled, origin })), [{ enabled: false, origin: 'user' }], ); await mgr.destroyAll(); }); test('newer absolute ON supersedes a slow OFF without publishing stale OFF', async () => { const mgr = new DataLayerManager({}); let releaseDisable; let markDisableStarted; let firstDisable = true; const disableGate = new Promise((resolve) => { releaseDisable = resolve; }); const disableStarted = new Promise((resolve) => { markDisableStarted = resolve; }); const radio = makeSlowLayer('radio', { updateInterval: -1 }); radio.module.disable = async () => { radio.calls.disable++; if (!firstDisable) return; firstDisable = false; markDisableStarted(); await disableGate; }; mgr.register(radio.module); await mgr.setEnabled('radio', true); const changes = []; mgr.subscribe((change) => changes.push(change)); const voiceDisable = mgr.setEnabled('radio', false, { origin: 'voice' }); await disableStarted; const userEnable = mgr.setEnabled('radio', true, { origin: 'user' }); releaseDisable(); assert.deepEqual(await Promise.all([voiceDisable, userEnable]), [false, true]); assert.deepEqual( changes.filter(({ type }) => type === 'visibility') .map(({ enabled, origin }) => ({ enabled, origin })), [{ enabled: true, origin: 'user' }], ); assert.equal(mgr.isEnabled('radio'), true); await mgr.destroyAll(); }); test('rapid ON then OFF then ON publishes only the final absolute intent', async () => { const mgr = new DataLayerManager({}); let releaseUpdate; let markUpdateStarted; let firstUpdate = true; const updateGate = new Promise((resolve) => { releaseUpdate = resolve; }); const updateStarted = new Promise((resolve) => { markUpdateStarted = resolve; }); const radio = makeSlowLayer('radio', { updateInterval: -1 }); radio.module.update = async () => { radio.calls.update++; if (!firstUpdate) return; firstUpdate = false; markUpdateStarted(); await updateGate; }; mgr.register(radio.module); const changes = []; mgr.subscribe((change) => changes.push(change)); const firstOn = mgr.setEnabled('radio', true, { origin: 'voice' }); await updateStarted; const middleOff = mgr.setEnabled('radio', false, { origin: 'programmatic' }); const finalOn = mgr.setEnabled('radio', true, { origin: 'user' }); releaseUpdate(); assert.deepEqual(await Promise.all([firstOn, middleOff, finalOn]), [false, false, true]); assert.deepEqual( changes.filter(({ type }) => type === 'visibility') .map(({ enabled, origin }) => ({ enabled, origin })), [{ enabled: true, origin: 'user' }], ); assert.equal(mgr.isEnabled('radio'), true); await mgr.destroyAll(); }); test('waitForLayerSettled defers reconciliation until the captured queue completes', async () => { const mgr = new DataLayerManager({}); let releaseEnable; let markEnableStarted; const enableGate = new Promise((resolve) => { releaseEnable = resolve; }); const enableStarted = new Promise((resolve) => { markEnableStarted = resolve; }); const missions = makeSlowLayer('rocket-launches', { updateInterval: 0 }); missions.module.enable = async () => { markEnableStarted(); await enableGate; }; mgr.register(missions.module); const enabling = mgr.setEnabled('rocket-launches', true); await enableStarted; let settled = false; const waiting = mgr.waitForLayerSettled('rocket-launches').then(() => { settled = true; }); await Promise.resolve(); assert.equal(settled, false); releaseEnable(); await Promise.all([enabling, waiting]); assert.equal(settled, true); await mgr.destroyAll(); }); test('programmatic Context enables neither create nor replace a restoration snapshot', async () => { const mgr = new DataLayerManager({}); const missions = makeSlowLayer('rocket-launches', { updateInterval: 0 }); mgr.register(missions.module); let snapshot = { enabledLayerIds: new Set(['flights', 'traffic']), marker: 'existing-session', }; const existingSnapshot = snapshot; mgr.subscribe((change) => { if (!shouldCaptureContextSession(change)) return; snapshot = { enabledLayerIds: contextSnapshotLayerIds(mgr.getEnabledLayerIds()), marker: 'captured-by-user', }; }); await mgr.setEnabled('rocket-launches', true, { origin: 'programmatic' }); assert.equal(snapshot, existingSnapshot, 'programmatic enable preserves an existing session snapshot'); await mgr.setEnabled('rocket-launches', false, { origin: 'programmatic' }); snapshot = null; await mgr.setEnabled('rocket-launches', true, { origin: 'programmatic' }); assert.equal(snapshot, null, 'programmatic enable does not create a new session snapshot'); await mgr.destroyAll(); }); test('visibility guards refuse incompatible enables before lifecycle work', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('flights', { updateInterval: 0 }); const changes = []; mgr.register(layer.module); mgr.subscribe((change) => changes.push(change)); const removeGuard = mgr.addVisibilityGuard((change) => ( change.layerId === 'flights' && change.enabled ? 'Replay isolation keeps Live Flights off' : null )); const changed = await mgr.setEnabled('flights', true, { origin: 'user' }); assert.equal(changed, false); assert.equal(mgr.isEnabled('flights'), false); assert.deepEqual(layer.calls, { enable: 0, disable: 0, update: 0, init: 0, presentation: [], }); assert.deepEqual(changes, [ { type: 'visibility-will-change', layerId: 'flights', enabled: true, origin: 'user', intentEpoch: 1, }, { type: 'visibility-blocked', layerId: 'flights', enabled: true, origin: 'user', intentEpoch: 1, reason: 'Replay isolation keeps Live Flights off', }, ]); removeGuard(); await mgr.setEnabled('flights', true, { origin: 'programmatic' }); assert.equal(mgr.isEnabled('flights'), true); await mgr.destroyAll(); }); test('failed asynchronous disable stays enabled and reports an explicit lifecycle failure', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('flights', { updateInterval: 0 }); const failure = new Error('poller refused to stop'); const changes = []; layer.module.disable = async () => { throw failure; }; mgr.register(layer.module); mgr.subscribe((change) => changes.push(change)); await mgr.setEnabled('flights', true); const changed = await mgr.setEnabled('flights', false); assert.equal(changed, false); assert.equal(mgr.isEnabled('flights'), true, 'manager must not publish a false disabled state'); assert.notEqual(mgr.layers.get('flights').intervalId, null, 'the live refresh interval remains owned'); const failed = changes.find(({ type }) => type === 'visibility-failed'); assert.deepEqual({ layerId: failed?.layerId, enabled: failed?.enabled, phase: failed?.phase, error: failed?.error, }, { layerId: 'flights', enabled: false, phase: 'disable', error: failure, }); clearInterval(mgr.layers.get('flights').intervalId); }); test('captured enabled set repairs siblings stopped before an isolation failure', async () => { const mgr = new DataLayerManager({}); const flights = makeSlowLayer('flights', { updateInterval: 0 }); const traffic = makeSlowLayer('traffic', { updateInterval: 0 }); const missions = makeSlowLayer('rocket-launches', { updateInterval: 0 }); traffic.module.disable = async () => { throw new Error('traffic teardown failed'); }; mgr.register(flights.module); mgr.register(traffic.module); mgr.register(missions.module); await Promise.all([ mgr.setEnabled('flights', true), mgr.setEnabled('traffic', true), ]); const snapshot = mgr.getEnabledLayerIds(); const results = await Promise.all([ mgr.setEnabled('flights', false), mgr.setEnabled('traffic', false), ]); assert.deepEqual(results, [true, false]); assert.deepEqual([...mgr.getEnabledLayerIds()], ['traffic'], 'one sibling stopped before failure surfaced'); // Model rollback running from inside Rocket Launches' own visibility guard: // that entry's queue cannot settle until the guard returns. Excluding the // trigger must let every sibling restore without enqueueing behind itself. mgr.layers.get('rocket-launches').toggleChain = new Promise(() => {}); await mgr.restoreEnabledLayerIds(snapshot, { origin: 'context-restore', excludeLayerIds: ['rocket-launches'], }); assert.deepEqual([...mgr.getEnabledLayerIds()], ['flights', 'traffic']); clearInterval(mgr.layers.get('flights').intervalId); clearInterval(mgr.layers.get('traffic').intervalId); }); test('deferred full restore reconciles an uncertain failed Context shell', async () => { const mgr = new DataLayerManager({}); const flights = makeSlowLayer('flights', { updateInterval: 0 }); const missions = makeSlowLayer('rocket-launches', { updateInterval: 0 }); let cleanupCanConfirm = false; missions.module.enable = async () => { throw new Error('mission activation fixture'); }; missions.module.disable = async () => cleanupCanConfirm; mgr.register(flights.module); mgr.register(missions.module); await mgr.setEnabled('flights', true); const snapshot = mgr.getEnabledLayerIds(); await mgr.setEnabled('flights', false); assert.equal(await mgr.setEnabled('rocket-launches', true, { origin: 'user' }), false); assert.equal(mgr.isEnabled('rocket-launches'), true, 'uncertain cleanup stays conservatively ON'); assert.equal(mgr.layers.get('rocket-launches').lifecycleUncertain, true); await mgr.waitForLayerSettled('rocket-launches'); cleanupCanConfirm = true; await mgr.restoreEnabledLayerIds(snapshot, { origin: 'context-restore' }); assert.equal(mgr.isEnabled('flights'), true); assert.equal(mgr.isEnabled('rocket-launches'), false); assert.equal(mgr.layers.get('rocket-launches').lifecycleUncertain, false); await mgr.destroyAll(); }); test('mission entry guard remains active until the slow Rocket Launches enable settles', async () => { const mgr = new DataLayerManager({}); const flights = makeSlowLayer('flights', { updateInterval: 0 }); let releaseMissionEnable; let markMissionEnableStarted; const missionEnableGate = new Promise((resolve) => { releaseMissionEnable = resolve; }); const missionEnableStarted = new Promise((resolve) => { markMissionEnableStarted = resolve; }); const missions = makeSlowLayer('rocket-launches', { updateInterval: 0 }); missions.module.enable = async () => { markMissionEnableStarted(); await missionEnableGate; }; mgr.register(flights.module); mgr.register(missions.module); let enteringMode = null; mgr.addVisibilityGuard(async (change) => { if (enteringMode === 'space-missions' && change.enabled && change.layerId !== 'rocket-launches') { return 'Replay isolation keeps other layers off'; } if (change.enabled && change.layerId === 'rocket-launches') { enteringMode = 'space-missions'; } return null; }); mgr.subscribe((change) => { if ( change.layerId === 'rocket-launches' && ['visibility', 'visibility-blocked', 'visibility-failed'].includes(change.type) ) { enteringMode = null; } }); const enablingMissions = mgr.setEnabled('rocket-launches', true, { origin: 'user' }); await missionEnableStarted; const flightsChanged = await mgr.setEnabled('flights', true, { origin: 'user' }); assert.equal(flightsChanged, false); assert.equal(mgr.isEnabled('flights'), false, 'incompatible layer stays off throughout mission startup'); assert.equal(enteringMode, 'space-missions', 'entry gate remains owned while enable is pending'); releaseMissionEnable(); await enablingMissions; assert.equal(mgr.isEnabled('rocket-launches'), true); assert.equal(enteringMode, null, 'settled visibility releases the entry gate'); await mgr.destroyAll(); }); test('manager awaits asynchronous dependency teardown before a rapid re-enable', async () => { const mgr = new DataLayerManager({}); const order = []; let releaseDisable; let markDisableStarted; const disableGate = new Promise((resolve) => { releaseDisable = resolve; }); const disableStarted = new Promise((resolve) => { markDisableStarted = resolve; }); mgr.register({ id: 'rocket-launches', name: 'missions', icon: '', source: 'test', updateInterval: 0, async init() {}, async enable() { order.push('enable'); }, async disable() { order.push('disable-start'); markDisableStarted(); await disableGate; order.push('disable-finish'); }, async update() {}, getStats() { return { count: 0, lastUpdate: null }; }, }); await mgr.setEnabled('rocket-launches', true); const disabling = mgr.setEnabled('rocket-launches', false); await disableStarted; const reenabling = mgr.setEnabled('rocket-launches', true); assert.deepEqual(order, ['enable', 'disable-start']); releaseDisable(); assert.deepEqual(await Promise.all([disabling, reenabling]), [false, true]); assert.deepEqual(order, ['enable', 'disable-start', 'disable-finish', 'enable']); assert.equal(mgr.isEnabled('rocket-launches'), true); await mgr.destroyAll(); }); test('layer feed states distinguish unavailable, fallback, stale, and degraded controls', () => { assert.equal(layerFeedState({ error: 'feed down', count: 0, lastUpdate: null }), 'unavailable'); assert.equal(layerFeedState({ status: 'unavailable', error: 'feed down', count: 50, lastUpdate: 1, }), 'unavailable', 'an explicit total outage stays unavailable while last-good data is preserved'); assert.equal(layerFeedState({ mode: 'sim', count: 100, lastUpdate: 1 }), 'fallback'); assert.equal(layerFeedState({ source: 'adsb.lol', count: 10, lastUpdate: 1 }), 'fallback'); assert.equal(layerFeedState({ source: 'adsb.lol', fallback: false, count: 10, lastUpdate: 1, }), 'nominal', 'an explicitly primary adsb.lol feed is not a fallback'); assert.equal(layerFeedState({ stale: true, count: 0, lastUpdate: 1 }), 'stale'); assert.equal(layerFeedState({ error: 'partial group failure', count: 50, lastUpdate: 1 }), 'degraded'); assert.equal(layerFeedState({ loading: true }), 'loading'); assert.equal(layerFeedState({ count: 5, lastUpdate: 1 }), 'nominal'); }); test('layer metadata names degraded state instead of presenting an ordinary age', () => { const mgr = new DataLayerManager({}); assert.match(mgr._buildMetaText({ source: 'AISStream', stats: { stale: true, count: 20, lastUpdate: Date.now() - 10_000 }, }), /^STALE · AISStream · /); assert.equal(mgr._buildMetaText({ source: 'TomTom', stats: { mode: 'sim', count: 120, lastUpdate: 1, loadingLabel: 'simulated traffic' }, }), 'FALLBACK · TomTom · simulated traffic'); assert.equal(mgr._buildMetaText({ source: 'CelesTrak', stats: { error: 'CelesTrak unreachable', count: 0, lastUpdate: null }, }), 'UNAVAILABLE · CelesTrak · CelesTrak unreachable'); assert.equal(mgr._buildMetaText({ source: 'CelesTrak', stats: { status: 'unavailable', error: 'CelesTrak unreachable', count: 50, lastUpdate: 1, }, }), 'UNAVAILABLE · CelesTrak · CelesTrak unreachable'); }); test('uncertain lifecycle state overrides ordinary feed status without disabling reconciliation', () => { const mgr = new DataLayerManager({}); const classes = new Map(); const attributes = new Map(); const button = { classList: { toggle(name, active) { classes.set(name, Boolean(active)); }, }, dataset: {}, disabled: false, textContent: '', setAttribute(name, value) { attributes.set(name, String(value)); }, }; const layer = { name: 'Radio', source: 'Radio Browser', enabled: true, lifecycleState: 'enabled', lifecycleUncertain: true, stats: { count: 750, lastUpdate: Date.now() }, }; mgr._syncToggleButton(button, layer); assert.equal(button.textContent, 'UNCERTAIN'); assert.equal(button.dataset.feedState, 'uncertain'); assert.equal(button.disabled, false, 'the lifecycle toggle remains available to reconcile authority'); assert.equal(attributes.get('aria-label'), 'Radio: UNCERTAIN'); assert.equal(classes.get('lifecycle-uncertain'), true); assert.equal(classes.get('feed-nominal'), false); assert.equal( mgr._buildMetaText(layer), 'UNCERTAIN · Radio Browser · lifecycle state requires reconciliation', ); }); test('pre-transition subscribers capture the exact enabled set before user changes', async () => { const mgr = new DataLayerManager({}); const context = makeSlowLayer('military-awareness', { updateInterval: 0 }); const satellites = makeSlowLayer('satellites', { updateInterval: 0 }); const snapshots = []; mgr.register(context.module); mgr.register(satellites.module); await mgr.setEnabled('satellites', true); mgr.subscribe((change) => { if ( change.type === 'visibility-will-change' && change.layerId === 'military-awareness' && change.origin === 'user' ) { snapshots.push({ enabled: change.enabled, ids: [...mgr.getEnabledLayerIds()] }); } }); await mgr.toggle('military-awareness', { origin: 'user' }); await mgr.toggle('military-awareness', { origin: 'user' }); assert.deepEqual(snapshots, [ { enabled: true, ids: ['satellites'] }, { enabled: false, ids: ['military-awareness', 'satellites'] }, ]); await mgr.destroyAll(); }); test('absolute Context entry intent is excluded from its own pre-entry restore snapshot', async () => { const mgr = new DataLayerManager({}); const missions = makeSlowLayer('rocket-launches', { updateInterval: 0 }); const satellites = makeSlowLayer('satellites', { updateInterval: 0 }); mgr.register(missions.module); mgr.register(satellites.module); let snapshot = null; mgr.subscribe((change) => { if (shouldCaptureContextSession(change)) { snapshot = contextSnapshotLayerIds( mgr.getEnabledLayerIds(), null, [change.layerId], ); } }); await mgr.setEnabled('rocket-launches', true, { origin: 'user' }); assert.deepEqual([...snapshot], []); await mgr.setEnabled('rocket-launches', false, { origin: 'user' }); await mgr.restoreEnabledLayerIds(snapshot, { origin: 'context-restore' }); assert.equal(mgr.isEnabled('rocket-launches'), false); assert.equal(mgr.isEnabled('satellites'), false); await mgr.destroyAll(); }); test('destroy waits for pre-destroy restoration before removing a layer', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('satellites', { updateInterval: 0 }); const order = []; layer.module.destroy = () => order.push('destroy'); mgr.register(layer.module); await mgr.setEnabled('satellites', true); mgr.subscribeBeforeDestroy(async ({ layerId }) => { order.push(`restore-start:${layerId}`); await Promise.resolve(); order.push('restore-finish'); }); await mgr.destroyLayer('satellites'); assert.deepEqual(order, ['restore-start:satellites', 'restore-finish', 'destroy']); assert.equal(mgr.layers.has('satellites'), false); }); test('destroy revokes an in-flight enable before it can publish settled visibility', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('rocket-launches', { updateInterval: -1 }); let releaseUpdate; let announceUpdate; const updateStarted = new Promise((resolve) => { announceUpdate = resolve; }); layer.module.update = async () => { announceUpdate(); await new Promise((resolve) => { releaseUpdate = resolve; }); }; mgr.register(layer.module); const changes = []; mgr.subscribe((change) => changes.push({ ...change })); const enable = mgr._setEnabledWithIntent('rocket-launches', true, { origin: 'user' }); await updateStarted; const destroying = mgr.destroyLayer('rocket-launches'); assert.equal(mgr.isEffectivelyEnabled('rocket-launches'), false); releaseUpdate(); assert.equal(await enable.promise, false); assert.equal(await destroying, true); assert.equal(mgr.layers.has('rocket-launches'), false); assert.equal( changes.some((change) => change.type === 'visibility' && change.enabled === true), false, 'revoked enable never publishes settled ON', ); const cancelled = changes.find((change) => ( change.type === 'visibility-cancelled' && change.intentEpoch === enable.intentEpoch )); assert.equal(cancelled?.cancellationReason, 'superseded'); assert.equal(cancelled?.successorOrigin, 'teardown'); assert.equal(cancelled?.successorEnabled, false); }); test('destroy revokes an in-flight public toggle before it can publish settled visibility', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('rocket-launches', { updateInterval: -1 }); let releaseUpdate; let announceUpdate; const updateStarted = new Promise((resolve) => { announceUpdate = resolve; }); layer.module.update = async () => { announceUpdate(); await new Promise((resolve) => { releaseUpdate = resolve; }); }; mgr.register(layer.module); const changes = []; mgr.subscribe((change) => changes.push({ ...change })); const enabling = mgr.toggle('rocket-launches', { origin: 'user' }); await updateStarted; const destroying = mgr.destroyLayer('rocket-launches'); releaseUpdate(); assert.equal(await enabling, false); assert.equal(await destroying, true); assert.equal(mgr.layers.has('rocket-launches'), false); assert.equal( changes.some((change) => change.type === 'visibility' && change.enabled === true), false, 'revoked public toggle never publishes settled ON', ); const cancelled = changes.find((change) => change.type === 'visibility-cancelled'); assert.equal(cancelled?.phase, 'update'); assert.equal(cancelled?.cancellationReason, 'superseded'); assert.equal(cancelled?.successorOrigin, 'teardown'); }); test('destroyLayer retains an enabled entry when semantic disable fails and permits retry', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('radio', { updateInterval: 1000 }); let moduleActive = false; let rejectDisable = true; let destroyCalls = 0; layer.module.enable = () => { moduleActive = true; }; layer.module.disable = () => { if (rejectDisable) return false; moduleActive = false; return true; }; layer.module.destroy = () => { destroyCalls += 1; }; mgr.register(layer.module); await mgr.setEnabled('radio', true); assert.equal(await mgr.destroyLayer('radio'), false); assert.equal(mgr.layers.has('radio'), true); assert.equal(mgr.isEnabled('radio'), true); assert.equal(moduleActive, true); assert.equal(destroyCalls, 0); assert.notEqual(mgr.layers.get('radio').intervalId, null); rejectDisable = false; assert.equal(await mgr.destroyLayer('radio'), true); assert.equal(mgr.layers.has('radio'), false); assert.equal(moduleActive, false); assert.equal(destroyCalls, 1); }); test('pre-destroy hook can restore the exact focused-session state and params', async () => { const mgr = new DataLayerManager({}); const local = makeSlowLayer('cctv', { updateInterval: 0 }); const satellites = makeSlowLayer('satellites', { updateInterval: 0 }); const missions = makeSlowLayer('rocket-launches', { updateInterval: 0 }); const satelliteParams = { catalog: 'default', showDots: true }; satellites.module.getParams = () => satelliteParams; satellites.module.setParams = (params) => Object.assign(satelliteParams, params); for (const layer of [local, satellites, missions]) mgr.register(layer.module); await Promise.all([ mgr.setEnabled('cctv', true), mgr.setEnabled('satellites', true), ]); const snapshot = { enabled: mgr.getEnabledLayerIds(), params: mgr.getLayerParams('satellites'), }; await Promise.all([ mgr.setEnabled('cctv', false), mgr.setEnabled('rocket-launches', true), ]); mgr.setLayerParams('satellites', { catalog: 'dense', showDots: false }); let stateAtDestroy = null; mgr.subscribeBeforeDestroy(async () => { mgr.setLayerParams('satellites', snapshot.params); await mgr.restoreEnabledLayerIds(snapshot.enabled, { origin: 'context-restore' }); stateAtDestroy = { enabled: [...mgr.getEnabledLayerIds()], params: mgr.getLayerParams('satellites'), }; }); await mgr.destroyLayer('rocket-launches'); assert.deepEqual(stateAtDestroy, { enabled: ['cctv', 'satellites'], params: { catalog: 'default', showDots: true }, }); assert.deepEqual([...mgr.getEnabledLayerIds()], ['cctv', 'satellites']); await mgr.destroyAll(); }); test('layer parameter snapshots are detached from module-owned nested state', () => { const mgr = new DataLayerManager({}); const params = { catalog: 'dense', filters: { altitude: [100, 200] } }; mgr.register({ id: 'satellites', name: 'satellites', icon: '', source: 'test', getParams() { return params; }, }); const snapshot = mgr.getLayerParams('satellites'); params.catalog = 'default'; params.filters.altitude[0] = 999; assert.deepEqual(snapshot, { catalog: 'dense', filters: { altitude: [100, 200] } }); }); test('feed state: guidance statuses are normal operation, not faults', () => { // The Military Installations wide-view prompt: zoom/search guidance must // never read DEGRADED — waiting for user action is instruction, not fault. assert.equal(layerFeedState({ status: 'zoom-in', error: 'zoom in to search', count: 12 }), 'nominal'); assert.equal(layerFeedState({ status: 'idle' }), 'nominal'); assert.equal(layerFeedState({ status: 'empty', error: 'no records in view' }), 'nominal'); // Honesty carve-out: rendered records from a genuinely stale cache still // read STALE through the guidance state. assert.equal(layerFeedState({ status: 'zoom-in', stale: true, count: 12 }), 'stale'); // Loading still wins over guidance, and a real declared outage still wins over everything. assert.equal(layerFeedState({ status: 'zoom-in', loading: true }), 'loading'); assert.equal(layerFeedState({ status: 'unavailable', error: 'down' }), 'unavailable'); // A bare error with no prior data and no guidance status remains unavailable. assert.equal(layerFeedState({ error: 'boom' }), 'unavailable'); }); test('effective visibility counts in-flight transitions as their target state', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('flights', { updateInterval: -1 }); let releaseInit; let announceInit; const initStarted = new Promise((resolve) => { announceInit = resolve; }); layer.module.init = async () => { announceInit(); await new Promise((resolve) => { releaseInit = resolve; }); }; mgr.register(layer.module); // Mid-ENABLING: settled false, effectively true, snapshot includes it. const pendingEnable = mgr.setEnabled('flights', true, { origin: 'user' }); await initStarted; assert.equal(mgr.isEnabled('flights'), false, 'settled state stays false during activation'); assert.equal(mgr.isEffectivelyEnabled('flights'), true, 'in-flight enable is effectively ON'); assert.ok(mgr.getEnabledLayerIds().has('flights'), 'snapshot captures the in-flight enable'); releaseInit(); assert.equal(await pendingEnable, true); // Mid-DISABLING: settled true, effectively false, snapshot excludes it. let releaseDisable; let announceDisable; const disableStarted = new Promise((resolve) => { announceDisable = resolve; }); layer.module.disable = async () => { announceDisable(); await new Promise((resolve) => { releaseDisable = resolve; }); }; const pendingDisable = mgr.setEnabled('flights', false, { origin: 'user' }); await disableStarted; assert.equal(mgr.isEnabled('flights'), true, 'settled state stays true during teardown'); assert.equal(mgr.isEffectivelyEnabled('flights'), false, 'in-flight disable is effectively OFF'); assert.equal(mgr.getEnabledLayerIds().has('flights'), false, 'snapshot honors the in-flight disable'); releaseDisable(); await pendingDisable; assert.equal(mgr.isEnabled('flights'), false); }); test('superseded-intent adoption re-runs visibility guards before publishing success', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('flights', { updateInterval: -1 }); let releaseDisable; let announceDisable; const disableStarted = new Promise((resolve) => { announceDisable = resolve; }); mgr.register(layer.module); assert.equal(await mgr.setEnabled('flights', true, { origin: 'user' }), true); // Slow user OFF whose cleanup will be superseded mid-flight. Only the FIRST // disable is gated; the guarded adoption's compensating disable must run // through unimpeded. let disableCalls = 0; layer.module.disable = async () => { disableCalls += 1; if (disableCalls > 1) return; announceDisable(); await new Promise((resolve) => { releaseDisable = resolve; }); }; const pendingOff = mgr.setEnabled('flights', false, { origin: 'user' }); await disableStarted; // An exclusive mode installs its guard while the OFF is still in flight. const guardChanges = []; mgr.addVisibilityGuard((change) => { guardChanges.push({ ...change }); return change.layerId === 'flights' && change.enabled ? 'Flights are unavailable in this Context mode' : null; }); const changes = []; mgr.subscribe((change) => changes.push({ ...change })); // Newer absolute ON supersedes the OFF; its adoption path must consult the // guard instead of announcing the compensated ON state as a success. const pendingOn = mgr.setEnabled('flights', true, { origin: 'user' }); releaseDisable(); const onResult = await pendingOn; await pendingOff; await mgr.waitForLayerSettled('flights'); assert.equal(onResult, false, 'guarded adoption reports the blocked request as unfulfilled'); assert.ok( guardChanges.some((change) => change.layerId === 'flights' && change.enabled), 'the guard was actually consulted for the adopted ON', ); assert.ok( changes.some((change) => change.type === 'visibility-blocked' && change.layerId === 'flights'), 'the blocked adoption is published as blocked', ); assert.equal( changes.some((change) => change.type === 'visibility' && change.layerId === 'flights' && change.enabled === true), false, 'no successful ON visibility event is published past the guard', ); assert.equal(mgr.isEnabled('flights'), false, 'the guard-forbidden adopted state is reconciled OFF'); assert.equal(mgr.isEffectivelyEnabled('flights'), false); }); test('superseding an intent inside the adoption guard publishes the exact successor envelope', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('rocket-launches', { updateInterval: -1 }); mgr.register(layer.module); assert.equal(await mgr.setEnabled('rocket-launches', true, { origin: 'user' }), true); let releaseDisable; let announceDisable; const disableStarted = new Promise((resolve) => { announceDisable = resolve; }); layer.module.disable = async () => { announceDisable(); await new Promise((resolve) => { releaseDisable = resolve; }); }; let releaseAdoptionGuard; let announceAdoptionGuard; const adoptionGuardStarted = new Promise((resolve) => { announceAdoptionGuard = resolve; }); mgr.addVisibilityGuard(async (change) => { if (change.layerId !== 'rocket-launches' || !change.enabled || change.origin !== 'replacement-b') return null; announceAdoptionGuard(); await new Promise((resolve) => { releaseAdoptionGuard = resolve; }); return null; }); const changes = []; mgr.subscribe((change) => changes.push({ ...change })); const off = mgr._setEnabledWithIntent('rocket-launches', false, { origin: 'user' }); await disableStarted; const replacementB = mgr._setEnabledWithIntent('rocket-launches', true, { origin: 'replacement-b' }); releaseDisable(); await adoptionGuardStarted; const replacementC = mgr._setEnabledWithIntent('rocket-launches', true, { origin: 'replacement-c' }); releaseAdoptionGuard(); assert.equal(await off.promise, false); assert.equal(await replacementB.promise, false); assert.equal(await replacementC.promise, true); const outcomeB = await mgr._waitForVisibilityIntent('rocket-launches', replacementB.intentEpoch); assert.equal(outcomeB.cancellationReason, 'superseded'); assert.equal(outcomeB.successorIntentEpoch, replacementC.intentEpoch); assert.equal(outcomeB.successorEnabled, true); assert.equal(outcomeB.successorOrigin, 'replacement-c'); assert.ok(changes.some((change) => ( change.type === 'visibility-cancelled' && change.intentEpoch === replacementB.intentEpoch && change.successorIntentEpoch === replacementC.intentEpoch ))); assert.equal(mgr.isEnabled('rocket-launches'), true); }); test('effective visibility follows the newest absolute intent in both supersede directions', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('flights', { updateInterval: -1 }); let releaseInit; let announceInit; const initStarted = new Promise((resolve) => { announceInit = resolve; }); layer.module.init = async () => { announceInit(); await new Promise((resolve) => { releaseInit = resolve; }); }; mgr.register(layer.module); // OFF supersedes an in-flight enable: from the synchronous moment of the // OFF request, effective visibility must read false even though the // superseded transaction's lifecycleState still says 'enabling'. const pendingOn = mgr.setEnabled('flights', true, { origin: 'user' }); await initStarted; assert.equal(mgr.isEffectivelyEnabled('flights'), true); const pendingOff = mgr.setEnabled('flights', false, { origin: 'user' }); assert.equal( mgr.isEffectivelyEnabled('flights'), false, 'synchronously after OFF supersedes, effective visibility is OFF', ); assert.equal(mgr.getEnabledLayerIds().has('flights'), false); releaseInit(); await pendingOn; await pendingOff; await mgr.waitForLayerSettled('flights'); assert.equal(mgr.isEnabled('flights'), false); // ON supersedes an in-flight disable: the inverse direction. assert.equal(await mgr.setEnabled('flights', true, { origin: 'user' }), true); let releaseDisable; let announceDisable; const disableStarted = new Promise((resolve) => { announceDisable = resolve; }); let disableCalls = 0; layer.module.disable = async () => { disableCalls += 1; if (disableCalls > 1) return; announceDisable(); await new Promise((resolve) => { releaseDisable = resolve; }); }; const pendingOff2 = mgr.setEnabled('flights', false, { origin: 'user' }); await disableStarted; assert.equal(mgr.isEffectivelyEnabled('flights'), false); const pendingOn2 = mgr.setEnabled('flights', true, { origin: 'user' }); assert.equal( mgr.isEffectivelyEnabled('flights'), true, 'synchronously after ON supersedes, effective visibility is ON', ); assert.ok(mgr.getEnabledLayerIds().has('flights')); releaseDisable(); await pendingOff2; await pendingOn2; await mgr.waitForLayerSettled('flights'); assert.equal(mgr.isEnabled('flights'), true); assert.equal(mgr.isEffectivelyEnabled('flights'), true, 'settled fallback after all intents release'); }); test('a newer absolute intent aborts a hung guard-compensation instead of starving', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('flights', { updateInterval: -1 }); let releaseFirstDisable; let announceFirstDisable; const firstDisableStarted = new Promise((resolve) => { announceFirstDisable = resolve; }); let hangCompensation = false; let compensationAborted = false; mgr.register(layer.module); assert.equal(await mgr.setEnabled('flights', true, { origin: 'user' }), true); let disableCalls = 0; layer.module.disable = async (_viewer, { signal } = {}) => { disableCalls += 1; if (disableCalls === 1) { announceFirstDisable(); await new Promise((resolve) => { releaseFirstDisable = resolve; }); return; } if (hangCompensation) { // Hang until the manager aborts this transition; resolve on abort so the // lifecycle can run its cancellation path. await new Promise((resolve) => { if (signal?.aborted) { compensationAborted = true; resolve(); return; } signal?.addEventListener('abort', () => { compensationAborted = true; resolve(); }, { once: true }); }); return false; } }; const pendingOff = mgr.setEnabled('flights', false, { origin: 'user' }); await firstDisableStarted; const removeGuard = mgr.addVisibilityGuard((change) => ( change.layerId === 'flights' && change.enabled ? 'blocked by mode' : null )); hangCompensation = true; const pendingBlockedOn = mgr.setEnabled('flights', true, { origin: 'user' }); releaseFirstDisable(); // Give the blocked adoption time to enter its hung compensation. await new Promise((resolve) => setTimeout(resolve, 10)); removeGuard(); // The newest intent must be able to abort the hung compensation and run. const pendingFinalOn = mgr.setEnabled('flights', true, { origin: 'user' }); const finalOn = await Promise.race([ pendingFinalOn, new Promise((resolve) => setTimeout(() => resolve('starved'), 2000)), ]); assert.notEqual(finalOn, 'starved', 'newest intent must not starve behind the compensation'); assert.equal(compensationAborted, true, 'the hung compensation was aborted'); assert.equal(await pendingBlockedOn, false, 'the guard-blocked request stays unfulfilled'); assert.equal(finalOn, true, 'the newest ON wins once the guard is gone'); await pendingOff; await mgr.waitForLayerSettled('flights'); assert.equal(mgr.isEnabled('flights'), true); }); test('re-entrant setEnabled from a blocked-adoption listener supersedes the compensation cleanly', async () => { const mgr = new DataLayerManager({}); const layer = makeSlowLayer('flights', { updateInterval: -1 }); let releaseFirstDisable; let announceFirstDisable; const firstDisableStarted = new Promise((resolve) => { announceFirstDisable = resolve; }); mgr.register(layer.module); assert.equal(await mgr.setEnabled('flights', true, { origin: 'user' }), true); let disableCalls = 0; layer.module.disable = async () => { disableCalls += 1; if (disableCalls === 1) { announceFirstDisable(); await new Promise((resolve) => { releaseFirstDisable = resolve; }); } }; const pendingOff = mgr.setEnabled('flights', false, { origin: 'user' }); await firstDisableStarted; let removeGuard = mgr.addVisibilityGuard((change) => ( change.layerId === 'flights' && change.enabled ? 'blocked by mode' : null )); // The moment the blocked event is announced, a listener re-enters with a // newer absolute intent — this used to land in the unregistered-compensation // window and starve, or orphan the transitional presentation. let reentrantResult = null; const effectiveDuringBlocked = []; const unsubscribe = mgr.subscribe((change) => { if (change.type !== 'visibility-blocked' || change.layerId !== 'flights') return; effectiveDuringBlocked.push(mgr.isEffectivelyEnabled('flights')); removeGuard(); reentrantResult = mgr.setEnabled('flights', true, { origin: 'user' }); }); const pendingBlockedOn = mgr.setEnabled('flights', true, { origin: 'user' }); releaseFirstDisable(); const blockedOn = await pendingBlockedOn; const reentrant = await Promise.race([ (async () => reentrantResult === null ? 'never-fired' : await reentrantResult)(), new Promise((resolve) => setTimeout(() => resolve('starved'), 2000)), ]); await pendingOff; await mgr.waitForLayerSettled('flights'); unsubscribe(); assert.equal(blockedOn, false, 'the guard-blocked request stays unfulfilled'); assert.notEqual(reentrant, 'starved', 'the re-entrant newest intent must not starve'); assert.notEqual(reentrant, 'never-fired', 'the blocked event fired and re-entered'); assert.deepEqual( effectiveDuringBlocked, [false], 'during the blocked callback, effective visibility reads the reconciliation target (OFF)', ); assert.equal(mgr.isEnabled('flights'), true, 'the re-entrant ON wins after the guard is removed'); const lifecycle = mgr.getLayerLifecycleState('flights'); assert.equal(lifecycle.lifecycleState, 'enabled', 'no orphaned transitional presentation'); assert.equal(lifecycle.uncertain, false); }); test('every manager registration exposes the normalized loading and refresh contract', () => { const mgr = new DataLayerManager({}); mgr.register({ id: 'minimal', name: 'Minimal', icon: '', source: 'test', updateInterval: -1, init() {}, enable() {}, disable() {}, update() {}, }); mgr.register({ id: 'specific', name: 'Specific', icon: '', source: 'test', updateInterval: -1, init() {}, enable() {}, disable() {}, update() {}, getStats() { return { count: 4, lastUpdate: 123, error: 'module-owned error', available: false, customHealth: 'preserved', }; }, }); mgr.layers.get('specific').initialized = true; for (const layer of mgr.getAll()) { assert.equal(typeof layer.stats.loading, 'boolean', `${layer.id} loading must be normalized`); assert.equal(typeof layer.stats.refreshing, 'boolean', `${layer.id} refreshing must be normalized`); assert.ok(Object.hasOwn(layer.stats, 'managerRefreshError')); } const specific = mgr.getAll().find(({ id }) => id === 'specific').stats; assert.equal(specific.error, 'module-owned error'); assert.equal(specific.available, false); assert.equal(specific.customHealth, 'preserved'); }); test('periodic refresh publishes work, failure, and later manager-owned recovery', async () => { const mgr = new DataLayerManager({}); let updateResult = true; let moduleError = null; let releaseUpdate; let updateStarted; const started = new Promise((resolve) => { updateStarted = resolve; }); const events = []; mgr.register({ id: 'flights', name: 'Live Flights', icon: '', source: 'test', updateInterval: 30000, init() {}, enable() {}, disable() {}, async update() { updateStarted(); await new Promise((resolve) => { releaseUpdate = resolve; }); return updateResult; }, getStats() { return { count: 8, lastUpdate: 123, error: moduleError, available: true }; }, }); const entry = mgr.layers.get('flights'); entry.initialized = true; entry.enabled = true; entry.lifecycleState = 'enabled'; mgr.subscribe((event) => events.push(event)); updateResult = false; const failedRefresh = mgr._runPeriodicUpdate('flights', entry); await started; assert.equal(mgr.getAll()[0].stats.refreshing, true); assert.equal(mgr.getAll()[0].lifecycleState, 'enabled'); releaseUpdate(); assert.equal(await failedRefresh, false); assert.match(mgr.getAll()[0].stats.managerRefreshError, /refresh rejected/); assert.deepEqual(events.map(({ type }) => type), ['refresh-transition', 'refresh-failed']); updateStarted = () => {}; updateResult = true; entry.module.update = async () => true; assert.equal(await mgr._runPeriodicUpdate('flights', entry), true); assert.equal(mgr.getAll()[0].stats.managerRefreshError, null); assert.deepEqual(events.map(({ type }) => type), [ 'refresh-transition', 'refresh-failed', 'refresh-transition', 'refresh', ]); assert.equal(mgr.isEnabled('flights'), true, 'refresh state never owns visibility'); let explicitRefreshCalls = 0; entry.module.update = async (_viewer, { signal } = {}) => { assert.equal(signal, null); explicitRefreshCalls += 1; return true; }; assert.equal(await mgr.refreshLayer('flights'), true); assert.equal(explicitRefreshCalls, 1, 'an enabled layer can be refreshed on demand'); }); test('periodic rejection preserves a module-specific error and recovers independently', async () => { const mgr = new DataLayerManager({}); let moduleError = 'upstream-specific outage'; let shouldReject = true; mgr.register({ id: 'satellites', name: 'Satellites', icon: '', source: 'test', updateInterval: 0, refreshInterval: 300000, init() {}, enable() {}, disable() {}, async update() { if (shouldReject) throw new Error('network rejected'); return true; }, getStats() { return { count: 12, lastUpdate: 456, error: moduleError, available: false }; }, }); const entry = mgr.layers.get('satellites'); entry.initialized = true; entry.enabled = true; entry.lifecycleState = 'enabled'; assert.equal(await mgr._runPeriodicUpdate('satellites', entry), false); let stats = mgr.getAll()[0].stats; assert.equal(stats.error, 'upstream-specific outage'); assert.equal(stats.available, false); assert.equal(stats.managerRefreshError, 'network rejected'); shouldReject = false; moduleError = null; entry.module.getStats = () => ({ count: 13, lastUpdate: 789, error: null, available: true }); assert.equal(await mgr._runPeriodicUpdate('satellites', entry), true); stats = mgr.getAll()[0].stats; assert.equal(stats.error, null); assert.equal(stats.available, true); assert.equal(stats.managerRefreshError, null); }); test('disable invalidates an active periodic refresh without publishing stale settlement', async () => { const mgr = new DataLayerManager({}); let releaseRefresh; let announceRefresh; const refreshStarted = new Promise((resolve) => { announceRefresh = resolve; }); const events = []; mgr.register({ id: 'flights', name: 'Live Flights', icon: '', source: 'test', updateInterval: 30000, init() {}, enable() {}, disable() {}, async update() { announceRefresh(); await new Promise((resolve) => { releaseRefresh = resolve; }); throw new Error('late refresh failure'); }, getStats() { return { count: 4, lastUpdate: 123, error: null, available: true }; }, }); const entry = mgr.layers.get('flights'); entry.initialized = true; entry.enabled = true; entry.lifecycleState = 'enabled'; mgr.subscribe((event) => events.push(event)); const pendingRefresh = mgr._runPeriodicUpdate('flights', entry); await refreshStarted; const waitingRefresh = mgr.refreshLayer('flights'); await Promise.resolve(); const pendingDisable = mgr.setEnabled('flights', false, { origin: 'user' }); assert.equal(await waitingRefresh, false); await new Promise((resolve) => setTimeout(resolve, 0)); releaseRefresh(); assert.equal(await pendingRefresh, false); assert.equal(await pendingDisable, true); assert.equal(mgr.isEnabled('flights'), false); assert.equal(entry.refreshing, false); assert.equal(entry.managerRefreshError, null); assert.ok(events.some(({ type }) => type === 'refresh-transition')); assert.ok(events.some(({ type, reason }) => ( type === 'refresh-cancelled' && reason === 'layer-disabled' ))); assert.ok(!events.some(({ type }) => type === 'refresh-failed' || type === 'refresh')); }); test('destroy settles an explicit refresh waiting behind invalidated periodic work', async () => { const mgr = new DataLayerManager({}); let releaseRefresh; let announceRefresh; const refreshStarted = new Promise((resolve) => { announceRefresh = resolve; }); const events = []; mgr.register({ id: 'flights', name: 'Live Flights', icon: '', source: 'test', updateInterval: 30000, init() {}, enable() {}, disable() {}, async update() { announceRefresh(); await new Promise((resolve) => { releaseRefresh = resolve; }); return true; }, getStats() { return { count: 4, lastUpdate: 123, error: null, available: true }; }, }); const entry = mgr.layers.get('flights'); entry.initialized = true; entry.enabled = true; entry.lifecycleState = 'enabled'; mgr.subscribe((event) => events.push(event)); const periodicRefresh = mgr._runPeriodicUpdate('flights', entry); await refreshStarted; const requestedRefresh = mgr.refreshLayer('flights'); await Promise.resolve(); const destroy = mgr.destroyLayer('flights'); assert.equal(await requestedRefresh, false); assert.ok(events.some(({ type, reason }) => ( type === 'refresh-cancelled' && reason === 'layer-destroyed' ))); releaseRefresh(); assert.equal(await periodicRefresh, false); assert.equal(await destroy, true); }); // ── Per-layer row controls (chips + color legend) ─────────────────────────── // The satellites layer is the first consumer: a DENSE catalog chip and a class // legend rendered under its row. The manager owns the DOM and the param write; // the layer only declares what it wants, so the chip can never disagree with // the layer's real state. /** DOM double rich enough for the row-controls render path. */ function makeControlElement() { const element = { children: [], className: '', dataset: {}, style: {}, attributes: {}, listeners: {}, textContent: '', hidden: false, disabled: false, title: '', type: '', classList: { toggle() {} }, appendChild(child) { child.parent = this; this.children.push(child); return child; }, append(...nodes) { for (const n of nodes) n.parent = this; this.children.push(...nodes); }, replaceChildren(...nodes) { this.children = [...nodes]; }, remove() { const siblings = this.parent?.children; if (siblings) this.parent.children = siblings.filter((n) => n !== this); // Browsers blur a node the moment it leaves the document. Modelling that // is the whole point: it is exactly what in-place chip reconciliation // exists to avoid, so a regression to rebuild-everything must fail here. if (globalThis.document?.activeElement === this) globalThis.document.activeElement = null; }, focus() { if (globalThis.document) globalThis.document.activeElement = this; }, addEventListener(name, handler) { this.listeners[name] = handler; }, removeEventListener(name, handler) { if (this.listeners[name] === handler) delete this.listeners[name]; }, setAttribute(name, value) { this.attributes[name] = String(value); }, getAttribute(name) { return this.attributes[name] ?? null; }, closest(selector) { const className = selector.slice(1); return String(this.className).split(/\s+/).includes(className) ? this : null; }, querySelector(selector) { if (selector.startsWith('[data-layer-id="')) { const id = selector.slice(16, -2); return this.children.find((child) => child.dataset.layerId === id) || null; } const className = selector.startsWith('.') ? selector.slice(1) : ''; const visit = (node) => { if (String(node.className).split(/\s+/).includes(className)) return node; for (const child of node.children || []) { const found = visit(child); if (found) return found; } return null; }; return visit(this); }, set innerHTML(value) { if (value === '') this.children = []; }, get innerHTML() { return ''; }, }; return element; } test('keyboard focus survives Data Layer enabling and disabling transitions', async () => { const originalDocument = globalThis.document; globalThis.document = { createElement: makeControlElement, activeElement: null }; const mgr = new DataLayerManager({}); let releaseEnable; let releaseDisable; const calls = { enable: 0, disable: 0 }; mgr.register({ id: 'focus-layer', name: 'Focus layer', icon: '', source: 'Local focus fixture', updateInterval: -1, init() {}, enable() { calls.enable += 1; return new Promise((resolve) => { releaseEnable = resolve; }); }, disable() { calls.disable += 1; return new Promise((resolve) => { releaseDisable = resolve; }); }, update() {}, destroy() {}, getStats() { return { count: 1, lastUpdate: Date.now() }; }, }); const container = makeControlElement(); const waitFor = async (predicate, label) => { for (let attempt = 0; attempt < 20; attempt += 1) { if (predicate()) return; await Promise.resolve(); } assert.fail(`timed out waiting for ${label}`); }; try { mgr.buildTogglePanel(container); const row = container.querySelector('[data-layer-id="focus-layer"]'); const button = row.querySelector('.data-toggle-btn'); button.focus(); const enablingClick = button.listeners.click(); await waitFor(() => typeof releaseEnable === 'function', 'enable transition'); assert.equal(globalThis.document.activeElement, button); assert.equal(button.disabled, false, 'ENABLING never uses native disabled'); assert.equal(button.getAttribute('aria-disabled'), 'true'); assert.equal(button.getAttribute('aria-busy'), 'true'); assert.equal(button.textContent, 'ENABLING'); await button.listeners.click(); assert.equal(calls.enable, 1, 'repeat activation is inert while enabling'); assert.equal(mgr.layers.get('focus-layer').visibilityIntentEpoch, 1); releaseEnable(); await enablingClick; assert.equal(globalThis.document.activeElement, button); assert.equal(button.disabled, false); assert.equal(button.getAttribute('aria-disabled'), 'false'); assert.equal(button.getAttribute('aria-busy'), 'false'); assert.equal(mgr.isEnabled('focus-layer'), true); const disablingClick = button.listeners.click(); await waitFor(() => typeof releaseDisable === 'function', 'disable transition'); assert.equal(globalThis.document.activeElement, button); assert.equal(button.disabled, false, 'DISABLING never uses native disabled'); assert.equal(button.getAttribute('aria-disabled'), 'true'); assert.equal(button.getAttribute('aria-busy'), 'true'); assert.equal(button.textContent, 'DISABLING'); await button.listeners.click(); assert.equal(calls.disable, 1, 'repeat activation is inert while disabling'); assert.equal(mgr.layers.get('focus-layer').visibilityIntentEpoch, 2); releaseDisable(); await disablingClick; assert.equal(globalThis.document.activeElement, button); assert.equal(button.disabled, false); assert.equal(button.getAttribute('aria-disabled'), 'false'); assert.equal(button.getAttribute('aria-busy'), 'false'); assert.equal(mgr.isEnabled('focus-layer'), false); } finally { releaseEnable?.(); releaseDisable?.(); await mgr.destroyAll(); if (originalDocument === undefined) delete globalThis.document; else globalThis.document = originalDocument; } }); test('a failed Data Layer transition clears busy state without losing keyboard focus', async () => { const originalDocument = globalThis.document; const originalWarn = console.warn; globalThis.document = { createElement: makeControlElement, activeElement: null }; console.warn = () => {}; const mgr = new DataLayerManager({}); let rejectEnable; let enableCalls = 0; mgr.register({ id: 'focus-failure-layer', name: 'Focus failure layer', icon: '', source: 'Local focus fixture', updateInterval: -1, init() {}, enable() { enableCalls += 1; if (enableCalls > 1) return true; return new Promise((resolve, reject) => { rejectEnable = reject; }); }, disable() { return true; }, update() {}, destroy() {}, getStats() { return { count: 0, lastUpdate: null }; }, }); const container = makeControlElement(); try { mgr.buildTogglePanel(container); const button = container .querySelector('[data-layer-id="focus-failure-layer"]') .querySelector('.data-toggle-btn'); button.focus(); const failedClick = button.listeners.click(); for (let attempt = 0; attempt < 20 && typeof rejectEnable !== 'function'; attempt += 1) { await Promise.resolve(); } assert.equal(typeof rejectEnable, 'function'); assert.equal(globalThis.document.activeElement, button); assert.equal(button.getAttribute('aria-disabled'), 'true'); assert.equal(button.getAttribute('aria-busy'), 'true'); rejectEnable(new Error('QA enable rejected')); await failedClick; assert.equal(globalThis.document.activeElement, button, 'failure settlement preserves focus'); assert.equal(button.disabled, false); assert.equal(button.getAttribute('aria-disabled'), 'false'); assert.equal(button.getAttribute('aria-busy'), 'false'); assert.equal(button.textContent, 'OFF'); assert.deepEqual(mgr.getLayerLifecycleState('focus-failure-layer'), { enabled: false, lifecycleState: 'disabled', uncertain: false, }); await button.listeners.click(); assert.equal(enableCalls, 2, 'the settled control accepts a later retry'); assert.equal(globalThis.document.activeElement, button); assert.equal(mgr.isEnabled('focus-failure-layer'), true); } finally { rejectEnable?.(new Error('test cleanup')); console.warn = originalWarn; await mgr.destroyAll(); if (originalDocument === undefined) delete globalThis.document; else globalThis.document = originalDocument; } }); /** Collect every node in a rendered subtree carrying `className`. */ function collectByClass(node, className) { const found = []; const visit = (current) => { if (String(current.className).split(/\s+/).includes(className)) found.push(current); for (const child of current.children || []) visit(child); }; visit(node); return found; } /** A layer that declares a two-state chip plus a legend, like satellites. */ function makeRowControlLayer() { let mode = 'core'; return { get mode() { return mode; }, module: { id: 'satellites', name: 'Satellites', icon: '', source: 'CelesTrak', updateInterval: -1, async init() {}, enable() {}, disable() {}, async update() {}, getStats() { return { count: 3, lastUpdate: Date.now() }; }, setParams(params) { if (params.catalog) mode = params.catalog; }, getParams() { return { catalog: mode }; }, getRowControls() { const dense = mode === 'dense'; return { chips: [{ id: 'catalog', label: 'DENSE', active: dense, title: 'toggle the dense catalog', params: { catalog: dense ? 'core' : 'dense' }, }], legend: [ { klass: 'nav', label: 'NAV', color: '#4fd8ff', blurb: 'GNSS', count: 2 }, { klass: 'geo', label: 'GEO', color: '#c89bff', blurb: 'belt', count: 5 }, ], }; }, }, }; } test('a layer that declares row controls renders its chips and color legend', async () => { const originalDocument = globalThis.document; globalThis.document = { createElement: makeControlElement }; const mgr = new DataLayerManager({}); const layer = makeRowControlLayer(); mgr.register(layer.module); const container = makeControlElement(); try { mgr.buildTogglePanel(container); const row = container.querySelector('[data-layer-id="satellites"]'); const controls = row.querySelector('.data-toggle-controls'); assert.ok(controls, 'the row gained a controls block'); // Disabled layers stay quiet — no chip, no legend. assert.equal(controls.hidden, true); assert.equal(await mgr.setEnabled('satellites', true), true); mgr._refreshTogglePanel(); assert.equal(controls.hidden, false); const chips = collectByClass(controls, 'data-toggle-chip'); assert.equal(chips.length, 1); assert.equal(chips[0].textContent, 'DENSE'); assert.equal(chips[0].dataset.chipId, 'catalog'); assert.equal(chips[0].attributes['aria-pressed'], 'false'); assert.equal(chips[0].title, 'toggle the dense catalog'); const swatches = collectByClass(controls, 'data-toggle-legend-swatch'); assert.deepEqual(swatches.map((s) => s.style.background), ['#4fd8ff', '#c89bff'], 'each legend swatch is painted the exact class color'); const items = collectByClass(controls, 'data-toggle-legend-item'); assert.deepEqual(items.map((i) => i.title), ['GNSS', 'belt']); assert.equal(items.length, 2); } finally { await mgr.destroyAll(); if (originalDocument === undefined) delete globalThis.document; else globalThis.document = originalDocument; } }); test('clicking a row chip applies the params it declared and re-renders', async () => { const originalDocument = globalThis.document; globalThis.document = { createElement: makeControlElement }; const mgr = new DataLayerManager({}); const layer = makeRowControlLayer(); mgr.register(layer.module); const container = makeControlElement(); try { mgr.buildTogglePanel(container); assert.equal(await mgr.setEnabled('satellites', true), true); const row = container.querySelector('[data-layer-id="satellites"]'); const controls = row.querySelector('.data-toggle-controls'); const chip = collectByClass(controls, 'data-toggle-chip')[0]; controls.listeners.click({ target: chip }); assert.equal(layer.mode, 'dense', 'the chip wrote the params it declared'); // setLayerParams refreshes the panel, so the chip already reflects the flip. const afterOn = collectByClass(controls, 'data-toggle-chip')[0]; assert.equal(afterOn.attributes['aria-pressed'], 'true'); assert.equal(afterOn.className.includes('active'), true); controls.listeners.click({ target: afterOn }); assert.equal(layer.mode, 'core', 'the chip toggles back rather than latching'); assert.equal(collectByClass(controls, 'data-toggle-chip')[0].attributes['aria-pressed'], 'false'); } finally { await mgr.destroyAll(); if (originalDocument === undefined) delete globalThis.document; else globalThis.document = originalDocument; } }); test('a click outside a chip is inert, and a throwing layer cannot blank the panel', async () => { const originalDocument = globalThis.document; globalThis.document = { createElement: makeControlElement }; const warn = console.warn; console.warn = () => {}; const mgr = new DataLayerManager({}); const layer = makeRowControlLayer(); mgr.register(layer.module); const container = makeControlElement(); try { mgr.buildTogglePanel(container); assert.equal(await mgr.setEnabled('satellites', true), true); const row = container.querySelector('[data-layer-id="satellites"]'); const controls = row.querySelector('.data-toggle-controls'); const legendItem = collectByClass(controls, 'data-toggle-legend-item')[0]; controls.listeners.click({ target: legendItem }); assert.equal(layer.mode, 'core', 'the legend is not a control'); controls.listeners.click({ target: { closest: () => null } }); assert.equal(layer.mode, 'core'); layer.module.getRowControls = () => { throw new Error('boom'); }; mgr._refreshTogglePanel(); assert.equal(controls.hidden, true, 'a throwing layer collapses to an empty block'); assert.ok(container.querySelector('[data-layer-id="satellites"]'), 'the row itself survives'); } finally { console.warn = warn; await mgr.destroyAll(); if (originalDocument === undefined) delete globalThis.document; else globalThis.document = originalDocument; } }); test('keyboard focus on a chip survives the refresh its own click triggers', async () => { // _syncRowControls runs on EVERY panel refresh, including the one the chip's // own click triggers. Rebuilding the button would blur it every time, so a // keyboard user loses their place on activation. const originalDocument = globalThis.document; globalThis.document = { createElement: makeControlElement, activeElement: null }; const mgr = new DataLayerManager({}); const layer = makeRowControlLayer(); mgr.register(layer.module); const container = makeControlElement(); try { mgr.buildTogglePanel(container); assert.equal(await mgr.setEnabled('satellites', true), true); const controls = container .querySelector('[data-layer-id="satellites"]') .querySelector('.data-toggle-controls'); const chip = collectByClass(controls, 'data-toggle-chip')[0]; chip.focus(); assert.equal(globalThis.document.activeElement, chip, 'the chip starts focused'); controls.listeners.click({ target: chip }); assert.equal(globalThis.document.activeElement, chip, 'activating the chip does not blur it'); assert.equal(collectByClass(controls, 'data-toggle-chip')[0], chip, 'the SAME button node is reused across the click-driven refresh'); mgr._refreshTogglePanel(); mgr._refreshTogglePanel(); assert.equal(globalThis.document.activeElement, chip, 'repeated refreshes never steal focus'); // Legend entries hold no focus, so they may be replaced — never duplicated. assert.equal(collectByClass(controls, 'data-toggle-legend-item').length, 2); // ...and a chip that genuinely goes away still releases focus. layer.module.getRowControls = () => ({ chips: [], legend: [] }); mgr._refreshTogglePanel(); assert.equal(globalThis.document.activeElement, null); } finally { await mgr.destroyAll(); if (originalDocument === undefined) delete globalThis.document; else globalThis.document = originalDocument; } }); test('an async layer pushes its own row refresh, and a busy chip refuses clicks', async () => { const originalDocument = globalThis.document; globalThis.document = { createElement: makeControlElement }; const mgr = new DataLayerManager({}); let settled = false; let writes = 0; const module = { id: 'satellites', name: 'Satellites', icon: '', source: 'CelesTrak', updateInterval: -1, async init() {}, enable() {}, disable() {}, async update() {}, getStats() { return { count: 1, lastUpdate: Date.now() }; }, setParams() { writes += 1; }, getRowControls() { return { chips: [{ id: 'catalog', label: settled ? 'DENSE' : 'DENSE ···', active: settled, busy: !settled, disabled: !settled, state: settled ? 'active' : 'loading', title: 'x', params: { catalog: 'core' }, }], legend: [], }; }, setRowControlsListener(fn) { module._listener = fn; }, }; mgr.register(module); const container = makeControlElement(); try { mgr.buildTogglePanel(container); assert.equal(await mgr.setEnabled('satellites', true), true); assert.equal(typeof module._listener, 'function', 'the manager installed its listener'); const controls = container .querySelector('[data-layer-id="satellites"]') .querySelector('.data-toggle-controls'); const chip = collectByClass(controls, 'data-toggle-chip')[0]; assert.equal(chip.textContent, 'DENSE ···'); assert.equal(chip.disabled, true); assert.equal(chip.attributes['aria-busy'], 'true'); assert.equal(chip.attributes['aria-pressed'], 'false', 'busy is never reported as active'); assert.equal(chip.className.includes('chip-loading'), true); controls.listeners.click({ target: chip }); assert.equal(writes, 0, 'a disabled chip is inert'); // The layer settles and pushes its own refresh — no panel poll involved. settled = true; module._listener(); assert.equal(chip.textContent, 'DENSE'); assert.equal(chip.disabled, false); assert.equal(chip.attributes['aria-pressed'], 'true'); assert.equal(chip.attributes['aria-busy'], 'false'); controls.listeners.click({ target: chip }); assert.equal(writes, 1, 'the settled chip writes again'); } finally { await mgr.destroyAll(); if (originalDocument === undefined) delete globalThis.document; else globalThis.document = originalDocument; } }); test('a layer that surrenders its row controls hides the block entirely', async () => { // Space Missions borrows the satellite layer with showPoints:false; the row // must not advertise a legend for an empty sky or offer a chip whose write // the dependency owner will silently revert. const originalDocument = globalThis.document; globalThis.document = { createElement: makeControlElement }; const mgr = new DataLayerManager({}); let surrendered = false; const layer = makeRowControlLayer(); const inner = layer.module.getRowControls; layer.module.getRowControls = () => (surrendered ? { chips: [], legend: [] } : inner()); mgr.register(layer.module); const container = makeControlElement(); try { mgr.buildTogglePanel(container); assert.equal(await mgr.setEnabled('satellites', true), true); const controls = container .querySelector('[data-layer-id="satellites"]') .querySelector('.data-toggle-controls'); assert.equal(controls.hidden, false); assert.equal(collectByClass(controls, 'data-toggle-chip').length, 1); surrendered = true; mgr._refreshTogglePanel(); assert.equal(controls.hidden, true); assert.equal(collectByClass(controls, 'data-toggle-chip').length, 0, 'the chip is removed, not merely hidden behind a style'); assert.equal(collectByClass(controls, 'data-toggle-legend-item').length, 0); surrendered = false; mgr._refreshTogglePanel(); assert.equal(controls.hidden, false, 'the row returns when the owner releases it'); assert.equal(collectByClass(controls, 'data-toggle-chip').length, 1); } finally { await mgr.destroyAll(); if (originalDocument === undefined) delete globalThis.document; else globalThis.document = originalDocument; } }); test('layer metadata shows a guidance prompt without reporting it as a fault', () => { const mgr = new DataLayerManager({}); const text = mgr._buildMetaText({ enabled: true, source: 'OpenStreetMap', stats: { status: 'zoom-in', error: null, count: 0, statusMessage: 'Zoom in to search mapped installations' }, }); assert.match(text, /Zoom in to search mapped installations$/); assert.doesNotMatch(text, /UNAVAILABLE|DEGRADED/); }); test('panel remount releases old listeners and destruction revokes retained controls', async () => { const originalDocument = globalThis.document; globalThis.document = { createElement: makeControlElement, activeElement: null }; const manager = new DataLayerManager({}); const layer = makeSlowLayer('lifecycle-panel', { updateInterval: -1 }); let rowListener; layer.module.getRowControls = () => ({ chips: [] }); layer.module.setRowControlsListener = (listener) => { rowListener = listener; }; manager.register(layer.module); try { const first = makeControlElement(); manager.buildTogglePanel(first); const oldButton = first.querySelector('.data-toggle-btn'); const retainedClick = oldButton.listeners.click; const second = makeControlElement(); manager.buildTogglePanel(second); assert.equal(oldButton.listeners.click, undefined); await retainedClick(); assert.equal(layer.calls.enable, 0, 'a revoked generation cannot issue actions'); const currentButton = second.querySelector('.data-toggle-btn'); assert.equal(typeof currentButton.listeners.click, 'function'); assert.equal(typeof rowListener, 'function'); await manager.destroyAll(); assert.equal(currentButton.listeners.click, undefined); assert.equal(rowListener, null); } finally { await manager.destroyAll(); if (originalDocument === undefined) delete globalThis.document; else globalThis.document = originalDocument; } }); test('row action chips use live disabled state and descriptive counts without writing parameters', async () => { const originalDocument = globalThis.document; globalThis.document = { createElement: makeControlElement }; const mgr = new DataLayerManager({}); const layer = makeRowControlLayer(); let calls = 0, blocked = false; layer.module.getStats = () => ({ count: 3, countLabel: '3 nearby' }); layer.module.getRowControls = () => ({ chips: [{ id: 'find', label: 'SHOW NEAREST', disabled: blocked, onClick: () => calls++ }] }); layer.module.setParams = () => { throw new Error('navigation must not write persistent params'); }; mgr.register(layer.module); const container = makeControlElement(); try { mgr.buildTogglePanel(container); await mgr.setEnabled('satellites', true); const row = container.querySelector('[data-layer-id="satellites"]'); assert.equal(row.querySelector('.data-count').textContent, '3 nearby'); const controls = row.querySelector('.data-toggle-controls'); const chip = collectByClass(controls, 'data-toggle-chip')[0]; controls.listeners.click({ target: chip }); assert.equal(calls, 1); blocked = true; controls.listeners.click({ target: chip }); assert.equal(calls, 1, 'live disabled descriptor wins before row repaint'); blocked = false; await mgr.setEnabled('satellites', false); controls.listeners.click({ target: chip }); assert.equal(calls, 1, 'disabled layer cannot navigate'); } finally { await mgr.destroyAll(); if (originalDocument === undefined) delete globalThis.document; else globalThis.document = originalDocument; } });