Commit Graph

17 Commits

Author SHA1 Message Date
bvandeusen 1f08ee39bd fix(web): add likes/QueryClient mocks to route tests rendering LikeButton
Pages that render TrackRow/AlbumCard/ArtistRow/PlayerBar now indirectly
mount LikeButton, which calls useQueryClient(). Route tests need the
same mocks the component-level tests have.

- Added createLikedIdsQuery mock to 7 route test files
- Added useQueryClient mock to all affected route tests
- Added readable store import where needed
2026-04-26 17:18:39 -04:00
bvandeusen 457b94005f feat(web): add /library/liked page with three sections
Each section is its own infinite query against /api/likes/{type}.
Empty sections show 'No liked X yet' (gated on total === 0). Reuses
ArtistRow / AlbumCard / TrackRow for rendering — same components as
the search overflow pages.
2026-04-26 17:08:06 -04:00
bvandeusen 6ff00ea24d feat(web): mount useEventsDispatcher() in root layout
Single mount point alongside useMediaSession. Once called, the
dispatcher's \$effect watchers run for the lifetime of the SPA.
2026-04-26 09:44:16 -04:00
bvandeusen 7fff293054 feat(web): add three search overflow pages
/search/artists, /search/albums, /search/tracks each read ?q= and run
their own createSearchInfiniteQuery (limit=50). Load more pulls the
next offset; back link returns to /search?q=. Empty q shows a 'no
query' prompt and fires no request. Tracks overflow uses the onPlay
prop on TrackRow to call playRadio(track.id).
2026-04-25 16:19:09 -04:00
bvandeusen e55520dab6 feat(web): /search page with three sections + see-all overflow links
Reads ?q= reactively; runs createSearchQuery (limit=10) when q is
non-empty. Renders Artists/Albums/Tracks sections reusing ArtistRow,
AlbumCard, TrackRow. Track click uses onPlay prop to call
playRadio(track.id) instead of the default playQueue. Empty facets
hide; 'See all N →' link points to overflow pages when total > items.
Empty q shows 'Start typing'; error → ApiErrorBanner; loading →
SearchSkeleton via useDelayed.
2026-04-25 16:16:55 -04:00
bvandeusen 4264ecf538 fix(web): trigger audio.play() during 'loading' state, not only 'playing'
The layout's play-intent effect gated audioEl.play() on player.isPlaying
(state === 'playing'), but the state progression is loading → DOM play()
→ 'playing' event → state='playing'. Without calling play() during
loading, the DOM never starts, the event never fires, and state is
pinned to loading forever. Call play() for the intent-to-play union
(loading + playing), matching the store's design.
2026-04-24 22:12:34 -04:00
bvandeusen 571eb2ec53 feat(web): mount <audio> at root layout and wire it to the player store
Single hidden <audio> survives navigation. Effects drive it from the
store (src, volume, play/pause) and DOM events report back (timeupdate,
loadedmetadata, playing, pause, waiting, ended, error). useMediaSession
is called once here so OS media controls light up as soon as the user
signs in.
2026-04-24 21:43:03 -04:00
bvandeusen 4bff8cc99a feat(web): make TrackRow click-to-play; pass tracks+index from album page
TrackRow becomes a <button> that dispatches playQueue(tracks, index).
Album page supplies the full track list + each index so the rest of
the album enqueues automatically when the user clicks a track.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 21:39:34 -04:00
bvandeusen 9b67bb7521 fix(web): restore album-page back link; loosen test matchers
The initial Task 13 pass dropped the back-chevron link above the hero
to satisfy a strict getByRole('link', {name: /Miles Davis/}) matcher
that was finding both the back link and the inline hero-artist link.

Better fix: keep the back affordance users expect, and use matchers
that accept ambiguity — getAllByRole for the hero check, and a
strict '^← …' regex for the back-link assertion.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 20:52:28 -04:00
bvandeusen 9d28c9d9de feat(web): add album detail page at /albums/:id
Hero with cover + title + linked artist + metadata; TrackRow list
below. Back link targets the artist page (not Library) so drilling
up feels correct. 404 renders a distinct non-retryable state.
2026-04-23 20:21:50 -04:00
bvandeusen 2e49335aab feat(web): add artist detail page at /artists/:id
Renders the artist name + album count and a responsive AlbumCard grid
from the nested ArtistDetail response. 404 surfaces a distinct
non-retryable 'not found' state; other errors share the retry banner.
2026-04-23 20:12:57 -04:00
bvandeusen 5174b093b9 feat(web): replace / placeholder with real artists list
Uses createArtistsQuery (infinite), URL-driven sort dropdown, Load
more pagination, delayed skeleton, and shared error banner. Also
introduces the test-utils/query.ts helpers for subsequent page tests.
2026-04-23 19:43:37 -04:00
bvandeusen 048663a4c8 feat(web): wire root layout — bootstrap, guard, Shell, QueryProvider
+layout.ts runs auth.bootstrap() in load() so the shell sees the user
synchronously. +layout.svelte installs the TanStack QueryClientProvider,
runs the route guard as a \$effect, and swaps Shell vs. bare slot
based on auth state.
2026-04-23 06:11:43 -04:00
bvandeusen 88961596b1 fix(web): rename route-colocated test to avoid +page.* collision
+page.test.ts triggered svelte-kit sync's route walker (which treats
any +-prefixed file as a route). Renaming to login.test.ts lets the
sync pass without disabling it in scripts, preserving the upstream
guarantee that types in .svelte-kit/ are fresh before check/test.

Plan bug introduced in Task 10 (filename chosen in the plan); fixing
here and updating the plan so re-runs use the correct name.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 06:10:11 -04:00
bvandeusen 78f69b8736 feat(web): add login page with returnTo support and typed error UX
Inline errors for invalid creds vs. server-side failures. Validates
returnTo to block open-redirect vectors (//, http://, /login,
parent-traversal).
2026-04-23 02:41:44 -04:00
bvandeusen d2d5d18e74 feat(web): add Library/Search/Playlists placeholder pages
Library replaces the scaffold splash. Search and Playlists are routable
so the Shell's sidebar nav works end-to-end before the real features land.
2026-04-22 22:22:11 -04:00
bvandeusen 86b024dc47 feat(web): scaffold SvelteKit SPA with Tailwind + adapter-static
- SvelteKit 2.x + Svelte 5 + TypeScript + Vite 5
- adapter-static with fallback:'index.html' for SPA deep-link routing
- Tailwind configured with dark-only palette matching spec
- Placeholder landing page at /
- Committed web/build/index.html placeholder (via .gitignore negation) so
  go:embed works before the SvelteKit build has been run
2026-04-22 09:26:36 -04:00