Commit Graph
100 Commits
Author SHA1 Message Date
bvandeusen 80b39f1713 feat(web): TrackRow div+role button with onPlay prop and + queue button
Outer <button> becomes <div role="button" tabindex="0"> with keyboard
handlers (Enter/Space) so a real <button> for + queue can nest without
invalid HTML. New optional onPlay prop overrides the default
playQueue(tracks, index); used by the search Tracks section to call
playRadio(track.id) instead. + queue button calls enqueueTrack and
stops propagation so the row click does not fire.
2026-04-25 15:37:11 -04:00
bvandeusen 7eb051c6f7 feat(web): add search query helpers (summary + 3 facet infinite queries)
createSearchQuery hits /api/search with limit=10 (the main /search
page summary). createSearchArtistsInfiniteQuery /
createSearchAlbumsInfiniteQuery / createSearchTracksInfiniteQuery each
hit /api/search with limit=50 and project to their own facet, used by
the per-facet overflow pages. All four are gated by q.length > 0 so
empty queries don't fire.
2026-04-25 15:30:06 -04:00
bvandeusen 7dd77a4723 feat(web): add enqueueTrack, enqueueTracks, playRadio to player store
enqueueTrack/enqueueTracks append to the queue without disturbing
playback state (no auto-play on append, no state mutation when queue
was non-empty). playRadio fetches /api/radio?seed_track=<id> and
feeds the response into playQueue. Empty response leaves queue alone.
2026-04-25 15:22:03 -04:00
bvandeusen 8e80b6e0de feat(web): add SearchResponse and RadioResponse types 2026-04-25 15:02:56 -04:00
bvandeusen 1e66910823 feat(api): add /api/radio stub returning the seed track only
M6 stub. Validates seed_track param (400 on missing/blank/bad UUID),
looks up the track (404 on miss), returns RadioResponse with a single
TrackRef. M4 will replace the body with similarity-driven candidate
pool + scoring; the request/response shape is final.
2026-04-25 15:01:39 -04:00
bvandeusen 8fd193e186 docs(plan): add web UI search implementation plan
12 tasks: server radio stub, web types, player-store enqueue/playRadio,
query helpers, TrackRow div+role refactor, AlbumCard +queue overlay,
SearchInput header component, SearchSkeleton, Shell wiring, /search
main page, three overflow pages, final verification.
2026-04-25 14:59:08 -04:00
bvandeusen 3588af83e5 docs(spec): add web UI search design
Specifies header search bar (debounced, URL-synced ?q=), /search page
with three sections of top-10 reusing library components, three
overflow routes (/search/artists, /search/albums, /search/tracks)
backed by createInfiniteQuery. Track click triggers playRadio with
seed via a new /api/radio stub that returns the seed alone (M4 fills
in real similarity-based selection later, response shape final). Adds
+queue button on TrackRow and AlbumCard via new enqueueTrack /
enqueueTracks player-store actions.
2026-04-25 14:49:01 -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 ccef9be2d3 feat(web): add PlayerBar slot to Shell
Grid grows to 3 rows (auto / 1fr / auto); PlayerBar spans both columns
in the last row, rendering only when player.current is defined so the
row collapses before first play.
2026-04-24 21:42:10 -04:00
bvandeusen ed082226a0 feat(web): add PlayerBar component
Bottom-bar UI with left: cover+title+linked artist, center: stacked
seek row (elapsed / slider / duration) + transport (prev/play/next),
right: shuffle/repeat/volume. Loading shows spinner; error replaces
transport with retry card.
2026-04-24 21:41:24 -04:00
bvandeusenandClaude Opus 4.7 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 4189ef9899 feat(web): add MediaSession glue for OS media controls
useMediaSession() wires navigator.mediaSession metadata + action
handlers (play/pause/previous/next/seekto) + positionState to the
player store via $effect. Exports nothing stateful — callers just
invoke it once at root-layout mount.
2026-04-24 21:03:01 -04:00
bvandeusen 1476d5081b feat(web): add shuffle, repeat, and audio-event reporting to player store
toggleShuffle (Fisher-Yates over queue[index+1..end] with injectable
RNG for test determinism), cycleRepeat (off → all → one → off),
reportStateFromAudio with full repeat-mode semantics on 'ended',
plus simple state mirroring for playing/paused/waiting/error. Extends
skipNext to wrap when repeat='all'.
2026-04-24 17:56:00 -04:00
bvandeusen 431f333e41 feat(web): add player rune store with basic actions
Core state (queue, index, state, position, duration, volume, shuffle,
repeat, error) plus actions: playQueue, togglePlay, linear skipNext,
skipPrev 3-second rule, seekTo, setVolume (localStorage-persisted),
registerAudioEl, reportTimeUpdate, reportDuration. Shuffle/repeat
logic and audio-event reporting land in follow-up commits.

