Commit graph

84 commits

Author SHA1 Message Date
le king fu
8c3a64d172 fix(balance): re-check collisions in-transaction in proposeStarterAccounts (S3)
Adds defense-in-depth: each iteration runs a SELECT COUNT(*) WHERE name=?
AND balance_category_id=? AND archived_at IS NULL inside the BEGIN/COMMIT
block, immediately before its INSERT. On a hit, the iteration skips the
INSERT silently and the returned ids array excludes the skipped starter.

Rationale: balance_accounts has no UNIQUE constraint on (name, category)
and the upstream pre-filter (getStarterCollisions) is best-effort. If a
race or a bypass slips a duplicate through, the in-txn check catches it
without surfacing a confusing error to the user.

Existing two tests in StarterAccountsModal.test.tsx updated to mock the
new SELECT call sequence; new test "skips silently when in-txn collision
check finds an existing account" added.

Suggestion S3 from PR #185 review (#187).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 16:27:16 -04:00
le king fu
2eeac78b40 fix(balance): exclude archived accounts from starter collisions (S4)
getStarterCollisions now filters `archived_at IS NULL` so a starter
account the user voluntarily archived no longer blocks re-creation
through the StarterAccountsModal. Matches the rest-of-codebase
convention (active = is_active=1 AND archived_at IS NULL).

Suggestion S4 from PR #185 review (#187).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 16:26:23 -04:00
le king fu
cd0a2b826f feat(balance): starter accounts + opt-in modal + ADR 0012
Part 1 — New profiles: seed 4 starter accounts in
consolidated_schema.sql (Compte chèque/CELI/REER/Compte
non-enregistré, currency CAD, is_active=1) right after the
balance_categories seeds. Categories resolved via SELECT subquery
on the seeded `key` values for robustness.

Part 2 — Existing profiles: StarterAccountsModal proposes the same
4 starters at first /balance visit. Default-checked checkboxes,
collision rule (case-insensitive trim name + matching category)
disables matches with a "Déjà présent" tooltip. The atomic helper
`proposeStarterAccounts` wraps the inserts in BEGIN/COMMIT (rolls
back on error). user_preferences.balance_starter_proposed records
{shown_at, accepted} so the modal never reappears, dismissed or
confirmed.

Part 3 — docs/adr/0012-balance-two-level-model.md (Proposed):
captures the future vehicles × compositions model for reflection,
no code change. Numbered 0012 because 0011 was already taken by
the providers-best-effort-yahoo ADR. Linked from architecture.md
ADR table and Bilan section.

Tests: StarterAccountsModal.test.tsx covers STARTER_ACCOUNTS shape,
getStarterCollisions (case-insensitive trim, category-scoped) and
proposeStarterAccounts (insert order, COMMIT, ROLLBACK on failure).
No render tests — mirrors the BalanceOnboardingCard pattern (no
jsdom configured).

Resolves #179
2026-05-02 11:59:45 -04:00
le king fu
50b119121f fix(balance): atomic snapshot save with BEGIN/COMMIT + cleanup migration
useSnapshotEditor.save now validates all simple/priced lines in-memory
before any DB write, then delegates to a new saveSnapshotAtomic helper
that wraps INSERT snapshot + INSERT lines in an explicit BEGIN/COMMIT
transaction (ROLLBACK on catch). Pattern matches categorizationService.

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

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

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

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

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

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

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

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

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

Fixes #169
2026-04-28 19:54:04 -04:00
le king fu
80c28d43ac feat(prices): Settings revocation toggle for price_fetching_consent
All checks were successful
PR Check / rust (push) Successful in 28m16s
PR Check / frontend (push) Successful in 3m0s
PR Check / rust (pull_request) Successful in 29m45s
PR Check / frontend (pull_request) Successful in 3m6s
- Adds PriceFetchConsentToggle to SettingsPage Privacy section
- Reads/writes user_preferences.price_fetching_consent for active profile
- Confirmation dialog before revoke (DELETE the key entirely so next click re-opens consent modal)
- Disabled (with notPremium tooltip) when license is not premium
- Adds deletePreference() to userPreferenceService
- Adds settings.privacy.title i18n key (FR + EN)
- 10 vitest tests covering all paths

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

