SW doesn't reliably intercept <img> requests (no-cors mode). Use a
thin AuthImg component that loads images via axios (which has the
Bearer token) and displays them as blob URLs. Audio streaming still
works via SW.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a Service Worker that intercepts /api/* requests and injects the
Bearer token. This allows <audio> and <img> elements to use direct
URLs instead of downloading entire files as blobs first.
- Audio now streams progressively (no full download before playback)
- Cover art loads via regular <img src> (SW adds auth header)
- Remove blob-based preloadStream, fetchCoverBlob, useCoverUrl hook
- Register SW in main.tsx, token synced via postMessage
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- GET /api/me/recent endpoint returning last 50 play events with track
and artist info
- RecentPlays modal component with time-ago display
- "Recent plays" button in user dropdown menu
- Clicking a track in history starts playback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend (Rust API):
- Add users and play_events tables (migration 0005)
- Extract full user identity from JWT (sub, username, email, name)
and pass AuthUser via request extensions to all handlers
- Auto-upsert user in background on every authenticated request
- POST /api/tracks/:slug/play endpoint to record play events
- Allow POST method in CORS
Frontend (Node player):
- Call recordPlay() when a track starts playing
- Add user profile avatar with dropdown menu (name, email, sign out)
- Pass user info from App through FurumiPlayer to Header
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cover images were loaded via <img src> which doesn't include the
Authorization header, resulting in 401 from the Rust API. Now covers
are fetched through axios as blobs and displayed via object URLs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>