Commit graph

274 commits

Author SHA1 Message Date
d834ffeb9c Merge pull request 'fix(reports/cartes): Budget Adherence card filtered out all expense categories' (#113) from issue-112-budget-adherence-signed-fix into main 2026-04-19 13:39:11 +00:00
le king fu
2ec48d4e90 fix(reports/cartes): Budget Adherence card was filtering out all expense categories
All checks were successful
PR Check / rust (push) Successful in 21m31s
PR Check / frontend (push) Successful in 2m13s
PR Check / rust (pull_request) Successful in 22m0s
PR Check / frontend (pull_request) Successful in 2m14s
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
2026-04-19 09:35:26 -04:00
da6041dc45 Merge pull request 'Highlights: default reference month to previous + YTD current year, user-changeable (#106)' (#111) from issue-106-highlights-default-ref-month into main 2026-04-19 12:34:37 +00:00
le king fu
8b90cb6489 feat(reports/highlights): default reference month to previous month + YTD current year, user-changeable (#106)
All checks were successful
PR Check / rust (push) Successful in 21m14s
PR Check / frontend (push) Successful in 2m16s
PR Check / rust (pull_request) Successful in 21m31s
PR Check / frontend (pull_request) Successful in 2m14s
- 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
2026-04-19 08:28:30 -04:00
3842a1102a Merge pull request 'feat(reports/trends): add stacked-area chart option for category view (#105)' (#110) from issue-105-trends-stacked-area into main 2026-04-19 12:22:03 +00:00
le king fu
94104c4223 refactor(reports/trends): rename chart type from 'line' to 'bar' to match actual rendering
All checks were successful
PR Check / rust (push) Successful in 21m44s
PR Check / frontend (push) Successful in 2m18s
PR Check / rust (pull_request) Successful in 21m27s
PR Check / frontend (pull_request) Successful in 2m11s
The legacy chart was a stacked BarChart, not a LineChart — the initial 'line'
naming was misleading. Rename internal type, i18n key (chartLine -> chartBar,
Lignes -> Barres, Lines -> Bars) and icon. Legacy 'line' in localStorage is
migrated to 'bar' on read.
2026-04-19 07:26:22 -04:00
le king fu
02efc75542 feat(reports/trends): add stacked-area chart option for category view (#105)
Some checks failed
PR Check / rust (push) Has been cancelled
PR Check / frontend (push) Has been cancelled
PR Check / rust (pull_request) Successful in 21m22s
PR Check / frontend (pull_request) Successful in 2m15s
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.
2026-04-19 07:23:49 -04:00
e95612a55d Merge pull request 'feat(reports/compare): 8-column table with monthly + cumulative YTD blocks (#104)' (#109) from issue-104-compare-eight-col-table into main 2026-04-19 11:19:21 +00:00
le king fu
bd8a5732c6 feat(reports/compare): 8-column table with monthly + cumulative YTD blocks (#104)
All checks were successful
PR Check / rust (push) Successful in 21m21s
PR Check / frontend (push) Successful in 2m11s
PR Check / rust (pull_request) Successful in 21m30s
PR Check / frontend (pull_request) Successful in 2m8s
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>
2026-04-18 21:17:32 -04:00
8d916a1283 Merge pull request 'feat(reports/category): searchable combobox for category zoom (#103)' (#108) from issue-103-category-combobox into main 2026-04-19 01:10:25 +00:00
le king fu
01869462f4 feat(reports/category): replace select with searchable combobox (#103)
All checks were successful
PR Check / rust (push) Successful in 21m2s
PR Check / frontend (push) Successful in 2m13s
PR Check / rust (pull_request) Successful in 21m14s
PR Check / frontend (pull_request) Successful in 2m9s
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>
2026-04-18 21:07:47 -04:00
49a4ef2171 Merge pull request 'fix(reports/cartes): remove broken period selector + add savings-rate tooltip' (#107) from issue-101-cartes-period-savings-tooltip into main 2026-04-19 01:04:38 +00:00
le king fu
b258e2b80a fix(reports/cartes): remove broken period selector + add savings-rate tooltip (#101)
All checks were successful
PR Check / rust (push) Successful in 21m51s
PR Check / frontend (push) Successful in 2m29s
PR Check / rust (pull_request) Successful in 21m3s
PR Check / frontend (pull_request) Successful in 2m11s
- 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.
2026-04-18 20:50:18 -04:00
le king fu
4416457c22 chore: release v0.8.2
All checks were successful
Release / build-and-release (push) Successful in 24m6s
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 10:41:48 -04:00
4f4ab87bea feat: feedback hub widget in Settings Logs card (#67)
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>
2026-04-17 14:36:26 +00:00
le king fu
3b2587d843 chore: bump version to 0.8.1
All checks were successful
Release / build-and-release (push) Successful in 24m56s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:07:53 -04:00
89b69f325e feat: new Cartes dashboard report — KPI cards, sparklines, top movers (#97)
Closes #97
2026-04-15 23:53:37 +00:00
le king fu
4c58b8bab8 feat(reports/cartes): new KPI dashboard sub-report with sparklines, top movers, budget adherence and seasonality (#97)
All checks were successful
PR Check / rust (push) Successful in 23m21s
PR Check / frontend (push) Successful in 2m24s
PR Check / rust (pull_request) Successful in 23m12s
PR Check / frontend (pull_request) Successful in 2m20s
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>
2026-04-15 19:44:58 -04:00
5fd2108d07 refactor: compare report — Actual-vs-actual mode with reference month picker (#96)
Closes #96
2026-04-15 23:10:23 +00:00
le king fu
4116db4090 refactor(reports/compare): unify MoM/YoY under one Actual-vs-actual mode with reference month picker (#96)
All checks were successful
PR Check / rust (push) Successful in 24m37s
PR Check / frontend (push) Successful in 2m21s
PR Check / rust (pull_request) Successful in 24m25s
PR Check / frontend (pull_request) Successful in 2m26s
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>
2026-04-15 14:24:11 -04:00
le king fu
54cbdad710 chore: release v0.8.0
All checks were successful
Release / build-and-release (push) Successful in 25m4s
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>
2026-04-14 15:39:27 -04:00
e93b741f26 Merge pull request 'docs: polish, changelog, ADR + legacy cleanup for reports refactor (#76)' (#95) from issue-76-polish-docs into main 2026-04-14 19:35:35 +00:00
le king fu
8d5fab966a docs: polish + changelog + ADR + legacy cleanup for reports refactor (#76)
Some checks failed
PR Check / rust (push) Has been cancelled
PR Check / frontend (push) Has been cancelled
PR Check / rust (pull_request) Has been cancelled
PR Check / frontend (pull_request) Has been cancelled
- 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>
2026-04-14 15:29:49 -04:00
001646db70 Merge pull request 'feat: propagate right-click add-as-keyword to transactions + highlights list (#75)' (#94) from issue-75-propagate-context-menu into main 2026-04-14 19:23:01 +00:00
le king fu
3b70abdb9e feat: propagate right-click "add as keyword" to transactions page and highlights list (#75)
Some checks failed
PR Check / rust (push) Has been cancelled
PR Check / frontend (push) Has been cancelled
PR Check / rust (pull_request) Has been cancelled
PR Check / frontend (pull_request) Has been cancelled
Wire the ContextMenu + AddKeywordDialog pair onto the remaining per-transaction
surfaces. No new business logic — pure composition of #69 / #74 pieces.

- HighlightsTopTransactionsList: optional onContextMenuRow prop, ReportsHighlightsPage
  renders ContextMenu + AddKeywordDialog on right-click
- TransactionTable: optional onRowContextMenu prop on each <tr>; TransactionsPage
  handles it and opens the dialog pre-filled with the row description + current
  category
- Aggregate tables (HighlightsTopMoversTable, ComparePeriodTable, MonthlyTrendsTable,
  CategoryOverTimeTable) are intentionally NOT wired: they show category / month
  aggregates, not individual transactions, so there is no keyword to extract from
  a row — the dialog would be nonsensical there

Fixes #75

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:15:33 -04:00
334f975deb Merge pull request 'feat: category zoom + secure AddKeywordDialog (#74)' (#93) from issue-74-zoom-add-keyword into main 2026-04-14 19:11:54 +00:00
le king fu
62430c63dc feat: category zoom + secure AddKeywordDialog with context menu (#74)
Some checks failed
PR Check / rust (push) Has been cancelled
PR Check / frontend (push) Has been cancelled
PR Check / rust (pull_request) Has been cancelled
PR Check / frontend (pull_request) Has been cancelled
Service layer
- New reportService.getCategoryZoom(categoryId, from, to, includeChildren) —
  bounded recursive CTE (WHERE ct.depth < 5) protects against parent_id cycles;
  direct-only path skips the CTE; every binding is parameterised
- Export categorizationService helpers normalizeDescription / buildKeywordRegex /
  compileKeywords so the dialog can reuse them
- New validateKeyword() enforces 2–64 char length (anti-ReDoS), whitespace-only
  rejection, returns discriminated result
- New previewKeywordMatches(keyword, limit=50) uses parameterised LIKE + regex
  filter in memory; caps candidate scan at 1000 rows to protect against
  catastrophic backtracking
- New applyKeywordWithReassignment wraps INSERT (or UPDATE-reassign) +
  per-transaction UPDATEs in an explicit BEGIN/COMMIT/ROLLBACK; rejects
  existing keyword reassignment unless allowReplaceExisting is set; never
  recategorises historical transactions beyond the ids the caller supplied

Hook
- Flesh out useCategoryZoom with reducer + fetch + refetch hook

Components (flat under src/components/reports/)
- CategoryZoomHeader — category combobox + include/direct toggle
- CategoryDonutChart — template'd from dashboard/CategoryPieChart with
  innerRadius=55 and ChartPatternDefs for SVG patterns
- CategoryEvolutionChart — AreaChart with Intl-formatted axes
- CategoryTransactionsTable — sortable table with per-row onContextMenu
  → ContextMenu → "Add as keyword" action

AddKeywordDialog — src/components/categories/AddKeywordDialog.tsx
- Lives in categories/ (not reports/) because it is a keyword-editing widget
  consumed from multiple sections
- Renders transaction descriptions as React children only (no
  dangerouslySetInnerHTML); CSS truncation (CWE-79 safe)
- Per-row checkboxes for applying recategorisation; cap visible rows at 50;
  explicit opt-in checkbox to extend to N-50 non-displayed matches
- Surfaces apply errors + "keyword already exists" replace prompt
- Re-runs category zoom fetch on success so the zoomed view updates

Page
- ReportsCategoryPage composes header + donut + evolution + transactions
  + AddKeywordDialog, fetches from useCategoryZoom, preserves query string
  for back navigation

i18n
- New keys reports.category.* and reports.keyword.* in FR + EN
- Plural forms use i18next v25 _one / _other suffixes (nMatches)

Tests
- 3 reportService tests cover bounded CTE, cycle-guard depth check, direct-only fallthrough
- New categorizationService.test.ts: 13 tests covering validation boundaries,
  parameterised LIKE preview, regex word-boundary filter, explicit BEGIN/COMMIT
  wrapping, rollback on failure, existing keyword reassignment policy
- 62 total tests passing

Fixes #74

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:09:17 -04:00
b3b832650f Merge pull request 'feat: compare report — MoM / YoY / Actual vs Budget (#73)' (#92) from issue-73-compare into main 2026-04-14 19:00:32 +00:00
le king fu
ff350d75e7 feat: compare report — MoM / YoY / budget with view toggle (#73)
Some checks failed
PR Check / rust (push) Successful in 23m24s
PR Check / frontend (push) Successful in 2m17s
PR Check / frontend (pull_request) Has been cancelled
PR Check / rust (pull_request) Has been cancelled
- Services: getCompareMonthOverMonth(year, month) and getCompareYearOverYear(year)
  return CategoryDelta[] (expense-side, ABS aggregates, parameterised SQL only)
- Shared CategoryDelta type with HighlightMover now aliased to it
- Flesh out useCompare hook: reducer + fetch + automatic year/month inference
  from the shared useReportsPeriod `to` date; budget mode skips fetch and
  delegates to CompareBudgetView which wraps the existing BudgetVsActualTable
- Components: CompareModeTabs (MoM/YoY/Budget tabs), ComparePeriodTable (sortable
  table with signed delta coloring), ComparePeriodChart (diverging horizontal
  bar chart with ChartPatternDefs for SVG patterns), CompareBudgetView
  (fetches budget rows for the current target year/month)
- ReportsComparePage wires everything with PeriodSelector + ViewModeToggle
  (storage key reports-viewmode-compare); chart/table toggle is hidden in budget
  mode since the budget table has its own presentation
- i18n keys: reports.compare.modeMoM / modeYoY / modeBudget in FR + EN
- 4 new vitest cases for the compare services: parameterised boundaries,
  January wrap-around to December previous year, delta conversion with
  previous=0 fallback to null pct, year-over-year spans

Fixes #73

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 14:57:13 -04:00
fae76a6b82 Merge pull request 'feat: trends report — global flow + by category (#72)' (#91) from issue-72-trends into main 2026-04-14 18:53:24 +00:00
le king fu
d06dd7a858 feat: trends report — global flow + by category with view toggle (#72)
All checks were successful
PR Check / rust (push) Successful in 23m52s
PR Check / frontend (push) Successful in 2m20s
PR Check / rust (pull_request) Successful in 23m55s
PR Check / frontend (pull_request) Successful in 2m15s
- Flesh out ReportsTrendsPage with internal subview toggle
  (global / byCategory) and ViewModeToggle (storage key reports-viewmode-trends)
- Reuse existing MonthlyTrendsChart/Table and CategoryOverTimeChart/Table
  without modification; wire them through useTrends + useReportsPeriod so the
  URL period is respected
- Add reports.trends.subviewGlobal / subviewByCategory keys in FR + EN

Fixes #72

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 14:52:34 -04:00
5d206d5faf Merge pull request 'feat: reports hub + highlights panel + detailed highlights page (#71)' (#90) from issue-71-highlights-hub into main 2026-04-14 18:50:40 +00:00
le king fu
ac9c8afc4a feat: reports hub with highlights panel and detailed highlights page (#71)
All checks were successful
PR Check / rust (pull_request) Successful in 24m54s
PR Check / frontend (pull_request) Successful in 2m32s
PR Check / rust (push) Successful in 24m14s
PR Check / frontend (push) Successful in 2m26s
- Transform /reports into a hub: highlights panel + 4 nav cards
- New service: reportService.getHighlights (parameterised SQL, deterministic
  via referenceDate argument for tests, computes current-month balance, YTD,
  12-month sparkline series, top expense movers vs previous month, top recent
  transactions within configurable 30/60/90 day window)
- Extended types: HighlightsData, HighlightMover, MonthBalance
- Wired useHighlights hook with reducer + window-days state
- Hub tiles (flat naming under src/components/reports):
  HubNetBalanceTile, HubTopMoversTile, HubTopTransactionsTile,
  HubHighlightsPanel, HubReportNavCard
- Detailed ReportsHighlightsPage: balance tiles, sortable top movers table,
  diverging bar chart (Recharts + patterns SVG), top transactions list with
  30/60/90 window toggle; ViewModeToggle persistence keyed as
  reports-viewmode-highlights
- New i18n keys: reports.hub.*, reports.highlights.*
- 5 new vitest cases: empty profile, parameterised queries, window sizing,
  delta computation, zero-previous divisor handling

Fixes #71

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 14:47:55 -04:00
a26d642b1b Merge pull request 'refactor: split useReports into per-domain hooks + URL period (#70)' (#89) from issue-70-hooks-per-domain into main 2026-04-14 18:40:16 +00:00
le king fu
6a6a196467 refactor: split useReports into per-domain hooks + URL-bookmarked period (#70)
All checks were successful
PR Check / rust (push) Successful in 24m38s
PR Check / frontend (push) Successful in 2m22s
PR Check / rust (pull_request) Successful in 24m56s
PR Check / frontend (pull_request) Successful in 2m31s
- New useReportsPeriod hook reads/writes period via ?from=&to=&period= URL params,
  default civil year, pure resolver exported for tests
- New per-domain hooks: useHighlights, useTrends, useCompare, useCategoryZoom
  (stubs wired to useReportsPeriod, to be fleshed out in #71-#74)
- Rewire legacy useReports to consume useReportsPeriod; keep backward-compat
  state shape (period/customDateFrom/customDateTo) so /reports tabs keep working
- Mark useReports @deprecated pending removal in #76
- Tests: 7 new cases covering resolveReportsPeriod defaults, bookmarks,
  invalid inputs, preset resolution

Fixes #70

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 14:37:33 -04:00
a50be5caf6 Merge pull request 'refactor: pivot removal + sub-route skeletons + shared components (#69)' (#88) from issue-69-foundation-pivot-removal into main 2026-04-14 18:33:17 +00:00
le king fu
91430e994a refactor: remove pivot report, add sub-route skeletons and shared components (#69)
All checks were successful
PR Check / rust (push) Successful in 24m21s
PR Check / frontend (push) Successful in 2m12s
PR Check / rust (pull_request) Successful in 23m5s
PR Check / frontend (pull_request) Successful in 2m16s
- Delete DynamicReport* components and pivot types (PivotConfig, PivotResult, PivotFieldId, etc.)
- Remove getDynamicReportData/getDynamicFilterValues from reportService
- Strip pivotConfig/pivotResult from useReports hook and ReportsPage
- Drop "dynamic" from ReportTab union
- Remove reports.pivot.* and reports.dynamic i18n keys in FR and EN
- Add skeletons for /reports/highlights, /trends, /compare, /category pages
- Register the 4 new sub-routes in App.tsx
- Add reports.hub, reports.viewMode, reports.empty, common.underConstruction keys
- New shared ContextMenu component with click-outside + Escape handling
- Refactor ChartContextMenu to compose generic ContextMenu
- New ViewModeToggle with localStorage persistence via storageKey
- New Sparkline (Recharts LineChart) for compact trends
- Unit tests for readViewMode helper

Fixes #69

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 14:25:38 -04:00
le king fu
cab4cc174a chore: release v0.7.4
All checks were successful
Release / build-and-release (push) Successful in 26m7s
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>
2026-04-14 11:24:40 -04:00
ba5257791f Merge pull request 'fix: migrate PIN hashing from SHA-256 to Argon2id (#54)' (#55) from fix/simpl-resultat-54-argon2id-pin into main 2026-04-14 12:49:05 +00:00
440a43683d Merge pull request 'fix(deps): bump vite to 6.4.2 (GHSA-4w7w-66w2-5vf9, GHSA-p9ff-h696-f583)' (#77) from issue-59-bump-vite into main 2026-04-14 12:29:08 +00:00
9ccfc7a9d9 Merge pull request 'docs: ADR 0006 + changelog + architecture for OAuth keychain (#82)' (#87) from issue-82-wrap-up into main 2026-04-14 12:28:07 +00:00
le king fu
65bc7f5130 docs: ADR 0006 + changelog + architecture for OAuth keychain (#82)
All checks were successful
PR Check / rust (push) Successful in 22m44s
PR Check / frontend (push) Successful in 2m19s
PR Check / rust (pull_request) Successful in 22m25s
PR Check / frontend (pull_request) Successful in 2m19s
- 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>
2026-04-14 08:26:21 -04:00
745f71782f Merge pull request 'feat: settings banner when OAuth tokens use file fallback (#81)' (#86) from issue-81-fallback-banner into main 2026-04-14 12:21:37 +00:00
le king fu
9a9d3c89b9 feat: dismissable banner with session-storage memory (#81)
All checks were successful
PR Check / rust (push) Successful in 22m28s
PR Check / frontend (push) Successful in 2m17s
PR Check / rust (pull_request) Successful in 22m30s
PR Check / frontend (pull_request) Successful in 2m18s
Adds a close button and session-scoped dismissal flag so the banner
can be acknowledged for the current run but reappears on the next
app launch if the fallback is still active — matches the #81
acceptance criterion.

- sessionStorage key survives page navigation within the run, is
  cleared on app restart.
- Graceful on storage quota errors.
- New `common.close` i18n key (FR: "Fermer", EN: "Close") used as
  the aria-label of the close button.
2026-04-14 08:20:20 -04:00
le king fu
3b1c41c48e feat: settings banner when OAuth tokens fall back to file store (#81)
Some checks are pending
PR Check / rust (push) Waiting to run
PR Check / frontend (push) Waiting to run
PR Check / rust (pull_request) Successful in 22m28s
PR Check / frontend (pull_request) Successful in 2m19s
Adds a visible warning in the Settings page when `token_store` has
landed in the file fallback instead of the OS keychain. Without this,
a user on a keychain-less system would silently lose the security
benefit introduced in #78 and never know.

- New `get_token_store_mode` service wrapper in authService.ts.
- New `TokenStoreFallbackBanner` component: fetches the mode on mount,
  renders nothing when mode is `keychain` or null, renders an
  amber warning card when mode is `file`.
- Mounted in SettingsPage right after AccountCard so it sits next to
  the account state the user can fix (log out + log back in once the
  keychain is available).
- i18n keys under `account.tokenStore.fallback.*` in fr/en.

Refs #66

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 08:18:41 -04:00
cf31666c35 Merge pull request 'feat: HMAC-verified account cache (#80)' (#85) from issue-80-subscription-integrity into main 2026-04-14 12:12:16 +00:00
le king fu
2d7d1e05d2 feat: HMAC-sign cached account info to close subscription tampering (#80)
All checks were successful
PR Check / rust (push) Successful in 26m11s
PR Check / frontend (push) Successful in 2m20s
PR Check / rust (pull_request) Successful in 22m22s
PR Check / frontend (pull_request) Successful in 2m18s
Before this change, `license_commands::check_account_edition` read
`account.json` directly and granted Premium when `subscription_status`
was `"active"`. Any local process could write that JSON and bypass
the paywall without ever touching the Logto session.

Introduce `account_cache` with:
- `save(app, &AccountInfo)` — signs the serialised AccountInfo with
  HMAC-SHA256 and writes a `{"data", "sig"}` envelope. The 32-byte
  key lives in the OS keychain (service `com.simpl.resultat`, user
  `account-hmac-key`) alongside the OAuth tokens from #78.
- `load_unverified` — accepts both signed and legacy payloads for UI
  display (name, email, picture). The license path must never use
  this.
- `load_verified` — requires a valid HMAC signature; returns None for
  legacy payloads, missing keychain, tampered data. Used by
  `check_account_edition` so Premium stays locked until the next
  token refresh re-signs the cache.
- `delete` — wipes both the file and the keychain key on logout so
  the next session generates a fresh cryptographic anchor.

`auth_commands::handle_auth_callback` and `refresh_auth_token` now
call `account_cache::save` instead of writing the file directly.
`logout` clears both stores. `get_account_info` delegates to
`load_unverified` so upgraded users see their profile immediately.

Trust boundary: the HMAC key lives in the keychain and shares its
security model with the OAuth tokens. If the keychain is unreachable,
the gating path refuses to grant Premium (fail-closed), which matches
the store_mode policy introduced in #78.

Refs #66, CWE-345

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 08:07:47 -04:00
b684c88d2b Merge pull request 'ci: libdbus-1-dev for keyring build, drop appimage target (#79)' (#84) from issue-79-ci-libdbus into main 2026-04-14 00:35:36 +00:00
le king fu
481018e1e3 ci: install libdbus-1-dev for keyring build, drop appimage target (#79)
All checks were successful
PR Check / rust (push) Successful in 23m16s
PR Check / frontend (push) Successful in 2m17s
PR Check / rust (pull_request) Successful in 21m37s
PR Check / frontend (pull_request) Successful in 2m10s
The new token_store module (#78) depends on `sync-secret-service` via
`dbus-secret-service`, which in turn links to libdbus-1 at build time
through the `dbus` crate. Add `libdbus-1-dev` to:

- `check.yml` rust job (alongside the existing webkit/appindicator
  system deps), so every PR run compiles the keyring backend.
- `release.yml` Linux deps step, so tagged builds link correctly.

Runtime requires `libdbus-1-3`, which is present on every desktop
Linux distro by default, so `.deb` / `.rpm` depends stay unchanged.

Also add a non-blocking `cargo audit` step to check.yml to surface
advisories across the transitive dep graph (zbus, dbus-secret-service,
etc.) without failing unrelated PRs.

Drop `appimage` from `bundle.targets` in tauri.conf.json: the release
workflow explicitly builds `--bundles deb,rpm` so AppImage was never
shipped, and its presence in the config risks a silent fallback to
plaintext token storage for anyone running `tauri build` locally
without libsecret/libdbus bundled into the AppImage. No behaviour
change for CI; follow-up to re-enable AppImage properly would need a
linuxdeploy workflow that bundles the backend.

Refs #66

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 20:27:14 -04:00
e331217c14 Merge pull request 'feat: OAuth token storage via OS keychain (#78)' (#83) from issue-78-token-store into main 2026-04-14 00:17:41 +00:00