# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview **Implausibly** — an open-source Android dashboard app for self-hosted Plausible Analytics CE. - **Package:** `no.naiv.implausibly` - **Stack:** Kotlin, Jetpack Compose, Material 3 - **License:** GPL-3.0-only - **Target:** API 26+ (Android 8.0) - **Distribution:** F-Droid (primary), Forgejo Releases on kode.naiv.no — no Google Play ## Build & Development Commands ```bash # Build (requires gradle wrapper JAR — run once from a machine with Gradle installed) gradle wrapper --gradle-version 8.10.2 # Standard build/test commands ./gradlew assembleDebug ./gradlew test # Unit tests ./gradlew connectedAndroidTest # Instrumented tests ./gradlew lint # Android lint ./gradlew :app:dependencies # Audit dependency tree (must be free of Play Services) ``` ## Architecture Four-layer architecture with unidirectional data flow: ``` UI (Jetpack Compose + Material 3) ↓ observes StateFlow ViewModel (per screen, sealed UiState classes) ↓ calls Repository (single source of truth, coordinates API + cache) ↓ delegates to Remote (Ktor) + Local Cache (SQLDelight) ``` ### Key technology choices | Component | Library | Rationale | |-----------|---------|-----------| | HTTP | **Ktor** | Pure Kotlin, no annotation processing; we only call one endpoint | | Serialization | **kotlinx.serialization** | Compiler plugin, no reflection | | Local DB | **SQLDelight** | SQL-first `.sq` files → typesafe Kotlin; KMP-ready for future iOS | | DI | **Hilt** | Compile-time safe, Android standard | | Preferences | **DataStore** | Non-sensitive prefs (theme, selected site) | | Secrets | **EncryptedSharedPreferences** | API keys via MasterKey + AES-256-GCM (AndroidX Security Crypto / Tink) | | Charts | **Vico 1.x** | Compose-native; uses `CartesianChartHost` + `CartesianChartModelProducer` API | ### Package structure (`no.naiv.implausibly/`) - `data/remote/` — `PlausibleApi` (single `POST /api/v2/query` endpoint), DTOs, auth interceptor - `data/local/` — SQLDelight `.sq` schemas (`CachedStats`, `Instance` tables) - `data/repository/` — `StatsRepository`, `SiteRepository`, `InstanceRepository`, `CacheManager` - `data/` — `ApiKeyStore` (encrypted), `AppPreferences` (DataStore) - `domain/model/` — Domain models decoupled from API DTOs (`AccessMode`, `PlausibleInstance`, `Site`, `DateRange`, `DashboardData`) - `di/` — Hilt modules (`DatabaseModule`, `NetworkModule`) - `ui/setup/` — Onboarding + instance management (add/edit/delete/switch) - `ui/sites/` — Site list / picker - `ui/dashboard/` — Main stats dashboard, components (`StatCard`, `VisitorChart`), sections - `ui/filter/` — Filter bottom sheet - `ui/theme/` — Material 3 theme, typography, colors - `ui/navigation/` — NavHost, routes ## API Integration Everything goes through **one endpoint**: `POST /api/v2/query` on the configured Plausible instance. - Dashboard loads fire ~8 concurrent queries via `coroutineScope { async }` — one failure cancels all - Rate limit: 600 req/hr (generous for dashboard use) - Realtime: same endpoint with `"date_range": "realtime"`, polled every 30s in foreground - Three access modes: full API key, stats-only key, public shared link (no key) - API docs: https://plausible.io/docs/stats-api/query ## Caching (SQLDelight) Cache key = hash of `(instanceId, siteId, queryBody)`. TTL varies by date range: - `realtime` → never cached - `today` → 5 min - `7d`/`30d` → 30 min - `6mo`/`12mo` → 2 hours - Custom past range → 24 hours Show cached data immediately on app open, refresh in background. ## Critical Constraints - **Zero Play Services dependencies** — audit `./gradlew :app:dependencies` before every release. AndroidX Security Crypto uses Tink (Apache 2.0, pure Java), not Play. - **All dependencies must be GPL-3.0-compatible** (Apache 2.0, MIT, LGPL OK; no AGPL, no proprietary) - **F-Droid reproducible builds** — pin all dependency versions in `gradle/libs.versions.toml`, use locked Gradle wrapper - **No telemetry** — zero analytics, nothing phones home - **SPDX header** `GPL-3.0-only` in every source file - **HTTPS enforced** for all network calls; no default certificate pinning (self-hosted users may use custom CAs) ## Gradle Configuration - Version catalog: `gradle/libs.versions.toml` (all versions pinned) - Convention plugins in `build-logic/` (`android.convention`, `compose.convention`) - Gradle 8.10.2, Kotlin 2.1.0, AGP 8.7.3 ## Repository Hosting This project is hosted on Forgejo at `kode.naiv.no`. Use `fj` (Forgejo CLI) for PRs, issues, releases — not `gh`.