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:
+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