`index.ts` caches primary views and hides them with a class so
scrollTop survives navigation. Nothing else was told: `disconnectedCallback`
never fires for one, so everything written to clean up there never
cleans up. The worst case was not a leak — pressing `s` on Settings
skipped two albums out of the Autotag queue, and `a` on that same live
handler rewrites tags on disk.
- `utils/view-lifecycle.ts` is the missing half: `viewActivated` /
`viewDeactivated`, with `listenWhileActive`, `intervalWhileActive`
and `whileActive` torn down on the way out, and an off-screen view
that does not render. `registerViewAware` gives a shared reactive
controller the same treatment, because a controller cannot know
whether its host is a cached view — `ContextMenuController` bound
three document listeners in `hostConnected`, which for a cached host
is "forever".
- `services/shortcut-scope.ts` publishes the ambient scope. Resolving
scope from focus alone was not enough: this app is driven with the
mouse, focus sits on `<body>`, and a focus-only rule would have made
the panel keys work only after a click landed inside the panel.
- Global bindings yield to a focused control that owns the key —
button, select, slider, checkbox, menu, grid row, or anything inside
an open dialog — so the unmodified single-key bindings stop stealing
Space and the arrows.
- `utils/roving-grid.ts` gives a card grid one tab stop moved with the
arrows, since a card per tab stop makes a library-length tab
sequence.