From 415f9f5efd5bf4de64a3a8a4c7defb0e76860e12 Mon Sep 17 00:00:00 2001 From: Jacob Hagstedt Date: Wed, 21 Oct 2026 21:07:21 +0200 Subject: [PATCH] CLAUDE.md: web client, three gates, generated web files, new server address Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B --- CLAUDE.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 4eeb461..42c598f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,7 +2,7 @@ Operating guide for AI agents and humans working on **Kvitt**. This is the only working-rules file; it says how to work here, not what the product is. For the product, read `SPEC.md`. It was seeded on 2026-10-17 from the Ebb project's rules after a year of running Opus agents in parallel; keep what fits, delete what does not, but keep one file. -**The product in one paragraph.** Kvitt is a Splitwise-style shared-expense app for exactly two people, the owner (Jacob, admin) and his spouse, in Sweden. A native Android app (Kotlin, Compose) keeps a full offline copy of the household's expense accounts, expenses, settlements and audit log in Room and syncs with a small Rust server (axum, SQLite) that runs in Docker on a Raspberry Pi 3 at home, reachable at `https://icehack.asuscomm.com:8443`. Money is SEK in integer öre. The phone computes balances, settle-up proposals and spending analytics itself; the server stores, syncs, schedules recurring expenses, sends push notifications and hosts the APK for in-app updates. Everything else is in `SPEC.md`. +**The product in one paragraph.** Kvitt is a Splitwise-style shared-expense app for exactly two people, the owner (Jacob, admin) and his spouse, in Sweden. A native Android app (Kotlin, Compose) keeps a full offline copy of the household's expense accounts, expenses, settlements and audit log in Room and syncs with a small Rust server (axum, SQLite) that runs in Docker on a Raspberry Pi 3 at home, reachable at `https://kvitt.suorra.duckdns.org` (port 443, behind Nginx Proxy Manager). A web client (SPEC §15) with the same features is served by the server under `/app/`. Money is SEK in integer öre. The phone computes balances, settle-up proposals and spending analytics itself; the server stores, syncs, schedules recurring expenses, sends push notifications and hosts the APK for in-app updates. Everything else is in `SPEC.md`. --- @@ -27,6 +27,7 @@ docs/ux/ the UX sprint deliverables (SPEC §13) docs/spikes/, docs/validation/ what the device and the data showed (reports are deliverables) android/ the Android app: one Gradle project (settings.gradle.kts, gradle/libs.versions.toml), modules as in SPEC §14.7 server/ the Rust backend: one Cargo workspace, crates kvitt-domain, kvitt-server, kvitt-import (SPEC §14.7) +web/ the web client: one npm project (Vite, React, TypeScript), served by the server under /app/ (SPEC §15) infra/ docker compose stack for the Pi (caddy, server, cert-sync, backup), image build scripts, ops runbooks tools/ gradle-serial.sh, clean-builds.sh, install-git-hooks.sh, git-hooks/, dependency-report/latest.py ``` @@ -83,7 +84,7 @@ Two phones: the owner's Samsung Galaxy S26 and the spouse's Galaxy S26+, both An Everything builds **locally on this MacBook**. No hosted CI, no remote build service. -**Two projects, two gates.** The Android app is the Gradle project in `android/`; the server is the Cargo workspace in `server/`. A change that touches both runs both gates. +**Three projects, three gates.** The Android app is the Gradle project in `android/`; the server is the Cargo workspace in `server/`; the web client is the npm project in `web/`. A change that touches more than one runs every gate it touches; a change to `contracts/openapi.yaml` runs all three (the server's OpenAPI test, the Android contract-example tests and the web's generated types). **JDK.** Gradle runs on JDK 25 (current LTS): `android/gradle/gradle-daemon-jvm.properties` asks for 25 and `android/gradle.properties` points Gradle at Homebrew's `openjdk@25` (auto-download off). The shell's `java` is a macOS stub or a newer non-LTS: run Gradle as `PATH=/opt/homebrew/opt/openjdk@25/bin:$PATH tools/gradle-serial.sh ` and **never set `JAVA_HOME`**. Do not install another JDK or change the pin without asking. @@ -95,13 +96,16 @@ Everything builds **locally on this MacBook**. No hosted CI, no remote build ser ../tools/gradle-serial.sh installDebug # install the debug app on the attached phone # Server gate, from server/: cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo audit && cargo test # THE server gate +# Web gate, from web/ (npm ci once per checkout; Node 22.18 or later, Homebrew node on this MacBook): +npm run check # THE web gate: generated-file freshness, tsc, eslint, prettier, vitest (contract vectors included), vite build # Either directory: tools/install-git-hooks.sh # once per checkout tools/clean-builds.sh [--dry-run] # delete Gradle build output in every checkout and worktree (disk) python3 tools/dependency-report/latest.py # version drift of android/gradle/libs.versions.toml ``` -* **`tools/gradle-serial.sh check` (Android) and `cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo audit && cargo test` (server) are the only gates.** Every check a plan asks for must be reachable from one of them (the OpenAPI match test and the test-vector tests included). Report gates with the real result lines (BUILD line, actionable tasks, test counts from fresh XML only). Never report a check as passing that you did not run in this session. +* **`tools/gradle-serial.sh check` (Android) and `cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo audit && cargo test` (server) and `npm run check` (web) are the only three gates.** Every check a plan asks for must be reachable from one of them (the OpenAPI match test and the test-vector tests included). Report gates with the real result lines (BUILD line, actionable tasks, test counts from fresh XML only). Never report a check as passing that you did not run in this session. +* **Generated web files.** A change to `contracts/openapi.yaml` regenerates `web/src/api/schema.d.ts` (`npm run api:types` in `web/`) and a change to `docs/ux/strings.csv` regenerates the web dictionaries (`npm run i18n:import`), in the same landing; the web gate fails when they are stale. * **One Gradle build at a time.** Parallel builds exhaust memory. Always `tools/gradle-serial.sh`; it waits on a lock in `/tmp` and then runs `android/gradlew` of your worktree. * **Disk.** Three agents plus the main checkout can fill a disk. `~/.gradle/init.d/cache-retention.gradle.kts` (user-level, outside the repo) keeps `~/.gradle/caches` at 7 days with cleanup on every build; Gradle 9 accepts build-cache retention only there. Run `tools/clean-builds.sh` after removing a worktree or when free space is under about 5 GB. Deleting `~/.gradle/caches/build-cache-1` and old `~/.gradle/caches/` folders is allowed; the next build is a slow full one. An agent that hits "no space left on device" loses only uncommitted work: free space and resume it, do not respawn. * **Version drift:** run the dependency report at the start of every plan. A library more than one stable minor behind gets a line in the plan's report; a toolchain component more than one line behind triggers an upgrade plan first. @@ -124,6 +128,7 @@ Enforced by an architecture check inside `check` where possible. Fill in from SP * **Licences first:** no vendor binary and no model file is committed, bundled or downloaded by a build before the owner has cleared its licence. * **Server layering:** `kvitt-domain` has no I/O and depends only on `serde` (`serde_json` only as a dev-dependency for the vector tests; ids are plain strings); `kvitt-server` and `kvitt-import` depend on it, never the reverse. Money and split rules exist only in `kvitt-domain` and `:domain`, and both pass the same `contracts/test-vectors/*.json`. * **No `unwrap()`/`expect()` in server non-test code**, start-up included; no `unsafe`; errors map to the error codes of SPEC §14.3. Enforced per crate by `#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))]` at every crate root, `main.rs` included (D-007); a new crate adds the same line. +* **Web layering:** `web/src/app`, `api`, `components`, `lib`, `design`, `i18n` are shared and owned by W0; a feature lives in `web/src/features//` with its own `index.ts` routes and `i18n/{sv,en}.json` (`web_` keys). Features import shared code, never each other. Money and split rules in `web/src/lib` pass the same `contracts/test-vectors/*.json` as the Rust and Kotlin domains. ---