mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-10 15:11:58 +01:00
5.4 KiB
5.4 KiB
AGENTS.md
Guidance for agents (and humans) working in this repository.
What this is
A native Android rewrite of Subtracks, a client for Subsonic-compatible servers (Navidrome, gonic, Airsonic, ...), in Kotlin and Jetpack Compose. The main history still contains the earlier Flutter app; the native app is being built fresh under app/ and ships as com.subtracks.next for side-by-side beta. The aim is a clean, F-Droid-friendly client: free/libre dependencies only, no telemetry.
Environment
Everything comes from the Nix flake devshell; do not install toolchains by hand.
- Enter it with
nix develop, or let direnv load it into your shell (.envrcrunsuse flake). - The devshell provides JDK 21, Gradle 9, the Android SDK (platform 37, build-tools 37.0.0, platform-tools 37.0.1), nushell, navidrome and gonic.
- Build with the devshell
gradle, not./gradlew. CI uses the nix Gradle pinned byflake.lock; the wrapper is kept only for people without Nix.
Commands
- Build:
gradle :app:assembleDebug - Release build:
gradle :app:assembleReleaseonly when build config, R8/ProGuard or dependencies change, and once before a merge; not part of routine verification - Unit tests:
gradle :app:testDebugUnitTest - Lint:
gradle :app:ktlintCheck :app:lintDebug - Format:
gradle :app:ktlintFormat - Integration tests:
./tools/integration-test.nu(starts navidrome and gonic, then runs:app:integrationTest) - Screenshots:
gradle :app:recordRoborazziDebugrenders the*Screencomposables toapp/src/test/screenshots/(gitignored) plus an HTML report inapp/build/reports/roborazzi/, for local review. No golden images are committed andverifyRoborazziDebugis not part of CI.
Testing layout
app/src/test- JVM unit tests (JUnit4, Robolectric, Roborazzi, MockWebServer). Run by:app:testDebugUnitTest.app/src/integrationTest- tests that need real servers. Compiled with the unit test sources but run by the dedicated:app:integrationTesttask, and excluded from every*UnitTesttask. The nushell harness starts the servers for you.
CI
- Forgejo Actions,
.forgejo/workflows/ci.yml, a singlecijob that runs the lint, unit test and integration test steps (one cache restore/save and Gradle warm-up instead of three). - Runs on the
nix-dockerrunner (thelocalhost/nix-ciimage built by the separatenix-homerepo) inside the devshell, restoring the Nix store viacache-nix-actionand caching Gradle and the integration test music. - Pull requests from a fork need approval before the workflow runs.
Conventions
- Prefer self-explanatory code; do not add comments unless the reason cannot be inferred from the code itself (docstrings for functions and exports are fine).
- ktlint (
org.jlleitschuh.gradle.ktlint) enforces style and flags unused imports; rungradle :app:ktlintFormatbefore committing..editorconfigexempts@Composablefunctions from the lowerCamelCase function-naming rule. - Room 3 (
androidx.room3) on AndroidXsqlite-bundled(BundledSQLiteDriver, SQLite 3.50+, FTS5). 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 asFlowfrom Room. Large lists page with Paging 3 over RoomPagingSourcerather than loading the whole table. - Playback is Media3:
PlaybackServiceis aMediaSessionServiceowning anExoPlayer, andPlaybackController(app-scoped Koin singleton) is the only thing the UI talks to. The queue is aqueue_entrieslist of references with optional ranges plus aplayback_cursorrow; 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 byCoverArtRefcache key, not by URL. - UI state lives in
androidx.lifecycle.ViewModel, wired with Koin (koinViewModel()); screens split into a stateful*Routeand a stateless*Screenfor screenshot tests. - UI and playback preferences go in DataStore (
UserPreferences), not Room. - Verify current dependency versions and their compatibility before adding or pinning anything.
Gotchas
- Robolectric runs at SDK 35 while
targetSdkis 37; raising it changes the on-demand Roborazzi renders (there are no committed goldens to re-record). - Room 3 does not map
PagingSourceautomatically: a DAO that returns it needs@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class). - Material icons are not pulled in by
material3; use the BOM-managedmaterial-icons-core/material-icons-extended(frozen at 1.7.8). Extended is large in debug builds but R8 strips unused icons from release. - Robolectric creates a fresh
Applicationper test in one JVM, soSubtracksApp.onCreatestops any running Koin beforestartKoin. applicationIdiscom.subtracks.nextfor the beta; change it tocom.subtracksbefore any store release.- The CI image provides
nix-ld,jq,sqlite,node,zstdand arunneruser thatcache-nix-actionexpects. None of that is needed locally beyond the devshell. - Integration servers: navidrome on 4533 (
admin/password), gonic on 4747 (admin/admin). The test music is cached in.integration/music(gitignored). nix developdoes not source your shell rc files (so tools like atuin are not active); use direnv if you want your normal interactive shell.