Also install an in-memory Storage polyfill in vitest.setup.ts: Node 25
exposes a partial localStorage global that lacks getItem/setItem/clear
(requires --localstorage-file with a path), and jsdom defers to the
Node global when present, leaving tests unable to exercise storage.
2026-04-24 17:54:07 -04:00
bvandeusenandClaude Opus 4.7 7c19fe8a6a docs(plan): add web UI player implementation plan
8 TDD tasks: player rune store (2 tasks covering basic actions then
shuffle/repeat/audio-reporting), MediaSession glue, click-to-play
wiring in TrackRow + album page, PlayerBar component, Shell 3rd-row
integration, audio element wiring in root layout, and a final
verification + branch-finish step.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 08:57:52 -04:00
bvandeusenandClaude Opus 4.7 d6ea734152 docs(spec): add web UI player design
Queue-based playback on top of HTMLAudioElement: rune store + pure
action functions, single <audio> mounted in the root layout driven
via $effect, MediaSession metadata/action handlers, and a persistent
bottom bar with shuffle/repeat/volume. Non-goals: server-side listen
history (awaiting /api/events), cross-refresh persistence, keyboard
shortcuts.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 08:50:58 -04:00
bvandeusenandClaude Opus 4.7 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
bvandeusenandClaude Opus 4.7 f9b02c4051 docs(plan): Task 13 — match the type-safety fixes applied in Task 12
page.params.id is string|undefined in SvelteKit types; use ?? ''.
TanStack Query's error is typed as Error; cast through unknown to ApiError.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 20:13:20 -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
bvandeusenandClaude Opus 4.7 25d44a180c docs(plan): update Tasks 12/13 to unwrap TanStack Query's Readable store
TanStack Query 5's Svelte adapter returns a Readable<QueryObserverResult>
store, not a plain object. Pages need to destructure with $-subscription:
  const queryStore = $derived(createXQuery(id));
  const query = $derived($queryStore);

Applied to artist + album detail page templates. Task 11 already
landed with this fix (and readable-wrapped mocks in test-utils).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 20:01:46 -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 f94bf26f02 feat(web): add ApiErrorBanner retry-capable error card 2026-04-23 18:42:22 -04:00
bvandeusen 9ed8e261d4 feat(web): add LibrarySkeleton with list/grid/album variants
Shimmer placeholders that mirror the layout of each library page so
the real content slides in without shifting.
2026-04-23 18:41:32 -04:00
bvandeusen 12d1cb739b feat(web): add TrackRow component
Non-interactive album track row with number, title, duration.
Player plan will add click-to-play.
2026-04-23 18:40:40 -04:00
bvandeusen e9b482da0c feat(web): add AlbumCard component
Grid card used on the artist detail page. Cover image, title, year.
Broken covers swap to FALLBACK_COVER via onerror.
2026-04-23 18:39:42 -04:00
bvandeusen a1252c1ef4 feat(web): add ArtistRow component
One row in the artists list: avatar initial, name, album count,
chevron. Entire row is an <a> for click + keyboard activation.
2026-04-23 18:18:52 -04:00
bvandeusen ce6b79ec95 feat(web): add TanStack Query helpers for library endpoints
qk.{artists,artist,album} key generators and create*Query wrappers
with shared page size, sort-aware keys, and infinite-query plumbing
for /api/artists.
2026-04-23 18:17:34 -04:00
bvandeusenandClaude Opus 4.7 4cd67405c9 docs(plan): update useDelayed task to match .svelte.test.ts convention
Rune-using test files need the .svelte. infix so vite-plugin-svelte
processes them. Also adds the flushSync() after $effect.root in the
'source already true' test so fake-timer advances fire the scheduled
timeout.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 18:16:37 -04:00
bvandeusenandClaude Opus 4.7 19d74d00d6 refactor(web): drop useDelayed sync-timeout hack; fix test timing instead
The synchronous pre-timeout in the implementation was a band-aid for a
missing flushSync() in the third test. $effect callbacks are scheduled
as microtasks and don't run until flushed — the test set source=true
before the initial effect had a chance to register the setTimeout, so
advanceTimersByTime fired nothing.

