d39eb2c79b
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
73 lines
8.7 KiB
Markdown
73 lines
8.7 KiB
Markdown
# PLAN-M12b: Category contract v2, server
|
|
|
|
## 1. Goal
|
|
Bring the landed server and importer to the category contract v2 (D-053, D-054): migration 0002 adds the four new default categories to every database and moves the server to schema version 2; the token lists, the default-key enum and the importer's category table follow the spec. Existing databases, the development stack included, upgrade in place without losing a row.
|
|
|
|
## 2. Read first
|
|
1. SPEC §14.1 and §14.1.1 Migration 0002 (all of it), §14.8 (contract files, migration texts).
|
|
2. SPEC AC-7.2 (fourteen defaults), §9.7 (33 icons, 14 colour tokens), §14.3.3 Category, §14.3.4 E-1.
|
|
3. SPEC §10.8 Splitwise import (the schema check and the new category table), D-044, D-046, D-053.
|
|
4. SPEC §8.9 (pragmas), §14.4.2 (pull: the new rows reach phones by `server_version`).
|
|
5. SPEC §11.2 (row M12b), §11.3 (note M12), §14.7 (row M12b).
|
|
6. `docs/decisions.md` D-053, D-054.
|
|
7. Files: `git show 6b60ef7` (M12a's commit message lists what it held back), `contracts/schema/migrations/0002_categories_v2.sql` and `tools/contracts-check.py` (from M12a), `server/crates/kvitt-server/migrations/0001_init.sql`, `src/db/mod.rs`, `src/api/validate.rs`, `src/wire/category.rs`, `src/wire/mod.rs`, `src/openapi.rs`, `tests/skeleton/migration.rs`, `tests/skeleton/routes.rs` (E-1 `schema_version`), `tests/openapi_contract.rs`, the tests in `tests/core_api/` and `tests/sync_api/` that count or list categories, `server/crates/kvitt-import/src/categories.rs`, `data/splitwise_categories.csv`, `tests/import.rs` (`category_table_matches_the_spec`), `contracts/examples/E-31-sync-pull/response.json`.
|
|
8. `CLAUDE.md` (server gate, no `unwrap` outside tests, every schema change gets a migration and a migration test).
|
|
|
|
## 3. Already established
|
|
- Migration 0002 is the SQL of §14.1.1 byte for byte; `server-v1.sql` and `0001_init.sql` stay unchanged (§14.1.1).
|
|
- It rebuilds `categories` under `PRAGMA defer_foreign_keys = ON` inside sqlx's per-migration transaction (foreign keys stay ON), inserts four rows with `server_version` from `sync_sequence`, sets `meta.schema_version` to `2` (§14.1.1). M12a's checker (`6b60ef7`) loads it on a fresh and on an in-use v1 database with foreign keys on, the way sqlx does; the docs agent's own run of an equivalent text with SQLite 3.50 also committed with `foreign_key_check` empty.
|
|
- `SCHEMA_VERSION` = 2; E-1 reports it; the importer's check reads that constant, so it follows (§10.8, §14.1.1; D-046 revised by D-054).
|
|
- Icons: the 30 of v1 plus `key`, `shield`, `account_balance`; colours: the 12 of v1 plus `slate`, `amber` (§9.7). Hex values are irrelevant to the server.
|
|
- `DefaultCategoryKey` has the fourteen keys in both `contracts/openapi.yaml` and the generated document. M12a (`6b60ef7`) held back the contract side, so this plan edits `contracts/openapi.yaml` (that enum only) and adds the category examples M12a held back (see its commit message), then the OpenAPI match test compares the two.
|
|
- Importer table per §10.8 (D-053): Rent/Mortgage/Hyra/Avbetalning/Amortering `housing`; Insurance/Försäkringar `insurance`; Childcare/Barnomsorg `children`; Taxes/Skatter `fees`; Swedish El, Värme/gas, Vatten, Sophämtning (`utilities`) and Husdjur (`home`) added. `category_table_matches_the_spec` parses SPEC §10.8, so it fails on `main` from SPEC v0.8 until this plan lands (the orchestrator lands them close together).
|
|
- The E-31 example lists two categories, not the defaults; change it only if it enumerates the defaults.
|
|
|
|
## 4. Owned paths
|
|
From SPEC §14.7 (row M12b, a contract-change plan): `contracts/openapi.yaml` (`DefaultCategoryKey` only) and the category examples under `contracts/examples/` that M12a held back (with `python3 tools/contracts-check.py` still passing); `server/crates/kvitt-server/migrations/0002_categories_v2.sql`, `src/db/mod.rs` (`SCHEMA_VERSION`), `src/api/validate.rs` (token lists), `src/wire/category.rs` and `src/wire/mod.rs` (key enum), and the tests of these (`tests/skeleton/**`, and the category-counting lines of `tests/core_api/**` and `tests/sync_api/**`); `server/crates/kvitt-import/data/splitwise_categories.csv`, `src/categories.rs` and its tests.
|
|
|
|
Edit nothing outside these paths; a needed change elsewhere (including `contracts/**`) is a contract request to the orchestrator.
|
|
|
|
## 5. Deliverables
|
|
- `server/crates/kvitt-server/migrations/0002_categories_v2.sql`: byte-identical to `contracts/schema/migrations/0002_categories_v2.sql`.
|
|
- An identity test asserting that equality (next to the existing 0001 identity test).
|
|
- `SCHEMA_VERSION` = 2 and the E-1 tests updated.
|
|
- A seed test: on a fresh database, the fourteen default rows exactly per AC-7.2 and §14.1.1 (ids, keys, icons, colours, sort orders, null names, `server_version` 11 to 14 after the ten seeds); on a v1 database (built from `0001_init.sql` alone, then data inserted: a custom category, a renamed and an archived default, expenses and a recurring template referencing categories) upgraded through the migrator: every earlier row unchanged, the four new rows with `server_version`s above the old sequence value, `PRAGMA foreign_key_check` empty, `meta.schema_version` `2`; a pull from cursor = the old sequence value returns exactly the four new categories.
|
|
- `validate.rs`: icon list of 33 and colour list of 14 (§9.7), with tests for the new tokens.
|
|
- `contracts/openapi.yaml` `DefaultCategoryKey` with the fourteen keys and the held-back category examples; the default-key enum in `src/wire/` with the fourteen keys; the OpenAPI match test and `python3 tools/contracts-check.py` green.
|
|
- `splitwise_categories.csv` per §10.8; `src/categories.rs` key list of fourteen; `category_table_matches_the_spec` green; an importer test that a Försäkringar / Insurance row maps to `insurance`.
|
|
- Existing tests that assumed ten defaults updated, each with the reason in the report.
|
|
|
|
## 6. Steps
|
|
1. Worktree. From the main checkout `/Users/jhag/projects/kvitt`: `git worktree add .claude/worktrees/agent-m12b -b agent-m12b main`, then in `.claude/worktrees/agent-m12b`: `git reset --hard main`. No git-ignored files are needed. Run `tools/install-git-hooks.sh`.
|
|
2. Read §2 in order. Confirm M12a has landed (`contracts/schema/migrations/0002_categories_v2.sql` exists and `python3 tools/contracts-check.py` passes on `main`); if not, stop and report.
|
|
3. Copy the migration file; add the identity test; bump `SCHEMA_VERSION`; run the server gate and list every failing test that counts categories or checks the schema version.
|
|
4. Write the seed and upgrade tests (§5), then fix the lists, the enum and the failing tests until green.
|
|
5. Update the importer table and key list; run `category_table_matches_the_spec`; add the insurance mapping test.
|
|
6. Run the gate (§7). Commit on `agent-m12b`, `git rebase main`, rerun the gate. Never merge, push or remove the worktree.
|
|
7. Do not migrate the running development stack's database yourself: report the command for the orchestrator (`kvitt-server migrate` runs at start, so a restart with the new image upgrades it; take a backup first with the dev backup script).
|
|
|
|
## 7. Gate
|
|
From SPEC §11.2, verbatim: "Server gate; `python3 tools/contracts-check.py` passes".
|
|
Command, from `server/`: `cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo test`; and `python3 tools/contracts-check.py` from the repository root (this plan edits `contracts/`).
|
|
Green means: all three exit 0 in this session; the `test result:` lines show 0 failed and include the identity, seed, upgrade, OpenAPI match and importer parity tests.
|
|
|
|
## 8. HUMAN steps
|
|
From SPEC §11.2, verbatim: "none".
|
|
|
|
## 9. Out of scope
|
|
- `contracts/**` beyond the OpenAPI enum and the held-back examples, Android (M12c), `server-v1.sql` and `0001_init.sql` (byte-locked).
|
|
- Any change to sync, push or the audit writer; the four new rows get no audit entries and no pushes (they are seed data like the v1 seeds).
|
|
- Running the importer against the real exports, or migrating the development or production database.
|
|
|
|
## 10. Report
|
|
- Branch `agent-m12b`, head commit hash after rebasing on `main`.
|
|
- Gate output: fmt and clippy exit status, every `test result:` line.
|
|
- Tests changed because they assumed ten defaults or schema 1, each with its reason.
|
|
- SQLite version of the bundled `libsqlite3-sys` and confirmation that the migration ran under sqlx's transaction with foreign keys on.
|
|
- The upgrade steps for the development stack, worded for the orchestrator.
|
|
- Proposed decision entries (never written to `SPEC.md` or `docs/decisions.md` by this agent) or `TODO(spec)` occurrences.
|
|
- Contract requests, open questions, spec gaps (with section numbers).
|
|
|
|
## 11. Numbering
|
|
Decision ids, spec version and schema version are assigned by the orchestrator in the start prompt; use `TODO(spec)` if none was given. This plan implements server schema version 2 (D-054); any further schema change would be 3.
|