Commit Graph

43 Commits

Author SHA1 Message Date
jacobh2 3d9eea8235 M8: app snackbars through showKvittSnackbar; account-archived undo with common_undo
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 14:28:02 +02:00
jacobh2 cb832a7cac M8: app integration — navigation, :data bindings in both variants, FCM adapter, push notifications, hardening
:app binds every :core:model interface to :data in debug and release (AppDataModule, D-085); the
debug fakes module is gone. NavHost over all feature screens with the session gate (S-1, S-2, S-18),
bottom destinations from each screen's own bar, result snackbars, deep links (kvitt://invite and the
/i/<CODE> landing link) and notification taps. :adapter:fcm maps payload v1 to PushEvent and registers
by Firebase Installation ID (firebase-messaging 26, D-087). Push work runs in WorkManager: expedited
sync waited up to 20 s, then the notification from local data or the generic text. WorkManager uses
KvittWorkerFactory, Coil uses :data's ImageLoader, SyncTriggers on start and foreground, update check
in release only. Network security configs (debug cleartext, D-086; pinned-root variant), release
signing from local.properties, notification permission after the first sync.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 14:25:46 +02:00
jacobh2 3dda414ec2 M7g: feature cleanups after the design-system fix pass
Resource-backed snackbars (showKvittSnackbar) in account, onboarding and
settings; SettingsScaffold replaced by KvittScaffold + SettingsMessageEffect.
KvittButton(loading) for S-6 saving and S-16 Synka nu. S-10 missing state
disables Synka nu offline (connectivity in the ViewModel, new
detail_missing_offline goldens). S-6 system back asks to discard unsaved
changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 11:45:40 +02:00
jacobh2 f4dc10c7ae SPEC v0.11: M7 wave and M6b outcomes (D-069 to D-080), HANDOFF, prompts
Records the readings of M7a to M7f (b9b061a, 6ac711d, 082b8dc, 31760f9,
1e42f76, a54eb7c) and the M6b design-system fix pass (3a1bd14): S-11/S-12
captions, titles and states; navigation wiring and result snackbars (§13.2);
new template day default (AC-9.8); manual update check (AC-16.6); FieldError
field names and settlement editing (AC-11.6); quickRowCategories; two-line
category labels; long day ranges (§9.8); amounts open with two decimals
(§9.6); string keys (strings.csv 578 rows); "Synka nu" online only (§9.9).
§11 marks M7a-M7f and M6b landed, M8 and M7g started; §11.3 lists contract
requests for a later contract pass (M12e). Docs only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 11:44:13 +02:00
jacobh2 b24c8b5ba3 M6b: core:design fixes after the M7 wave
Bugs: viewerPercentOf reads basis points (Samples in bp); FullSplitPicker and the
split labels use editor_split_full_on_you ("Allt på dig"); AmountInput.of restores
money with both decimals (AmountInput.percent for ratio fields), replacing M7c's
moneyInput; quickRowCategories does the D-060 fill itself; KvittScaffold resolves
resource-backed snackbars (showKvittSnackbar) while shown.

