Issue 3 of overnight-2026-06-01-bilan-axe-vehicule. Builds the tracking UI
on top of the merged data layer (#202) and input UI (#203).
- Service: getSnapshotTotalsByVehicleAndDate(range) mirrors the by-category
aggregation with GROUP BY COALESCE(a.vehicle_type, 'none') so null-envelope
accounts land in a single 'none' bucket (never a SQL NULL key). Add
vehicle_type to getAccountsLatestSnapshot SELECT + type.
- useBalanceOverview: new groupAxis ('class'|'vehicle') state ORTHOGONAL to
chartMode; loads byVehicle alongside byCategory. Default groupAxis='class'.
- BalanceEvolutionChart + BalancePage: stacked-mode sub-toggle 'Par classe
d'actif' (default) / 'Par enveloppe'. Vehicle legend reuses the #203
vehicleType.* labels; the 'none' bucket uses balance.vehicle.none.
- BalanceAccountsTable: 4 return columns collapsed by default with a toggle,
persisted across sessions via userPreferenceService key balance_show_returns.
- i18n FR/EN: balance.chart.axis.{byAssetClass,byVehicle}, balance.vehicle.none,
balance.accountsTable.toggleReturns.{show,hide} (+ axisLegend aria label).
Tests: npm run build green (0 type errors); vitest 3314 passed. Added 5
service tests for the 'none' bucket + mixed envelopes + date range.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Before this commit, /balance rendered the BalanceOnboardingCard plus the
period selector + evolution chart + accounts table whenever the user had
no accounts or no snapshot. The lower three components surfaced their
own empty states, producing 3 stacked "no data" messages under the
onboarding card.
Lifts the (accountsCount, hasAnySnapshot) computation out of the inline
IIFE and uses a single isEmpty branch: empty profiles see only the
BalanceOnboardingCard; populated profiles see the full overview.
No logic change — only JSX restructuring. Tests covering useBalanceOverview
and BalanceOnboardingCard remain green (61 tests passing).
Suggestion S2 from PR #184 review (#187).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Before this commit, a brand-new profile briefly showed the
StarterAccountsModal even though the 4 starter accounts were already
seeded — the modal rendered 4 collision rows with no actionable choice
before being dismissed. Pre-seeding the pref in consolidated_schema.sql
suppresses the modal on first /balance visit for new profiles entirely.
Existing profiles already running the app are unaffected: they handle
the modal once on their first /balance visit (the pref-write happens on
dismiss). No migration is needed for them.
Suggestion S1 from PR #185 review (#187).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Part 1 — New profiles: seed 4 starter accounts in
consolidated_schema.sql (Compte chèque/CELI/REER/Compte
non-enregistré, currency CAD, is_active=1) right after the
balance_categories seeds. Categories resolved via SELECT subquery
on the seeded `key` values for robustness.
Part 2 — Existing profiles: StarterAccountsModal proposes the same
4 starters at first /balance visit. Default-checked checkboxes,
collision rule (case-insensitive trim name + matching category)
disables matches with a "Déjà présent" tooltip. The atomic helper
`proposeStarterAccounts` wraps the inserts in BEGIN/COMMIT (rolls
back on error). user_preferences.balance_starter_proposed records
{shown_at, accepted} so the modal never reappears, dismissed or
confirmed.
Part 3 — docs/adr/0012-balance-two-level-model.md (Proposed):
captures the future vehicles × compositions model for reflection,
no code change. Numbered 0012 because 0011 was already taken by
the providers-best-effort-yahoo ADR. Linked from architecture.md
ADR table and Bilan section.
Tests: StarterAccountsModal.test.tsx covers STARTER_ACCOUNTS shape,
getStarterCollisions (case-insensitive trim, category-scoped) and
proposeStarterAccounts (insert order, COMMIT, ROLLBACK on failure).
No render tests — mirrors the BalanceOnboardingCard pattern (no
jsdom configured).
Resolves#179
Replace empty BalanceOverviewCard with BalanceOnboardingCard showing
two steps:
1. Create an account
2. Enter a snapshot
Step 2 is grayed out until at least one account exists; the entire
card is replaced by BalanceOverviewCard once a snapshot is recorded.
Hide "+ New snapshot" button when 0 accounts (it lives inside the
overview card, which is now hidden in that state).
Improve SnapshotEditPage noAccounts copy to clarify account vs
snapshot semantics.
Resolves#178
Issue #142 / Bilan #4 — UI for transfer linking + per-account returns.
- New `LinkTransfersModal.tsx`: portal modal with date-range / category /
free-text filters, multi-select with auto-proposed direction (`in` for
negative bank amounts, `out` for positive — flippable per row).
Submits via sequential `linkTransfer` calls; reports per-row failures
inline (most common case: `transfer_already_linked` on a re-submit).
- `BalanceAccountsTable.tsx`: 4 new columns rendered side-by-side —
3M / 1A / Since-inception (Modified Dietz via `compute_account_return`)
+ Unadjusted (`(V_end - V_start) / V_start`). Returns load lazily
after mount via `Promise.all` over (account × horizon); per-cell
failure leaves the slot at "—" without blocking the rest of the
table. The actions menu gains a *Link transfers* item that bubbles
the request up to the parent page. New props:
`sinceCreationDate` (anchors the since-inception horizon) and
`onLinkTransfers` (modal opener).
- `BalancePage.tsx`: hosts the new modal, loads the categories list
once on mount for the filter dropdown, fetches the union of
`listAccountTransfers` per account so the chart can render markers,
and threads the earliest snapshot date down to the table. Reload
is triggered after the modal reports at least one successful link.
- `balance.service.ts`: dropped the unused `BalanceAccountTransfer`
import to satisfy `tsc --noUnusedLocals`.
`npm run build` clean. `npm test` → 429 passed. Manual sanity check:
the table renders "…" placeholders during the per-row return load,
then resolves to either a percentage or a "—" with the partial
tooltip when the underlying snapshot endpoint is missing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three new components composed under a new BalancePage at /balance:
- BalanceOverviewCard — latest aggregate net worth, Δ% vs the
previous chronological snapshot (rendered as "—" when only
one snapshot exists), 60-day staleness warning, and a
"+ Nouveau snapshot" CTA pointing at /balance/snapshot.
- BalanceEvolutionChart — Recharts-based line / stacked-area
toggle. Line mode plots SUM(value) per snapshot_date with a
single primary-coloured stroke. Stacked mode transposes the
byCategory series into one Area per category_key with a
fixed 10-color palette indexed deterministically. Tooltip
formats CAD via Intl.NumberFormat.
- BalanceAccountsTable — one row per active account with name,
category label, latest value, and Δ% over the active period
(latest_value vs the period anchor). Returns columns
(3M / 1Y / since-creation / unadjusted) reserved for #142
with a TODO marker. Action menu includes a disabled "Detail"
placeholder + functional "Archive" wired through reload().
BalancePage composes the three with an inline period selector
(3M / 6M / 1A / 3A / Tout) and chart-mode toggle, both styled
as segmented controls. State flows through useBalanceOverview.
Route /balance registered before /balance/accounts in App.tsx.
Refs: #141
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>