Files
kvitt/docs/HANDOFF.md
T
jacobh2 400eac9d60 SPEC v0.17: D-103 to D-106 Pi bundle, D-107 S-19 option rows, D-108 instant navigation; HANDOFF, prompts
D-103 the infra/pi/ bundle (compose.yaml, .env.example, Caddyfile, install,
deploy, bootstrap, backup-now, install-apk; APK path via infra/pi/out/apk/;
new .env keys; gate docker compose -f infra/pi/compose.yaml config).
D-104 image names gitea.lan/jacobh2/kvitt-*, the Gitea workflow, Mac
fallback, supercronic, pinned versions. D-105 backup uid 10001 and
rclone.conf ownership (revises D-023 for that file). D-106 cert-sync tar
stream and refusals, backup confirm-then-delete. D-107 S-19 option rows.
D-108 instant navigation (design-spec §2.11). Chips cleanup and the
Dockerfile CARGO_BUILD_JOBS argument recorded without an id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 21:56:55 +02:00

16 KiB
Raw Blame History

Handoff: state of the work

Living document, rewritten in place (never a second copy). A new orchestrator session reads CLAUDE.md, then this.

1. Where things stand

  • main at 4755fe0 (chips cleanup) plus the SPEC v0.17 docs commit from agent-docs-v017 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, 03b7184 SPEC v0.13, 2b4e21c Vico catalog entries removed (D-063), c694f78 server FCM target in fid (D-096), 1e404cc Pi image arm64 check (docs/spikes/pi-image-arm64.md), fd60170 SPEC v0.14, f0575d3 M8b UI fixes from the first phone test, 09f474f FcmMessageBody.fid in :data (D-096 follow-up), e7d963a SPEC v0.15, b481fab M8c goldens at 1x in three variants (D-098), 64f5706 M8c S-22 licences row removed (D-099), 7c453d8 every PNG through oxipng, 57f984f R8 release build, 3c68594 M8d editor rows (D-102), 3a3720b SPEC v0.16, 5e027df M2c-pi Pi bundle (D-103 to D-106), 15397e4 M8e S-19 option rows (D-107), 965bb5d server Dockerfile CARGO_BUILD_JOBS argument, ed1b130 M8f instant navigation (D-108), 4755fe0 unused editor chips removed.
  • 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), SPEC v0.13 (03b7184, D-086 to D-095), Vico removal (2b4e21c), FCM fid target (c694f78, D-096), Pi image arm64 check (1e404cc: C stage, CA roots and an OAuth plus FCM call from the arm64 image confirmed on the Mac; running on the Pi stays M2c-pi's), SPEC v0.14 (fd60170, D-096), M8 on-phone test (2026-10-18, passed), M8b (f0575d3, D-097), FcmMessageBody.fid (09f474f), SPEC v0.15 (e7d963a, D-097, D-100, D-101); landed today after v0.15: M8c (b481fab, 64f5706, 7c453d8; D-098, D-099), the R8 release build (57f984f), M8d (3c68594, D-102); SPEC v0.16 (3a3720b); then the M2c-pi bundle (5e027df, D-103 to D-106), M8e (15397e4, D-107), the Dockerfile CARGO_BUILD_JOBS argument (965bb5d), M8f (ed1b130, D-108) and the chips cleanup (4755fe0).
  • M2c-pi bundle outcome (SPEC v0.17): infra/pi/ with compose.yaml, .env.example, Caddyfile, install.sh, deploy.sh <tag> [--skip-backup] (backup before every deploy, /opt/kvitt/previous-tag), bootstrap.sh, backup-now.sh, install-apk.sh (D-103); images gitea.lan/jacobh2/kvitt-server|kvitt-cert-sync|kvitt-backup, Gitea workflow .gitea/workflows/build-images.yaml (runner host, secret kvitt_cicd, tags v1.0.N + latest, CARGO_BUILD_JOBS=1), infra/build-images.sh as Mac fallback, supercronic instead of crond (D-104); backup uid 10001, rclone.conf owned 10001 (D-105); cert-sync tar stream for both key variants, backup confirm-then-delete (D-106). Gate at landing: shellcheck clean on 18 scripts, compose config valid, cert-sync arm64 image builds; rehearsed on the Mac with Podman (cert-sync install, renew, skip, refuse; Caddy reload; Authorization redacted; TLS 1.1 refused; backup upload, lsf confirmation, delete after confirm, failed-upload retry; healthcheck red at 26 h; APK prune keeps 5). Not verifiable from the Mac: the add-on's authorized_keys options, the Gitea workflow run, pulls from gitea.lan, the router forward, Drive, real Docker compose.
  • M8e, M8f and cleanup outcome (SPEC v0.17): S-19's account, payer and split are three fixed EditorOptionRows (D-107); navigation is instant, no NavHost transitions (D-108); AccountChip, PayerChip, SplitChip removed (4755fe0), S-9 keeps DateChip in EditorChipRow.
  • 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).
  • M8 end-to-end test (2026-10-18, dev server, owner's S26): pairing with the bootstrap code OK; a scripted partner joined through a second admin device paired with kvitt-server pair-device (the CLI has no partner-invite subcommand: small ops request for a later server plan); Home showed the account; the offline expense showed the pending marker and synced after reconnect (pending 0, the server has the row); the owner's device registered fcm_token and app_version_name; a push by fid was delivered and the notification tap worked. Still unverified on the phone: the status-bar fix (next install) and the design-spec §2.7 typeface check (the owner looked, no complaint).
  • M8b outcome (SPEC v0.15): invite-code field holds the raw code, hyphens as a VisualTransformation (cursor bug fixed, unit test); quick-row selected badge drawn above the ripple clip; KvittTopAppBar pads for the status-bar inset (windowInsets parameter), KvittScaffold follows; chart bars top 4, bottom 2 dp (D-097); 221 goldens re-recorded. FcmMessageBody.fid in :data followed (09f474f).
  • M8c, R8 and M8d outcome (SPEC v0.16): goldens <name>_sv_light, _sv_dark, _en_light plus _sv_light_200, at 1x (w411dp-h<height>dp-mdpi), oxipng-compressed: 1143 PNGs / 131 MB to 865 / 26 MB, mock-ups 36.8 to 26.0 MB (D-098, revises D-066); S-22 has no licences row, settings_about_licences gone (D-099, OQ-5 closed); release APK R8-shrunk, about 11 MB (was 43 MB), mapping.txt to be kept beside the APK by the publish script (SPEC §10.6); release signing verified (alias kvitt, RSA 4096, v2); S-7 options as four fixed rows EditorOptionRows, connected buttons 8 dp side padding (D-102).
  • Owner answers 2026-10-18: "boende" keeps suggesting travel (no change); the 128 px picture size is not needed; OQ-5 "no, hide the button" (implemented by M8c); the account page split bar stays as designed; M2c-pi becomes an owner-run bundle (D-100); the keystore lives at the repository root (D-101); the Gitea push waits for fewer goldens and a history rewrite, no LFS.
  • 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: the owner confirmed it is not needed (single 512 size, SPEC §14.2.4).
  • 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-v017 (this docs change: SPEC v0.17, D-103 to D-108), an AC-coverage audit (its report to be committed later as docs/validation/ac-coverage.md). Then a one-off history rewrite (drop the superseded PNG blobs; no clone exists) and the first push to origin (owner authorised, no confirmation needed), which also triggers the Gitea Actions workflow.
  • Remote: origin on Gitea exists and the SSH key works; the golden reduction (M8c) has landed; the history rewrite and the first push follow this docs pass (owner authorised 2026-10-18, no further confirmation). The owner created the Gitea secret kvitt_cicd and the Drive token (infra/secrets/rclone-drive-token.json, turned into infra/secrets/rclone.conf with [gdrive] type, scope, token; the folder id stays RCLONE_FOLDER_ID in .env; both git-ignored); the cert-sync key infra/secrets/ha_ssh_key exists.
  • 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 branch UX-sprint and its worktree ~/orca/workspaces/kvitt/UX-sprint were removed on 2026-10-18 (fully merged).
  • 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; rebuilt on 2026-10-18 with the M12e server and the fid target (a push by fid arrived in the M8 test), database at schema 3, data kept. 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. The owner's phone paired with the bootstrap code on 2026-10-18; a scripted partner exists, joined through a second admin device paired with kvitt-server pair-device (tokens held by the orchestrator outside the repo). 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, paired against the dev server for the M8 test on 2026-10-18; the next install carries M8b to M8f.

2. Open work (one line each, with who owns it: agent or owner)

  • Land agent-docs-v017 (orchestrator): SPEC v0.17, D-103 to D-108, HANDOFF, prompts/README.md, design-spec §2.11, §3.7 and S-19.
  • AC-coverage audit (agent, running): report to docs/validation/ac-coverage.md, committed later.
  • History rewrite (drop superseded PNG blobs; no clone exists), then the first push to Gitea origin (orchestrator; owner authorised, no confirmation needed); the push triggers the Gitea Actions workflow (§3 item 1).
  • Install the next debug build and have the owner look (§3 item 2); build a signed release for the smoke taps (§3 item 3).
  • Owner runs the M2c-pi bundle on the Kvitt Pi (§3 item 4); record the add-on key variant (A/B) in infra/README.md and the router-forward answer.
  • Cleanup (agents of the owning modules, no plan yet): 20 unused strings R8 found (editor_next, home_notification_rationale, settle_empty_settled, home_sync_status_tag_*, …) could be deleted by their modules; optional localeFilters en+sv.
  • Ops request (no plan yet): a kvitt-server subcommand for a partner invite, so a scripted partner needs no second admin device.
  • Plans (SPEC §11.2 v0.17), in dependency order; prompt prompts/<id>.md:
    • M1, M2a, M2b, M2c-dev, M3, M4, M5, M6, M6b, M7a to M7g, M8 (phone test passed), M8b to M8f, M9, M12a to M12e, U1, U2, R8 release build, M2c-pi bundle: landed.
    • Next: the owner runs the M2c-pi bundle on the Kvitt Pi (D-100); M10 First release after it (keystore per §10.6, D-101); 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)

  1. After the first push: tell me whether the Gitea Actions run ("Build Kvitt images") succeeded and how long it took.
  2. Next install on the phone (yes/no each): screen changes are instant, with no fade or slide; plus the earlier checks: the title bar sits below the status bar; the quick-row selected badge is whole; the invite-code field cursor stays after the last character; the expense editor shows Konto, Datum, Betalt av, Delning as four rows that do not move when a value changes; the recurring editor shows Konto, Betalt av, Delning as three such rows; "Household" fits its half of the Analytics filter bar.
  3. Release build smoke taps: the app opens; S-1 or Home renders; Settings, About shows the version; add one expense and watch it sync.
  4. Pi, after the app is proven: the nine steps of infra/README.md "Pi: what the owner does":
    1. Provision: Raspberry Pi Imager, Raspberry Pi OS Lite (64-bit), user, SSH key, time zone; static DHCP lease in the router.
    2. Docker Engine from Docker's Debian arm64 repository; docker run --rm hello-world prints "Hello from Docker!".
    3. Home Assistant, Settings, Add-ons, the SSH add-on, Configuration, authorized_keys: add the cert-sync key as variant A (restricted tar command); if the add-on refuses it, variant B (plain key). Question: which variant was accepted, A or B?
    4. On the Mac: infra/pi/.env from .env.example, every CHANGE_ME replaced; secrets staged into infra/pi/secrets/.
    5. scp the bundle to the Pi, remove infra/pi/secrets on the Mac; on the Pi ssh-keyscan the Home Assistant host into secrets/ha_known_hosts (at least 1 line).
    6. ./install.sh on the Pi; expected end "Kvitt is running."; then remove ~/kvitt-pi/secrets.
    7. Router, WAN, Virtual Server / Port Forwarding: TCP 8443 to the Kvitt Pi's 8443; on the phone with Wi-Fi off, https://icehack.asuscomm.com:8443/health shows "status":"ok" without a certificate warning. Question: yes/no?
    8. /opt/kvitt/backup-now.sh ends with "last-success: "; /opt/kvitt/bootstrap.sh prints the admin invite code (first install only).
    9. New versions: /opt/kvitt/deploy.sh v1.0.N after the Gitea workflow pushed that tag.

4. Owner questions (asked, unanswered)

  • None. OQ-3 is answered by placeholders (D-100); OQ-5 answered and implemented (D-099).

5. Paused (waiting on something outside the repo)

  • Nothing.

6. Numbering

  • Decisions: D-001 to D-108 used except D-008 and D-009. Next free: D-109.
  • Spec version: v0.17 written; next v0.18.
  • 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. Both unchanged by v0.17.
  • Sync protocol 1, push payload 1.

7. How the work is run

See CLAUDE.md › Orchestration. HUMAN steps in reports are relayed in chat by the orchestrator; the owner does not read them in files.