SPEC v0.15: D-097 chart corners, D-100 Pi bundle, D-101 keystore; M8 phone test, M8b, owner answers
- D-097: chart bars top 4 dp, bottom 2 dp (M8b f0575d3); design-spec §2.9, §3.10 - D-100: no SSH from the Mac to the Pis; M2c-pi delivers infra/pi/ for the owner to run; OQ-3 answered by .env placeholders; revises D-023, keeps D-001 - D-101: kvitt-release.jks at the repository root, git-ignored; revises D-024 - M8 end-to-end test result and M8b fixes in §11.2/§11.3; OQ-5 answered; 128 px picture size not needed (§14.2.4) - HANDOFF and prompts/README rewritten for the current state and numbering Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
This commit is contained in:
+6
-3
@@ -27,8 +27,8 @@ entry and the old one a strike-through plus a pointer. The spec is updated in th
|
||||
| D-020 | 2026-10-17 | No bundled font: the app uses the Material 3 default typeface. No licence step. | PLAN-M6 open point (b); avoids a font licence clearance. | SPEC §6.2, §11.2, §13.4; PLAN-M2b, PLAN-M6, PLAN-U1, PLAN-U2 |
|
||||
| D-021 | 2026-10-17 | `sync_mutations` pruning runs daily at 04:00 Europe/Stockholm (DST-aware), deleting rows whose `received_at` is older than 90 days. | PLAN-M4 open point (c); the spec said only "daily". | SPEC §14.1 (rules text, not the DDL), §14.4.9, §11.2; PLAN-M4 |
|
||||
| D-022 | 2026-10-17 | Development infrastructure (M2c-dev, landed `c885aba`). No new CLI installs: Firebase project and Android apps through the Firebase Management REST API with a `gcloud` access token, no Firebase CLI; `rclone` belongs to M2c-pi. The agent may write git-ignored secrets, `google-services.json` included, under `infra/secrets/`; M8 copies it into `android/app/` with `infra/dev/copy-google-services.sh`. Phone health check against the dev server is a HUMAN step. Gate: `docker compose -f infra/dev/compose.yaml --env-file infra/dev/.env.example config`; `shellcheck $(find infra -name '*.sh')`. As built: dev stack without Caddy or TLS, bind-mounted data folder, the whole `infra/secrets/` mounted read-only at `/secrets` (a missing key file means push disabled), env file `infra/dev/.env.example` (the Pi's stays `infra/env.example` for M2c-pi), `kvitt-server healthcheck` used by compose, backups with `sqlite3 .backup` (§10.9). The server must start when `KVITT_TRUSTED_PROXY` does not resolve. Firebase on `kvitt-1337` with apps `se.hagstedt.kvitt` and `se.hagstedt.kvitt.debug`; service account `kvitt-fcm@kvitt-1337.iam.gserviceaccount.com` with `roles/firebasecloudmessaging.admin` (`roles/firebasemessaging.admin` does not exist). | Owner's tools already on the Mac; M2c-dev report. | SPEC §10 intro, §10.4, §10.7, §11.2, §11.3, §14.7; PLAN-M2c-dev, PLAN-M8 |
|
||||
| D-023 | 2026-10-17 | M2c-pi: key-based SSH to the Kvitt Pi and the Gitea credentials are HUMAN steps; the allowances to act on the Kvitt Pi, the Home Assistant Pi, Gitea and Google Drive are requested from the owner at the start of that sprint; the Gitea Actions workflow lives at `.gitea/workflows/`, owned by M2c-pi; secret files on the Pi (`ha_ssh_key`, `ha_known_hosts`, `rclone.conf`) mode 0600 owned by uid 1000, the uid of the `cert-sync` and `backup` containers; adding `gitea.lan` to `insecure-registries` is its own HUMAN step. | PLAN-M2c-pi open points (a) to (c). | SPEC §10.2, §10.3, §11.2, §11.3, §14.7; PLAN-M2c-pi |
|
||||
| D-024 | 2026-10-17 | Release keystore: `keytool -genkeypair -v -keystore kvitt-release.jks -alias kvitt -keyalg RSA -keysize 4096 -validity 10000`, created by the owner and kept outside the repository (was `android/keystore/release.jks`); `local.properties` holds its absolute path. | §10.6 did not fix the key parameters (PLAN-M10 H-1); a keystore outside every checkout cannot be committed or deleted with a worktree. | SPEC §10.6, §11.2, §11.3; PLAN-M10 |
|
||||
| D-023 | 2026-10-17 | M2c-pi: ~~key-based SSH to the Kvitt Pi and the Gitea credentials are HUMAN steps; the allowances to act on the Kvitt Pi, the Home Assistant Pi, Gitea and Google Drive are requested from the owner at the start of that sprint;~~ (revised by D-100: no SSH from the Mac to the Pis and no agent allowances; the owner runs the `infra/pi/` bundle; the Gitea credentials stay a HUMAN step); the Gitea Actions workflow lives at `.gitea/workflows/`, owned by M2c-pi; secret files on the Pi (`ha_ssh_key`, `ha_known_hosts`, `rclone.conf`) mode 0600 owned by uid 1000, the uid of the `cert-sync` and `backup` containers; adding `gitea.lan` to `insecure-registries` is its own HUMAN step. | PLAN-M2c-pi open points (a) to (c). | SPEC §10.2, §10.3, §11.2, §11.3, §14.7; PLAN-M2c-pi |
|
||||
| D-024 | 2026-10-17 | Release keystore: `keytool -genkeypair -v -keystore kvitt-release.jks -alias kvitt -keyalg RSA -keysize 4096 -validity 10000`, created by the owner ~~and kept outside the repository~~ (was `android/keystore/release.jks`; revised by D-101: at the repository root, git-ignored by `*.jks`); `local.properties` holds its absolute path. | §10.6 did not fix the key parameters (PLAN-M10 H-1); a keystore outside every checkout cannot be committed or deleted with a worktree. | SPEC §10.6, §11.2, §11.3; PLAN-M10 |
|
||||
| D-025 | 2026-10-17 | Production Splitwise import (M11): a backup first; user ids resolved by display name from the database; CSVs copied to the Pi into `/opt/kvitt/import/`, bind-mounted read-only into `server` as `/import`; the import runs as `docker compose run --rm --entrypoint kvitt-import server ...` with the server stopped (`--entrypoint` because the image's entrypoint is `kvitt-server`); CSVs deleted afterwards. The category table stays at `server/crates/kvitt-import/data/splitwise_categories.csv`. | PLAN-M11 open points (a) to (c); PLAN-M9 table location. | SPEC §10.2, §10.8, §11.2, §11.3; PLAN-M2c-pi, PLAN-M9, PLAN-M11 |
|
||||
| D-026 | 2026-10-17 | A 409 `cursor_ahead` on pull is treated as epoch recovery: cursor 0, epoch cleared, every local expense and settlement marked pending with a new `pending_mutation_id`, cycle restarted. | M5 report (`c28c52b`): a cursor ahead of the server means the server lost rows, as after a restore without `rotate-epoch`. | SPEC §14.4.2 |
|
||||
| D-027 | 2026-10-17 | A `clock_skew` rejection gives the row a new `pending_mutation_id` and the row stays pending (the server stored the rejection under the old id and would replay it). Orchestrator addition: on the first sync attempt after a `clock_skew` rejection, the phone re-stamps `updated_at` of each such pending row to its current clock before pushing, so a corrected clock heals the row. ~~The re-stamp is not in `:data` at `c28c52b`: contract request for M8 or a small `:data` follow-up (HANDOFF open work).~~ (Revised by D-094: implemented by M12e as a re-stamp of pending rows stamped later than now, at the start of a cycle with `clock_skew` set.) | M5 report; without the re-stamp a row stamped by a wrong clock would stay rejected after the clock is fixed. | SPEC §14.4.5 |
|
||||
@@ -67,7 +67,7 @@ entry and the old one a strike-through plus a pointer. The spec is updated in th
|
||||
| D-060 | 2026-10-18 | `ExpenseEditorOptions.frequentCategoryIds` holds only categories actually used in each active account, so a list may have fewer than five entries or be empty. Filling up to five from `sort_order` and placing the selected category fifth stays in the editor (UX-9, `docs/ux/design-spec.md` §3.7). Not a contract change. As landed by M12d (`bdba9d3`). | M12d report: D-059's "at most 5, unused ones left out" left open whether the repository or the editor fills the row; keeping the fill in the editor keeps the repository a plain usage count. | SPEC §14.2.4 ("Readings fixed in v0.10"), §11.2, §11.3 |
|
||||
| D-061 | 2026-10-18 | `frequentCategoryIds` has a key for every active account (an empty list when nothing was used there) and no key for an archived account. After use count (descending) and `sort_order` (ascending), ties fall back to the category id (ascending), so the order is identical in Room and in the fakes. | M12d report: without a last tie-break two categories with equal count and `sort_order` (custom categories can share one) could order differently in `:data` and `:core:fakes`. | SPEC §14.2.4 |
|
||||
| D-062 | 2026-10-18 | `CategoryRepository.deletable(id)` returns false for an unknown or already deleted id. Archived categories follow the same AC-7.6 rule as active ones. In `:core:fakes`, `delete` and `deletable` share one rule function. | M12d report: D-059 did not cover unknown, deleted or archived ids; one rule function keeps the fake from offering a delete it then refuses. | SPEC §14.2.4, AC-7.6 |
|
||||
| D-063 | 2026-10-18 | Charts (S-11) are drawn with Compose `Canvas` in `:core:design`, not Vico. No chart dependency is added; ~~Vico stays in `android/gradle/libs.versions.toml` unused and can be removed in a later cleanup.~~ (Vico removed from the catalog on 2026-10-18, `2b4e21c`; SPEC v0.14.) Takes the fallback §6.2 already allowed. As landed by M6 (`3080804`). | M6 report: Vico 3.3.1 cannot give design-spec §3.10's one focusable TalkBack node per bar with its own sentence and tap target, 2 dp gaps between segments, the 10/4 corner radii on the whole bar (not per segment) and a y-axis gutter that grows at 200 % font scale. | SPEC §6.2 (Charts, module table), §11.2, §11.3 |
|
||||
| D-063 | 2026-10-18 | Charts (S-11) are drawn with Compose `Canvas` in `:core:design`, not Vico. No chart dependency is added; ~~Vico stays in `android/gradle/libs.versions.toml` unused and can be removed in a later cleanup.~~ (Vico removed from the catalog on 2026-10-18, `2b4e21c`; SPEC v0.14.) Takes the fallback §6.2 already allowed. As landed by M6 (`3080804`). | M6 report: Vico 3.3.1 cannot give design-spec §3.10's one focusable TalkBack node per bar with its own sentence and tap target, 2 dp gaps between segments, the 10/4 corner radii on the whole bar (not per segment; 4/2 since D-097) and a y-axis gutter that grows at 200 % font scale. | SPEC §6.2 (Charts, module table), §11.2, §11.3 |
|
||||
| D-064 | 2026-10-18 | Material 3 Expressive against material3 1.4.0: `ShortNavigationBar` is public and used. Not usable, with the fallback taken: `MaterialShapes` (Cookie4, Clover4Leaf, Sunny, Cookie9) → rounded 30 %, rounded 16 dp, circle, circle; `ButtonGroup` → `SingleChoiceSegmentedButtonRow` styled per the design spec; `LoadingIndicator` → `CircularProgressIndicator`; `LinearWavyProgressIndicator` → `LinearProgressIndicator`; medium extended FAB → `ExtendedFloatingActionButton` 56 dp high, radius 20; Expressive button sizes → `Button` with `heightIn`; segmented list items → Kvitt's own `SegmentedGroup`; Expressive slider → Kvitt's own split bar with a knob; `MotionScheme.expressive()` and the `MaterialTheme(motionScheme)` overload are internal → Material components keep the default motion and Kvitt components use the design-spec §2.11 springs in `KvittMotion`; `Shapes.largeIncreased` and the other increased shapes are internal → held in `KvittShapes`. Fulfils design-spec §6 open point 5. | M6 report: the design spec names Expressive APIs that material3 1.4.0 (pinned since M2b) does not expose publicly; the spec's rule is a fallback without a design change. | SPEC §6.2 (UI); `docs/ux/design-spec.md` §3.1, §6 |
|
||||
| D-065 | 2026-10-18 | Formatting tables are code: Swedish and English month and weekday names are tables inside `DateFormatter`, not platform locale data, so JVM tests and phone output are identical. Chart axis labels have no dots ("maj, jun, … okt"); list dates keep the Swedish dot ("3 okt.", "25 sep."). File sizes are SI megabytes (10^6 bytes) with one decimal, rounded half up, U+00A0 before "MB" ("8,4 MB", "8.4 MB"). | M6 report: platform locale data differs between the JVM used by tests and Android, and §9.8 gave no file-size format for the update banner. | SPEC §9.8 |
|
||||
| D-066 | 2026-10-18 | Golden layout: one Roborazzi golden per component sheet × light/dark × sv/en (`<sheet>_<lang>_<theme>.png`), plus a 200 % font-scale variant for long states; the content is composed once and language, theme and font scale are switched through a configuration context, so Compose's non-linear font scaling still applies. Feature modules (M7a to M7f) follow the same layout. | M6 report: one layout keeps the goldens reviewable and the feature agents' screenshot tests consistent. | SPEC §6.2 (Screenshot tests) |
|
||||
@@ -101,3 +101,6 @@ entry and the old one a strike-through plus a pointer. The spec is updated in th
|
||||
| D-094 | 2026-10-18 | The D-027 re-stamp as implemented: when `sync_state.clock_skew` is set at the start of a cycle, every pending expense and settlement whose `updated_at` (or a tombstone's `deleted_at`) is later than the phone's clock is re-stamped to now, in one Room transaction before pushing; rows stamped no later than now are untouched. Room schema unchanged. Revises D-027. | M5 report (D-027, §11.3 list): the re-stamp was not in `:data`. | SPEC §14.4.5 step 3 |
|
||||
| D-095 | 2026-10-18 | `PushEvent` lives in `:core:model` (`model/Push.kt`); `:adapter:fcm` keeps `PushListener` and `PushMapping`. Six screens host the shared `snackbarHostState` in their own scaffold (Home, AccountDetail, ExpenseDetail, SettlementDetail, DrillDown, SyncStatus); `:app` has no overlay host. `ExpenseEditorScreen.onSaved(ExpenseId, @StringRes Int?)`, `onDeleted()` without argument on S-7 and S-8; `:app` shows `snackbar_expense_deleted`. Launcher icon: adaptive, vectors only, `mipmap-anydpi/ic_launcher.xml`, background `#9C5238` (light primary), foreground and monochrome `receipt_long` mark at 48 dp, manifest `icon` and `roundIcon`; lint `MissingApplicationIcon` cleared. Revises D-071. | M8 report (§11.3 list): `PushEvent` was in the adapter, the overlay host could cover a FAB or the bottom bar, S-7 and S-8 handed over resolved strings, the app had no launcher icon. | SPEC §6.2, §13.2, §13.3 (S-7, S-8), §14.2.4, §14.6, §11.3 |
|
||||
| D-096 | 2026-10-18 | The server sends the device's stored `fcm_token` (the Firebase Installation ID of D-083) in the FCM HTTP v1 `message.fid` field, no longer in the deprecated `message.token`. No database or wire change: the `devices.fcm_token` column and the E-17 field keep the name `fcm_token`. `contracts/examples/push/*.json` use `fid` (a contract-file change, hence this entry and SPEC v0.14). Landed `c694f78`. Revises D-083's "no server change". | The FCM HTTP v1 reference (projects.messages, last updated 2026-09-08, read 2026-10-18) deprecates `token` with "Use fid instead" and defines `fid` as the Firebase Installation ID target; `token`, `fid`, `topic` and `condition` are mutually exclusive. Live probe from the arm64 image (`docs/spikes/pi-image-arm64.md`): FCM answered "unregistered" for a made-up id, so the field is accepted. | SPEC §14.6, AC-13.7, §11.3; `server/crates/kvitt-server/src/push/fcm.rs`, `contracts/examples/push/` |
|
||||
| D-097 | 2026-10-18 | S-11 chart bars have top corners 4 dp and bottom corners 2 dp on the whole bar, replacing top 10, bottom 4. The 2 dp gaps between segments and the clip of the whole bar (not per segment) are unchanged. Assumption recorded: the owner's "4 outer / 2 inner" means top / bottom. Landed by M8b (`f0575d3`) in `KvittShapes` and `Charts.kt`, goldens re-recorded. | First phone test: the owner found the bar tops too round. | SPEC §6.2 (Charts), §13.4; `docs/ux/design-spec.md` §2.9 (shapes table), §3.10 |
|
||||
| D-100 | 2026-10-18 | No SSH from the Mac to the Pis. M2c-pi produces a self-contained bundle `infra/pi/` (compose file, Caddyfile, env template, install, deploy, bootstrap and backup scripts, a README checklist) that the owner copies to the Kvitt Pi and runs there. The OQ-3 values (Kvitt Pi address, Home Assistant Pi address, SSH port and user, certificate directory, SSH add-on) become placeholders the owner fills in the bundle's `.env`; OQ-3 is answered by this. Where the spec has a Mac script act on the Pi (`infra/deploy.sh`, `infra/publish-apk.sh`), the step becomes a bundle script the owner runs; M2c-pi fixes the layout inside `infra/pi/`, the script names and how images and APKs reach the Pi. M2c-pi runs as the last step before M10, after the app is proven on the phone against the development server. Agents never need allowances for the Kvitt Pi, the Home Assistant Pi, Gitea or Google Drive. Revises D-023 and the M2c-pi plan; D-001 stands. | Owner decision 2026-10-18: no agent access to the home servers; the Pi is set up once, by hand, from a checklist. | SPEC §10 intro, §10.1, §10.10, §11.1, §11.2 (M2c-pi, M10), §11.3, §12 (OQ-3); PLAN-M2c-pi |
|
||||
| D-101 | 2026-10-18 | The release keystore `kvitt-release.jks` (created with the D-024 `keytool` command) lives at the repository root of the main checkout, git-ignored by the `*.jks` pattern of `.gitignore` and never committed; `android/local.properties` references it by absolute path in `kvitt.release.storeFile` with the other three `kvitt.release.*` keys. The owner keeps a copy of the file and its password in their password manager. Revises D-024 ("outside the repository"). | Owner decision 2026-10-18; the `*.jks` ignore rule already keeps the file out of git. | SPEC §10.6, §11.2 (M10), §11.3; PLAN-M10 |
|
||||
|
||||
Reference in New Issue
Block a user