mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
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 <[email protected]>
This commit is contained in:
1 parent
94f5e4dd45
commit
a04502559e
6 files changed
+182
-9
No files matched your search
@@ -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.
|
||||
|
||||
@@ -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.<panel-id>` (collapsed states at `godsEyeView.v6.panelCollapsed.<panel-id>`).
|
||||
- Panel positions have a versioned storage name, `godsEyeView.v8.panelPos.<panel-id>`, but the current rails lay panels out adaptively and write none. Collapsed state does persist for every panel at `godsEyeView.v6.panelCollapsed.<panel-id>` (`'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.<panel-id>`.
|
||||
- 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`.
|
||||
|
||||
+21
-6
@@ -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.<panel-id>` (re-versioned 2026-06-10)
|
||||
- Panel collapsed state: `godsEyeView.v6.panelCollapsed.<panel-id>`
|
||||
- Panel collapsed state: `godsEyeView.v6.panelCollapsed.<panel-id>` — `'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.<panel-id>` — the versioned name for a
|
||||
stored position. The current layout writes none, so deleting one changes
|
||||
nothing.
|
||||
- CCTV calibration: `godsEyeView.cctv.calibration.v2`
|
||||
|
||||
---
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.<panel-id>'],
|
||||
['docs/KNOWN-ISSUES.md', 'godsEyeView.{position}.panelPos.<panel-id>'],
|
||||
['scripts/dev-fresh.sh', 'godsEyeView.{layout}.panelCollapsed.cctv-panel'],
|
||||
['docs/CURRENT-STATE.md', 'godsEyeView.{position}.panelPos.<panel-id>'],
|
||||
['docs/CURRENT-STATE.md', 'godsEyeView.{position}.panelPos.<id>'],
|
||||
['docs/CURRENT-STATE.md', 'godsEyeView.{layout}.panelCollapsed.<panel-id>'],
|
||||
];
|
||||
|
||||
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,
|
||||
/<div id="cctv-panel" class="panel-collapsible collapsed"/,
|
||||
'the CCTV panel starts collapsed in the markup',
|
||||
);
|
||||
|
||||
const saved = {
|
||||
document: globalThis.document,
|
||||
localStorage: globalThis.localStorage,
|
||||
};
|
||||
const classes = new Set(['panel-collapsible', 'collapsed']);
|
||||
const panel = {
|
||||
classList: {
|
||||
contains: (name) => classes.has(name),
|
||||
toggle: (name, active) =>
|
||||
active ? classes.add(name) : classes.delete(name),
|
||||
add: (name) => classes.add(name),
|
||||
remove: (name) => classes.delete(name),
|
||||
},
|
||||
};
|
||||
const store = new Map();
|
||||
globalThis.document = { getElementById: () => panel };
|
||||
globalThis.localStorage = {
|
||||
getItem: (key) => (store.has(key) ? store.get(key) : null),
|
||||
setItem: (key, value) => store.set(key, String(value)),
|
||||
removeItem: (key) => store.delete(key),
|
||||
};
|
||||
try {
|
||||
const controls = new PanelPositionControls({
|
||||
syncPanelCollapseButton: () => {},
|
||||
layoutRightPanels: () => {},
|
||||
syncCctvPanelViewport: () => {},
|
||||
showToast: () => {},
|
||||
});
|
||||
|
||||
// Nothing stored: the markup default wins, and the panel is collapsed. So
|
||||
// deleting the key is a reset, not a way to open the panel.
|
||||
controls._restorePanelCollapsedState('cctv-panel');
|
||||
assert.equal(classes.has('collapsed'), true);
|
||||
|
||||
// The value the documents tell a reader to set.
|
||||
store.set('godsEyeView.v6.panelCollapsed.cctv-panel', '0');
|
||||
controls._restorePanelCollapsedState('cctv-panel');
|
||||
assert.equal(classes.has('collapsed'), false);
|
||||
|
||||
// ...and the opposite value keeps it shut, so '0' is doing the work.
|
||||
store.set('godsEyeView.v6.panelCollapsed.cctv-panel', '1');
|
||||
controls._restorePanelCollapsedState('cctv-panel');
|
||||
assert.equal(classes.has('collapsed'), true);
|
||||
|
||||
// A view opened from a share link is laid out from the link: the shell
|
||||
// restores with allowStored false, and the stored value is not consulted.
|
||||
// This is why the console workaround is documented for ordinary loads only.
|
||||
store.set('godsEyeView.v6.panelCollapsed.cctv-panel', '0');
|
||||
controls._restorePanelCollapsedState('cctv-panel', { allowStored: false });
|
||||
assert.equal(
|
||||
classes.has('collapsed'),
|
||||
true,
|
||||
'a shared view must ignore the stored open state',
|
||||
);
|
||||
} finally {
|
||||
globalThis.document = saved.document;
|
||||
globalThis.localStorage = saved.localStorage;
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user