Files
kvitt/PLAN-K3.md
T
jacobh2 b5114adc06 SPEC v0.35: card purchases via Enable Banking (D-158 to D-176), plans K0 to K3
Owner request and answers of 2026-10-24: each user connects their own SJ Prio
Mastercard through Enable Banking (restricted production, BankID); only the
card holder sees the purchases; a fourth bottom destination "Kortköp"
(revises D-048); "Anslut kort" in Settings with the card artwork; one- and
two-tap linking to household expenses; web as a later plan.

FR-22, §9.12 (CP-1 to CP-14), server schema 4 (migration 0004), Room 2,
E-36 to E-44, S-23 to S-27, W-21 to W-23, OQ-7, OQ-8. PLAN-K0 describes the
test app of 2ce317f and the remaining run and report; K1 server, K2 Android,
K3 web. HANDOFF and prompts/README updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 11:49:14 +02:00

6.3 KiB
Raw Blame History

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.
  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): web/src/features/cards/assets/card-sj-prio-mastercard.png when the owner has put it there, mapped from the image key sj_prio_mastercard; otherwise, and 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').
  • 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. The web copy of the artwork: put the same SJ Prio Mastercard PNG at /Users/jhag/projects/kvitt/web/src/features/cards/assets/card-sj-prio-mastercard.png and say done.
  2. 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).
  3. Kortköp › a purchase › "Skapa utgift" › "Spara": the purchase is under "Kopplade" on the laptop and on the phone after a sync (yes/no).
  4. 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

From the start prompt (TODO(spec) if none was given).