Files
kvitt/PLAN-K3.md
T
jacobh2 07df30d04d SPEC v0.38: K1's report recorded (D-181 to D-188), K2 v0.39 and K3 v0.40
K1 landed 671c00d (card purchases on the server, schema 4, E-36 to E-44). Decisions: D-181
history counts today (6 windows for 180 days, revises CP-3 and CP-7); D-182 the server's CS-4
first token; D-183 Enable Banking errors to CP-14; D-184 callback (expired state stored as
expired, revises CP-2; atomic claim; session without accounts revoked); D-185 fetch details;
D-186 PSU headers; D-187 E-41, E-43, E-44 details, CardAttempt.failure a plain string; D-188
placement and the secrets/cards compose mount. D-189, D-190 unused.

SPEC: §9.12 CP-2 to CP-5, CP-7, CP-8, CP-14; §10.2; §11.1, §11.2 K1 row with the landing
gates, §11.3; §14.1.3; §14.3.3; §14.3.4 (E-1 schema_version 4, E-37, E-39, E-41, E-43, E-44);
§14.3.5; §14.7; change log. PLAN-K2, PLAN-K3 and prompts/README.md name v0.39 and v0.40.
HANDOFF: K1 landed, K2 and K3 running, the Android gate red until K2, K1 owner steps 1 to 4
pending, next free D-212 and v0.39.

Pre-commit hook bypassed (--no-verify): docs only; its Android fast tests fail only in
:domain VectorFilesTest, red on main by design until K2 lands (as for 671c00d).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-24 16:50:04 +02:00

