docs: record locked tech-stack decisions

This commit is contained in:
2026-04-24 11:47:24 +02:00
parent 0c87978547
commit 2e595705fe

View File

@@ -113,6 +113,8 @@ A mobile-first meal planning app for a small household — pick recipes for the
## Key Decisions ## Key Decisions
### Product & scope
| Decision | Rationale | Outcome | | Decision | Rationale | Outcome |
|----------|-----------|---------| |----------|-----------|---------|
| KMP with Compose Multiplatform for UI | iOS-primary + Android secondary + Desktop/Wasm optional; single codebase for 90%+ of UI | — Pending | | KMP with Compose Multiplatform for UI | iOS-primary + Android secondary + Desktop/Wasm optional; single codebase for 90%+ of UI | — Pending |
@@ -126,6 +128,52 @@ A mobile-first meal planning app for a small household — pick recipes for the
| Nutrition is informational only in v1 | Keep scope tight; tracking/goals are a natural v2 if usage patterns justify | — Pending | | Nutrition is informational only in v1 | Keep scope tight; tracking/goals are a natural v2 if usage patterns justify | — Pending |
| Mockup is functional spec only, not visual spec | Visual direction is changing (Liquid Glass); logic is mature and worth mining | — Pending | | Mockup is functional spec only, not visual spec | Visual direction is changing (Liquid Glass); logic is mature and worth mining | — Pending |
### Client tech stack (composeApp)
| Decision | Rationale | Outcome |
|----------|-----------|---------|
| Navigation: Jetpack Navigation Compose (CMP port) — `org.jetbrains.androidx.navigation:navigation-compose:2.9.x` | JetBrains-official recommendation on kotlinlang.org; type-safe routes via `@Serializable`; works across Android/iOS/Desktop/Wasm; skill transferable to Android | — Pending |
| Architecture: ViewModel + StateFlow + method-per-action | Standard modern pattern; matches JetBrains/Google samples; lowest ceremony; upgrade individual screens to sealed-event onEvent only when they grow complex | — Pending |
| DI: Koin — `koin-core`, `koin-compose`, `koin-compose-viewmodel` | De facto KMP standard; smoothest `koinViewModel()` integration with Jetpack Nav back-stack scoping; no codegen; small surface to learn | — Pending |
| Local DB: SQLDelight 2.x | Most mature KMP DB; Wasm-ready (hedge for future Compose-for-Web target); raw SQL is a transferable skill; clear migration story via .sq files | — Pending |
| HTTP client: Ktor Client | Same team as server; first-class KMP; shared serialization config | — Pending |
| Serialization: kotlinx.serialization (JSON) | Standard; works everywhere; pairs with Ktor and SQLDelight | — Pending |
| Date/time: kotlinx.datetime | Standard; SQLDelight adapters available | — Pending |
| Logging: Kermit (Touchlab) | KMP-native logger; simple API; optional Crashlytics/Sentry bridges | — Pending |
| Image loading: Coil 3 (`io.coil-kt.coil3:coil-compose`) | First-class Compose Multiplatform support; modern API | — Pending |
| Key-value settings: `com.russhwolf:multiplatform-settings` | Small prefs (last tab, theme toggles) that don't belong in SQLDelight | — Pending |
| Glass/blur effects: Haze (`dev.chrisbanes.haze:haze`) | Purpose-built for glass UI in CMP; handles content capture + efficient re-blur; multiplatform | — Pending |
| Mobile OIDC: AppAuth (Android) + ASWebAuthenticationSession wrapper (iOS), exposed via KMP interface | Platform-native OAuth flows; no cross-platform auth library mature enough yet for this in 2026 | — Pending |
### Server tech stack
| Decision | Rationale | Outcome |
|----------|-----------|---------|
| Server framework: Ktor Server 3.x | Same team as client HTTP; Kotlin-native; fits homelab deployment; coroutines throughout | — Pending |
| Database: Postgres | Homelab-friendly (Docker); JSONB for meal-entry extras; room to grow; standard skill | — Pending |
| SQL: Exposed DSL | Kotlin-backend standard; type-safe SQL builders; JSONB first-class; strong tutorial trail with Ktor. Avoid Exposed's DAO (active record) API | — Pending |
| Migrations: Flyway | Industry-standard numbered `V__.sql` files; auto-apply on startup; works with any JDBC-backed stack | — Pending |
| Token validation: `io.ktor:ktor-server-auth-jwt` | Built-in JWKS support with caching + rotation; direct integration with Authentik's OIDC endpoint | — Pending |
### Sync
| Decision | Rationale | Outcome |
|----------|-----------|---------|
| Strategy: last-write-wins with server-assigned `updated_at` per row | Household of 2 has negligible concurrent-edit risk; simplest to implement and debug; upgradeable per-table to op-log later if hurt | — Pending |
| Transport: HTTP polling (2030s when foreground) + pull-to-refresh + debounced push after local writes | Sufficient freshness for a household; SSE is a v2 enhancement if polling feels laggy | — Pending |
| API shape: REST, versioned `/api/v1`, two sync endpoints (`POST /sync/push`, `GET /sync/pull?since=...`) plus catalog + households/invites CRUD | Versioning leaves room to evolve; minimal surface area; read-mostly catalog is heavily cached on client | — Pending |
| Server-side data model: `users`, `households`, `memberships`, `invites` + household-scoped tables carrying `household_id`, `updated_at`, `deleted_at` | Supports household sharing + invites, JIT user provisioning from OIDC `sub`, soft deletes for sync | — Pending |
### Build & module structure
| Decision | Rationale | Outcome |
|----------|-----------|---------|
| Gradle version catalog (`gradle/libs.versions.toml`) | Single source of truth for versions; standard KMP practice | — Pending |
| Convention plugins (`build-logic/` module) from day 1 | Centralizes Kotlin/Compose/test config; educational payoff; small upfront cost | — Pending |
| Keep template modules: `composeApp/`, `iosApp/`, `server/`, `shared/` — no feature modules in v1 | Feature modules don't pay off until ~10 features or multiple devs; flat is clearer at this scale | — Pending |
| `shared/commonMain` holds: domain models + API DTOs only (no UI, no HTTP, no DB) | Keeps shared dep graph minimal; both client and server depend on `shared/` | — Pending |
| `composeApp/commonMain` package layout: `app/ navigation/ ui/{theme,components,screens/{recipes,planner,pantry,shopping}} data/{local,remote,repository} domain/` | Groups by UI concern + data layer; resists premature modularization | — Pending |
## Evolution ## Evolution
This document evolves at phase transitions and milestone boundaries. This document evolves at phase transitions and milestone boundaries.
@@ -144,4 +192,4 @@ This document evolves at phase transitions and milestone boundaries.
4. Update Context with current state 4. Update Context with current state
--- ---
*Last updated: 2026-04-23 after initialization* *Last updated: 2026-04-24 after initial tech-stack discussion*