Proper fix: call flushSync() after $effect.root so the initial effect
runs synchronously, then advance fake timers. Removed the duplicate
setTimeout branch from useDelayed — one code path, no race.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 18:16:22 -04:00
bvandeusen 6ca877b3ad feat(web): add useDelayed rune helper
Boolean mirror that flips true only after the source stays true for
delayMs. Used to hide the skeleton on sub-100ms cache hits so
back-navigation feels instant.
2026-04-23 17:23:23 -04:00
bvandeusen 828273a78f feat(web): add cover URL helper + fallback SVG data URL 2026-04-23 08:23:59 -04:00
bvandeusen 7345625ecd feat(web): add formatDuration helper
Renders seconds as mm:ss or h:mm:ss. Negative inputs clamp to 0:00
so a bad backend value doesn't crash the UI.
2026-04-23 08:23:22 -04:00
bvandeusen d15995d19e feat(web): add library API types mirroring internal/api/types.go 2026-04-23 08:22:33 -04:00
bvandeusenandClaude Opus 4.7 ea17be9d45 docs(plan): add web UI library views implementation plan
14 TDD tasks: API types, formatDuration + covers helpers, useDelayed
rune, TanStack Query wrappers, 5 shared components (ArtistRow,
AlbumCard, TrackRow, LibrarySkeleton, ApiErrorBanner), 3 page routes
(/, /artists/:id, /albums/:id) with mocked-query integration tests,
and a final verification + branch-finish step.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 08:18:08 -04:00
bvandeusenandClaude Opus 4.7 248f03495e docs(spec): add web UI library views design
Covers the three browse routes (/, /artists/:id, /albums/:id) on top
of the existing /api/artists, /api/artists/{id}, /api/albums/{id},
and /api/albums/{id}/cover endpoints. Uses TanStack Query's infinite
query for the artists list with Load-more pagination, reusable card
and row components, and a shared delayed-skeleton pattern for
cache-hit navigation feel.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 08:09:55 -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
bvandeusenandClaude Opus 4.7 fe410f14eb docs(plan): correct login test filename to avoid route walker collision
Renamed references from +page.test.ts to login.test.ts throughout,
matching the fix in 8896159. Keeps the plan accurate for re-runs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 06:10:29 -04:00
bvandeusenandClaude Opus 4.7 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 e1504f8e6c feat(web): add Shell component with header, sidebar, and user menu
Persistent chrome used by every authenticated route. Sidebar hides
below md:; mobile nav is a separate concern for a later plan.
2026-04-22 22:20:18 -04:00
bvandeusen 7a6aa50693 feat(web): auto-logout on 401 inside apiFetch
Dynamic import breaks the apiFetch <-> auth/store cycle. silent:true
prevents re-POSTing /logout against an already-dead session.
2026-04-22 17:29:36 -04:00
bvandeusen 07b5912fae feat(web): add rune-based auth store with bootstrap/login/logout
Central synchronous source of truth for 'who is signed in'. bootstrap()
runs once in +layout.ts load(); login/logout mutate _user and the
TanStack query cache. The silent option on logout is used by the 401
interceptor (next commit) to avoid POSTing /logout against an already-
invalid session.
2026-04-22 17:28:10 -04:00
bvandeusenandClaude Opus 4.7 12bf873f39 docs(plan): fix invalid toMatchObject<T> type-arg in auth plan
vitest's toMatchObject doesn't accept a type parameter. Removed the
annotation in Task 3 test code (and the unused ApiError import) so
re-runs of the plan don't reintroduce the type error fixed in 8a6a496.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 17:26:37 -04:00
bvandeusenandClaude Opus 4.7 8a6a496c67 fix(web): drop invalid type-arg on toMatchObject in client tests
vitest's toMatchObject doesn't accept a type parameter; tests passed
under vitest (esbuild transpile) but svelte-check rejected. Plan-spec
bug introduced in 4d1a7f8; fixing here so check is clean before Task 6.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 17:26:15 -04:00
bvandeusen 4591618e7b feat(web): add TanStack Query client singleton
Tuned defaults: retry disabled (we throw typed errors), 30s staleTime
to make tab-switch re-navigation feel instant, no refetchOnWindowFocus
(users on a music app don't expect spurious network when they tab back).
2026-04-22 16:20:31 -04:00
bvandeusen 37e5539e5a feat(web): add api.{get,post,del} typed facade over apiFetch 2026-04-22 16:04:25 -04:00
bvandeusen 4d1a7f8b93 feat(web): add apiFetch with typed error envelope
Wraps fetch, parses JSON, and throws ApiError{code,message,status}
on non-2xx. Degrades to {code:'unknown'} when the error body is not
the expected envelope shape.
2026-04-22 15:56:15 -04:00
bvandeusen e31242b57f feat(web): add api client types (User, LoginResponse, ApiError) 2026-04-22 15:55:00 -04:00
bvandeusen d0015d3638 build(web): add TanStack Query + Testing Library deps
Wires @tanstack/svelte-query for the data layer and
@testing-library/{svelte,jest-dom} for component-level tests. Registers
the jest-dom matchers via a new vitest setup file.
2026-04-22 15:51:35 -04:00
bvandeusenandClaude Opus 4.7 0b5c4a13bf docs(plan): add web UI auth implementation plan
12 TDD tasks: deps, api client, query client, auth store, 401
interceptor, Shell, placeholder pages, login page, root layout
wiring, and final verification + branch finish. Each task writes
the failing test first, then minimum implementation, then commits.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 15:47:23 -04:00
bvandeusenandClaude Opus 4.7 d53e7e2985 docs(spec): add web UI auth design
Covers the login flow, session store, fetch wrapper, persistent Shell,
and route guarding for the first frontend feature on top of the scaffold.
Commits to TanStack Query as the data layer going forward.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 15:40:07 -04:00
bvandeusenandClaude Opus 4.7 207798b667 docs: document two-process dev workflow with Vite proxy
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 12:59:22 -04:00
bvandeusenandClaude Opus 4.7 0ee4dfcf42 ci: install Node + build web before Go steps
Go's //go:embed needs web/build/ to exist at compile time. CI now
runs 'npm ci && npm run build && npm test' ahead of the Go steps
so the embed directive sees real, freshly-built assets.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 12:59:07 -04:00
bvandeusenandClaude Opus 4.7 d9f55df347 build: multi-stage Dockerfile with node->go->slim for embedded SPA
Web assets are built in the node stage, copied into the go stage before
go build so //go:embed picks them up, then the minimal slim runtime
carries only the final binary and ffmpeg.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 12:58:29 -04:00
bvandeusen 75ce65c80e feat(server): serve embedded SPA at root with /api,/rest carve-out
- NotFound wrapper routes unknown /api/* and /rest/* to a JSON 404
- Everything else falls through to the embedded web handler, which
  resolves static assets or returns index.html for SPA deep links
2026-04-22 10:31:30 -04:00
bvandeusen 8fe4a5f578 refactor(web): move embed package to web/ so go:embed reaches build/
go:embed paths are relative to the source file and cannot reach parent
directories, so internal/web/embed.go could not see web/build/ without
duplicating the placeholder. Relocating to web/embed.go lets the
directive resolve to the real SvelteKit build output with no copy step.
2026-04-22 10:29:31 -04:00
bvandeusenandClaude Sonnet 4.6 d5692a73ca feat(web): embed SvelteKit build output with SPA fallback handler
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 10:26:36 -04:00
bvandeusen 8d88a76dbf test(web): add Vitest smoke test and jsdom config 2026-04-22 09:30:08 -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
bvandeusenandClaude Opus 4.7 8600b253fd style: gofmt fixes across api package
CI's golangci-lint run flagged three files; two pre-existed this
branch but the Plan 3 seedTrackWithFile struct-literal alignment
is new. Applying gofmt across all three keeps the lint baseline clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 23:02:20 -04:00
bvandeusen 63d3e234d9 feat(api): register cover + stream routes under RequireUser 2026-04-21 22:48:45 -04:00
bvandeusenandClaude Opus 4.7 3656461b20 feat(api): GET /api/tracks/{id}/stream with Range support
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 22:44:48 -04:00
bvandeusen 945b773cbd feat(api): GET /api/albums/{id}/cover with sidecar fallback 2026-04-21 22:37:53 -04:00
bvandeusen 5af6d7fac6 docs(api): document intentional audioContentType divergence from subsonic 2026-04-21 22:35:29 -04:00
bvandeusen c799e5c1c9 feat(api): scaffold media helpers for cover + stream endpoints 2026-04-21 22:30:25 -04:00
bvandeusenandClaude Opus 4.7 8da51a1d13 docs: add Plan 3 web UI media endpoints implementation plan
Four-task TDD plan for /api/albums/{id}/cover and /api/tracks/{id}/stream:
scaffold media.go helpers (Task 1), cover handler (Task 2), stream handler
(Task 3), production Mount wiring + route-registration regression (Task 4).
Uses seedTrackWithFile helper to materialize real bytes on disk so Range /
If-Modified-Since tests hit http.ServeContent's real code path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 22:02:55 -04:00
bvandeusenandClaude Opus 4.7 20c948a346 docs: clarify sidecar ordering and test helper extension in Plan 3 spec
Self-review fixups: make "first track" explicit (by disc/track order) and
replace the awkward "so existing tests continue to compile" phrasing with a
clearer description of why newLibraryRouter needs extending.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 21:52:07 -04:00
bvandeusenandClaude Opus 4.7 7fcee05c1e docs: add Plan 3 web UI media endpoints spec
Covers /api/albums/{id}/cover and /api/tracks/{id}/stream — the byte-serving
endpoints Plan 2's cover_url / stream_url forward references point at.
Duplicates protocol-agnostic helpers locally per subsonic-is-legacy direction
rather than extracting a shared package.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 21:51:29 -04:00
bvandeusenandClaude Opus 4.7 b94b7514f9 fix(api): clamp negative offset instead of erroring + clarify duration_sec=0
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 19:27:21 -04:00
bvandeusen db2ee3b21f feat(api): register library + search routes under RequireUser 2026-04-21 08:34:12 -04:00
bvandeusenandClaude Sonnet 4.6 683c11661b feat(api): GET /api/search with three paged facets
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 08:29:28 -04:00
bvandeusenandClaude Sonnet 4.6 8fb2f4e184 feat(api): GET /api/artists with alpha/newest sort and paging
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 08:06:03 -04:00
bvandeusen f37fd782af feat(api): GET /api/artists/{id} with nested albums 2026-04-21 08:01:25 -04:00
bvandeusenandClaude Sonnet 4.6 c0487ecddb feat(api): GET /api/albums/{id} with nested tracks
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 07:43:39 -04:00
bvandeusenandClaude Sonnet 4.6 8c18e44d5b feat(api): GET /api/tracks/{id} handler with parent metadata
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 07:12:11 -04:00
bvandeusen 98b4755b49 test(api): add library seed fixtures 2026-04-21 00:16:24 -04:00
bvandeusen 3855166a09 feat(api): add dbq→ref projection helpers 2026-04-21 00:13:06 -04:00
bvandeusen e43b6f2761 feat(api): add conversion helpers for library DTOs 2026-04-21 00:09:30 -04:00
bvandeusen c5af7bb53a feat(api): add DTOs for library reads and search 2026-04-21 00:04:51 -04:00
bvandeusen fb49a39492 feat(db): add paged + count queries for library reads 2026-04-21 00:01:15 -04:00
bvandeusenandClaude Opus 4.7 62e48642e2 docs(plan): add implementation plan for library reads + search
12 TDD-style tasks covering SQL additions, DTOs, conversion helpers,
per-endpoint handlers (tracks, albums, artist detail, artist list, search),
route wiring, and an end-to-end smoke + PR. Follows the approved design at
docs/superpowers/specs/2026-04-20-web-ui-library-reads-design.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 23:42:47 -04:00
bvandeusenandClaude Opus 4.7 14aa9d7fba docs(spec): add design for library reads + search endpoints
Plan 2 of the web UI rollout. Covers GET /api/artists (paged, two sort
modes), artist/album/track detail, and unified search with enveloped
pagination and forward-reference stream/cover URLs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 23:33:51 -04:00
bvandeusen 728c852ba1 Merge pull request 'feat: web UI server auth foundation (/api/* login, logout, me)' (#14) from dev into main 2026-04-21 02:51:45 +00:00
bvandeusenandClaude Opus 4.7 2324c5d576 fix(auth): silence unused-parameter lint in RequireUser test
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 22:43:22 -04:00
bvandeusenandClaude Opus 4.7 1e5c8b5ab7 fix(api): trim whitespace from bearer token in logout
sessionTokenFromHTTP now matches extractBearerToken's trimming
behavior. Without this, a bearer header with trailing whitespace
(e.g. a buggy client or proxy) authenticates via RequireUser but
hashes the padded value on logout, silently no-oping and leaving
the server-side session alive.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 22:32:12 -04:00
bvandeusenandClaude Opus 4.7 06a1fe16e0 feat(server): mount /api/* (login, logout, me)
Wires the native JSON surface alongside the existing /api/admin
and /rest/* routes. Subsonic compatibility remains untouched.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 22:19:59 -04:00
bvandeusenandClaude Opus 4.7 2adf41604f feat(api): GET /api/me
Returns the authenticated user (id/username/is_admin). Shape
matches UserView used in the login response so SPA stores stay
typed against one interface.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 22:11:02 -04:00
bvandeusenandClaude Opus 4.7 8537821d29 feat(api): POST /api/auth/logout
Deletes the session row keyed by the cookie/bearer token and
clears the cookie on the client. Best-effort DB delete — logout
still succeeds for the client if the row's already gone.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 22:07:16 -04:00
bvandeusenandClaude Opus 4.7 2f2dfa86df feat(api): POST /api/auth/login
Verifies bcrypt password hash, mints a session token, stores its
sha256 in the sessions table, and returns the token in both the
response body (for Flutter) and an httpOnly/SameSite=Strict
cookie (for the web SPA).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 21:25:52 -04:00
bvandeusenandClaude Opus 4.7 dc43921bcc feat(api): package skeleton + error envelope
Introduces internal/api with Mount(), the {error:{code,message}}
response shape, and stubbed login/logout/me so later tasks can
land one handler at a time without breaking the build.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 20:57:09 -04:00
bvandeusenandClaude Opus 4.7 8cdaf8f0f1 feat(auth): RequireUser middleware (cookie or bearer)
Resolves /api/* callers from session cookie first, Authorization
bearer second. Touches last_seen on success for future active-
sessions UI. Adds GetUserByID query used by the middleware.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 20:53:11 -04:00
bvandeusenandClaude Opus 4.7 ff4d443269 feat(auth): session token + password verification helpers
Shared primitives for /api/* auth: mint a url-safe opaque token,
hash it for storage, verify a bcrypt password hash.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 20:24:09 -04:00
bvandeusenandClaude Opus 4.7 c8a4a930ea feat(dbq): generate session queries
Adds Insert/Get/Touch/Delete helpers over the sessions table.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 20:16:48 -04:00
bvandeusenandClaude Opus 4.7 1741b57a0b feat(db): add sessions table for /api/* auth
Stores sha256(token) plus user_agent + last_seen_at so future
active-sessions UI doesn't need another migration.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 19:37:26 -04:00
bvandeusenandClaude Opus 4.7 9b0433ad9b docs: add web UI server auth foundation plan
10-task plan for the first implementation slice of the web UI
scaffold: sessions table, /api/auth/{login,logout}, /api/me, and
the RequireUser middleware (cookie + bearer). Stops short of the
library read endpoints and the SvelteKit project, which become
their own follow-up plans.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 19:30:24 -04:00
bvandeusenandClaude Opus 4.7 cb3df51f08 docs: add web UI scaffold design spec
First pass of the web UI design doc from the M1 close-out brainstorm.
Covers the stack (SvelteKit static + Go embed), auth model
(cookie + bearer from one endpoint), /api/* surface, shell layout
(sidebar + bottom player), first-cut feature scope, and how later
web UI work threads into M2–M5 alongside each backend milestone
instead of landing as a post-hoc M6.

Also ignores the local .superpowers/ brainstorming companion cache.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 19:18:04 -04:00
bvandeusen cd535b9de6 Merge pull request 'feat(library): extract track duration via ffprobe' (#13) from dev into main 2026-04-20 12:11:09 +00:00