From f0ee1b95caf12d48414787b3103de74ae9cb68d3 Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Fri, 13 Nov 2026 18:12:44 -0800 Subject: [PATCH] Collapse the CCTV panel when the user switches CCTV off inside Contacts Turning a layer on does not leave Contacts, so a user could turn CCTV back on inside the mode and off again; the layer was still in the session snapshot and the panel stayed open on an empty frame. The snapshot now records every layer the user switched during the session, and suspendsLayer is true only for a layer isolation switched off that the user has not touched since. --- src/contextModePolicy.js | 1 + src/ui/contextControls.js | 9 +++++++-- src/ui/contextControls.test.mjs | 36 +++++++++++++++++++++++++++++++++ src/ui/contextSession.js | 3 +++ 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/contextModePolicy.js b/src/contextModePolicy.js index 6e3238ec5..ef485fe97 100644 --- a/src/contextModePolicy.js +++ b/src/contextModePolicy.js @@ -277,6 +277,7 @@ export function recordContextSessionUserChange({ ) { return false; } + snapshot.userSwitched?.add(change.layerId); const isCompanion = CONTEXT_COMPANIONS.has(change.layerId); if (change.enabled) { if (CONTEXT_ENTRY_LAYER_IDS.includes(change.layerId)) return false; diff --git a/src/ui/contextControls.js b/src/ui/contextControls.js index 552a9e3e9..3c297f277 100644 --- a/src/ui/contextControls.js +++ b/src/ui/contextControls.js @@ -61,12 +61,17 @@ export class ContextControls { } /** * Whether Context isolation switched this layer off and leaving the mode - * restores it. Such a layer is suspended, not switched off by the user. + * restores it. Such a layer is suspended, not switched off by the user: a + * layer the user switched during the session is theirs again. * @param {string} layerId Layer id. * @returns {boolean} */ suspendsLayer(layerId) { - return Boolean(this._contextSessionSnapshot?.enabledLayerIds?.has(layerId)); + const snapshot = this._contextSessionSnapshot; + return Boolean( + snapshot?.enabledLayerIds?.has(layerId) && + !snapshot.userSwitched?.has(layerId), + ); } setClearBusy(...args) { if (!this.destroyed) this.actions.setClearBusy(...args); diff --git a/src/ui/contextControls.test.mjs b/src/ui/contextControls.test.mjs index 4a4e69a9f..caaba2060 100644 --- a/src/ui/contextControls.test.mjs +++ b/src/ui/contextControls.test.mjs @@ -1,6 +1,7 @@ import assert from 'node:assert/strict'; import { test } from 'node:test'; import { ContextControls } from './contextControls.js'; +import { recordContextSessionUserChange } from '../contextModePolicy.js'; class Button extends EventTarget { constructor() { @@ -189,3 +190,38 @@ test('a layer Context isolation switches off is suspended until the mode is left await controls._dataManager.setEnabled('cctv', false); assert.deepEqual(seen.at(-1), ['cctv', false, false]); }); + +test('a layer the user switches inside the mode is no longer suspended', async (t) => { + const { controls } = fixture(t); + const enabled = new Set(['cctv']); + controls._dataManager = { + layers: new Map([['cctv', {}]]), + getEnabledLayerIds: () => [...enabled], + getLayerParams: () => null, + isEffectivelyEnabled: (layerId) => enabled.has(layerId), + isEnabled: (layerId) => enabled.has(layerId), + async setEnabled(layerId, next) { + if (next) enabled.add(layerId); + else enabled.delete(layerId); + return true; + }, + }; + controls._captureContextSessionSnapshot(); + await controls._clearLayersOutsideContextMode('flights'); + assert.equal(controls.suspendsLayer('cctv'), true); + // Inside Contacts the user turns CCTV back on, then off again: the panel + // must close like any other switch-off. + for (const next of [true, false]) { + recordContextSessionUserChange({ + snapshot: controls._contextSessionSnapshot, + change: { + type: 'visibility', + origin: 'user', + layerId: 'cctv', + enabled: next, + }, + effectiveContextMode: 'flights', + }); + } + assert.equal(controls.suspendsLayer('cctv'), false); +}); diff --git a/src/ui/contextSession.js b/src/ui/contextSession.js index 936fc2cb1..3f1f2eeb1 100644 --- a/src/ui/contextSession.js +++ b/src/ui/contextSession.js @@ -36,6 +36,8 @@ export function _captureContextSessionSnapshot({ excludeLayerIds = [] } = {}) { ), userAdded: new Set(), userRemoved: new Set(), + // Every layer the user switched during the session, either way. + userSwitched: new Set(), params, }; } @@ -149,6 +151,7 @@ export async function _restoreContextSession({ enabledLayerIds: new Set(restoreState.enabledLayerIds), userAdded: new Set(), userRemoved: new Set(), + userSwitched: new Set(), params: snapshot.params, }; }