DashboardPage now reuses the Cartes report's presentational widgets
(KpiCard with MoM/YoY deltas, top movers, budget adherence) sourced from
getCartesSnapshot against a Dashboard-owned reference month (defaults to
the last complete month). The expense-only pie chart is replaced by a
ranked bar chart of top expense categories (resurrecting the previously
unused CategoryBarChart), and the account (import-source) filter
introduced on Trends/Compare/Budget now also applies to the Dashboard's
own transactional widgets via a Dashboard-local accountIds state (kept
separate from useReportsPeriod, since the Dashboard owns its own two
temporal axes).
A new net-worth tile surfaces the Balance sheet's latest total
(getSnapshotTotalsByDate) — a distinct metric from every transactional
card here, so it stays hidden (never a misleading "$0") until at least
one balance account has a recorded snapshot, reusing deriveLandingState
rather than re-inferring emptiness from nulls. It is not scoped by the
account filter (balance_accounts is a disjoint concept from
import_sources) and is fetched independently on mount.
The category-over-time trend chart now passes typeFilter "expense",
fixing a latent mismatch where a revenue category could silently show
up in a chart titled "expenses over time".
Resolves#279
Align BudgetVsActualTable on the income-statement gold standard already
shipped for the real-vs-real compare report (#253/#256): flip TYPE_ORDER
to income-first (revenue -> expense -> transfer) and replace the flat
"Total" row with interleaved Result before transfers / Net result lines.
Roll-up logic extracted into a new pure, tested module
(budgetVsActualResults.ts) mirroring compareResults.ts, with one sign
difference: BVA amounts already arrive signed the accounting way
(expense actual/budget are negative), so the operating result is a
straight add of income + expense rather than a subtraction.
Resolves#277
Generated autonomously by /autopilot run of 2026-07-11
Align BudgetTable on the income-statement standard shipped for the
compare/trend reports: sections now read Income -> Expense -> Transfer
(useBudget's TYPE_ORDER and BudgetTable's typeOrder both flipped), and
the previously unlabeled grand-total row is replaced by two interleaved
result rows (Result before transfers / Net result), computed by a new
pure, tested module (budgetTableResults.ts) mirroring compareResults.ts's
shape. Roll-up covers the previous-year-actual, budgeted-annual, and
budgeted-monthly columns alike.
All categories remain displayed (the grid stays a full edit surface) --
no collapse, no empty-row toggle, matching this issue's frozen decisions.
Resolves#278
Mounts <FilterPanel> on ReportsComparePage (next to the existing
CompareReferenceMonthPicker) and BudgetPage (next to the existing
YearNavigator), keeping each page's temporal control unchanged. Threads
accountIds from useReportsPeriod through useCompare into
getCompareMonthOverMonth/getCompareYearOverYear, and through
CompareBudgetView into getBudgetVsActualData — closing the gap where
that sub-tab silently ignored the filter while the rest of the Compare
page respected it.
Budget: the issue named getBudgetVsActualData as the target for
useBudget, but that hook never calls it (it's exclusive to
CompareBudgetView/Dashboard/Cartes) — its only real actuals fetch is
getActualTotalsForYear(year - 1), the previous-year reference column.
Extended that function with the same optional accountIds pass-through
established by #273, rather than wiring in an unrelated call shape.
Both hooks fetch their account checkbox list via getAllImportSources,
mirroring the useTrends/#275 pattern. Empty selection = no filter,
byte-identical to pre-#276 output (regression-tested).
Resolves#276
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mounts <FilterPanel> (from #274) on ReportsTrendsPage next to the period
selector, and threads accountIds from useReportsPeriod through useTrends
into getMonthlyTrends/getCategoryOverTime (both already accountIds-aware
since #273). The account checkbox list reuses the same getAllImportSources
query as the Transactions single-source filter. Empty selection = no
filter, byte-identical to pre-#275 output.
Resolves#275
Adds src/components/reports/FilterPanel.tsx, the shared filter bar for the
report pages. It renders the page's own temporal control (PeriodSelector /
CompareReferenceMonthPicker / YearNavigator / …) as-is via a `temporalControl`
ReactNode prop, plus a checkbox multi-select over `accounts: ImportSource[]`.
No `temporalMode` enum — that design was rejected to avoid mixing state
sources; the panel owns no temporal state at all.
Empty `accountIds` means no filter (all sources). Selection toggling is a
pure, exported `toggleAccountId` helper. Copy says "sources"/"import
sources" everywhere, never "comptes"/"accounts", so the filter reads as
distinct from the Bilan module's own "Compte" vocabulary.
This issue creates the standalone component only; wiring it into report
pages is left to follow-up issues (#275/#276).
Resolves#274
Give every report service an optional accountIds?: number[] filter that
narrows results to a subset of import sources (transactions.source_id),
matched via a parameterized IN (...) clause — one bound placeholder per
id, never a joined string (CWE-89). New shared inPlaceholders() helper
(src/utils/sqlFilters.ts) generates the placeholder list so the pattern
lives in exactly one place.
New accountIds param: getCompareMonthOverMonth, getCompareYearOverYear,
getBudgetVsActualData, getCartesSnapshot (forwarded to its
getCompareMonthOverMonth + getBudgetVsActualData sub-calls, so the
Cartes dashboard's top-movers and budget-adherence cards respect an
active filter instead of silently ignoring it).
Signature change scalar -> plural: getMonthlyTrends, getCategoryOverTime,
getExpensesByCategory (sourceId?: number -> accountIds?: number[]). No
scalar production caller of these three passed sourceId today, so only
the test call-sites needed updating to the array shape.
Omitted/empty accountIds adds no clause at all, so every service stays
byte-identical to its pre-#273 SQL/results — pinned by a regression test
per service (new budgetService.test.ts / dashboardService.test.ts files,
extended reportService.test.ts / reportService.cartes.test.ts).
This is backend plumbing only: no report page exposes an account filter
control yet (follow-up issues #274-#276 add the shared <FilterPanel> and
wire it into Trends/Compare/Budget).
Resolves#273
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add accountIds: number[] to the shared useReportsPeriod hook, URL-backed
via a dedicated `sources` query param (comma-separated, bookmarkable like
the existing period/from/to). Purely additive: the hook's existing shape
is unchanged, so all 6 consumers (useTrends, useCompare, useCategoryZoom,
ReportsCategoryPage, ReportsPage, ReportsComparePage) keep compiling as-is.
Also adds the shared ReportFilters { period, accountIds } type for
follow-up issues to consume, and exports pure parseAccountIds/
serializeAccountIds helpers (same hookless-testability pattern as
resolveReportsPeriod). URL parsing validates each token as a finite
integer via a strict regex + Number.isSafeInteger, dropping invalid
tokens individually rather than discarding the whole selection.
This is foundation only: no UI control exposes the filter yet, and no
service reads accountIds yet (both land in follow-up issues of the
"rapports uniformes" epic).
Resolves#272
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Renders the trends-by-category table from the #264 id-keyed tree as an indented
parent/child hierarchy (parity with ComparePeriodTable): top-level parents carry
a chevron and fold their subtree down to a subtotal, with an "expand all /
collapse all" button. Collapsed by default; expansion state is persisted under a
trends-specific localStorage key (reports-trends-expanded), distinct from the
comparable tables. The "result before transfers" line is interleaved before the
transfers section instead of sitting at the bottom.
Collapse is purely visual: section subtotals, before/net results and totals come
from computeOverTimeResults over the raw tree, never the visible rows.
New pure module overTimeTableModel.ts (section grouping + collapse accessors)
keeps the component thin and unit-testable without a React render harness;
overTimeResults.ts is left untouched. 8 new tests; build + 728 vitest green.
Resolves#265
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds an id-keyed hierarchical `tree: OverTimeRow[]` sidecar to
getCategoryOverTime, built via the generic buildLeafDrivenTree (#263) as
buildOverTimeTree — leaf-driven, income-first, no top-N / no "Other" bucket.
The name-keyed pivot (data/categories/colors/types/categoryIds) is left
BYTE-IDENTICAL, so the Trends chart and the dashboard render unchanged.
computeOverTimeResults now consumes the id-keyed tree LEAVES (never the
`is_parent` subtotals) grouped by each leaf's category_id-resolved type, so
the section subtotals and the Result-before/net lines are exact: two homonym
categories of different types no longer collide, and a non-top-N income or
transfer category is no longer lumped into "Other" as an expense.
CategoryOverTimeTable renders the tree leaves (every category, id-safe)
instead of the top-N pivot; the chart keeps reading the top-N-capped pivot so
its stacked series stay bounded. OverTimeRow mirrors CategoryDelta's snake_case
hierarchy block (parent_id/is_parent/depth/category_type) so it composes with
collapsibleRows / useCollapsibleGroups for the #265 hierarchy work.
Tests: rewrote overTimeResults.test.ts onto the tree (homonym regression +
leaves-only-not-parents); added buildOverTimeTree suite (nesting, income-first,
grand-total invariance, orphan, A->B->A cycle depth guard) and a
getCategoryOverTime tree-wiring test proving the pivot stays top-N+Other while
the tree carries every category. 720 vitest green, build + tsc green.
Resolves#264
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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
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>
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
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>
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>
@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>
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>
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>
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>
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>
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>
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>
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>
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>
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
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
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
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
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>
- 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>
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>
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>
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>
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>
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>
Surfaces the pre-migration SREF backup to the user so they can roll back a
category migration without digging into the filesystem:
- 90-day dismissable banner at the top of Settings > Categories pointing to
the automatic backup (hidden once reverted, once dismissed, or past 90d).
- Permanent "Restore a backup" entry in Settings > Categories, available as
long as a migration journal exists (even past the 90-day window).
- Confirmation modal with two-step consent, red Restore button, fallback
file picker when the recorded path is missing, PIN prompt for encrypted
SREF files, full-page reload on success.
Internals:
- New `categoryRestoreService` wrapping `read_import_file` +
`importTransactionsWithCategories` with stable error codes
(file_missing, read_failed, parse_failed, wrong_envelope_type,
needs_password, wrong_password, import_failed).
- New `file_exists` Tauri command for the pre-flight presence check.
- On success: `categories_schema_version=v2` + merge `reverted_at` into
`last_categories_migration`.
- Pure `shouldShowBanner` / `isWithinBannerWindow` helpers with tests.
- FR/EN i18n keys under `settings.categoriesCard.restore*`.
- CHANGELOG entries in both locales.
Closes#122
New user-facing 3-step migration flow at /settings/categories/migrate that
allows legacy v2 profiles to opt in to the v1 IPC taxonomy.
Step 1 Discover — read-only taxonomy tree (reuses CategoryTaxonomyTree from
Livraison 1, #117).
Step 2 Simulate — 3-column dry-run table with confidence badges (high /
medium / low / needs-review), transaction preview side panel, inline target
picker for unresolved rows. The "next" button is blocked until every row is
resolved.
Step 3 Consent — checklist + optional PIN field for PIN-protected profiles +
4-step loader (backup created / verified / SQL applied / committed).
Success and error screens surface the SREF backup path and the counts of
rows migrated. Errors never leave the profile in a partial state — the new
categoryMigrationService wraps the entire SQL writeover in a
BEGIN/COMMIT/ROLLBACK atomic transaction and aborts up-front if the backup
is not present / verified.
New code:
- src/services/categoryMigrationService.ts — applyMigration(plan, backup)
atomic writer (INSERT v1 → UPDATE transactions/budgets/budget_templates/
keywords/suppliers → reparent preserved customs → deactivate v2 seed →
bump categories_schema_version=v1 → journal last_categories_migration).
- src/hooks/useCategoryMigration.ts — useReducer state machine
(discover → simulate → consent → running → success | error).
- src/hooks/useCategoryMigration.test.ts — 13 pure reducer tests.
- src/components/categories-migration/{StepDiscover,StepSimulate,StepConsent,
MappingRow,TransactionPreviewPanel}.tsx — UI per the mockup.
- src/pages/CategoriesMigrationPage.tsx — wrapper with internal router,
stepper, backup/migrate orchestration, success/error screens.
Tweaks:
- src/App.tsx — new /settings/categories/migrate route.
- src/components/settings/CategoriesCard.tsx — additional card surfacing
the migrate entry for v2 profiles only.
- src/i18n/locales/{fr,en}.json — categoriesSeed.migration.* namespace
(page / stepper / 3 steps / running / success / error / backup error codes).
- CHANGELOG.{md,fr.md} — [Unreleased] / Added entry.
Scope limits respected: no SQL migration modified, no new migration added,
no unit tests of the applyMigration writer (covered by #123 in wave 4),
no restore-backup button (#122 in wave 4), no post-migration banner (#122).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a non-destructive, dismissable banner on the Dashboard that invites
profiles still on the legacy v2 category seed to discover the new v1 IPC
taxonomy. The banner links to the standard categories guide page
(/settings/categories/standard, shipped in #117).
Visibility rules:
- Only rendered when `categories_schema_version='v2'` in `user_preferences`.
- Hidden once the user dismisses it — dismissal is persisted in the same
`user_preferences` table under the key `categories_v1_banner_dismissed`
(value '1'), so the banner never reappears across app restarts.
- New v1-seeded profiles never see it.
No DB schema change: reuses the existing key/value `user_preferences`
table via the existing `getPreference`/`setPreference` helpers. No
migration added.
i18n keys under `dashboard.categoriesBanner.*` (FR + EN).
Changelog entry added under [Unreleased] in both CHANGELOG.md and
CHANGELOG.fr.md.
Adds a read-only Settings subpage at /settings/categories/standard that
exposes the full v1 IPC category taxonomy:
- Recursive tree with per-root expand/collapse (chevron buttons), clickable
only via the disclosure caret — no destructive actions anywhere on the
page.
- Live counter banner: roots / subcategories / leaves / total, computed
from the bundled categoryTaxonomyV1 JSON.
- Accent- and case-insensitive full-text search over translated names;
matching nodes keep their ancestor chain visible, non-matching branches
are pruned from the visible tree.
- Hover tooltips (native `title`) showing i18n_key, type (income /
expense / transfer — translated) and numeric id of each node — useful
for power-users cross-referencing the consolidated schema.
- Export as PDF button that triggers window.print(); a dedicated
@media print rule in styles.css forces every branch to render fully
expanded during printing regardless of the on-screen collapse state,
and hides the toolbar / back-link.
- All labels resolve via t(node.i18n_key, { defaultValue: node.name })
to be forward-compatible with future user-created taxonomy rows that
have no i18n_key.
Also:
- New CategoriesCard in Settings that links to the page (FolderTree
icon, consistent with the userGuide / changelog card pattern).
- i18n keys added under categoriesSeed.guidePage.* and
settings.categoriesCard.* (FR + EN).
- CHANGELOG.md + CHANGELOG.fr.md updated under [Unreleased] / Added.
Route uses the English-style `/settings/categories/standard` to match
the rest of the app (/settings, /categories, /changelog, ...). The
original spec mentions a French-accented path but the existing router
is English-only; documenting here so reviewers can see the decision.
No SQL migration, no schema change, no write to the database — this
is strictly a read-only view on the TS-side taxonomy bundle.
Type-check clean (tsc --noEmit), 148/148 vitest tests pass, vite build
succeeds.
Livraison 1 du milestone spec-refonte-seed-categories-ipc. Applies the
new v1 IPC (Indice des prix à la consommation) taxonomy to freshly
created profiles while leaving existing v2 profiles untouched until the
migration wizard (upcoming issue #121) prompts them to move.
- Migration v8 (additive only):
- ALTER TABLE categories ADD COLUMN i18n_key TEXT
- INSERT OR IGNORE user_preferences.categories_schema_version=v2
(existing profiles tagged as v2 for later migration)
- consolidated_schema.sql rewritten with the full v1 seed and
categories_schema_version='v1' default for brand-new profiles
- src/data/categoryTaxonomyV1.json bundled as the TS-side source of
truth (consumed by #116 categoryTaxonomyService next)
- categoriesSeed.* i18n namespace (FR/EN) — 150 entries each
- CategoryTree and CategoryCombobox fall back to the raw `name` when
i18n_key is null (user-created categories stay literal)
- CategoryTreeNode and CategoryRow gain the i18n_key field end-to-end
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a segmented Monthly/YTD toggle next to the reference-month picker that
flips the four KPI cards (income, expenses, net, savings rate) between the
reference-month value (unchanged default) and a Year-to-Date cumulative view.
In YTD mode, the "current" value sums January to the reference month of the
reference year; MoM delta compares it to Jan to (refMonth - 1) of the same
year (null in January, since no prior YTD window exists); YoY delta compares
it to Jan to refMonth of the previous year; savings rate is recomputed from
YTD income and expenses, and stays null when YTD income is zero.
The 13-month sparkline, top movers, seasonality and budget adherence cards
remain monthly regardless of the toggle (by design). The savings-rate tooltip
is now dynamic and mirrors the active mode. The mode is persisted in
localStorage under `reports-cartes-period-mode`.
Also adds a dedicated Cartes section to `docs/guide-utilisateur.md` covering
the four KPI formulas, the Monthly/YTD toggle and its effect on deltas, the
sparkline, top movers, seasonality, budget adherence and the savings-rate
edge case. Mirrored in the in-app `docs.reports` i18n tree (features/steps/
tips extended) for both FR and EN.
No SQL migration: YTD sums are computed from the already-fetched
`flowByMonth` map, so no extra round trip.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Expense budgets are stored signed-negative by budgetService. The Cartes
budget-adherence card used raw values in its filter (monthBudget > 0),
its in-target comparison (|actual| <= monthBudget), and its overrun
calculation — all of which silently rejected every expense row. Route
every amount through Math.abs() so the card reflects real budget data.
Test: regression fixture with a signed-negative monthBudget that must
pass the filter and count as in-target or overrun based on absolute
values.
Fixes#112
- Extract shared defaultReferencePeriod helper (src/utils/referencePeriod.ts)
- useHighlights now reads ?refY=YYYY&refM=MM, defaults to previous month
- getHighlights signature: (referenceYear, referenceMonth, ytdYear, windowDays, ...)
- YTD tile pinned to Jan 1 of current civil year, independent of reference month
- CompareReferenceMonthPicker surfaced on /reports/highlights
- Hub highlights panel inherits the same default via useHighlights
- useCartes and useCompare now delegate their default-period helpers to the shared util
Adds a segmented chart-type toggle to the /reports/trends "By category"
sub-view that switches between the existing stacked bars (default,
unchanged) and a new Recharts AreaChart with stackId="1" showing total
composition over time. Both modes share the same category palette and
SVG grayscale patterns so the visual signature is preserved.
- CategoryOverTimeChart gains a chartType: 'line' | 'area' prop
(default 'line' for backward compatibility with the dashboard usage).
- New TrendsChartTypeToggle component, persisted in localStorage under
"reports-trends-category-charttype".
- Toggle only renders in the "By category" sub-view with chart view
mode selected; hidden otherwise.
- i18n keys reports.trends.chartLine / chartArea / chartTypeAria in
FR and EN.
- CHANGELOG entries in both languages.
Mirror the BudgetVsActualTable structure in the Actual-vs-Actual compare
mode so MoM and YoY both surface a Monthly block (reference month vs
comparison month) and a Cumulative YTD block (progress through the
reference month vs progress through the previous window).
- CategoryDelta gains cumulative{Previous,Current}Amount and
cumulativeDelta{Abs,Pct}. Legacy previousAmount / currentAmount /
deltaAbs / deltaPct are kept as aliases of the monthly block so the
Highlights hub, Cartes dashboard and ComparePeriodChart keep working
unchanged.
- getCompareMonthOverMonth: cumulative-previous window ends at the end
of the previous month within the SAME year; when the reference month
is January, the previous window sits entirely in the prior calendar
year (Jan → Dec).
- getCompareYearOverYear: now takes an optional reference month
(defaults to December for backward compatibility). Monthly block
compares the single reference month across years; cumulative block
compares Jan → refMonth across years.
- ComparePeriodTable rebuilt with two colspan header groups, four
sub-columns each, a totals row and month/year boundary sub-labels.
- ComparePeriodChart unchanged: still reads the monthly primary fields.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Swap the native <select> in CategoryZoomHeader for the reusable
CategoryCombobox. Enhances the combobox with ARIA compliance
(combobox, listbox, option roles + aria-expanded, aria-controls,
aria-activedescendant) and hierarchy indentation based on parent_id
depth. Adds reports.category.searchPlaceholder in FR/EN.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Remove the non-functional PeriodSelector from /reports/cartes — the Cartes
report is by design a "month X vs X-1 vs X-12" snapshot, so the
reference-month picker is the only control needed.
- Simplify useCartes to drop its useReportsPeriod dependency; the hook now
only exposes the reference year/month and its setter.
- Add a (?) help bubble (lucide HelpCircle) next to the savings-rate KPI
title, wired up via a new `tooltip?: string` prop on KpiCard.
- Propagate `number | null` through CartesKpi.current and buildKpi so
savings rate is now null (rendered as "—") when reference-month income
is 0 instead of a misleading "0 %". Use refExpenses directly for
seasonality.referenceAmount since it is always numeric.
- Update the cartes snapshot tests to expect null for the zero-income case.
- Add FR/EN strings reports.cartes.savingsRateTooltip + CHANGELOG entries
in both locales.
Closes#67
Add opt-in Feedback Hub widget integrated into the Settings Logs card. Routes through a Rust command to bypass CORS and centralize privacy audit. First submission triggers a one-time consent dialog; three opt-in checkboxes (context, logs, identify with Maximus account) all unchecked by default. Wording and payload follow the cross-app conventions in la-compagnie-maximus/docs/feedback-hub-ops.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New /reports/cartes page surfaces a dashboard-style snapshot of the
reference month:
- 4 KPI cards (income / expenses / net / savings rate) showing MoM and
YoY deltas simultaneously, each with a 13-month sparkline highlighting
the reference month
- 12-month income vs expenses overlay chart (bars + net balance line)
- Top 5 category increases + top 5 decreases MoM, clickable through to
the category zoom report
- Budget adherence card: on-target count + 3 worst overruns with
progress bars
- Seasonality card: reference month vs same calendar month averaged
over the two previous years, with deviation indicator
All data is fetched in a single getCartesSnapshot() service call that
runs four queries concurrently (25-month flow, MoM category deltas,
budget-vs-actual, seasonality). Missing months are filled with zeroes
in the sparklines but preserved as null in the MoM/YoY deltas so the UI
can distinguish "no data" from "zero spend".
- Exported pure helpers: shiftMonth, defaultCartesReferencePeriod
- 13 vitest cases covering zero data, MoM/YoY computation, January
wrap-around, missing-month handling, division by zero for the
savings rate, seasonality with and without history, top mover sign
splitting and 5-cap
Note: src/components/reports/CompareReferenceMonthPicker.tsx is a
temporary duplicate — the canonical copy lives on the issue-96 branch
(refactor: compare report). Once both branches merge the content is
identical and git will dedupe. Keeping the local copy here means the
Cartes branch builds cleanly on main without depending on #96.
Closes#97
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Collapse the three Compare tabs (MoM / YoY / Budget) into two modes. The
new "Actual vs actual" mode exposes an explicit reference-month dropdown
in the header (defaults to the previous month, wraps around January) and
a MoM/YoY sub-toggle. The chart is rewritten to a grouped side-by-side
BarChart with two bars per category (reference period vs comparison
period) so both values are visible at a glance instead of just the
delta. The URL PeriodSelector stays in sync with the reference month.
- useCompare: state splits into { mode: "actual"|"budget", subMode:
"mom"|"yoy", year, month }. Pure helpers previousMonth(),
defaultReferencePeriod(), comparisonMeta() extracted for tests
- CompareModeTabs: 2 modes instead of 3
- New CompareSubModeToggle and CompareReferenceMonthPicker components
- ComparePeriodChart: grouped bars via two <Bar dataKey="..."/> on a
vertical BarChart
- i18n: modeActual / subModeMoM / subModeYoY / referenceMonth (FR+EN),
retire modeMoM / modeYoY
- 9 new vitest cases covering the pure helpers (January wrap-around for
both MoM and YoY, default reference period, month/year arithmetic)
Closes#96
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Milestone spec-refonte-rapports: reports hub + 4 sub-reports, per-domain
hooks, contextual keyword editing, category zoom with recursive CTE.
Dynamic pivot table removed. See CHANGELOG for the full list of changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Delete legacy src/hooks/useReports.ts (the monolithic hook is now fully
replaced by the per-domain hooks from #70)
- Delete src/components/reports/ReportFilterPanel.tsx (last caller was the
pre-refactor ReportsPage; no longer referenced anywhere)
- Update docs/architecture.md: reports hook list now lists the 5 per-domain
hooks, reports service entry lists every new endpoint, routing section
lists the 4 sub-routes, categorizationService entry mentions the new
keyword-editing helpers, components folder count + page count updated
- Update docs/guide-utilisateur.md section 9: rewrite around hub + 4
sub-reports, explain bookmarkable period via query string, walk through
the right-click keyword editing flow, remove stale pivot section
- Rewrite in-app docs.reports.* i18n in both FR and EN to match the new
UX (hub, sub-reports, contextual keywords)
- New ADR docs/adr/0007-reports-hub-refactor.md: context, decision (hub +
four routes, per-domain hooks, URL period, security guarantees on the
keyword dialog, bounded recursive CTE for category zoom), consequences,
alternatives considered
- CHANGELOG.md + CHANGELOG.fr.md: Unreleased entries describing the hub,
each sub-report, contextual keyword editing, bookmarkable period, view
mode persistence, useReports split, pivot removal, and the security
posture of AddKeywordDialog / getCategoryZoom
Fixes#76
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wraps up the spec-oauth-keychain milestone: OAuth tokens in OS keychain,
HMAC-signed account cache, fallback banner, and Argon2id PIN hashing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New ADR-0006 documenting the OS keychain migration: context,
options considered (keyring vs stronghold vs AES-from-PIN), the
backend choice rationale (sync-secret-service vs async-secret-
service), anti-downgrade design, migration semantics, and the
subscription-tampering fix via account_cache.
- architecture.md updated: new token_store / account_cache module
entries, auth_commands descriptions now point at the keychain-
backed API, OAuth2 + deep-link flow diagram mentions the HMAC
step, command count bumped to 35.
- CHANGELOG.md + CHANGELOG.fr.md under Unreleased:
- Changed: tokens moved to keychain with transparent migration
and Settings banner on fallback.
- Changed: account cache is now HMAC-signed.
- Security: CWE-312 and CWE-345 explicitly closed.
Manual test matrix (pop-os + Windows) is tracked in issue #82 and
will be run by the release gatekeeper before the next tag.
Refs #66, #78, #79, #80, #81
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The listener `app.listen("deep-link://new-url", ...)` did not reliably
fire when tauri-plugin-single-instance (deep-link feature) forwarded a
simpl-resultat://auth/callback URL to the running instance. The user
saw the browser complete the OAuth flow, the app regain focus, and
then sit in "loading" forever because the listener never received the
URL.
Switch to the canonical Tauri v2 API — `app.deep_link().on_open_url()`
via DeepLinkExt — which is directly coupled to the deep-link plugin
and catches URLs from both initial launch and single-instance forwards.
Also surface OAuth error responses: if the callback URL contains an
`error` parameter instead of a `code`, emit `auth-callback-error` so
the UI can show the error instead of staying stuck in "loading".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The auto-update gate added in #48 requires the Base edition, but the
license server (#49) needed to grant Base does not exist yet. This
chicken-and-egg left the only current user — myself — unable to
receive the critical v0.7.1 OAuth callback fix via auto-update.
Add EDITION_FREE to the auto-update feature tiers as a temporary
measure. The gate will be restored to [BASE, PREMIUM] once paid
activation works end-to-end via the Phase 2 license server.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Maximus Account sign-in flow was broken in v0.7.0: clicking "Sign in"
opened Logto in the browser, but when the OAuth2 callback fired
simpl-resultat://auth/callback?code=..., the OS launched a second app
instance instead of routing the URL to the running one. The second
instance had no PKCE verifier in memory, and the original instance
never received the deep-link event, leaving it stuck in "loading".
Fix: register tauri-plugin-single-instance (with the deep-link feature)
as the first plugin. It forwards the callback URL to the existing
process, which triggers the existing deep-link://new-url listener and
completes the token exchange.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Both code paths that touch the updater now consult `check_entitlement`
from the Rust entitlements module before calling `check()`:
- `useUpdater.ts` adds a `notEntitled` status; on Free, the check
short-circuits and the Settings page displays an upgrade hint instead
of fetching update metadata.
- `ErrorPage.tsx` (recovery screen) does the same so the error path
matches the main path; users on Free no longer see network errors when
the updater would have run.
The gate name (`auto-update`) is the same string consumed by
`commands/entitlements.rs::FEATURE_TIERS`, so changing which tier
unlocks updates is a one-line edit in that file.
Bilingual i18n keys for the new messages are added to both `fr.json`
and `en.json`. CHANGELOG entries in both languages.
Adds the user-facing layer on top of the Rust license commands shipped
in #46.
- `licenseService.ts` thin wrapper around the new Tauri commands
- `useLicense` hook follows the project's useReducer pattern (idle,
loading, ready, validating, error) and exposes `submitKey`,
`refresh`, and `checkEntitlement` for cross-component use
- `LicenseCard` shows the current edition, the expiry date when set,
accepts a license key with inline validation feedback, and links to
the purchase page via `openUrl` from `@tauri-apps/plugin-opener`
- Card is inserted at the top of `SettingsPage` so the edition is the
first thing users see when looking for license-related actions
- i18n: new `license.*` keys in both `fr.json` and `en.json`
- Bilingual CHANGELOG entries
Replace SHA-256 with Argon2id (m=64MiB, t=3, p=1) for PIN hashing.
Existing SHA-256 hashes are verified transparently via format detection
(argon2id: prefix). New PINs are always hashed with Argon2id.
Addresses CWE-916: Use of Password Hash With Insufficient Computational Effort.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds .forgejo/workflows/check.yml (and a GitHub mirror) that runs on
every branch push (except main) and on every PR targeting main.
Two parallel jobs:
- rust: cargo check + cargo test, with cargo registry/git/target caches
keyed on Cargo.lock. Installs the minimal Rust toolchain and the
webkit2gtk system deps that the tauri build script needs.
- frontend: npm ci + npm run build (tsc + vite) + npm test (vitest),
with the npm cache keyed on package-lock.json.
The Forgejo workflow uses the ubuntu:22.04 container pattern from
release.yml. The GitHub mirror uses native runners (ubuntu-latest)
since the GitHub mirror exists for portability and uses GitHub-native
actions.
Documents the new workflow in CLAUDE.md alongside release.yml so future
contributors know what CI runs before merge.
- Correct CHANGELOG to reflect default type is expense, not all types
- Validate select onChange value against allowed CategoryTypeFilter values
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Category Over Time report previously only showed expenses (t.amount < 0).
This removes that filter so all transaction types are shown by default,
and adds a type filter (expense/income/transfer) in the right filter panel.
Ref: simpl-resultat#41
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Includes fixes#34, #37, #39: budget prev year actuals, changelog sync via Vite, inline buildPrevYearTotalMap.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace planned budget data with actual transaction totals for the
previous year column in the budget table. Add getActualTotalsForYear
helper to budgetService.
Ref #34
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>