Commit graph

98 commits

Author SHA1 Message Date
le king fu
64ae00a847 bugfix(reports): open Trends on byCategory + table by default
All checks were successful
PR Check / rust (pull_request) Successful in 24m51s
PR Check / frontend (pull_request) Successful in 2m34s
The Trends report previously opened on the global monthly chart, which
has no income section — so a user's revenue (e.g. their pay) was hidden
until they manually switched to the "By category" table view.

Open the report on byCategory + table by default so the income section
(and therefore payroll) is visible immediately. Two scoped call-site
changes:
- useTrends initial subView: "global" -> "byCategory"
- ReportsTrendsPage viewMode fallback: readViewMode(key, "table")

Both defaults are scoped to Trends. The real-vs-real compare report
(distinct "reports-viewmode-compare" storage key) is unaffected, and a
user's own persisted view choice still wins over the new fallback.

Resolves #262

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:58:25 -04:00
le king fu
616cccec37 feat(reports): collapse/expand sub-categories in comparable reports (#254)
All checks were successful
PR Check / rust (pull_request) Successful in 22m52s
PR Check / frontend (pull_request) Successful in 2m24s
The two hierarchical comparable reports — real-vs-real Compare and
real-vs-budget — now let the user collapse or expand each top-level
category's sub-categories. Groups start collapsed by default (#260): only
the parent's subtotal row shows until the user expands it, and each expanded
group is remembered per report.

- New pure module utils/collapsibleRows.ts (visibility filter, group-key
  extraction, expanded-set (de)serialization) + useCollapsibleGroups hook
  wrapping the localStorage-persisted expanded set. Persisting the *expanded*
  set (not the collapsed one) makes "all collapsed" the zero/default state.
- A chevron toggles each top-level parent; an "Expand all / Collapse all"
  button toggles them together. Section subtotals and result lines keep
  summing every leaf, so collapsing never changes any total.
- Accessors mirror each table's own depth/parent logic so hidden rows are
  exactly a group's indented descendants.
- i18n keys reports.collapse.{expandAll,collapseAll} (FR/EN); CHANGELOG.

CategoryOverTimeTable (Trends -> by category) is intentionally left out: its
rows are a flat top-N category list with no parent/child hierarchy to fold.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 20:18:52 -04:00
le king fu
f3ccb115ae chore: release v0.12.0
All checks were successful
Release / build-and-release (push) Successful in 25m5s
2026-07-05 20:59:03 -04:00
le king fu
44976767d1 Merge PR #257: feat(reports) trend-by-category = income statement (#256)
# Conflicts:
#	.gitignore
#	CHANGELOG.fr.md
#	CHANGELOG.md
2026-07-05 20:53:11 -04:00
le king fu
82040abc74 feat(reports): category-over-time table as result analysis (sections + net result per month)
All checks were successful
PR Check / rust (pull_request) Successful in 22m9s
PR Check / frontend (pull_request) Successful in 2m28s
Apply the compare report's "result analysis" presentation to Trends -> By
category (table view). Categories are grouped into Income -> Expenses ->
Transfers sections with per-month and total subtotals; the old mixed "Total"
row (which summed absolute magnitudes into a meaningless figure) is replaced by
a Net result row per month (income - expenses + transfers) plus a Result before
transfers row shown only when transfers exist, both coloured by sign. Category
cells stay neutral levels.

- reportService.getCategoryOverTime: project COALESCE(c.type,'expense') in both
  SELECTs and return an additive `types` map (built from the top-N rows, like
  `colors`). Dashboard widget and CategoryOverTimeChart are untouched.
- shared types: add `types` to CategoryOverTimeData.
- new pure module overTimeResults.ts (computeOverTimeResults) with unit tests -
  the per-month result reducer, mirroring the compare's pure Totals helper.
- i18n: add reports.compare.resultNet / resultBeforeTransfers (FR+EN); reuse the
  existing sections.* and total* keys.
- .gitignore: anchor the autopilot `reports/` rule to `/reports/` so it no
  longer swallows new files under src/components/reports/.

Section order is income-first (Revenus -> Depenses -> Transferts), per the
issue; the existing COMPARE_TYPE_ORDER is expense-first, so a local
OVER_TIME_TYPE_ORDER is used rather than reusing it.

Resolves #256

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 20:15:50 -04:00
le king fu
f45845e408 feat(reports): real-vs-real compare as an income statement + previous-month default
All checks were successful
PR Check / rust (pull_request) Successful in 23m4s
PR Check / frontend (pull_request) Successful in 2m26s
The "actual vs actual" compare was expenses-only (WHERE amount < 0), so revenue
categories were invisible — you could not tell a surplus from a deficit. Make it
an income statement (the product's namesake "résultat"):

- COMPARE_DELTA_SQL: broaden the WHERE and per-type CASE to include `income` as
  a signed SUM (like `transfer`), so revenue credits survive the outflow filter.
  Expenses stay ABS-of-outflows, byte-identical. Transfer netting (#243) intact.
- Section order is now income -> expense -> transfer (COMPARE_TYPE_ORDER).
- ComparePeriodTable: replace the now-meaningless flat grand total with two
  result lines — "result before transfers" (revenues - expenses) and the net
  "result" (after transfers) — extracted into a pure, tested compareResults
  module. Delta colours are direction-aware (income/result up = green, spending
  up = red); result amounts are coloured by sign (surplus/deficit).
- ComparePeriodChart stays a spending view: filter to expense leaves so revenue
  bars don't mix into the same axis.
- useCompare: skip the initial period-sync so the compare opens on the previous
  (last complete) month instead of the civil-year December that useReportsPeriod
  yields by default.

Also anchor the `reports/` gitignore to `/reports/` — the unanchored rule was
silently ignoring new files under src/components/reports/.

Tests: new compareResults.test.ts (result roll-up, unbalanced transfer, deficit,
subtotal exclusion); reportService.test.ts updated for the broadened SQL, the
income bucket rule, and income-first section order. Build + 685 vitest green.

Resolves #253

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 18:36:57 -04:00
le king fu
206f89e1b1 chore: release v0.11.0
All checks were successful
Release / build-and-release (push) Successful in 24m28s
2026-07-05 17:10:00 -04:00
le king fu
9124c888de Merge PR #252: feat(categories) editable migration target with type-ahead (#246) 2026-07-05 17:04:15 -04:00
le king fu
2a3cc07a0e Merge PR #251: feat(balance) CSV import of holdings in detailed snapshot (#245) 2026-07-05 17:03:00 -04:00
le king fu
ead62943fe Merge PR #250: feat(balance) tile-based landing hub + reachable account management (#244) 2026-07-05 17:01:20 -04:00
le king fu
e45736bbde feat(categories): editable migration target with type-ahead (#246)
All checks were successful
PR Check / rust (pull_request) Successful in 22m32s
PR Check / frontend (pull_request) Successful in 2m28s
Make the v1 target editable on EVERY row of the categories-migration
preview (StepSimulate), not only the "needs review" ones. Each row now
renders a reused CategoryCombobox (accent-insensitive type-ahead,
keyboard, hierarchical) in place of the read-only text / flat <select>.

A pure adapter (taxonomyToComboboxCategories) flattens the full v1
taxonomy into Category-shaped rows once in StepSimulate and passes it as
a prop to the rows. The full tree (not just leaves) is fed so mid-tree
default targets such as "Divertissement" (id 1710, the default for
"Jeux, Films & Livres") both display and can be re-picked. The target
cell stops click/keydown propagation so picker interaction never toggles
the row preview. Reducer/guard behavior unchanged: resolving a "none"
row still bumps confidence to medium, and the unresolved-count guard
stays functional.

Resolves #246
2026-07-04 18:17:10 -04:00
le king fu
c8c765e2f0 feat(balance): CSV import of holdings in detailed snapshot
All checks were successful
PR Check / rust (pull_request) Successful in 22m45s
PR Check / frontend (pull_request) Successful in 2m25s
A detailed (by-security) account can now import its positions from a CSV
instead of adding each security one by one. An "Import CSV" button next to
"Add a title" opens a native picker; the delimiter, encoding and columns
(symbol, quantity, optional price + book_cost) are auto-detected via new
csvAutoDetect helpers and adjustable in a small mapping editor. Rows sharing
a symbol are merged (SUM qty + book_cost, first price) and the batch is merged
into the basket by normalized symbol, so no UNIQUE(snapshot_line_id,
security_id) violation can occur at save. A CSV without a price column imports
quantities with an empty unit_price (fetch/type later) — the existing atomic
save path is unchanged (no SQL/Rust change).

- csvAutoDetect: autoDetectHoldingColumns + analyzeHoldingsCsv (+ tests)
- useSnapshotEditor: holdingsFromCsvRows + IMPORT_HOLDINGS action + importHoldings (+ tests)
- HoldingsCsvImportModal: file pick -> parse -> mapping editor + preview
- i18n FR/EN under balance.snapshot.detailed.importCsv.*
- CHANGELOG (Added / Ajouté)

Resolves #245

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 18:04:12 -04:00
le king fu
82550d6d38 feat(balance): tile-based landing hub + reachable account management
All checks were successful
PR Check / rust (pull_request) Successful in 25m34s
PR Check / frontend (pull_request) Successful in 2m30s
Decouple the /balance empty-state guard into three explicit states via a
new pure helper `deriveLandingState` (empty / accounts-no-snapshot /
populated). Having accounts but no snapshot no longer strands the user on a
snapshot-only onboarding card: each state now renders navigation tiles
(HubReportNavCard, modeled on the Reports hub), and "Manage accounts"
(/balance/accounts) is reachable in every state, including the populated
dashboard.

- Remove BalanceOnboardingCard (component + test) and the dead
  balance.onboarding.* i18n keys; add balanceLanding.ts + test.
- Add balance.hub.* and balance.landing.* keys in FR and EN.
- CHANGELOG (EN + FR) under [Unreleased].

Resolves #244
2026-07-04 17:43:43 -04:00
le king fu
f3e8e94b16 feat(reports): hierarchical real-vs-real compare with subtotals
Render the real-vs-real comparable report as a parent/child category tree
with subtotal rows, mirroring the budget report — rows grouped into
expense/income/transfer sections with per-section and grand totals and a
subtotals-on-top/bottom toggle. The compare service now builds the tree on
top of the flat per-category deltas, so leaf-category figures are unchanged
and the #243 transfer netting is preserved (a balanced transfer group
subtotals to ~0, i.e. the group's net).

- reportService: buildCompareTree() synthesizes subtotal rows from the flat
  leaves + category metadata; getCompareMonthOverMonth/YoY return the tree
  (COMPARE_DELTA_SQL and rowsToDeltas untouched).
- CategoryDelta gains optional parent_id/is_parent/depth/category_type.
- ComparePeriodTable: sections, depth indentation, reorderRows toggle,
  section/grand net totals.
- Cartes top-movers and ComparePeriodChart filter to leaf rows only.
- reorderRows constraint loosened to the two fields it reads.
- i18n (FR+EN) section labels; CHANGELOG entries.

Resolves #247

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 17:32:38 -04:00
le king fu
4b000d022f fix(reports): net transfer-type categories in real-vs-real compare
All checks were successful
PR Check / rust (pull_request) Successful in 23m23s
PR Check / frontend (pull_request) Successful in 2m25s
Reports > Compare (real vs real) summed only outflows (SUM(ABS(amount))
filtered on amount < 0), so a `transfer` category like "Paiement CC" -
whose debit and matching credit should cancel - always showed the sum of
its debits instead of ~0.

Net transfer-type categories via a signed SUM(t.amount) while keeping the
expense behavior (ABS + amount < 0) for every other type, and broaden the
WHERE so transfer credits survive the expense filter. Both MoM and YoY now
share a single COMPARE_DELTA_SQL constant so they stay in sync. Real-vs-
budget already nets (signed SUM, no filter) and is unchanged.

Resolves #243

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 17:08:17 -04:00
le king fu
54ef0f0229 fix(deps): scope @babel/core to 7.29.7 via override (fixes #241)
All checks were successful
PR Check / rust (pull_request) Successful in 23m2s
PR Check / frontend (pull_request) Successful in 2m30s
@babel/core@7.29.0 (pulled transitively by @vitejs/plugin-react, build-only)
was flagged by GHSA-4x5r-pxfx-6jf8 (arbitrary file read via sourceMappingURL,
low, CVSS 3.2). npm audit fix cannot resolve it — the version is pinned by the
parent — so a scoped overrides entry forces the patched 7.29.7 within the
parent's accepted 7.x range (non-breaking). npm audit now reports 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:43:28 -04:00
le king fu
4dc3baaa9b state: sync after deps-security-2026-07 sprint (4/4) + changelog security entry
Milestone deps-security-2026-07 shipped 4/4 (closed): PR #239 (react-router-dom
7.18.1, fixes #235+#238) + PR #240 (vite 6.4.3 + vitest 4.1.9, fixes #236+#237).
npm audit 5 -> 1 (only @babel/core low remains, out of scope). Added [Unreleased]
Security entries in both changelogs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 20:22:45 -04:00
le king fu
0eb7a3887d chore: release v0.10.1
All checks were successful
Release / build-and-release (push) Successful in 26m19s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 17:22:13 -04:00
le king fu
8cb7e53698 fix(db): serialize DB access to fix "database is locked"; live-update log console
All checks were successful
PR Check / rust (pull_request) Successful in 24m30s
PR Check / frontend (pull_request) Successful in 2m38s
Database lock (the reported 0.10.0 regression):
- tauri-plugin-sql loads SQLite through a default multi-connection sqlx pool
  (Pool::connect => max_connections = 10) and exposes no JS transaction
  primitive. BEGIN and COMMIT issued as separate db.execute calls could land on
  different pooled connections, stranding an open write transaction on an idle
  connection — a permanent write lock until app restart. Triggered when the
  Bilan page's concurrent reads interleaved with an in-flight snapshot save.
- Fix: funnel every db operation through one FIFO lock in db.ts; withTransaction
  holds it across the whole BEGIN..COMMIT so a transaction's statements never
  span connections (and the saves become genuinely atomic). A reentrancy guard
  lets nested getDb() calls run directly instead of deadlocking. Applied to all
  5 transaction sites (saveSnapshotAtomic, upsertSnapshotLines,
  proposeStarterAccounts, applyKeywordWithReassignment, applyMigration) via
  in-place helper extraction. New db.test.ts covers serialization,
  cross-transaction non-interleaving, reentrancy, and lock release on error.

Log console (Settings -> Systems -> Journaux):
- getLogs returned the same mutated array reference, so useSyncExternalStore
  (identity comparison) never re-rendered on a new log -> the console never
  updated live. getLogs now returns an immutable snapshot rebuilt on each
  mutation (stable between mutations, new identity after each).
- Add logInfo/logWarn/logError app-logging API and instrument the snapshot save
  (info on success, error on failure) so DB issues surface in the console.

tsc + 635 vitest + vite build green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 16:50:39 -04:00
le king fu
36291585b2 chore: release v0.10.0
All checks were successful
Release / build-and-release (push) Successful in 29m11s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 17:13:10 -04:00
le king fu
d41ccbd618 docs(balance): ADR 0015 + guide + architecture + CHANGELOG for per-security detail (#218)
Final docs link of the Étape 2 stack (#210-#217).

- ADR 0015 (Accepted): holdings-per-snapshot model; aggregated-line-is-source-of-truth invariant (why non-breaking, cites ADR 0008 Modified Dietz per account); transaction-based alternative rejected (PP PR #779); latent gain vs per-security Modified Dietz (out of scope); detailed_since authoritative pivot; security-immortal-once-referenced (ON DELETE RESTRICT). Mirrors ADR 0014 structure.
- Guide: new 'Détail par titre' section in docs/guide-utilisateur.md (per-security entry, detail-account wizard, latent gain) + matching docs.balance.* i18n keys (FR + EN, in-app guide surface).
- architecture.md + CLAUDE.md: reconciled stale DB counters to real values — 20 tables / 24 indexes / 16 migrations (were 13/15/7 and 18/16/v9). Étape 2 delta: +2 tables (balance_securities, balance_snapshot_holdings) + 2 indexes + migrations v14/v15/v16. Backfilled v10-v16 in the migrations table, ADR table (0012 Rejected, +0013/0014/0015), new securities/detailed-save/latent-gain service surface.
- CHANGELOG.md + CHANGELOG.fr.md [Unreleased]: extended with #215 (wizard), #216 (drill-down + latent gain), #211 (auto-conversion note); did not duplicate #214's per-security entry. Bilingual parity.

Docs-only: no production TS/Rust logic touched. Gate green: build (tsc+vite) + 627 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 14:17:03 -04:00
le king fu
4846120b0f feat(balance): multi-security snapshot entry UI + SecurityPicker (#214)
Turn the detailed-account snapshot variant into the real per-title entry
surface (building on the minimal sub-rows from #213):

- New SecurityPicker (src/components/balance/SecurityPicker.tsx): an
  autocomplete combobox over the existing balance_securities catalogue
  (loaded via listSecurities()) with inline creation. Accepts any
  normalized symbol (UPPER/TRIM) with NO live ticker validation — the
  price fetch is best-effort and separate. On pick/create it emits a
  SecurityPick {symbol, asset_type, name, isNew}; a stock/crypto toggle
  lets the user set the asset class when creating a new symbol (default
  'stock'). Built on the CategoryCombobox UI idiom (ARIA listbox,
  keyboard nav, click-outside). Pure helpers filterSecurities /
  decideCreateOption are exported and unit-tested (no jsdom harness).

- SnapshotLineRow detailed sub-rows: labeled columns
  [title (SecurityPicker), quantity, price (+ existing PriceFetchControl),
  value (qty x price, read-only), book_cost, live unrealized gain].
  Account value = displayed SUM of positions. Simple accounts unchanged.

- useSnapshotEditor: new SET_HOLDING_SECURITY action + setHoldingSecurity
  callback (atomically sets symbol + asset_type + name and drops the
  stale fetched-price attribution since the symbol changed). The
  securities catalogue is loaded in loadForDate and exposed as
  state.securities, so it refreshes after a save that creates a security.

- i18n: extended balance.snapshot.detailed.* (col.*, picker.*, book cost,
  unrealized gain) in FR + EN — no hardcoded UI text.

- CHANGELOG (EN + FR) under [Unreleased]: first user-visible surface of
  the per-title detail chain (#210-#213 were schema/service/reducer).

Build (tsc + vite) green; npm test green (613 tests, +10 SecurityPicker).

Generated autonomously by /autopilot run of 2026-06-06

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 13:39:42 -04:00
le king fu
ebc709a277 docs(balance): ADR 0014 + reject 0012 + guide + changelog (#205)
All checks were successful
PR Check / rust (pull_request) Successful in 22m24s
PR Check / frontend (pull_request) Successful in 2m22s
Document Étape 1 of the balance audit (vehicle_type axis), already shipped
in #202/#203/#204:
- ADR 0014 (Accepted): fiscal envelope is an account attribute, the category
  is a pure asset class; Étape 2 (per-security detail) explicitly out of scope.
- ADR 0012 marked Rejected (never accepted, not Superseded) + pointer to 0014.
- User guide (markdown + in-app docs.balance i18n FR/EN): optional fiscal
  envelope, the two chart axes, type renaming, and the historical-reclass note.
- CHANGELOG.md + CHANGELOG.fr.md [Unreleased]: Added (envelope field, envelope
  axis, collapsible returns) + Changed (asset-class category, CELI/REER reclass,
  rename no longer alters translation, historical-reclass note).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 21:15:06 -04:00
a73bf2ebb0 feat(balance): audit quick wins — terminology, optional symbol, movable snapshot date (#201) 2026-05-31 21:05:13 +00:00
le king fu
75ea48d96a chore: release v0.9.1
All checks were successful
Release / build-and-release (push) Successful in 29m42s
2026-05-10 20:36:51 -04:00
3024374e50 docs(changelog): note maximus-api activation post-0.9.0 (#197) 2026-05-10 19:27:33 +00:00
le king fu
4095aec453 Merge remote-tracking branch 'origin/main' into issue-187-balance-cleanup-post-184-185
All checks were successful
PR Check / rust (pull_request) Successful in 22m28s
PR Check / frontend (pull_request) Successful in 2m33s
# Conflicts:
#	CHANGELOG.fr.md
#	CHANGELOG.md
2026-05-03 19:41:55 -04:00
le king fu
9dd78b77f2 fix(rust): wrap Modified Dietz formula doc block in text fence (S7)
All checks were successful
PR Check / rust (pull_request) Successful in 23m2s
PR Check / frontend (pull_request) Successful in 2m42s
Before this commit, `cargo test --doc --manifest-path src-tauri/Cargo.toml`
failed: the indented formula at return_calculator.rs:12-13 was parsed by
rustdoc as a Rust code block and the pseudo-math (`R = ... sum(CF_i)`)
did not compile. Pre-existing since commit 531624b.

Wrapping the formula in an explicit `\`\`\`text` fence tells rustdoc to
render but not compile-test the block. `cargo test --doc` now passes
(0 doctests, no failures).

Also adds the consolidated #187 entry to CHANGELOG.md and CHANGELOG.fr.md
under Fixed/Corrigé summarizing all six fixes (S1, S2, S3, S4, S5, S7) —
S6 already factorized, S8 deferred to backlog, S9 obsolete.

Suggestion S7 from worker note on #176 (#187).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 16:31:29 -04:00
le king fu
3b9badb726 fix(ui): apply WebKitGTK date picker workaround to remaining 7 inputs (#188)
All checks were successful
PR Check / rust (pull_request) Successful in 22m55s
PR Check / frontend (pull_request) Successful in 2m29s
Extends PR #189's fix (one input on /balance/snapshot) to the 7 remaining
native <input type="date"> fields across 4 components:
- transactions/TransactionFilterBar.tsx (dateFrom + dateTo)
- adjustments/AdjustmentForm.tsx (form.date)
- balance/LinkTransfersModal.tsx (from + to)
- dashboard/PeriodSelector.tsx (localFrom + localTo)

Each onChange handler now calls e.currentTarget.blur() after the state
update to dismiss the native date popup on Linux Tauri WebView. The call
is a no-op on Windows WebView2 / macOS WKWebView, where the picker
already auto-closes.

No automated test added: this is a WebKitGTK-specific WebView quirk that
cannot be reproduced in jsdom/vitest. Manual smoke test on Linux Tauri
dev was the validation, mirroring PR #189's approach.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 16:19:20 -04:00
le king fu
0a8b5c7805 fix(deps): bump postcss to 8.5.13 to address GHSA-qx2v-qp2m-jg93 (#180)
All checks were successful
PR Check / rust (pull_request) Successful in 23m30s
PR Check / frontend (pull_request) Successful in 2m26s
Transitive dependency via vite (range ^8.5.3 already accepts the fix).
Lockfile-only change; no package.json modification needed.

Advisory GHSA-qx2v-qp2m-jg93 is a moderate severity XSS via unescaped
</style> in the CSS stringifier output. postcss runs at build time only
and never ships in the Tauri binary, so practical exposure is nil — but
this clears the npm audit warning and the defenseur finding.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 15:21:18 -04:00
le king fu
f02fd95ab1 refactor(settings): split monolithic Settings page into 3 sub-pages (#190)
All checks were successful
PR Check / rust (pull_request) Successful in 22m55s
PR Check / frontend (pull_request) Successful in 2m27s
The single 12-card SettingsPage is replaced by a hub at /settings linking
to three thematic sub-pages mounted via a shared SettingsLayout (Outlet):

  /settings           SettingsHomePage     (3 cards-cluster + PageHelp)
  /settings/users     UsersSettingsPage    (Account, License, DocsContent)
  /settings/data      DataSettingsPage     (Categories, DataManagement,
                                            PriceFetchConsentToggle)
  /settings/systems   SystemsSettingsPage  (Version, UpdateCard,
                                            ChangelogContent, LogViewer)

DocsPage and ChangelogPage are extracted into reusable DocsContent /
ChangelogContent components and the standalone /docs and /changelog
routes become Navigate redirects to preserve external bookmarks and
release-note links. UpdateCard is extracted from the inline updater
block for symmetry and testability.

TokenStoreFallbackBanner is mounted once in SettingsLayout, surfacing
the OS-keychain-fallback warning across the four main routes only.
The two existing /settings/categories/{standard,migrate} sub-routes
stay flat (siblings of SettingsLayout) to keep their focused flows
free of the banner — their internal back-links now point to
/settings/data.

i18n FR/EN gain settings.{home, users, data, systems, backToHome};
docs/architecture.md and CHANGELOG{,.fr}.md updated. Pure refactor of
presentation: no new business logic, no Tauri commands, no SQL
migrations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:50:02 -04:00
le king fu
0d50a92b0e fix(ui): close native date picker after selection on WebKitGTK (#177)
All checks were successful
PR Check / rust (pull_request) Successful in 22m43s
PR Check / frontend (pull_request) Successful in 2m26s
WebKitGTK (Linux Tauri WebView) does not auto-dismiss the native
<input type="date"> popup after a value commit — the user has to
press Esc. Force-blur on change is a no-op on Edge Chromium WebView2
(Windows) and WKWebView (macOS), where the popup already closes.

Scope narrowed to /balance/snapshot per issue body. Six other date
inputs across the app share the same WebKitGTK bug; tracked in a
follow-up issue rather than bundled here.

Diagnostic: Ubuntu 24.04 + libwebkit2gtk-4.1-0 2.50.4-0ubuntu0.24.04.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 15:55:57 -04:00
le king fu
4cd0ac9a13 Merge PR #186: feat(branding) replace default Tauri icon + bundle 64x64
# Conflicts:
#	CHANGELOG.fr.md
#	CHANGELOG.md
2026-05-02 15:46:03 -04:00
le king fu
d147520d6b feat(branding): replace default Tauri icon with custom design
All checks were successful
PR Check / rust (pull_request) Successful in 22m51s
PR Check / frontend (pull_request) Successful in 2m24s
Robot-faced calculator with a privacy lock on the Enter / `=` key.
Conveys the four product values: robot (assistant), simplicity
(geometric shapes), accounting (calculator), privacy (lock).

- New source SVG at src-tauri/icons/icon.svg (kept in repo for future
  iterations) and public/icon.svg (web favicon)
- Regenerated 16 platform-specific raster icons via `tauri icon`
- Removed unused default Vite/Tauri SVG assets from public/
- Fixed window <title> ("Tauri + React + Typescript" → "Simpl'Résultat")
- gitignore ios/ and android/ subdirs (out-of-scope, desktop-only targets)
2026-05-02 14:51:55 -04:00
le king fu
cd0a2b826f feat(balance): starter accounts + opt-in modal + ADR 0012
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
2026-05-02 11:59:45 -04:00
le king fu
eac2a516b5 feat(balance): 2-step onboarding card on /balance empty state
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
2026-05-02 11:48:57 -04:00
le king fu
50b119121f fix(balance): atomic snapshot save with BEGIN/COMMIT + cleanup migration
useSnapshotEditor.save now validates all simple/priced lines in-memory
before any DB write, then delegates to a new saveSnapshotAtomic helper
that wraps INSERT snapshot + INSERT lines in an explicit BEGIN/COMMIT
transaction (ROLLBACK on catch). Pattern matches categorizationService.

Migration v11 cleans existing orphan snapshots in profiles that hit the
old race; new orphans are no longer possible thanks to the transaction.

Resolves #176
2026-05-01 07:33:44 -04:00
le king fu
44cc77d8f6 fix(balance): use ROW_NUMBER window function in getAccountsPeriodAnchor
All checks were successful
PR Check / rust (pull_request) Successful in 22m48s
PR Check / frontend (pull_request) Successful in 2m22s
SQLite raised "misuse of aggregate function MIN()" because MIN was used
in the WHERE clause of a scalar subquery. Replace with ROW_NUMBER()
OVER (PARTITION BY account_id ORDER BY snapshot_date ASC) filtered on
rn = 1.

Adds vitest coverage and a regression test for /balance load.

Resolves #175
2026-05-01 07:18:53 -04:00
le king fu
5836760f3c chore: release v0.9.0
All checks were successful
Release / build-and-release (push) Successful in 25m25s
2026-04-29 19:20:03 -04:00
le king fu
3963f552ae feat(balance): add asset_type column to balance_categories
All checks were successful
PR Check / rust (push) Successful in 23m42s
PR Check / frontend (push) Successful in 2m26s
PR Check / rust (pull_request) Successful in 22m55s
PR Check / frontend (pull_request) Successful in 2m24s
Priced balance categories now carry an explicit `asset_type`
('stock' | 'crypto') so PriceFetchControl can route to the right
provider without symbol heuristics. ETH = Ethan Allen NYSE AND
Ethereum crypto are no longer ambiguous.

Migration v10 adds a nullable column and backfills the two seeded
priced categories (key='stock','crypto'). Legacy custom priced rows
stay NULL until the user edits the category — SnapshotLineRow hides
the price-fetch button when asset_type is NULL on a priced row, so
manual entry remains available.

Service-side validation rejects priced creation without asset_type
('asset_type_required') and rejects values outside ('stock','crypto')
('asset_type_invalid'). Simple kind coerces asset_type to NULL.

The CategoryVariant of AccountForm shows the selector only when
kind=priced, requires it on submit, and resets it on kind switch.
i18n keys added under balance.category.assetType.* (FR + EN).

Tests:
- 4 new Rust migration tests in lib.rs (column add, seed backfill,
  legacy row stays NULL, CHECK rejects 'gold')
- 6 new vitest cases on createBalanceCategory + listBalanceAccounts
  asserts c.asset_type AS category_asset_type in the join
- balance-flow integration test updated to pass asset_type='stock'

No new test for SnapshotLineRow render guard — project lacks
@testing-library/react + jsdom; the guard is one boolean expression
covered by manual QA per autopilot decisions in PR #167.

Fixes #169
2026-04-28 19:54:04 -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
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
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
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
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
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
80c0a97841 feat(balance): i18n + CHANGELOG for priced kind
All checks were successful
PR Check / rust (push) Successful in 22m31s
PR Check / frontend (push) Successful in 2m21s
- Adds keys under balance.category.kind, balance.category.form.kindLabel
  / kindHint*, balance.category.actions.deleteHasAccountsHint,
  balance.category.error.has_accounts, balance.account.form
  .symbolRequiredForPriced, balance.snapshot.priced.* (FR + EN).
- Extends balance.errors.* with the four new typed codes:
  snapshot_priced_quantity_required,
  snapshot_priced_unit_price_required,
  snapshot_priced_value_mismatch,
  snapshot_simple_must_be_scalar.
- CHANGELOG entries (FR + EN) under [Unreleased].

Refs #140

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 15:02:18 -04:00
le king fu
8f5cc71707 feat(balance): add i18n keys + CHANGELOG entry for snapshot editor
All checks were successful
PR Check / rust (push) Successful in 21m48s
PR Check / frontend (push) Successful in 2m19s
i18n FR/EN under balance.snapshot.* — page (titles, date label and
immutability notice, total, prefill, save/create/delete buttons),
editor (empty state), line (placeholder + a11y label), delete
(double-confirm modal copy). Five new error codes added to
balance.errors.* (snapshot_date_required, snapshot_date_taken,
snapshot_not_found, snapshot_value_invalid, snapshot_priced_unsupported).

Adds common.back so the SnapshotEditPage back arrow has a localized title.

CHANGELOG entries for #146 under [Unreleased] in both EN and FR.

Refs #146

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 14:49:41 -04:00
le king fu
4c71eaca2d feat(balance): add i18n keys + CHANGELOG entry
All checks were successful
PR Check / rust (push) Successful in 22m25s
PR Check / frontend (push) Successful in 2m27s
PR Check / rust (pull_request) Successful in 22m44s
PR Check / frontend (pull_request) Successful in 2m21s
Adds the FR/EN translation namespace `balance.*` covering:
- balance.accountsPage.* (page chrome, tab labels, empty states)
- balance.account.* (table fields, status badges, action labels,
  full account form copy with priced/simple-aware hints)
- balance.category.* (intro text, table fields, kind labels, origin
  labels, action prompts, simple-only creation form, seeded labels
  for the 7 standard categories)
- balance.errors.* (one entry per BalanceErrorCode union member)

CHANGELOG.md and CHANGELOG.fr.md both gain a single entry under
`[Unreleased] / Added` summarising the schema migration v9, the new
balance.service CRUD section and the AccountsPage tabs.

Refs #138

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 14:38:09 -04:00
le king fu
871768593d fix(reports): render category combobox in hierarchical DFS order (#126)
All checks were successful
PR Check / rust (push) Successful in 22m7s
PR Check / frontend (push) Successful in 2m19s
PR Check / rust (pull_request) Successful in 21m37s
PR Check / frontend (pull_request) Successful in 2m14s
The by-category report combobox (`/reports/category`) was showing its full
category list with scrambled indentation — parents from one sub-tree
interleaved with children from another. Root cause: `getAllCategoriesWithCounts`
returns rows via `ORDER BY sort_order, name`, which is a *global* sort; two
different roots with sort_order=1 would be followed by their respective
children in the same bucket, mixing depths together.

Add a pure `sortHierarchical(categories, resolveName)` helper in
`CategoryCombobox.tsx` that rebuilds the display order as a DFS walk of the
tree: each parent is emitted immediately followed by its descendants, with
siblings within a group sorted by `sort_order` then localized display name.
Orphans (parent filtered out or missing) are appended at the end so nothing
disappears. The helper runs client-side inside the combobox's `useMemo`, so
the fix is scoped to this component and doesn't affect other consumers of
`getAllCategoriesWithCounts`. Filtering on the input query remains unchanged.

Covered by 7 unit tests on the helper (empty list, single root, the exact
bug-reproducing scrambled case, sort_order + name tiebreak, 3-level
hierarchy, orphans, idempotence).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 20:58:53 -04:00