diff --git a/AGENTS.md b/AGENTS.md index 2d30c558..234b109c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -43,6 +43,7 @@ Everything comes from the Nix flake devshell; do not install toolchains by hand. - Room 3 (`androidx.room3`) on AndroidX `sqlite-bundled` (`BundledSQLiteDriver`, SQLite 3.50+). Do not reintroduce SQLDelight or the platform SQLite. - Networking uses OkHttp and DOM XML parsing; auth uses the Subsonic token scheme by default. - Use coroutines and `Flow`; library reads are exposed as `Flow` from Room. Large lists page with Paging 3 over Room `PagingSource` rather than loading the whole table. +- The library is mirrored in full at sync and must work offline. Never fetch library metadata on demand for browsing, filtering or sorting; only media bytes (streams, downloads) and images are on-demand. A server-derived sort key must be synced into a local column, not requested lazily when the sort is selected. - Playback is Media3: `PlaybackService` is a `MediaSessionService` owning an `ExoPlayer`, and `PlaybackController` (app-scoped Koin singleton) is the only thing the UI talks to. The queue is a `queue_entries` list of references with optional ranges plus a `playback_cursor` row; keep only a bounded window in memory (never the whole queue, and never load a whole table to build one). Stream URLs are freshly salted per request, so attach artwork by `CoverArtRef` cache key, not by URL. - UI state lives in `androidx.lifecycle.ViewModel`, wired with Koin (`koinViewModel()`); screens split into a stateful `*Route` and a stateless `*Screen` for screenshot tests. - UI and playback preferences go in DataStore (`UserPreferences`), not Room. diff --git a/docs/architecture.md b/docs/architecture.md index e25c76d7..853bdc66 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -94,6 +94,8 @@ The window is also the queue the media notification and Android Auto can browse `sources` and `subsonic_sources` hold the configured servers. Library tables are keyed by `(sourceId, id)` and cascade from `sources`: `artists`, `albums`, `playlists`, `playlist_songs`, `songs`. +The library is mirrored wholesale at sync and must work offline, so metadata is never fetched on demand: everything used to browse, filter or sort a local list has to be present locally. The only on-demand network is media bytes (streams and downloads) and images. A sort whose key is server-derived — say play frequency — therefore has to be synced into a local column, not requested lazily when the sort is selected. + UI and playback preferences live in DataStore (`user_prefs`), not in SQLite: they are not relational, nothing joins against them, and keeping them out of Room avoids a schema migration per preference. Room is for the library only. Every schema version step ships a hand-written `Migration` (`data/db/Migrations.kt`), and there is no destructive fallback, so an upgrade never silently wipes the local mirror. The exported schemas under `app/schemas/` are the source of truth for the migration SQL, and `MigrationsTest` runs each step against a real SQLite connection and asserts the resulting tables, columns and indices.