No single-click destructive action belongs in the kebab. Removing the item
orphaned its whole path (RemoveTrackPopover was its only caller, and the
admin/tracks API client was the popover's only caller), so per the repo's
no-dead-code convention the chain is fully removed: the menu item + its
admin/isAdmin plumbing in TrackMenu, RemoveTrackPopover(.svelte/.test),
src/lib/api/admin/tracks(.ts/.test), and the now-needless transitive mocks
in the CompactTrackCard / PlaylistTrackRow / playlist specs.
The kebab is now an 8-item, admin-agnostic menu. The DELETE /api/admin/tracks
server endpoint is untouched — a future safer admin surface can rebind it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bring the web TrackMenu to parity with Android's canonical TrackActionsSheet
so the kebab reads the same on both clients:
- Reorder to Android's groups: queue → like/add-to-playlist/start-radio →
go-to-album/artist → hide.
- Drop the duplicate "Flag this track…" item — it opened the very same
FlagPopover as "Hide" (Android folds flag into a single Hide).
- Align icons (ListVideo / ListMusic / ListPlus / Disc3 / User).
- Admin-only "Remove from library" stays as a web superset (Android has no
surface for it), past its own divider.
Mount the kebab on the full-screen /now-playing route with hideQueueActions,
mirroring Android's NowPlayingScreen — Start radio / Add to playlist / Hide
were previously unreachable there (only like + volume + queue existed).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Radio was fully wired (playRadio → /api/radio + 80% auto-refresh) but its
only entry point was TrackRow's inline 📻 button, so it was unreachable from
the kebab — i.e. missing on the Most Played compact cards and the mini-player.
Add a "Start radio" item to TrackMenu, shown even under hideQueueActions since
reseeding a station from the current track is meaningful there.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI svelte-check was blocking on:
- 1 ERROR I introduced in the previous cleanup: QueueDrawer.test.ts
passed { hidden: true } to getByLabelText, but that option only
exists on getByRole. Fixed by switching to a direct
document.querySelector for the aria-hidden assertion.
- 13 WARNINGS pre-existing in the codebase from M7 #352/#372/#349
era, never surfaced because earlier CI runs failed before reaching
type-check. Now that CI gets that far, they accumulate. Cleared:
- FlagPopover, RemoveTrackPopover, AddToPlaylistMenu, TrackMenu:
interactive role divs gain tabindex="-1" + onkeydown that stops
propagation and closes on Escape (functional, not just warning-
suppression).
- FlagPopover state-from-props: $state(untrack(() => prop ?? default))
for explicit initial-snapshot semantics; const isUpdate switched
to $derived so it reacts to prop changes.
- PlaylistTrackRow drag-div: role="listitem" added.
- playlists/+page.svelte: autofocus replaced with bind:this + $effect.
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.
The "Add to playlist…" entry that #372 reserved as a disabled slot
is now active. Submenu lists the operator's own playlists
alphabetically; "New playlist…" toggles an inline create form that
makes the playlist + appends the track in two API calls.
TrackMenu's existing test asserts the entry is enabled and opens the
submenu instead of the previous "is disabled with tooltip" check.
Replaces the M5b-era FlagPopover-only menu with the full track-actions
surface: queue (Play next, Add to queue), collection (Like/Unlike,
Add to playlist… reserved for #352), navigation (Go to album/artist),
lifecycle (Flag, Hide/Unhide, Remove from library — admin-only).
Remove from library opens a new RemoveTrackPopover with a single
"Also stop Lidarr from finding a replacement" checkbox. The
destructive flow always deletes file + DB through Minstrel; the
checkbox controls whether Lidarr is also told to unmonitor. Lidarr
unmonitor failure flows back as lidarr_unmonitor_failed in the
response — destructive part already succeeded.
The component's prop API (track, direction) is unchanged so TrackRow
and PlayerBar pick up the new entries with no code change.
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>