# Changelog ## [Unreleased] ### 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). ### 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). - 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). ### 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). ## [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 `