Phase 2C WIP: PreviewOverlay modal + root integration (reel centering bug)

Working:
- PreviewOverlay.svelte mounted at app root; opens over a fixture manifest
  (window.__previewFixture(n) in dev), inert background, player bar hidden,
  focus trap, keyboard nav, F3 rail, T3 swaps, R1b capsule, accent extraction.
- previewSession.svelte.js: store + service wiring (production + fixture).
- Root integration in App.svelte; revision-gateway bumps + notePlaybackGeneration
  threaded through state.svelte.js player entry points; api.win guarded so plain
  vite browser mounts.
- Launcher on the artist page builds a real artist manifest and opens the modal
  with a play-full starter.
- 70 JS tests still green.

Two bugs found and fixed during bring-up:
- effect_update_depth_exceeded: bleed-crossfade and bump effects read AND wrote
  the same $state; wrapped writes in untrack(). This loop was breaking Svelte's
  scheduler globally (even ui.* stopped reacting).
- accentFromImage now sets crossOrigin for remote covers.

KNOWN BROKEN (handoff): reel vertical centering. The active card stays centred
at index 0 but drifts by one stride per step. Root cause isolated — see
TODO-Feature-Docs/scrollable-track-previews/HANDOFF-PHASE2C.md.
This commit is contained in:
KeelrLeek committed 2026-07-22 19:26:41 -05:00
1 parent 8cb3cd593b
commit d051f0db28
7 files changed
+1492 -16

No files matched your search

+27 -10
View File
@@ -146,18 +146,35 @@ export const previewDispose = () => invoke("preview_dispose");
export const playerPauseAcked = () => invoke("player_pause_acked");
// Window chrome (the app draws its own titlebar; decorations are off).
// Guarded like onEvent below: getCurrentWindow() THROWS outside Tauri, and
// it runs at mount via initWindowActivity — un-guarded it made every plain
// `vite` browser load a blank page, which also blocked browser-side preview
// fixtures. In-Tauri behavior is unchanged.
import { getCurrentWindow } from "@tauri-apps/api/window";
export const win = {
minimize: () => getCurrentWindow().minimize(),
toggleMaximize: () => getCurrentWindow().toggleMaximize(),
close: () => getCurrentWindow().close(),
isMinimized: () => getCurrentWindow().isMinimized(),
isFocused: () => getCurrentWindow().isFocused(),
isMaximized: () => getCurrentWindow().isMaximized(),
// returns Promise<unlisten>
onResized: (handler) => getCurrentWindow().onResized(handler),
onFocusChanged: (handler) => getCurrentWindow().onFocusChanged(handler),
const inTauri = typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
const noWin = {
minimize: () => {},
toggleMaximize: () => {},
close: () => {},
isMinimized: async () => false,
isFocused: async () => true,
isMaximized: async () => false,
onResized: async () => () => {},
onFocusChanged: async () => () => {},
};
export const win = !inTauri
? noWin
: {
minimize: () => getCurrentWindow().minimize(),
toggleMaximize: () => getCurrentWindow().toggleMaximize(),
close: () => getCurrentWindow().close(),
isMinimized: () => getCurrentWindow().isMinimized(),
isFocused: () => getCurrentWindow().isFocused(),
isMaximized: () => getCurrentWindow().isMaximized(),
// returns Promise<unlisten>
onResized: (handler) => getCurrentWindow().onResized(handler),
onFocusChanged: (handler) => getCurrentWindow().onFocusChanged(handler),
};
// Subscribe to a backend event; returns an unlisten function.
// No-ops outside Tauri (e.g. plain-browser preview).