107 Commits
Author SHA1 Message Date
EislM0203andClaude Sonnet 4.6 082b6081b7 feat: fin CLI + agent skill + opt-in API bearer auth
Adds `fin` — a typer/httpx CLI for the deployed API — so Claude Code and
other agents can consume fin over HTTP without an MCP server.

Backend:
- api/app/auth.py: require_api_token dependency (no-op unless FIN_API_TOKEN
  env is set; RFC 7235 case-insensitive Bearer, constant-time compare). All
  nine routers wired; /api/health exempt for k8s probes.
- GET /api/openapi.json added as an explicit token-gated route (FastAPI's
  built-in openapi_url bypasses dependency injection).
- GET /api/transactions gains an optional `limit` query param (ge=1).

CLI (cli/):
- fin_cli/client.py: FinClient — sync httpx wrapper, zero typer/rich imports
  (MCP-ready core for a future MCP server).
- Subcommands: networth, accounts, categories, tx, transfer, settle,
  split-expense, splitwise-paid, reconcile, holdings, stock, rsu,
  spending, prices. Every command supports --json for agent use.
- fin tx add refuses transfer/settlement types to prevent single-leg writes.
- Config via FIN_API_URL (required) and FIN_API_TOKEN (optional).

Agent skill: .claude/skills/fin/SKILL.md — command map, jq patterns, and
domain invariants (two-leg atomicity, net-share splitwise math, derived
holdings, funded-buy, soft-deactivate, FIN_API_TOKEN web-UI lockout warning).

Tests: 25 e2e tests (FinClient → real routes → temp SQLite) covering auth
on/off, all key invariants (two-leg linkage, fee-in-balance, net-share math),
transfer whole-group delete, and linked-leg edit rejection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPBt3s7Jyr2nozQTrVVGA4
2026-07-03 19:22:15 +00:00
EislM0203andClaude Sonnet 4.6 e36d719cbe fix: stock trades and receivable/payable settles no longer inflate spending/income
Asset movements between own accounts (buy/sell cash leg, reconciling a
receivable or payable) were typed 'expense'/'income', so they showed up in
the spending and income metrics. They are now typed 'transfer'/'settlement',
which the spending queries already exclude.

Backend:
- funded-buy/sell bank leg now typed 'transfer' (linked to investment account)
  instead of 'expense' — buys no longer inflate spending
- FundedBuyRequest accepts type 'sell' in addition to 'buy'; the sell leg
  credits proceeds (shares × price − fee) to the bank account
- /reconcile legs retyped to 'settlement' — collecting a receivable no longer
  inflates income, paying off a payable no longer inflates spending
- RSU vest picks currency from PriceCache instead of hardcoding 'USD'
- create_account writes account + opening balance in one atomic commit
- create_split_expense links the expense row to the transfer legs so deleting
  any one of the three rows deletes the whole group
- delete_transaction follows linked_transaction_id in both directions to
  collect and delete the full linked group atomically
- splitwise-i-paid rejects partner_share >= total_amount (422)
- Migration h8c9d0e1f2a3 retypes historical rows the same way

Frontend:
- StockTradeFlow: sell now shows bank-account selector ('Credit proceeds…');
  selector shown for both buy and sell, hidden only for rsu_vest
- Portfolio Add Trade form: same buy/sell bank-account selector added,
  routes to createFundedBuy when an account is chosen
- Portfolio price-fetch handlers guard against stale ticker closures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 14:27:39 +00:00
EislM0203andClaude Fable 5 bc256f6c6e fix: bug hunt findings — currency derivation, local dates, atomic split expense, RSU vest unlink
Frontend:
- Derive transaction currency from the selected account instead of
  hardcoding EUR in SplitwiseIPaid/SplitwiseSettle/SplitwiseShePayed/IOwe
  flows; add currency-mismatch guards and dynamic labels (IAmOwed too)
- Replace toISOString().split('T')[0] with local-time todayLocalISO()
  helper in Dashboard, Portfolio, Spending and all QuickAdd flows —
  UTC dates booked trades/vests/settles to yesterday before ~1-2 AM
- SplitExpenseFlow now calls the new atomic /transactions/split-expense
  endpoint instead of two sequential requests that could partially fail
