forked from Keelrleek/SoulStream
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:
1 parent
8cb3cd593b
commit
d051f0db28
7 files changed
+1492
-16
No files matched your search
@@ -1,6 +1,11 @@
|
||||
// Shared reactive app state (Svelte 5 runes module).
|
||||
import * as api from "./api.js";
|
||||
import { accentFromImage, hexToRgb, rgbToHex, lerpRgb } from "./color.js";
|
||||
import {
|
||||
initPreviewBridge,
|
||||
bumpMainRevision,
|
||||
notePlaybackGeneration,
|
||||
} from "./preview/previewSession.svelte.js";
|
||||
|
||||
export const app = $state({
|
||||
connection: "offline", // offline | connecting | online
|
||||
@@ -3554,6 +3559,17 @@ export function initEvents() {
|
||||
if (started) return;
|
||||
started = true;
|
||||
initWindowActivity();
|
||||
// Preview audio-focus bridge (scrollable-track-previews). Injected here
|
||||
// rather than imported there: this module owns the player store, and the
|
||||
// preview module must stay import-cycle-free. resumeMain goes straight to
|
||||
// api — NOT through any wrapper that bumps the revision gateway, because
|
||||
// a focus-internal resume must not invalidate its own token.
|
||||
initPreviewBridge({
|
||||
readPlayer: () => player,
|
||||
resumeMain: () => {
|
||||
api.playerControl("resume");
|
||||
},
|
||||
});
|
||||
|
||||
// The `connected` event fires once, at login. A WebView reload (dev
|
||||
// iteration, crash recovery) lands AFTER it, so the fresh frontend
|
||||
@@ -3620,6 +3636,19 @@ export function initEvents() {
|
||||
});
|
||||
|
||||
api.onEvent("soulseek:player", (ev) => {
|
||||
// Revision-gateway bumps at the ONE choke point every native source
|
||||
// change flows through. Each new decode ("buffering") is a new
|
||||
// playback generation for the focus bridge; track-ended is the
|
||||
// auto-advance revision. A pause caused by the focus coordinator
|
||||
// emits "paused", which deliberately bumps nothing.
|
||||
if (ev.kind === "buffering") {
|
||||
notePlaybackGeneration();
|
||||
bumpMainRevision("nativeSourceChange");
|
||||
} else if (ev.kind === "track-ended") {
|
||||
bumpMainRevision("autoAdvance");
|
||||
} else if (ev.kind === "stopped") {
|
||||
bumpMainRevision("userStop");
|
||||
}
|
||||
switch (ev.kind) {
|
||||
case "buffering":
|
||||
player.status = "buffering";
|
||||
@@ -3835,6 +3864,7 @@ export async function streamFile(username, file, subLabel) {
|
||||
|
||||
/** Play a finished download from disk (single track). */
|
||||
export async function playLocalFile(path, title, subLabel, artPath = null) {
|
||||
bumpMainRevision("userPlay");
|
||||
autoAlbum = null;
|
||||
player.queue = [{ path, title, sub: subLabel ?? "", duration: null, art: artPath, quality: localQuality({ path }) }];
|
||||
player.queueIndex = 0;
|
||||
@@ -3867,6 +3897,10 @@ export function playAlbum(tracks, subLabel, start = 0, artPath = null) {
|
||||
function playQueueIndex(i) {
|
||||
const item = player.queue[i];
|
||||
if (!item) return;
|
||||
// Every queue start is a user-command + queue + playback revision for the
|
||||
// preview focus gateway (auto-advance re-enters here too — that bump is
|
||||
// correct: a restore must never resurrect a track the queue moved past).
|
||||
bumpMainRevision("queueJumpOrNextPrev");
|
||||
player.queueIndex = i;
|
||||
player.track = {
|
||||
title: item.title,
|
||||
|
||||
Reference in new issue
Block a user