"Start radio" in the track menu seeds the queue from a track; RadioController
(headless, in AppShell) tops it up via /radio/next as it drains. QueuePanel's
radio scaffolding is now live: the exploration slider drives familiar<->new and
each entry shows its reason ("why this is playing"). Adds the radio endpoints
(start/next/similar), radio context + actions on the queue slice, and the pick->
entry mapper. Degrades to metadata picks while ML is offline.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Full-screen overlay expanded from the persistent player: big artwork or a
Lyrics tab. Synced (LRC) lyrics highlight the active line by playback position,
auto-scroll, and seek on tap; plain text is the fallback; missing lyrics offers
a re-fetch. Adds lyrics endpoint + LRC parser + isNowPlayingOpen player state.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AdminInstance shows backing-service health (db/redis/ml), read-only source
status (env-configured), and a library reindex with graceful handling when no
indexable source exists. InstanceSettings shows the active instance and offers
switch/sign-out to /connect. Adds getServices/getSources/reindex endpoints.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Duplicates / failed / missing-metadata tabs (paginated, reusing TrackRow) plus
admin orphan-file cleanup. Adds getDuplicates/getBrokenFiles/getMissingMetadata/
runCleanup endpoints and a DuplicateGroup type; drops the dead scanStorage/
deleteTrackFile stubs that had no backend route.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
User list with create dialog, role toggle, (de)activate, and reset-password;
self-lockout guard. Adds getUser/resetPassword endpoints, an isActive field on
the User type, and en/ru strings.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds src/api/endpoints/settings.ts (getSettings/updateSettings,
getScrobbling/updateScrobbling) plus AppSettings/ScrobblingConfig types
and a Settings cache tag. Replaces the "coming soon" scaffolds:
- Playback: stream-quality preference (SegmentedControl), server-synced.
- Scrobbling: enable + provider (Last.fm/ListenBrainz) + username +
write-only session key, with a linked/not-linked badge and save states.
Built with modern-sk (Window/SegmentedControl/TextField/Button/Callout/
Badge) + the shared Loading/Error states; en + ru strings added. Server
state flows through RTK Query hooks only (no fetch in components).
Also drops a pre-existing unused TextField import in LibraryPage that was
failing `tsc --noEmit`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The album cards always fell back to the 💿 placeholder: the mapper dropped
the backend's `has_cover` and no album cover URL was ever built. Carry
`hasCover` through `RawAlbum`/`Album` and add `getAlbumCoverUrl` (GET
/albums/{id}/cover, token in the query like the track/stream URLs). The
Library Albums grid and the artist-detail discography now render real
covers, same source the album-detail page already used.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The transient client-side upload queue vanished on refresh, so a just-
uploaded track seemed to disappear. Add a server-backed "Recently
uploaded" section (source=upload, newest first) that survives refresh and
auto-refreshes after each upload (the upload mutation already invalidates
the `Track` tag this query provides).
- api: `source` filter on `LibraryFilters` → `GET /tracks?source=`
- i18n: `upload.recent.*` (en + ru); loading/error/empty states
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the "coming soon" stub with a real dashboard wired to
`GET /storage`. modern-sk visuals: a layered disk-capacity gauge (library
share vs other-used vs free), stat tiles (tracks/artists/albums/playtime/
footprint/avg size), per-format size bars, metadata-health badges, source
breakdown, a popularity-weighted top-genres cloud, and playful fun facts.
- types: full `StorageStats` shape + `toStorageStats` snake→camel mapper
- endpoint: re-point `getStorageStats` to `GET /storage` with transform
- lib: `formatLongDuration` for big playtime spans
- i18n: `storage.*` keys (en + ru)
- three list states (loading / error / empty) per the UI invariant
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the placeholder with a controlled form for title/artist/album/
year/genre/track number, an AcoustID "find matches" action showing
ranked candidates with confidence, a diff/apply picker, a re-enrich
button, and save via PUT /metadata. Adds matches/apply API endpoints,
mappers, types, and en/ru i18n strings. Batch editor remains a
placeholder (deferred).
Add a right-side track info drawer that sits to the right of the queue
panel when both are open. Shows a large cover, title/artist/album links,
a Play/Queue/Edit actions row, and Status/General/File/Identifiers
sections (empty rows omitted). Opens from the track context menu, the
player now-playing tile, and the queue now-playing card.
- ui slice: trackInfoId + open/closeTrackInfo
- TrackInfoDrawer rendered after QueuePanel in AppShell; overlays content
on narrow viewports
- map source/createdAt/enrichedAt from the wire (were unmapped)
- formatDateTime helper, info icon, i18n (en/ru)
- drop orphaned toggleNowPlaying/isNowPlayingOpen from player slice
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The mapper dropped metadata_status and hardcoded availability, so enrichment
state was invisible and a just-uploaded track never appeared to change. Map
metadata_status/metadata_error/has_cover onto Track; add MetadataStatusBadge
(pending spinner / enriched / failed-with-reason / manual) shown in TrackRow,
and serve token-bearing track covers via getTrackCoverUrl.
UploadPage now polls each uploaded track (stops once enrichment settles) so the
resolved title/artist — or a failure reason — appears live. i18n in en + ru.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a login/register toggle to ConnectPage backed by a new
useRegisterMutation (register -> /auth/me, mirroring login). The toggle
is shown only when REGISTRATION_ENABLED, resolved with the same
precedence as the API base URL: runtime window.__APP_CONFIG__ >
PUBLIC_ENABLE_REGISTRATION env > default true. The prod runtime-config
script injects the runtime flag. The backend's ALLOW_REGISTRATION stays
the real authority; this only gates the UI. EN/RU strings added.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the faked ConnectPage login with a real /auth/login -> /auth/me
flow, including loading/error states. Add a backend-contract adapter layer
(api/mappers.ts) translating the backend's snake_case, lean *Out schemas and
{items,total,limit,offset} paging into the UI's camelCase domain types, so
swapping backends only touches the mappers.
- auth: chained login (tokens) + /auth/me (user); refresh on snake_case;
expiresIn optional (reauth is 401-driven, backend sends no TTL)
- streaming: GET /stream/{id}?token= (token query param for <audio>); SW
audio cache route + tests follow the path change (token stays cache-stable)
- library/playlists/likes/admin: correct paths (/tracks not /library/tracks),
page/pageSize<->limit/offset, duration_seconds->durationMs, likes as
append-only POST /likes event-log, admin is_superuser<->role
- downloads/storage: marked provisional (backend routes still stubs)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implement the A8 upload screen against the existing /upload contract:
- UploadResponse type ({track_id, title, already_exists}) + mutation typed to it
- buildUploadFormData helper (single file under field `file`, per FastAPI)
- UploadPage: drag-and-drop + file picker, client-side queue with
concurrency cap (3), per-file status badges, retry on error,
already_exists -> "Already in library", deep-link to A7 metadata editor
- i18n upload.* section (en/ru) incl. "metadata pending" hint
Indeterminate spinner per file; percent progress is a follow-up
(needs an XHR baseQuery — fetchBaseQuery gives no upload progress).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>