Closes #156

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 08:28:24 -04:00
le king fu
0e996a5aa1 feat(transactions): inline transfer icon + FK error message
Issue #142 / Bilan #4 — non-regressive transfer awareness in the
transactions table + clean error mapping on bulk delete.

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:38:46 -04:00
le king fu
a45e5c3cd0 feat(balance): add LinkTransfersModal + return columns in accounts table
Issue #142 / Bilan #4 — UI for transfer linking + per-account returns.

- New `LinkTransfersModal.tsx`: portal modal with date-range / category /
  free-text filters, multi-select with auto-proposed direction (`in` for
  negative bank amounts, `out` for positive — flippable per row).
  Submits via sequential `linkTransfer` calls; reports per-row failures
  inline (most common case: `transfer_already_linked` on a re-submit).
- `BalanceAccountsTable.tsx`: 4 new columns rendered side-by-side —
  3M / 1A / Since-inception (Modified Dietz via `compute_account_return`)
  + Unadjusted (`(V_end - V_start) / V_start`). Returns load lazily
  after mount via `Promise.all` over (account × horizon); per-cell
  failure leaves the slot at "—" without blocking the rest of the
  table. The actions menu gains a *Link transfers* item that bubbles
  the request up to the parent page. New props:
  `sinceCreationDate` (anchors the since-inception horizon) and
  `onLinkTransfers` (modal opener).
- `BalancePage.tsx`: hosts the new modal, loads the categories list
  once on mount for the filter dropdown, fetches the union of
  `listAccountTransfers` per account so the chart can render markers,
  and threads the earliest snapshot date down to the table. Reload
  is triggered after the modal reports at least one successful link.
- `balance.service.ts`: dropped the unused `BalanceAccountTransfer`
  import to satisfy `tsc --noUnusedLocals`.

`npm run build` clean. `npm test` → 429 passed. Manual sanity check:
the table renders "…" placeholders during the per-row return load,
then resolves to either a percentage or a "—" with the partial
tooltip when the underlying snapshot endpoint is missing.

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

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

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

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:27:16 -04:00
le king fu
396310aa74 feat(balance): add timeseries aggregator helpers + tests
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>
2026-04-25 16:06:23 -04:00
le king fu
db5bffbdcf feat(balance): add priced-kind validation to service + tests
- Export validateLineKindInvariants helper for both 'simple' and 'priced'
  account kinds; surfaces typed BalanceServiceError codes.
