Commit Graph
1 Commits
  • Rebuild the UI on Next.js, and give search real metadata
    The old page was hand-written HTML/CSS/JS. That was fine for a search box and
    a list, but the roadmap here is multi-user, an admin dashboard with usage
    graphs, and an import-review queue — all of which get painful in vanilla DOM
    code. Moves to Next.js 16 / React 19 / Tailwind 4 / Framer Motion.
    
    Built as a STATIC EXPORT served by FastAPI. Running `next start` alongside
    uvicorn would add a Node runtime, a second process to supervise, and a second
    thing that can be down while the other is up. There is no use for SSR — every
    view is behind auth and renders from live JSON — so the deployment stays one
    container, one process, one healthcheck. Image is 60MB; Node only exists in
    the build stage.
    
    Search now carries what a music UI needs:
    
    - year and genre, grafted on from /album/{id} concurrently, deduplicated by
      album and cached forever since a release date is immutable
    - 30-second previews, so you can hear a track before committing a Soulseek
      search to it. Not a library player — that is Navidrome's job.
    - autocomplete over songs/albums/artists, debounced and aborted per keystroke
      so a slow response to "rad" cannot land after "radiohead"
    - album and artist pages, with `Get all` to queue a release in one request
    - the accent colour is sampled from the artwork, so the palette follows the
      music. Deezer's CDN sends ACAO:*, which is what makes canvas sampling work
      with no image proxy.
    
    /api/album/{id} deliberately calls Deezer twice: the track list embedded in
    the album response has no track_position, and using the list index instead
    misnumbers every multi-disc release — verified against the White Album, where
    disc 2 track 1 would have been filed as 18.
    
    Schema is multi-user ready ahead of the feature: users table with roles and
    quotas, requested_by on jobs, and the first login becomes admin. Auth is still
    the single-user GitHub allowlist.
    
    CSP survives all of this intact. Next emits inline hydration scripts, which
    script-src 'self' blocks; rather than per-request nonces, their SHA-256 is
    computed at startup from the built page — a static export never varies between
    requests, so hashes need no maintenance and no HTML rewriting. The one
    concession is style-src-attr 'unsafe-inline', because Framer Motion writes its
    pre-hydration state into style="" and without it every element flashes in.
    That permits style attributes only. img-src/media-src now name Deezer's CDN
    explicitly instead of allowing https: wholesale, closing an exfiltration
    channel that survived connect-src 'self'. /_next/* sits behind the session
    too — there is no local login page to style, so the bundle need not be public.
    
    Verified: unauthenticated /→302 and every /api/*→401 including the four new
    endpoints, forged state→400, forged cookie→401, misconfigured auth refuses to
    start, and the production image hydrates with zero CSP violations.
    
    Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>