Commit graph

364 commits

Author SHA1 Message Date
877aff8d6d Merge pull request 'feat(prices): Settings revocation toggle for price_fetching_consent (#159)' (#168) from issue-159-settings-revoke-toggle into main 2026-04-28 01:36:14 +00:00
le king fu
a6097afcf3 chore: drop decisions-log.md (autopilot scratch, conflicts with main cleanup)
All checks were successful
PR Check / rust (push) Successful in 23m34s
PR Check / frontend (push) Successful in 2m30s
PR Check / rust (pull_request) Successful in 23m22s
PR Check / frontend (pull_request) Successful in 2m27s
2026-04-27 21:35:57 -04:00
d140ed938a Merge pull request 'feat(prices): PriceFetchControl + consent modal + best-effort UX (#158)' (#167) from issue-158-pricefetchcontrol into main 2026-04-28 01:35:23 +00:00
le king fu
da4eef2bdd chore: drop decisions-log.md (autopilot scratch, conflicts with main cleanup)
All checks were successful
PR Check / rust (push) Successful in 23m37s
PR Check / frontend (push) Successful in 2m36s
PR Check / rust (pull_request) Successful in 23m18s
PR Check / frontend (pull_request) Successful in 2m27s
2026-04-27 21:35:04 -04:00
le king fu
88c3c04dea chore: untrack decisions-log.md (autopilot scratch from #166 merge) 2026-04-27 21:32:59 -04:00
97f91f87aa Merge pull request 'feat(prices): balance.service prices section + rate-limit + tests (#156)' (#166) from issue-156-balance-service-prices into main 2026-04-28 01:32:41 +00:00
le king fu
55c610c1f2 chore: untrack decisions-log.md (autopilot scratch file)
The autopilot worker prompt instructed writing decisions to
decisions-log.md at worktree root, but didn't exclude it from git
add — so each worker committed its version, causing add/add merge
conflicts between sibling PRs in the prices milestone.

Add to .gitignore so future autopilot runs leave the scratch file
local only.
2026-04-27 21:32:28 -04:00
edd1a5cbe4 Merge pull request 'feat(prices): Rust Tauri command fetch_price + tests (#155)' (#165) from issue-155-rust-fetch-price into main 2026-04-28 01:06:40 +00:00
01cfbdba8b Merge pull request 'feat(prices): useIsPremium hook (#157)' (#164) from issue-157-use-is-premium into main 2026-04-28 01:06:35 +00:00
3b2384af25 Merge pull request 'feat(prices): i18n FR/EN keys + CHANGELOG entries (#160)' (#163) from issue-160-i18n-changelog into main 2026-04-28 01:06:30 +00:00
0511d2ef06 Merge pull request 'feat(prices): commit /v1/prices contract + ADR 0011 (#154)' (#162) from issue-154-contract-and-adr into main 2026-04-28 01:04:48 +00:00
le king fu
80c28d43ac feat(prices): Settings revocation toggle for price_fetching_consent
All checks were successful
PR Check / rust (push) Successful in 28m16s
PR Check / frontend (push) Successful in 3m0s
PR Check / rust (pull_request) Successful in 29m45s
PR Check / frontend (pull_request) Successful in 3m6s
- Adds PriceFetchConsentToggle to SettingsPage Privacy section
- Reads/writes user_preferences.price_fetching_consent for active profile
- Confirmation dialog before revoke (DELETE the key entirely so next click re-opens consent modal)
- Disabled (with notPremium tooltip) when license is not premium
- Adds deletePreference() to userPreferenceService
- Adds settings.privacy.title i18n key (FR + EN)
- 10 vitest tests covering all paths

Closes #159
2026-04-27 08:41:15 -04:00
le king fu
8fa34d786d merge: bring in #158 (transitively #156/#157/#160) 2026-04-27 08:38:16 -04:00
le king fu
043e9bf622 feat(prices): PriceFetchControl component + consent modal + best-effort UX
All checks were successful
PR Check / rust (push) Successful in 30m45s
PR Check / frontend (push) Successful in 3m12s
PR Check / rust (pull_request) Successful in 28m50s
PR Check / frontend (pull_request) Successful in 3m15s
- New component renders button + consent modal + spinner + attribution
- Best-effort warning shown once per session for stock categories
- Hidden if not premium or category kind != 'priced'
- Consent persisted per-profile in user_preferences.price_fetching_consent
- Manual unit_price input remains active in all paths
- 17 vitest tests (no RTL/jsdom — logged MEDIUM in decisions-log.md)
- Wired into SnapshotLineRow/SnapshotEditor/SnapshotEditPage
- asset_type hardcoded to 'stock' pending category schema extension (MEDIUM)

Closes #158
2026-04-27 08:36:23 -04:00
le king fu
c90badae39 merge: bring in balance.service prices namespace from #156 2026-04-27 08:30:53 -04:00
le king fu
99814b9a0d merge: bring in useIsPremium hook from #157 2026-04-27 08:30:50 -04:00
le king fu
b1dc76b487 merge: bring in i18n keys from #160 2026-04-27 08:30:48 -04:00
le king fu
920f81fce5 feat(prices): balance.service prices section with rate-limit + dedup + retries
All checks were successful
PR Check / rust (push) Successful in 27m27s
PR Check / frontend (push) Successful in 2m49s
PR Check / rust (pull_request) Successful in 28m58s
PR Check / frontend (pull_request) Successful in 2m57s
- prices.fetchPrice wraps invoke('fetch_price', ...) with local rate-limit (1/2s), in-flight dedup, exp backoff on 5xx (2/4/8s, max 3 retries), no retry on 4xx/429, hard 100/session cap
- 9 vitest tests with vi.useFakeTimers() (happy, 401/403/404, 429 no-retry, 5xx retries, dedup, pacing, session cap)
- Annexe B i18n mapping wired (PriceError → balance.priceFetching.errors.* keys)
- Session cap checked before rate-limit/dedup; failures do not consume budget (MEDIUM decision)

Closes #156

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 08:28:24 -04:00
le king fu
531624bcb4 feat(prices): Rust Tauri command fetch_price + tests
All checks were successful
PR Check / rust (push) Successful in 25m28s
PR Check / frontend (push) Successful in 2m33s
PR Check / rust (pull_request) Successful in 25m38s
PR Check / frontend (pull_request) Successful in 2m44s
- Add fetch_price command with PriceResponse and FetchPriceError types
- Privacy-strict header policy (Authorization, Accept, User-Agent only)
- Rename SIMPL_API_URL -> MAXIMUS_API_URL across src-tauri
- 7+ mockito tests covering happy path, 401/403/404/429/5xx, and header allowlist
- Fix pre-existing clippy warnings (doc_overindented_list_items, is_multiple_of)

Closes #155
2026-04-27 08:23:18 -04:00
le king fu
98f68f7a1f feat(prices): useIsPremium hook from license.edition
All checks were successful
PR Check / rust (push) Successful in 26m18s
PR Check / frontend (push) Successful in 2m37s
PR Check / rust (pull_request) Successful in 25m0s
PR Check / frontend (pull_request) Successful in 2m41s
- Reads useLicense().state.edition === 'premium'
- Ergonomic only — server enforces independently (ADR 0011)
- 3 vitest tests (premium, base, free)
- CLAUDE.md hook count 12 -> 13

Closes #157
2026-04-27 08:11:23 -04:00
le king fu
ab7e0a3362 feat(prices): i18n FR/EN keys + CHANGELOG entries
All checks were successful
PR Check / rust (push) Successful in 26m25s
PR Check / frontend (push) Successful in 2m34s
PR Check / rust (pull_request) Successful in 26m20s
PR Check / frontend (pull_request) Successful in 2m54s
Closes #160
2026-04-27 08:06:54 -04:00
le king fu
ddb0cb257b docs(prices): commit /v1/prices contract + ADR 0011
All checks were successful
PR Check / rust (push) Successful in 25m39s
PR Check / frontend (push) Successful in 2m42s
PR Check / rust (pull_request) Successful in 26m45s
PR Check / frontend (pull_request) Successful in 2m58s
- Add frozen v2 /v1/prices API contract (docs/api-contract-prices.md)
- Add ADR 0011: providers best-effort Yahoo (docs/adr/0011-providers-best-effort-yahoo.md)
- Add dedicated ADR table section to docs/architecture.md (rows 0001-0011)

Closes #154
2026-04-27 08:06:03 -04:00
c14de9a6f8 Merge pull request 'feat(license): rotate Ed25519 public key for maximus-api (#49)' (#137)
Closes #49 — maximus-api now serves /licenses/* in production at https://api.lacompagniemaximus.com

Fixes #49
2026-04-26 13:45:01 +00:00
le king fu
97680417ee feat(license): rotate embedded Ed25519 public key (#49)
All checks were successful
PR Check / rust (push) Successful in 22m25s
PR Check / frontend (push) Successful in 2m25s
PR Check / rust (pull_request) Successful in 22m19s
PR Check / frontend (pull_request) Successful in 2m24s
Replace the placeholder public key with the one whose private
counterpart is now held by the maximus-api license server. The old
key had no licenses issued against it (the server did not exist), so
no users are affected.

The 34 Rust unit tests still pass — license_commands tests use
ad-hoc test keypairs rather than the embedded one, and
embedded_public_key_pem_parses confirms the new PEM is valid.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 09:42:40 -04:00
9c79b73871 Merge pull request 'docs(balance): architecture + ADRs + user guide (#145)' (#153) from issue-145-bilan-7 into main 2026-04-26 13:25:43 +00:00
51a6cec8f1 Merge pull request 'test(balance): cross-cutting integration tests (#144)' (#152) from issue-144-bilan-6 into main 2026-04-26 13:25:37 +00:00
8df1aed258 Merge pull request 'feat(balance): Modified Dietz returns + transfer linking (#142)' (#151) from issue-142-bilan-4 into main 2026-04-26 13:25:32 +00:00
47ecf886d2 Merge pull request 'feat(balance): /balance page + evolution chart + sidebar (#141)' (#150) from issue-141-bilan-3 into main 2026-04-26 13:25:26 +00:00
6341aeb74c Merge pull request 'feat(balance): priced-kind support (#140)' (#149) from issue-140-bilan-2 into main 2026-04-26 13:25:20 +00:00
a344eab2bb Merge pull request 'feat(balance): SnapshotEditPage + simple-kind editor (#146)' (#148) from issue-146-bilan-1b into main 2026-04-26 13:25:15 +00:00
b6387f4b31 Merge pull request 'feat(balance): schema migration v9 + service skeleton + AccountsPage (#138)' (#147) from issue-138-bilan-1a into main 2026-04-26 13:25:09 +00:00
le king fu
ce15c903e4 docs: i18n + CHANGELOG for Bilan documentation
All checks were successful
PR Check / rust (push) Successful in 22m56s
PR Check / frontend (push) Successful in 2m23s
- Add docs.balance.* keys (FR + EN) for the new Balance Sheet section
  consumed by DocsPage (title / overview / features / steps / tips).
- Wire the new section into DocsPage.tsx SECTIONS array with a Wallet
  icon, slotted between reports and settings.
- Add bilingual [Unreleased] CHANGELOG entry for #145 covering the
  architecture.md updates, the 3 ADRs, the new guide section and the
  i18n keys.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 17:06:53 -04:00
le king fu
bef330affb docs(guide): add user guide section for Bilan
New section 10 "Bilan" walks through:
- the 3 routes (/balance, /balance/snapshot, /balance/accounts)
- snapshot entry (simple + priced kinds, prefill button)
- transfer linking with auto-suggested direction
- multi-horizon Modified Dietz returns (3M / 1Y / since inception)
  read alongside the unadjusted return for comparison
- the FK RESTRICT user-facing message on linked-transaction deletion
- price-fetching premium flagged "coming Phase 5"

Renumbers Settings to section 11.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 17:06:46 -04:00
le king fu
098e15bb5c docs(adr): add ADRs 0008-0010 (Modified Dietz, proxy price-fetching, FK RESTRICT)
- 0008 — Modified Dietz: justifies the choice over ROI / TWR / IRR;
  references commands/return_calculator.rs and the 7 TDD cases.
- 0009 — Proxy price-fetching via maximus-api: documents the privacy
  proxy architecture (header stripping, no log correlation, fixed
  simpl-resultat UA), the Yahoo + CoinGecko adapter abstraction, the
  Bearer activation_token auth strategy, the rate limiting (client
  + server), and the dual-side premium gating. Implementation stays
  BLOCKED in #143; this ADR documents the agreed-upon design.
- 0010 — FK ON DELETE RESTRICT on balance_account_transfers
  .transaction_id: justifies the integrity-over-friction trade-off
  for Modified Dietz reproducibility.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 17:06:40 -04:00
le king fu
4d5a0e2e3b docs(architecture): document balance domain (tables, services, hooks, commands, routes)
Update docs/architecture.md to reflect the Bilan feature:
- BDD: 5 new tables + 7 indexes + CHECK + FK invariants (CAD lock,
  kind invariants, ON DELETE RESTRICT on transaction_id)
- Migrations: v8 + v9 added to history
- Services: balance.service.ts with its 4 logical sections
- Hooks: useBalanceAccounts / useSnapshotEditor / useBalanceOverview
- Commands: compute_account_return (1 new) + Phase 5 fetch_price stub
- Routing: /balance, /balance/snapshot, /balance/accounts
- Components: balance/ folder added in tree

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 17:06:30 -04:00
le king fu
5274e51907 chore: CHANGELOG entry for cross-cutting tests
All checks were successful
PR Check / rust (push) Successful in 22m44s
PR Check / frontend (push) Successful in 2m23s
Bilingual entry under [Unreleased] documenting the integration test
suite added for Issue #144: end-to-end happy path, currency lock,
priced-kind tolerance safety, computeAccountReturn wiring, three Rust
migration-on-seeded-DB scenarios, and the source-level non-regression
test on the inlined transfer icon.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:54:04 -04:00
le king fu
5a54d37de5 test(transactions): add non-regression test for inline transfer icon
Source-level structural test on `TransactionTable.tsx` to lock down the
inlined transfer icon contract introduced in #142. Without RTL or jsdom
in the dev-deps, the test reads the component source and asserts:

  - the icon is gated by `linkedTransfersByTxId?.has(row.id)`,
  - optional-chaining short-circuits cleanly when the prop is omitted
    (zero-impact on pre-#142 callers),
  - the prop is declared OPTIONAL on the component interface,
  - the `Link2` glyph comes from lucide-react,
  - tooltip + aria-label go through `transactions.transferIcon.*` i18n
    keys,
  - the row's description cell layout (truncate span + title) stays
    shared between linked and non-linked rows.

Catches the specific regression vectors: someone removing the gate,
renaming the prop, or breaking the optional-chaining pattern that
guarantees the page renders identically when no transfers are linked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:53:59 -04:00
le king fu
50fe0ab1ac test(balance): add migration v9 integration on seeded DB
Three new Rust integration tests applied at the bottom of `lib.rs`'s
`#[cfg(test)] mod tests`. They exercise the realistic upgrade path: a v1
profile DB with imported transactions + categories already there gets the
v9 migration applied on top.

`migration_v9_preserves_existing_transactions_on_seeded_db` asserts no
row loss / data mutation after the migration runs. Spot-checks one
amount preserved verbatim and that the v9 seeded categories coexist with
the v1 categories table.

`integration_link_unlink_transfer_roundtrip_on_seeded_db` walks link →
joined-view read → blocked deletion (FK RESTRICT) → unlink → allowed
deletion → orphan-row sanity check. Covers the FK chain end-to-end on
real (non-stub) transaction ids.

`integration_modified_dietz_inputs_read_back_correctly_on_seeded_db`
mirrors the exact SQL used by `balance_commands.rs::read_value_at_or_before`
and `read_cash_flows`, asserting the snapshot-endpoint lookups and the
period-bounded JOINed cash flows return the expected shapes when run
against a seeded v1+v9 DB.

`integration_v9_preserves_v1_categories_and_keywords` verifies the
`categories.id` and `balance_categories.id` namespaces are independent
(same numeric id allowed on each table without collision).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:53:50 -04:00
le king fu
9adfb85d84 test(balance): add cross-cutting integration tests
End-to-end happy path through the full Bilan stack: account → priced
category → priced snapshot → linked transfer → return. Drives every
service against the existing in-memory FakeDb harness used by
category-migration tests so SQL shape (table names + parameters) can be
asserted alongside service outputs.

Currency lock: USD / EUR / GBP / JPY / AUD all rejected up-front by the
service with a typed `currency_unsupported` code, no DB hit. The CAD
default is verified to land in the INSERT params explicitly.

Priced-kind safety: a snapshot save with one out-of-tolerance line must
NOT clear pre-existing lines (the DELETE is gated behind the validation
loop). A drift just within ε is accepted unchanged.

computeAccountReturn wiring: malformed dates are rejected client-side
without invoking the Rust command; missing active profile yields a typed
`transfer_active_profile_unknown`; partial-period payloads are forwarded
unchanged (null fields preserved).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:53:36 -04:00
le king fu
ca275821bc feat(balance): i18n + CHANGELOG for returns/transfers
All checks were successful
PR Check / rust (push) Successful in 22m37s
PR Check / frontend (push) Successful in 2m30s
Issue #142 / Bilan #4 — translations and changelog entries.

i18n (FR + EN):
- `balance.returns.partialTooltip`, `balance.returns.noTransfersWarning`
- `balance.accountsTable.return3m/return1y/sinceCreation/unadjusted`
  (label + tooltip variants)
- `balance.transfers.linkAction` + `balance.transfers.direction.{in,out}`
- `balance.transfers.modal.*` (every modal label, including the
  partial-failure summary and the per-row direction toggle)
- `balance.transfers.errors.*` (5 new typed error codes)
- `balance.evolution.transferIn/transferOut` (chart label)
- `transactions.transferIcon.tooltip/ariaLabel`

CHANGELOG (English source + French translation):
- New entry under `[Unreleased]` summarising the Modified Dietz
  formula, the per-account return columns (3M / 1A / since-inception
  + unadjusted), the link-transfers modal, the transactions-page
  inline icon, the typed FK error on bulk-delete paths, and the
  vertical reference markers on the evolution chart. References #142.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:39:06 -04:00
le king fu
faa09614a3 feat(balance): add transfer markers on evolution chart
Issue #142 / Bilan #4 — vertical reference lines for tagged transfers.

`BalanceEvolutionChart.tsx` accepts a new optional prop
`transferMarkers?: BalanceAccountTransferWithTransaction[]`. For every
marker whose `transaction_date` matches a date already on the X axis,
the chart renders a `<ReferenceLine>` (Recharts) — green for `in`
(capital added), red for `out` (capital removed). The marker is drawn
in both `line` and `stacked` modes; in line mode an inline label
("In" / "Out") sits at the top-right of the marker so the user can
identify the direction without hovering.

Markers whose date is between two snapshot ticks are filtered out
(Recharts categorical axis silently drops unknown ticks; preferred
over an off-axis bug). A future improvement is to switch the X axis
to a numeric/time scale so markers can land anywhere — out of scope
here per the autopilot prompt's "least invasive" guideline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:38:55 -04:00
le king fu
0e996a5aa1 feat(transactions): inline transfer icon + FK error message
Issue #142 / Bilan #4 — non-regressive transfer awareness in the
transactions table + clean error mapping on bulk delete.

- `TransactionTable.tsx`: optional new prop
  `linkedTransfersByTxId?: Map<txId, links[]>`. When supplied, a small
  `<Link2>` icon appears next to the description for every linked
  transaction; tooltip lists the account name(s) and direction(s).
  Without the prop, the table renders byte-for-byte identical to
  before — preserves the spec's non-regression invariant.
- `TransactionsPage.tsx`: loads the linked-transfers map once on mount
  via `listAllLinkedTransfersForTooltip()` (one batch SELECT) and
  threads it through to the table. Failure to load the map degrades
  gracefully to an empty map (icon simply doesn't appear).
- `transactionService.ts`: new `deleteTransaction(id)` helper +
  `TransactionLinkedToBalanceError` (typed FK guard). Pre-checks
  `balance_account_transfers` before attempting the DELETE so the
  error carries the offending account names; falls back to the FK
  pattern matcher if a race linked the transaction between the
  SELECT and the DELETE.
- `importedFileService.ts`: both bulk delete paths
  (`deleteImportWithTransactions`, `deleteAllImportsWithTransactions`)
  now pre-check for any linked transfer and surface the same typed
  error before they would explode on FK RESTRICT. The pre-check has
  a `LIMIT 50` safety cap on the global path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:38:46 -04:00
le king fu
a45e5c3cd0 feat(balance): add LinkTransfersModal + return columns in accounts table
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>
2026-04-25 16:38:24 -04:00
le king fu
dafdd4ce17 feat(balance): add returns + transfers section to balance.service
Issue #142 / Bilan #4 — TS bridge for the Modified Dietz command + plain
CRUD for transfer linking.

Types (`src/shared/types/index.ts`):
- `BalanceTransferDirection` ('in' | 'out')
- `BalanceAccountTransfer` (raw row) +
  `BalanceAccountTransferWithTransaction` (joined view)
- `AccountReturn` (mirrors the Rust struct, ready to receive the invoke
  payload as-is)

Service (`src/services/balance.service.ts`):
- `computeAccountReturn(accountId, periodStart, periodEnd)`: resolves the
  active profile's `db_filename` from `loadProfiles()` and calls the
  `compute_account_return` Tauri command.
- `linkTransfer(accountId, transactionId, direction, notes?)`: INSERT
  with duplicate guard (typed `transfer_already_linked` error instead of
  raw SQL UNIQUE failure).
- `unlinkTransfer(accountId, transactionId)`: DELETE with
  `transfer_not_linked` guard for stale-UI calls.
- `listAccountTransfers(accountId, dateRange?)`: joined SELECT for
  modal/list rendering.
- `listLinkedTransactionIds()`: returns a `Set<number>` for the
  transaction icon (one query, in-memory `.has()` lookups thereafter).
- `listAllLinkedTransfersForTooltip()`: returns
  `Map<transactionId, links[]>` for tooltip rendering.
- `suggestTransferDirection(amount)`: pure helper for the modal — maps
  negative bank amounts to 'in', positive to 'out'.
- `isLinkedTransactionFkError(error)`: detects the canonical SQLite "FK
  constraint failed" text so `transactionService.deleteTransaction` can
  surface a clear i18n message.
- 5 new error codes added to `BalanceErrorCode`.

Tests (`balance.service.test.ts`): 22 new vitest cases bringing the file
to 85 passed. Mocks `@tauri-apps/api/core` `invoke` and
`./profileService` `loadProfiles`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:27:16 -04:00
le king fu
23ff8466c0 fix(balance): use transactions.date column (not transaction_date)
The schema's transactions table uses `date` (see schema.sql:67), not
`transaction_date`. Compile-checked the column name was correct.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:24:13 -04:00
le king fu
0381dd48bb feat(balance): add compute_account_return Tauri command
Issue #142 / Bilan #4 — server-side Modified Dietz wrapper.

- New `src-tauri/src/commands/balance_commands.rs` with single command
  `compute_account_return(db_filename, account_id, period_start, period_end)`:
  - Opens the active profile DB via `rusqlite::Connection::open(app_data_dir
    / db_filename)` — matches `repair_migrations` / `delete_profile_db`.
  - Reads `value_start` (latest snapshot ≤ period_start) + `value_end`
    (latest snapshot ≤ period_end) via correlated SELECT.
  - Reads cash flows via JOIN `balance_account_transfers` ⨝
    `transactions` filtered by `transaction_date BETWEEN`. Sign applied
    per direction (`in` → +, `out` → −).
  - Calls `return_calculator::modified_dietz`, returns typed
    `AccountReturn`.
- Registered in `commands/mod.rs` (pub use) and in `lib.rs`'
  `tauri::generate_handler!` array.

`cargo check` clean. `cargo test --lib` → 54 passed (including the 7
return_calculator + 7 migration_v9 tests).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:23:14 -04:00
le king fu
c9cdb5a891 feat(balance): add chrono dep + Modified Dietz return_calculator with tests
Issue #142 / Bilan #4 — TDD step 1.

- Added `chrono = "0.4"` (default-features off, `serde` + `std` features)
  to `src-tauri/Cargo.toml` for day-precision date arithmetic.
- New private module `src-tauri/src/commands/return_calculator.rs`:
  - `pub(crate) fn modified_dietz(value_start, value_end, cash_flows,
    period_start, period_end) -> AccountReturn`
  - `AccountReturn { value_start, value_end, net_contributions, return_pct,
    annualized_pct, is_partial, has_no_transfers_warning }` (Serialize)
  - Edge cases handled: missing start/end snapshot (`is_partial = true`,
    `return_pct = None`), no transfers (collapses to simple return + warn
    flag), zero-length period (skips annualization), V_start = 0 with first
    flow > 0 (account-created mid-period), depleted-then-refilled (no
    panic, finite output).
- 7 co-located TDD tests covering nominal + every edge case above.
- Module declared `pub(crate)` in `commands/mod.rs` (kept out of the
  wildcard re-export — only `balance_commands.rs` will consume it).

`cargo test --lib commands::return_calculator` → 7 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:21:37 -04:00
le king fu
1e261ae2ea feat(balance): i18n + CHANGELOG for /balance page
All checks were successful
PR Check / rust (push) Successful in 22m24s
PR Check / frontend (push) Successful in 2m22s
FR + EN translations under:
- nav.balance — sidebar label
- balance.overview.* — page title, latest total, Δ% vs previous,
  staleness warning, new-snapshot CTA, accounts table headers,
  empty/no-snapshot states
- balance.period.* — 3M / 6M / 1A / 3A / all selector labels
- balance.chart.* — empty state, mode legend, line / stacked
  toggle labels
- balance.sidebar — entry label (mirrors nav.balance)

CHANGELOG entry under [Unreleased] / Added documenting the new
page, period selector, evolution chart modes, accounts table,
sidebar entry, the four service helpers, and the new hook.

Refs: #141

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:08:10 -04:00
le king fu
83ac484a22 feat(balance): add sidebar Bilan entry
Insert a new "Bilan" / "Balance sheet" entry in NAV_ITEMS pointing
at /balance with the Wallet lucide-react icon. Position: between
Reports and Settings, matching the autopilot prompt instruction
and the spec-plan-bilan v2 ordering.

Sidebar.tsx imports Wallet and registers it in iconMap.

Refs: #141

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:07:13 -04:00
le king fu
ffefa90fd0 feat(balance): add BalancePage with chart + accounts table
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>
2026-04-25 16:07:04 -04:00