105 lines
4.6 KiB
Markdown
105 lines
4.6 KiB
Markdown
|
|
# 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`.
|