- Extend SnapshotLineInput with optional account_kind / quantity /
  unit_price (default 'simple' to preserve #146 callers).
- upsertSnapshotLines now validates kind invariants ahead of the SQL
  CHECK and persists priced lines with non-NULL qty / unit_price.
- Tolerance constant PRICED_VALUE_TOLERANCE = 0.01 absorbs FP drift.
- 14 new unit tests covering simple invariants, priced invariants,
  tolerance edge cases, and mixed batches.

Refs #140

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 14:55:20 -04:00
le king fu
afc338b564 feat(balance): extend balance.service with snapshots + lines (simple kind)
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>
2026-04-25 14:49:19 -04:00
le king fu
58d3c86336 feat(balance): add balance.service CRUD section + tests
Adds the TypeScript service layer for the Bilan feature, scoped to
Issue #138 (Bilan #1a) — categories + accounts CRUD only. Snapshots,
snapshot lines, transfers and price-fetching land in subsequent issues.

The service uses `getDb()` + tauri-plugin-sql directly per project
convention (96 occurrences across 15 services). No new Tauri commands
introduced — the only future Rust commands are `compute_account_return`
(Issue #142) and `fetch_price` (Issue #144).

API surface:
- listBalanceCategories / getBalanceCategory / createBalanceCategory /
  updateBalanceCategory / deleteBalanceCategory (with seed + has-accounts
  guards)
- listBalanceAccounts (excludes archived by default) / getBalanceAccount
  / createBalanceAccount (CAD-only at MVP) / updateBalanceAccount /
  archiveBalanceAccount / unarchiveBalanceAccount (soft delete)

Typed errors via BalanceServiceError + BalanceErrorCode union so the UI
can render distinct i18n messages. Domain types added under
`src/shared/types/index.ts`: BalanceCategoryKind, BalanceCategory,
BalanceAccount, BalanceAccountWithCategory, BALANCE_CURRENCY_CAD.

19 vitest cases cover: ordering, kind validation, seed protection,
linked-account guard, currency rejection, missing-category lookup,
soft delete + restore round-trip, symbol/notes normalization.

Refs #138

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 14:33:39 -04:00
le king fu
12d1877870 test(categories): complete test coverage for migration flow (#123)
All checks were successful
PR Check / rust (push) Successful in 22m48s
PR Check / frontend (push) Successful in 2m20s
PR Check / rust (pull_request) Successful in 22m51s
PR Check / frontend (pull_request) Successful in 2m21s
Adds unit + integration + regression tests and a QA checklist for the
v2→v1 seed migration feature.

- Fixtures: src/__fixtures__/profiles.ts (makeV2Profile, makeV1Profile,
  makeV2ProfileWithCustom) with realistic categories, keywords,
  suppliers, transactions, budgets.
- Unit: categoryMappingService (100 cases covering every DEFAULT_MAPPINGS
  entry, 4-pass priority, splits, preserved/custom detection),
  categoryBackupService (23 cases — Tauri mocks: success, write error,
  integrity check, PIN-encrypted profile), categoryMigrationService (16
  cases — BEGIN/COMMIT/ROLLBACK flow, backup-missing abort, journaling,
  custom parent creation).
- Integration: full plan→backup→migrate→verify flow; rollback via SREF
  import; backup failure → no DB write; migration SQL failure → ROLLBACK
  + intact state.
- Regression: parameterised v2/v1 fixtures covering auto-categorisation,
  budget aggregation, splits preservation.
- Docs: docs/qa-refonte-seed-categories-ipc.md — manual checklist for UX,
  system errors, encrypted profile, custom preservation, 90-day banner,
  restore flow.

331 vitest tests pass (up from 193 baseline).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 19:25:13 -04:00
le king fu
0132e6e164 feat(categories): add restore backup banner and permanent restore action (#122)
All checks were successful
PR Check / rust (push) Successful in 21m45s
PR Check / frontend (push) Successful in 2m17s
PR Check / rust (pull_request) Successful in 21m1s
PR Check / frontend (pull_request) Successful in 2m13s
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
2026-04-20 21:47:43 -04:00
le king fu
0646875327 feat(categories): add 3-step migration page + categoryMigrationService (#121)
All checks were successful
PR Check / rust (push) Successful in 21m39s
PR Check / frontend (push) Successful in 2m21s
PR Check / rust (pull_request) Successful in 21m49s
PR Check / frontend (pull_request) Successful in 2m15s
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>
2026-04-20 21:31:21 -04:00
le king fu
be3cda1556 feat(categories): add categoryMappingService (4-pass algo) (#119)
All checks were successful
PR Check / rust (push) Successful in 22m33s
PR Check / frontend (push) Successful in 2m18s
PR Check / rust (pull_request) Successful in 21m36s
PR Check / frontend (pull_request) Successful in 2m13s
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).
2026-04-20 21:01:55 -04:00
le king fu
742aa9ec3c feat(categories): add categoryTaxonomyService + useCategoryTaxonomy hook (#116)
All checks were successful
PR Check / rust (push) Successful in 22m44s
PR Check / frontend (push) Successful in 2m17s
PR Check / rust (pull_request) Successful in 22m4s
PR Check / frontend (pull_request) Successful in 2m15s
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>
2026-04-20 20:53:15 -04:00
f3af3d7c1b Merge pull request 'feat(categories): v1 IPC seed + i18n keys + migration v8 (#115)' (#125) from issue-115-seed-v1-i18n into main 2026-04-19 20:51:16 +00:00
le king fu
bd992f2f94 feat(categories): add v1 IPC seed, i18n keys, and migration v8 (#115)
All checks were successful
PR Check / rust (push) Successful in 22m29s
PR Check / frontend (push) Successful in 2m18s
PR Check / rust (pull_request) Successful in 22m39s
PR Check / frontend (pull_request) Successful in 2m18s
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>
2026-04-19 16:41:55 -04:00
le king fu
3c628d4cd1 feat(categories): add categoryBackupService for pre-migration SREF backup (#120)
All checks were successful
PR Check / rust (push) Successful in 22m20s
PR Check / frontend (push) Successful in 2m18s
PR Check / rust (pull_request) Successful in 22m1s
PR Check / frontend (pull_request) Successful in 2m16s
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>
2026-04-19 15:46:23 -04:00
le king fu
3be05db41a feat(reports/cartes): Mensuel/YTD toggle on KPI cards + user guide section (#102)
All checks were successful
PR Check / rust (push) Successful in 21m48s
PR Check / frontend (push) Successful in 2m15s
PR Check / rust (pull_request) Successful in 21m44s
PR Check / frontend (pull_request) Successful in 2m16s
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>
2026-04-19 09:49:21 -04: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
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
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
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
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
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
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
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
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
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
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
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
le king fu
e314bbe1e3 fix: remove handle_auth_callback from invoke_handler
All checks were successful
PR Check / rust (push) Successful in 17m12s
PR Check / frontend (push) Successful in 2m12s
PR Check / rust (pull_request) Successful in 16m56s
PR Check / frontend (pull_request) Successful in 2m14s
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>
2026-04-10 15:35:10 -04:00
le king fu
b53a902f11 feat: Maximus Account OAuth2 PKCE + machine activation + subscription check (#51, #53)
All checks were successful
PR Check / rust (push) Successful in 16m34s
PR Check / frontend (push) Successful in 2m14s
PR Check / rust (pull_request) Successful in 16m31s
PR Check / frontend (pull_request) Successful in 2m13s
- Add auth_commands.rs: OAuth2 PKCE flow (start_oauth, handle_auth_callback,
  refresh_auth_token, get_account_info, check_subscription_status, logout)
- Add deep-link handler in lib.rs for simpl-resultat://auth/callback
- Add AccountCard.tsx + useAuth hook + authService.ts
- Add machine activation commands (activate, deactivate, list, get_activation_status)
- Extend LicenseCard with machine management UI
- get_edition() now checks account subscription for Premium detection
- Daily subscription status check (refresh token if last check > 24h)
- Configure CSP for API/auth endpoints
- Configure tauri-plugin-deep-link for desktop
- Update i18n (FR/EN), changelogs, and architecture docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 14:18:51 -04:00
le king fu
2da2de183a feat: license card in settings (#47)
All checks were successful
PR Check / rust (push) Successful in 16m19s
PR Check / frontend (push) Successful in 2m14s
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
2026-04-09 15:47:04 -04:00
escouade-bot
34626711eb fix: address reviewer feedback (#54)
- 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>
2026-04-09 15:46:27 -04:00
escouade-bot
d099678ddf fix: address reviewer feedback (#41)
- 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>
2026-03-18 02:02:41 -04:00
escouade-bot
56b46f1dfa Remove hard-coded expense filter from Category Over Time report
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>
2026-03-18 00:05:27 -04:00
4e70eee0a8 feat: show actual transactions in budget previous year column
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>
2026-03-10 23:03:26 -04:00
66d0cd85ff fix: address reviewer feedback (#23)
- 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>
2026-03-09 21:02:24 -04:00
dbe249783e fix: display level 4+ categories under their parent in dashboard budget table (#23)
- 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>
2026-03-09 01:10:46 -04:00
le king fu
e1192beca3 Fix missing categories and gray text in category reports
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>
2026-03-07 09:06:40 -05:00
le king fu
0a5b7bce10 Bump version to 0.6.0 — Reports enhancements and comment visibility fix
All checks were successful
Release / build-and-release (push) Successful in 26m12s
- 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>
2026-03-04 21:01:13 -05:00
le king fu
15d626cbbb Fix migration checksum mismatch on startup
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>
2026-03-01 09:17:35 -05:00
le king fu
fb92cfc12c Add startup retry logic and persist logs across refresh
All checks were successful
Release / build-and-release (push) Successful in 25m5s
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>
2026-02-28 22:12:41 -05:00
le king fu
d604d5ae63 Fix updater latest.json and add log viewer in settings
Some checks failed
Release / build-and-release (push) Has been cancelled
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>
2026-02-28 21:09:10 -05:00