update the architecture docs for the removed search index

This commit is contained in:
austinried
2026-10-04 00:29:56 +00:00
parent ef5bf40bf7
commit bd23f4369f
2 changed files with 10 additions and 11 deletions
+7 -7
View File
@@ -7,7 +7,7 @@ Subtracks is a native Android client for Subsonic-compatible servers (Navidrome,
- Talk to any Subsonic-compatible server over its REST API.
- Behave well on a poor connection: a local Room mirror, paging, and (later) offline playback.
- F-Droid friendly: free/libre dependencies only, no telemetry, buildable from source without proprietary services.
- `minSdk 24`. The app bundles its own SQLite so behaviour is identical across devices and FTS5 is always available.
- `minSdk 24`. The app bundles its own SQLite so behaviour is identical across devices and does not depend on the platform version.
## Stack
@@ -16,7 +16,7 @@ Subtracks is a native Android client for Subsonic-compatible servers (Navidrome,
| UI | Jetpack Compose, Material 3 |
| Navigation | Navigation Compose |
| DI | Koin, constructor injection |
| Persistence | Room 3 (`androidx.room3`) on AndroidX `sqlite-bundled` (SQLite 3.50+, FTS5) |
| Persistence | Room 3 (`androidx.room3`) on AndroidX `sqlite-bundled` (SQLite 3.50+) |
| Lists | Paging 3 over Room `PagingSource` (local mirror, not remote paging) |
| Networking | OkHttp + DOM XML parsing |
| Async | Kotlin coroutines / `Flow` |
@@ -46,7 +46,7 @@ app/src/main/kotlin/com/subtracks
PlaybackService.kt MediaSessionService hosting ExoPlayer
PlaybackController.kt MediaController wrapper: state, queue, transport
data/
model/Models.kt Room entities, including the search_index FTS5 table
model/Models.kt Room entities
db/ SubtracksDatabase, DAOs, bundled-SQLite builder
prefs/UserPreferences.kt DataStore-backed UI prefs
repo/ SourceRepository, LibraryRepository
@@ -92,7 +92,7 @@ The window is also the queue the media notification and Android Auto can browse
## Data model
`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`. `search_index` is an FTS5 table (trigram tokenizer) over titles, maintained by sync.
`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`.
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.
@@ -106,11 +106,11 @@ Images: grids and lists request a 256 px `getCoverArt` thumbnail; the 120 dp det
## Sync
`SyncService.sync()` is serialized with a mutex. It fetches the whole library first (artists, albums, songs, playlists and their entries), then writes in a single `immediateTransaction`: upserts, deletes rows no longer present on the server (diffed against the DB), rebuilds `playlist_songs`, and rebuilds the FTS index for that source. Because fetching happens outside the transaction, a network failure never leaves a half-applied state.
`SyncService.sync()` is serialized with a mutex. It fetches the whole library first (artists, albums, songs, playlists and their entries), then writes in a single `immediateTransaction`: upserts, deletes rows no longer present on the server (diffed against the DB), and rebuilds `playlist_songs`. Because fetching happens outside the transaction, a network failure never leaves a half-applied state.
## Search
`search_index` is FTS5 with the trigram tokenizer, so `MATCH` supports substring queries. Queries shorter than three characters are rejected by `SearchDao` (below the trigram minimum). Rows are keyed by `(sourceId, type, itemId)`; the DAO scopes results to a source and orders by rank.
Search is a case-insensitive substring filter on the library queries (`instr(lower(column), lower(:search)) > 0` in `LibraryDao`), scoped to the active tab and with the matching rows ordered by the tab's sort. There is no separate search index; the filter runs directly against the library tables.
## Networking and auth
@@ -122,7 +122,7 @@ No analytics or third-party telemetry. Credentials are stored locally in app dat
## Testing
- Unit tests use Robolectric, Roborazzi (screenshots) and MockWebServer (client), plus an in-memory Room database on the bundled SQLite JVM driver (Robolectric's SQLite has no FTS5).
- Unit tests use Robolectric, Roborazzi (screenshots) and MockWebServer (client), plus an in-memory Room database on the bundled SQLite JVM driver (Robolectric's SQLite differs from the bundled one).
- Roborazzi screenshots exercise the stateless `*Screen` composables with fake data; paging screens are fed `PagingData.from(...)`. They are rendered on demand with `gradle :app:recordRoborazziDebug` (into the gitignored `app/src/test/screenshots/`) for local review; no images are committed and nothing verifies them in CI.
- Cover art in the screenshots is generated deterministically in the test and served through Coil's `FakeImageLoaderEngine` (synchronous, and no third-party images are committed); a real `ImageLoader` is installed for the run and reset afterwards. Each image is two or three flat colours in a pattern that identifies its type — stripes for albums, dots for playlists, rings for artists.
- Integration tests run `SubsonicSourceIntegrationTest` against real navidrome and gonic instances, driven by `tools/integration-test.nu`.
+3 -4
View File
@@ -11,10 +11,9 @@ Nix devshell (JDK, Gradle, Android SDK, nushell, navidrome, gonic), Gradle Kotli
Everything needed to mirror a server library locally:
- `MusicSource` abstraction with `SubsonicClient`, `SubsonicXml` and `SubsonicSource`.
- Room 3 schema on bundled SQLite, so the SQLite version and FTS5 are identical on every device.
- Room 3 schema on bundled SQLite, so the SQLite version is identical on every device.
- `SyncService`: fetch first, then a single transaction of upserts with diff-based pruning.
- FTS5 trigram search index over titles.
- Unit tests (client, XML, sync, search), native integration tests against navidrome and gonic, and CI (`unit`, `lint`, `integration`) with warm caches.
- Unit tests (client, XML, sync), native integration tests against navidrome and gonic, and CI (`unit`, `lint`, `integration`) with warm caches.
## Phase 2 - UI vertical slice (done)
@@ -43,7 +42,7 @@ Not done yet in this slice (deliberately): cover-art tonal colour extraction, lo
## Phase 4 - Search, playlists, offline
- Search UI over the FTS5 index (substring, ranked).
- (done) Search: a substring filter on the library queries, scoped to the active tab, with a docked search field and per-tab sort/filter controls.
- Playlist browsing and editing (create/add/remove), syncing changes back to the server.
- Offline mode and downloads.