Gaps: ConfirmDialog optional and styled body; KvittButton loading form (M7a uses it,
ProgressBottomButton removed); InviteCodeField wraps after a group, placeholder,
imeAction; EmptyState icon tile and actionEnabled; DateFormatter.monthName and
dayRangeLong; AnalyticsFilterBar periodOwnRow; segmentedItem builder; screen tokens
in KvittDimens (OnboardingDimens removed); common_ok, common_undo; module-proposed
keys recorded in strings.csv; quick-row labels end in an ellipsis on line two.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 11:35:06 +02:00
jacobh2 c4ab35f66e M7f: feature settings, S-13 to S-15 and S-19 to S-22
Hub, profile (photo picker), categories (drag reorder, editor with D-059 delete reasons), recurring
expenses (list and S-7-style editor with schedule, AC-9.8 default start), devices, invites and about,
against the :core:model interfaces. Scenarios and scripted online failures in fakes/settings.
84 states screenshot-tested in sv/en x light/dark (336 goldens); state-holder unit tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 11:04:32 +02:00
jacobh2 0b140aa3ad M7c: feature expense, S-7 editor and S-8 detail with timeline
Expense editor (S-7) and expense detail (S-8) in :feature:expense against the
:core:model interfaces and :core:design components: defaults (AC-8.6), keypad
input (§9.6), the four split modes with live preview (AC-8.5, AC-8.7, AC-8.8),
category suggestion with the picked-by-hand flag (CS-2, CS-3, CS-7), quick row
from frequentCategoryIds (D-059, D-060), inline errors, delete and discard
confirmations; detail with hero, facts, timeline, recurring, deleted and missing
states. Fake scenarios in fakes/expense, view-model tests and 127 goldens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 10:54:28 +02:00
jacobh2 0e1d99df93 M7b: feature home and account (S-4, S-5, S-6, S-16, S-17)
:feature:home: Home with balance hero, sync pill, accounts, archived section,
empty and first-sync states, update banner and "Tillåt installation" dialog;
sync status with pending rows, rejections of every §14.4.6 reason and the
clock-skew banner. :feature:account: account detail with header, ledger by
month, overflow menu, archive/unarchive/delete and their errors; account
editor with name, percent fields (two decimals), slider, presets, preview,
name_taken/validation errors and offline state. Fake scenarios in
fakes/home and fakes/account; full-screen Roborazzi goldens for every state
in sv/en × light/dark (200 % for long states); ViewModel unit tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 10:49:55 +02:00
jacobh2 7380e50ae3 M7d: feature settle, S-9 settle up and S-10 settlement detail
SettleScreen (global, per account, editing) and SettlementDetailScreen with
Hilt ViewModels over the :core:model interfaces; proposals, remaining,
warnings and enablement come from SettlementRepository. Fake scenarios in
fakes/settle/, 19 states x sv/en x light/dark goldens (+ 200 % for long),
unit tests for amount input and both state holders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 10:46:54 +02:00
jacobh2 ea7d494ffa M7e: feature analytics (S-11, S-12) with fake scenarios and screenshot tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 10:44:45 +02:00
jacobh2 1ea752cd80 M7a: feature onboarding (S-1, S-2, S-3, S-18) with fakes, unit and screenshot tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 10:41:52 +02:00
jacobh2 64539324e8 app: release Hilt wiring of :data (pre-M8)
AppBuildInfo from BuildConfig for both variants (buildConfig enabled); the
release source set includes DataBindingsModule (D-041), so features'
@HiltViewModels resolve in release as they do against the debug fakes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 10:39:04 +02:00
jacobh2 a41f240873 app: debug-only Hilt bindings of every :core:model interface to the fakes (temporary until M8)
Features' @HiltViewModel classes need bindings in :app's graph; until M8 binds :data, debug builds bind
FakeRepositories(FakeScenarios.empty). Debug source set only; M8 deletes the file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 10:35:17 +02:00
jacobh2 9ee67d4c64 SPEC v0.10: M12d and M6 outcomes (D-060 to D-068), Dining label, ledger_settlement_label
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 10:32:06 +02:00
jacobh2 7730a31af6 M6: design system in :core:design (Kalas muted, design-spec §2 and §3)
KvittTheme with every Material and Kvitt colour role light and dark, the 14
category colours with containers, chart and avatar colours, type scale with
tabular amount styles, spacing, shapes (stable fallbacks: MaterialShapes is not
in Material 3 1.4.0), elevation and motion springs; 191 Material Symbols
Rounded vector drawables (33 category tokens filled and outline, UI icons);
253 shared strings from docs/ux/strings.csv in values and values-sv with
plurals; pure-Kotlin MoneyFormatter, DateFormatter (ISO weeks) and AmountInput
with unit tests; every component of design-spec §3 with TalkBack semantics,
including the ledger rows and editor fields (D-019) and Canvas charts; a
preview catalogue in the debug source set; 212 Roborazzi goldens (49 sheets x
light/dark x sv/en plus 16 at 200 %). The two :app empty-screen goldens are
re-recorded because they render the real theme now (PNG only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 09:30:49 +02:00
jacobh2 557af7e00a M12d: frequentCategoryIds and CategoryRepository.deletable (UX-9, D-059)
ExpenseEditorOptions.frequentCategoryIds per active account (up to five active
categories by non-deleted expense count in the account, ties by sort_order) and
CategoryRepository.deletable(id) (AC-7.6), from Room queries in :data and the
same rule in :core:fakes. Room schema unchanged (version 1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 08:35:35 +02:00
jacobh2 e6768121d6 SPEC v0.9: M12b and M12c outcomes (D-055 to D-058), U2 values, UX-9 (D-059, M12d)
- D-055 build-time copy of the keyword table, D-056 parseCategoryKeywords,
  D-057 enum declaration orders, D-058 E-7 example shows server_version
  15 and 16 and its test compares them again.
- D-051 fulfilled: §9.7 hex values from docs/ux/design-spec.md §2.3;
  design-spec.md binding, strings.csv the source of string resources.
- D-059: ExpenseEditorOptions.frequentCategoryIds and
  CategoryRepository.deletable; new milestone M12d with PLAN-M12d and
  prompts/M12d.md; M7a-f depend on M6 and M12d.
- suggestCategory readings (null when other is archived), §11.2 landed
  marks and the 2026-10-18 dry run, dev stack on schema 2, PLAN-M12b
  SQLite 3.51.x, design-spec §6 notes in PLAN-M6/M7c/M7f; HANDOFF rewritten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 08:34:45 +02:00
jacobh2 6da6b1b023 UX sprint U2: design-spec.md, screen catalogue, strings.csv (owner approved)
Kalas (muted) design spec with final tokens, components and every screen S-1 to
S-22 in all states; 80-frame screen catalogue with 155 PNGs in docs/ux/screens/;
strings.csv with D-017 module prefixes (558 rows, en and sv); colour tokens
renamed to the landed contract names slate and amber; fixed category ids;
proposed-decisions.md with the final §9.7 hex values and UX-9. Supersedes
tokens.md, components.md, screens.md and the U1 Kalas PNGs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U2TeboHB8AtnzTrt3bWFzY
2026-10-18 08:27:51 +02:00
jacobh2 0006bd79be UX: Kalas with 14 default categories, 33 icons, plum and sand colour tokens
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U2TeboHB8AtnzTrt3bWFzY
2026-10-18 08:27:51 +02:00
jacobh2 4d3c343f16 UX: owner answers round 2: no suggestion caption, most-often history, 14 default categories, finer import mapping
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 08:27:51 +02:00
jacobh2 4c1a4b5a9d UX: category suggestion rule and evidence from the Splitwise export; importer mapping gap
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 08:27:51 +02:00
jacobh2 01b36e77b2 UX: Kalas muted palette and Roboto, owner answers and proposed decisions
Owner picked direction 3 Kalas with calmer colours. The palette, category and chart
colours are muted (closest category pair CIEDE2000 13.7 light, 12.3 dark), the page
uses the Material 3 default typeface (D-020), S-7 shows the category chosen from the
description, and a before/after section compares bright and muted. Owner answers,
findings and proposed decision and spec text are in docs/ux/proposed-decisions.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 08:27:51 +02:00
jacobh2 b72ac5bb9e UX sprint U1: three directions, IA, screens, flows, components, tokens, strings
Three visual directions (Skärgård, Kvitto, Kalas) as self-contained HTML pages with
every SPEC §13.3 screen in light and dark, Swedish, plus PNG exports and the exporter
script. Direction-independent docs: ia.md, screens.md, flows.md, components.md,
tokens.md (values for all three directions), strings.csv (key,en,sv,notes), and the
comparison with a recommendation in directions/README.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 08:27:51 +02:00
jacobh2 6eaccec7ac M12b: category contract v2 on the server (schema 2, fourteen defaults, finer import mapping)
- migrations/0002_categories_v2.sql, byte-identical to contracts/schema/migrations/0002_categories_v2.sql
  (SPEC §14.1.1), with an identity test; SCHEMA_VERSION = 2 (E-1 reports it, the importer's check follows).
- Seed test on a fresh database (the fourteen rows of AC-7.2, server_version 11 to 14) and an upgrade test of a
  v1 database in use (0001 applied through the migrator, then users, a custom, a renamed and an archived default,
  an account, a recurring template, expenses): every earlier row unchanged, the four rows above the old
  sequence, foreign_key_check empty, schema_version 2, a pull from the old cursor returns exactly the four rows.
- DefaultCategoryKey with the fourteen keys (wire enum and contracts/openapi.yaml); icons 33 and colours 14
  (§9.7) in validation.
- The two category examples M12a held back (E-26 response-default, sync/pull-page-categories-v2) and its
  contracts-check patch for them and the enum.
- Importer: splitwise_categories.csv per the §10.8 v0.8 table (D-053), DEFAULT_KEYS of fourteen, synthetic
  fixtures for the finer mapping (Insurance/Försäkringar to insurance among them), a schema-1 refusal test.
- Test harnesses seed users at server_version 15/16 (after the fourteen seeds); pull counts 10 -> 14.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 08:25:22 +02:00
jacobh2 b09f3220a1 M12c: category contract v2 on Android (14 defaults, suggestCategory)
- :core:model: DefaultCategoryKey in AC-7.2 order (14 keys: housing, insurance, children, fees added),
  ColorToken + SLATE, AMBER, CategoryIcons.ALL + key, shield, account_balance (33),
  ExpenseRepository.suggestCategory(accountId, description); ContractTest updated.
- :domain: parseCategoryKeywords (pure, shared by :data and :core:fakes) with a parser test; the
  suggestCategory records and rule unchanged, suggest_category vectors pass.
- :data: PlatformUnicodeText (java.text.Normalizer, Character), CategoryKeywords parsed once from the
  Java resource that copyCategoryKeywords copies from contracts/category-keywords.csv at build time
  (missing file fails the build), ExpenseRepositoryImpl.suggestCategory over the Room snapshot and the
  DataStore last-used category; tests: byte identity and the 14.8 rules of the bundled table, the
  UnicodeText binding, and Room/Robolectric cases for history, first token, ties, keywords, fallback,
  archived categories, deleted expenses, both users and accounts, null account.
- :core:fakes: the 14 defaults with the schema v2 ids, fake suggestCategory through :domain with its own
  build-time copy of the table; tests.
- Room schema unchanged (version 1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 08:21:59 +02:00
jacobh2 d39eb2c79b SPEC v0.8: UX-session decisions (D-047 to D-054), category contract v2, M12 plans
Records the owner's UX-sprint decisions UX-1 to UX-8 of docs/ux/proposed-decisions.md:
D-047 direction Kalas muted, D-048 three tabs, D-049 category suggestion (§9.11,
suggestCategory, contracts/category-keywords.csv, suggest_category.json), D-050 S-1
display-name field, D-051 colour token values from Kalas (slate, amber TODO(U2)),
D-052 smaller UX rules, D-053 finer Splitwise mapping, D-054 fourteen default
categories with server schema 2 (migration 0002, §14.1.1). Adds milestone group M12
(PLAN-M12a/b/c, prompts), the CLAUDE.md UX-sprint note, and rewrites HANDOFF.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 08:14:12 +02:00
jacobh2 aa765bbb48 Contracts for 14 default categories and the category suggestion (UX-3, UX-8)
- contracts/schema/migrations/0002_categories_v2.sql: server migration 0002 rebuilds categories with the
  14-key default_key CHECK, inserts housing, insurance, children and fees with server_versions taken from
  the global sequence, sets schema_version 2. server-v1.sql unchanged.
- contracts/category-keywords.csv: the keyword table of the suggestion (148 entries, from docs/ux).
- contracts/test-vectors/suggest_category.json: 65 Kotlin-only cases of CS-4 to CS-6.
- contracts/examples/E-1-health: schema_version 2.
- tools/contracts-check.py: reference suggestCategory, keyword-table checks, 0002 load checks on a fresh
  and an in-use database, plain icon/color check, new icon and colour tokens.
- android/domain: suggestCategory (CS-4 to CS-6, code points, platform NFC and categories through
  UnicodeText), vector runner for suggest_category, eight vector files.
- server/crates/kvitt-domain/tests/vectors.rs: suggest_category is Kotlin-only.

The OpenAPI DefaultCategoryKey extension and the two new category examples are held back until the
server's wire types know the new keys (M12b).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 08:10:57 +02:00
jacobh2 86e08a12cf SPEC v0.7: M9 importer decisions (D-044 to D-046), category table, HANDOFF rewritten
D-044 adds nine Swedish Splitwise category names to the SPEC §10.8 table and
splitwise_categories.csv. D-045 records the reconciliation against the totals
rows; D-046 the importer CLI semantics and exit codes. M9 marked landed at
ab298ab, the M11 rehearsal recorded, server/README.md and prompts/README.md
updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 07:51:04 +02:00
jacobh2 50339aef6d M9: kvitt-import Splitwise importer (SPEC §10.8, FR-17)
CLI per §10.8 with --help, --dry-run and a summary; English and Swedish
headers with member columns matched by name; CSV quoting; payments as
settlements with one allocation; trailing totals row used to reconcile
the account balance (mismatch rolls back, exit 4); non-SEK rows abort
unless --fx; committed category table data/splitwise_categories.csv;
idempotent re-runs through import_key; rows validated with the server's
push checks, audited with source import, no pushes; one BEGIN IMMEDIATE
transaction. Tests over synthetic fixtures only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 07:45:37 +02:00
jacobh2 d9dd5848fe SPEC v0.6: M4 decisions (D-035 to D-039), HANDOFF rewritten
- D-035 unusable mutation_id/entity_type/entity.id fails the whole push with 400; other shape errors per mutation.
- D-036 0 mutations is batch_too_large ("outside 1 to 100").
- D-037 never-synced delete runs full step-8 validation; current on every rejection; created_at kept; canonical ids.
- D-038 FCM retries: one send plus three retries (1/5/25 s), 401 drops the token, other 4xx not retried.
- D-039 pruning strictly older than 90 days; tasks::start returns AppState with the push queue, SqlAuditWriter default.
- Clarifications: step 8 order, negative since, pull read transaction, FCM JWT kid and cache, token clearing only if
  unchanged, unusable credentials, scheduler clock jumps, rotate-epoch exit codes, /i/ empty code, image build needs
  a C toolchain and CA roots (dev image rebuilt 2026-10-18).
- PLAN-M4 pruning wording, prompts/README status, HANDOFF at bd6f10a.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 07:33:46 +02:00
jacobh2 15056dc7df M4: sync pull and push (E-31, E-32), audit writer and diff, FCM sender, push batching, scheduler, pruning, rotate-epoch
- src/sync/: pull with UNION ALL paging and cursor_ahead; push processing of SPEC §14.4.4 steps 1 to 12 in one
  BEGIN IMMEDIATE with a savepoint per mutation, LWW with delete-wins, tombstones, clock_skew, replay from
  sync_mutations, sync_batch for more than 3 notifiable changes; 90-day pruning.
- src/audit/: SqlAuditWriter and the §14.4.7 diff (now the default in AppState::new).
- src/push/: bounded PushHub (100, drop oldest), FCM HTTP v1 client with service-account JWT (RS256, jsonwebtoken
  on aws-lc-rs) and token cache, sender with 1/5/25 s retries and token clearing; disabled without credentials.
- src/tasks/: start installs push and spawns the 06:00 generation (and at start) and 04:00 pruning loops on the
  injectable clock.
- cli: rotate-epoch; serve uses into_make_service_with_connect_info; stale doc comments updated; /i/ empty code
  gets the HTML 404.
- tests: sync_api, push_fcm (local stub of Google's endpoints), scheduler_daily; skeleton routes test now asserts
  no route is 501.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-17 22:27:30 +02:00
jacobh2 853c038c58 SPEC v0.5: M3 and M5 decisions (D-026 to D-034, D-040 to D-043), HANDOFF rewritten
Records the decisions proposed by M3 (503cee5) and M5 (c28c52b). M5's proposals
D-030 to D-033 are renumbered D-040 to D-043; D-035 to D-039 are reserved for M4.
Adds the D-027 clock-skew re-stamp rule, M5's contract requests for M8 (PLAN-M8,
SPEC 11.3), the connect-info note for M4 (PLAN-M4), the picture size note (PLAN-M6),
and updates prompts/README status.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-17 22:21:43 +02:00
jacobh2 8f0e156b38 M5: Android data layer: DAOs, Ktor client and DTOs, token store, sync engine and workers, repositories, APK updater
DAOs on M2b's Room 3 entities and KvittDatabase v1 (schema 1.json unchanged), DTOs for every wire
object and endpoint body with a round-trip test over all 80 contract examples, the Ktor client with the
section 14.3.1 headers and 14.3.2 error mapping, the Keystore-encrypted token store in auth.preferences_pb,
the sync engine of section 14.4 (push batches, pull pages, epoch recovery, rejections, clock skew, 401 and
426 handling) tested with MockEngine including AC-15.6, WorkManager triggers, every :core:model repository,
the APK updater behind an installer interface, Coil pictures, Hilt modules, Room migration test
infrastructure. Fakes: the five settled TODO(spec) comments resolved per SPEC v0.4 (settlement row in an
account's ledger carries that account's allocation).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 22:14:53 +02:00
jacobh2 7869ec48d8 M3: server core API (E-3 to E-30, E-33), landing page, pictures, rate limiting, recurring generation, bootstrap and pair-device
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 22:01:09 +02:00
jacobh2 6dce4c73a9 SPEC v0.4: M2a and M2b decisions, handoff to the real state
Records the decisions proposed in the M2a (fdf2093) and M2b (fad6fc9)
reports: D-005 OpenAPI equality normalisations and CommonRules, D-006
bp_sum_not_10000 token, D-007 per-crate no-unwrap deny, D-010 Roborazzi,
D-011 IncompleteCustomRangeException, D-012 Room 3 with M2b's entities and
1.json as a contract file, D-013 JDK 25 Robolectric flags, D-014
failOnNoDiscoveredTests (D-008, D-009 unused). Clarifications in §6, §8.2,
§8.6, §9.6, §10.3, §10.4, §11, §14.2, §14.3, §14.5, §14.7, §14.8 and
AC-11.7. PLAN-M5 builds on the existing entities; v0.4 readings added to
PLAN-M3, M5, M7b to M7f. HANDOFF rewritten: next free D-040, spec v0.5,
server schema 2, Room schema 2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 21:40:42 +02:00
jacobh2 06f48ef9e9 M2a: server workspace, kvitt-domain, skeleton routes, OpenAPI match, Dockerfile
Cargo workspace (kvitt-domain, kvitt-server, kvitt-import), Rust 1.99.1 pinned.
kvitt-domain passes every Rust vector and adds next_occurrence_date (D-015).
kvitt-server: config, SQLite pool (WAL, §8.9 pragmas), migration 0001 identical to
the contract schema, error type for all 25 codes, auth extractor and protocol
layer, all 33 routes (E-1/E-2 implemented, the rest 501), utoipa document equal
to contracts/openapi.yaml, seams for M3/M4/M9, serve/migrate/healthcheck.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 21:30:20 +02:00
jacobh2 c277ee37c2 M2b: Android skeleton, :core:model contract, :domain passing all vectors
Gradle project in android/ with every module of SPEC §6.2 (build-logic convention plugins, version catalog,
architecture check in check), :core:model types and repository interfaces of §14.2.4 and §14.5, :domain
implementing §14.5.2 and §14.5.4 (125 vector cases), scenario-driven fakes with the empty scenario, Room 3
entities and KvittDatabase v1 with exported schema, :app shell with Hilt, en/sv strings with MissingTranslation
as a lint error, Roborazzi goldens of the empty screen in light and dark.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 21:25:19 +02:00
jacobh2 76ee957f41 SPEC v0.3: orchestrator resolutions of the plans' open points
Records D-004 and D-015 to D-025: shells and ownership, server seams
(router middleware, rate_limit layer, tasks::start, AuditWriter and PushQueue
traits, next_occurrence_date in kvitt-domain, M3 owns recurring generation,
M4 lands after M3), OpenAPI parsed-value equality, feature convention and
strings, empty fake scenario and resolveRange exception, shared ledger rows and
editor fields in :core:design, no bundled font, pruning at 04:00, development
infrastructure as M2c-dev built it, M2c-pi allowances, release keystore,
production import procedure. Plans and prompts close their open points;
HANDOFF rewritten to the real state; CLAUDE.md: implementation agents never
edit SPEC.md or docs/decisions.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 21:23:38 +02:00
jacobh2 c885aba313 M2c-dev: development compose stack, backup job, Firebase setup docs
infra/dev: compose stack on the Mac (server kvitt-server:dev, on-demand backup),
.env.example, build/up/logs/backup/copy-google-services scripts. No Caddy, no TLS
(debug builds accept http to private LAN addresses, AC-2.8). Bind-mounted data.
infra/backup: alpine 3.24.2 image and backup.sh (SPEC 10.9; Drive upload skipped
when RCLONE_FOLDER_ID is empty). infra/README.md for the dev stack and Firebase.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 21:18:36 +02:00
jacobh2 5e1be38364 Plans and start prompts for every milestone; SPEC v0.2
PLAN-<id>.md for U1, U2, M1, M2a, M2b, M2c-dev, M2c-pi, M3, M4, M5, M6,
M7a-M7f, M8, M9, M10, M11, each with the eleven fixed sections, and a
start prompt per plan in prompts/ plus prompts/README.md (dependency
order and parallel waves).

SPEC v0.2: owner answers D-001 (development-first, M2c split into
M2c-dev and M2c-pi, Gitea registry), D-002 (local backups deleted once
on Drive), D-003 (Splitwise CSV format, English and Swedish variants,
category mapping), OQ-1, OQ-2, OQ-4 answered; M1's readings of
ambiguous contract points stated explicitly in §14.3 to §14.5.
HANDOFF §2 lists the plans.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 21:05:34 +02:00
jacobh2 c6df2ab8f5 M1: contracts and vectors (SPEC v0.1, schema 1)
contracts/openapi.yaml (OpenAPI 3.1, E-1..E-33, error shape and the
exhaustive error code enum, device bearer scheme), contracts/schema/
server-v1.sql (SPEC §14.1 verbatim), seven test-vector files per §14.5.5,
80 examples (every JSON body of §14.3.4, every error code, sync pull/push,
every FCM push type), and tools/contracts-check.py (stdlib-only reference
of §9.1, §9.3, §9.4 and §14.5.4 plus shape checks; the M1 gate).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 20:57:59 +02:00
jacobh2 cab3e935d6 SPEC v0.1 and project rules for Kvitt
Write SPEC.md v0.1 from the owner interview (requirements, data model,
stack, API outline, security, business rules, deployment, milestones,
UX sprint brief, contracts). Fill the CLAUDE.md blanks, move the Gradle
pin files into android/, point the tools at android/ and server/, and
record the git bootstrap exception in the handoff.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 20:39:02 +02:00
jacobh2 8bef22afb2 Scaffold: agent starter kit (seeded from Ebb)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 20:15:40 +02:00