New SPEC §15 Web client and FR-21; §1, §2, G-5, AC-14.1 revised; §8.5 and §10.5.2 gate lines for /app/, the invite accept and bearer requests (except /health, /setup/, /i/, /apk/) plus the /app/ CSP via $kvitt_csp, identical in infra/npm/kvitt.conf and infra/README.md step 4 (checked with nginx 1.31.7); §10.3, §10.6, §11.1 to §11.3 (rows W0 to W7), §14.7 ownership; change log is now §16. PLAN-W0..W7 and prompts/W0..W7; HANDOFF numbering D-139 / v0.30. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
6.8 KiB
PLAN-W2: Web expenses, search and export
1. Goal
Build the web client's expense editor (W-5) with the category suggestion of §9.11, expense detail with the audit timeline (W-6), and the search page (W-12) with its CSV export, in web/src/features/expenses/, web/src/features/search/ and web/src/features/export/, on W0's foundation (SPEC §15.6, §15.7), with tests.
2. Read first
- SPEC §15 (all), FR-21, FR-8 (expenses), FR-12 (audit, AC-12.4), §9.1 (splits), §9.2, §9.6 (limits), §9.8, §9.11 (CS-1 to CS-7) and §14.5.6, §14.4.3 to §14.4.6 (push, rejections, audit diff), §13.3 rows S-7 and S-8, D-049, D-055, D-080, D-102.
docs/ux/design-spec.md§3.7 (editor fields) and the S-7 and S-8 sections;docs/ux/category-suggestion.md;docs/ux/strings.csvkeysexpense_*,editor_*and the shared ones.contracts/test-vectors/suggest_category.json,contracts/category-keywords.csv,contracts/examples/sync/*.json.web/README.mdand W0's report (module map).- Files:
web/src/{app,api,lib,components,i18n}/**(read only),web/src/features/{expenses,search,export}/**(W0's placeholders). CLAUDE.md.
3. Already established
- W0 owns everything shared; this plan edits only its three feature folders. Missing shared pieces are requests to the orchestrator.
- Editor (W-5, mirrors S-7), built from W0's shared editor fields (design-spec §3.7, D-019; W5's recurring editor uses the same): amount (kr, two decimals, §9.6 range), description, category grid (active categories, the S-7 quick row of
frequentCategoryIds' rule computed in the browser from the snapshot: the five most used active categories of the account, ties bysort_order, D-060), account (active accounts), date (today by default), payer, split modesdefault,ratio,exact,fullwith live shares from W0'scomputeSplit, notes; account, date, payer and split as four fixed rows (D-102); validation inline; Save writes onePOST /syncmutation (§15.5); Delete in edit mode (soft delete, with confirmation); Cancel with the discard dialog (title, no body, D-080). After saving, the page returned to shows "Utgiften sparades" (D-071) without undo. - Category suggestion (§9.11, §15.7): a TypeScript port of
suggestCategoryand its normalisation inweb/src/features/expenses/suggest/, passingcontracts/test-vectors/suggest_category.jsonin a Vitest test; the keyword table imported at build time fromcontracts/category-keywords.csv(Vite raw import of the repository file, never a copy), parsed with the §14.8 rules. For a new expense the category follows the description until the user picks one (CS-2, CS-3, CS-7), with no marker. The history for CS-1 comes from the snapshot. Unicode normalisation withString.prototype.normalize('NFC')and\p{L}/\p{Nd}classes (theUnicodeTextroles of §14.5.6). - Detail (W-6, mirrors S-8): every field, each user's share and the split mode, "Recurring: " for a generated expense, the timeline from the snapshot's audit entries (AC-12.4; AC-12.5's pending line does not apply on the web), a deleted state; Edit (
/app/expenses/:idwith an edit sub-route of this plan's choice) and Delete. - A rejection from
POST /sync(§14.4.6, for examplestale) shows its reason with the copy of S-16's rejection texts and reloads the row from the server; nothing is queued (§15.5). - Search (W-12, §15.7): in the browser over the snapshot; non-deleted expenses whose description or notes contain the query (case-insensitive); filters amount from–to, date from–to, account, category, payer, combined with AND; result count and sum; ledger rows (W0's) opening W-6; newest first. The filter state may live in the URL query so a search can be bookmarked (this plan's choice).
- CSV export (§15.7): downloads the current filtered list, one row per expense: date, description, category, account, payer, amount, each user's share, notes; a header row in the page's language. Choose separator, decimal mark, quoting, byte-order mark and file name so that the file opens correctly in Numbers and in Excel with a Swedish locale; test the output byte for byte in Vitest; propose the format as a decision in the report. Built in the browser (
Blob, a download link); no server endpoint. - Keyboard: W0's shell binds
Nto/app/expenses/newand/to/app/search; this plan focuses the amount field on W-5 and the search field on W-12 when they open, and handlesEsc(discard rule) inside its pages. - Receipt pictures do not exist (§2): no picture field.
- W-5, W-6 and W-12 with every state of §15.6 (loaded, empty, loading, error, long content), the editor's validation and split states, the detail's deleted state.
- The suggestion port with the vector test (65 cases); the CSV writer with its tests; tests of every split mode's shares, the quick row, the discard dialog, the rejection path, the search filters and their combination.
- Worktree: from
/Users/jhag/projects/kvitt,git worktree add .claude/worktrees/agent-w2 -b agent-w2 main; in itgit reset --hard main,tools/install-git-hooks.sh,cd web && npm ci. - Read §2. Confirm W0 has landed (placeholders in this plan's folders on
main); if not, stop and report. npm outdated; record it. No new library is expected (a CSV library is not needed).- Suggestion port and vector test; editor; detail; search; export.
- Manual check (optional): a throwaway server,
npm run dev, add, edit and delete an expense, search, export and open the file in Numbers. Never the owner's development stack or the Pi. - Gate (§7). Commit on
agent-w2,git rebase main, rerun the gate. Never merge, push or remove the worktree. - Home and accounts (W1), settle up (W3), analytics (W4), settings (W5).
- A server search or export endpoint; receipt pictures; edits outside the three folders.
- Branch
agent-w2, head commit after rebasing; gate lines; Vitest counts (suggestion vectors: 65). - The CSV format chosen (bytes of a two-row sample) as a proposed decision; pages × states covered.
web_keys added (en, sv); strings missing fromstrings.csv; design-spec mismatches.- Requests for W0's modules; proposed decision entries (numbered from the brief); open questions with SPEC section numbers.
4. Owned paths
From SPEC §14.7: web/src/features/expenses/**, web/src/features/search/**, web/src/features/export/**. web/package.json only for a dependency line. Nothing else; contracts/** is read only.
5. Deliverables
6. Steps
7. Gate
The web gate, from web/: npm run check. Green: every step passed in this session; Vitest summary quoted, including the suggestion vector test.
8. HUMAN steps
From SPEC §11.2: add, edit and delete an expense in a browser; open an exported CSV in Numbers (yes/no), relayed after landing.
9. Out of scope
10. Report
11. Numbering
Assigned in the start prompt; use TODO(spec) if none was given.