- Round partner_share to cents and reject 0% split client-side
- Portfolio: don't render accounts when holdings fetch failed
- client.ts: deleteAccount returns the Account, not void

Backend:
- New POST /api/transactions/split-expense writing expense + both
  transfer legs in one commit
- delete_transaction legacy fallback now matches date/type and closest
  id, so it can no longer delete a leg of a different transfer between
  the same two accounts
- delete_stock_transaction reverts any grant vested through the deleted
  transaction to pending, fixing the undeletable vested-grant deadlock
  (422 on grant delete, FK 500 on transaction delete)
- FundedBuyRequest.type constrained to Literal['buy'] (422 instead of 500)
- calculate_net_worth logs a warning before falling back to EUR/USD=1.0
- Scheduler engine applies PRAGMA foreign_keys=ON and uses proper URL
  parsing via db_config helpers
- fetch_all_prices counts only real updates; stale retry exception no
  longer logged after a clean no-data result

Infra:
- nginx: Cache-Control no-cache on index.html so deploys aren't served
  from stale browser caches
- api Dockerfile: COPY --chown=app:app

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019uL1qrmNyCP8Bmybns1qTQ
2026-07-01 21:06:19 +00:00
EislM0203andClaude Fable 5 3c2766e98f chore: migrate npm dependencies from Dynatrace mirror to public registry
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019uL1qrmNyCP8Bmybns1qTQ
2026-07-01 21:06:19 +00:00
Markus Eisl 42308a1476 fix: remove unused hasReceivable prop and variable from Dashboard 2026-07-01 13:21:24 +02:00
markus.eisl 9aae3e8aa4 fix 2026-07-01 13:19:04 +02:00
Markus Eisl 1e2ad89d64 fix: remove Split Expense from bank card actions, too cluttered 2026-07-01 13:14:32 +02:00
Markus Eisl 37bc10159f fix: currency mismatch guard in SplitExpense, net_worth override conversion, onOpenChange state reset, preview on mode switch 2026-07-01 13:10:50 +02:00
Markus Eisl 00409f0769 fix: review findings — SplitExpense userShare guard, initialAccountId, local-time dates, Split on bank cards, Splitwise accountId 2026-07-01 13:05:54 +02:00
Markus Eisl 30edddb4cb feat: add quick-action links on account cards (Expense/Income, Trade, I Paid) 2026-07-01 12:50:43 +02:00
Markus Eisl a3d9da7313 fix: accept comma as decimal separator in all QuickAdd amount inputs 2026-07-01 12:41:16 +02:00
Markus Eisl 35988fe3b5 feat: add Split Expense QuickAdd flow (user share as expense + partner share to receivable) 2026-07-01 12:24:14 +02:00
Markus Eisl 0496a58298 feat: show native currency on dashboard cards, use account currency in expense/income flows 2026-07-01 11:29:25 +02:00
Markus Eisl c4a47c888c feat: store and display stock/ETF name from yfinance shortName 2026-07-01 11:19:52 +02:00
Markus Eisl c96f72d44d fix: wrap transaction filters instead of horizontal scroll to remove spurious scrollbar 2026-07-01 11:16:22 +02:00
Markus Eisl c5fbcf34f0 feat: display account cards in a 2-col grid (3-col on wider screens) 2026-07-01 11:09:06 +02:00
Markus Eisl 0b5ba7c76c feat: refresh prices every 5min, add API_UPSTREAM to docker-compose 2026-07-01 09:27:41 +02:00
Markus Eisl 90bfdbd0a8 fix: shorten withholding label to Tax % and narrow column 2026-07-01 09:22:44 +02:00
Markus Eisl c8351188ce fix: withholding_pct defaults to 51% in pot form, add UI field, fix hardcoded 0 2026-07-01 09:19:48 +02:00
Markus Eisl e61b254ccc fix: include payable account expenses in spending calculations 2026-07-01 09:14:07 +02:00
Markus Eisl babea72e4a feat: show unvested RSU value (after withholding) greyed out on dashboard 2026-07-01 09:12:57 +02:00
Markus Eisl bc8c8d5499 fix: auto-discover exchange suffix for European ETFs in price fetcher 2026-07-01 09:02:50 +02:00
Markus Eisl 89d241a985 chore: upgrade npm to v11 before npm ci to fix Alpine exit-handler bug 2026-07-01 08:27:47 +02:00
Markus Eisl bfe7590bb2 chore: use npm ci with lockfile in web Dockerfile for reproducible builds 2026-07-01 08:21:59 +02:00
Markus Eisl 98ddc978bd chore: add gitignore entries, commit CLAUDE.md, README, and lockfile 2026-07-01 08:21:04 +02:00
Markus Eisl 9eadff1c28 chore: remove unused categories prop from IAmOwedFlow 2026-07-01 08:19:50 +02:00
Markus Eisl 062ba524fa feat: category type toggle and two-section display in Settings 2026-07-01 08:14:21 +02:00
Markus Eisl fd26786787 feat: filter category pickers by type, add category picker to IOweFlow 2026-07-01 08:14:04 +02:00
Markus Eisl 16a19af3e2 feat: IAmOwedFlow records bank→receivable transfer instead of income transaction 2026-07-01 08:13:04 +02:00
Markus Eisl 309b8ae2da feat: add type field to Category interface in client.ts
Also passes type:'expense' default in Settings.tsx createCategory call
to satisfy the now-required type field in the Category interface.
2026-07-01 08:11:44 +02:00
Markus Eisl 07ad335090 feat: add type column to categories (expense/income) with migration 2026-07-01 08:11:02 +02:00
Markus Eisl 196bef0104 docs: implementation plan for typed categories and IAmOwedFlow redesign 2026-07-01 08:09:11 +02:00
Markus Eisl e86df27637 docs: spec for typed categories, IOweFlow category, IAmOwedFlow transfer redesign 2026-07-01 08:06:55 +02:00
Markus Eisl 0a97ad0699 feat: Earned/Spent/Net strip on Spending page 2026-07-01 07:32:14 +02:00
Markus Eisl 9a49be2886 feat: cross-currency transfer UI with FX rate pre-fill 2026-07-01 07:32:04 +02:00
Markus Eisl a72b052682 feat: client.ts cross-currency transfer types, income totals, FX rate method 2026-07-01 07:31:43 +02:00
Markus Eisl f14c5233d7 feat: total-first investment buy, trade currency selector 2026-07-01 07:30:39 +02:00
Markus Eisl b2a7c7a719 feat: cross-currency transfer backend, income totals in spending, FX rate endpoint 2026-07-01 07:30:28 +02:00
Markus Eisl ed3febad10 feat: income QuickAdd flow 2026-07-01 07:30:14 +02:00
EislM0203 d6001dc90b fixes 2026-07-01 07:10:33 +02:00
Markus Eisl 81091fc6d4 fix: remove 1.0 FX fallback — skip upsert when rate unavailable and no cache exists 2026-06-30 20:36:49 +02:00
Markus Eisl 54d64e0e63 fix: Literal type validation for transaction/account types, readable 422 error messages 2026-06-30 20:36:40 +02:00
Markus Eisl 0a8a43ff00 fix: block SplitwiseShePayedFlow submit when computed user share is 0 2026-06-30 20:36:08 +02:00
Markus Eisl a1b5763d95 fix: 400 on malformed month param, validate custom date range order 2026-06-30 20:35:56 +02:00
Markus Eisl 256c27f99b fix: reject negative shares and price in Portfolio add-trade form 2026-06-30 18:23:35 +02:00
Markus Eisl 9d06e1bf2a fix: refetch after delete to clear orphan legs, local date in IOwe/IAmOwed, History error handling 2026-06-30 18:23:27 +02:00
Markus Eisl a1289f6fec fix: block update on paired transaction legs, allow payable in adjust_balance 2026-06-30 18:23:22 +02:00
Markus Eisl 9b78d0ee65 fix: fetch_eur_gbp error handling mirrors fetch_eur_usd on failure 2026-06-30 18:23:17 +02:00
Markus Eisl 2bd4730d88 fix: block edit on linked transactions, show price unavailable for missing prices, dashboard uses transaction currency 2026-06-30 17:53:22 +02:00
Markus Eisl 6b7d19b828 fix: _to_eur returns 0 with warning when FX rate missing, not wrong-currency fallback 2026-06-30 17:52:09 +02:00