Simpl-Resultat/CHANGELOG.md
le king fu 1ed6a67af0
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m6s
PR Check — Rust / rust (pull_request) Successful in 3m46s
fix(i18n): the "already exists" message also holds for a file (#384)
Self-review finding: create_sample_import_folders refuses on any existing
entry named like a sample folder, a file included, which "Refresh the list"
never shows. The message now says to refresh the list and, if the entry
does not show up, to rename it before trying again (FR and EN), and the
changelog entries follow.

Resolves #384

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 20:47:05 -04:00

100 KiB
Raw Permalink Blame History

Changelog

[Unreleased]

Added

  • Categories: a “Check hierarchy” button, between “All keywords” and “Re-initialize”, lists the orphan categories: those caught in a parent loop, which hides them and their subcategories from the tree, the category menus, the budget and the reports, and those whose parent was deleted or cannot be found. Each one can be reattached to the root, chosen by default, or to another parent; it then comes back in its place with its transactions, keywords and budget amounts. The Journal notes each reattachment, without any category name (#393).

Changed

  • Settings → System: “Logs and feedback” now comes right after “Update”, before the version history, which no longer pushes the Journal to the bottom of the page. The version history opens on the notes of the installed version, with every other version folded: click a version number to read its notes, or “Expand all” to open the whole history (#372).
  • Adjustments: the category of an adjustment line now takes typing, like the category cell of the Transactions page: type a few letters to filter the list, which shows the hierarchy and the names of the standard categories in your language. It offers the same categories as the Transactions page, active and allowed for input, as does the split window opened from the Adjustments page; a line already saved on a category hidden from input keeps it, its field empty until you choose another one. The category filter of the “Link transfers” window of the Balance sheet takes typing too, with “Any category” first. An open category list is no longer cut by the edge of a window or of the Transactions table, in the split window as on the last rows of the table, and it opens upward when it does not fit below but fits above (#429).
  • Transactions: on a profile without any transaction, the “No transactions yet” card now shows as soon as the app knows the profile is empty, instead of the filters, the totals and “Loading...” until the first page of the list comes back (#384).

Fixed

  • Categories: a new parent is now checked when a category is saved. One of the category's own subcategories (the parent loop that hid a whole branch), a parent that would make a fourth level, and a parent with transactions of its own (once a parent, it would no longer take input) are refused with a message, and nothing is saved. A category saved with its parent unchanged is never refused by these checks (#393).
  • Budget: a category whose parent was deleted or cannot be found no longer disappears from the Budget grid and the Budget vs Actual report: it now shows at the top level, as in the category tree (#393).
  • Import: the messages of “Create a sample folder” now follow the app's language: a sample folder already in the import folder (named, with what to do: refresh the list, or rename it if it does not show up), an import folder that cannot be found, today's date unreadable. Any other error still shows as the system wrote it. Once the sample folders are written, the success message only shows when the import folder could be read again; otherwise the error message shows alone, instead of both at once (#384).

[0.17.2] - 2026-10-18

Added

  • Import: a warning names the CSV files placed directly in the import folder, which are ignored (#427).
  • Getting started: two visit steps, “Browse categories and keywords” (before categorizing) and “Browse the reports” (last), checked with “Got it”; a profile that had finished the path finds them already done; their cards, on the Categories and Reports pages, explain keywords and the reports hub (#425, #432).

Changed

  • The "Parent category" field of the category form and the "Category" field of the "New keyword" dialog (right-click on a transaction) now take typing, like the category cell of the Transactions page: type a few letters to filter the list, which shows the hierarchy with indentation and the names of the standard categories in your language. The parent list no longer offers the category being edited, its own sub-categories, or a parent that would create a fourth level; a parent already stored stays shown. The keyword dialog offers the same categories as the Transactions page (active and allowed for input) and keeps the pre-filled category only when it is among them; its two fields sit above the scrolling list of matches, so the open category list is never cut (#423).
  • One copy is kept per profile in the migration-backups folder: the copy taken before a database update is now taken again at each update, and once the update succeeds, the older copies of that profile are deleted. A copy left by an update that stopped is no longer reused as is, since it predated what you entered after it. When an update stops, the Journal now names the copy taken before it (#395).
  • Import: the empty state shows the expected structure (a parent folder, one subfolder per account), and the getting-started cards ask you to choose the parent folder and to check that an expense counts among the outflows (#422).
  • Getting started: the Transactions card explains keywords and suggests “Add as keyword”; the Budget card states that only last-level categories take an amount and what the Annual column does (#430).
  • Splitting a transaction across categories (the Split button of the Transactions page) is now part of the Base edition, as the unlock screen already promised. On the Free edition the button keeps its place, shows a lock and opens the unlock screen instead of the split window, for every row. Splits made earlier stay in your data: they are still counted in the reports and the dashboard, and still flagged in the list (#369).

Fixed

  • The startup error page no longer stays on screen when the profile finishes opening after the 10-second delay: the app now shows as soon as the profile is open (#395).
  • In French, the small badge next to the "Fetch price" button of a stock in the Balance sheet now reads "non garanti" instead of the English "best-effort" (#389).
  • Installs from the .rpm package now receive automatic updates. The update manifest only listed the .deb, which an .rpm install downloaded and then refused; it now lists each Linux package under its own key. Existing .rpm installs need no reinstall: they will receive the next version (checked on the published v0.17.1 package). The download instructions of the release page no longer mention an AppImage, which is not built, and now name the .rpm. The manifest no longer has a generic Linux entry: the AppImage builds of 0.4.3 to 0.4.5, which could not install the .deb it pointed to and were broken by it, now report an error when checking for updates instead; install the .deb or .rpm package (#320).
  • Budget: an expanded subcategory now shows under its parent category instead of at the end of the group. With subtotals at the bottom, each subtotal follows its own rows, in the Budget grid as in the Budget vs Actual and Compare reports (#424).
  • Import: a source is named after its subfolder, read-only; a source renamed during configuration is no longer taken for a new source at the next import (#426).
  • Help and guide: Budget describes typing in the Annual column (no more split button) and that only last-level categories take an amount; Reports have five cards and no tabs; adding a keyword from a transaction goes through the right-click menu (#431).
  • Deleting a profile, a category, an adjustment, a budget template or a Balance sheet category, and removing the license key, now wait for your answer to the confirmation. Until now the action ran as soon as the confirmation appeared, and “Cancel” stopped nothing: a deleted profile lost its data. The confirmation buttons now follow the app language (#421).

Security

  • Updated npm dependencies to clear the eight vulnerabilities reported by npm audit (five high, three moderate), through the lockfile alone: every new version stays within the range package.json already declares. react-router-dom and react-router 7.18.1 → 7.18.4 clear GHSA-qwww-vcr4-c8h2 (CSRF in React Server Components mode), the advisory left as is until now because leaving its range seemed to require migrating to react-router v8; 7.18.2 fixed it. The rest is build and test tooling, never shipped in the app: vitest and @vitest/mocker 4.1.9 → 4.1.11, browserslist 4.28.4 → 4.29.3, baseline-browser-mapping 2.10.41 → 2.11.28, nanoid 3.3.16 → 3.3.20 and source-map-js 1.2.1 → 1.2.2. None of these advisories was reachable in the shipped app, which runs react-router in its own window, with no server. Of all these updates, only react-router's changes the built app: it now refuses a navigation to another origin, which the app never makes, so navigation behaves as before (#358).
  • Updated the Rust dependency rustls (0.23.45 → 0.23.46), clearing RUSTSEC-2026-0346, RUSTSEC-2026-0347, RUSTSEC-2026-0348 and RUSTSEC-2026-0349. rustls is the TLS layer of the automatic update check and download, reached through tauri-plugin-updater; the application's own licence, authentication, market-price and feedback calls use a different TLS backend. None of the four applies to how the application uses it: no custom session store exists that could log session secrets (0346), Encrypted Client Hello is never enabled (0347), the optional Brotli certificate decompression is not compiled in (0348), and the ring cryptography provider offers no post-quantum key exchange that could be downgraded (0349). Updated anyway rather than reasoned around, since a fix exists. Nothing in the application's own code changes — only the lockfile moves, and Cargo.toml is untouched. What changes is the TLS layer's own behaviour: besides these four fixes, 0.23.46 carries about ten other corrections (protocol conformance, hardening, I/O), such as rejecting protected change_cipher_spec records and performing TLS 1.3 key updates on time (#447).
  • Removed the archive extraction of the automatic updater (its zip dependency on Windows and tar on Linux), clearing RUSTSEC-2026-0367, RUSTSEC-2026-0368 and RUSTSEC-2026-0369. The updater only extracted an update that arrived as an archive (a zip on Windows, a .tar.gz AppImage on Linux), after checking its signature, and this application's updates are never archives: the Windows installer (.exe) is run as it is and the Linux packages (.deb, .rpm) are installed by the system's package manager. None of the three advisories, two of which concern Unix permissions while zip was only compiled for Windows, was reachable; the dependency is removed rather than reasoned around, since no fixed version fits the updater. Updates of the Windows installer and of the Linux packages work as before (#455).

[0.17.1] - 2026-10-08

Added

  • A getting-started banner at the top of every page while the getting-started path is shown: "Getting started 3/6", the next step with a link to the page where it is done, "Show the list", which unfolds the six steps in place, and "Hide", which hides the banner and the list for the current profile. It does not show on the dashboard of a profile without transactions, whose welcome card already holds the list. Once every step is done or skipped, it says so and offers "Open the guide", which leads to the "Getting Started" section of the user guide, and "Close", which removes it until it is shown again from Settings → Users (#397).
  • Getting-started cards on the Import, Transactions and Budget pages. While the next step of the path is done on one of these pages, a card takes the place of the banner there: it shows "Step n of N", says what to do and points at the control to use with an outline that pulses three times, not at all when the system asks for reduced motion. When the step gets done, the card stays, says "Done." and offers "Next step", which leads to the page of the following step, or the end of the path. Arriving on a page by a "Next step" link puts the keyboard focus on the card's title. "Skip this step" marks the step as skipped in the getting-started list, where "Resume" takes it back (#398, #399, #400).
  • On the Import page, the getting-started card follows the import wizard screen by screen through its three steps (choose an import folder, set up an import source, import a first file) and says "Done." once the file is imported, never for an import that added no transaction (#398).
  • On the Transactions page, the link of the "Categorize a transaction by hand" step opens the page on the uncategorized transactions whatever their date, or on every transaction when none is left uncategorized, and the card points at the category of the first transaction shown. In the Free edition, where the Budget step is locked, categorizing a transaction ends the path (#399).
  • On the Budget page, whose grid opens with its categories folded, the getting-started card first points at "Expand all", then at the current month's cell of the first category you can fill in, and never unfolds the grid for you. It reminds you that Enter saves, and that Tab saves and moves to the next month. Saving an amount ends the path (#400).
  • A sub-section, "The getting-started path", in the "Getting Started" section of the user guide, the one "Open the guide" opens at the end of the path: the banner and its list, the step cards and their outline, "Skip this step" and "Resume", the end of the path, the profiles that find it hidden from the start, and how to show it again from Settings → Users. The Dashboard, Profiles and Settings sections of the guide describe the path as it now works (#401).

Changed

  • The getting-started path no longer shows in a profile that already had transactions before this version: the first time such a profile is opened, the banner and its checklist are hidden, as if you had clicked "Hide", and "Show the getting-started checklist" in Settings → Users brings them back. A profile without transactions keeps them (#396).
  • The dashboard no longer shows the compact getting-started bar once the profile has transactions: the banner replaces it on every page. In the detailed list, screen readers now hear whether each step is done, skipped, locked or pending (#397).
  • Settings → Users offers "Show the getting-started checklist" as soon as the path is hidden, also when it was hidden from the banner of that same page, and now confirms that the path reappears at the top of the pages (#397).

Fixed

  • Opening the Import page no longer flashes the "No import folder" empty state while the saved import folder is being read (#398).
  • The "Import a first file" getting-started step is no longer checked by an import that added no transaction, such as a file holding only its header line, or one whose rows were all in error in the preview: the step now waits for a file that brought at least one transaction (#398).
  • Categorizing a transaction by hand on the Transactions page now ticks the "Categorize a transaction by hand" step of the getting-started checklist. The app stored that mark in a form the checklist could not count. Transactions you categorized by hand before this version are counted as soon as the profile is opened, and a backup exported in the meantime restores them correctly (#390).
  • Profiles other than the default one now receive database updates. Until now each one kept the database layout of the version that created it, so features added since could fail in it: in a profile created before 0.15.0, for instance, the import format saved on a source. When such a profile is opened, the app first keeps a copy of its database in the migration-backups folder next to the databases, then applies the missing updates. The copy is deleted with the profile, and each update is written to the logs (Settings → System) (#390).
  • On Linux, deleting a profile now deletes its database, which used to stay on disk in ~/.config/com.simpl.resultat/; the file of a profile deleted with version 0.17.0 or earlier can be deleted by hand. The return columns of the Balance sheet accounts now show the returns on Linux, where every cell showed a dash (#390).

[0.17.0] - 2026-10-05

Added

  • Guided empty states on the Dashboard, Import, Transactions and Budget pages: a profile without transactions now explains what the page will show and offers the next step ("Choose an import folder", "Import transactions") instead of an empty chart or table (#352, #353, #356).
  • A getting-started checklist on the Dashboard, computed from your data: six steps (create a profile, choose an import folder, set up an import source, import a first file, categorize a transaction by hand, enter a first budget) tick themselves as you go, and the Budget step is marked "Base and up" in the Free edition. The list fills the empty dashboard, shrinks to a compact bar once transactions exist, disappears when every step is done, and "Hide" dismisses it for the current profile only (#351, #352).
  • "Create a sample folder" on the Import page when the import folder has no bank sub-folder: it writes two fictitious sources ("Exemple Desjardins", "Exemple Tangerine"), one statement each covering the two previous months, so you can try the wizard before your real statements are in place. It refuses to overwrite an existing folder of the same name (#354, #356).
  • An "Export a CSV from your bank" sub-section in the user guide, with a procedure per bank (Desjardins, Tangerine, RBC, National Bank, BMO) and a status line saying whether each one was confirmed on a real account (#355).
  • "Remove the key" on the License card (Settings → Users) removes the stored license key from this computer, after a confirmation that says what it changes. A valid key: the modules of its edition lock and the app goes back to the Free edition, with your data kept. A revoked key: the app stops sending it to the license server, which it did at most once every 24 hours for as long as the key stayed stored. When this machine is activated, its slot is freed on the license server first; if the server cannot be reached, the app asks whether to remove the key anyway, and the slot then stays occupied until you free it from another computer that has the key. A revoked key removed then entered again stays revoked. Each removal is written to the logs (Settings → System) (#370).

Changed

  • Settings → Users offers "Show the getting-started checklist" once the checklist was hidden, and confirms that it will reappear on the dashboard (#352).
  • The "?" help popover of the Import page now links to the guide's "Export a CSV from your bank" sub-section, as does the Import empty state (#356).

Fixed

  • A license key revoked by the license server and entered during a session now puts the app back in the Free edition right away: the key is checked as soon as it is stored, in the background, instead of at the next start. It used to keep its edition until the app restarted, or until you tried to activate the machine. Entering a key still answers at once, online or offline, and a revoked key shows its edition for a second or two before the switch. The check still goes out at most once every 24 hours for the same key, and each check is written to the logs (Settings → System) (#373).
  • A machine whose activation no longer serves gets "Activate" back on the License card (Settings → Users), without touching any file. This happened to a computer where a version up to 0.15.0 had left the activation of a previous license next to another key: the card read "Activated", market prices were refused because that license was revoked, and "Activate" was never offered. The activation is now removed when the price server refuses it as belonging to a revoked license (the refusal message now invites you to activate the machine again), and when the machine list, once opened, does not count this machine, which also covers a machine deactivated from another computer. The stored key and its edition are untouched, and each removal is written to the logs (Settings → System) (#367).

[0.16.0] - 2026-10-03

Added

  • Buy the Base edition from every screen that requires it: "Get Base" is now active on Budget, Adjustments, the advanced reports and extra profiles, and opens the product page in the app's language (#346).
  • In the free edition, the Updates card offers "Get Base", "I already have a key" and "Download the latest version", and the error page offers "Get Base" and "Download the latest version". The download link opens the product page, which serves the latest installers; no update check runs (#346).
  • License check at startup: when a license key is stored, the app now asks the license server whether the key was revoked, at most once every 24 hours. This is the first request the app sends on its own. It carries the product and the key, nothing else; nothing is sent without a stored key; if the server cannot be reached, nothing changes; and each check is written to the logs (Settings → System). A revoked key keeps being checked as long as it stays stored, so that a correction made on the license server is picked up at the next start (#365).

Changed

  • The Maximus Account card no longer says an account is required for Premium: the account lets you find your licenses on lacompagniemaximus.com and identifies you in your feedback, and editions come from the license key (#346).
  • The Balance locked screen (Premium) now says Premium isn't sold online yet, instead of "Online purchase coming soon" (#346).
  • In the free edition, the Updates card says automatic updates are included in the Base edition (#346).
  • A license key revoked by the license server, for instance after a refund, now puts the app back in the Free edition, automatic updates included, and the app stays there from one session to the next. The License card, the locked screens and the Updates card say that the key was revoked. Your data is kept, and another valid key gives its edition back. A revoked key used to keep its edition (#347, #365).
  • The machine list shows how many machines are activated out of the number your key allows (#347).
  • A license key valid for more than 50 years shows "No expiry date" instead of a date (#347).
  • The market price authentication error now tells you to activate this machine in Settings → Users (#347).

Fixed

  • Machine activation, deactivation and the machine list now call the license server's current /v1 endpoints and name the product explicitly, instead of going through a redirect on every call (#270).
  • A refusal from the license server is now explained in your language: revoked, expired or unknown key, machine limit reached, too many attempts (with the wait time), server unreachable. It used to show a generic "Activation failed" in English, and a revoked key showed up as "No machines activated" (#270).
  • When the machine limit is reached, the machine list opens by itself so you can deactivate one, and the message stays visible while it loads. Deactivating a machine no longer queries the server a second time, and deactivating a machine the server already considers free now counts as done (#270).
  • The "Buy" button of the License card now opens the product page directly, in the app's language, instead of an old address that only works through a redirect on the website (#270).
  • A valid license key now keeps its edition whatever the machine activation: an activation that has expired (after two years), or one carried over from another computer when you restore your data after a reinstall or a move, no longer drops the app to the Free edition. The License card shows the machine as not activated, and "Activate" brings back the market prices (#347, #360).
  • A refused license key is now explained in your language (wrong prefix, a key that cannot be read or verified, most often because it was not copied in full, an expired key, an edition this version does not know) instead of a raw English message, and a license file that cannot be read is named too (#347).
  • Dates on the License card follow the app's language instead of the system's (#347).
  • Entering a different license key now clears the previous key's machine activation, so the License card offers "Activate" for the new key. It used to show the machine as activated while market prices were refused for the previous license, for instance after a new purchase following a refund, or when moving from Base to Premium. Entering a key and activating a machine can no longer overlap, so they cannot cross either (#347).
  • Market prices refused because this machine's activation belongs to a revoked license now say so, instead of "This feature requires a premium subscription" (#365).

Security

  • Updated the Rust dependency rustls (0.23.36 → 0.23.45, which requires rustls-webpki 0.103.13 → 0.103.15 alongside it), clearing RUSTSEC-2026-0285. TLS 1.3 handshake messages were accepted at the wrong encryption level when they followed a key-changing message inside the same record — a plaintext EncryptedExtensions packed together with the ServerHello, for instance — where RFC 8446 requires closing the connection instead. Medium severity, confidentiality only: the handshake transcript stays authenticated, so this cannot be used to alter or complete a handshake from a network position; the effect is that a peer can send in the clear what should have been encrypted without rustls refusing the connection. rustls is the TLS layer of the automatic update check and download, reached through tauri-plugin-updater — the application's own licence, authentication, market-price and feedback calls use a different TLS backend and were never on this path. Nothing in the application's own code changes — only the lockfile moves, and Cargo.toml is untouched. The one behaviour that does change is the fix itself: a handshake that breaks RFC 8446 §5.1 is now closed instead of accepted (#348).
  • Activating a machine no longer sends the operating system's machine identifier to the license server. The app now sends an identifier derived from it and specific to Simpl'Résultat, from which the system identifier cannot be recovered. A machine activated with an earlier version stays activated and is still marked "This machine" in the list; that earlier activation keeps the identifier it was made with until you deactivate the machine and activate it again. This change does not erase the identifiers the server has already received (#350).

[0.15.0] - 2026-09-12

Added

  • Import: opening a source you have never configured now detects its format on its own — delimiter, encoding, date format, and which column holds the date, the description and the amount. Columns are recognised by their header label in French and in English (Montant/Amount, Débit/Withdrawal, Crédit/Deposit…), and the exported layouts of Desjardins, RBC, National Bank and Tangerine are recognised by name. Detection then replays its own decision over every row of the file and reports a confidence score ("147 of 150 rows read"): above 90 % the banner is neutral, below it the banner warns and points you at the column mapping and the preview. Nothing is ever blocked by the score — it says what could be read, not whether the meaning is right. The magic-wand button is still there to re-run detection on demand, and a source that already has a saved format is never re-detected (#327, #328).
  • Import: the data preview is now a mandatory step of every import, no longer an optional pop-up, and it shows a signed recap of what the file will actually write: how many outflows and for what total, how many inflows and for what total, and how many rows could not be read. If inflows and outflows are swapped, an "Flip the signs" button fixes them — and because it corrects the source's configuration rather than just this preview, the next file from that bank reads correctly on its own. The confirmation screen now also states the amount mode, the sign convention and the column mapping named by header, so you can check them before the import runs (#329).
  • Import: when a bank changes the layout of its export, the wizard now says so before importing. The header of every successful import is recorded on the source, and a file whose header no longer matches opens a panel listing the columns that moved, appeared or disappeared ("Amount: column 3 → 4"), with the two choices that exist: adopt the newly detected format, or keep the stored one. A purely cosmetic rename (Montant → MONTANT ($)) is recognised as the same header and says nothing. Both the drift panel and the sign-flip button now also carry a note explaining the safe way to repair an import that was already written wrong: delete it from the import history first, then replay the file — re-importing on top of it would double the rows, since duplicates are matched on date, description and amount together (#330).
  • Import: your import sources and templates are now included in the encrypted backup. Exporting and re-importing your data used to wipe every import configuration and replace it with a single synthetic "Data Import" source, so restoring a backup meant reconfiguring every bank by hand. Older backup files, which do not carry them, are still restored exactly as before (#331).
  • Category migration: custom categories that have no standard match can now be merged into a standard category instead of only being set aside. Each custom category in the "Custom categories" block gets the same target picker as the seeded rows — choose a standard leaf and its transactions, budgets, keywords and suppliers are reassigned to it, then the custom category is removed. Leaving a custom category unmapped keeps the previous behaviour (grouped under "Custom categories (migration)") and never blocks the migration (#259).

Changed

  • Import: the format of a source — including the amount mode (single amount, or separate debit and credit columns) and the sign convention (expenses written negative or positive) — is now stored in full on the source itself and restored exactly as you set it. Until now those two settings were only kept on templates, and reopening a configured source re-derived the mode and silently reset the convention to "expenses negative"; a source you had configured for positive expenses came back on the default at its second import. Reconfiguring a source is unchanged, but a source configured once is never guessed at again. The sign-convention selector is now hidden in debit/credit mode, where it never applied (your stored value is left untouched), and your configuration is written only once the import actually runs — abandoning the wizard at the duplicate-check step no longer leaves a half-saved configuration behind (#323, #324, #328).

  • The application now unlocks its modules per edition — Free, Base or Premium, resolved from your license key. Free keeps the Dashboard, CSV Import, Transactions, Categories, the Trends report (and the Reports hub), encrypted export/import and the changelog, with a single profile. Base additionally unlocks Budget, Adjustments, the four advanced reports (Highlights, Compare, Category analysis, Cards), multiple profiles and automatic updates. Premium additionally unlocks the full Balance module (net worth, per-security detail, market prices). Locked modules stay visible — a lock badge shows in the sidebar and on the report tiles — and opening one shows an unlock screen with an "I already have a key" shortcut to the license card; the online "Get Base / Premium" purchase button is shown but disabled for now ("coming soon"). Locking is never destructive: whatever the edition, your data is kept untouched and everything reappears as soon as a valid key is entered — on Free your active profile always stays accessible, only creating or switching to another profile is locked (#297, #298, #299, #300, #301).

Fixed

  • Import: a statement whose debit and credit columns are written in that order (Date;Description;Credit;Debit) used to have every single sign inverted — expenses imported as income and income as expenses. The two columns were told apart by their position, never by their label. They are now identified by their header, so the order they appear in no longer matters (#327).
  • Import: on the many banks that write 0,00 in the unused column instead of leaving it empty, every expense used to import as 0,00 and simply vanished from your ledger, with no error anywhere. Amounts are now computed as credit minus debit, which needs no special case for a zero cell. A row that cannot be read in either column is reported as an error instead of being silently written as a free 0,00 transaction, and an amount column left unmapped is now reported up front rather than falling back to reading the date column (#325).
  • Import: amounts carrying a trailing currency code or accounting marker were read a hundred times too large — 100,00 CAD imported as 10 000,00 and passed every validation, since only the leading digits were read and the rest was discarded. Such a value is now rejected as unreadable and reported on its row. Accounting forms are handled properly instead: (50,00) and 50,00- both read as −50,00. The same fix applies to the CSV import of securities into a balance snapshot, where a price written 150,25 CAD used to be stored as 15 025 and an unreadable quantity was silently saved as a zero-value position (#325).
  • Import: in a file where one column uses a comma for decimals and another a period, 1.234 could be read as 1234 in one place and 1,234 in the other. The decimal separator is now decided per column, from that column's own values (#325).
  • Import: a statement with positive amounts plus a separate "D" / "C" indicator column used to be indistinguishable from an all-positive file, so it was configured as "expenses positive" and every deposit imported as an expense. This layout is now detected and refused with an explanation, instead of being imported backwards. Re-export the statement with signed amounts, or with separate debit and credit columns (#327).
  • Import: a header row whose first cell starts with digits (a column titled 2025, for example) was mistaken for a data row, and the header was imported as a transaction. Header rows are now recognised by their labels as well as their shape (#325, #327).
  • Data export/import: restoring a backup was not wrapped in a transaction — a constraint failure partway through would abandon the restore after the existing data had already been wiped, destroying your financial history with no rollback. The wipe and the restore now succeed or fail as a whole. Restoring into a profile that already had import templates also no longer fails on a name collision (#331).

Security

  • Updated two Rust dependencies sitting on the automatic-update path, clearing six RustSec advisories: rustls-webpki 0.103.9 → 0.103.13 (certificate name-constraint handling and certificate-revocation-list parsing, including a reachable panic) and tar 0.4.44 → 0.4.46 (arbitrary directory chmod by following symlinks during extraction, and mishandled PAX size headers). Both are pulled in by tauri-plugin-updater: the TLS one runs on every update check and download, while the archive one is only reached by installer formats this app does not ship — it was updated anyway rather than reasoned around. No behaviour change. One further advisory is still reported against the lockfile and does not apply to the shipped product: rsa is never compiled at all, its only parent being a MySQL driver that a SQLite application never builds. It is recorded as accepted, with its justification and the conditions for removing it, instead of leaving the daily security audit permanently red. The two advisories against quick-xml were accepted the same way for a few hours, then resolved outright: plist 1.10.0 turned out to ship the fixed quick-xml 0.41.0 (#310, #312).
  • Updated the build-time dependency postcss (8.5.13 → 8.5.23), clearing GHSA-r28c-9q8g-f849 (path traversal while auto-loading a previous source map from a sourceMappingURL comment, leading to arbitrary .map file disclosure). Build tooling only, no runtime or behaviour change — the generated CSS is byte-for-byte identical. One advisory remains reported against react-router, and is deliberately left as is: it targets the React Server Components mode, which a local desktop app never runs, and no fix exists to move forward to since react-router-dom is frozen at 7.18.1 (leaving the affected range would mean migrating to react-router v8, not bumping a version) (#311).
  • Updated the Rust dependency h2 (0.4.13 → 0.4.19), clearing RUSTSEC-2026-0258 (empty HTTP/2 DATA frames are accepted and queued without any limit: on a stream that is not actively drained, memory grows unbounded and the queued length can overflow into a panic). Low severity, denial of service only. h2 is hyper's HTTP/2 layer, and it sits on two paths this application does take: the automatic update check and download, through tauri-plugin-updater, and the licence, authentication, market-price and feedback calls, through the app's own reqwest. Each of those is an outbound request to a server the application itself picks, so reaching the flaw takes a hostile or compromised endpoint rather than an exposed listener — it was updated anyway rather than reasoned around, on the same grounds as the rustls-webpki bump before it. No behaviour change: only the lockfile moves, Cargo.toml is untouched (#343).

[0.14.0] - 2026-07-18

Changed

  • Reports: the three hierarchical report tables (real-vs-real Compare, real-vs-budget Compare, and Trends by category) now let you collapse or expand every level of the category hierarchy, not just the top level. Every parent category — including the intermediate ones — carries its own chevron; expanding one reveals its direct children (themselves collapsed), so you drill down one level at a time. The tables open fully collapsed (only the top-level categories visible), "Expand all" opens every level at once, and your collapse choices are now saved per profile — in the profile's own database, so they are destroyed with the profile — instead of in the browser. Subtotals and result lines stay exactly the same whatever you fold away (#288).
  • Budget: the budget grid now collapses and expands at every category level too, matching the reports. Each parent category carries a chevron, the grid opens fully collapsed (only the top-level categories visible) for a compact overview, and "Expand all" opens every level in one click. Folding a category is purely visual — section totals and the result lines are unchanged, and no budget figure you have typed is ever lost. Your collapse choices are saved per profile (#289).

Fixed

  • Standard categories tree (both the standalone guide and the category-migration wizard's overview step): the "Expand all / Collapse all" button now reflects the tree's real state — it stays on "Expand all" until every group is open, instead of flipping to "Collapse all" as soon as a single category was expanded (#290).

[0.13.0] - 2026-07-12

Added

  • Reports → Compare: the two hierarchical comparable reports (real-vs-real and real-vs-budget) now let you collapse or expand each parent category's sub-categories. A chevron on every top-level category folds its breakdown away while keeping the category's subtotal row in view, and an "Expand all / Collapse all" button toggles them together. Groups start collapsed so the report opens on a compact, subtotal-level overview; whatever you expand is remembered per report (#254).
  • Reports → Trends → by category (table view): the income-statement table is now hierarchical and collapsible, matching the compare report. Parent categories appear as indented groups above their sub-categories, each with its own subtotal, and a chevron (plus an "Expand all / Collapse all" button) folds a group down to just that subtotal. Groups start collapsed, and your choices are remembered separately from the comparable tables. The Result before transfers line now sits between the expense sections and the transfers section instead of at the bottom, and folding a group never moves any subtotal or result — the figures are always computed from the full data (#265).
  • Reports: laid the groundwork for an upcoming account filter across the report pages — the shared period hook now also tracks a URL-backed, multi-account selection (bookmarkable like the date range, and validated against malformed/hand-edited URLs). Internal only for now: no filter control is visible yet, the account picker and per-report wiring land in follow-up issues (#272).
  • Reports: all seven report services (trends, by-category-over-time, real-vs-real compare, budget-vs-actual, expenses-by-category, and the Cartes dashboard) now accept an optional multi-account filter, matched against a parameterized list of import sources. Backend plumbing only — leaving every filter out still returns byte-identical results, and no report page exposes a filter control yet; that lands in follow-up issues (#273).
  • Reports: laid the groundwork for the account filter's UI — a shared filter panel renders each report page's own period control alongside a multi-select (checkbox list) of your import sources. Internal only for now: no report page renders this panel yet, that lands in follow-up issues (#274).
  • Reports → Trends: the shared filter panel now appears on this report, next to the period selector. Checking one or more import sources narrows both the global monthly view and the by-category result table to those sources; leaving none checked keeps showing every source, unchanged (#275).
  • Reports → Compare & Budget: the shared filter panel now appears on both reports too, next to their existing period control (the compare report keeps its reference-month picker, the budget grid keeps its year navigator — unchanged). Checking one or more import sources narrows the compare report — including its budget-vs-actual tab — and the budget grid's previous-year reference column to those sources; leaving none checked keeps showing every source, unchanged (#276).

Fixed

  • Reports → Trends → by category (table view): the income-statement table now covers every category instead of only the 50 largest, and its Result lines are exact. Two different categories that happen to share a name are no longer merged together, and a smaller income or transfer category is no longer miscounted as an expense — so the revenues and the net result add up correctly (#264).

Changed

  • Reports → Trends: the report now opens on the "By category" table view by default, instead of the global monthly chart. The by-category table has an income section, so a revenue category (e.g. your pay) shows up straight away — no need to switch views first. Whatever view you pick yourself is still remembered (#262).
  • Reports → Compare (actual vs budget): the budget-vs-actual table now reads the same way as the other comparable reports — an income statement. Categories are grouped Income → Expenses → Transfers (instead of Expenses → Income → Transfers), and the old flat "Total" row is replaced by a Result before transfers line (shown when transfers exist) and a Net result line, both coloured green for a surplus and red for a deficit. Leaf and section figures are unchanged (#277).
  • Budget: the budget grid now reads as an income statement — categories are grouped Income → Expenses → Transfers (previously expenses first), and the old plain "Total" row is replaced by two result lines: Result before transfers (income − expenses) and Net result (after transfers), computed across the previous-year, annual, and monthly columns alike. Every category still shows on the grid — it stays an editable surface, with no rows hidden or collapsed (#278).
  • Dashboard (/): the home page now matches the Cartes report's model. KPI cards for income, expenses, net result and savings rate show their change vs the previous month and vs last year (reference-month picker, defaulting to the last complete month); rising/falling categories and budget adherence are shown the same way as on Cartes. A new net worth tile shows the Balance sheet's latest total — a different metric from every other card here, so it stays hidden (never a misleading "$0") until at least one balance account has a recorded snapshot, and it is never affected by the account filter below. The expense-only pie chart is replaced by a ranked bar chart of top expense categories, and the account (import-source) filter — introduced on Trends/Compare/Budget — now also applies to the Dashboard's own transactional widgets (#279).

[0.12.0] - 2026-07-05

Changed

  • Reports → Compare (real vs real): the report is now an income statement. Revenue categories are shown alongside expenses, with two result lines — result before transfers (revenues − expenses) and the net result (after netting transfers) — so you can see at a glance whether the period ran a surplus or a deficit. Categories are grouped Income → Expenses → Transfers, colours follow direction (more income is green, more spending is red), and a result amount turns green for a surplus, red for a deficit (#253).
  • Reports: the comparable reports now open on the previous (last complete) month by default instead of the current civil year's December (#253).
  • Reports → Trends → By category (table view): the category-over-time table now reads as a result analysis, mirroring the compare report. Categories are grouped into Income → Expenses → Transfers sections with per-month and total subtotals, and the old mixed "Total" row — which summed income, expenses, and transfers as absolute magnitudes into a meaningless figure — is replaced by a Net result row per month (income − expenses + transfers), coloured green for a surplus and red for a deficit, plus a Result before transfers row shown when transfers exist. Category cells stay neutral levels; only the result rows carry a sign colour (#256).

[0.11.0] - 2026-07-05

Added

  • Reports → Compare (real vs real): the comparable report is now shown as a category hierarchy with subtotals, like the budget report. Parent categories roll their children up into a subtotal row — its value is the group's net, so a group of balanced transfers subtotals to ~0 — with rows grouped into Expenses / Income / Transfers sections carrying per-section and grand totals, and a subtotals-on-top/bottom toggle. Leaf-category figures are unchanged, and the transfer netting from the previous fix is preserved (#247).
  • Balance: a detailed (by-security) account can now import its positions from a CSV instead of adding each security one by one. From a snapshot, an "Import CSV" button next to "Add a title" opens a file picker; the delimiter, encoding, and columns (symbol, quantity, and — optionally — price and cost basis) are auto-detected and can be adjusted before importing. Rows that share a symbol are merged into one position, and a CSV without a price column still imports the quantities so the price can be fetched or typed afterwards (#245).

Changed

  • Balance: the balance-sheet landing page is now a tile-based hub. Having accounts but no snapshot yet no longer strands you on a "create a snapshot" prompt — the page now shows navigation tiles, and Manage accounts is reachable at all times, including from the populated dashboard. The account-management page (create/edit/archive accounts and asset types) was previously only reachable by typing its URL (#244).
  • Categories migration (Settings → Categories → migrate to the standard set): the target category on the preview step is now editable on every row, not only the "needs review" ones. Each row carries a type-ahead picker (accent-insensitive search) so you can override an auto-detected target as easily as resolving an unmatched one (#246).

Security

  • Updated dependencies to clear four advisories reported by npm audit: react-router-dom 7.13 → 7.18.1 (which pulls react-router past the turbo-stream RCE, __manifest/single-fetch DoS, and RSC-redirect XSS advisories), plus the dev toolchain vite 6.4.2 → 6.4.3 and vitest 4.0.18 → 4.1.9. No behaviour change. The app ships as a local desktop client, so the runtime advisories (which require a react-router SSR/RSC server) did not apply to the shipped product; these are hardening bumps (#235, #236, #237, #238).
  • Pinned the build-time transitive dependency @babel/core to 7.29.7 through a scoped overrides entry (under @vitejs/plugin-react), clearing the last npm audit advisory (GHSA-4x5r-pxfx-6jf8, arbitrary file read via a sourceMappingURL comment, low severity). Build tooling only — no runtime or behaviour change — and npm audit is now clean (#241).

Fixed

  • Reports → Compare (real vs real): transfer-type categories (e.g. "Paiement CC") now net to ~0 instead of showing only their outflows. A transfer is a money move, not spending — its debit and matching credit over the same period cancel out. Expense and income categories keep exactly their previous amounts (#243).

[0.10.1] - 2026-06-30

Fixed

  • Balance: fixed a "database is locked" error that could appear after starting a snapshot and leaving the page before saving, then blocked every later write until the app was restarted. All database access is now serialized, and a transaction holds a single connection for its whole duration — so a write transaction can no longer be stranded across SQLite connections (the root cause). No stored data was ever at risk; only the in-progress snapshot was lost (#230).
  • The in-app log console (Settings → Systems → Logs) now updates live as events happen, instead of only showing entries that existed before the page was opened. Snapshot saves and their failures are now logged, so database issues surface there (#230).

[0.10.0] - 2026-06-29

Added

  • Balance: per-security snapshot entry. A detailed account is now entered title by title — each holding has its own row with a security picker (autocomplete over your existing securities, with inline creation of a new ticker), quantity, price (with the optional automatic price fetch), cost basis, and a live unrealized-gain figure. The account's value is the displayed sum of its positions. Simple accounts are unchanged. The security picker accepts any normalized symbol (UPPER/TRIM) — there is no live ticker validation, since the price fetch is a separate, best-effort step; you choose the asset class (Stock / Crypto) when creating a new symbol (#214).
  • Balance: detail an aggregated account into securities (wizard). A simple account can be switched to detailed via a "Detail into securities" action in the accounts table. The switch is one-way: it sets the account to detailed and stamps today as the pivot date (detailed_since) — past aggregated history stays frozen and read-only, and the individual securities are entered at your next normal snapshot (the wizard does not capture an initial portfolio). Once any holding is entered, the account can no longer revert to simple (enforced both in the UI and in the service) (#215).
  • Balance: per-security drill-down and unrealized gain in the accounts table and overview. A detailed account can be expanded to show its securities (value + unrealized gain %), and the unrealized gain (value − cost basis) is aggregated by account, by asset class, and by fiscal envelope in the accounts table and the balance overview card. Positions without a cost basis are flagged "N/A" and excluded from the percentage (no division by zero); the per-account Modified Dietz return is unchanged (#216).
  • Balance: an existing snapshot's date can now be moved. The date field is editable in edit mode — change it and save, and the snapshot (with all its lines) is moved to the new date inside a single atomic transaction. If another snapshot already occupies the target date, the move is rejected with a clear message and nothing changes (#200).
  • Balance: fiscal envelope on accounts. An account can now carry an optional fiscal envelope (Non-registered, TFSA, RRSP, RRIF, FHSA, RESP — or none), set via a dropdown in the account form. This is a separate axis from the account's type (asset class), so an account holding stocks inside a TFSA is finally expressible — type Stocks + envelope TFSA. Migration v12 adds the nullable balance_accounts.vehicle_type column with a CHECK on the enum and backfills the former TFSA/RRSP accounts (#202, #203).
  • Balance: stacked-chart envelope axis. The stacked evolution chart gains an axis sub-toggle — By asset class (default, unchanged behaviour) or By envelope (groups by fiscal envelope, with a "None" bucket for accounts without one). Read your net worth by what you hold or by where it's sheltered (#204).
  • Balance: collapsible return columns. The accounts table's four Modified Dietz return columns (3M / 1Y / since inception / unadjusted) are now collapsed by default behind a show/hide toggle; the choice is persisted across sessions (user_preferences.balance_show_returns) to reduce visual noise for users who only track values (#204).

Changed

  • Balance: clearer terminology. Account groupings (Cash, TFSA, RRSP, etc.) are now consistently called types across the balance UI and user guide, to avoid confusion with transaction categories (a separate module). The "Cash" type is now labelled Cash (FR: Liquidités) and "Mutual fund" becomes Funds / ETF (FR: Fonds / FNB). The wording of snapshot is unchanged but glossed on first use (#198).
  • Balance: a type is now a pure asset class. The standard types are the five asset classes — Cash, Funds / ETF, Stocks, Crypto, Other. The fiscal envelopes that used to be types (TFSA, RRSP) are no longer types: they moved to the new per-account fiscal-envelope attribute. On migration, existing TFSA/RRSP accounts are reclassed to the Other asset class while carrying their envelope (tfsa/rrsp); the tfsa/rrsp seed types are deactivated and disappear from the type dropdowns (migrations v12 + v13). New profiles seed five asset classes; the TFSA/RRSP starter accounts now sit under Other with their envelope set. Your amounts and history are unchanged — only the grouping changes (#202).
  • Balance: historical reclass note. The "by asset class" chart axis is recomputed on each account's current type, so a snapshot taken before this migration for a former TFSA/RRSP account now appears under Other on that axis (it no longer shows under "TFSA"/"RRSP"). This is expected — the new by envelope axis still surfaces those CELI/REER, and no value or history is altered (#204).
  • Balance: renaming a type no longer breaks the translation (bug fix). Renaming a type used to overwrite its i18n key with the free text, clobbering the FR/EN translation. The custom name is now stored separately in balance_categories.custom_label, and the original translation key is never touched (and reappears if you clear the custom name). Migration v12 also defensively recovers any seed type whose translation key had already been clobbered by an earlier rename (#202, #203).
  • Balance: existing priced accounts auto-converted to detailed. On migration, every existing priced account (one with an asset class and a symbol) becomes a detailed account with a single position — its historical quantity/price lines are mirrored into per-security holdings with no value or history loss, and the aggregated line keeps the total. Priced accounts without an asset class or without a symbol are left untouched. The account's value and all aggregations are unchanged; you can now add further securities to the same account (migrations v14/v15/v16) (#211).

Fixed

  • Balance: the symbol (ticker) is now optional for accounts in a priced type. A priced account can be created or edited without a symbol — manual valuation (quantity × unit price) never needed it; a symbol is only required to use the automatic price-fetch button (#199).

[0.9.1] - 2026-05-10

Added

  • Bilan: 4 starter accounts (Checking account, TFSA, RRSP, Non-registered account) are seeded for new profiles, and a one-shot opt-in modal proposes them to existing profiles on their first /balance visit. Default-checked checkboxes; existing accounts with the same name + category disable the matching row with a "Already exists" tooltip. Confirming or dismissing both write user_preferences.balance_starter_proposed so the modal never re-appears. ADR 0012 (Proposed) captures the future two-level vehicle × composition model (#179).

Changed

  • Price-fetching activated — maximus-api /v1/prices is live in production since 2026-05-05. The premium price-fetching feature shipped in 0.9.0 (#160) is now functionally available end-to-end (#161).
  • Settings reorganized into 3 sub-pages — the single 12-card Settings page is split into a hub (/settings) that links to three thematic sub-pages: /settings/users (accounts, licenses, user guide), /settings/data (categories, backup, price-fetch privacy) and /settings/systems (version, update, version history, logs + feedback). The user guide and changelog, previously full-page routes, are now embedded inside their parent sub-page; the legacy /docs and /changelog URLs redirect to keep external bookmarks and release-note links working. The token-store fallback security banner is now rendered once at the top of the settings layout, visible from every main settings sub-page (#190).
  • App icon — replaced the default Tauri scaffolding icon with a custom design: a robot-faced calculator with a privacy lock on the Enter / = key. Conveys the four product values — robot (assistant), simplicity (geometric shapes), accounting (calculator), privacy (lock). Source SVG kept at src-tauri/icons/icon.svg for future iterations; all 16 platform-specific raster sizes regenerated via tauri icon. Web favicon and window <title> updated too (was "Tauri + React + Typescript" from the default scaffold).
  • Bilan: replaced empty /balance state with a 2-step onboarding card (Create an account → Enter a snapshot) so users no longer see a confusing "no snapshot" screen before any account exists. The "+ New snapshot" button is hidden until at least one account exists. The /balance/snapshot empty-state copy now clarifies what an account is vs. what a snapshot is (#178).

Fixed

  • Bilan: fix SQLite "misuse of aggregate function MIN()" error when loading /balance with existing snapshots; replaced aggregate-in-WHERE pattern with ROW_NUMBER() window function in getAccountsPeriodAnchor (#175).
  • Bilan: snapshot save now uses atomic BEGIN/COMMIT and validates all lines before any DB write, preventing orphan snapshot rows when validation fails. Migration v11 cleans existing orphans (#176).
  • Bilan: snapshot date picker on /balance/snapshot now closes after a date is selected on Linux (WebKitGTK), instead of staying open until the user pressed Esc. Workaround calls blur() on the input after each change — no-op on Windows WebView2 / macOS WKWebView, where the picker already auto-closes (#177).
  • Updated postcss dependency (8.5.6 → 8.5.13) to address moderate severity advisory GHSA-qx2v-qp2m-jg93 (XSS via unescaped </style> in CSS stringifier). Transitive via vite, build-time only — no runtime impact on the shipped Tauri binary (#180).
  • WebKitGTK date picker workaround extended to the remaining 7 native <input type="date"> fields across 4 components (Transactions filter bar, Adjustments form, Link Transfers modal, Period selector). Each onChange handler now calls e.currentTarget.blur() to dismiss the native popup on Linux Tauri WebView — no-op on Windows WebView2 / macOS WKWebView. Same approach as #177 (#188).
  • Bilan: post-merge cleanup of suggestions raised in the #182-#185 reviews. Six fixes bundled: (1) getStarterCollisions now filters archived_at IS NULL so re-creating a voluntarily archived starter is no longer blocked; (2) proposeStarterAccounts re-checks each (name, category) collision in-transaction before INSERT as defense-in-depth (skips silently on hit, no UNIQUE constraint added); (3) brand-new profiles now get balance_starter_proposed pre-seeded in consolidated_schema.sql so the StarterAccountsModal never briefly opens with all-collisions on first /balance visit; (4) /balance now hides the period selector, evolution chart and accounts table while the empty-state onboarding card is shown (avoids three stacked empty messages); (5) BalanceOnboardingCard.Step now calls useTranslation() directly instead of receiving t as a prop; (6) return_calculator.rs Modified Dietz formula doc block is wrapped in a text fence so cargo test --doc no longer fails to compile pseudo-math as Rust (#187).

[0.9.0] - 2026-04-29

Added

  • Balance sheet — asset_type column on priced categories (route /balance/accounts): priced balance categories now carry an explicit asset_type (stock or crypto) that drives PriceFetchControl provider routing without relying on symbol heuristics (e.g. ETH = Ethan Allen NYSE and Ethereum crypto are no longer ambiguous). Migration v10 adds a nullable column and backfills the two seeded priced categories (stock, crypto) with their matching values; legacy custom priced rows stay NULL until a future edit-category UI lets the user fill them in. The category creation form (Categories tab) now shows an asset-type selector when kind = priced and rejects submission until a value is picked. The snapshot editor hides the price-fetch button on priced rows whose asset_type is still NULL — manual entry remains the only path on those legacy rows. (#169)

  • Balance sheet — documentation and ADRs (docs/): closes the Bilan milestone with the documentation pass. docs/architecture.md now lists the 5 new tables (balance_categories, balance_accounts, balance_snapshots, balance_snapshot_lines, balance_account_transfers), the 7 new indexes, the SQL CHECK and FK invariants (CAD-only, kind invariants, RESTRICT on transaction_id for Modified Dietz reproducibility), the balance.service.ts 4-section layout (CRUD / snapshots+lines / returns+transfers / prices), the 3 page-scoped hooks (useBalanceAccounts, useSnapshotEditor, useBalanceOverview), the compute_account_return Tauri command (with the fetch_price future-Phase-5 mention), and the 3 new /balance* routes. Three new ADRs land alongside: 0008 — Modified Dietz (justifies the choice vs. ROI / TWR / IRR with reference to return_calculator.rs); 0009 — Proxy price-fetching via maximus-api (architecture documented now, implementation stays BLOCKED by maximus-api Phase 2 — covers privacy considerations like header stripping, no (symbol, license) log correlation and the fixed simpl-resultat UA, the Yahoo + CoinGecko provider abstraction, the Bearer auth strategy, the client + server rate limiting and the dual-side premium gating); 0010 — FK RESTRICT on balance_account_transfers.transaction_id (justifies the integrity over friction trade-off for Modified Dietz reproducibility). The user guide gains a new Balance sheet section walking through snapshot entry (simple + priced), transfer linking, multi-horizon return reading (3M / 1Y / since inception with the side-by-side unadjusted column), with the price-fetching premium flagged "coming in Phase 5". docs.balance.* i18n keys (FR + EN) ship so the in-app guide reflects the new section (#145)

  • Balance sheet — cross-cutting integration test suite (test infrastructure): closes out the Bilan feature with a layer of integration tests that exercise the whole TypeScript surface in a single happy-path flow (account → priced category → priced snapshot → linked transfer → return) plus dedicated assertions for currency lock (CAD-only at the MVP, rejected at both the service layer and SQL CHECK), priced-kind tolerance safety (a bad save must NOT clear pre-existing lines), computeAccountReturn wiring (active-profile resolution, ISO date forwarding, partial-period payload pass-through). Three new Rust integration tests apply migration v9 on top of a seeded v1 schema with pre-existing transactions to verify (1) no row loss / data mutation, (2) link / unlink transfer round-trip on real transaction ids, (3) the FK RESTRICT chain (linked transaction deletion blocked, unblocked after unlink), (4) the v1 categories.id and v9 balance_categories.id namespaces coexist independently. A non-regression source-level test on TransactionTable.tsx locks down the inlined transfer icon contract: optional prop, optional-chaining short-circuit, i18n keys, aria-label, shared description-cell layout — so the page renders identically when no transfers are linked. (#144)

  • Balance sheet — Modified Dietz returns and transfer linking (route /balance): per-account performance now ships. New Rust module commands/return_calculator.rs implements the Modified Dietz formula R = (V_end − V_start − ΣCF_i) / (V_start + ΣW_i × CF_i) with day-precision contribution weights W_i = (T − t_i) / T, plus (1 + R)^(365/T) − 1 annualization. Edge cases — missing endpoint snapshot, no flows tagged in the period, account created mid-period, depleted-then-refilled, zero-length period — are surfaced with explicit is_partial / has_no_transfers_warning flags so the UI shows a clean dash + tooltip instead of a confusing number. The new Tauri command compute_account_return(account_id, period_start, period_end) runs three short SQL reads against the active profile DB (latest snapshot ≤ period start, latest snapshot ≤ period end, transfers JOINed with transactions filtered to the period) and feeds the calculator. Seven co-located TDD tests cover every case before the implementation. The accounts table on /balance now shows four extra columns side-by-side: 3M / 1Y / Since-inception (Modified Dietz) plus an Unadjusted column showing the simple (V_end − V_start) / V_start so the user can see at a glance how much of the return came from contribution timing. Each row's actions menu gains a Link transfers item that opens a multi-select modal with date range / category / free-text filters; the modal auto-proposes the direction (in for negative bank amounts, out for positive) and the user can flip it per row before submitting. Transactions linked to one or more balance accounts now show a small Link2 icon next to the description in the Transactions page, with a tooltip listing the account name(s) and direction(s). Bulk transaction-deletion paths (per-imported-file and clear-all) now pre-check for any link in balance_account_transfers and surface a typed TransactionLinkedToBalanceError ("This transaction is linked to balance account X — unlink it before deleting") instead of leaking the raw SQLite FK error. The evolution chart on /balance now overlays vertical reference lines at every linked-transfer date (green for in, red for out). New i18n keys under balance.returns.*, balance.accountsTable.*, balance.transfers.*, balance.evolution.*, transactions.transferIcon.* (FR + EN) (#142)

  • Balance sheet — /balance overview page, evolution chart and sidebar entry (route /balance): fourth slice of the Bilan feature finally surfaces it in the navigation. The new page composes (1) an overview card with the latest aggregate net worth, the Δ% versus the previous chronological snapshot (rendered as "—" when only one snapshot exists), a 60-day staleness warning when the latest snapshot is older than that threshold, and a New snapshot CTA pointing at /balance/snapshot; (2) a period selector (3 months / 6 months / 1 year / 3 years / All) that re-fetches every series in parallel; (3) an evolution chart with two modes — Line (single series of SUM(value) GROUP BY snapshot_date) and Stacked by category (one Recharts <Area stackId> per balance_categories.key); (4) an accounts table listing every active account with its latest snapshot value, the per-account Δ% over the active period (latest value vs the value at the earliest snapshot inside the window — null when no anchor exists, rendered as "—"), and an actions menu (Details placeholder, Archive). Return-metric columns (3M / 1Y / since-creation / unadjusted) are reserved for a later release with a TODO marker. The sidebar now exposes the Balance sheet entry (Wallet icon) between Reports and Settings. The service grows three time-series helpers: getSnapshotTotalsByDate(range?), getSnapshotTotalsByCategoryAndDate(range?), getAccountsLatestSnapshot() and a per-account anchor query getAccountsPeriodAnchor(range) — all guarded by unit tests. New useBalanceOverview hook (scoped useReducer) drives the page state. New i18n keys under balance.overview.*, balance.period.*, balance.chart.* plus nav.balance (FR + EN) (#141)

  • Balance sheet — priced kind (quantity × unit price) (routes /balance/accounts and /balance/snapshot): third slice of the Bilan feature. Categories now expose a kind selector at creation: simple (direct value entry) or priced (quantity × unit_price). Accounts linked to a priced category require a symbol. The snapshot editor dispatches on the account's category kind: simple accounts keep their single value field, priced accounts get three inputs — quantity, unit_price (both required) and a read-only value field computed live from quantity × unit_price (rounded to 2 decimals). A [Manual] / [Manuel] attribution tag is shown on each priced row; the future [via Maximus on YYYY-MM-DD] tag will land with automatic price-fetching. The Prefill from previous button now copies quantities for priced accounts but leaves unit prices blank (a fresh price must be entered each time). The service validates priced lines ahead of the SQL CHECK: kind invariants (priced lines must carry both quantity and unit_price; simple lines must carry neither) and a value-match invariant |value − quantity × unit_price| ≤ 0.01 (one cent tolerance to absorb floating-point drift). Category deletion now blocks earlier and surfaces a richer error: a category linked to one or more accounts shows a dismissable banner listing the count and up to three account names so the user knows exactly which accounts to archive first; seeded categories remain protected at the service layer with their button disabled in the UI. New i18n keys balance.category.kind.*, balance.category.form.kindLabel/kindHint*, balance.category.error.has_accounts, balance.snapshot.priced.* (FR + EN) (#140)

  • Balance sheet — snapshot editor (simple kind) (route /balance/snapshot): second slice of the Bilan feature. The new page lets you create or edit a dated snapshot of your balance: pick a date (defaulting to today), enter the value of each active account grouped by category, and save. The mode is driven by the ?date= query parameter — when a snapshot already exists at that date the page automatically flips into edit mode (the underlying balance_snapshots.snapshot_date UNIQUE constraint guarantees one snapshot per day). The date of an existing snapshot is immutable: to change it, delete the snapshot and create a new one. A Prefill from previous snapshot button copies values from the most recent earlier snapshot (simple-kind accounts only — priced accounts will be handled when the priced editor lands in a later release). A Delete button surfaces a double-confirmation modal that requires retyping the snapshot date before the destructive action is enabled. Only simple-kind values are accepted at this stage (quantity and unit_price are kept NULL); the priced editor (quantity × unit price + price fetch) ships in a later release. New useSnapshotEditor hook (scoped useReducer covering the full lifecycle) and two new components SnapshotEditor + SnapshotLineRow. FR/EN i18n under balance.snapshot.* (#146)

  • Balance sheet — schema foundation and accounts page (route /balance/accounts): first slice of the upcoming Bilan feature. New SQL migration v9 introduces 5 tables (balance_categories, balance_accounts, balance_snapshots, balance_snapshot_lines, balance_account_transfers) with 7 indexes and seeds 7 standard categories — Cash, TFSA, RRSP, Mutual Fund, Other (simple kind) plus Stock and Crypto (priced kind). The currency column is hardcoded to CAD via a CHECK constraint at the MVP — multi-currency support will come in a later release. The new accounts page exposes two tabs: Accounts (full CRUD over the user's holdings, soft-archive instead of hard delete to preserve historic snapshots) and Categories (rename any category, create simple-kind ones, delete user-created ones — seeded categories are protected). The full FR/EN i18n coverage uses keys under balance.*. Snapshots, transfers, returns and the price-fetching premium remain to ship in upcoming issues; for now the route is reachable directly via URL (no sidebar entry yet) (#138)

  • Price-fetching premium for stocks (best-effort) and crypto (direct exchanges) — privacy preserved via maximus-api proxy. Privacy toggle in Settings to revoke consent (server activation pending — feature dormant until maximus-api /v1/prices ships). (#160)

Changed

  • License Ed25519 public key rotated to match the freshly deployed maximus-api license server (now live at https://api.lacompagniemaximus.com). No production licenses had been issued against the previous key, so this change is invisible to existing users — but /licenses/activate now answers, so machine activation (Issue #53) is unblocked once this release ships. The matching private key lives only on the server (#49)

Fixed

  • Category zoom report (/reports/category): the category combobox dropdown now renders the full list in proper hierarchical DFS order — each root is emitted before its descendants, with siblings sorted by sort_order then display name. Previously the list was ordered by sort_order globally (from a SQL ORDER BY sort_order, name), which interleaved parents and children from different sub-trees that shared the same sort_order, producing scrambled indentation and a mis-leading tree. Filtering (accent-insensitive search) still behaves identically (#126)

[0.8.4] - 2026-04-21

Added

  • Settings banner (90-day) and permanent Restore action to roll back a category migration from the automatic pre-migration backup (Settings → Categories): after a v2→v1 migration, a dismissable banner (ShieldCheck icon) now appears at the top of the Categories card for 90 days, pointing at the automatic SREF backup written by categoryBackupService. A dedicated Restore a backup entry stays available below the migrate link as long as a migration is recorded — even past the 90-day window — so the rollback is never lost. The confirm modal reads the last_categories_migration journal for its timestamp and backup path, enforces a two-step confirmation with a red Restore button, falls back to a file picker when the recorded path is missing on disk, prompts for the profile PIN when the SREF file is encrypted, and on success resets categories_schema_version=v2 and stamps reverted_at on the journal before reloading the app. The banner hides automatically once the migration has been reverted. New Tauri command file_exists for the pre-flight presence check, new categoryRestoreService wrapping read_import_file + importTransactionsWithCategories with stable error codes (#122)
  • 3-step category migration page (route /settings/categories/migrate, Settings → Migrate to the standard structure): legacy v2 profiles can now opt in to migrate to the new v1 IPC taxonomy through a guided flow — Discover (read-only tree reused from the guide page), Simulate (3-column dry-run table with high / medium / low / needs-review confidence badges, a clickable side panel showing the first 50 affected transactions per row, inline target picker for unresolved rows, next button blocked until every row is resolved), and Consent (checklist + optional PIN field for protected profiles + 4-step loader). On confirm, the page creates a verified SREF backup via categoryBackupService (mandatory, abort on failure with no DB write) and then runs an atomic SQL transaction via the new categoryMigrationService.applyMigration(plan, backup) — BEGIN → INSERT v1 taxonomy → UPDATE transactions / budgets / budget_templates / keywords / suppliers to the new v1 category ids → reparent custom categories under a new Custom categories (migration) parent → soft-deactivate the v2 seed categories → bump categories_schema_version='v1' and journal the run in user_preferences.last_categories_migration → COMMIT. Any thrown error triggers ROLLBACK so the profile stays in its pre-migration state. Success and error screens surface the backup path and (for success) the counts of rows inserted / transactions, keywords and budgets migrated (#121)
  • Dashboard banner inviting v2 profiles to discover the new v1 IPC category taxonomy: legacy profiles (tagged categories_schema_version='v2') now see a dismissable banner at the top of the Dashboard pointing to the new standard categories guide page. The banner is non-destructive (read-only CTA, no category changes), only shown to v2 profiles (new v1-seeded profiles never see it), and its dismissal is persisted in user_preferences under categories_v1_banner_dismissed so it never reappears once closed (#118)
  • Standard categories guide page (Settings → Standard category structure, route /settings/categories/standard): new read-only page that exposes the full v1 IPC taxonomy as a navigable tree with expand/collapse per root, a live category counter (roots · subcategories · leaves · total), accent-insensitive full-text search over translated names, hover tooltips showing the i18n_key / type / ID of each node, and a Export as PDF button that triggers the browser print dialog. A dedicated @media print rule forces every branch to render fully expanded regardless of the on-screen collapse state. All labels resolve via categoriesSeed.* with name as fallback for future custom rows. No database writes, no destructive actions (#117)
  • IPC-aligned categories seed for new profiles: brand-new profiles are now seeded with the v1 IPC (Indice des prix à la consommation) taxonomy — a structured hierarchy aligned with Statistics Canada consumer price index categories. Category labels are now translated dynamically from the categoriesSeed.* i18n namespace (FR/EN), so seed categories display in the user's current language. Existing profiles remain on the legacy v2 seed, marked via a new categories_schema_version user preference (a later migration wizard will offer the v2→v1 transition). Internally: nullable categories.i18n_key column added in migration v8 (additive only), src/data/categoryTaxonomyV1.json bundled as the TS-side source of truth, CategoryTree and CategoryCombobox renderers fall back to the raw name when no translation key is present (user-created rows) (#115)

[0.8.3] - 2026-04-19

Added

  • Cartes report — Monthly / YTD toggle (/reports/cartes): new segmented toggle next to the reference-month picker flips the four KPI cards (income, expenses, net balance, savings rate) between the reference-month value (unchanged default) and a Year-to-Date cumulative view. In YTD mode, the "current" value sums January → reference month, MoM delta compares it to the same-year Jan → (refMonth − 1) window (null for January), YoY delta compares it to Jan → refMonth of the previous year, and the savings rate uses the YTD income/expenses. The 13-month sparkline, top movers, seasonality and budget adherence cards remain monthly regardless of the toggle. The savings-rate tooltip now reflects the active mode. Choice persisted in localStorage (reports-cartes-period-mode) (#102)
  • User guide — Cartes section: new dedicated section documenting the four KPI formulas, the Monthly/YTD toggle, the sparkline, top movers, seasonality and budget adherence rules, along with the savings-rate edge case ("—" when income is zero) (#102)
  • Cartes report: help tooltip on the savings-rate KPI explaining the formula — (income − expenses) ÷ income × 100, computed on the reference month (#101)
  • Trends report — by category (/reports/trends): new segmented toggle to switch the category-evolution chart between stacked bars (default, unchanged) and a Recharts stacked-area view (<AreaChart stackId="1">) that shows total composition over time. Both modes share the same category palette and SVG grayscale patterns. The chosen type is persisted in localStorage (reports-trends-category-charttype) (#105)

Changed

  • Category zoom report (/reports/category): the category picker is now a typeable, searchable combobox with accent-insensitive matching, keyboard navigation (↑/↓/Enter/Esc) and hierarchy indentation, replacing the native <select> (#103)
  • Compare report — Actual vs. actual (/reports/compare): the table now mirrors the rich 8-column structure of the Actual vs. budget table, splitting each comparison into a Monthly block (reference month vs. comparison month) and a Cumulative YTD block (progress through the reference month vs. progress through the previous window). MoM cumulative-previous uses Jan → end-of-previous-month of the same year; YoY cumulative-previous uses Jan → same-month of the previous year. The chart remains a monthly-only view (#104)
  • Highlights report (/reports/highlights): the monthly tiles (current-month balance, top movers vs. previous month) now default to the previous calendar month instead of the current one, matching the Cartes and Compare reports. The YTD tile stays pinned to Jan 1st of the current civil year. A new reference-month picker lets you pivot both the monthly balance and the top-movers comparison to any past month; the selection is persisted in the URL via ?refY=YYYY&refM=MM so the view is bookmarkable. The hub highlights panel follows the same default (#106)

Fixed

  • Cartes report: removed the non-functional period selector — the Cartes report is a "month X vs X-1 vs X-12" snapshot, so only the reference-month picker is needed (#101)
  • Cartes report: savings-rate KPI now shows "—" instead of "0 %" when the reference month has no income (division by zero is undefined, not zero) (#101)
  • Cartes report — budget adherence: the card was always saying "no budgeted categories this month" even when budgets were defined on expense categories. Root cause: expense budgets are stored signed-negative, and the filter/comparison used raw values instead of absolutes. Categories, in-target counts, and worst-overrun amounts are now all computed on absolute values (#112)

[0.8.2] - 2026-04-17

Added

  • Feedback Hub widget (Settings → Logs): a Send feedback button in the Logs card opens a dialog to submit suggestions, comments, or bug reports to the central Feedback Hub. A one-time consent prompt explains that submission reaches feedback.lacompagniemaximus.com — an explicit exception to the app's 100% local operation. Three opt-in checkboxes (all unchecked by default): include navigation context (page, theme, viewport, app version, OS), include recent error logs, identify with your Maximus account. Routed through a Rust-side command so nothing is sent unless you press Send (#67)
  • Cartes report (/reports/cartes): new dashboard-style sub-report in the Reports hub. Combines four KPI cards (income, expenses, net balance, savings rate) showing MoM and YoY deltas simultaneously with a 13-month sparkline highlighting the reference month, a 12-month income vs. expenses overlay chart (bars + net balance line), top 5 category increases and top 5 decreases vs. the previous month, a budget-adherence card (N/M on-target plus the three worst overruns with progress bars), and a seasonality card that compares the reference month against the same calendar month from the two previous years. All data comes from a single getCartesSnapshot() service call that runs its queries concurrently (#97)

Changed

  • Compare report (/reports/compare): reduced from three tabs (MoM / YoY / Budget) to two modes (Actual vs. actual / Actual vs. budget). The actual-vs-actual view now has an explicit reference-month dropdown in the header (defaults to the previous month), a MoM ↔ YoY sub-toggle, and a grouped side-by-side bar chart (two bars per category: reference period vs. comparison period). The URL PeriodSelector stays in sync with the reference month picker (#96)

[0.8.0] - 2026-04-14

Added

  • Reports hub: /reports is now a hub surfacing a live highlights panel (current month + YTD net balance with sparklines, top movers vs. last month, top recent transactions) and four navigation cards to dedicated sub-reports (#69–#76)
  • Highlights report (/reports/highlights): balance tiles with 12-month sparklines, sortable top movers table, diverging bar chart, recent transactions list with 30/60/90 day window toggle (#71)
  • Trends report (/reports/trends): internal sub-view toggle between global flow (income vs. expenses) and by-category evolution, chart/table toggle on both (#72)
  • Compare report (/reports/compare): tab bar for Month vs. Previous Month, Year vs. Previous Year, and Actual vs. Budget; diverging bar chart centered on zero for the first two modes (#73)
  • Category zoom (/reports/category): single-category drill-down with donut chart of subcategory breakdown, monthly evolution area chart, and filterable transactions table (#74)
  • Contextual keyword editing: right-click any transaction row to add its description as a categorization keyword; a preview dialog shows every transaction that would be recategorized (capped at 50, with an opt-in checkbox for N+) before you confirm. Available on the category zoom, the highlights list, and the main transactions page (#74, #75)
  • Bookmarkable period: the reports period now lives in the URL (?from=YYYY-MM-DD&to=YYYY-MM-DD), so you can copy, paste, and share the link and keep the same state (#70)
  • View mode preference (chart vs. table) is now persisted in localStorage per report section

Changed

  • The legacy monolithic useReports hook has been split into per-domain hooks (useReportsPeriod, useHighlights, useTrends, useCompare, useCategoryZoom) so every sub-report owns only the state it needs (#70)
  • Context menu on reports (right-click) is now a generic ContextMenu shell reused by the existing chart menu and the new keyword dialog (#69)

Removed

  • The dynamic pivot table report was removed. Over 90% of its real usage was zooming into a single category, which is better served by the new Category Zoom report. Git history preserves the old implementation if it ever needs to come back (#69)

Security

  • New AddKeywordDialog enforces a 2–64 character length bound on user keywords to prevent ReDoS on large transaction sets (CWE-1333), uses parameterized LIKE queries for the preview (CWE-89), wraps its INSERT + per-transaction UPDATEs in an explicit BEGIN/COMMIT/ROLLBACK transaction (CWE-662), renders all untrusted descriptions as React children (CWE-79), and recategorizes only the rows the user explicitly checked — never retroactively. Keyword reassignment across categories requires an explicit confirmation step (#74)
  • getCategoryZoom walks the category tree through a bounded recursive CTE (WHERE depth < 5), protecting against malformed parent_id cycles (CWE-835) (#74)

[0.7.4] - 2026-04-14

Changed

  • OAuth tokens are now stored in the OS keychain (Credential Manager on Windows, Secret Service on Linux) instead of a plaintext JSON file. Existing users are migrated transparently on the next sign-in refresh; the old file is zeroed and removed. A "tokens stored in plaintext fallback" banner appears in Settings if the keychain is unavailable (#66, #78, #79, #81)
  • Cached account info is now HMAC-signed with a keychain-stored key: writing subscription_status to account.json manually can no longer bypass the Premium gate (#80)
  • PIN hashing migrated from SHA-256 to Argon2id for brute-force resistance (CWE-916). Existing SHA-256 PINs are verified transparently and rehashed on next successful unlock; new PINs use Argon2id (#54)

Security

  • Closed CWE-312 (cleartext storage of OAuth tokens), CWE-345 (missing integrity check on the subscription cache), and CWE-916 (weak PIN hashing). Legacy tokens.json and legacy unsigned account.json caches are rejected by the gating path until the next token refresh re-establishes a keychain-anchored trust (#66, #54)

[0.7.3] - 2026-04-13

Fixed

  • Maximus Account sign-in: the deep-link callback now uses the canonical Tauri v2 on_open_url API, so the auth code is properly received by the running app instead of leaving the UI stuck in "loading" (#51, #65)
  • OAuth callbacks containing an error parameter now surface the error to the UI instead of being silently ignored (#51)

[0.7.2] - 2026-04-13

Changed

  • Auto-updates are temporarily open to the Free edition until the license server (issue #49) is live. Gating will be restored once paid activation works end-to-end (#48)

[0.7.1] - 2026-04-13

Fixed

  • Maximus Account sign-in: the OAuth2 callback now correctly returns to the running app instead of launching a second instance and leaving the original one stuck in "loading" (#51, #65)

[0.7.0] - 2026-04-11

Added

  • CI: new check.yml workflow runs cargo check/cargo test and the frontend build on every branch push and PR, catching errors before merge instead of waiting for the release tag (#60)
  • License card in Settings page: shows the current edition (Free/Base/Premium), accepts a license key, and links to the purchase page (#47)
  • Maximus Account card in Settings: optional sign-in via OAuth2 PKCE for Premium features (#51)
  • Machine activation: activate/deactivate machines against the license server, view activated machines in the license card (#53)
  • Daily subscription status check: automatically refreshes account info once per day at launch (#51)

Changed

  • Automatic updates are now gated behind the Base edition entitlement; the Free edition shows an upgrade hint instead of fetching updates (#48)
  • Edition detection now considers Maximus Account subscription: Premium overrides Base when subscription is active (#51)

[0.6.7] - 2026-03-29

Changed

  • Category Over Time report: removed hard-coded expense-only filter, added type selector defaulting to expense (#41)
  • Category Over Time report: added type filter (expense/income/transfer) in the right filter panel (#41)

Fixed

  • Updated picomatch dependency (4.0.3 → 4.0.4) to fix HIGH severity vulnerabilities (#43)

[0.6.6]

Changed

  • Budget table: previous year column now shows actual transactions instead of planned budget (#34)
  • Refactored buildPrevYearTotalMap inline and simplified tests (#39)

Fixed

  • Changelog files synced automatically via Vite plugin, removed stale public/ copies (#37)

[0.6.5]

Added

  • Dashboard: month dropdown selector for the Budget vs Actual section with last completed month as default (#31)

Changed

  • Reports & Dashboard: reduced font size of month dropdown for better visual balance (#31)

[0.6.4]

Added

  • Budget table: previous year total column displayed as first data column for baseline reference (#16)

Fixed

  • Dashboard: level 4+ categories now appear under their parent instead of at the bottom of the section (#23)
  • Dashboard: category hierarchy now supports arbitrary nesting depth (#23)

Changed

  • Dashboard: pie chart takes 1/3 width instead of 1/2, giving more space to the budget table (#23)
  • Dashboard: pie chart labels now shown only on hover via tooltip instead of permanent legend (#23)
  • Budget vs Actual: category column now stays fixed when scrolling horizontally (#29)
  • Budget vs Actual: title changed to "Budget vs Réel pour le mois de [month]" with a dropdown month selector (#29)
  • Budget vs Actual: default month is now the last completed month instead of current month (#29)

[0.6.3]

Added

  • Dashboard: expenses over time stacked bar chart by category and month (#15)
  • Dashboard: budget vs actual table for current month with variance in $ and % (#15)
  • Budget table and Budget vs Actual report: section subtotal formatting with increasing visual weight (#14)

Changed

  • Dashboard: default period changed from "month" to "year to date" (#15)
  • Dashboard: removed recent transactions section (#15)
  • All report tables: grand total rows now use larger font (text-sm), bold weight, and thicker top border for better visual hierarchy (#14)

Fixed

  • Category over time report: all categories now displayed (limit increased from 8 to 50) (#13)
  • Category bar chart: Y-axis labels now use foreground color instead of muted gray (#13)
  • Category over time chart: legend text now uses foreground color instead of inheriting category color (#13)

[0.6.2]

Added

  • Budget table: section subtotals for expenses, income, and transfers displayed after each group (#11)
  • Budget vs Actual report: section subtotals with actual, planned, variation ($) and variation (%) per type (#11)

Fixed

  • Category page: detail panel now stays visible when scrolling through a long category list (#12)

[0.6.1]

Added

  • Changelog page: full version history accessible from Settings at any time
  • Bilingual changelog: release notes displayed in the user's selected language (EN/FR)

Fixed

  • Chart label visibility: amount labels on stacked bar charts now use black text with white outline for better contrast (#8)
  • Budget table: editable cells now show hover background, pointer cursor, and tooltip hint for clearer affordance (#9)

[0.6.0]

Added

  • Reports: toggle between table and chart view for Trends, By Category, and Over Time tabs
  • Reports: "Show amounts" toggle displays values directly on chart bars and area curves
  • Reports: filter panel with category checkboxes (search, select all/none) and source dropdown
  • Reports: source filter applies at SQL level for accurate filtered totals
  • Reports: sticky table headers on all report tables (Dynamic Report, Budget vs Actual)
  • Reports: interactive hover — dimmed non-hovered bars, tooltip filtered to hovered category
  • Reports: legend hover highlights category across all months (Over Time chart)

Fixed

  • Transaction table: comment icon now turns orange (like split icon) when a note is present (#7)

[0.5.0]

Added

  • Error boundary catches React crashes and displays an error page instead of a white screen
  • Startup timeout (10s) on database connection — shows error page instead of infinite spinner
  • Error page with "Refresh", "Check for updates", and contact/issue links
  • Log viewer in settings page — captures console output, filterable by level, copyable, persists across refresh
  • GPL-3.0 license — project is now open source

Changed

  • Report detail modal: sortable columns — click headers to sort by date, description, or amount (#1)
  • Report detail modal: toggle to show/hide amounts column (#3)
  • Budget table: column headers stay fixed when scrolling vertically (#2)

Fixed

  • Auto-updater on Linux: latest.json version field no longer has v prefix, package registry upload is more robust
  • Startup retry: DB connection retries up to 3 times before showing error page (fixes first-launch failure on Windows)
  • Migration checksum mismatch: automatically repairs stale migration 1 checksum on startup

[0.4.4]

Fixed

  • Linux binary now compatible with glibc 2.35+ (Ubuntu 22.04 / Pop!_OS) — CI builds in Ubuntu 22.04 container

[0.4.3]

Fixed

  • Auto-updater endpoint now uses Forgejo package registry for stable URL
  • Linux updater signatures (.AppImage.sig) now correctly collected in CI
  • All platform signatures (.deb.sig, .rpm.sig) now included in release assets

[0.4.2]

Changed

  • Auto-updater now points to self-hosted Forgejo instance
  • Windows builds now cross-compiled via cargo-xwin

[0.4.1]

Fixed

  • App stuck on infinite spinner after updating from v0.3.x (migration checksum mismatch on seed_categories.sql)
  • DB connection errors now logged to console instead of silently failing

[0.4.0]

Added

  • Categories: support for 3 levels of hierarchy (e.g., Dépenses récurrentes → Assurances → Assurance-auto)
  • Dynamic Report: new "Category (Level 3)" pivot field
  • Budget: intermediate subtotals and 3-level indentation for nested categories
  • Categories: automatic is_inputable management when creating/deleting subcategories
  • Categories: depth validation prevents creating a 4th level
  • Seed data: Assurances split into Assurance-auto, Assurance-habitation, Assurance-vie

Fixed

  • Auto-categorization: keywords starting/ending with special characters ([, ], (, ), -, etc.) now match correctly
  • Auto-categorization: pre-compile regex patterns for better batch performance

[0.3.11]

Added

  • Dynamic Report: support multiple column dimensions (composite column keys)

Fixed

  • Dynamic Report: no longer affected by global page date filters — uses only its own panel filters

[0.3.10]

Added

  • Dynamic Report: fields can now be used in multiple zones simultaneously (rows + filters, columns + filters)
  • Dynamic Report: right-click on a filter value to exclude it (shown with strikethrough in red)
  • "This year" period option in reports and dashboard (Jan 1 to today)

[0.3.9]

Added

  • Dynamic Report (pivot table): compose custom reports by assigning dimensions to rows, columns, filters and measures to values
  • Delete keywords from the "All Keywords" view

[0.3.8]

Added

  • Custom date range picker for reports and dashboard
  • Toggle to position subtotals above or below detail rows
  • Display release notes from CHANGELOG in GitHub releases and in-app updater

[0.3.7]

Fixes

  • Remove MSI bundle to prevent updater install path conflict
  • Change Windows updater installMode to basicUi
  • Improve split indicator visibility and adjustments layout

0.3.2

New Features

  • Linux support: Add Linux build (.deb, .rpm, .AppImage) to release workflow
  • Transaction splits on Adjustments page: View transaction split adjustments in a dedicated section on the Adjustments page

Fixes

  • Fix CSV auto-detect edge cases
  • Remove accent from productName for Linux .deb compatibility

0.3.1

Fixes

  • Always show profile switcher in sidebar (#2)

0.3.0

New Features

  • Multiple profiles: Create multiple profiles with separate databases, custom names, and colors
  • PIN protection: Protect profiles with an optional numeric PIN
  • Profile switcher: Quick profile switching from the sidebar
  • Drag-and-drop categories: Reorder categories or change parent via drag-and-drop in the category tree
  • Transaction splits: Split a transaction across multiple categories with adjustable amounts

0.2.10

New Features

  • Period quick-select: Add quick period filter buttons (This month, Last month, etc.) on the Transactions page
  • Budget vs Actual report: Monthly and year-to-date comparison table in Reports
  • Parent category subtotals: Budget page shows aggregated subtotals for parent categories
  • User guide: Complete documentation page accessible from Settings, printable to PDF

Improvements

  • Persist template selection and add Update template button
  • Don't pre-select already-imported files when entering source config
  • Make settings data imports visible in Import History
  • Replace per-template delete buttons with single delete on selection
  • Replace refresh icon with save icon on update template button
  • Add sign convention to budget page

0.2.9

Fixes

  • Allow duplicate-content files with different names (#1)

0.2.8

New Features

  • Data export/import: Export and import your data (transactions, categories, or both) with optional AES-256-GCM encryption (#3)

Fixes

  • Cross-file duplicate detection and per-file import tracking

0.2.5

New Features

  • Import config templates: Save and load import source configurations as reusable templates
  • 12-month budget grid: Full year budget view with monthly cells and annual totals

Fixes

  • Budget and category fixes
  • Migration checksum issue (schema.sql must not be modified after initial release)

0.2.3

New Features

  • Chart patterns: Added SVG fill patterns (diagonal lines, dots, crosshatch, etc.) to differentiate categories in bar charts, pie chart, and stacked bar charts beyond just color
  • Chart context menu: Right-click any category in a chart to hide it or view its transactions in a detail popup
  • Hidden categories: Hidden categories appear as dismissible chips above charts with a "Show all" button to restore them
  • Transaction detail modal: View all transactions composing a category's total directly from any chart
  • Import preview popup: The data preview is now a popup modal instead of a separate wizard step, allowing quick inspection without leaving the configuration page
  • Direct duplicate check: New "Check Duplicates" button on the import configuration page skips directly to duplicate validation without requiring a preview first

Improvements

  • Import wizard flow simplified: source-config → duplicate-check (preview is optional via popup)
  • Duplicate-check back button now returns to source configuration instead of the removed preview step
  • Added categoryIds map to CategoryOverTimeData for proper category resolution in the over-time chart

0.2.2

  • Bump version

0.2.1

  • Add "All Keywords" view on Categories page
  • Add dark mode with warm gray palette
  • Fix orphan categories, persist has_header for imports, add re-initialize
  • Add Budget and Adjustments pages