From 4c85fec4cad940b032da6a6779b40425b38f58de Mon Sep 17 00:00:00 2001 From: Honey Date: Fri, 19 Jun 2026 20:48:29 +0200 Subject: [PATCH] docs: add CLAUDE.md with project conventions and download-pipeline knowledge 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 --- CLAUDE.md | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..2fcf05a --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,67 @@ +# 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. See `CHANGELOG.md`. +- Keep the `CHANGELOG.md` Platform Parity Matrix accurate when adding cross-platform features. + +## Layout + +- `src/` — web PWA (vanilla ES modules). State in `src/state.js` (the `S` object); + Subsonic/iTunes API in `src/api.js`; views in `src/views/`, UI in `src/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 + +```bash +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.ext` with 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_music` player 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** + (`sourceUrl` path) is the reliable route. +- **File resolution for edit/delete:** Navidrome's stored path may use a different mount root + or a stale stem. `metadata.js` rebases 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)`.