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

+36 -3
View File
@@ -5,6 +5,8 @@
import NowPlaying from "./components/NowPlaying.svelte";
import ArtworkViewer from "./components/ArtworkViewer.svelte";
import LoginModal from "./components/LoginModal.svelte";
import PreviewOverlay from "./components/PreviewOverlay.svelte";
import { previewUi, openPreviewFixture } from "./lib/preview/previewSession.svelte.js";
import Home from "./views/Home.svelte";
import Search from "./views/Search.svelte";
import Library from "./views/Library.svelte";
@@ -40,16 +42,38 @@
autoConnect();
refreshLibrary();
refreshPlaylists();
// Dev-only hook for the Phase 2 exit-gate check (500-item fixture in a
// plain browser) and for driving the modal without a Tauri backend.
if (import.meta.env.DEV && typeof window !== "undefined") {
window.__previewFixture = (n = 12) => openPreviewFixture(n);
window.__previewUi = previewUi; // debug: the app's own store instance
window.__ui = ui; // debug: known-good reactive store, same instance
window.__togglePv = () => (previewUi.open = !previewUi.open);
window.__toggleNp = () => (ui.nowPlaying = !ui.nowPlaying);
}
</script>
<div class="app" class:np-open={ui.nowPlaying} class:sidebar-collapsed={ui.sidebarCollapsed}>
<div
class="app"
class:np-open={ui.nowPlaying}
class:sidebar-collapsed={ui.sidebarCollapsed}
class:preview-open={previewUi.open}
>
<TitleBar />
<!-- Previews take over the whole window (decision 2026-07-22): while the
modal is open the page and the player bar are inert/not rendered —
there is deliberately NO UI path to command main audio behind the
focus coordinator's back. The titlebar stays live for window chrome. -->
<Sidebar />
<main class="content" use:floatScroll>
<main class="content" use:floatScroll inert={previewUi.open || undefined}>
<CurrentView />
</main>
<NowPlaying />
<PlayerBar />
{#if !previewUi.open}
<PlayerBar />
{/if}
<PreviewOverlay />
<ArtworkViewer />
<LoginModal />
</div>
@@ -57,6 +81,15 @@
<style>
/* Frameless window: the titlebar row is part of the app background —
no separate chrome color, the panels float beneath it. */
/* While the preview modal is open: keyboard is contained by the dialog's
focus trap and aria-modal, the content pane is `inert`, and the flanking
panels stop taking pointer input here. (The player bar is simply not
rendered — see the markup note above.) */
.app.preview-open :global(.sidebar),
.app.preview-open :global(.nowplaying) {
pointer-events: none;
}
.app {
display: grid;
grid-template-columns: var(--sidebar-width) 1fr var(--nowplaying-width);