fix(street-level): review round — safer docking, mount retry, provider fallthrough

- Only a user's own collapse docks a floating window; cockpit entry,
  restores and the Cyber accordion no longer dock it or delete its saved
  place, and the accordion leaves floating windows alone.
- The viewer host records an adapter as active only after its mount
  succeeds, so a library that failed to load once is retried; concurrent
  opens share one mount, and unmounting mid-mount leaves nothing active.
- openNearest tries every active provider; one provider's error (no key,
  offline) no longer hides the others' imagery.
- The SINCE readout shows its cut-off date as soon as the committed value
  matches the thumb, even while the slider keeps focus.
- A floating window that was only dragged is bounded to the viewport and
  scrolls instead of running off-screen.
- getStats summarises coverage (summarizeCoverage) instead of building a
  full UI snapshot every second; disable() uses credits.hideAll(); smaller
  cleanups in the viewer host and sequence snapshot.
- Unit tests: panel docking and header double press, viewer-host mount
  retry/sharing/cancel, coverage summary.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Said TurkseverandClaude Opus 5.5 committed 2026-09-25 20:56:12 +01:00
1 parent 5877f24f0b
commit 6207e4144e
12 files changed
+425 -55

No files matched your search

+19 -8
View File
@@ -16,6 +16,23 @@ import { COLORS } from './policy.js';
* @property {Array<{key: string, label: string, color: string}>} legend
*/
/**
* What the switched-on providers add up to: summed counts, any loading, the
* first hint and error, and whether every one of them lacks its key.
* @param {Array<ProviderSnapshot>} providers
*/
export function summarizeCoverage(providers) {
const active = providers.filter((p) => p.on);
return {
count: active.reduce((sum, p) => sum + (p.count || 0), 0),
loading: active.some((p) => p.loading),
hint: active.find((p) => p.hint)?.hint || '',
error: active.find((p) => p.error)?.error || null,
keyRequired:
active.length > 0 && active.every((p) => p.keyRequired === true),
};
}
/**
* Compose the snapshot the panel renders from the core state and one
* snapshot per registered provider. Pure, so the merge rules are testable:
@@ -33,8 +50,7 @@ export function composeUIState({
sequence,
}) {
const active = providers.filter((p) => p.on);
const keyRequired =
active.length > 0 && active.every((p) => p.keyRequired === true);
const { keyRequired, ...coverage } = summarizeCoverage(providers);
const legend = [];
for (const provider of active)
for (const entry of provider.legend)
@@ -53,12 +69,7 @@ export function composeUIState({
keyRequired,
filter: { ...filter },
providers: providers.map((p) => ({ ...p, legend: [...p.legend] })),
coverage: {
loading: active.some((p) => p.loading),
count: active.reduce((sum, p) => sum + (p.count || 0), 0),
hint: active.find((p) => p.hint)?.hint || '',
error: active.find((p) => p.error)?.error || null,
},
coverage,
legend,
sequence: { ...sequence },
street: { ...street },