W2 web expenses, search and CSV export

W-5 expense editor (S-7): amount, description, category quick row and
"Alla" grid with the §9.11 suggestion for a new expense (CS-1 to CS-7),
the four fixed option rows (D-102), split modes default, ratio, exact and
full with live shares, notes, inline §9.6 validation, one POST /sync per
save, delete with confirmation, the discard dialog on Cancel, Esc and
in-app navigation (D-080), rejection and network failures shown on the
page with the form kept (§15.5). Defaults per AC-8.6 with this browser's
last account and category; ?account= preselects the opener's account.

W-6 expense detail (S-8): hero, facts, shares and mode, recurring
template badge, the AC-12.4 timeline, deleted and missing states.

W-12 search with filters in the URL query, count and sum, rows a hundred
at a time, and the CSV export (UTF-8 BOM, semicolons, CRLF, decimal
comma, ISO dates, header in the page language), built in the browser.

The §9.11 port passes contracts/test-vectors/suggest_category.json (65
cases); the keyword table is a Vite raw import of
contracts/category-keywords.csv, which needs ../contracts in
server.fs.allow (vite.config.ts).

Shared editor fields of design-spec §3.7 in web/src/components/editor/
for W5's template editor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
This commit is contained in:
2026-10-21 21:35:41 +02:00
parent 3ebe57d8b9
commit a65ab711ef
49 changed files with 5721 additions and 13 deletions
+4
View File
@@ -34,6 +34,10 @@ export default defineConfig({
__KVITT_VERSION_CODE__: JSON.stringify(pkg.kvitt.versionCode),
},
server: {
// web/ itself, plus contracts/ for the one contract file the app imports at build time: W2's keyword table
// (SPEC §15.7, D-055). Vite refuses `?raw` imports outside these paths, in Vitest as well as the dev server, and
// matches the id with its query, so the entry must be the directory, not the file.
fs: { allow: ['.', '../contracts'] },
proxy: Object.fromEntries(API_PREFIXES.map((prefix) => [prefix, { target: devApi, changeOrigin: true }])),
},
build: {