docs: record 1.1 hard-won knowledge in CLAUDE.md; refresh README
CLAUDE.md: - Download pipeline: leading aesthetic characters in artist names must be trimmed before the iTunes lookup (a missed match cascades into a wrong-duration YouTube grab), and an album tag is always written (defaulting to Singles) so Navidrome and the metadata editor agree. - New Android section covering the traps this release cost the most time on: a BackHandler inside a NavHost destination loses back priority (overlay back must be registered at the app root), overlay state must live in the ViewModel rather than remember/rememberSaveable, bottomBar and ModalBottomSheet inset handling, reusing the Ktor client, global optimistic StarState, and iTunes artists having no artwork. README: offline downloads also cover single songs and work without a server connection; StarState added to the data/ listing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
a80ac3deb0
commit
c5649ac22f
2 files changed
+37
-2
No files matched your search
@@ -56,6 +56,15 @@ Backend runs in Docker (see `docker-compose.yml`); on the deploy host it lives a
|
|||||||
pulled same-titled covers by other uploaders, and wrong Piano Echoes for Japanese tracks.
|
pulled same-titled covers by other uploaders, and wrong Piano Echoes for Japanese tracks.
|
||||||
- **Size cap:** `--max-filesize 30M` on every yt-dlp call; Mittelweg steps also cap at 2×
|
- **Size cap:** `--max-filesize 30M` on every yt-dlp call; Mittelweg steps also cap at 2×
|
||||||
expected duration. Prevents saving a 28-min video (52 MB) when the wrong result slips through.
|
expected duration. Prevents saving a 28-min video (52 MB) when the wrong result slips through.
|
||||||
|
- **Leading aesthetic characters:** artists often style their name with leading punctuation
|
||||||
|
(`.diedlonely`, `†…`). `metadata.js` strips leading non-alphanumerics (`stripLeadSym`) before the
|
||||||
|
iTunes lookup and in `firstWord`. Without it the lookup misses, falls through to a title-only
|
||||||
|
match, and returns an unrelated same-titled track — whose wrong duration then *also* misleads the
|
||||||
|
YouTube duration filter (observed: a 96 s track resolved to a 176 s slowed version). The same trim
|
||||||
|
runs client-side in Android's `ExploreViewModel.search()`.
|
||||||
|
- **Always write an `album` tag.** `ytdlp.js` defaults it to `Singles` when no album is known.
|
||||||
|
Previously no tag was written at all, so Navidrome showed its own "Singles" fallback while the
|
||||||
|
metadata editor showed "[Unknown Album]" — the two never matched and file resolution failed.
|
||||||
- **Duplicate detection** (`metadata.js findExistingTrack`): searches case-insensitively across
|
- **Duplicate detection** (`metadata.js findExistingTrack`): searches case-insensitively across
|
||||||
all album folders under the matching artist folder; compares normalized artist+title ignoring
|
all album folders under the matching artist folder; compares normalized artist+title ignoring
|
||||||
track prefix and release/variant suffix. Handles DJ OKAWARI vs DJ Okawari and "- Single" drift.
|
track prefix and release/variant suffix. Handles DJ OKAWARI vs DJ Okawari and "- Single" drift.
|
||||||
@@ -79,6 +88,32 @@ Backend runs in Docker (see `docker-compose.yml`); on the deploy host it lives a
|
|||||||
- **Playback:** `MusicRepository.streamUrl()` checks `offlineRepo.localAudioPath(id)` first; if the file exists it returns a `file://` URI. This applies to every play path — album, playlist, Offline tab — no special offline mode needed.
|
- **Playback:** `MusicRepository.streamUrl()` checks `offlineRepo.localAudioPath(id)` first; if the file exists it returns a `file://` URI. This applies to every play path — album, playlist, Offline tab — no special offline mode needed.
|
||||||
- **Offline tab:** Library → tab 2 (OFFLINE). Driven by `OfflineViewModel`. Play button on a completed group calls `offlineVm.songsForGroup()` (JOIN query → `DownloadedTrackEntity.toSong()`) then `player.playSongs()`.
|
- **Offline tab:** Library → tab 2 (OFFLINE). Driven by `OfflineViewModel`. Play button on a completed group calls `offlineVm.songsForGroup()` (JOIN query → `DownloadedTrackEntity.toSong()`) then `player.playSongs()`.
|
||||||
|
|
||||||
|
## Android navigation, state & insets (hard-won)
|
||||||
|
|
||||||
|
- **A `BackHandler` inside a NavHost destination loses back priority to the NavHost.** The Erkunden
|
||||||
|
(Apple) artist/album are in-screen *overlays* on the `erkunden` destination, not routes — their
|
||||||
|
`BackHandler` never fired, so one back press popped the whole tab to Home. Fix: the overlay reports
|
||||||
|
its close action upward via `onOverlayBackChange`, and `AppNavigation` registers a **root-level**
|
||||||
|
`OnBackPressedCallback` for it (the same mechanism the NowPlaying overlay always used successfully).
|
||||||
|
Real nav routes (`album/{id}`, `artist/{id}`) are unaffected — `popBackStack()` is correct there.
|
||||||
|
- **Overlay state belongs in the ViewModel, not `remember`.** Anything an overlay shows (e.g. the
|
||||||
|
artist's "all albums" section) is destroyed when the user drills into an album, because the overlay
|
||||||
|
leaves composition. `rememberSaveable` does **not** save you — it only survives for real nav
|
||||||
|
destinations. Hoist it into the ViewModel (see `ExploreViewModel.artistAllAlbums`).
|
||||||
|
- **`bottomBar` insets:** the tab `NavigationBar` absorbs the system-navigation inset. Detail screens
|
||||||
|
have no tab bar, so the MiniPlayer needs `navigationBarsPadding()` itself — otherwise it sits behind
|
||||||
|
the phone's gesture/navigation bar.
|
||||||
|
- **`ModalBottomSheet`:** use `rememberModalBottomSheetState(skipPartiallyExpanded = true)` *and*
|
||||||
|
`navigationBarsPadding()` on the content, or the bottom-most action (e.g. "Herunterladen") opens
|
||||||
|
hidden behind the navigation bar and needs an extra drag-up.
|
||||||
|
- **Reuse the Ktor client.** `MusicRepository.coverArtUrl()/streamUrl()` must go through
|
||||||
|
`ClientHolder.get(creds)`. Constructing a `SubsonicClient` per call spins up (and never closes) an
|
||||||
|
`HttpClient` for *every cover art request* — this was the cause of visible scroll/tab-switch lag.
|
||||||
|
- **Star/favorite is optimistic and global** via `data/StarState.kt`. Toggle through it so every icon
|
||||||
|
(song rows, NowPlaying, context sheet) flips instantly and rolls back if the API call fails.
|
||||||
|
- **iTunes artists have no artwork.** The Search API returns no `artworkUrl100` for artist entities;
|
||||||
|
borrow the cover of one of their albums (`entity=album&limit=1`) — otherwise you get grey circles.
|
||||||
|
|
||||||
## UI / references
|
## UI / references
|
||||||
|
|
||||||
When referencing files in chat, use clickable markdown links, not backticks:
|
When referencing files in chat, use clickable markdown links, not backticks:
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Built with Vite + vanilla ES modules (web) and Jetpack Compose + Media3/ExoPlaye
|
|||||||
- **Playlists** — Create, rename, delete, add/remove tracks, and upload a cover (Web + Android).
|
- **Playlists** — Create, rename, delete, add/remove tracks, and upload a cover (Web + Android).
|
||||||
- **Importer & downloads** — Paste a Spotify / YouTube / YouTube Music / SoundCloud link to download via yt-dlp, with iTunes metadata enrichment and duplicate detection.
|
- **Importer & downloads** — Paste a Spotify / YouTube / YouTube Music / SoundCloud link to download via yt-dlp, with iTunes metadata enrichment and duplicate detection.
|
||||||
- **Metadata editor** (web) — Edit tags + cover art; files are renamed/moved and Navidrome is rescanned on save.
|
- **Metadata editor** (web) — Edit tags + cover art; files are renamed/moved and Navidrome is rescanned on save.
|
||||||
- **Offline** (Android) — Download albums/playlists to private storage; an Offline tab to play and manage them; downloaded tracks play from disk on every screen.
|
- **Offline** (Android) — Download albums, playlists, or single songs to private storage; an Offline tab to play and manage them (works without a server connection); downloaded tracks play from disk on every screen.
|
||||||
- **Audiobookshelf** — Browse and stream audiobooks with chapter navigation, speed control, sleep timer, and progress sync.
|
- **Audiobookshelf** — Browse and stream audiobooks with chapter navigation, speed control, sleep timer, and progress sync.
|
||||||
|
|
||||||
Web is an installable PWA (themes, German UI); Android is native Jetpack Compose + Media3/ExoPlayer with a mini player, media/ABS notifications, and Android Auto. See [CHANGELOG.md](CHANGELOG.md) for the full web/Android parity matrix.
|
Web is an installable PWA (themes, German UI); Android is native Jetpack Compose + Media3/ExoPlayer with a mini player, media/ABS notifications, and Android Auto. See [CHANGELOG.md](CHANGELOG.md) for the full web/Android parity matrix.
|
||||||
@@ -65,7 +65,7 @@ NovaSonic/
|
|||||||
├── android/ # Native Android app (Jetpack Compose + Media3)
|
├── android/ # Native Android app (Jetpack Compose + Media3)
|
||||||
│ └── app/src/main/kotlin/com/novasonic/app/
|
│ └── app/src/main/kotlin/com/novasonic/app/
|
||||||
│ ├── api/ # SubsonicClient, AbsClient, LrcLibClient, ItunesClient
|
│ ├── api/ # SubsonicClient, AbsClient, LrcLibClient, ItunesClient
|
||||||
│ ├── data/ # PrefsStore (DataStore), MusicRepository
|
│ ├── data/ # PrefsStore (DataStore), MusicRepository, StarState
|
||||||
│ ├── offline/ # Room DB, DAO, OfflineRepository, DownloadWorker (offline downloads)
|
│ ├── offline/ # Room DB, DAO, OfflineRepository, DownloadWorker (offline downloads)
|
||||||
│ ├── player/ # PlayerController, MusicService (Media3)
|
│ ├── player/ # PlayerController, MusicService (Media3)
|
||||||
│ ├── ui/
|
│ ├── ui/
|
||||||
|
|||||||
Reference in new issue
Block a user