Add four service helpers used by the upcoming `/balance` overview:
- getSnapshotTotalsByDate(range?) — SUM(value) GROUP BY snapshot_date
with an optional inclusive [from, to] range. LEFT JOIN preserves
empty snapshots as zero rows so the chart shows continuity.
- getSnapshotTotalsByCategoryAndDate(range?) — same aggregation broken
down by balance_categories.key, returned as one row per snapshot
date with a `byCategory` map. Powers the stacked-area variant.
- getAccountsLatestSnapshot() — one row per active account with the
value of its most-recent snapshot line (NULL when none exists).
Filters archived accounts via WHERE is_active = 1 AND archived_at
IS NULL, matches the listBalanceAccounts default.
- getAccountsPeriodAnchor(range) — earliest snapshot_date >= from
per account, with the value at that date — the anchor used to
compute the per-account Δ% column on the accounts table.
Tests cover empty DB, single/multi snapshot, archived exclusion via
SQL inspection, date-range params (from-only, both bounds, open).
Refs: #141
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the snapshots + lines section of the balance service for Issue #146
(Bilan #1b). Simple-kind only — quantity / unit_price are forced to NULL
both at the SQL CHECK level (already in v9) and at the service level
(`upsertSnapshotLines` validates ahead of time). Priced-kind upsert lands
in #140.
New service exports:
- listSnapshots / getSnapshotByDate / getSnapshotById / getPreviousSnapshot
- createSnapshot (throws snapshot_date_taken when UNIQUE per date violated
so the UI can redirect to edit mode)
- updateSnapshot / deleteSnapshot (cascade lines via FK)
- listLinesBySnapshot / upsertSnapshotLines (rewrite-all strategy)
New BalanceErrorCode entries: snapshot_date_required, snapshot_date_taken,
snapshot_not_found, snapshot_value_invalid, snapshot_priced_unsupported.
New shared types: BalanceSnapshot, BalanceSnapshotLine.
22 new vitest cases cover: invalid-date guards, unique-per-date violation,
simple-kind null invariant on inserts, NaN/Infinity rejection,
clear+rewrite line semantics, getPreviousSnapshot strict-before ordering.
Refs #146
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>
Pure function that computes a v2 → v1 category migration plan from a
snapshot of the profile data. The 4-pass algorithm (keyword → supplier
propagation → default fallback → needs review) produces a MigrationPlan
with confidence badges (high/medium/low/none) per row, exposes split
targets for categories that ventilate across multiple v1 leaves (e.g.
Transport en commun → Autobus + Train), and preserves user-custom
categories in a dedicated bucket for later placement under
"Catégories personnalisées (migration)".
- Mapping tables encoded from
.spikes/archived/seed-standard/code/mapping-old-to-new.md
- No DB I/O: the caller hands us categories, keywords, transactions and
optional suppliers; the service stays testable and side-effect-free.
- 20 unit tests cover every pass, custom preservation, split exposure,
stats aggregation and pass priority.
Prepares the ground for #121 (migration writer UI).
Source of truth for the v1 IPC taxonomy on the TS side. Loads the bundled
JSON, exposes typed helpers (findById, findByPath, getLeaves, getParentById)
used by the upcoming Guide (#117) and Migration (#121) pages.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Wrapper around dataExportService that creates and verifies a full SREF
backup before the v2->v1 categories migration. Throws on any failure to
ensure migration aborts cleanly.
- Generates filename <ProfileName>_avant-migration-<ISO8601>.sref
- Writes to ~/Documents/Simpl-Resultat/backups/ (creates dir if missing)
- Verifies integrity via re-read + SHA-256 checksum
- Reuses profile PIN for encryption when protected
- Adds two minimal Tauri commands: ensure_backup_dir, get_file_size
- Stable error codes (BackupError) to map to i18n keys in the UI layer
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
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>
- 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>
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>
- 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>
- 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>
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>
The auth callback is handled exclusively via the deep-link handler in
lib.rs — exposing it as a JS-invocable command is unnecessary attack
surface. The frontend listens for auth-callback-success/error events
instead.
Plaintext token storage documented as known limitation (see #66).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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
- Add automatic re-hashing of legacy SHA-256 PINs to Argon2id on
successful verification, returning new hash to frontend for persistence
- Use constant-time comparison (subtle::ConstantTimeEq) for both
Argon2id and legacy SHA-256 hash verification
- Add unit tests for hash_pin, verify_pin (Argon2id and legacy paths),
re-hashing flow, error cases, and hex encoding roundtrip
- Update frontend to handle VerifyPinResult struct and save rehashed
PIN hash via profile update
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Default categoryType filter to "expense" so top-N ranking is not
skewed by mixing income and expense amounts
- Remove redundant showFilterPanel condition (hasCategories already
covers the overTime tab)
- Add unit tests for getCategoryOverTime query construction and output
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>
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>
- Extract reorderRows into shared utility (src/utils/reorderRows.ts) to
deduplicate identical function in BudgetTable and BudgetVsActualTable
- Restore alphabetical sorting of children in budgetService.ts
- Fix styling for intermediate parent rows at depth 2+ (was only handling
depth 0-1)
- Reduce pie chart size (height 220->180, radii reduced) and padding to
give more space to the table
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace flat alphabetical sort with tree-order traversal so child
categories appear directly under their parent subtotal row
- Make category hierarchy recursive (supports arbitrary depth)
- Reduce pie chart width from 1/2 to 1/3 of the dashboard
- Show pie chart labels only on hover via tooltip
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Increase category over time limit from 8 to 50 so all categories appear.
Fix category name visibility: use foreground color for bar chart Y-axis
labels and chart legend text instead of muted/inherited colors.
Closes#13
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add table/chart toggle for Trends, By Category, and Over Time reports
- Add "Show amounts" toggle to display values on chart elements
- Add filter panel with category checkboxes and source dropdown
- Add source filter at SQL level for all chart report queries
- Add sticky headers on Dynamic Report and Budget vs Actual tables
- Add interactive hover: dimmed non-hovered bars, filtered tooltip, legend hover
- Fix comment icon color to match split indicator (orange) (#7)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add repair_migrations Tauri command that deletes stale migration 1
checksum from _sqlx_migrations before Database.load(). Migration 1
is idempotent (CREATE IF NOT EXISTS) so re-applying is safe.
Fixes "migration 1 was previously applied but has been modified".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Retry connectActiveProfile up to 3 times with 1s delay before showing
the error page. Persist log buffer to sessionStorage so logs survive
page refresh and are visible in the settings log viewer.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix updater: strip v-prefix from version in latest.json, and delete
old package before re-uploading to avoid 409 conflicts.
Add frontend log capture (console intercept) with a log viewer card
in the settings page (filterable, copyable, clearable).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Keywords starting/ending with non-word characters (brackets, parens,
dashes) never matched because \b requires a word↔non-word transition.
Now uses smart boundaries: \b for word-char edges, (?<=\s|^)/(?=\s|$)
for non-word edges. Also pre-compiles regex patterns once per batch
instead of recreating them for every description × keyword combination.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Support up to 3 levels of categories (e.g., Dépenses récurrentes →
Assurances → Assurance-auto) while keeping SQL JOINs bounded and
existing 2-level branches fully compatible.
Changes across 14 files:
- Types: add "level3" pivot field, depth property on budget row types
- Reports: grandparent JOIN for 3-level resolution in dynamic reports
- Categories: depth validation (max 3), auto is_inputable management,
recursive tree operations, 3-level drag-drop with subtree validation
- Budget: 3-level grouping with intermediate subtotals, leaf-only
aggregation, depth-based indentation (pl-8/pl-14)
- Seed data: Assurances split into Assurance-auto/habitation/vie
- i18n: level3 translations for FR and EN
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The dynamic report now relies exclusively on its own panel filters
instead of inheriting the global period selector date range.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Combine column dimensions into composite keys instead of using only the
first column dimension, enabling richer pivot tables and charts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fields can now be assigned to multiple zones simultaneously (e.g. rows + filters).
Right-clicking a filter value excludes it (NOT IN), shown with strikethrough in red.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement a pivot table feature allowing users to compose custom reports
by assigning dimensions (Year, Month, Type, Level 1/2) to rows, columns,
and filters, with periodic and YTD measures as values. Includes a side
panel for configuration, a dynamic table with subtotals, and a stacked
bar chart visualization.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a "Répartitions" section below manual adjustments listing all
split transactions. Clicking a split opens the existing modal to
view, edit, or delete it.
Fix CSV auto-detect failing on files with preamble lines (e.g.
Mastercard CSVs with metadata header). Three fixes:
- Delimiter detection uses mode of column counts instead of first-line
- Detect and skip preamble rows before header/data detection
- Exclude date-like columns from amount candidates and prefer columns
with decimal values when picking the amount column
Bumps version to 0.3.4.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Allow users to split a transaction across multiple categories directly
from the transactions table. Split children are hidden from the list
and automatically included in dashboard, report, and budget aggregates.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Auto-fix duplicate sort_order values on load, auto-assign sort_order on
category creation, and add drag-and-drop via @dnd-kit to reorder and
reparent categories in the tree (with 2-level nesting constraint).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Each profile gets its own SQLite database file for complete data isolation.
Profile selection screen at launch, sidebar switcher for quick switching,
and optional 4-6 digit PIN for privacy. Existing database becomes the
default profile with seamless upgrade.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New tabular report showing actual vs budgeted amounts per category,
with dollar and percentage variations for both the selected month
and year-to-date. Includes parent/child hierarchy, type grouping,
variation coloring, and month navigation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>