PlaylistCard: per-artist system variants (songs_like_artist) all share
one variant tag but exist as one playlist per seed artist; routing
their play handler through systemShuffle(variant) hit the wrong
playlist (or 404). Detect via seed_artist_id != null and fall through
to getPlaylist(playlist.id) for those; still tag the queue with the
variant so source attribution stays correct.
smoothPosition.svelte.ts: new useSmoothPosition() hook mirrors
Android's rememberSmoothPositionMs. player.position only updates
~4 Hz (HTML audio timeupdate), so the seek thumb stepped visibly;
$effect resets on each canonical tick / seek / track-change and a
rAF loop extrapolates at playback rate between ticks.
Wired into both PlayerBar.svelte (mini + expanded seek rows) and
now-playing/+page.svelte. Seek input handler still reads the raw
range value (not smoothed.value) so user drags stay authoritative.
#9 — new lib/media/dominantColor.ts: load cover image, downsample to
1x1 canvas, read pixel. Approximates the dominant tone via the
browser's bilinear mean — close enough for an ambient accent without
the 5KB ColorThief dependency. Same-origin cover URLs so no CORS
dance. Result cached by URL so revisits are free.
PlayerBar samples the current track's cover and pipes the resulting
rgb into a 2px accent strip above both the compact and desktop
variants. Transparent until the first resolve; 300ms transition on
colour change so track-skips fade rather than snap.
#10 — slight bump to Home tile widths so a typical viewport shows
roughly 5-6 across instead of cramming 8-9: Playlists w-56, all
remaining AlbumCard rows w-48 (Recently Added + both Rediscover
album scrollers). Replaces the wave-2 sizes that were still showing
7-8 across on wider screens.
Mirrors Android's NowPlayingScreen. New /now-playing route renders
outside the Shell (no top bar, no PlayerBar) so it's a focused full
viewport: large square cover, title + linked artist/album, full-width
scrubber with timestamps, prominent transport (prev / play-pause /
next, with a 56dp circular play button), shuffle + repeat toggles,
like button, volume slider, and a queue button that opens the
existing queue drawer.
Tapping the cover or title area in PlayerBar (compact + desktop)
now navigates to /now-playing instead of the album page. Pattern
matches Spotify / YouTube Music. The album link stays reachable via
the album-name link inside NowPlaying itself, so no nav is lost.
Back button in the NowPlaying header uses history.back() with a
fallback to /. Empty state when no track is loaded points the user
at Home or the Library.
Scribe 528, local task #62.
The previous commit message said I added data-testid="player-bar-compact"
+ "player-bar-desktop" to PlayerBar.svelte but the edits actually
dropped silently — only the test file changes landed. Without the
testids, the test file's within(getByTestId(...)) calls all fail.
This adds the actual attributes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the kebab-with-popover PlayerOverflowMenu approach with all
player controls visible inline. Operator's stated intent was "two-row
layout with all controls visible," not "kebab hides shuffle/repeat/
queue/volume" — restructured to match.
### Compact view (below md)
┌─────────┬───────────┬───────────────────┐
│ art │ ♥ ⋮ │ 🔀🔁 ☰ │ ← short sub-row
│ title │ │ │
│ artist │ ⏮ ⏯ ⏭ │ volume slider │ ← play row at full size
├─────────┴───────────┴───────────────────┤
│ 0:42 ━━━━━●━━━━━━━━━━━━━━━━━━━━━━ 3:21 │
└─────────────────────────────────────────┘
- Column 1: cover + title/artist
- Column 2: like+kebab on top sub-row, play controls (40/48/40) below
- Column 3: shuffle/repeat/queue on top, volume slider below
- Bottom: full-width seek slider with time labels
### Desktop view (md+)
Existing 3-column layout preserved; only change is the redundant
PlayerOverflowMenu kebab (which had been mounting at all widths)
is no longer there. Volume + shuffle + repeat + queue stay inline
in the right cluster as before.
### Cleanup
- Deleted PlayerOverflowMenu.svelte + its test (no callers remain)
- The TrackMenu kebab (per-track actions) stays — it's a varying
list of actions that doesn't fit inline
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Those actions don't have meaningful semantics for the track you're
already listening to. New TrackMenu prop hideQueueActions gates the
pair; PlayerBar passes it true. Other mount sites (track rows in
lists) keep the full 8-entry menu.
Three coordinated polish changes:
1. PlayerBar cover bumps from h-20 (80px) to h-24 (96px) and the bar's
vertical padding tightens from py-4 to py-1.5. Bar height stays
~108px but the cover now fills ~89% of it (was ~74%) — reads as the
substantial primary content the operator wanted, not a thumbnail.
2. InfiniteScrollSentinel default rootMargin moves from 300px to 800px
so the next page fetches well before the user reaches the bottom of
the rendered set. Empirically that's ~3-4 rows of cards on a typical
library grid — loading feels seamless rather than catching up.
3. HorizontalScrollRow takes rows: T[][] instead of items: T[]. Multiple
rows of items now render inside one shared overflow-x-auto container,
so the rows scroll together as a single coupled section. Recently
added (2 album rows) and Most played (3 track rows) on the home page
now scroll as one unit. Rediscover keeps two separate scrollers
because its rows are different card types (square albums vs circular
artists) — coupling those would interleave shapes awkwardly. The
item snippet's second arg is now the global flat index so consumers
like CompactTrackCard (which needs sectionTracks + index for play
actions) work without per-row re-indexing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three theming/density problems with the old PlayerBar:
- Emoji glyphs (⏮ ▶ ⏸ ⏭ 🔀🔁 ◌) painted at OS-default sizes inside p-1
hit areas, so each button had a 6-8px ring of dead space and the row
clashed with the Lucide stroke-icon language used elsewhere.
- No focal point on transport — play looked structurally identical to
prev/next so the eye had nothing to anchor on.
- Cover at 48x48 in a 72px bar left visible vertical breathing room
around it that read as accidental empty space.
Changes:
- All emoji replaced with Lucide icons: SkipBack, Play, Pause, Loader2,
SkipForward, Shuffle, Repeat, Repeat1, Volume2/Volume1/VolumeX.
- Play button is the brand moment: 48px circular, accent forest-teal
background, 24px parchment icon. Prev/next stay minimal at 40px
circular with hover-bg.
- Cover bumped to 80x80 in a min-h-[108px] bar (~1.5x the previous
height) so the player reads as substantial rather than a thin strip.
- Shuffle / repeat get consistent 36px circular hit areas with
bg-accent-tint + text-accent active state instead of color-only
toggling. Repeat1 icon swap on repeat-one mode replaces the old
'🔁¹' string concatenation.
- Volume slider gets a state-aware Lucide icon to its left
(VolumeX / Volume1 / Volume2 by level), giving the right column a
visual anchor.
- Right column tightens from w-56 to w-48 since icons are denser than
emoji + text.
TrackMenu gains a 'direction' prop ('up' | 'down', default 'down') so
PlayerBar can pass direction='up' — the kebab menu opens upward instead
of off the bottom of the page.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Heart appears on every entity rendering surface. ArtistRow restructured
to a <div> with the link as a positioned overlay so the heart button
can nest without invalid <button>-in-<a> markup. Shell nav grows a
'Liked' entry pointing at /library/liked.