* feat(fullscreenPlayer): add Prism style — full-bleed backdrop, right lyrics panel, glass control bar
Third fullscreen player style (Settings → Appearance). Reuses the existing
pipeline: full-bleed artist backdrop (fanart/artistBackdrop), FsLyricsApple in a
floating right-side glass panel, player-store transport, cover-derived accent.
New: the bottom bar layout (transport · time elapsed/−remaining · now-playing
pill with integrated scrubbable progress · volume/queue/lyrics-toggle/minimize)
and fullscreen-player-prism.css. Wired into the style toggle + AppShell routing
+ picker + 13 locales.
* fix(fullscreenPlayer): Prism controls stay visible; transport/utils float bare (no 3-box split)
* fix(fullscreenPlayer): Prism bottom bar is one continuous glass bar (not three separate elements)
* fix(fullscreenPlayer): Prism control row is a centred middle band; only the now-playing element is boxed
* fix(fullscreenPlayer): Prism bar has both an outer glass strip and a darker nested now-playing box
* fix(fullscreenPlayer): Prism outer bar is half-width and centred
* feat(fullscreenPlayer): Prism lyrics — progressive blur on upcoming lines + accent-tinted active line
* style(fullscreenPlayer): centre the title/album/artist text in the Prism now-playing pill
* refactor(fullscreenPlayer): share seek/volume/backdrop/time across FS players
The Prism player had copy-pasted the seekbar, volume, time readout and
backdrop resolution from the Static/Immersive players. Two of those copies
carried bugs: the progress input dropped FsSeekbar's touch/pointer handlers
(no scrubbing on touchscreens) and the volume toggle only recorded the
pre-mute level on the mute click (unmuting after dragging the slider to 0
restored a stale value).
Extract the shared logic into single sources of truth and rewire all three
players onto them:
- useFsArtistBackdrop — the artist-backdrop URL resolution (was duplicated
verbatim in Static, Immersive and Prism).
- useImperativeSeek — the drag/preview/commit + progress-subscription loop
with mouse, touch, pointer and keyboard handlers; FsSeekbar and Prism's
progress line now share it, so touch scrubbing works everywhere.
- useVolumeToggle — mute toggle that continuously tracks the last non-zero
volume, restoring it correctly regardless of how the level reached 0.
- FsTimeReadout gains `remaining`/`className` props and replaces Prism's
bespoke time component.
Drops a dead aria-valuetext no-op on the progress input. Adds regression
tests for the volume-toggle restore path and a Prism smoke test.
* docs(changelog): add Prism fullscreen player style (#1251)
Relocate the playback/queue/transport/audio-output engine out of the type-first
store/ + utils/playback/ + utils/audio/ dirs into a cohesive src/features/playback/,
structure-preserving:
store/<x> -> features/playback/store/<x>
store/audioListenerSetup/<x> -> features/playback/store/audioListenerSetup/<x>
utils/playback/<x> -> features/playback/utils/playback/<x>
utils/audio/<x> -> features/playback/utils/audio/<x>
184 files moved (107 source + 77 tests), 365 consumers rewritten. Pure move — no
behavior change, no state-split (the playerStore state-split stays a separate M5
question). Enabled by this session's decouple seams (artist/offline/orbit/auth →
core registries), so the engine carries no inbound core->feature inversion: store/
now holds only the 50 cross-cutting global stores (auth family, the seams, library
index, UI/settings stores).
KEPT OUT of the move (would re-create global->engine edges): the 3 pure config
helpers utils/audio/{loudnessPreAnalysisSlider,hiResCrossfadeResample} +
utils/playback/autodjOverlapCap (authStore + settings UI read them — they stay in
utils/). Ambiguous view-state stores (eqStore, queueToolbarStore,
playerBarLayoutStore) stay global (no engine imports).
Consumers use DEEP paths (@/features/playback/...), no barrel — matches the lib/
approach and avoids barrel-mock-collapse across the 140 usePlayerStore consumers.
Two tolerated type-only core->feature edges remain (localPlaybackStore->QueueItemRef,
localPlaybackMigration->HotCacheEntry, both erased).
tsc 0, lint 0, full suite 319/2353 green, iron-rule clean (no runtime store->feature
import). Behavior-touching only via the prerequisite bridge seam (already QA-flagged);
the move itself is pure.
Extract the queue UI (QueuePanel + queuePanel/* components + useQueue* hooks)
into features/queue/ with a barrel. This is the chosen playback/queue boundary:
queue = the QueuePanel UI that CONSUMES the playback store; queue STATE and the
audio engine stay in store/ (playback-core, moved separately). Verified
one-way: no playback-core file imports the queue UI back (only doc comments
mention it).
Excluded as NOT queue-UI: useIdlePlayQueuePull (AppShell) and
usePlayQueueSyncLedState (ConnectionIndicator) are queue-SYNC orchestration,
not panel UI — left in hooks/ to move with playback.
Pure move. One test fix: QueuePanel.test.tsx reads its subject via a hardcoded
readFileSync('src/components/QueuePanel.tsx') path (architecture-pin grep for
forbidden HTML5 DnD) — repointed to the new location (the move tool can't
rewrite non-import string paths). tsc 0, lint 0/0, suite 319/2353 green.