Files
kvitt/PLAN-W2.md
T
jacobh2 7bddc50a53 SPEC v0.28: web client programme (D-129 to D-134), plans W0 to W7
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
2026-10-21 20:49:00 +02:00

6.8 KiB
Raw Blame History

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

  1. 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.
  2. 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.csv keys expense_*, editor_* and the shared ones.
  3. contracts/test-vectors/suggest_category.json, contracts/category-keywords.csv, contracts/examples/sync/*.json.
  4. web/README.md and W0's report (module map).
  5. Files: web/src/{app,api,lib,components,i18n}/** (read only), web/src/features/{expenses,search,export}/** (W0's placeholders).
  6. 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 by sort_order, D-060), account (active accounts), date (today by default), payer, split modes default, ratio, exact, full with live shares from W0's computeSplit, notes; account, date, payer and split as four fixed rows (D-102); validation inline; Save writes one POST /sync mutation (§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 suggestCategory and its normalisation in web/src/features/expenses/suggest/, passing contracts/test-vectors/suggest_category.json in a Vitest test; the keyword table imported at build time from contracts/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 with String.prototype.normalize('NFC') and \p{L}/\p{Nd} classes (the UnicodeText roles 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/:id with an edit sub-route of this plan's choice) and Delete.
  • A rejection from POST /sync (§14.4.6, for example stale) 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 N to /app/expenses/new and / to /app/search; this plan focuses the amount field on W-5 and the search field on W-12 when they open, and handles Esc (discard rule) inside its pages.
  • Receipt pictures do not exist (§2): no picture field.
  • 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

    • 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.

    6. Steps

    1. Worktree: from /Users/jhag/projects/kvitt, git worktree add .claude/worktrees/agent-w2 -b agent-w2 main; in it git reset --hard main, tools/install-git-hooks.sh, cd web && npm ci.
    2. Read §2. Confirm W0 has landed (placeholders in this plan's folders on main); if not, stop and report.
    3. npm outdated; record it. No new library is expected (a CSV library is not needed).
    4. Suggestion port and vector test; editor; detail; search; export.
    5. 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.
    6. Gate (§7). Commit on agent-w2, git rebase main, rerun the gate. Never merge, push or remove the worktree.

    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

    • Home and accounts (W1), settle up (W3), analytics (W4), settings (W5).
    • A server search or export endpoint; receipt pictures; edits outside the three folders.

    10. Report

    • 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 from strings.csv; design-spec mismatches.
    • Requests for W0's modules; proposed decision entries (numbered from the brief); open questions with SPEC section numbers.

    11. Numbering

    Assigned in the start prompt; use TODO(spec) if none was given.