Preview modal audio, five rounds:
- True track-change crossfade: scheduleSettle no longer silences the
outgoing clip at t=0; preview_load's equal-power fade owns both ramps,
gated on the incoming voice's first Playing event (audible_marks), with
failure/overstay fallbacks. CROSSFADE_MS = 340 — locked to the reel's
card-slide so audio and visuals land together.
- Two-neighbour lease prefetch + a 15-min on-disk clip byte-cache
(recent_clips): steps in BOTH directions crossfade in ~15 ms; revisits
never re-download.
- Adaptive settle (isolated steps fire immediately, bursts coalesce) and
anticipatory auto-advance (step at clip end minus 400 ms) so auto-play
crossfades over the tail instead of gapping.
- Session resume memory: revisited tracks continue where they left off
(decoder-level start offset via play_local_from/resume_at); finished
tracks restart from the top.
- Open/close crossfades with the MAIN player: fade-pause with the
desired-paused latch landing at ramp end, fade-resume from the paused
position; MAIN_FADE_GEN guards; focus handshake relaxation documented
in focusBridge.
Volume normalization (Settings > Playback, default on):
- Dependency-free BS.1770/EBU R128 meter in the player crate (K-weighting,
400 ms gated blocks, incremental) + analyzer that reads the same growing
file playback streams from — first plays are measured at download speed.
- Gain layered under user volume; audible start GATED on a preliminary
measurement (10 s of audio or 800 ms budget) so the level is right from
the first sample and never changes mid-track; prelim boosts capped 3 dB,
peak-aware clipping guard, persistent loudness-cache.json.
- Gapless boundaries get their own sample-accurate gain via
SetNextNormGainDb, fed by preload analysis.
Debug overlay (Ctrl+Shift+D): process-tree CPU / PDH GPU / working-set +
private-commit RAM / network rates / FPS / current-track download progress
read from the backend's own StreamProgress / live normalization state /
preview session phase. Polls only while visible.
131 JS + player + app tests green; every path verified live over CDP.
src/lib/preview/ — framework-free ESM modules, per design §12–14:
- contracts.js: versioned manifest/item/source-ref factories; signed
URLs structurally excluded from manifests; Apple identity first-class.
- lease.js: discriminated MediaLease union; computeSafeForMs derives
lifetimes provider-clock-first with fallback/contradiction handling
(margins are ADRs backed by Phase 0 soak evidence).
- redact.js: URL redaction (host+hash+exp only) and the HTTPS + exact
host allowlist gate applied to initial URLs and every redirect.
- manifest.js: artist (rank order, exact-id dedupe), album (disc/track
sort, context inheritance) and fixture builders — pure, no fetching.
- session.js: reducer with session/generation guards on every async
action; keyed active item with derived playable indices/cursor;
auto-advance under fresh generations; close is terminal.
- resolver.js: PreviewResolver contract + scripted fixture resolver
with classified failures (stale_lease/not_found/network/…).
- transport.js: transport seam + deterministic fake (manual ticks).
- focus.js: revision gateway (all main-player entry points; focus-
internal tag) and the coordinator — acquisition only via a
generation-bound silence ack, restore only with revisions unchanged.
tests/preview/*.test.mjs run via `npm test` (node --test): 61 passing,
covering the full Phase 1 list in IMPLEMENTATION-PLAN §6 including the
exit gate: stale lease/play events are inert, no restoration after any
simulated main command, and no component renders anywhere.