From a04502559e07c31dcec7245e89bddcb556c97be8 Mon Sep 17 00:00:00 2001 From: Bilawal Sidhu <106619546+bilawalsidhu@users.noreply.github.com> Date: Mon, 14 Sep 2026 19:42:24 -0500 Subject: [PATCH] docs(cctv): correct the panel reset instructions to the current storage key (#509) Supersedes #408 by @vegettto, ported onto the current component layout with fixes from review. Verified before merge: format, package boundaries, unit suite, build, and the browser tracking suite; a browser harness for the change where one applies; adversarial review applied. Co-authored-by: vegettto --- CHANGELOG.md | 9 ++ docs/CURRENT-STATE.md | 2 +- docs/KNOWN-ISSUES.md | 27 +++-- scripts/dev-fresh.sh | 4 +- scripts/format-scope.json | 1 + src/tooling/panelStorageDocs.test.mjs | 148 ++++++++++++++++++++++++++ 6 files changed, 182 insertions(+), 9 deletions(-) create mode 100644 src/tooling/panelStorageDocs.test.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d29756ee..83fd11a75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -273,6 +273,15 @@ of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md and environment the code builds; the Windows onboarding CI job now also runs this file, where its one Windows-only case exercises the real hardener against real native tools. Contributed by michaelhan1208 (#161). +- The panel-recovery instructions in `docs/KNOWN-ISSUES.md`, `docs/CURRENT-STATE.md` + and `scripts/dev-fresh.sh` describe what the interface does. The rails lay panels + out themselves and write no stored position, so a CCTV panel that looks missing + is collapsed or its layer is off; the collapsed-state key is what opens it, and + the value to store is `'0'`, since removing the key returns the panel to its + default, which is collapsed. A view opened from a share link is laid out from + the link and ignores the stored value, so the console workaround is for ordinary + loads only. A check keeps the documented keys and outcomes in step with the + code. Contributed by vegettto (#408). - Extract panel disclosure and hover/focus controls into a reusable module; cancel their listeners and pending work during replacement and teardown. diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index ba03ad08a..fb0339a95 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -3305,7 +3305,7 @@ easier to meet (detection is now on more often), but does not create it. samples and constant elevation during E/N drag; one shared-floor resolution on release; late one-shot shared-cell work is permitted during viewshed idle. The A+B harness intentionally excludes citywide LOD assertions. -- Draggable panel positions persist at `godsEyeView.v7.panelPos.` (collapsed states at `godsEyeView.v6.panelCollapsed.`). +- Panel positions have a versioned storage name, `godsEyeView.v8.panelPos.`, but the current rails lay panels out adaptively and write none. Collapsed state does persist for every panel at `godsEyeView.v6.panelCollapsed.` (`'0'` open, `'1'` closed, absent means the panel's own default). - Legacy draggable-panel position keys may remain in local storage for backward compatibility, but the map-mode right rail ignores them; collapsed states still persist at `godsEyeView.v6.panelCollapsed.`. - Flight/military tracked entities cache dead-reckoned positions per frame to avoid callback desync flicker. - Aircraft 3D-model and tracking invariants are covered by `npm run test:track`; run this before touching `flights.js`, `militaryFlights.js`, `detection.js`, or `trackedReadout.js`. diff --git a/docs/KNOWN-ISSUES.md b/docs/KNOWN-ISSUES.md index 33c665f39..92c92c1f9 100644 --- a/docs/KNOWN-ISSUES.md +++ b/docs/KNOWN-ISSUES.md @@ -30,21 +30,36 @@ Next iteration candidates: --- -### CCTV panel can appear "missing" after layout refactors +### CCTV panel can appear "missing" Status: Open (workaround available) Context: -- Panel positions are persisted in local storage and can restore off-screen after UI changes. +- The rails lay their panels out themselves. No panel is dragged into place at + startup and no stored position is read, so a panel that looks missing is + collapsed or its layer is off rather than parked off-screen. The CCTV panel + starts collapsed and stays that way until you open it or a camera activates. Workaround: -- In browser console: - - `localStorage.removeItem('godsEyeView.v6.panelPos.cctv-panel');` +- Check that the CCTV layer is enabled in the Layers panel, then open the panel + from its header control; it also opens on its own when a camera activates. +- To force it open on an ordinary load, store the expanded state and reload. In + the browser console: + - `localStorage.setItem('godsEyeView.v6.panelCollapsed.cctv-panel', '0');` + - `location.reload();` +- Removing that key instead returns the panel to its default, which is collapsed: - `localStorage.removeItem('godsEyeView.v6.panelCollapsed.cctv-panel');` - `location.reload();` +- Neither console line changes anything when the page was opened from a share + link: a shared view is laid out from the link, not from what this browser has + stored, so open the panel from its header control instead. Related keys (current versions): -- Panel positions: `godsEyeView.v7.panelPos.` (re-versioned 2026-06-10) -- Panel collapsed state: `godsEyeView.v6.panelCollapsed.` +- Panel collapsed state: `godsEyeView.v6.panelCollapsed.` — `'0'` open, + `'1'` closed, absent means the panel's own default. A view opened from a share + link ignores the stored value entirely. +- Panel positions: `godsEyeView.v8.panelPos.` — the versioned name for a + stored position. The current layout writes none, so deleting one changes + nothing. - CCTV calibration: `godsEyeView.cctv.calibration.v2` --- diff --git a/scripts/dev-fresh.sh b/scripts/dev-fresh.sh index 6ec989ed6..2f9454401 100755 --- a/scripts/dev-fresh.sh +++ b/scripts/dev-fresh.sh @@ -306,8 +306,8 @@ case "${HOST}" in esac echo "Google Maps key source: ${GOOGLE_MAPS_API_KEY_SOURCE}" echo "Tip: after server starts, hard refresh browser (Cmd+Shift+R)." -echo "If panels are still missing, run this once in browser console:" -echo "localStorage.removeItem('godsEyeView.v6.panelPos.cctv-panel'); location.reload();" +echo "The CCTV panel starts collapsed; open it from its header, or in browser console:" +echo "localStorage.setItem('godsEyeView.v6.panelCollapsed.cctv-panel', '0'); location.reload();" echo "OpenSky auth mode: ${OPENSKY_AUTH_MODE}" if [[ -n "${OPENSKY_CREDENTIALS_FILE}" ]]; then if [[ -f "${OPENSKY_CREDENTIALS_FILE}" ]]; then diff --git a/scripts/format-scope.json b/scripts/format-scope.json index 10a18fd63..5813b3a58 100644 --- a/scripts/format-scope.json +++ b/scripts/format-scope.json @@ -10,6 +10,7 @@ "src/tooling/fixtureRoot.mjs", "src/tooling/fixtureRoot.test.mjs", "src/tooling/format.test.mjs", + "src/tooling/panelStorageDocs.test.mjs", "src/tooling/packageBoundaries.test.mjs", "src/data/infrastructure.js", "src/data/infrastructure.test.mjs", diff --git a/src/tooling/panelStorageDocs.test.mjs b/src/tooling/panelStorageDocs.test.mjs new file mode 100644 index 000000000..5af18b96c --- /dev/null +++ b/src/tooling/panelStorageDocs.test.mjs @@ -0,0 +1,148 @@ +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +const root = fileURLToPath(new URL('../..', import.meta.url)); + +// Documentation consistency only: that every storage key a document spells out +// is the key the code writes. Whether deleting one has the effect the document +// claims is not checked here. +const DOCUMENTED_KEYS = [ + ['docs/KNOWN-ISSUES.md', 'godsEyeView.{layout}.panelCollapsed.cctv-panel'], + ['docs/KNOWN-ISSUES.md', 'godsEyeView.{layout}.panelCollapsed.'], + ['docs/KNOWN-ISSUES.md', 'godsEyeView.{position}.panelPos.'], + ['scripts/dev-fresh.sh', 'godsEyeView.{layout}.panelCollapsed.cctv-panel'], + ['docs/CURRENT-STATE.md', 'godsEyeView.{position}.panelPos.'], + ['docs/CURRENT-STATE.md', 'godsEyeView.{position}.panelPos.'], + ['docs/CURRENT-STATE.md', 'godsEyeView.{layout}.panelCollapsed.'], +]; + +test('documented panel storage keys are the keys the code writes', async () => { + const source = await readFile( + path.join(root, 'src/ui/panelPositionControls.js'), + 'utf8', + ); + const version = (name) => { + const [, found] = + source.match(new RegExp(`const ${name} = '([^']+)';`)) || []; + assert.ok(found, `panelPositionControls.js must declare ${name}`); + return found; + }; + const position = version('PANEL_POSITION_STORAGE_VERSION'); + const layout = version('PANEL_LAYOUT_STORAGE_VERSION'); + + const seen = new Map(); + for (const [file, shape] of DOCUMENTED_KEYS) { + if (!seen.has(file)) + seen.set(file, await readFile(path.join(root, file), 'utf8')); + const expected = shape + .replace('{position}', position) + .replace('{layout}', layout); + assert.ok( + seen.get(file).includes(expected), + `${file} must use ${expected}`, + ); + } + + // A document that still names a superseded version sends the reader to a key + // nothing writes. + for (const [file, content] of seen) { + const stalePositions = [ + ...content.matchAll(/godsEyeView\.(v\d+)\.panelPos/g), + ] + .map(([, found]) => found) + .filter((found) => found !== position); + assert.deepEqual( + stalePositions, + [], + `${file} names superseded position keys`, + ); + const staleCollapsed = [ + ...content.matchAll(/godsEyeView\.(v\d+)\.panelCollapsed/g), + ] + .map(([, found]) => found) + .filter((found) => found !== layout); + assert.deepEqual( + staleCollapsed, + [], + `${file} names superseded collapsed-state keys`, + ); + } +}); + +test('the documented outcomes hold: default, stored open, stored shut, and a shared view', async () => { + // The instructions promise two different outcomes; both are exercised here + // against the code that decides them. + const { PanelPositionControls } = + await import('../ui/panelPositionControls.js'); + const html = await readFile( + path.join(root, 'src/ui/templates/layer-panels.html'), + 'utf8', + ); + assert.match( + html, + /