Commit Graph

11 Commits

Author SHA1 Message Date
bvandeusen d12afdad6e feat(playlists): system playlists default to shuffle on tile play
Closes Fable #412 (For You force-refresh on play) and #413
(shuffle-on-play default for system playlists).

Web (PlaylistCard.svelte + player/store.svelte.ts):
- Drop the refreshForYou() call from the play handler. The daily
  03:00 user-local snapshot is what plays now. Stops burning server
  compute on every press and stops swapping the playlist out from
  under the user.
- Generalize the kebab affordance to render for any system playlist
  (was Discover-only). Adds "Refresh For You" as an explicit
  replacement so users can still force a regen when they want one.
- Extend playQueue(tracks, startIndex, { shuffle? }) to Fisher-Yates
  the queue when shuffle:true. PlaylistCard passes shuffle:true for
  any non-null system_variant.

Flutter (player_provider.dart + playlists/widgets/playlist_card.dart):
- playTracks now accepts shuffle:bool. When true, picks a random
  starting index and enables AudioServiceShuffleMode.all after
  setQueueFromTracks. PlaylistCard passes shuffle:playlist.isSystem.

User playlists keep linear order. Detail-screen play buttons are
unchanged for now (follow-up if user requests).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 22:55:57 -04:00
bvandeusen 0eaaf66748 refactor(web): playlistTrackToRef helper to dedupe TrackRef
reconstruction (#375)

PlaylistTrackRow.svelte and PlaylistCard.toTrackRefs both rebuilt
TrackRef from PlaylistTrack with the same field-by-field literal.
Server adding a new TrackRef field would have needed both sites
updated; now one helper owns the mapping.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:37:11 -04:00
bvandeusen 3e263657c6 refactor(web): hoist .play-overlay CSS to app.css; remove triplicated <style> blocks (C2) 2026-05-08 11:28:58 -04:00
bvandeusen 617477b702 refactor(web): pushToast store + ToastHost; 8 sites migrated (W3) 2026-05-08 05:45:34 -04:00
bvandeusen 9c91a342e2 refactor(web/api): errCode + errMessage helpers; 41 sites migrated (W1) 2026-05-07 22:17:45 -04:00
bvandeusen 081b8e62d5 fix(web): a11y + reactivity warnings (paying down accumulated warnings)
Cleans up the 8 svelte-check warnings I deferred in prior CI runs.
Two distinct categories — handling them differently:

REAL REACTIVITY BUGS — fixed:
- AlbumMenu.svelte / ArtistMenu.svelte: cachedTracks initialized
  from the `tracks` prop only at component creation. When the menu
  is reused across rows (operator opens menu on one album, then
  another), the cache from the first open serves stale tracks.
  Fix: initialize cachedTracks to null; ensureTracks() reads the
  current `tracks` prop on each call before falling through to
  the network fetch.

INTENTIONAL PATTERNS LINT FLAGGED — suppressed with explanatory
comments:
- AlbumMenu / ArtistMenu / PlaylistCard menu containers: the
  onclick={(e) => e.stopPropagation()} is a guard against the
  window-level "any click closes the menu" listener — not
  user-facing interaction. A paired keyboard handler is
  intentionally absent (Escape-to-close lives on svelte:window
  onkeydown). Suppressed a11y_click_events_have_key_events with
  explanatory comments.
- CompactTrackCard overlay div: catches clicks on its inner
  LikeButton + queue button so they don't bubble to the wrapping
  play-card button. Inner buttons carry their own keyboard
  handling. Suppressed both a11y_click_events_have_key_events and
  a11y_no_static_element_interactions.
- theme.svelte.ts module-scope `_resolved = $state(resolve(_theme))`:
  module-init reads _theme to seed _resolved; subsequent updates
  go through setTheme() and the matchMedia listener. $derived
  doesn't apply at module scope. Suppressed state_referenced_locally
  with an explanatory comment.

Promised in the prior fix-forward: "I'll fix bugs when I see them
or open a follow-up task with a specific name, not park them in an
umbrella." Following through.
2026-05-07 18:38:44 -04:00
bvandeusen e48d84cde1 feat(web/playlists): For-You tile play button refreshes and plays
Click on the For-You tile's play button now triggers a synchronous
refresh, then enqueues + auto-plays the freshly-built playlist.
Two-click-target tile pattern: tile BODY click still navigates to
the playlist detail page (existing behavior); the play button is
the new generate-and-play action.

Behavior is gated on playlist.system_variant === 'for_you' — every
other playlist (user, Discover, Songs-like-X) keeps the existing
"fetch detail, enqueue, play" flow. The atomic-replace BuildSystem-
Playlists creates a new playlist_id, so the play handler uses the
refresh response's id for the follow-up getPlaylist call rather
than the stale tile prop's id.

Empty-library response (playlist_id: null) is a no-op — clicking
play in a degenerate-empty library doesn't error, it just doesn't
start playback. Caches are invalidated post-refresh so the home
view re-fetches and the tile re-renders with the new id, avoiding
the corner case of a click-then-tile-body 404.

Tests cover the refresh-and-play happy path, the empty-library
no-op, and that non-For-You playlists keep the existing flow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 10:40:23 -04:00
bvandeusen b20738f925 feat(web/playlists): Discover refresh button (detail + home kebab)
Operator can refresh their Discover playlist without waiting for
the next daily cron tick. Two surfaces, both gated on
system_variant === 'discover':

- Detail page header: "Refresh" button next to the cover art / meta.
- Home Playlists row tile kebab: "Refresh Discover" menu item.

Both call POST /api/playlists/system/discover/refresh, show a
confirmation toast, and invalidate the playlist + playlists queries
so the new content lands without a manual page reload.

Extends Playlist.system_variant union with 'discover'. Adds
refreshDiscover() typed client. Tests cover client behaviour
(success + empty-library) plus conditional rendering on both
surfaces.
2026-05-07 08:44:33 -04:00
bvandeusen 835580ef1e fix(web/m7-380): convert PlaylistTrack to TrackRef in PlaylistCard play handler 2026-05-04 19:46:43 -04:00
bvandeusen b290b4ff0b feat(web/m7-380): animate pending placeholder + add play overlay to PlaylistCard 2026-05-04 19:42:30 -04:00
bvandeusen 0eb346e0c6 feat(web): PlaylistCard component for M7 #352 slice 1
Square card with cover (or "No tracks yet" glyph fallback), name,
track count, and owner attribution when the playlist isn't the
current user's. Click navigates to /playlists/{id}.
2026-05-03 11:14:30 -04:00