Commit graph

26 commits

Author SHA1 Message Date
le king fu
ab78aa9ddc fix(license): check a revoked key as soon as it is entered
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m44s
PR Check — Rust / rust (pull_request) Successful in 3m25s
A license key entered during a session was only checked against the
license server at the next start: a revoked key kept its edition until
the app restarted, or until a machine activation was refused (#365
checked at start only).

Front: performSubmitKey launches the revocation check after
VALIDATE_DONE, without waiting for it, and never after a refused key.
SubmitKeyDeps gets a single launcher, startRevocationCheck(wasRevoked),
built once by the provider with revocationCheckLauncher and shared with
the start check, whose starter is unchanged.

Rust: the command runs the checks one at a time. A static
tauri::async_runtime::Mutex (REVOCATION_CHECK_TURN) is held by
check_revocation_in_turn over the whole check, request and verdict
included, and the time is read once the turn has come. A key entered
before the first ready state, checked by the entry and by the start,
sends one request; the check of a key entered while another one is in
flight waits for its verdict instead of overwriting license.check.
check_revocation_in is unchanged, and so are Cargo.toml and Cargo.lock.

Docs: ADR 0017 (amendment #365, point 5), user guide, in-app docs
(docs.settings.tips, docs.editions.tips), architecture, and both
changelogs under [Unreleased] (Fixed / Corrigé).

Resolves #373

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 16:26:10 -04:00
le king fu
667e058a77 fix(onboarding): apply the three review follow-ups on the stack head
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m47s
- Guide: drop the internal provenance (author, date, issue number) from the
  BMO status line in the three copies (fr.json, en.json, guide-utilisateur.md).
  The status now only says the procedure is reconstructed and unconfirmed.
- Dashboard: gate `showCompactChecklist` on `budgetReady` so a Base+ profile
  with the five other items done no longer sees the compact list vanish (5/5
  under the fail-closed entitlement) and reappear (5/6) once the licence
  resolves.
- Test: pin TZ=America/Toronto around the `formatLocalDate` tests and assert
  that `toISOString()` disagrees, so a UTC regression is caught on the UTC CI
  runner too (measured: the mutation passed 7/7 under TZ=UTC before, fails now).

Follow-ups raised by the independent reviews of PRs #381, #378 and #382.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 08:47:30 -04:00
le king fu
d11337ccf6 docs(onboarding): changelog, architecture, user guide and interactive QA checklist (#357)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m54s
Closes phase 1 of the onboarding wave (#351-#356) on the documentation side.

- CHANGELOG.md / CHANGELOG.fr.md: Added (guided empty states, getting-started
  checklist, sample folder, bank CSV export guide) and Changed (re-show button
  in Settings > Users, Import "?" popover linking to the guide) under
  [Unreleased] / [Non publié], same entries in both languages.
- docs/architecture.md: create_sample_import_folders (fs_commands 6 -> 7,
  total 36 -> 37) with its contract, onboardingService, useOnboardingChecklist,
  the onboarding_checklist_hidden preference, the samples/ corpus next to csv/,
  and an "Onboarding" section (four empty states and their signal, source of
  truth of the six checklist rows, sample-folder flow). Tree counts measured
  for the directories the wave touched.
- docs/guide-utilisateur.md chapters 3 and 11 + docs.dashboard.* and
  docs.settings.* keys (FR/EN). Hand-off from #355: docs.gettingStarted.steps
  aligned with the six-step quick start of the markdown, docs.import.steps
  mentions the sample folder, so the three guide copies agree.
- docs/qa-onboarding.html: interactive manual-test checklist, French only,
  static, no network dependency, state in localStorage, plain-text report to
  the clipboard with a visible fallback, reset button.

Resolves #357

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 20:25:55 -04:00
le king fu
e0ee09bca7 feat(docs): sub-sections in the user guide and bank CSV export guides (#355)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m53s
Add an optional `subsections` array to a user-guide section, rendered by
the new pure `DocsSubsections` component between the "How to" block and
the tips. Each entry has an id, a title, an optional italic status line,
an ordered list of steps and an optional trailing note. The ids are
registered in `sectionRefs` of `DocsContent`, so the existing hash
navigation (`/settings/users#import-export`) reaches the sub-sections
too. Only the Import section declares the key; the eleven other
sections render exactly as before.

The Import section gets six blocks in French and English: a generic
"export a CSV from your bank" block, then Desjardins, Tangerine, RBC,
National Bank and BMO with their verification status. The National Bank
and BMO blocks, which ask the user to send a header line through the
feedback form, carry the warning never to paste an account number, a
balance or a real transaction, in the JSON files and in the markdown
guide alike.

`docs/guide-utilisateur.md` receives the same six blocks in chapter 4,
a mention of the "Create a sample folder" button in its "How to", and a
"Quick start" in chapter 1 rewritten as the six steps of the
getting-started checklist, same order and wording as
`onboarding.checklist.items.*`.

`DocsSubsections.test.tsx` calls the component directly (no jsdom):
null for a section without sub-sections, one block per entry with its
id and ref registration, status line and note rendered only when
present, malformed entries dropped.

Resolves #355

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 20:00:09 -04:00
le king fu
b107cf42ce fix(license): apply the review of #368
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m45s
PR Check — Rust / rust (pull_request) Successful in 8m54s
- A state file that cannot be written now has a test on both of its paths:
  the attempt (nothing is sent, or a read-only data directory would send a
  request at every start) and the verdict (the key is not reported revoked
  when nothing on disk says so). The first path was correct but unguarded:
  the review's mutation left all 185 tests green.
- The storage outcome no longer claims that nothing was sent: it also covers
  a verdict that could not be written. Reworded in licenseService.ts, the
  in-app log line and docs/architecture.md.
- The Updates card names a revoked key in its Free branch, between the
  message and the CTAs: a refunded Base buyer first meets the loss of
  automatic updates there (Max, 2026-10-03). The error page stays as it is.
- A revoked key keeps being checked once per 24 h while it stays stored, so
  that a correction made on the license server is picked up (Max,
  2026-10-03). Declared in the user guide, the in-app docs, the changelogs
  and the ADR 0017 amendment.

Refs #365

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 09:29:47 -04:00
le king fu
e24c30fe6a feat(license): online revocation check — a revoked key falls back to Free (#365)
A refunded license was revoked on the server but kept working in the app:
the edition is resolved offline from the key alone, and the flag added by
#347 lasted the session only and never touched the edition.

Rust
- license.check, next to the key, holds what the license server last said
  about it: {key_sha256, attempted_at, revoked}. Never the key itself. A
  missing, unreadable or invalid file, or a state about another key, reads
  as "no state": not revoked, check due.
- entitlements_from_dir resolves a revoked key to ("free", []), which also
  locks auto-update. read_license and store_license report the state.
- check_license_revocation: POST /v1/licenses/verify with the product and
  the key only. Nothing is sent without a stored key that validates, nor
  within 24 h of the last attempt, which is written before the request.
  Fail-open: only "valid" lifts the state, only "revoked" sets it.
- Activation, machine list and deactivation record a license_revoked answer
  under the key they sent.
- fetch_price reads error.code of a 403 and names license_revoked instead of
  premium_required. It records nothing: the price route judges the license
  of the activation token, which the client cannot tie to the stored key,
  and 0.15.0 left the token of a previous key next to a new one (#367).

Front
- LicenseContext takes the revoked state from Rust (info.revoked); the
  session flag and markRevoked are gone. The check starts once per app
  start, writes its outcome to the in-app log, and reloads the context only
  when the state changed. A revoked key entered again stays Free.
- UpsellGate and the License card name the revocation.

Docs: ADR 0017 amendment (D13 replaced, the first unsolicited request and
its bounds), architecture, user guide, price contract annex, changelogs.

Refs #365

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 14:45:34 -04:00
le king fu
e5662ad018 fix(license): send a derived machine identifier, never the OS machine id (#350)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m49s
PR Check — Rust / rust (pull_request) Successful in 9m19s
Activation sent `machine_uid::get()` as is: /etc/machine-id on Linux,
MachineGuid on Windows. machine-id(5) calls that id confidential and asks
applications to send a keyed hash under a fixed, application-specific key
instead. The license server stores what it receives next to the license
email and the host name, lists it back and signs it into the activation
token.

- derive_machine_id: HMAC-SHA256 under a frozen key, 64 lowercase hex
  characters, pinned by two reference vectors. An empty OS id is refused,
  never hashed.
- Activation sends the derived identifier and requires the issued token to
  carry it. Nothing else originates a machine identifier.
- A machine activated before this change stays activated: a valid token
  whose claim is the raw id of this machine still reads "Activated" (D1).
- The machine list flags `is_this_machine` in Rust, for a row under either
  identifier (D3). `ActivationStatus` no longer carries an identifier and
  the unused `get_machine_id` command is removed: no command hands the OS
  id to the webview any more.
- After a deactivation the local token is dropped when it stood for the
  freed slot: any row of this machine takes it, except the old raw row
  while the token stands for the derived one.
- ADR 0020 records the derivation, the transition and the condition for
  removing the raw-id compatibility.

Behaviour changes beyond the issue text: the machine list and a
deactivation no longer fail on an unreadable OS id (best effort), the
activation status then reads "not activated" instead of failing, and the
unused `store_activation_token` command refuses a token issued for the
raw id.

No new dependency (hmac and sha2 were already declared); Cargo.lock is
untouched. Rust 151 -> 168 tests, vitest 1311 -> 1314.

Refs #350

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 19:52:48 -04:00
le king fu
3f90635298 fix(license): apply the review of #364
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m56s
PR Check — Rust / rust (pull_request) Successful in 10m35s
- store_license_in drops activation.token when the stored key changes.
  The token names the previous license to the price server (its sub is
  the license id, the key carries the email), so the card showed the
  machine as activated while prices were refused for the old license.
  The same key entered again keeps its token; a refused key changes
  nothing.
- get_activation_status goes through activation_status_in(dir,
  machine_id, key), tested in a temporary directory: a missing,
  expired, foreign-machine, foreign-key or unreadable token reads as
  not activated. It alone decides "Not activated" and "Activate".
- LicenseCard is keyed by licenseIdentity(info): another key remounts
  the body, which re-reads the activation and drops the previous key's
  machine list.
- invalidSignature asks to check the copy first. jsonwebtoken checks
  the signature before decoding the payload, so a damaged copy often
  reads as a signature failure (characterization test). User guide and
  changelogs aligned.
- Render tests: the other errors stay visible under Machines, and the
  edition's green check gives way to an alert for a revoked key.

Refs #347

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 19:58:22 -04:00
le king fu
d2ded515fd fix(license): name key-entry errors, keep the edition off the activation token, flag revoked keys
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m43s
PR Check — Rust / rust (pull_request) Successful in 9m11s
Rust (license_commands.rs)
- LicenseError gains InvalidPrefix, InvalidSignature, Malformed and
  UnknownEdition. validate_with_key returns it (ExpiredSignature maps
  to LicenseExpired), and store_license, read_license and
  validate_license_key reject with the same JSON as the network
  commands. store_license_in and read_license_in take a directory, so
  the Storage mapping is tested at the source.
- D12: the edition comes from the license key alone.
  resolve_license_entitlements no longer takes the activation token,
  and current_entitlements reads only license.key
  (entitlements_from_dir). A token that had expired (2-year server
  lifetime) or came from another machine (restored data) used to drop
  a valid key to free, while a key copied without its token kept its
  edition. get_activation_status and persist_activation_token keep the
  strict token validation, and the key keeps its strict validation
  (CWE-613).

Front
- LicenseCard names the load error, and LicenseKeyForm (extracted so a
  static render reaches it) names a refused key, through
  parseLicenseError: no raw backend string reaches the card.
- D13: a license_revoked answer is recorded in LicenseContext for the
  whole session, because the card unmounts when the user leaves
  Settings > Users. Only a different key clears it. Card-level
  message, alert icon, Activate disabled through selectCanActivate.
- Machine counter n/limit, "Not activated", activating and
  deactivating labels, dates in fr-CA/en-CA, and "No expiry date"
  beyond 50 years (licenseExpiry.ts, for the 100-year keys of
  maximus-api#63).
- license.removeLicense and license.machines.activateError removed;
  the price authFailed message points to activating the machine.

Tests: 15 new Rust tests (resolution from a data directory, named
variants, the 100-year key of #363, a key without exp) replace 3
machine-binding tests; vitest covers the card and form renders, the
context revocation, the hook wiring and the locales.
test: reproduces #347 (an expired token of this machine resolved a
valid Base key to free on 5d549d6).

Docs: CHANGELOG FR/EN, guide section 12 and in-app docs,
architecture.md, ADR 0017 amendment for #347, qa-update-cycle.md,
api-contract-prices.md.

Fixes #347
Fixes #360
Refs #363

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 21:39:01 -04:00
le king fu
170b22d356 fix(upsell): apply the review of #362
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m39s
- Render tests for the upsell CTAs (UpsellCtas.render.test.tsx): the
  reviewer's probe, which needs no jsdom (react-dom/server in the node
  environment, plus the FilterPanel.test.tsx pattern of calling a
  component as a function). It pins the Base/Premium CTA on UpsellGate,
  the router guard of HaveKeyButton, the localized product URL, the
  Premium note and the onNavigate forwarding, none of which the suite
  guarded before.
- A failure to open the browser (openUrl) is now logged through
  logError in PurchaseButton, DownloadLatestButton and LicenseCard,
  instead of vanishing as an unhandled rejection that never reaches the
  in-app logs. Tested for both shared buttons.
- upsellCtaFor: comment the trap in making Premium purchasable, since
  PurchaseButton sells Base only.
- ADR 0017 amendment: the Account card text holds in practice only;
  current_entitlements still grants Premium to an active subscription,
  a dormant path until a subscription is sold.
- User guide and in-app docs: the purchase step now comes before the
  key entry, and the Balance button is named "Get Premium".
- CHANGELOG: "Maximus Account" in English, "every screen that requires
  it" in both files (the Balance screen sells nothing), and the French
  elision before a guillemet.

Refs #346

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 20:47:04 -04:00
le king fu
57a0fbc2f7 feat(upsell): activate "Get Base", honest Premium note, purchase path in Free, Account card text
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m42s
- upsellCtaFor(tier) decides what a locked screen offers: Base is
  purchasable, Premium stays disabled with "Premium isn't sold online
  yet". Pure and tested on the whole entitlements matrix.
- Shared CTAs, one component per file: PurchaseButton ("Get Base") and
  DownloadLatestButton open the localized product page (purchaseUrl) and
  need no router; HaveKeyButton renders only inside BrowserRouter.
- UpsellGate uses them; the purchase link never calls onNavigate, so
  modal hosts stay open. ProfileFormModal reuses UpsellGate instead of
  its compact panel, keeps its Cancel button, and closes after "I
  already have a key" (onNavigate={onClose}).
- In Free, the Updates card offers Get Base, I already have a key and
  Download the latest version; the error page (outside the router)
  offers Get Base and the download link. No update check, no network
  call.
- i18n FR/EN: Account card text, Premium note, Updates notEntitled text,
  download label, editions guide step and tip; the "coming soon" note
  key is gone. Tests pin the upsell key set and the decided texts.
- Docs: ADR 0017 amendment, architecture (UpsellGate row, shared CTAs,
  upsellCtaFor, multi-profile row, counts), user guide section 12;
  CHANGELOG FR/EN.

Proven red: Premium made purchasable 2, balance sold as Base in the
matrix 4, the old note key back in a locale 1, Account text altered 1,
download label altered 1.

Fixes #346

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 15:50:45 -04:00
le king fu
e2b8eb8b22 docs: architecture, ADR 0019, user guide and changelog for the import format
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m38s
Last link of the ten-link import-format stack. Links 1-9 deliberately wrote
no changelog and no documentation, to avoid a conflict at every level of a
linear pile; this link owes all of it.

- ADR 0019 records the structuring decision: the import format is a fully
  persisted value, never re-inferred. It documents the three independent paths
  by which it used to be lost (hardcoded restore, header drift, data export),
  and why a single codec with a completeness test closes the class rather than
  a composed type -- the two carriers are structurally incompatible
  (`has_header` is boolean on one and number on the other). `template_id` is
  recorded as a provenance label, never re-read as format.
- `docs/architecture.md` gains a dedicated "Import CSV" section covering the
  codec, the lexical detection layer and its separate dictionary module, the
  bank signatures, the now-mandatory preview step, and sources/templates in
  the SREF envelope. Migration v17 and its four CHECK-guarded columns are
  listed in the migrations table.
- Stale counts corrected against the tree, not by arithmetic: 16 -> 17
  migrations (both files), `src/components/import/` 13 -> 14, `src/utils/`
  4 -> 13. Tables (20) and indexes (24) were re-measured and are NOT stale --
  v17 is an ALTER TABLE only -- so they are left as they are, with the reason
  written down. ADR 0018, missing from the ADR table, is added.
- The user guide and the `docs.*` keys in both locales carry the same new
  import journey: automatic detection, confidence score, mandatory preview
  with its signed recap, sign inversion, recognised bank formats, drift panel,
  and the safe repair path.
- Both changelogs carry the same entries, translated, verified section by
  section including issue references. The `public/` copies sync automatically
  via `syncChangelogs()`.

1176 vitest green, build clean, cargo check clean. No DB migration.

Resolves #332

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 11:13:25 -04:00
le king fu
01da65c215 docs(gating): ADR 0017 + architecture + user guide + CHANGELOG
Document the edition-gating work (#297-#301):

- ADR 0017 (accepted): tier->features matrix, signed features[] override
  fail-closed in Free (CWE-863), UI-only enforcement as an assumed GPL
  soft-paywall (server-enforced price fetching stays the only hard gate),
  non-destructive downgrade, dev-override behind an explicit Cargo
  feature (CWE-489), rejected alternatives.
- architecture.md: new 'Gating par edition' section (entitlements matrix,
  LicenseContext, useEntitlement, RequireFeature/UpsellGate, NavLock,
  profileGate, Rust side), rewritten entitlements.rs section (auto-update
  now Base+, stale 'open to free' note removed), gated routes listed in
  the routing section, hooks table updated (useLicense removed in #297 ->
  useEntitlement/useIsPremium), ADR index + header refreshed.
- guide-utilisateur.md + docs.editions.* i18n keys (FR/EN) wired into
  DocsContent: new 'Editions' section with the Free/Base/Premium table,
  unlock flow and non-destructive locking tips.
- CHANGELOG.md + CHANGELOG.fr.md: one global [Unreleased] entry listing
  the modules now gated Base (Budget, Adjustments, advanced reports,
  multi-profile, auto-update) and Premium (Balance), the visible-but-
  locked upsell with disabled 'coming soon' purchase CTA, and the
  data-preserving behaviour.

Resolves #302

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 22:48:55 -04:00
le king fu
5a3d87b31f docs(collapse): ADR 0016 per-profile UI state + guide, architecture, i18n
Document the final multi-level category collapse behavior (shipped in
#288/#289/#290) and the structural decision to persist per-profile UI
state in user_preferences rather than localStorage.

- ADR 0016 (accepted): profile-specific UI state (category collapse) lives
  in the profile's own SQLite user_preferences table, not localStorage.
  deleteProfile drops the .db but purges no localStorage, so a per-profile
  localStorage key would be a residue surviving profile deletion, leaking
  which categories a (possibly PIN-protected) profile explored. States the
  boundary: profile-specific -> user_preferences; machine-global (theme,
  subtotals position, Cartes period mode) -> localStorage.
- guide-utilisateur.md sections 8 (Budget) + 9 (Reports): multi-level
  collapse, collapsed-by-default, "Expand all / Collapse all" button,
  memory per profile.
- docs.* i18n keys (fr + en): mirror the guide additions in the in-app help
  page (docs.budget + docs.reports features/tips).
- architecture.md: user_preferences line now names the 4 collapse keys,
  useCollapsibleGroups cross-cutting hook note, ADR index row.

No DB migration, no behavior change (docs only). Build clean, 828 vitest.

Resolves #291

Generated autonomously by /autopilot run of 2026-07-15
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 14:43:24 -04:00
le king fu
d41ccbd618 docs(balance): ADR 0015 + guide + architecture + CHANGELOG for per-security detail (#218)
Final docs link of the Étape 2 stack (#210-#217).

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

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

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

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 21:15:06 -04:00
a73bf2ebb0 feat(balance): audit quick wins — terminology, optional symbol, movable snapshot date (#201) 2026-05-31 21:05:13 +00:00
le king fu
bef330affb docs(guide): add user guide section for Bilan
New section 10 "Bilan" walks through:
- the 3 routes (/balance, /balance/snapshot, /balance/accounts)
- snapshot entry (simple + priced kinds, prefill button)
- transfer linking with auto-suggested direction
- multi-horizon Modified Dietz returns (3M / 1Y / since inception)
  read alongside the unadjusted return for comparison
- the FK RESTRICT user-facing message on linked-transaction deletion
- price-fetching premium flagged "coming Phase 5"

Renumbers Settings to section 11.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 17:06:46 -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
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
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
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
le king fu
c8b92517e8 Update docs: dashboard revamp, version 0.6.3
- CLAUDE.md: bump version to 0.6.3
- architecture.md: update version, component counts, dashboard description
- guide-utilisateur.md: rewrite dashboard section (budget table, chart, no recent transactions)
- i18n docs: update dashboard help text in both FR and EN

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 09:53:37 -05:00
le king fu
2a61ffcdb4 Update documentation for v0.4.7 changes
Sync CHANGELOG, architecture docs, user guide, and i18n docs keys
with recent features: log viewer, sticky budget headers, sortable
report detail columns, amount toggle, migration repair, and startup
retry logic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 09:43:28 -05:00
le king fu
20b3a54ec7 feat: add Dynamic Report (pivot table) tab to Reports page
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>
2026-02-22 08:26:50 -05:00
le king fu
942cbb0624 docs: organize technical documentation into docs/ folder
Move specs and masterplan to docs/archive/, add architecture.md with
full technical overview, create 5 ADRs (Tauri v2, useReducer, sqlx
migrations, AES-256-GCM encryption, multi-profile DB), and move
guide-utilisateur.md into docs/.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 07:34:01 -05:00