51 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PLAN-K3: Card purchases, web
## 1. Goal
The card features of FR-22 in the web client (D-174): W-21 Kortköp, W-22 Anslut kort with BankID by QR in the browser, W-23 the connection's settings, the sidebar entry, "Skapa utgift" through W-5 with the prefill of CP-9, the card line on W-6, "Från kortköp" on W-3, and `matchCardPurchase` in TypeScript passing its vectors.
## 2. Read first
1. SPEC §15 (§15.3 strings and layout, §15.5 data, §15.6 rows W-3, W-5, W-6, W-21 to W-23, the navigation paragraph), §9.12 (CP-1 to CP-14, with K0's answers in v0.36), FR-22, §13.3 rows S-23 to S-27, the "Card callback page" and "Card purchase strings" tables, §14.3.3, §14.3.4 E-36 to E-44, §14.5.8, §14.7; D-174, D-169, D-170, D-172, D-177 to D-180 (K0's results and the artwork).
2. K1's `contracts/openapi.yaml` and `web/src/api/schema.d.ts` (regenerated by K1), `contracts/examples/E-36-*` to `E-44-*`, `contracts/test-vectors/match_card_purchase.json`.
3. `web/README.md`, `web/src/app/` (routes, layout, sidebar), `web/src/api/` (client, snapshot), `web/src/lib/domain/`, `web/src/components/`, `web/src/features/{expenses,accounts,settings}/`; `docs/ux/design-spec.md` §2 and the S-23 to S-27 design (K2's report, if landed).
4. `CLAUDE.md` (web gate, web layering).
## 3. Already established
- Card data is read through E-40 into an in-memory card snapshot next to the household snapshot (§15.5: pulled from 0 on load, from its cursor on focus and after every card write); nothing persisted across a reload; writes (E-44) go at once, no queue.
- Connect: E-37 with `client` `web`; the browser goes to the returned URL in the same tab; the bank shows its QR for BankID on the phone; the callback page's link returns to `/app/cards?attempt=<id>`, where W-21 polls E-38 every 2 seconds until the attempt ends, then drops the parameter (CP-2).
- "Skapa utgift" opens W-5 at `/app/expenses/new?cardPurchase=<id>` (plus `&account=<id>` from W-3's "Från kortköp", `/app/cards?account=<id>`); W-5 prefills per CP-9, runs the §9.11 suggestion as for any new expense, and after an accepted save sends E-44 with the new expense id.
- Artwork (D-174, D-180): `web/src/features/cards/assets/card-sj-prio-mastercard.png` is committed (the same file as Android's, 1313 × 829, cropped to the card, transparent only at its rounded corners and in the notch on the right edge). The cards feature imports it (Vite emits it under `/app/assets/` with a hashed name) and maps the image key `sj_prio_mastercard` to it from the first commit, drawn at its own aspect ratio (`object-fit: contain`, no clipping, no background), `alt` the card's name; for any unknown key, the drawn fallback of D-173 (CSS: aspect ratio 1.586, radius 16 px, the `green` category token's light value, the name in white). Served from the server's own origin (CSP `img-src 'self'`). Do not edit or re-encode the PNG.
- Strings: the keys of §13.3's table (K2 adds them to `docs/ux/strings.csv`; `npm run i18n:import` regenerates the dictionaries); web-only strings get `web_cards_` keys in `web/src/features/cards/i18n/{sv,en}.json`.
- The sidebar and bottom bar gain "Kortköp" after Home (D-172).
## 4. Owned paths
From SPEC §14.7 (row K3): `web/src/features/cards/**` (new); in shared code the route in `web/src/app/`, the sidebar entry and `web/src/lib/domain/` (`matchCardPurchase` and its vector test); `web/src/features/expenses/**` (W-5's prefill and link, W-6's card line), `web/src/features/accounts/**` (W-3's "Från kortköp"), `web/src/features/settings/**` (W-13's row); the regenerated i18n dictionaries. Not `web/src/api/schema.d.ts` (K1), `docs/ux/**`, `server/**`, `contracts/**`, `android/**`.
## 5. Deliverables
- The card snapshot and API calls; W-21 (header, filters, day groups, the purchase panel or dialog with every action of S-23's sheet, match chips, the `?attempt=` and `?account=` handling), W-22 (catalogue with artwork, history choice, "Anslut med BankID"), W-23 (as S-26 with the disconnect dialog); W-5, W-6, W-3 and W-13 changes; the sidebar entry.
- `matchCardPurchase` passing `match_card_purchase.json` in `web/src/lib/vectors.test.ts` (or the domain's vector test), listed as ported.
- Tests: pages against the fake server with every state of §13.3 (no connection, first fetch, loaded, empty filter, expiring, expired, fetch failed, long content), the attempt polling, E-44 outcomes (`expense_already_linked`, `not_found`), the prefill rules.
## 6. Steps
1. Worktree: from `/Users/jhag/projects/kvitt`, `git worktree add .claude/worktrees/agent-k3 -b agent-k3 main`; in it `git reset --hard main`; `npm ci` in `web/`; `tools/install-git-hooks.sh`.
2. Confirm SPEC v0.36 is on `main`. K3 may start before K1 lands, against the spec and the fake server; before its gate it rebases on K1 (the generated types and the vectors are K1's) and on K2 (the card strings in `docs/ux/strings.csv`; then `npm run i18n:import`). If K1 or K2 has not landed when the rest is done, stop and report.
3. Build; check manually against a throwaway server (`cargo run -p kvitt-server -- serve` with a temporary `KVITT_DATA_DIR` and `KVITT_EB_API_URL` pointed at a stub, as K1's tests do), never the owner's development or Pi data.
4. Gate (§7). Commit on `agent-k3`, `git rebase main`, rerun the gate. Never merge, push or remove the worktree.
## 7. Gate
The web gate, from `web/`: `npm run check`. Report the real result lines (test files, tests).
## 8. HUMAN steps (relayed by the orchestrator in chat, after K1 is deployed and an image with K3 is deployed)
1. On the laptop, `https://kvitt.suorra.duckdns.org/app/` › Inställningar › Anslut kort: the SJ Prio card shows "Ansluten" if the phone connected it (yes/no). Kortköp lists the same purchases as on the phone (yes/no).
2. Kortköp › a purchase › "Skapa utgift" › "Spara": the purchase is under "Kopplade" on the laptop and on the phone after a sync (yes/no).
3. If the card is not connected yet or after "Koppla från": Anslut kort › SJ Prio Mastercard › "Anslut med BankID": the bank's page shows a QR; scan it with BankID on the phone; "Tillbaka till Kvitt" opens Kortköp with your purchases (yes/no each).
## 9. Out of scope
- The server and the contract (K1), the app (K2); `docs/ux/strings.csv` (K2); a card notification and bulk add (D-171).
## 10. Report
- Branch `agent-k3`, head commit after rebasing; gate lines.
- `web_cards_` strings added; proposed decision entries and spec text for anything beyond §15.6 and §9.12 (numbered from the brief); HUMAN steps outstanding worded for relay; open questions with SPEC section numbers.
## 11. Numbering
Decisions from the start prompt (`TODO(spec)` if none was given). Proposed spec text goes into SPEC **v0.40** (v0.36 recorded K0's results, v0.38 K1's report, v0.39 K2's).