SPEC v0.13: M12e outcomes (D-086 to D-095), server schema 3, HANDOFF, prompts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
This commit is contained in:
+16
-14
@@ -3,25 +3,27 @@
|
||||
Living document, rewritten in place (never a second copy). A new orchestrator session reads CLAUDE.md, then this.
|
||||
|
||||
## 1. Where things stand
|
||||
- `main` at `00a14a3` (M8 app integration, second commit) plus the SPEC v0.12 docs commit from `agent-docs-v012` once it lands. History: `5e1be38` plans and prompts, `c885aba` M2c-dev, `76ee957` SPEC v0.3, `fad6fc9` M2b, `fdf2093` M2a, `040bf41` SPEC v0.4, `503cee5` M3, `c28c52b` M5, `982451d` SPEC v0.5, `bd6f10a` M4, `23a2f1e` SPEC v0.6, `ab298ab` M9, `e8ee518` SPEC v0.7, `6b60ef7` M12a, `7fb9f6f` SPEC v0.8, `e499107` M12c, `2e2ebe7` M12b, `d452fa6` UX Kalas categories and tokens, `16c4d01` UX sprint U2, `45eecd1` SPEC v0.9, `bdba9d3` M12d, `3080804` M6, `7bd0aa1` SPEC v0.10, `72f21b2` app debug fakes bindings (temporary, deleted by M8), `e6ad858` app release wiring of `:data` (pre-M8), `b9b061a` M7a, `6ac711d` M7e, `082b8dc` M7d, `31760f9` M7b, `1e42f76` M7c, `a54eb7c` M7f, `3a1bd14` M6b, `d504004` SPEC v0.11, `34a2590` M7g, `369e516` and `00a14a3` M8.
|
||||
- Landed: M0 spec, M1 contracts and vectors (`c6df2ab`), M2a (`fdf2093`), M2b (`fad6fc9`), M2c-dev (`c885aba`), M3 (`503cee5`), M4 (`bd6f10a`), M5 (`c28c52b`), M9 (`ab298ab`), M12a (`6b60ef7`), M12b (`2e2ebe7`), M12c (`e499107`), U1 and U2 (`16c4d01`, owner approved), M12d (`bdba9d3`), M6 (`3080804`), M7a to M7f, M6b (`3a1bd14`), SPEC v0.11 (`d504004`, D-069 to D-080), M7g feature cleanups (`34a2590`), M8 app integration (`369e516` + `00a14a3`, landed 2026-10-18, gate green: BUILD SUCCESSFUL, 710 actionable tasks).
|
||||
- `main` at `90505a8` (M12e launcher icon) plus the SPEC v0.13 docs commit from `agent-docs-v013` once it lands. History: `5e1be38` plans and prompts, `c885aba` M2c-dev, `76ee957` SPEC v0.3, `fad6fc9` M2b, `fdf2093` M2a, `040bf41` SPEC v0.4, `503cee5` M3, `c28c52b` M5, `982451d` SPEC v0.5, `bd6f10a` M4, `23a2f1e` SPEC v0.6, `ab298ab` M9, `e8ee518` SPEC v0.7, `6b60ef7` M12a, `7fb9f6f` SPEC v0.8, `e499107` M12c, `2e2ebe7` M12b, `d452fa6` UX Kalas categories and tokens, `16c4d01` UX sprint U2, `45eecd1` SPEC v0.9, `bdba9d3` M12d, `3080804` M6, `7bd0aa1` SPEC v0.10, `72f21b2` app debug fakes bindings (temporary, deleted by M8), `e6ad858` app release wiring of `:data` (pre-M8), `b9b061a` M7a, `6ac711d` M7e, `082b8dc` M7d, `31760f9` M7b, `1e42f76` M7c, `a54eb7c` M7f, `3a1bd14` M6b, `d504004` SPEC v0.11, `34a2590` M7g, `369e516` and `00a14a3` M8, `cf8ef45` SPEC v0.12, `1470a2f` M12e contract pass, `90505a8` M12e launcher icon.
|
||||
- Landed: M0 spec, M1 contracts and vectors (`c6df2ab`), M2a (`fdf2093`), M2b (`fad6fc9`), M2c-dev (`c885aba`), M3 (`503cee5`), M4 (`bd6f10a`), M5 (`c28c52b`), M9 (`ab298ab`), M12a (`6b60ef7`), M12b (`2e2ebe7`), M12c (`e499107`), U1 and U2 (`16c4d01`, owner approved), M12d (`bdba9d3`), M6 (`3080804`), M7a to M7f, M6b (`3a1bd14`), SPEC v0.11 (`d504004`, D-069 to D-080), M7g feature cleanups (`34a2590`), M8 app integration (`369e516` + `00a14a3`, landed 2026-10-18, gate green: BUILD SUCCESSFUL, 710 actionable tasks), SPEC v0.12 (`cf8ef45`, D-081 to D-085), M12e contract pass (`1470a2f` + `90505a8`, landed 2026-10-18; gates green: Android BUILD SUCCESSFUL, 710 actionable tasks, 906 tests; cargo 253 tests, OpenAPI equality, vectors and migration byte-identity green).
|
||||
- M8 outcome (recorded in SPEC v0.12 as D-081 to D-085): both variants bind `:data` through `di/AppDataModule.kt`, debug fakes module gone (D-081, revises D-041); debug network security config allows cleartext everywhere, the AC-2.8 limit is enforced in `SessionRepositoryImpl.normaliseServerUrl`, pinned-root config behind `kvitt.tls.pinnedRoot` (D-082); FCM registration by Firebase Installation ID, firebase-messaging 26.0.0, no server change (D-083); invite links ignored while paired (D-084); generic-notification, deletion and `sync_batch` taps open Home, push in an expedited WorkManager job (D-085). Not yet run: the end-to-end pairing, offline and push test on the phone and the design-spec §2.7 typeface check (phone was not attached; §3 item 1).
|
||||
- In flight: M12e contract pass (started 2026-10-18; SPEC §11.3 list; proposes D-086 onwards; spec v0.13 next). `agent-docs-v012` (this docs change: SPEC v0.12).
|
||||
- M12e outcome (recorded in SPEC v0.13 as D-086 to D-095): E-3 `invite_kind` and `Pairing`, S-2 only after a `partner` pairing (D-086); admin name in E-12 links via `?from=`, passed on by E-4, S-1 help line names the admin (D-087); `SyncSummary.lastAttemptFailed` (D-088); `Rejection.actor` (D-089); `X-Kvitt-App-Version-Name`, migration 0003, server schema 3, S-20 "Kvitt 1.1" (D-090); `NewRecurringTemplate` (D-091); `UpdateCheck` (D-092, revises D-073); `SettleState.untouched` (D-093, revises D-074); D-027 re-stamp in `:data` (D-094); `PushEvent` in `:core:model`, six screen-hosted snackbar hosts, snackbar resource ids, adaptive vector launcher icon (D-095, revises D-071). 128 px picture size left unchanged by assumption (owner question open, §3 item 5).
|
||||
- Compatibility: the M12e APK cannot pair or list devices against a pre-M12e server (DTOs without defaults, by design), so the server is rebuilt and redeployed before the new APK is installed (SPEC §14.8). Old phones on the new server work.
|
||||
- In flight: `agent-docs-v013` (this docs change: SPEC v0.13).
|
||||
- Both gates exist: Android `tools/gradle-serial.sh check` (from `android/`), server `cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo test` (from `server/`). Both green on `main` as far as reported.
|
||||
- The UX sprint (owner's session in `~/orca/workspaces/kvitt/UX-sprint`, branch `UX-sprint`) has landed; nothing is in flight there.
|
||||
- Importer: the dry run of 2026-10-18 with M12b's mapping reconciled on both real files (Vardag 1746 rows, 0 unmapped; Hemma 37 rows, 0 unmapped). The CSV files stay in `export/` (git-ignored).
|
||||
- Development stack (M2c-dev): running on the Mac, reachable at `http://192.168.1.210:8080`; database at schema 2 since 2026-10-18 (migrated after a backup). Backup `infra/dev/backups/kvitt-20261018T093143Z.sqlite.gz` exists (git-ignored). Its bootstrap pairing code is held by the orchestrator outside the repo and expires 2026-10-21T12:28Z. No scripted partner device exists yet (it needs the admin token, available only after the owner pairs). Firebase project `kvitt-1337` with apps `se.hagstedt.kvitt` and `se.hagstedt.kvitt.debug`; service account `kvitt-fcm`; `infra/secrets/fcm-service-account.json` and `infra/secrets/google-services.json` in the main checkout (git-ignored).
|
||||
- Installed on the owner's phone (SM-S942B): the debug app `se.hagstedt.kvitt.debug` built from `c28c52b`, as of the last report; the M8 build is not installed yet (phone not attached).
|
||||
- Development stack (M2c-dev): running on the Mac, reachable at `http://192.168.1.210:8080`; already rebuilt from `main` `90505a8` on 2026-10-18 (M12e server), database at schema 3, data kept, bootstrap code unchanged. Backups `infra/dev/backups/kvitt-20261018T093143Z.sqlite.gz` (before schema 2) and `infra/dev/backups/kvitt-20261018T130443Z.sqlite.gz` (before schema 3) exist (git-ignored). On this Podman setup `infra/dev/up.sh` alone does not swap in a rebuilt image: run `infra/dev/up.sh --down`, then `infra/dev/up.sh`. Its bootstrap pairing code is held by the orchestrator outside the repo and expires 2026-10-21T12:28Z. No scripted partner device exists yet (it needs the admin token, available only after the owner pairs). Firebase project `kvitt-1337` with apps `se.hagstedt.kvitt` and `se.hagstedt.kvitt.debug`; service account `kvitt-fcm`; `infra/secrets/fcm-service-account.json` and `infra/secrets/google-services.json` in the main checkout (git-ignored).
|
||||
- Installed on the owner's phone (SM-S942B): the debug app `se.hagstedt.kvitt.debug` built from `c28c52b`, as of the last report; the M8 and M12e builds are not installed yet (phone not attached); the dev server is already at M12e, so the M12e debug build can be installed.
|
||||
|
||||
## 2. Open work (one line each, with who owns it: agent or owner)
|
||||
- Land `agent-docs-v012` (orchestrator): SPEC v0.12, D-081 to D-085, HANDOFF, `prompts/README.md`.
|
||||
- M8 on-phone test (orchestrator with the owner, §3 item 1): install the M8 debug build, pair against the dev server, script a partner device with the admin token, run the offline and push checks; then the design-spec §2.7 typeface check (One UI font styles on the S26).
|
||||
- M12e contract pass (in flight): the SPEC §11.3 list, including M8's additions (`PushEvent` to `:core:model`, S-22 licences (OQ-5), detail-screen snackbar hosts, S-7/S-8 snackbar strings as resource ids, launcher icon, the `fid` field on the server).
|
||||
- Land `agent-docs-v013` (orchestrator): SPEC v0.13, D-086 to D-095, HANDOFF, `prompts/README.md`.
|
||||
- On-phone test (orchestrator with the owner, §3 item 1): install the debug build from `main` (M12e; the dev server already runs M12e), pair against the dev server, script a partner device with the admin token, run the offline and push checks; then the design-spec §2.7 typeface check (One UI font styles on the S26).
|
||||
- Remaining contract requests (SPEC §11.3, no plan yet): S-22 licences screen (OQ-5, default not in v1); the server sends the FCM installation id in `fid` instead of `token` (D-083).
|
||||
- Image build for the Pi (M2c-pi or M10): since M4 the arm64 server image needs a C toolchain in the build stage (aws-lc-rs) and CA roots in the distroless image (§10.3); confirm both and one FCM send from the shipped image.
|
||||
- Later cleanup (any Android plan that owns `android/gradle/**`): remove the unused Vico entries from `libs.versions.toml` (D-063).
|
||||
- Plans (SPEC §11.2 v0.12), in dependency order; prompt `prompts/<id>.md`:
|
||||
- M1, M2a, M2b, M2c-dev, M3, M4, M5, M6, M6b, M7a to M7g, M8, M9, M12a, M12b, M12c, M12d, U1, U2: landed.
|
||||
- Now: M12e contract pass.
|
||||
- Plans (SPEC §11.2 v0.13), in dependency order; prompt `prompts/<id>.md`:
|
||||
- M1, M2a, M2b, M2c-dev, M3, M4, M5, M6, M6b, M7a to M7g, M8, M9, M12a, M12b, M12c, M12d, M12e, U1, U2: landed.
|
||||
- Now: the on-phone test (§3 item 1).
|
||||
- Next: M2c-pi Infra on the Pi (blocked on OQ-3; owner: answer OQ-3, grant the Pi, Home Assistant Pi, Gitea and Drive allowances at the start, D-023); M10 First release after M2c-pi (owner: keystore per §10.6, D-024); M11 Import history after M9 and M10 (procedure §10.8 "Production run", D-025; each run must reconcile, D-045).
|
||||
|
||||
## 3. HUMAN steps pending (relay in chat)
|
||||
@@ -48,9 +50,9 @@ Living document, rewritten in place (never a second copy). A new orchestrator se
|
||||
- M2c-pi: OQ-3.
|
||||
|
||||
## 6. Numbering
|
||||
- Decisions: D-001 to D-085 used (D-008 and D-009 unused). Next free: **D-086** (M12e reserved D-086 to D-095).
|
||||
- Spec version: v0.12 written; next **v0.13** (M12e).
|
||||
- Server schema version **2** (migration 0002, landed with M12b); next **3**. Room schema version **1** (`android/data/schemas/se.hagstedt.kvitt.data.db.KvittDatabase/1.json`); M12e may propose **2**.
|
||||
- Decisions: D-001 to D-095 used (D-008 and D-009 unused). Next free: **D-096**.
|
||||
- Spec version: v0.13 written; next **v0.14**.
|
||||
- Server schema version **3** (migration 0003, landed with M12e); next **4**. Room schema version **1** (`android/data/schemas/se.hagstedt.kvitt.data.db.KvittDatabase/1.json`); next **2**.
|
||||
- Sync protocol **1**, push payload **1**.
|
||||
|
||||
## 7. How the work is run
|
||||
|
||||
+14
-4
@@ -31,7 +31,7 @@ entry and the old one a strike-through plus a pointer. The spec is updated in th
|
||||
| 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-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). | 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 |
|
||||
| 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 |
|
||||
| D-028 | 2026-10-17 | Periodic sync work is named `kvitt-sync-periodic` (policy `KEEP`); one-off work keeps `kvitt-sync` (`APPEND_OR_REPLACE`). | M5 report: WorkManager cannot share one unique name between periodic and one-off work. | SPEC §14.4.1, AC-15.3 |
|
||||
| D-029 | 2026-10-17 | A REST call that fails on the network returns `OnlineResult.Failed("network", null)`; `network` is the one allowed code outside §14.3.2. | M5 report: the UI must tell "Could not reach the server" (§9.9) from `Offline` (call not attempted). | SPEC §14.2.4 |
|
||||
| D-030 | 2026-10-17 | Picture version after a removal: E-10 deletes the JPEGs and leaves an empty `<v>-removed` marker in `pictures/<user_id>/`; the next upload uses the highest version on disk plus one, so caches keyed by (`user_id`, `picture_version`) never show a removed picture; the first upload is version 1. | M3 report (`503cee5`): `picture_version` is null after a removal, so the next version needs a record. | SPEC AC-4.4, §10.4, E-10 |
|
||||
@@ -75,10 +75,10 @@ entry and the old one a strike-through plus a pointer. The spec is updated in th
|
||||
| D-068 | 2026-10-18 | New shared string `ledger_settlement_label` ("Payment" / "Betalning", `module=core:design`) as the type label on the second line of a settlement row (design-spec §3.5), added to `docs/ux/strings.csv` beside the other `ledger_settlement_*` rows and to `:core:design`'s `values/strings.xml` and `values-sv/strings.xml`. Also: `SettlementRow` titles may wrap to 2 lines (3 at 200 % font scale), and a chart whose range has no expenses shows only the 0 baseline with no gridline values (design-spec §3.10). | M6 report: `settle_detail_title` belongs to `:feature:settle` and `:feature:account` cannot use it (D-017); the wrap and the empty-chart rendering were not stated in the design spec. | SPEC §13.3 (S-5, S-11), §13.4; `docs/ux/strings.csv` |
|
||||
| D-069 | 2026-10-18 | S-11 readings. Total-card caption "<range> · Hushållet" for the household measure and "<range> · N veckor" in week granularity; for my share the label is "Totalt · Min del", so the caption does not repeat it. Month presets show whole-month ranges ("maj–okt 2026"), custom ranges and week granularity show days. Legend and stacking order: categories by `sortOrder`, accounts in `AccountRepository.all()` order; the order of `AnalyticsState.groups` is unspecified and the UI sorts. "Loading" means `SyncSummary.lastSuccessAt == null` and shows skeleton cards; the design spec has no offline or loading frame for S-11/S-12, so offline looks like loaded. | M7e report (`6ac711d`): the design spec left caption wording, order and the loading condition open. | SPEC §13.3 (S-11), §14.2.4 ("Readings fixed in v0.11") |
|
||||
| D-070 | 2026-10-18 | S-12 readings. A week title follows `docs/ux/strings.csv` ("Vardag, vecka 41"), not "v. 41"; a category or account over the whole range is titled "<name>, <range caption>". No error state on S-11 or S-12 (analytics are local, AC-14.1); S-12 has no loading state. | M7e report (`6ac711d`): design spec and strings disagreed on the week title; the error and loading states of §13.3 do not apply to local analytics. | SPEC §13.3 (S-11, S-12) |
|
||||
| D-071 | 2026-10-18 | Navigation wiring. The top-level screens S-3, S-4, S-11 and S-13 render their own `KvittNavigationBar` and take `onNavigate: (KvittDestination) -> Unit`; the `:app` scaffold draws no second bar; the `NavHost` switches top-level destinations from those lambdas. Navigation-argument keys are constants in each feature (`ARG_EXPENSE_ID`/`ARG_ACCOUNT_ID`, `SettleArgs.SETTLEMENT_ID`/`ACCOUNT_ID`, `DrillDownArgs.KEYS`, settings `"categoryId"`/`"templateId"`), read through `SavedStateHandle`. Result snackbars of S-7, S-9 and account archiving are shown by the screen returned to (Home through its `snackbarHostState`); the account-archived snackbar has an undo action (`common_undo`). | M7a, M7b, M7e, M7f reports and M8's brief: the features built the bar and the argument keys themselves; M8 needs one rule to wire them. | SPEC §13.2, §11.2 (M8), §11.3 (M8) |
|
||||
| D-071 | 2026-10-18 | Navigation wiring. The top-level screens S-3, S-4, S-11 and S-13 render their own `KvittNavigationBar` and take `onNavigate: (KvittDestination) -> Unit`; the `:app` scaffold draws no second bar; the `NavHost` switches top-level destinations from those lambdas. Navigation-argument keys are constants in each feature (`ARG_EXPENSE_ID`/`ARG_ACCOUNT_ID`, `SettleArgs.SETTLEMENT_ID`/`ACCOUNT_ID`, `DrillDownArgs.KEYS`, settings `"categoryId"`/`"templateId"`), read through `SavedStateHandle`. Result snackbars of S-7, S-9 and account archiving are shown by the screen returned to ~~(Home through its `snackbarHostState`)~~ (revised by D-095: six screens host the shared `snackbarHostState` in their own scaffold, `:app` has no overlay host); the account-archived snackbar has an undo action (`common_undo`). | M7a, M7b, M7e, M7f reports and M8's brief: the features built the bar and the argument keys themselves; M8 needs one rule to wire them. | SPEC §13.2, §11.2 (M8), §11.3 (M8) |
|
||||
| D-072 | 2026-10-18 | A new recurring template's "Dag i månaden" defaults to today's day of the month; the start month is the current month if that day is today or later, else the next month (so by default the current month). | M7f report (`a54eb7c`): AC-9.8 gave the start-month rule but no default day. | SPEC AC-9.8, §13.3 (S-19) |
|
||||
| D-073 | 2026-10-18 | "Sök efter uppdatering" (S-22) reports "Du har den senaste versionen" when `UpdateState` is still `None` after `check()`; a network failure cannot be told apart because `check()` returns nothing. Accepted for v1; "give `UpdateRepository.check()` a result" is a contract request for a later contract pass. | M7f report (`a54eb7c`): the UI contract has no result for a manual check. | SPEC AC-16.6 (new), §13.3 (S-22), §14.2.4, §11.3 |
|
||||
| D-074 | 2026-10-18 | `FieldError.field` values are the model property names `amountOre`, `description`, `notes`, `date`, `split`, `accountId`, `categoryId`, `allocations`, as `:core:fakes` and `:data` emit them. Editing a settlement opens with its saved rows counted as user-edited, so changing the amount keeps them; "Föreslå igen" re-proposes. The S-9 info line on untouched accounts derives them from Home balances and may be slightly off while editing (balances include the settlement being edited). | M7c (`1e42f76`) and M7d (`082b8dc`) reports: field names were not fixed; AC-11.6 did not say whether an edit re-proposes; `SettleState` has no untouched-accounts list. | SPEC §14.2.4, AC-11.6, §13.3 (S-9), §11.3 |
|
||||
| D-073 | 2026-10-18 | ~~"Sök efter uppdatering" (S-22) reports "Du har den senaste versionen" when `UpdateState` is still `None` after `check()`; a network failure cannot be told apart because `check()` returns nothing. Accepted for v1; "give `UpdateRepository.check()` a result" is a contract request for a later contract pass.~~ (Revised by D-092: `check()` returns `UpdateCheck`, and S-22 tells up to date from a failure.) | M7f report (`a54eb7c`): the UI contract has no result for a manual check. | SPEC AC-16.6 (new), §13.3 (S-22), §14.2.4, §11.3 |
|
||||
| D-074 | 2026-10-18 | `FieldError.field` values are the model property names `amountOre`, `description`, `notes`, `date`, `split`, `accountId`, `categoryId`, `allocations`, as `:core:fakes` and `:data` emit them. Editing a settlement opens with its saved rows counted as user-edited, so changing the amount keeps them; "Föreslå igen" re-proposes. ~~The S-9 info line on untouched accounts derives them from Home balances and may be slightly off while editing (balances include the settlement being edited).~~ (Revised by D-093: S-9 reads `SettleState.untouched`, nets without the settlement being edited.) | M7c (`1e42f76`) and M7d (`082b8dc`) reports: field names were not fixed; AC-11.6 did not say whether an edit re-proposes; `SettleState` has no untouched-accounts list. | SPEC §14.2.4, AC-11.6, §13.3 (S-9), §11.3 |
|
||||
| D-075 | 2026-10-18 | The quick-row fill of D-060 lives in `:core:design`'s `quickRowCategories(categories, frequent, selected)`; editors pass the raw `frequentCategoryIds[accountId]`; an empty or short list is filled from `sort_order`, and the selected category takes fifth place when absent. Replaces `docs/ux/design-spec.md` §3.7's interim "selected first, then sort_order". | M6b (`3a1bd14`): S-7 and S-19 both need the fill; one helper keeps them identical. | SPEC §14.2.4, §13.3 (S-7), §11.2 (M6b) |
|
||||
| D-076 | 2026-10-18 | Category item labels (quick row and grid) take up to two centred lines; longer custom names end the second line in "…"; single long words still hyphenate ("Entertain-ment"); TalkBack reads the full name. "No ellipsis" (design-spec §3.7) still holds for the default names. | M6b (`3a1bd14`): custom category names (up to 40 code points, §9.6) can be longer than two lines; the default names still fit without an ellipsis. | SPEC §13.3 (S-7); `docs/ux/design-spec.md` §3.7 |
|
||||
| D-077 | 2026-10-18 | Day ranges in captions and summaries are written out with the year ("24 augusti–17 oktober 2026", "1–30 november 2026", "28 december 2025–3 januari 2026"); the period chip and the S-12 title keep the short form "24 aug.–17 okt.". Extends D-065. | M6b (`3a1bd14`, `DateFormatter.dayRangeLong`): M7e needed a long form for the total caption. | SPEC §9.8, §13.3 (S-11) |
|
||||
@@ -90,3 +90,13 @@ entry and the old one a strike-through plus a pointer. The spec is updated in th
|
||||
| D-083 | 2026-10-18 | FCM registration by Firebase Installation ID (firebase-messaging 26.0.0 through the Firebase BoM 35.0.2, google-services plugin 4.5.0). `register()` runs whenever the phone is paired and at each app start while paired; `onRegistered(fid)` sends the id as `fcm_token` with E-17; when the device is revoked the app calls `unregister()`. Needs the manifest meta-data `firebase_messaging_installation_id_enabled`. No server change: FCM HTTP v1 `message.token` accepts installation ids during Firebase's transition. Follow-up request for the server (§11.3 M12e list): send the id in the `fid` field. (M8's D-087, renumbered.) | M8 report (`369e516`): firebase-messaging 26 registers an app instance by its Firebase Installation ID; AC-13.7 described a rotating FCM token. | SPEC AC-13.7, §6.2, §14.6, §11.3 |
|
||||
| D-084 | 2026-10-18 | An invite link (`kvitt://invite` or `https://icehack.asuscomm.com:8443/i/<CODE>`, declared with `autoVerify="false"`) opened while the phone is paired is ignored; the app never re-pairs over live data. (M8's D-088, renumbered.) | M8 report (`369e516`): AC-2.6 did not say what a link does on a paired phone; re-pairing would discard the local copy and its pending changes. | SPEC AC-2.6 |
|
||||
| D-085 | 2026-10-18 | Push handling: an expedited WorkManager job calls `syncForPush` (waited up to 20 s), then builds the notification text from Room. A tap on the generic notification (sync failed, or entity or actor missing locally) opens Home, not the detail; deletions and `sync_batch` also open Home; an unknown `type` or `v` ≠ `1` only syncs. Channel `activity`; notification id = hash of `entity_id`, `sync_batch` uses id `1`. (M8's D-089, renumbered.) | M8 report (`369e516`): AC-13.6 and §14.6 did not say where a generic notification leads or which fixed id `sync_batch` uses. | SPEC AC-13.6, §14.6 |
|
||||
| D-086 | 2026-10-18 | E-3's response gains `invite_kind` (the accepted invite's kind). `SessionRepository.pair` returns `OnlineResult<Pairing(user, inviteKind)>`; a wire value the app does not know is read as `device`. S-2 follows only a `partner` pairing; a member pairing a second phone with a `device` invite goes to Home. Fakes: `FakeScenario.pairingInviteKind`. | M7a and M8 reports (§11.3 list): without the kind, S-2 showed after any first `member` pairing, device invites included. | SPEC AC-2.4, AC-2.10, §13.2, §13.3 (S-2), §14.2.4, §14.3.4 (E-3); `contracts/examples/E-3-invites-accept` |
|
||||
| D-087 | 2026-10-18 | E-12's link is `<public_url>/i/<code>?from=<percent-encoded admin display name>`. E-4 takes the optional `from` and passes it only into `kvitt://invite?server&code&from`; it is never shown on the page (AC-2.5 still holds). An invalid value (not UTF-8, a control character, more than 40 code points) is dropped and the page still opens. `InviteLink` (`PastedInvite.adminName`), `AppLink.Invite`, `PairingScreen(initialAdminName)` and `PairingViewModel.prefill` carry the name; S-1 shows `onboarding_code_help` with it. Older links without a name still parse. | M7a report (§11.3 list): `onboarding_code_help` could not be filled, so S-1 showed only the generic line. | SPEC AC-2.5, AC-2.6, §13.3 (S-1), §14.3.4 (E-4, E-12); `contracts/examples/E-12-create-invite`, `contracts/openapi.yaml` |
|
||||
| D-088 | 2026-10-18 | `SyncSummary.lastAttemptFailed`: true when the last finished cycle started after the last success (or none ever succeeded) and no cycle is running. `SyncStatusPill`, `syncPillVariant` and `syncLineText` drop their `failed` parameter; S-16 derives "failed" as online plus `lastAttemptFailed`. | M6 and M7b reports (§11.3 list): Home passed `failed = false`, so "Sync failed" never showed. | SPEC §13.3 (S-4, S-16), §14.2.4, §11.3 (M6) |
|
||||
| D-089 | 2026-10-18 | `Rejection.actor: User?`, from `updated_by_user_id` of the server's `current` row stored in `sync_rejections.server_current`; S-16's stale copy names the partner when it is null. No protocol change. | M7b report (§11.3 list): the stale copy always named the partner. | SPEC §14.2.4, §14.4.6 |
|
||||
| D-090 | 2026-10-18 | New optional request header `X-Kvitt-App-Version-Name` on every operation (`CommonRules`), stored at E-3 and on the hourly last-seen update; blank or longer than 32 characters is ignored. Server migration `0003_device_version_name.sql` adds `devices.app_version_name TEXT CHECK (NULL or length 1..32)`; server schema **3**, E-1 reports 3, locked contract copy with its byte-identity test, a new v2→v3 data-preserving migration test. `app_version_name` on the Device wire object; `Device.appVersionName`; S-20 shows "Kvitt 1.1". `kvitt-import` expects schema 3. Room schema unchanged at 1 (the phone stores no devices). | M7f report (§11.3 list): S-20 showed the version code ("Kvitt 11") where the design shows the name. | SPEC §8.2, §10, §10.8, §14.1, §14.1.2 (new), §14.3.1, §14.3.3, §14.3.4 (E-1, E-3), §14.8, AC-3.1, §13.3 (S-20), §14.2.4; `contracts/schema/migrations/0003_device_version_name.sql`, examples E-1, E-3, E-7, E-15 |
|
||||
| D-091 | 2026-10-18 | `RecurringRepository.create(NewRecurringTemplate)` takes the template fields without an id; the server mints it (E-28's body never had one) and `create` returns it. The S-19 editor builds `RecurringForm.newTemplate()` for a new template and `template(original, …)` for an edit. | M7f report (§11.3 list): the editor sent `TemplateId("")`. | SPEC §13.3 (S-19), §14.2.4, §14.3.4 (E-28) |
|
||||
| D-092 | 2026-10-18 | `UpdateRepository.check(): UpdateCheck` = `UpToDate` \| `Available(info)` \| `Failed`. A 404 from E-5 means up to date; debug builds report `UpToDate` without a request. S-22 shows `settings_about_up_to_date` or, on failure, `common_could_not_reach_server`. Revises D-073 and AC-16.6. | M7f report (D-073, §11.3 list): S-22 could not tell "up to date" from a network failure. | SPEC AC-16.6, §13.3 (S-22), §14.2.4 |
|
||||
| D-093 | 2026-10-18 | `SettleState.untouched: List<UntouchedAccount(account, payerNetOre)>`: accounts not in the rows where the payer is owed, with nets that exclude the settlement being edited, in `AccountRepository.all()` order; empty for a per-account settle-up. Computed in `:data` and in the fakes. S-9 uses it and no longer reads Home nets. Revises D-074's S-9 reading. | M7d report (D-074, §11.3 list): Home balances include the settlement being edited. | SPEC §13.3 (S-9), §14.2.4 |
|
||||
| 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 |
|
||||
|
||||
Reference in New Issue
Block a user