# Crypto Vue dashboard readmodel hardening

Use this reference when fixing or extending the FinanceManager Vue/FastAPI Crypto page after the read-only FastAPI boundary exists.

## Problem pattern

Crypto holdings, wallets, and prices are naturally one-to-many. A direct SQL join such as `crypto_holdings -> crypto_prices` can duplicate wallet rows whenever an asset has multiple historical/current price rows. Symptoms:

- duplicate wallet rows in coin detail
- inflated wallet or coin market value
- wallet allocation totals that differ from aggregate crypto position totals
- detail DTOs disagreeing with list DTOs

## Durable fix pattern

1. Build holdings from the canonical crypto holdings readmodel first, grouped by asset and wallet.
2. Resolve the latest relevant price separately once per asset/currency, e.g. latest CHF `crypto_prices` by provider timestamp/fetched timestamp.
3. Apply the latest price to already-aggregated holdings; never let price-row multiplicity affect quantities.
4. Keep quantities/prices as `Decimal`/TEXT in Python-facing logic and format with non-scientific decimal strings.
5. Add regression tests with one asset split across multiple wallets plus multiple price rows for the same asset. Assert:
   - no duplicate wallet allocation rows
   - wallet quantity totals are exact
   - market value uses the latest price only once
   - wallet summary/detail coin rows are not duplicated

## Vue UX pattern

- Crypto page should be compact and user-facing, not a raw technical status page.
- System/runtime status belongs in a small, low-prominence badge area.
- Show aggregate cards: total value, coin count, wallet count, latest local price update/data quality.
- Allocation charts should be sorted descending, limited to top entries, with an `Andere` bucket for the remainder.
- Wallet allocation on the overview should come from wallet summary DTOs, not only from the currently selected coin detail.
- Coin detail wallet rows should be clickable and open wallet detail/drilldown without exposing raw internal IDs as the primary UI.
- Sparklines need a meaningful minimum history. With fewer than 3 usable price points, show a calm empty state such as `Noch zu wenig Kurshistorie für Chart` instead of drawing a misleading diagonal line.

## Start/stop operations pattern

For the local Vue User Dashboard, provide scripts rather than asking the user to remember multiple commands:

- `scripts/start_vue_dashboard.sh`
- `scripts/stop_vue_dashboard.sh`

Scripts should:

- use the runtime venv outside the repo
- write PID files and logs under `~/jarvis_runtime/finance-system/logs/`
- check that `frontend/node_modules` exists and tell the user to run `npm install` if missing
- check/avoid occupied ports unless explicitly asked to kill script-managed PIDs
- avoid placing secrets/API keys in frontend env, command lines, or logs

## Verification checklist

Run both focused regressions and full gates:

- focused FastAPI tests for duplicate holdings/price joins
- focused Vue page test for compact allocations, sparkline empty state, wallet drilldown
- `python -m compileall -q src`
- full `pytest` with `JARVIS_FINANCE_DB_PATH` unset unless a fixed runtime DB is intended
- `npm test` and `npm run build` from `frontend/`
- `bash -n` for scripts
- changed-source secret scan and built-asset secret scan
- remove `.pytest_cache`, `__pycache__`, `.vite`, and `frontend/dist` before final Git safety/staging
- `git diff --check`, commit, push, and verify local/remote hash equality
