Captures the rules and hard-won details from recent work: no mocked frontend data, two-part versioning, yt-dlp-only download cascade + Mittelweg rule, file naming, the YTM-search limitation for obscure tracks, and edit/delete file resolution. Loaded automatically by Claude Code at session start. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3.6 KiB
3.6 KiB
CLAUDE.md
Guidance for Claude Code when working in this repository.
What this is
NovaSonic — a self-hosted music streaming PWA (Vite + vanilla ES modules) and Android app (Jetpack Compose + Media3/ExoPlayer) for Navidrome / Subsonic servers, plus a Node.js/Express backend that downloads tracks via yt-dlp and proxies APIs.
Critical rules
- Never hardcode or mock frontend data. All library data comes from the Navidrome (Subsonic) API. No placeholder songs/albums/artists, ever.
- Don't commit or push unless asked. When asked, push to the Gitea remote.
- Two-part versioning
MAJOR.MINOR(e.g.0.8) — no patch component. Each release bumps the minor; features and fixes go in the same release. SeeCHANGELOG.md. - Keep the
CHANGELOG.mdPlatform Parity Matrix accurate when adding cross-platform features.
Layout
src/— web PWA (vanilla ES modules). State insrc/state.js(theSobject); Subsonic/iTunes API insrc/api.js; views insrc/views/, UI insrc/ui/.backend/novasonic-server.js— Express API (downloads, metadata edit/delete, events/ABS proxy).backend/lib/—ytdlp.js(download + cascade + ffmpeg tagging),downloader.js,metadata.js(iTunes lookup, file resolution helpers),config.js,jobs.js,romaji.js.android/— native Kotlin app (separate; backend-dependent web features like the metadata editor are not in the Android app).
Commands
npm run dev # Vite dev server (HMR)
npm run build # production build → dist/
npm run preview # preview the production build
Backend runs in Docker (see docker-compose.yml); on the deploy host it lives at
/opt/navidrome/music. Restart after backend changes: docker compose restart novasonic-backend
(this also re-pulls the latest yt-dlp).
Download pipeline (hard-won knowledge)
- Downloads are yt-dlp only (the old squid.wtf/Qobuz backend was removed).
- File naming:
{NN - }Artist - Title.extwith the release suffix stripped from the title (no- Single/- EP). The album folder keeps the full album name. The cleaned title (cleanTitle/searchTitle) drives both the filename and the embedded title tag. - Search cascade (
ytdlp.js): YTM search first (web_musicplayer client, Songs filter param), then regular YouTube; each candidate filtered by duration (±10%, min 10s), title keyword, artist channel, and variant exclusions (remix/cover/sped/…). - "Mittelweg" rule: when the duration is known but nothing matches the duration window, relax only the duration constraint while still requiring the artist channel. If nothing matches, download nothing and fail cleanly with "Nicht eindeutig gefunden — bitte Direktlink im Importer nutzen". Never fall back to a blind first-result grab (it pulled same-titled videos by other uploaders).
- Known limitation: yt-dlp's YTM/YouTube search can't surface very obscure Art Tracks by
ambiguously-named artists (e.g. "MINA") — the browser finds them, yt-dlp's API extraction
doesn't (confirmed across params/clients/deno). For those, the direct link in the importer
(
sourceUrlpath) is the reliable route. - File resolution for edit/delete: Navidrome's stored path may use a different mount root
or a stale stem.
metadata.jsrebases the path and falls back to recursive/stem matching (tolerant of release-suffix mismatch). A ghost Navidrome entry needs a full library rescan.
UI / references
When referencing files in chat, use clickable markdown links, not backticks:
[ytdlp.js](backend/lib/ytdlp.js).