mirror of
https://github.com/actualbudget/actual.git
synced 2026-11-14 21:15:41 -06:00
+123








![github-actions[bot]](/avatar/af2ab225b7c0eec44a8d0eba6b5c869a?size=40)
github-actions[bot]
GitHub
youngcw
Claude Opus 5
Matiss Janis Aboltins
Michael Clark
Marcin Radoszewski
Harsh Mathur
tabedzki
Matt Fiddaman
parapsychic
coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Zannis Kalampoukis
divinevalley
Sanidhya Awasthi
Sanidhya Awasthi
Caleb Burton
github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Tim
Som Samantray
JerryNee
cakeni
cakeni
Patrick Danilevici
Anju
Shahid
Junyuan Zheng
davidlitmanowicz
DavidGrinberg
Tim Smart
Haim Gelfenbeyn
Stephen Brown II
Joel Brenstrum
Emil Tveden Bjerglund
autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Christian Jansen
Parman Mohammadalizadeh
github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
github-merge-queue
Julian Dominguez-Schatz
Henfry De Los Santos
Onika Dye-Flynn
Spaghettio73
carru
Ronan Presle
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
dreptschar
dreptschar
Sam Bai
Colin Hebert
Artem Ivanov
Alexander Lopez
Ruben van Duyneveldt
flafleur
François Lafleur
Fabian Markert
Krzysztof Wójt
Sreetam Das
Amy Hawkins
Amy Hawkins
Albert Bendicho
danielcovill
J-LCRX
Terry Lockett
John Costa
Baris Ari
Baris Arı
Baris Ari
Felipe Medici
Felipe Medici
Manaf
Copilot
Maksim Ustinov
jonesfionn101-dotcom
Fionn Jones
Mats Nilsson
Boris Rybalkin
Mohammed Yasin Zuhayr
Saurabh Mishra
Yury Semikhatsky
Soba
Daniel Nieto
Bar M
Guo-Yixin
zannis
Arbel
arbelonson-source
cakallie
Carsten
Piyush Mehta
Harshil Patel
Brad Marshall
bsmarsh333
Hrushikesh Ramilla
Emil George
Bennett Payoyo
Bennett Payoyo
Eric
Kristián Valentín
Kristián Valentín
Alexis Vielma
spatterlight
Moshe Brevda
Sebastián Maluk
Dakyne
vatsanbalaji-ossagent
vatsanbalaji-ossagent
Andrea
bubbletroubles
James N
James N
Barend
devilking7x
Dami Oyeniyi
grzdev
Jayesh Kugsiya
Emdadul Haque Apu
ANURAG VERMA
zotabee
Antoine A.
Junaid Khan
Ilia Novikov
Stephan
Matty Ice
Ryan Jinnette
spa77k
Alireza Shaeri
Koen van Staveren
Margarida Reis
Malcolm
Alok Ranjan
Alejandro de Blas
EMAD BASHARI
04e58dccd3
* [AI] Fix flickering text on the formula dashboard card (#8676) `useFormulaExecution` keys its effect on the identity of the `queries` object. `Formula.tsx` honours that by passing a stable ref plus a `queriesVersion` counter, but `FormulaCard` passed `meta?.queries || {}`, which allocates a fresh object every render. React Compiler would normally memoize that away, but it skips `FormulaCard` entirely: babel-plugin-react-compiler@1.0.0 bails out of any component using a default value in its destructured props (`meta = {}`). So the effect re-ran on every render, and since each run toggles `isLoading`, it rendered again -- an endless loop that swapped the card's value for the loading skeleton over and over. - Depend on the serialized contents of `queries`/`namedExpressions` instead of their identity, reading the live objects through refs. This fixes every caller and still re-executes on real query edits, which matters because `meta.queriesVersion` is declared on the widget type but never actually persisted. - Clear `isLoading` on the invalid-formula early return, which previously left it stuck on. - Drop the destructured default from `FormulaCard` so React Compiler compiles it again, and use a module-level constant for the empty queries object. - Only show the skeleton when there is no value yet, so a refresh leaves the previous value on screen rather than blanking the card. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * [AI] Add impeccable design skill and project design context (#8653) * [AI] Add impeccable design skill wiring and project design context Commit only the shareable parts of the impeccable frontend-design skill setup: defensive hook wiring (.codex, .cursor, .github hooks that no-op when the skill is not installed locally) and the generated design context files (PRODUCT.md, DESIGN.md, DESIGN.json) derived from the existing component-library tokens and themes. The skill payload itself is gitignored; developers install it locally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * [AI] Recommend installing the impeccable skill to agents at boot AGENTS.md documents the install step, a Claude Code SessionStart hook surfaces it when the skill is missing from the checkout, and the Codex and Copilot hook wiring echoes the same hint instead of silently no-oping. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * [AI] Mention DESIGN.json among committed design context files Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * [AI] Keep DESIGN.json out of the repo as a generated artifact It is a regenerable sidecar of DESIGN.md consumed only by the locally installed impeccable live panel, so gitignore it like the skill payload. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * [AI] Clarify that DESIGN.md hex values are the light-theme mapping Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * [AI] Add blog post announcing sidenav design competition winner (#8724) * [AI] Add blog post announcing sidenav design competition winner Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EKXivBzxyCeySkqbsqCC9Z * [AI] Remove dashes from sidenav winner blog post Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EKXivBzxyCeySkqbsqCC9Z * [AI] Use absolute docs links in blog post previews to fix paginated tag pages Relative ../../docs/ links resolve against the page URL, so when a post's truncated preview renders on a paginated listing page (e.g. /blog/tags/announcement/page/2) the link breaks and fails the Docusaurus build. Absolute /docs/ paths resolve correctly everywhere. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EKXivBzxyCeySkqbsqCC9Z --------- Co-authored-by: Claude <noreply@anthropic.com> * fix monte carlo explaination of fees and taxes (#8732) * GoCardless: derive Alior payee from the card transaction description (#8738) * GoCardless: derive Alior payee from the card transaction description Alior Bank stopped returning creditorName on outgoing transactions in early July 2026. Without it, formatPayeeName falls through to debtorName, which on an outgoing transaction is the account holder, so every card payment is booked against the user instead of the merchant. The merchant is still available behind a fixed prefix in remittanceInformationUnstructured, so take it from there when creditorName is missing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * rename release note to the PR number Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * [AI] Fix category autocomplete keyboard selection (#8570) * [AI] Fix category autocomplete selection indices * test: update category autocomplete VRT snapshot * test: update dark category autocomplete VRT snapshot * test: update midnight category autocomplete VRT snapshot --------- Co-authored-by: youngcw <calebyoung94@gmail.com> * [AI] feat(budget analysis report): allow selecting future months in date range (#8722) * [AI] feat(budget-report): allow selecting future months in date range Extend the allMonths picker list 12 months past the current month so users can select future months for long-term goal planning. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * [AI] fix(budget-report): preserve running balance for future months with no budget data Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * [AI] fix(budget-report): fix balance zeroing for future months with no budget data Use monthBalance as the next month's running balance instead of carryoverToNextMonth, which is 0 for months where no category has any budget or spending data. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * [AI] fix(budget-report): fix import path in budget-analysis-spreadsheet test * [AI] fix: added in release note Assisted-by: ClaudeCode:claude-opus-5 * [AI] fix(budget-report): address CodeRabbit review findings Addresses both review comments from CodeRabbit on PR #8722: 1. "Keep later-dated transaction months selectable" — the month-range endpoint now takes the later of the latest transaction month and December of next year, so existing future-dated transactions are no longer hidden from the picker. This PR had replaced latestMonth with a hardcoded December-of-next-year, which dropped transactions dated beyond it. 2. "Preserve the no-carryover reset when propagating balances" — running-balance propagation goes back to carrying over only what actually carries over, instead of the full month balance. Using monthBalance had re-included negative balances for categories with carryover disabled, so an overspend both dragged forward through later months and still surfaced as an overspending adjustment. A month with no budget data at all now passes the running balance through untouched, which fixes the original future-month bug without defeating the carryover reset. Extracts summarizeMonthCategories, getNextRunningBalance and getLastSelectableMonth so this logic is unit-testable, and replaces the self-referential range tests with ones that exercise the real functions. Assisted-by: ClaudeCode:claude-opus-5 * [AI] test(budget-report): cover never-budgeted months in data detection CodeRabbit's follow-up review on PR #8722 suggested setting hasBudgetData from cell presence rather than cell value. That change would be wrong here: envelope-budget-month builds its response by iterating the category list, so it emits a cell for every category in every month and normalizes missing sheet values to 0 (see loot-core/src/server/budget/app.ts). Cells are always present, so presence-based detection would report every month as populated and re-introduce the running-balance bug the previous commit fixed. The zero-value case it worries about is not reachable in a way that changes the result: leftover already folds in the previous month's balance, so a month with a real balance carrying in always has a non-zero cell and is detected correctly. When all three are zero there is nothing to carry, and both detection strategies agree. Adds a regression test pinning the never-budgeted month to hasBudgetData=false, and documents why detection is value-based so the suggestion is not re-applied later. Assisted-by: ClaudeCode:claude-opus-5 --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * add cleared to rule conditions (#8740) * add cleared to rule conditions * test * add to docs * note * Fix amount resetting to zero (#6056) (#8733) * fix amount resetting to zero (#6056) * Add release notes * Update upcoming-release-notes/fix-amount-reset-when-clicking-cleared.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Add tests suggested by CodeRabbit * Remove null assertion in tests (Code review change) --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * [AI] Make summary card formula fit narrow screens (#8583) * [AI] Make summary card formula fit narrow screens Scale the sigma, parentheses spacing, filter text and result down on narrow widths, stack the fraction above the result instead of using the equals signs, and shrink the fraction bar margins so the percentage formula no longer overflows on mobile. * [AI] Reword summary card release note * [AI] Stop summary range labels from wrapping onto the sigma The `to`/`from` labels are absolutely positioned with `right: -30` inside a wrapper whose content box collapses to 6px on narrow screens, giving them a 36px shrink-to-fit budget. `Aug 26` at 11px needs ~37px, so it wrapped onto a second line that landed on the sigma glyph. `whiteSpace: nowrap` is applied unconditionally — desktop had the same latent margin (50px budget, ~46px of text). Also pulls the repeated conditionals in the touched style objects into named values: the two fraction numbers now share one style object, and the paired margin/width ternaries and the grid track list are each stated once. * [AI] Block AI agents from creating GitHub issues (#8711) * [AI] Prohibit agents from creating GitHub issues via agent hooks * [AI] Simplify issue-creation guard after review --------- Co-authored-by: Claude <noreply@anthropic.com> * Update pikapods.md PWA intro sentence for clarity (#8690) * Update pikapods.md PWA intro sentence for clarity added a sentence to define PWA to make section easier to understand to people who are unfamiliar * Update pikapods.md * Revise PWA installation instructions for clarity Updated the description of using a PWA for clarity and conciseness. --------- Co-authored-by: Michael Clark <5285928+MikesGlitch@users.noreply.github.com> * Skip overspent sources when a named cleanup pool collects funds (#8749) The end of month cleanup did not check whether a category set to send its leftover funds to a named pool actually had a positive balance. An overspent category was still processed, so its negative balance was budgeted back onto itself and subtracted from the amount available to the pool, pushing the overspending onto the other categories in the group. Global sources already guard against this and warn instead. Apply the same check to named pool sources so an overspent category is skipped and reported. Closes #8739 Co-authored-by: Sanidhya Awasthi <sanidhya.awasthi@bfhl.in> Co-authored-by: youngcw <calebyoung94@gmail.com> * [AI] Fix phantom overspending after deleting a category (#8756) * [AI] Fix phantom overspending after deleting a category Deleting a category with a negative budgeted amount left a permanent "Overspent in [Month]" that could never be cleared, because the category is no longer visible in the UI. `handleCategoryChange` added the category's `leftover`/`carryover` cells to `last-month-overspent` when a category was created or un-deleted, but never removed them when it was tombstoned. The deleted category keeps its budgeted amount while its spend is transferred away, so its `leftover` goes negative and keeps counting as overspending. `addDeps` and `removeDeps` now both derive their work from a single `getDeps` list so they cannot drift apart again. This also covers `buffered-auto`, which had the same add-only asymmetry for income categories. Closes #8754 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * clear comment --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * Replace "convert to schedule" prompt with inline button (#8588) * feat: centralize schedule settings * feat: undo settings-based approach * feat: new approach with inline button * fix: make it work on mobile too * feat: add shift+enter as keyboard shortcut * feat: code review comments * Update VRT screenshots Auto-generated by VRT workflow PR: #8588 * feat: address all coderabbit comments --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * [AI] Pin bundle size baselines to the base commit (#8753) * Pin bundle comparisons to the base commit * Organize bundle run resolver files * Pin resolver checkout to the trusted base * Resolve bundle baselines from the merge base * Pair bundle baselines with the PR base * Strengthen bundle workflow regression coverage * Delete packages/ci-actions/src/build-runs/size-compare-workflow.test.ts * [AI] Fix duplicate payees in rules after merging payees (#6486) (#8719) * [AI] fix: deduplicate mapped rule ids (#6486) * [AI] docs: add release note for #6486 * chore: retrigger CI (flaky sharp/libvips build on setup job) --------- Co-authored-by: SomSamantray <> * [AI] Default new transaction amount filters to positive values (#8726) * [AI] Default new amount filters to positive values Initialize new transaction amount filters with a positive zero sign so values entered without toggling the sign match the displayed greater-than and less-than operators. Preserve the sign of existing filters and cover both paths with component tests. * [AI] Handle the amount filter directly instead of adding a GenericInput prop Takes @tim-smart's suggested shape. `FiltersMenu` now renders `AmountInput` itself for the amount field, and `GenericInput` is left byte-identical to master — the `zeroSign` prop it grew in the previous commit is gone. Three reasons this is the better shape, beyond it being tidier: - The prop was not actually general. `FIELD_INFO` has exactly one `type: 'number'` entry, `amount`, so `field === 'amount'` and `type === 'number'` select the same thing. A prop on a shared component serving one field and one call site is the same specificity, moved somewhere harder to find. - It type-checks. The `GenericInput` call site carries four `@ts-expect-error`s, and the previous commit's `value={type === 'number' ? 0 : ...}` sat under one of them, so nothing verified the value shape matched the `number` variant. The new branch has no suppressions. - `zeroSign` had been declared at the top level of `GenericInputProps`, outside the discriminated union, so it was accepted on `id`, `date`, `boolean` and `string` inputs where it does nothing. Dropping the `<View style={{ flex: 1, ...style }}>` wrapper costs nothing: `AmountInput`'s own container already sets `flex: 1` and spreads `...style` after its defaults, so `marginTop: 10` still lands. Using `value` rather than `formattedValue` is equivalent here too — `formattedValue` only diverges for `field === 'date' && subfield === 'month'`. `subfieldToOptions(field, subfield)` is hoisted to a `fieldOptions` const rather than called twice inside the new branch; it was already being called three times per render. The regression test moves from `GenericInput.test.tsx` up to `FiltersMenu`, which is an improvement rather than a concession: #8725 reports "pick greater than, type 150, and it filters as greater than -150". The new test drives the editor and asserts on what it submits, so it reproduces that; the old one only proved a prop was plumbed through. Verified it fails without the fix, with `value: -15000` against the expected `15000`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] Simplify amount filter test comments --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Tim Smart <hello@timsmart.co> * [AI] Fix bulk edit date picker formatting regression (#8765) * [AI] Fix bulk edit date picker formatting regression The react-aria calendar that replaced Pikaday rendered a compact grid pinned to the left of the bulk edit modal, with the header arrows spanning the full width and six-row months clipped by the fixed modal height. Stretch the grid to the container in embedded mode (same technique as DayRangeCalendar) and let the date modal size to its content, restoring the pre-v26.8.0 layout. Fixes #8654. * [AI] Give embedded date picker containers definite widths The embedded calendar's 100%-wide grid blows up shrink-to-fit containers to their max width: on wide screens the bulk edit modal stretched to 90vw. Pin the date modal to tokens.breakpoint_small and the formula timeframe picker popover to its existing 260px clamp so the stretched grid fills a bounded container, matching the v26.7.0 look at any screen size. Extend the e2e test with a wide-viewport assertion on the modal width. * [AI] fix(api): translate default rule stage at API boundary (#8689) Co-authored-by: cakeni <211545599+cakeni@users.noreply.github.com> * [AI] Use noble Playwright image for the desktop VRT job to compile against Electron 43 headers (#8770) The /update-vrt workflow always runs from the default branch, so this container fix must land there for the desktop shard to build native modules against Electron 43's V8 headers, which gcc 11/12 (jammy) cannot parse. Matches the change to the functional-desktop-app job in e2e-test.yml on the Electron 43 upgrade branch (#8769). Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * feat: add Palenight to the custom theme catalog (#8771) * feat: add Palenight to the custom theme catalog * docs: release note for the Palenight theme * Don't send a starting balance of 0 when only the starting date is set (#8750) Linking a bank account defaulted the custom starting balance to 0. Editing only the starting date kept that default and sent it to the server, which treats any defined balance as the real opening balance instead of deriving one from the bank, so the account opened out of sync. Leave the balance undefined until the user enters one. The amount input still shows 0, and a balance the user deliberately sets to 0 is still sent. Closes #8105 Co-authored-by: Sanidhya Awasthi <sanidhya.awasthi@bfhl.in> * [AI] Migrate health-check script to TypeScript (#8599) * [AI] Migrate health-check script to TypeScript * [AI] Include TypeScript scripts in knip entries * [AI] Validate health status response type * Change title util to TypeScript (#8553) * Migrate title util to TypeScript * Add release notes * Type the options parameter to preserve custom specials * Move title options type inline * [AI] Fix bank sync showing the wrong bank for a second connection (#8625) Bank records were looked up by the provider identifier alone. SimpleFIN reports the institution's domain as that identifier, so two separate logins at the same bank (e.g. two connections for different family members) collapsed into a single bank record, and accounts linked through the second connection were listed under the first connection's name on the Bank Sync page. Match on the institution name as well, so connections that report different institution names get their own bank record while relinking the same connection still reuses the existing one. Also handle institutions reported as a plain string, which previously bound `undefined` as a SQL parameter and threw. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * [AI] Add Peruvian Sol (PEN) to the currency list (#8773) Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * ⬆️ Upgrade electron to 43 (#8769) * upgrade electron to 43 * [AI] Use noble Playwright image for desktop jobs to compile against Electron 43 headers Electron 43's V8 headers expand to 'class [[deprecated(...)]] __attribute__((visibility("default")))' when node-gyp defines USING_V8_SHARED and V8_DEPRECATION_WARNINGS. gcc 11 and 12 (jammy) cannot parse a GNU attribute after a standard attribute in a class-head; gcc 13 (noble) can. Verified by compiling the headers with g++-11/12 (fail) and g++-13/14 (pass) in Ubuntu 22.04/24.04 containers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * glboal setup for playwright due to new download behaviour * Update VRT screenshots Auto-generated by VRT workflow PR: #8769 * upgrading runner * add exit codes back * build for arm64 --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * sidebar pt. 1: add account grouping models and handlers (#8764) * add account_group schema and field * add loot-core handlers * add handlers to the API * cleanup type docs * note * coderabbit feedback * add _id suffix * coderabbit: use await in batchMessages * [AI] Long note values will show a full tooltip on hover in the transactions table (#8759) * [AI] Long note values will show a full tooltip on hover in the transactions table * Fix typo in tx-notes-tooltip.md * [AI] Rename tags from the Tags page (#8752) * [AI] Rename tags from the Tags page Adds a Rename action to the tag row context menu. Renaming updates the tag record and rewrites the hashtag in every transaction note that uses it, so users no longer have to find and replace it by hand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] Address PR feedback and simplify tag rename Insert the new tag name literally in notes (replacement tokens like $& were interpreted before), drop a non-null assertion in the ManageTags test, and remove dead code from the tag InputCell. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * [AI] Close the tag rename input when Enter is pressed The 'tag' field is not registered with the table navigator, so the navigator's Enter handler fell through to flashInput, which reopened the rename input 100ms after it closed. Handle Enter on the input itself: commit the rename and exit the editing state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * [AI] Keep tag rename tests focused * [AI] Reject deleted tag rename collisions * [AI] Reference the navigator type for renderItem onEdit The renderItem prop receives navigator.onEdit verbatim, so point its type at TableNavigator['onEdit'] instead of duplicating the signature. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * [AI] Offer tag rename from the selection menu Rename appears in the selection menu when exactly one tag is selected, reusing the inline rename editor the row context menu already opens. The table navigator moves up to ManageTags so the toolbar can start the edit. Choosing Rename clears the selection first: the closing menu restores focus to its trigger, which would otherwise leave focus outside the input with the delete/hide hotkeys still live. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Apply suggestion from @tim-smart * [AI] Register the tag field with the table navigator Tab and Shift+Tab in the rename input navigated wrongly while 'tag' was absent from the navigator's field list: Tab moved backwards to 'select' and Shift+Tab jumped to the previous row's description. Registering the field makes both move within the row as expected. The local Enter handler stays. Without it the navigator carries editing on to the next row's tag field, which after the list re-sorts is an unrelated tag; the submit test now asserts no editor is left open. Also constrain the rename menu item with `satisfies MenuItemObject` in place of `as const`, which additionally validates the action name. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] Drop tag rename from the context menu for a multi-selection Delete and Hide act on every id in the selection, while Rename edits the one row it was opened on. Offer it only when those are the same tag. Right-clicking a row outside the selection still offers Rename, since the menu then acts on that row alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Tim Smart <tim.smart@effectful.co> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * [AI] Add payeeNameNormalization option to import transactions API (#8710) * [AI] Add rawPayeeName option to import transactions API Title casing lowercases the whole string before re-capitalizing each word, so acronyms in bank strings don't survive it: "New York NY" becomes "New York Ny". Custom importers that already supply payee names in final form had no way to opt out. * [AI] Rename import option to payeeNameNormalization A boolean named after the implementation detail is a poor public API. Replace `rawPayeeName?: boolean` with `payeeNameNormalization?: 'original' | 'title-case'`, defaulting to 'title-case' so existing behavior is unchanged. * [AI] Cover imported_payee in all transactions-import tests * Do not allow payeeNameNormalization to be null * [AI] Convert reconcileTransactions trailing flags to an options object `reconcileTransactions` had grown to nine positional parameters with mixed true/false defaults, so `payeeNameNormalization` would have landed as a bare string in ninth position. Replace the trailing flags on `reconcileTransactions` and `matchTransactions` with a typed options object, and rename the internal `rawPayeeName` boolean to `payeeNameNormalization` so the API option keeps its name all the way down to `normalizeTransactions`. * reduce repetition to improve maintainability --------- Co-authored-by: Tim Smart <hello@timsmart.co> * [AI] docs(migration): explain nYNAB credit card payment overbudgeting in To Budget (#8784) [autofix.ci] apply automated fixes * [AI] Fix mobile report drill-downs landing on the unfiltered accounts screen (#8774) Issue: on mobile, tapping a category in a report (or a tag on the tags screen) navigated to /accounts with filter conditions in the location state, but the mobile accounts screen never read them — the user just saw the plain account list. The same navigation worked on desktop because the wide account view applies location.state.filterConditions (resizing the browser to full width revealed the filtered list). Mobile budget drill-downs are unaffected; they use their own /categories/:id route. Fix: the mobile accounts page now redirects such navigations to /accounts/all, and the all-accounts transaction list translates the conditions via make-filters-from-conditions and applies them to its query, mirroring the desktop applyFilters flow in Account.tsx. A "Filters applied" chip next to the balance shows the list is filtered, scheduled-transaction previews are excluded while filtered, and searching within the filtered list keeps the filters. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: youngcw <calebyoung94@gmail.com> * Add option to show transfers in Sankey chart Spent view (#8780) * Add transfers as option to Spent view * Fix recursion error happening in otherGrouping * Filter out transfers to off-budget accounts * Add release note * Aggregate transfers to get net result and avoid cycles * [autofix.ci] apply automated fixes * Adjustments based on Rabbit feedback * [AI] Fix hidden node allocation to place transfer-account nodes properly * Apply coderabbit test improvement Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * [AI] Prevent infinite recursion in layer number resolution * [AI] Make Sankey cyclic layer caching deterministic across query order --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix schedules not showing as paid (#8801) * fix schedules not showing as paid * [AI] tests * note * coderabbit * [AI] test updates * [AI] Bump nanoid to 3.3.18 (#8808) * [AI] Bump nanoid to 3.3.18 * [AI] Add release note for nanoid update * add tim (#8813) * 📜 Reorganising the docs (#8796) * reorganising the docs a bit * remove border on sidebar * Envelope budgeting typo fix (#8818) * Fixed Envelope Budgeting typo Fixed a typo on the Envelope Budgeting page that said "once size fits all" instead of "one size fits all". * Add release notes * [AI] Fix category and group menus not opening after a rename (#8763) * Fix category and group menus not opening after a rename useRefEventListener resolved ref.current once on mount and only rebound when the ref object identity or the event name changed. A ref object never changes identity and mutating current does not re-run effects, so the listener stayed on whatever element existed at mount. Renaming a budget category or group exposes the InputCell's input, which unmounts the row's display node and puts a brand new one back when editing ends. The contextmenu listener was left on the detached original, so useContextMenu's synthetic event and a plain right-click both landed on an element with no listener and the menu never opened until a reload. Re-resolve the element after every render and rebind only when it has actually moved. Unmount cleanup moves to its own effect so that re-running the binding effect does not unbind and rebind on every render. Fixes #8760 * Add a SidebarGroup rename regression test SidebarGroup has its own context-menu trigger and InputCell rename path, so cover it the same way SidebarCategory is covered. * Mirror the listener target into state Replaces the manual binding bookkeeping with a state-backed target, so the binding effect gets ordinary dependencies and cleanup again. The dep-less effect that resolves the element trips exhaustive-deps because it calls setState; the bail-out when the element has not moved is what stops the update chain, so it carries a suppression with a reason. * fix flaky e2e transactions test (#8833) * fix flaky test * note * 🔖 (26.9.0) (#8789) * 🔖 (26.9.0) * Generate release notes for v26.9.0 * add release notes for 26.9.0 * tweak wording * Generate release notes for v26.9.0 * Generate release notes for v26.9.0 * wording tweaks * [AI] Add Tim Smart to blog authors * update authors * Use relative doc links in 26.9.0 release notes * Update packages/docs/blog/2026-09-01-release-26-9-0.md Co-authored-by: Julian Dominguez-Schatz <julian.dominguezschatz@gmail.com> * tweak schedule button * fix links * Apply suggestion from @jfdoming Co-authored-by: Julian Dominguez-Schatz <julian.dominguezschatz@gmail.com> --------- Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Tim Smart <hello@timsmart.co> Co-authored-by: Julian Dominguez-Schatz <julian.dominguezschatz@gmail.com> * Add actual-budget-mcp to Community Projects (#8836) * Add actual-budget-mcp to Community Projects * [autofix.ci] apply automated fixes * Keep Actual Bench with its description, and apply review wording --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * [AI] Respect the date format preference in daily and weekly report intervals (#8715) * [AI] test(reports): cover date-preference derivation for interval labels Daily and Weekly custom-report intervals render with a hardcoded 'yy-MM-dd' format and never consult Settings -> Formatting -> Dates. These tests describe the intended behaviour ahead of the fix: the two day-level intervals derive their format from the preference, Monthly and Yearly are untouched by it because they carry no day/month ambiguity, and an unset preference keeps producing today's output. Fails at this commit -- getIntervalFormat does not exist yet. Relates to #5902 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] fix(reports): derive interval label format from the date preference ReportOptions.intervalFormat maps Daily and Weekly to a literal 'yy-MM-dd', so those labels render in year-month-day order regardless of Settings -> Formatting -> Dates. Reproduced on edge.actualbudget.org with the preference set to MM/DD/YYYY: a weekly custom report's axis reads 26-04-26, and the Net Worth graph on Weekly reads 26-03-01. Add getIntervalFormat(interval, dateFormat), deriving the format from the preference for the two day-level intervals and leaving Monthly and Yearly untouched -- neither spells out a day and a month, so neither can disagree with the preference. Nothing calls it yet; the call sites follow in the next commit. Relates to #5902 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] fix(reports): use the date preference for daily and weekly labels Route the nine interval-label call sites through getIntervalFormat so Daily and Weekly labels follow Settings -> Formatting -> Dates. Custom reports and the Net Worth graph share the same map, so both were wrong in the same way and both are fixed here. The components read the preference through useDateFormat. The two spreadsheets are plain functions rather than components, so their callers pass dateFormat in, and it joins the memo dependencies -- the labels have to change when the preference does. NetWorthGraph's weekly tick filter is fixed in the same commit rather than a later one, and that is deliberate: it parses point.x back with the format that produced it, so changing the format without it would silently return Invalid Date, make getDay produce NaN, and drop the weekly gridlines with no error anywhere. Splitting them would leave a commit that renders correct labels and a broken graph. Verified: desktop-client is green (65 files, 919 tests). The 4 failures in the full run are loot-core server tests -- Windows file locking and load-dependent timeouts, present before this branch, and this change touches no loot-core file. Relates to #5902 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] docs(release-notes): add note for the interval date format fix Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] fix(reports): rebuild interval labels when the date preference changes The effect that populates the interval dropdown calls onSetAllIntervals, which formats each label with getIntervalFormat(interval, dateFormat). onSetAllIntervals is an effect event, so it reads the current preference without re-triggering -- meaning the dropdown kept stale labels until an unrelated dependency happened to change. Add dateFormat to the dependency list. The graph memo and NetWorthGraph's tick memo already had it; this was the one call site that did not. Found in review by CodeRabbit on #8715. Relates to #5902 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] test(reports): expect interval labels to keep the full year Review feedback on #8715: a date format preference containing `YYYY` was showing as `YY` in report interval labels. The earlier revision shortened it deliberately, to keep tick labels narrow, but that is not what the preference says and it has been reversed in review. These tests now assert the new rule and fail against the current helper: Daily and Weekly return the preference verbatim, the four-digit year survives, and the no-preference fallback is `yyyy-MM-dd`. The Monthly, Yearly and unknown-interval cases are untouched and still pass -- that boundary is what the change must not disturb. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] fix(reports): use the date format preference exactly as it is set Drops the `yyyy` -> `yy` substitution, so a preference of `MM/dd/yyyy` now labels daily and weekly intervals as `MM/dd/yyyy` rather than `MM/dd/yy`. The substitution existed to keep tick labels as narrow as the hardcoded format they replaced. That trade was made without asking, and the review on #8715 decided against it: the preference says what it says. Labels are wider as a result, which is the intended consequence rather than a side effect. Turns the tests added in the previous commit green. Monthly and Yearly still bypass the preference entirely. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] fix(reports): honour the date format on dashboard report cards A saved custom report shown as a dashboard card still labelled its daily and weekly intervals with the old hardcoded format. GetCardData builds that card's data through the same createCustomSpreadsheet as the full report view, but was the one call site not passing `dateFormat`. It went unnoticed because the parameter is optional, so the incomplete call type-checked and silently fell back to the default. Every call site of that spreadsheet was audited; this was the only one missing it. `dateFormat` is also added to the useMemo dependencies. Without it the card holds its old formatter after the preference changes, until something else happens to invalidate the memo -- the same defect caught in review on CustomReport.tsx earlier in this PR. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: youngcw <calebyoung94@gmail.com> * fix release automation with branch protection (#8842) * open PRs instead of pushing * use release notes on the release branch only * monte carlo linked account feedback (#8847) * add spacing tokens (#8834) * add spacing tokens * note * add more tokens * fix schedules moved before weekend occasionally becoming stuck (#8854) * centralise weekend solving logic * [AI] tests * note * coderabbit * extend test to 5 attempt case * 📈 Monte carlo - pair each year with its historical inflation on historic simulations (#8852) * pair inflation with the histroical returns * update docs images * making column a bit wider * clarifying comment on source of inflation * add the links to the sources in the comment too * docs: add bank2actual (US bank statement converter) to Community Projects (#8723) * docs: add bank2actual to community projects * docs: section intro covers file converters as well as API sync --------- Co-authored-by: youngcw <calebyoung94@gmail.com> * 📈 Monte Carlo - Fix floor and ceiling/boundaries anchors (#8861) * fix floor and ceiling and boundaries rules when retirement starts after years of no spending * updated doc * [AI] Add mergeTransactions to the API (#8877) * [AI] Add mergeTransactions to the API Wires the existing transactions-merge handler through the API layer so integrations (MCPs, etc.) can merge duplicate transactions. Merge semantics and validation are unchanged, and are shared with the UI merge action. * [AI] Reject merging a transaction with itself The guard counted ids, not distinct ids, so the same id twice passed every other check, resulting in the deleted transaction. Unreachable from the UI; reachable through the new API method. * Type mergeTransactions ids as a two-element tuple * sidebar pt. 2: UI to manage groups and assign accounts (#8832) * add experimental flag and account group plumbing * add group management modal * coderabbit * wording suggestion * move to autocomplete * update points payment docs (#8841) * For balance forecast report cards/widgets, use the granularity defined in the report (daily / monthly) instead of always monthly (#8885) * fix: use dynamic granularity in BalanceForecastCard component * add release notes * address review comments. use granularity in countForecastScheduledOccurrences * stop posting welcome checklist on maintainer PRs (#8884) * stop posting welcome checklist on maintainer PRs * note * [AI] Migrate FormulaCard to DateRangePicker #8449 (#8557) * [AI] reports: factor shared date range presets and add coverage * Add release notes * Fix unused variable * [autofix.ci] apply automated fixes * fix test after comment * [autofix.ci] apply automated fixes * Align current-quarter with DateRangePicker.maxDate * [autofix.ci] apply automated fixes * Fix comment by coderabbit * [autofix.ci] apply automated fixes * Fix nitpick * [autofix.ci] apply automated fixes * Change type from Feature to Enhancements * Add more clamping to dates * Fix race condition between loading date and opening component * Set value in case of error when reaching backend --------- Co-authored-by: Ronan Presle <ronan.presle> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * 📈 Monte carlo - Historic sim - custom allocation mix (#8881) * historical pots now show the historical return and std deviation instead of just being disabled * monte carlo custom allocations * updtae error message --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * sidebar pt. 3: redesigned shell (#8882) * add sidebar theme tokens and shared text styles * extract useSyncStatus hook * extract SidebarShell from the old sidebar * add the redesigned sidebar shell * Bump postcss-selector-parser (#8844) Bumps the npm_and_yarn group with 1 update in the / directory: [postcss-selector-parser](https://github.com/postcss/postcss-selector-parser). Updates `postcss-selector-parser` from 6.1.2 to 6.1.4 - [Release notes](https://github.com/postcss/postcss-selector-parser/releases) - [Changelog](https://github.com/postcss/postcss-selector-parser/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss-selector-parser/compare/v6.1.2...6.1.4) --- updated-dependencies: - dependency-name: postcss-selector-parser dependency-version: 6.1.4 dependency-type: indirect dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * [AI] Add Tags to bulk edit notes (#8866) * Add Tags to bulk edit notes * Add width breakpoint to keep inital width even with alot of tags * Keep width and use the suggesting behavior from editing notes in TransactionEdit.tsx * Fix Coderabbit findings * Add Tab to complete and # Button for mobile view * coderabbit finding --------- Co-authored-by: dreptschar <dreptschar@gmail.com> * 🆕 News feed feature (#8802) * whats new experimental feature * pointing preview builds at preview docs and fixing some niggles * notification bell and fixing some bits * moving the number down a bit * update release notification * optional feature flag so users can turn it off if they dont want it * cleanup * add discord link to page for more news * feedback * feedback * filter all posts by a tag to ensure only the ones we tag are announcement * feed should be served from github instead of docusaurus * remove advertising rules in discord cause they're not app related * [autofix.ci] apply automated fixes * release pipeline update so the news is updated in the release pr * fail instead of skip * fix edge case where slugs are bad or no date * remove experimental * notifications intead of whats new * adding hr styling * docs * Update VRT screenshots Auto-generated by VRT workflow PR: #8802 * Add in_app_notification flag to release blog post * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * [AI] Keep net worth stable across transfer dates (#8829) * [AI] Fix net worth across interval transfers * [AI] Cover transfers spanning report range * [AI] Stabilize transaction notes E2E input * [AI] Support reading secrets from a file with a _FILE environment variable (#8868) * [AI] Support reading any setting from a file with a _FILE environment variable Adds the `_FILE` environment-variable suffix convention used by Docker secrets and Kubernetes file-mounted secrets. Setting `<VAR>_FILE` to a path makes Actual read that setting's value from the file, so secrets can be mounted rather than passed through the environment. This covers both config systems: every env-backed setting in the sync server's convict schema, and every env-backed setting in the CLI. On the sync-server side the override is applied after `loadFile()` and before `validate()`, using `set()`. convict re-imports the environment at the end of both `load()` and `loadFile()`, so an override applied any earlier would be clobbered by the plain environment variable. A `_FILE` variable pointing at an unreadable file is a hard error rather than a silent fallback, so a secret that failed to mount stops startup instead of leaving the server running with an empty value. Refs #5233, #7686. Supersedes the approach in #5235, which applied the override before `loadFile()` and so had the precedence backwards. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] Address review feedback on _FILE support - Treat a `_FILE` variable that is set but empty as an unreadable path rather than as unset. Previously it fell back to the plain environment variable, which contradicted the documented behaviour and hid a failed mount. This applied to both the CLI and the sync server; regression tests added for both. - Read `_FILE` variables by their literal name in the CLI's resolution chain, so the `_FILE` variables are greppable in the config module and the precedence order is visible in the chain itself. This also keeps `utils.ts` free of filesystem access. - List the remaining supported variables in the CLI environment variable table. - Rename a test to describe what it asserts. - Narrow the release note, which overstated the scope: `_FILE` applies to settings that already have an environment variable binding. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] Limit _FILE support to secrets Following the convention set by Docker's official images, where only secrets get a _FILE variant, this narrows the scope to: - sync server: ACTUAL_OPENID_CLIENT_SECRET, ACTUAL_GITHUB_TOKEN - CLI: ACTUAL_PASSWORD, ACTUAL_SESSION_TOKEN, ACTUAL_ENCRYPTION_PASSWORD ACTUAL_HTTPS_KEY and ACTUAL_HTTPS_CERT already accept a path, so they get no _FILE variant. Both sides now name the `_FILE` variables literally at the point of use, so they are greppable and the precedence order is visible in the call itself. On the sync server this replaces the generic schema walk, which had to mirror convict's internal rule for telling a setting from a group; the schema no longer has to be hoisted to a named const, so load-config.js is reduced to an import and the override loop. Also documents ACTUAL_ENCRYPTION_PASSWORD, which the CLI supports but neither environment variable table listed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] Use a descriptive name for the temp directory in the config file env tests Addresses the remaining review nitpick: `dir` did not say what the directory held. Renamed in both test files for consistency. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] Trim comments and documentation per review - Shorten the readFileEnv doc comments in both packages, and drop the note in load-config.js about which variables are deliberately excluded, keeping the two lines explaining why the override runs between loadFile and validate. - Reduce the server documentation to a short note and move it to the top of the page. - Point at the runtime's own secret support (e.g. Docker secrets) in the CLI security notes rather than suggesting a hand-made file. - Drop the per-variable OpenID section, which repeated the note above it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] Simplify the wording of the _FILE precedence note Replaces the sentence in the server docs, the CLI docs and the CLI README with a shorter statement of precedence, and drops the note about stopping on an unreadable file, which can be inferred. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk> * 📈 Monte carlo - withdrawal rule breakdown (#8863) * fix floor and ceiling and boundaries rules when retirement starts after years of no spending * updated doc * added a breakdown of how the withdrwal rule was calculated * add a bit of a gap between table and explainations * unneded comment * [AI] Fix CLI failing to push changes to end-to-end encrypted budgets (#8800) * [AI] Fix CLI failing to push changes to end-to-end encrypted budgets `withConnection` opens an already-cached budget with `api.loadBudget`, which does not register the end-to-end encryption key. Any command that mutates then fails on the push with `SyncError: encrypt-failure` / `isMissingKey`, and every later command fails with `We had an unknown problem opening "<budget>"`, so the failure looks like a corrupt budget file rather than a missing key. Only the `download` branch passed the password, via `api.downloadBudget`, so the key was registered only on the first run or after the cache was invalidated. Reads kept working because they only decode, which hid the problem until a write was attempted. Route encrypted budgets through `downloadBudget` on the sync branch. With the budget already present locally, `api/download-budget` runs `key-test`, `load-budget` and `sync-budget` and returns without re-downloading the file, so this registers the key at no extra cost. `lastDownloadedAt` is deliberately left untouched, since nothing is downloaded. The `skip` branch is left alone: `decideSyncAction` always returns `sync` when `encrypted` is set, so it is unreachable for encrypted budgets. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * [AI] Add release note Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * Add api.setPreference(id, value) for setting synced preferences (#8893) * Added api.setPreference({id, value}) Added the setPreference({id, value}) function to the api methods to allow api consumers to set SyncedPerfs values. * Added return type to api.setPreference(id, value) Added return type and removed unnecessary function comment. Updated api docs with correct return type Promise<void> instead of void. * Removed useless setPreference persist test * Type setPreference value by preference key Hardened type on setPreference so that values are based on the type corresponding to the id. Also updated api doc reference.md * sidebar pt. 4: accounts list (#8892) * add account group balance bindings, collapse pref and expand * add the sidebar account and collapse hooks * add the redesigned sidebar account list components * add the account, group and section components * use the new account list * increase account row spacing * declutter pill * icon hover * icon tooltips * change case and maintain heirarchy of on/off budget headers * dynamically show/hide sync dot based on whether there are bank synced accounts * tweak expand/collapse icon * swap icon on expand/collapse * remove templates in padding * [AI] Document mobile bank sync refresh (#8908) * [AI] Merge duplicate Themes/themes directories (#8916) * [AI] Merge duplicate Themes/themes directories `packages/component-library/src/Themes` and `packages/component-library/src/themes` collide on case-insensitive filesystems (macOS), which makes checkouts of the repo hash differently there than on Linux. Move the single file in `Themes/` (`Theming.mdx`) into `themes/` and point the Storybook stories glob at the merged directory. The docs entry keeps its id and sidebar title. Fixes #8843 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] Fix broken custom themes docs link The Theming page linked to /docs/experimental/custom-themes, which 404s — the page lives at /docs/custom-themes/. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * Bump the npm_and_yarn group across 1 directory with 5 updates (#8906) Bumps the npm_and_yarn group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@humanfs/node](https://github.com/humanwhocodes/humanfs/tree/HEAD/packages/node) | `0.16.7` | `0.16.8` | | [@xmldom/xmldom](https://github.com/xmldom/xmldom) | `0.8.13` | `0.8.15` | | [browserslist](https://github.com/browserslist/browserslist) | `4.28.2` | `4.28.9` | | [fast-uri](https://github.com/fastify/fast-uri) | `3.1.5` | `3.1.7` | | [qs](https://github.com/ljharb/qs) | `6.15.2` | `6.15.3` | Updates `@humanfs/node` from 0.16.7 to 0.16.8 - [Release notes](https://github.com/humanwhocodes/humanfs/releases) - [Changelog](https://github.com/humanwhocodes/humanfs/blob/main/packages/node/CHANGELOG.md) - [Commits](https://github.com/humanwhocodes/humanfs/commits/node-v0.16.8/packages/node) Updates `@xmldom/xmldom` from 0.8.13 to 0.8.15 - [Release notes](https://github.com/xmldom/xmldom/releases) - [Changelog](https://github.com/xmldom/xmldom/blob/master/CHANGELOG.md) - [Commits](https://github.com/xmldom/xmldom/compare/0.8.13...0.8.15) Updates `browserslist` from 4.28.2 to 4.28.9 - [Release notes](https://github.com/browserslist/browserslist/releases) - [Changelog](https://github.com/browserslist/browserslist/blob/main/CHANGELOG.md) - [Commits](https://github.com/browserslist/browserslist/compare/4.28.2...4.28.9) Updates `fast-uri` from 3.1.5 to 3.1.7 - [Release notes](https://github.com/fastify/fast-uri/releases) - [Commits](https://github.com/fastify/fast-uri/compare/v3.1.5...v3.1.7) Updates `qs` from 6.15.2 to 6.15.3 - [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md) - [Commits](https://github.com/ljharb/qs/compare/v6.15.2...v6.15.3) --- updated-dependencies: - dependency-name: "@humanfs/node" dependency-version: 0.16.8 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: "@xmldom/xmldom" dependency-version: 0.8.15 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: browserslist dependency-version: 4.28.9 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: fast-uri dependency-version: 3.1.7 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: qs dependency-version: 6.15.3 dependency-type: indirect dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump the npm_and_yarn group across 1 directory with 5 updates (#8921) * Bump the npm_and_yarn group across 1 directory with 5 updates Bumps the npm_and_yarn group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [csv-parse](https://github.com/adaltas/node-csv/tree/HEAD/packages/csv-parse) | `7.0.1` | `7.0.2` | | [colord](https://github.com/omgovich/colord) | `2.9.3` | `2.10.0` | | [joi](https://github.com/hapijs/joi) | `17.13.4` | `17.13.7` | | [smol-toml](https://github.com/squirrelchat/smol-toml) | `1.6.1` | `1.8.0` | | [svgo](https://github.com/svg/svgo) | `3.3.4` | `3.3.5` | Updates `csv-parse` from 7.0.1 to 7.0.2 - [Changelog](https://github.com/adaltas/node-csv/blob/master/packages/csv-parse/CHANGELOG.md) - [Commits](https://github.com/adaltas/node-csv/commits/csv-parse@7.0.2/packages/csv-parse) Updates `colord` from 2.9.3 to 2.10.0 - [Release notes](https://github.com/omgovich/colord/releases) - [Changelog](https://github.com/omgovich/colord/blob/master/CHANGELOG.md) - [Commits](https://github.com/omgovich/colord/commits/v2.10) Updates `joi` from 17.13.4 to 17.13.7 - [Commits](https://github.com/hapijs/joi/compare/v17.13.4...v17.13.7) Updates `smol-toml` from 1.6.1 to 1.8.0 - [Release notes](https://github.com/squirrelchat/smol-toml/releases) - [Commits](https://github.com/squirrelchat/smol-toml/compare/v1.6.1...v1.8.0) Updates `svgo` from 3.3.4 to 3.3.5 - [Release notes](https://github.com/svg/svgo/releases) - [Commits](https://github.com/svg/svgo/compare/v3.3.4...v3.3.5) --- updated-dependencies: - dependency-name: csv-parse dependency-version: 7.0.2 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: colord dependency-version: 2.10.0 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: joi dependency-version: 17.13.7 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: smol-toml dependency-version: 1.8.0 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: svgo dependency-version: 3.3.5 dependency-type: indirect dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] <support@github.com> * Bump version from 3.1.1 to 3.1.2 --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv> * [AI] Respect OFX ENCODING/CHARSET headers when importing files (#8874) * [AI] Respect OFX ENCODING/CHARSET headers when importing files Read OFX/QFX files as raw bytes and decode using the encoding declared in the file header instead of always assuming UTF-8. Files declaring CHARSET:8859-1 or 1252 are decoded as windows-1252, and ENCODING:UTF-8 files as UTF-8. Also converts the 8859-1.qfx fixture to actual Latin-1 bytes (it was saved as UTF-8 despite declaring CHARSET:8859-1, so the test did not exercise the charset path) and adds fixtures and tests for windows-1252 and UTF-8 with non-ASCII content. Fixes #8872 * [AI] Add release note for OFX charset decoding fix * Apply suggestion from @coderabbitai[bot] Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: François Lafleur <francois_lafleur@yahoo.ca> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Ensure only valid months can be selected in Sankey chart datepicker (#8790) * Make sure datepicker cannot go before earliest transaction * [AI] use sendCatch for Sankey budget-month queries Avoids triggering global server-error notifications when selected months have no budget data. * Release note * Revert "[AI] use sendCatch for Sankey budget-month queries" This reverts commit89db5a9d8c. * Add function to calculate bounded month range * Ensure only valid months can be selected * Update release note * Revert "Ensure only valid months can be selected" This reverts commit12b3850ec6. * Use bounded start and end in createBaseGraphSpreadsheet * Linting fix * [AI] Adapt SankeyCard initialization per rabbit comments * Update release note * 📈 Monte carlo - Cashflow chart (#8903) * cashflow chart * showing years that are unfunded * update screenshots * refactor a bit * fix dead years withdrawal rules * [AI] migrations: pending messages DB (#8519) * [AI] Keep older app versions syncing and loading budgets touched by newer versions Cross-version compatibility for sync and budget files: - Sync messages referencing tables/columns this client doesn't have yet (sent by a newer app version) are deferred into a messages_pending table (coalesced per cell) instead of failing the sync, and replayed after migrations run on budget load. Deferral applies only to inbound messages; local writes still surface apply-failure. - Budgets already migrated by a newer app version now load, as long as every known migration is present (checkDatabaseValidity tolerance). - A CI guard (schema introspection over the real migration chain) enforces that migrations stay additive-only: no dropped/renamed tables or columns, no constraint changes on existing tables (NOT NULL, DEFAULT, UNIQUE, CHECK, primary keys), and new synced tables use a single id primary key with optional columns. - An "Update available" notification tells the user when deferred changes are waiting for an app update, re-shown on budget load while messages remain pending. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * [AI] Address review findings for cross-version sync - Clear messages_pending during sync reset so replaying deferred messages can't resurrect hard-deleted rows as empty stubs - Treat an explicit DEFAULT NULL as no usable default in the additive-migration validator - Make the CHECK-clause scanner quote/comment-aware so changes behind a quoted paren can't slip through - Flag type and DEFAULT changes on existing columns - Make the deferred-messages notification sticky so it isn't dismissed during budget load * [AI] Harden and simplify the additive-migration schema validator - Ignore CHECK( tokens inside quoted literals and comments by scanning a masked copy of the stored CREATE TABLE text - Skip schema snapshots for pre-cutoff migrations in the chain test - Collapse per-column attribute comparisons into a single changed-keys check and drop the redundant required-flip branch * [AI] Harden the cross-version sync deferral and replay flow Findings from several review rounds of the deferral feature: - Carry inbound values with unknown serialization formats opaquely (deferring them like missing columns) instead of wedging sync - Replay honors last-write-wins against the crdt log, takes a real UPDATE path so row constraints see the merged row, retries dependency chains to a fixpoint, drops only deterministic failures, and aborts safely on possibly-transient errors - Notify the user when deferred changes are dropped or discarded (new dropped-messages event), delivered reliably during budget load - Reset sync counts only real losses toward the discard warning - Guard replay behind a configured server so serverless-session edits can't be reverted; ignore legacy spreadsheet_cells messages - Restore corruption detection for empty migration directories and narrow the UI's schema-mismatch classification - Move serialization and notification helpers into their own modules * [AI] Simplify the cross-version sync branch and shrink its diff - Hoist the applyMessages callback to a named function, undoing ~280 lines of reindentation churn - Fold the sync notification helpers into sync/utils.ts - Drop an unreachable pending-message cleanup clause, a consumer-less re-export, and a committed empty-fixture directory (mkdtemp instead) - Tighten review-era comments to their load-bearing constraints - Skip pointless work: unfiltered return when nothing deferred, snapshot reuse in the additive chain test * [AI] Bump pending-messages migration timestamp past latest master migration Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BYNhGvhoHHKeAwiCJxfHsN * [AI] Make messages_pending migration idempotent Use CREATE TABLE IF NOT EXISTS so budgets that already have the table from the previous migration id still open after the timestamp bump. * [AI] Document additive-only migration rules for contributors Add a README in the migrations folder explaining cross-version sync constraints, allowed schema changes, and what the additive test misses. * [AI] Preserve CHECK literal case in migration schema diff Lowercase SQL tokens outside quoted strings when comparing CHECK constraints so 'ARCHIVED' and 'archived' are treated as different. * [AI] Create messages_pending on demand instead of relying on the migration The browser serves migration files through caches that can lag behind the app code (stale service-worker precache, unrebuilt dev environment), producing 'no such table: messages_pending' on every budget load — and a failed sync batch if a message needed deferring. The code that uses the table now creates it on first touch, sharing one copy of the DDL with the (idempotent) migration. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * [AI] Fix theme colour ordering and custom theme refresh (#8922) * [AI] Fix theme colour ordering and custom theme refresh * Apply suggestion from @tim-smart * [AI] Fix character encoding corruption when importing CAMT files (#8912) (#8913) * Bump @vitest/mocker from 4.1.10 to 5.0.0 in the npm_and_yarn group across 0 directory (#8923) * Bump @vitest/mocker in the npm_and_yarn group across 0 directory Updates `@vitest/mocker` from 4.1.10 to 5.0.0 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md) - [Commits](https://github.com/vitest-dev/vitest/commits/v5.0.0/packages/mocker) --- updated-dependencies: - dependency-name: "@vitest/mocker" dependency-version: 5.0.0 dependency-type: indirect dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] <support@github.com> * [AI] Fix typecheck failures under vitest 5 Vitest 5 changed two things that broke the typecheck/build jobs: - `vi.fn()`'s inferred type now references `Procedure` from an internal vitest chunk that cannot be named from outside, so the hoisted mocks in `cloud-storage.test.ts` tripped TS2883. Annotate them as `Mock`. - The `expect` package was inlined and the global `jest.Matchers` declaration dropped, so `@testing-library/jest-dom`'s default entry no longer augments vitest's `Assertion`. Use the `/vitest` entry point instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bvwt76cXUoLwfHiy1jhToc * [AI] Bump @actual-app/crdt to 3.1.3 The dependency bump touches packages/crdt/package.json, so the CRDT version bump check requires a new version. Matches how the previous dependabot group bump (#8921) was handled. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bvwt76cXUoLwfHiy1jhToc * [AI] Add release note for the vitest bump Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bvwt76cXUoLwfHiy1jhToc --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: MatissJanis <matiss@mja.lv> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * [AI] Expose parent transaction fields to split rule formulas (#8898) * [AI] Expose parent transaction text to split rule formulas * [AI] Categorize split parent context as an enhancement * [AI] Add Karma to the custom theme catalog (#8930) * [AI] Add Karma to the custom theme catalog Karma is a dark theme for Actual Budget. The CSS is hosted at sreetamdas/karma/actual.css and every --color-* token is mapped from the canonical Karma palette, so the theme stays in step with the Karma ports for VS Code, Zed and iTerm. Colors are the six representative hues for the catalog swatch: the near-black background, the purple primary accent, and the green/pink/orange/cyan accents. * [AI] Add release note for the Karma theme * [AI] Fix missing vite-plugin-peggy workspace in sync-server.Dockerfile (#8798) * [AI] Fix missing vite-plugin-peggy workspace in sync-server.Dockerfile The deps stage copies each workspace's package.json individually for Docker layer caching, but packages/vite-plugin-peggy/package.json was never added to that list even though loot-core and api both depend on it via workspace:*, so yarn install fails resolution with Workspace not found. Add the missing COPY line. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * release note --------- Co-authored-by: Amy Hawkins <aeh1@alum.wustl.edu> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * Update enable-banking.md Clarify needed steps (#8821) * Clarify enable-banking.md steps It seems to be a common problem people face that they skip the `Link the accounts you want in the Enable Banking interface` https://github.com/actualbudget/actual/issues/7799#issuecomment-4433943297 https://github.com/actualbudget/actual/issues/7799#issuecomment-4629194825 https://github.com/actualbudget/actual/issues/7799#issuecomment-4641704020 https://github.com/actualbudget/actual/issues/7799#issuecomment-4477748895 https://github.com/actualbudget/actual/issues/7799#issuecomment-4488926240 https://github.com/actualbudget/actual/issues/7799#issuecomment-4511171800 https://github.com/actualbudget/actual/issues/7799#issuecomment-4641704020 * [autofix.ci] apply automated fixes * coderabbitai requested improvements * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * [AI] Warn API users about deferred sync messages and fix stale docs row (#8925) Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> * Tips tricks docs fix (#8940) * Update tips-tricks.md Correct out of date documentation. * Add release notes * Clearer writing, filename fix for release notes * :electron: When data folder is unavailable show a friendly message (#8928) * when data folder is unavailable due to permissions etc, show a friendly prompt * feedback from rabbit * [AI] fix(sync): receive only the latest timestamp to stop counter overflow (#8706) * [AI] fix(sync): receive only the latest timestamp to stop counter overflow Timestamp.recv was called once per incoming message. Each call burns one tick of the 16-bit HLC counter whenever the logical clock does not advance, and the counter only resets when Date.now() moves forward. Browsers that coarsen Date.now() (LibreWolf, or Firefox with resistFingerprinting, which rounds it to 100ms) give the loop enough time to pass 65535 messages in a single tick, so a large sync throws OverflowError and the budget never opens. Receive only the highest incoming timestamp instead. The clock lands in the same place, the drift check still sees the furthest-ahead message, and the counter advances once per batch. * [AI] fix(sync): address timestamp review feedback Change-Id: oowtnqoxrmltvupuxowqqnnynnqwkxqx * [AI] Rank exact payee matches above tied substring matches (#8811) * [AI] Rank exact payee matches above tied substring matches fzf's tiebreakers default to none, so when two suggestions tie on match score (e.g. querying 'Amazon' against both 'Amazon' and 'Amazon Prime'), the ranking falls back to original array order instead of favoring the exact match. useTags.ts already fixes this for tag filtering via tiebreakers: [byLengthAsc, byStartAsc]; this applies the same fix to payee autocomplete matching. Split out from the combined payee/category fix on fix/exact-match-tiebreak-scoring — the category half is intentionally not carried over, since category suggestions are meant to stay ordered the way they appear within the budget arrangement. * [AI] Use alphabetically-ordered payee names in exact-match tiebreak test 'Amazon Prime'/'Amazon' needed to be listed out of alpha order to demonstrate the bug, which doesn't match how the payee list is naturally sorted. 'AAA Google'/'Google' demonstrates the same fix while staying in alpha order like the rest of the suite. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * 🦔 Make saving transaction edits slightly faster on big budgets (#8944) * increasing cache size, wrap updatetransactions in one transaction to reduce number * rabbit feedback * [AI] Show a progress bar while importing a YNAB budget (#8956) * [AI] Show a progress bar while importing a YNAB budget * [AI] Name the account in transaction import progress * [AI] Defer scheduled import progress until rule updates finish Change-Id: rnuttktwqnkslztsvklpqxvnwnvqvopy * [AI] Prevent dismissing an import while it is running Change-Id: tnwzlovsmtsqwtkxorqnymtxvwuyrsvk * [AI] Subscribe to import progress before importing Change-Id: kwnmmmslpoxpxrmpzltnwsrqvuryzous * 📈 Monte Carlo income streams (#8955) * monte carlo income streams * simplify release note * enhancement * fix withdrawal rule floor when income is specified * update screenshots * suplus pot and images * way better description in the run details * covering case where income is all thats setup and spending is the same as the income * feedback * screenshots and rename of minimum withdrawal to min spending * additional docs * 🦔 Speeding up large budgets by only running rules when needed (#8946) * optimise transactions edits/deletes by making the running balance query find applicable rules first, and only run if required * balance now fetched at most once and only for a rule whos conditions matched * fix ToBudget actions not showing in firefox (#8958) * 🐛 Monte carlo cashflow chart underfunding fix (#8964) * fix underfunded gap amount when using withdrawal rules and min spending amount * release notes * docs: add receipt2actual and actual2ics to Community Projects (#8945) * sidebar pt. 5: account search (#8914) * filter helper * add search * note * review improvements * add account hover card (#8969) * add account hover card * add context menus * note * generalise touch helper * coderabbit * 📈 Monte carlo -Warn when contribution is too large (#8971) * warn when monte carlo contribution is too large * feedback * ⬆️ better-sqlite3 v13 (#8736) * better-sqlite3 v13 * note * fix build * engine update * fix yarn warning and arm32 docker builds * Retry Weblate repository lock conflicts (#8979) * [AI] Retry Weblate repository lock conflicts * [AI] Add release note for translation retries * fix(import): accept "Sept" as a month abbreviation in CSV dates (#8957) The month rewrite in parseDate matched "Sep", "Sep." and "September" but not the four-letter "Sept" that some bank exports use. The word boundary after "sep" failed on the trailing "t", so the month was never converted to a number and every September row was rejected as an invalid date. Extend the regex to also accept "Sept" and "Sept." and cover the DD MMM YY, DD MMM YYYY and MMM DD, YYYY forms in the parseDate table. Closes #8888 * 📊 Monte carlo performance improvement (#8988) * improve performance of monte carlo * release notes * [AI] Fix YNAB4 import dropping the parent payee on split children (#8973) * fix YNAB4 import dropping the parent payee on split children YNAB4 stores the payee on the parent transaction only, but the importer spread a `payee: undefined` key onto every non-transfer sub transaction. `makeChild` treats a present key as explicit, so the children never fell back to the parent's payee and showed up with no payee at all. Only emit the key when a payee was actually resolved; transfer children are unchanged. Fixes #3561 * Update packages/loot-core/src/server/importers/ynab4.ts --------- Co-authored-by: Tim <hello@timsmart.co> * [AI] Detect CSV file encoding and add a per-account encoding selector (#8924) * [AI] Detect CSV file encoding and add a per-account encoding selector Read CSV/TSV files as raw bytes and decode them based on the detected encoding instead of always assuming UTF-8: the byte order mark, BOM-less UTF-16 (NUL byte analysis), strict UTF-8 validation, and a windows-1252 fallback that also decodes ISO-8859-1 content. A mostly-valid UTF-8 file with a few corrupted bytes still decodes as UTF-8 with replacement characters instead of falling back to windows-1252. Add an encoding selector to the CSV import options, persisted per account like the delimiter, for files in encodings that cannot be detected automatically (e.g. windows-1250, ISO-8859-2). Adds fixtures and tests for UTF-16 (with and without BOM), UTF-8 with BOM, corrupted UTF-8, NUL-padded UTF-8, and windows-1252 content. Fixes #6327 * Update VRT screenshots Auto-generated by VRT workflow PR: #8924 * Update VRT screenshots Auto-generated by VRT workflow PR: #8924 * [AI] Address review: require dominant NUL parity and translate encoding labels Require one parity to clearly dominate (>= 90% of NUL bytes) before detecting BOM-less UTF-16, so a UTF-8 file with dense contiguous NUL padding (even split across parities) is no longer misdetected as UTF-16. Adds a fixture with padding above the 10% density threshold. Wrap the encoding selector labels in t() per the repository's translated user-facing text requirement. * [AI] Drop iso-8859-1 from the CSV encoding selector TextDecoder resolves the iso-8859-1 label to the windows-1252 decoder per the WHATWG encoding spec, so the option was redundant and could mislead users into expecting true ISO-8859-1 decoding. windows-1252 already covers ISO-8859-1 content; keep the alias accepted in decodeCsvBytes with a clarifying comment and an alias test. * [AI] Make the iso-8859-1 alias test distinguish Windows-1252 The alias test now uses a fixture containing the 0x80 byte, which windows-1252 decodes as the euro sign while a true ISO-8859-1 decoder would produce a C1 control character, so the assertion actually verifies the windows-1252 alias semantics. * [AI] Simplify CSV encoding detection to BOM and explicit selection Follow the review suggestion: the byte order mark selects UTF-16 LE/BE and everything else decodes as UTF-8; other encodings are handled by the manual per-account selector this PR adds. Drop the speculative BOM-less UTF-16, windows-1252 fallback, and corrupted UTF-8 heuristics along with their fixtures and tests. --------- Co-authored-by: François Lafleur <francois_lafleur@yahoo.ca> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * [AI] Migrate create-folders migration to TypeScript (#8851) * [AI] Migrate create-folders migration to TypeScript * [AI] Add release note for create-folders migration * [AI] Preserve migration titles after TypeScript conversion * [AI] Reject duplicate normalized migration titles --------- Co-authored-by: Baris Arı <226740765+rapsoidmorningstar@users.noreply.github.com> Co-authored-by: Baris Ari <226740765+barislabs@users.noreply.github.com> * sidebar: trigger group balance updates on sync (#8994) * trigger group balance updates on sync * note * coderabbit * [AI] Keep custom report charts stable with long legends (#5394) (#8911) * [AI] Keep custom report charts stable with long legends (#5394) Fixes #5394 * [AI] Use forEach for parameterized report tests --------- Co-authored-by: Felipe Medici <femedicidev1@gmail.com> * sidebar pt. 7: group-aware account drag and drop (#8974) * let account-move reassign the account group * provide a react-aria router to the app * add account tree keys and pure move helpers * rebuild the sidebar account list on the react-aria tree with drag and drop * add a keyboard drag handle to sidebar tree rows * fix typecheck * coderabbit * fix chevron spacing and indentation * coderabbit * sidebar: add feedback link (#9008) * add feedback link * note * experimental sidebar: respect light theme sidebar overrides (#9003) * fix light themes * note * coderabbit * [AI] Add light-theme border to redesigned sidebar (#9013) * [AI] Add light-theme border to redesigned sidebar Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * [AI] Allow custom themes to override sidebar border Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * renamed the css variable for the sidebar right border forclarity (RightBorder) + used exisitng palette colors --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * 🐛 Fix app failing to start when opened offline (#9025) * fix app failing to start when opened offline after latest pendng migrations change * feedback * fix rare freeze when saving a change (#9027) * 📝 add experimental sidebar docs (#9022) * add images * add docs * fix spelling * [AI] Reserve children of matched bank splits (#8909) * [AI] Reserve children of matched bank splits * [AI] Add split matching release note * [AI] Avoid split-child lookup during bank matching * docs: add Rauthy to the list of tested OpenID providers (#8966) * docs: add Rauthy to the list of tested OpenID providers Closes #8838 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: note Rauthy needs RS256 for token and ID token algorithms Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Fionn Jones <264670603+jonesfionn101-dotcom@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: youngcw <calebyoung94@gmail.com> * [AI] Keep mobile payee/rule filter (#8977) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * Add Syncloud to the additional installation options (#9034) * Add Syncloud to the additional installation options Syncloud is a server operating system for a Raspberry Pi, a spare PC or a VPS, which packages actual-server as one of the apps it installs and updates. Listed under the community options alongside the other platform guides, with one link for what it is and one for setting a device up. * Drop the Syncloud description and setup link Keep only the link that is about running Actual, as requested in review. * chore: migrate sync-server app-sync validation and errors to TypeScript (#9030) * chore: migrate sync-server app-sync validation and errors to TypeScript * fix: reject an empty x-actual-group-id header on upload * [AI] Only show stacked bar tooltip truncation indicator when categories are hidden (#8917) * [AI] Only show stacked bar tooltip truncation indicator when categories are hidden * [AI] Cover the non-compact tooltip case and sharpen the release note * [AI] Prevent custom CSS indicator overlap in theme settings (#8896) (#8910) Fixes #8896 Co-authored-by: Felipe Medici <femedicidev1@gmail.com> * fix: point the tour's To Budget step at a displayed month (#9038) * 🐰 Label possible low-quality pull requests using CodeRabbit (#9044) * have coderabbit mark PRs as possible slop if it thinks they are low effort * release note * make the label friendlier * [AI] Fix timing-dependent flakiness in budget automations modal and notes tooltip test (#8993) Memoize the schedules query and the automation load callback in BudgetAutomationsModal because downstream hooks use their identities as effect dependencies. When either value changes identity on re-render, the hooks restart and the modal contents are repeatedly replaced with the loading state, which detaches controls a test is interacting with. In the transaction notes tooltip e2e test, wait for the newly inserted transaction rows to display the expected payees before hovering their notes cells. * close abandoned wip prs (#9060) * fix mobile datepicker not opening (#9071) * 🔖 (26.10.0) (#9011) * 🔖 (26.10.0) * Generate release notes for v26.10.0 * add frontmatter * [autofix.ci] apply automated fixes * Generate release notes for v26.10.0 * Generate release notes for v26.10.0 * link to experimental sidebar docs * [autofix.ci] apply automated fixes * cleanup from cherrypicks * Generate release notes for v26.10.0 * cleanup cp notes * update release date * [autofix.ci] apply automated fixes --------- Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * fix yarn publish warning (#9073) * fix yarn publish warning * note * [AI] Preserve categories during QIF transaction import (#9041) * fix: keep loading off-screen dashboard widgets after toggling edit mode (#9056) * [AI] Fix clearing the payee when editing a transaction on mobile (#8926) The mobile payee selector only allowed picking a payee, so once one was selected there was no way to leave the transaction without a payee. Add a "No payee" option to the payee autocomplete modal, mirroring the category modal's "Uncategorized" button, and show it while editing a transaction that already has a payee. * [AI] fix(schedules): allow auto-posting to run again the same day (#8951) * [AI] fix(schedules): allow auto-posting to run again the same day advanceSchedulesService is throttled to one run per calendar day through the lastScheduleRun pref, so a schedule that starts posting transactions after that run could not post until the next day. Clear the marker whenever such a schedule is created or updated. * [AI] test(schedules): cover the auto-posting marker reset * [AI] chore: add release note * [AI] fix(schedules): type the shared test date condition as const Without the assertion the shared object literal widens op/field to string, so it is not assignable to RuleConditionEntity and typecheck (and every build that runs tsc) fails. * [AI] test(schedules): cover the persisted posts_transaction fallback The existing omitted-field test creates a schedule that does not post transactions, so it only proves the no-reset branch. This one updates a schedule that already posts transactions without repeating the field, which is the case that has to fall back to the stored value before the marker is cleared. * [AI] fix(sync-server): prevent duplicate Enable Banking transactions (#9046) * [AI] Add mobile e2e coverage for the transaction calendar report (#8783) * [AI] Add mobile e2e coverage for the transaction calendar report The narrow-width branch of the calendar report renders the mobile TransactionList, whose rows call useDisplayPayee. That hook throws when it renders outside a DisplayPayeeProvider, which took the whole report down to the error boundary on mobile while desktop stayed fine — the wide-width branch renders TransactionTable, which mounts its own provider internally. The provider wrap is already in place; there was simply no e2e coverage of the calendar report on any viewport to catch it, which is why it shipped broken. Adds reports.mobile.test.ts covering the narrow-viewport calendar path, plus ReportsPage.goToCalendarPage() to navigate into the calendar widget from the dashboard. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] Add release note for #8783 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] Fix calendar card lookup scrolling the wrong container in e2e The dashboard scrolls inside Page's own overflowY: auto container, so scrolling the window never moved it. ReportCard only renders its children once the card is in the viewport, so the newly added Calendar card stayed an empty button and its heading never appeared. Walk the grid items with scrollIntoViewIfNeeded instead, matching the approach already used by goToBalanceForecastPage. --------- Co-authored-by: zannis <1011451+zannis@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * :electron: Window store app fix for temp global-store rewrites (edge case) (#8968) * fix for temp global-store rewrites when running via windows store * rabbit feedback * feedback * more feedback * cleanup * last cleanup * update test * [AI] prefer unlinked transactions when assigning fuzzy matches (#9001) * Prefer unlinked candidates when fuzzy-matching bank transactions With strictIdChecking off (every real bank-sync provider), an already-imported row was as eligible a fuzzy candidate as a fresh manually-entered one; a same-distance tie between them was resolved by SQLite's arbitrary row order. If the already-imported row won, the merge silently overwrote its imported_id/payee/notes in place instead of matching the manually-entered row that was the actual intended target -- bypassing the protected-match review flow entirely and permanently orphaning the manual entry. Also fixes the date-distance comparator, which never returned 0 and so non-deterministically reordered same-day candidates. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * use Number because it reads more clearly than ternary operator; remove claude history lesson comment * forgot to commit release notes * address review comments and update tests * extract comparator to its own method for testing * remove useless claude comment * clarify comment --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * Bump image-size from 2.0.2 to 2.0.4 in the npm_and_yarn group across 1 directory (#9009) Signed-off-by: dependabot[bot] <support@github.com> * [AI] Fix schedule template underfunding for long day/week intervals (#8824) * [AI] Fix schedule template underfunding for long day/week intervals `isSubMonthly` counted every daily or weekly schedule, however long its interval. A schedule repeating every 360 days is daily in shape but yearly in effect, so it wrongly satisfied the second branch of the "budget the full amount" check, switching the category to the steady base contribution. That contribution divides the target by the months the interval spans: subDays(2026-05-15, 360) lands on 2025-05-19, giving 12 months, so a $500 bill due in May was funded at $41.67 a month instead of the $100 needed to meet the deadline. It also ignores what is already saved, so the shortfall persists every month after the first. Apply the same interval caps `isPayMonthOf` already uses, so only schedules recurring more often than monthly count as sub-monthly. * Follow the repo's own TypeScript guidelines in the new code Two review nitpicks, both checked against AGENTS.md rather than taken on the bot's word: line 292 asks for the function keyword on pure functions and line 283 prefers satisfies over a type assertion for narrowing. isSubMonthly was an arrow function with a parameter named c, copying its immediate neighbour isPayMonthOf, which predates the guideline. It is now a function declaration taking a named ScheduleTemplateTarget. The new test fixture used as const, as all 21 other fixtures in the file do; it now uses satisfies ScheduleTemplate, which actually checks the fixture's shape instead of asserting it. Only the new code changes - the surrounding legacy is left alone rather than reformatted into an unrelated diff. 18 schedule-template tests pass, oxfmt --check and oxlint --quiet are both clean on the two files, and tsgo reports no errors in either. The 16 errors it does report are in cloud-storage.test.ts and customFunctions.ts, untouched files failing on a missing hyperformula typings path in this local install. Committed with --no-verify only because the husky hook shells out to yarn, which is not installed here; the gates it runs were run by hand instead. --------- Co-authored-by: arbelonson-source <269032023+arbelonson-source@users.noreply.github.com> * Bump the npm_and_yarn group across 2 directories with 6 updates (#9077) Bumps the npm_and_yarn group with 6 updates in the / directory: | Package | From | To | | --- | --- | --- | | [electron](https://github.com/electron/electron) | `43.4.0` | `43.5.0` | | [axios](https://github.com/axios/axios) | `1.18.1` | `1.20.0` | | [dompurify](https://github.com/cure53/DOMPurify) | `3.4.13` | `3.4.16` | | [fast-uri](https://github.com/fastify/fast-uri) | `3.1.7` | `3.1.8` | | [ip-address](https://github.com/beaugunderson/ip-address) | `10.4.0` | `10.7.2` | | [undici](https://github.com/nodejs/undici) | `7.29.0` | `7.30.0` | Bumps the npm_and_yarn group with 1 update in the /packages/desktop-electron directory: [electron](https://github.com/electron/electron). Updates `electron` from 43.4.0 to 43.5.0 - [Release notes](https://github.com/electron/electron/releases) - [Commits](https://github.com/electron/electron/compare/v43.4.0...v43.5.0) Updates `axios` from 1.18.1 to 1.20.0 - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.18.1...v1.20.0) Updates `dompurify` from 3.4.13 to 3.4.16 - [Release notes](https://github.com/cure53/DOMPurify/releases) - [Commits](https://github.com/cure53/DOMPurify/compare/3.4.13...3.4.16) Updates `fast-uri` from 3.1.7 to 3.1.8 - [Release notes](https://github.com/fastify/fast-uri/releases) - [Commits](https://github.com/fastify/fast-uri/compare/v3.1.7...v3.1.8) Updates `ip-address` from 10.4.0 to 10.7.2 - [Release notes](https://github.com/beaugunderson/ip-address/releases) - [Commits](https://github.com/beaugunderson/ip-address/compare/v10.4.0...v10.7.2) Updates `undici` from 7.29.0 to 7.30.0 - [Release notes](https://github.com/nodejs/undici/releases) - [Commits](https://github.com/nodejs/undici/compare/v7.29.0...v7.30.0) Updates `electron` from 43.4.0 to 43.5.0 - [Release notes](https://github.com/electron/electron/releases) - [Commits](https://github.com/electron/electron/compare/v43.4.0...v43.5.0) --- updated-dependencies: - dependency-name: electron dependency-version: 43.5.0 dependency-type: direct:development dependency-group: npm_and_yarn - dependency-name: axios dependency-version: 1.20.0 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: dompurify dependency-version: 3.4.16 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: fast-uri dependency-version: 3.1.8 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: ip-address dependency-version: 10.7.2 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: undici dependency-version: 7.30.0 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: electron dependency-version: 43.5.0 dependency-type: direct:development dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * [AI] Make the mobile Reconciled toggle work and match the Cleared toggle (#9064) * [AI] Make the mobile Reconciled toggle work and match the Cleared toggle The Reconciled toggle in the mobile transaction edit form was rendered as a disabled `Toggle` with no handler, so a reconciled transaction could not be unlocked from there. It now opens the existing `unlockReconciled` confirmation and clears `reconciled` on confirm, matching the desktop table and the mobile transaction list. It also used the raw `Toggle` rather than `ToggleField`, which made it visibly smaller than the Cleared toggle next to it. Fixes #8453 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] Guard the optional onCancel in the reconciled-transaction confirm modal `onCancel` is optional in the modal's options type, but the Cancel button called it unconditionally, so cancelling threw a TypeError at the 9 call sites that omit it — including the new unlock path and the pre-existing desktop one. The file is `// @ts-strict-ignore`, so the compiler did not catch it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * [AI] Implement tag-split report calculations and persistence (#8947) * [AI] fix(enablebanking): fix HypoVereinsbank auth and data access (#8355) * fix(enablebanking): pass PSU IP address header for HypoVereinsbank auth HypoVereinsbank falls back to corporate auth portal when psu-ip-address header is missing, because the first auth_method is "business". Passing the PSU headers from the incoming request selects the personal banking method instead. * fix(enablebanking): remove PSU headers from data access calls HypoVereinsbank requires Psu-Ip-Address during authorization to select the personal auth method, but rejects it on balance/transaction API calls with ASPSP_ERROR. Restrict PSU headers to startAuth only. Also add balances/transactions consent scopes to the auth request as required by the Enable Banking API. * [AI] chore: add release notes for #8355 * [autofix.ci] apply automated fixes * Scope PSU header removal to HypoVereinsbank only for data access calls Instead of stripping PSU headers globally from balance/transaction fetches, introduce `ASPSPS_WITHOUT_DATA_ACCESS_PSU_HEADERS` and `dataAccessPsuHeaders()` so only ASPSPs known to reject these headers are affected. Other ASPSPs continue to receive PSU headers on data access calls. Also restore PSU headers being passed through `buildSessionResult()` at both the auth_callback and poll-auth call sites, and propagate `aspspName` from the client via the `/transactions` endpoint so the scoping helper has the context it needs to decide. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * [autofix.ci] apply automated fixes * Fix dataAccessPsuHeaders to pass through headers when aspspName is unknown The previous condition `!aspspName || set.has(aspspName)` stripped PSU headers whenever aspspName was undefined, breaking all ASPSPs that don't send it. The correct default is to pass headers through for unknown ASPSPs and only strip them when the ASPSP is explicitly listed in the exclusion set. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Carsten <carsten@kallies-net.de> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: match "is nothing" rule conditions on transactions without a payee or category (#9017) * [AI] Use translation contexts for news Post badge and update notes link (#9080) * add retry when GoCardless access token exires (#9088) * add requisitions retry * note * 🎨 Notifications style updates (#9084) * updating styles of notifications * lint * [AI] Show clear error and configure action when GoCardless credentials are missing (#8963) * [AI] Show clear error and configure action when GoCardless credentials are missing * [AI] Restore GoCardless transaction handler after test * [AI] Persist not-configured sync status and hide configure action for non-admins * [AI] Keep explicitly cleared fields empty when entering new transactions (#8792) * [AI] Keep explicitly cleared fields empty when entering new transactions Rules re-run on every field commit while a new transaction is being entered, and they are allowed to fill any empty field. A field the user deliberately cleared (e.g. a category pre-assigned from the payee) was treated as empty too, so the rule immediately re-filled it - either on the clear itself or on a later run triggered by editing another field such as the amount. Track which fields the user explicitly emptied for the lifetime of the new-transaction row (desktop register) or edit screen (mobile), and bar rule runs from re-filling those fields until the row is added or reset. Auto-fill for untouched fields and notes append/prepend rules keep working as before. Fixes the desktop half of #7390; the mobile modal was fixed in #7521. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * [AI] Keep cleared fields blocked during mobile payee rule runs The payee exception in the mobile transaction edit bypassed shouldApplyRuleChange entirely, so selecting a payee could re-fill a field the user had explicitly cleared. Check the cleared-field set before the payee exception, matching the desktop behavior. Addresses CodeRabbit review feedback on #8792. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * [AI] Track cleared fields per transaction row Review feedback on the cleared-fields tracking: the fields were stored by name only, shared across the parent and all split rows, so clearing e.g. the category on one split row stopped rules from filling the category on its siblings too. Track cleared fields in a Map keyed by transaction id on both the desktop register and the mobile transaction edit screen. The rules merge is extracted into a pure applyRulesToTransaction() helper with unit tests covering the split scenarios, and split propagation now skips only rows whose field the user actually cleared instead of inferring a clear from the parent's (always empty) field value. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * [AI] Share split propagation between desktop and mobile rule merges The mobile transaction edit screen kept its own copy of the parent-to-subtransaction propagation step. Extract it into an exported propagateRuleChangeToSubtransactions() helper and call it from both paths so the two cannot drift apart. The mobile payee override is unchanged. Addresses CodeRabbit review feedback on #8792. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: bsmarsh333 <2703972+bsmarsh333@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * [AI] Add press-and-hold calculator backspace (#9083) * [Issue-3831] - Auto bank sync (#8744) * [AI] feat: auto bank sync * chore: add release note * [AI] fix: move auto sync settings to bank sync screen * fix: typecheck failure due to missing required prop * fix: update available intervals for auto sync * [AI] feat: add custom interval logic * [AI] fix: address review comments * fix: consider server status to run auto sync * fix: use oldest sync as reference point * fix: update description * [autofix.ci] apply automated fixes * fix: rm irrelevant comments * fix: rm cast * doc: add auto bank sync info * fix: discard empty non numeric draft val --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * [AI] Keep focus on parent payment when enabling split (#8894) When turning a new transaction into a split via keyboard, stay on the parent amount field so the user can enter the total before split lines. After split is enabled, Tab from the parent cleared field moves to the first child row instead of Cancel. Fixes actualbudget/actual#8522. Co-authored-by: Bennett Payoyo <Yahiro025@users.noreply.github.com> * [AI] Implement standard sizes on Button, Text and Input components (#9002) * [AI] Add standard size system to Button, Text and Input components * [AI] Migrate modals, settings, rules, schedules and sidebar text to standard sizes * [AI] Migrate mobile, budget, manager, banksync and transactions text to standard sizes * [AI] Migrate reports, formula and news text to standard sizes * Update VRT screenshots Auto-generated by VRT workflow PR: #9002 * Revert "Update VRT screenshots" This reverts commit35c947ef54. * [AI] Fix line-height rendering as px strings in size tokens * Update VRT screenshots Auto-generated by VRT workflow PR: #9002 * [AI] Tighten bank sync mobile row gap so larger labels fit on one line * Update VRT screenshots Auto-generated by VRT workflow PR: #9002 * [AI] Restore amount display size to preserve underline spacing * Update VRT screenshots Auto-generated by VRT workflow PR: #9002 * [AI] Pass caller-provided size through SingleAutocomplete render input * [AI] Omit forced line-height on medium-sized controls to match the default look * [AI] Document standard component sizes in DESIGN.md * [AI] Adopt sidebar spacing tokens where values match exactly * [AI] Scope the mobile touch-target rule to touch-primary control sizes --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * [DOCS] - Add Indian statement convertor to community repos (#8962) * docs: add indian statement convertor to community repo doc * fix: use trimmed version * remove repo activty from readme (#9102) * [AI] Add Tanzanian Shilling (TZS) currency (#9109) * [AI] fix: let Firefox place the caret when renaming a category (#8950) * fix: let Firefox place the caret when renaming a category or account react-dnd's HTML5 backend sets draggable="true" on the connected node and only consults canDrag on dragstart, so the attribute stays on while a row is being edited. Firefox refuses to place the caret in an input under a draggable ancestor, which made click-to-position impossible in the category and account rename fields. useDraggable now mirrors canDrag onto the attribute, which covers every caller at once. Closes #5620 * chore: name the release note after the PR * chore: scope release note to category rename * fix flaky e2e test (#9114) * fix race * note * protect prefs from loading during a save * sidebar: remove budget header dropdown (#9115) * redesign budget header/dropdown * note * youngcw feedback * coderabbit: bring back backup button for electron * [AI] Give the budget name edit box a full-size input and save on blur The rename input now covers the name and sync status lines at the standard medium size, and the collapse arrows are not rendered while editing, so the input no longer changes width when the mouse leaves the sidebar. Clicking out saves the name like Enter does, and Escape still discards. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> * [AI] Use amounts instead of cents in rule formulas (#8717) * [AI] Use amounts instead of cents in rule formulas Rule formulas handed `amount`, `balance`, `parent_amount` and BALANCE_OF() to the formula as integer cents, but read a numeric result back as an amount, so `=amount * 1.05` came out 100x too large and users had to write `=amount / 100 * 1.05`. Convert the monetary variables with `integerToAmount` when building the named expressions, so a formula reads and returns the same units either way. The `amountToInteger` on the result moves out of executeFormulaSync into the two branches that actually write an amount column, so a numeric result stringified into a text field is no longer scaled up. The transaction object itself stays in integers, keeping rule conditions, Handlebars templates and the split percent/remainder math unchanged. A migration rewrites stored formulas (`=amount / 100` becomes `=(amount * 100) / 100`) so they keep producing the same result. It keeps its own frozen copy of the variable names and the factor of 100, since those describe how formulas worked when it was written. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] Stop spelling out the units in rule formula docs With formulas working in the amounts shown in the app, saying so on every variable is noise — the values now read the way you would expect. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] Move the rule formula migration after the account groups one The migration id has to sort last, since checkDatabaseValidity compares applied ids to available ones by position — a nightly user who already ran 1787013118115_add_account_groups would otherwise fail to open their budget with out-of-sync-migrations. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] Drop the rule formula migration in favour of a documented break Per review: the migration cannot hold up across devices. A new device migrates and then replays newer messages, so a formula saved after the migration ran is never converted; and once one device upgrades, the converted formula it syncs reads 100x off on a device still running the old build. Rule formulas are experimental, so document the break instead and let people drop the `/ 100` themselves. Also clears the leftover `/ 100` from the ROUND, ROUNDDOWN, ROUNDUP and TRUNC examples. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * note update * note update2 --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * feat: add BDT currency (#9119) * [AI] Fix live spending reports not updating on month rollover (#9098) * [AI] Fix live spending reports not following the current month Spending reports marked Live stayed pinned to the month they were saved in. Every other report stores liveness inside its range object (TimeFrame's `mode`), which makes "live but pinned to a month" unrepresentable and lets a live range be read for its width rather than its position. Spending instead stores `isLive` as a sibling of `compare`/`compareTo`, so it can persist both at once, and #7672 made `calculateSpendingReportTimeRange` resolve that conflict in favour of the saved month. Restore the two invariants the other reports rely on: - Budget and average live reports resolve to the current month instead of the saved one, and live single-month reports fall through to the sliding-window path, which re-anchors to now while preserving the gap between the two series. - Picking a compare month now makes the report static, the same way the date picker does elsewhere. This keeps #7648 fixed - the chosen month is still preserved - without making live reports behave statically. The "to" select deliberately stays live, since its offset is what slides. - Toggling Static back to Live re-anchors the shown months immediately, matching the Live button in the shared reports header. Already-saved cards self-heal on load; no migration is needed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] Default a missing compareTo to the month before compare A spending report saved with `compare` but no `compareTo` fell through to the default end, which is the month before now. The comparison then widened to the distance between the two months instead of staying on the adjacent-month pair - a report on 2016-01 became a 12-month span. The #7672 early return masked this for live reports by never reaching the shared range path; removing it exposed the gap, which predates that PR. Static reports were affected the same way and are fixed too. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] Trim comments in the spending range code Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * [AI] Fix mobile running balance when reconciled transactions are hidden (#9107) Co-authored-by: Kristián Valentín <621478+kikoval@users.noreply.github.com> * fix: keep the decimal point of imported amounts that have a currency suffix (#9100) * monte carlo block bootstrap (#9101) * [AI] Smooth report card number resizing with CSS (#9123) * [AI] Smooth report card number resizing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update VRT screenshots Auto-generated by VRT workflow PR: #9123 * [AI] Fix multiline formula result sizing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * [AI] Fix running balance text selection in draggable transaction rows (#9125) Co-authored-by: Kristián Valentín <621478+kikoval@users.noreply.github.com> * [AI] Add MASH to additional installation options (#9026) Claude-Session: https://claude.ai/code/session_01FK49NqPPMe9sJGo6uXE1Vg Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * Merge commit from fork * [AI] Reject invalid password inputs before hashing - Require non-empty strings at password validation, hashing and verification boundaries so JSON objects, arrays and other scalar types cannot reach Argon2's buffer conversion. - Add regressions for malformed passwords on fresh and configured servers, including assertions that rejected input never reaches Argon2. Advisory: https://github.com/actualbudget/actual/security/advisories/GHSA-3jjw-2hq7-mmm2 * [AI] Require admin permission for EnableBanking configuration - Require administrator permission to configure EnableBanking, even when credentials have not been configured yet. - Check permission before validating credentials with the provider or persisting secrets, and disable configuration controls for non-admins. - Test unauthenticated, BASIC and ADMIN requests against empty and existing configuration, preserving secrets and avoiding provider calls on denial. Advisories: https://github.com/actualbudget/actual/security/advisories/GHSA-hfxx-qc34-gqcw * [AI] Neutralize BOM-prefixed CSV formulas - Detect spreadsheet formula triggers after leading BOM characters and prepend an apostrophe before the original text. - Protect textual CSV headers and scalar values as well as ordinary strings. - Preserve numeric output, including negative amounts, and cover repeated BOMs, formula triggers, quoting and Unicode text in regression tests. Advisories: https://github.com/actualbudget/actual/security/advisories/GHSA-7jvc-qfpw-85g2 * [AI] Remove ambiguous Commerzbank payee matching - Split Commerzbank payees into non-empty whitespace/slash-delimited tokens and escape their literal text before constructing the cleanup expression. - Use consuming separators that cannot overlap the literal tokens, avoiding ambiguous matching on long runs of spaces or slashes. - Require separators between payee tokens: concatenated words in notes are no longer stripped. Allow 20 seconds for the hostile-case subprocess, including Node and Vite startup on slower CI runners. - Skip matching for empty payees while preserving normal note cleanup. - Exercise hostile payees, including the report's spaced-word reproducer, in a subprocess with a hard timeout; retain the existing bank fixtures. Advisories: https://github.com/actualbudget/actual/security/advisories/GHSA-p34v-m2gp-w3jm * [AI] Reject unsafe IPv6 address classes in SSRF checks - Allow public unicast addresses by default. The private-network option additionally permits private, loopback, unique-local and carrier-grade NAT addresses. - Permit the well-known NAT64 prefix only for embedded public IPv4 destinations; private and metadata addresses remain blocked in both modes. - Reject other IPv6 transition, multicast, reserved and link-local classes in both modes, and classify IPv4-mapped addresses by the embedded IPv4 address. - Cover literal URLs, DNS answers, redirects and both SimpleFIN fetch paths, including NAT64, 6to4 and Teredo addresses, with synthetic upstreams. Advisories: https://github.com/actualbudget/actual/security/advisories/GHSA-97j5-6g5r-42v5 * [AI] Exclude callback secrets from ordinary server logs - Log request method, pathname and response status without query strings, and restrict serialized metadata to the same safe fields. - Keep full OpenID and banking provider messages, response bodies, session identifiers and error stacks behind existing actual-sensitive namespaces. - Apply the same opt-in handling to shared banking errors, proxy target URLs and unhandled rejections; do not forward raw streaming errors to Express's ordinary error logger. - Stop logging stored secret values, including with debugging enabled. - Document targeted sensitive debugging and private support exchanges. - Test ordinary and opt-in output, query and serialized metadata leaks, provider failures, session URLs, streaming errors and secret storage. - Preserve provider format sequences by passing SimpleFIN response text as a debug format argument, rather than part of the format string. Advisories: https://github.com/actualbudget/actual/security/advisories/GHSA-ppcp-hx2h-fm35 * [AI] Revoke password sessions when credentials change - Update the password hash and revoke all password-authenticated sessions in one transaction, including password changes through the local CLI. - Issue a fresh token for each successful password login instead of reusing an existing session token. - Recheck the verified password hash before creating a session so an in-flight login cannot restore access after a credential change. - Correct the UI's claim that other sessions remain signed in, and test multiple sessions, CLI resets, transaction rollback and login/reset races. This addresses session revocation and token reuse. Requiring the current password for remote changes is covered by the following commit. Advisories: https://github.com/actualbudget/actual/security/advisories/GHSA-7363-3qq5-2frj https://github.com/actualbudget/actual/security/advisories/GHSA-4wgp-48c6-v9w3 * [AI] Require the current password for remote password changes - Require currentPassword alongside the replacement password for remote changes, preserving the administrator and password-authentication checks. - Verify the current credential, rate-limit attempts and reject a change if the verified password hash changes before the update. - Collect the current password in the client and handle failure to sign in after a successful change. Keep the local reset command available without the old password. - Test that a session token alone cannot change credentials and that failed requests preserve the existing password and sessions. - Clear the redundant success notice when signing in with the new password fails. Advisories: https://github.com/actualbudget/actual/security/advisories/GHSA-4wgp-48c6-v9w3 * [AI] Restrict Electron protocol launches and network file reveals - Allow only parsed HTTP/HTTPS URLs through external-opening IPC, new-window handling and navigation, addressing GHSA-7pf2-w24j-p6h2. - Also reject network/UNC paths in the separate file-reveal handler. A note such as [View receipt](//host/share/invoice.pdf) reaches showItemInFolder without passing through openExternal. On Windows, resolving that share can disclose NTLM authentication responses to an attacker-controlled server where network and authentication policies permit it. This additional flow was not demonstrated in the advisory. - Accept native absolute local paths without adding file-URL conversion. - Test protocol filtering and network-path rejection without invoking OS handlers. The note-to-file-reveal flow is code-confirmed; an actual Windows authentication exchange has not been reproduced. The following commit addresses the advisory's separate internal-origin classification finding. Advisory: https://github.com/actualbudget/actual/security/advisories/GHSA-7pf2-w24j-p6h2 Additional finding references: https://github.com/electron/electron/blob/v43.4.0/shell/common/platform_util_win.cc#L270-L340 https://learn.microsoft.com/en-us/windows-server/storage/file-server/smb-ntlm-blocking - Exercise Windows path semantics on all test platforms and exclude generated Electron tests and Vitest configuration from the packaged application. * [AI] Classify Electron app URLs exactly and block redirects - Replace substring checks with exact app://actual scheme/host matching and the existing development origin, http://localhost:3001. - Use the classification for browser handoffs and focus/log script execution so remote URLs containing app:// or localhost: are not mistaken for app pages. - Preserve the existing global renderer-navigation block and add redirect blocking there, without duplicating it in the main-window handlers. - Keep ordinary IPC registration unchanged; broad sender validation is outside this URL-classification fix. - Test misleading hosts, ports and URL substrings. Exercise ordinary clicks, new-window links and explicit browser IPC in Electron, asserting the exact HTTP/HTTPS URLs handed to the browser without opening real OS handlers. Exercise a local HTTP redirect to check the app stays put. Advisory: https://github.com/actualbudget/actual/security/advisories/GHSA-7pf2-w24j-p6h2 - Retain startup logs until an internal page finishes loading, then forward them to DevTools; cover startup delivery in the Electron navigation suite. * [AI] Choose import destinations from trusted local context - Allocate fresh local IDs for cloud downloads and local archive imports, ignoring destination IDs supplied in archived metadata. - Pass an explicit local destination for replacement downloads and verify its cloud-file association. Do not require matching sync group IDs, so downloading the reset version of the same cloud file remains possible. - Reject server metadata describing a different cloud file and close the budget before an authorized replacement. - Retain existing import metadata compatibility, collision error handling, nested archive layouts and backups; do not require a budget name. - Test targeted collisions, local recovery imports with obsolete IDs or missing optional metadata, mismatched cloud associations and replacement after a sync ID reset using real local files and databases. Advisory: https://github.com/actualbudget/actual/security/advisories/GHSA-7fr5-fjj8-hwjq - Reuse the matching local budget for API downloads after a sync reset, report cloud-file mismatches in API/CLI errors, and document local IDs. * [AI] Revoke sessions on sign-out and API shutdown Revoke only the supplied session on explicit sign-out. Clear local credentials if the server cannot be reached, and revoke password-created API sessions during shutdown without revoking caller-supplied tokens. Cover isolated and repeated revocation, invalid tokens, offline sign-out, borrowed API tokens and shutdown after a close failure. Session expiry policy is unchanged. Follow-up to: https://github.com/actualbudget/actual/security/advisories/GHSA-7363-3qq5-2frj * [AI] Fix Electron e2e test on master (#9139) * [AI] Make Electron startup log test deterministic Seed corrupt global preferences before launching the startup-log test, so it exercises a real queued diagnostic. Fresh installs no longer log missing preferences after #8968. * [AI] Add release note for desktop startup log test fix * [AI] Improve cache generation perf (~22x) (#9122) * [AI] Make spreadsheet topological sort O(V+E) instead of O(n^2) The iterative topological sort pushed every child onto the stack at once and, on every node pop, scanned the whole remaining stack to decrement its parent's pending-child count. On large budgets the stack gets very tall, so the sort was quadratic and dominated cold `loadBudget` time. Keep the same depth-first traversal and output order, but give each stack frame a cursor into its own neighbor list so finishing a node is O(1), and build the result with push + a single reverse instead of unshift. Also mark nodes visited on entry (as the recursive version before #2848 did), so a dependency cycle is skipped instead of growing the stack until the process runs out of memory. On a real budget (~514k cells, cold load) `loadBudget` drops from ~134s to ~6s with identical computed cell values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016C82Fnx6cnESa3HUTuM9SE * [AI] Use plainer wording in release note Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016C82Fnx6cnESa3HUTuM9SE --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * [AI] Fix Balance Forecast privacy reveal alignment (#9128) * [AI] reports: keep forecast balances aligned when revealed * Update VRT screenshots Auto-generated by VRT workflow PR: #9128 --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * [AI] Fix Net Worth privacy reveal alignment (#9141) * [AI] reports: keep net worth amounts aligned when revealed * [AI] reports: simplify net worth privacy alignment * [AI] Fix Age of Money privacy reveal alignment (#9142) * [AI] reports: keep age of money aligned when revealed * [AI] reports: simplify age of money privacy alignment * ⚡ [AI] Speed up resetting the budget cache (#9143) * [AI] Speed up resetting the budget cache Resetting the budget cache recomputed nearly the whole spreadsheet twice: `loadUserBudgets` closed its own spreadsheet transaction, which sorted from the budget input cells and queued a pass over everything downstream, and the `await` let that pass drain before `recomputeAll` queued it all again. Wrap both in a single transaction so there is one sort, one computation pass and one cache write, and hold the cache barrier for the duration so an interrupted reset leaves the cache marked dirty. `recomputeAll` also marked every `sum-amount` cell dirty, each of which runs its own `SELECT SUM(amount)` — the categories x months cost that #8140 removed from cold builds by seeding those cells from one grouped query. Reuse that query via `reseedSumAmounts` and let `recomputeAll` skip the seeded cells. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] Release the reset transactions and cache barrier on failure `resetBudgetCache` now holds a spreadsheet transaction across two awaited database calls, and `loadUserBudgets` already held its own across one. A rejection in any of them left the transaction depth above zero, which stops the spreadsheet queueing further computations and makes every later `waitOnSpreadsheet` throw — including the one `closeBudget` awaits before unloading the spreadsheet. Close both transaction scopes and the cache barrier in `finally`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * :electron: Disable node options fuse (#9140) * disable node options fuse * shorted release notes * Bump the npm_and_yarn group across 1 directory with 6 updates (#9138) Bumps the npm_and_yarn group with 6 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@capacitor/android](https://github.com/ionic-team/capacitor) | `8.4.2` | `8.4.3` | | [compression](https://github.com/expressjs/compression) | `1.8.1` | `1.8.2` | | [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) | `4.2.0` | `4.3.0` | | [joi](https://github.com/hapijs/joi) | `17.13.7` | `17.13.8` | | [proxy-addr](https://github.com/jshttp/proxy-addr) | `2.0.7` | `2.0.8` | | [source-map-js](https://github.com/7rulnik/source-map-js) | `1.2.1` | `1.2.2` | Updates `@capacitor/android` from 8.4.2 to 8.4.3 - [Release notes](https://github.com/ionic-team/capacitor/releases) - [Changelog](https://github.com/ionic-team/capacitor/blob/8.4.3/CHANGELOG.md) - [Commits](https://github.com/ionic-team/capacitor/compare/8.4.2...8.4.3) Updates `compression` from 1.8.1 to 1.8.2 - [Release notes](https://github.com/expressjs/compression/releases) - [Changelog](https://github.com/expressjs/compression/blob/master/HISTORY.md) - [Commits](https://github.com/expressjs/compression/compare/v1.8.1...v1.8.2) Updates `http-cache-semantics` from 4.2.0 to 4.3.0 - [Commits](https://github.com/kornelski/http-cache-semantics/commits) Updates `joi` from 17.13.7 to 17.13.8 - [Commits](https://github.com/hapijs/joi/compare/v17.13.7...v17.13.8) Updates `proxy-addr` from 2.0.7 to 2.0.8 - [Release notes](https://github.com/jshttp/proxy-addr/releases) - [Changelog](https://github.com/jshttp/proxy-addr/blob/master/HISTORY.md) - [Commits](https://github.com/jshttp/proxy-addr/compare/v2.0.7...v2.0.8) Updates `source-map-js` from 1.2.1 to 1.2.2 - [Release notes](https://github.com/7rulnik/source-map-js/releases) - [Changelog](https://github.com/7rulnik/source-map-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/7rulnik/source-map-js/compare/v1.2.1...v1.2.2) --- updated-dependencies: - dependency-name: "@capacitor/android" dependency-version: 8.4.3 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: compression dependency-version: 1.8.2 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: http-cache-semantics dependency-version: 4.3.0 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: joi dependency-version: 17.13.8 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: proxy-addr dependency-version: 2.0.8 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: source-map-js dependency-version: 1.2.2 dependency-type: indirect dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Julian Dominguez-Schatz <julian.dominguezschatz@gmail.com> * Bump the npm_and_yarn group across 1 directory with 4 updates (#9151) Bumps the npm_and_yarn group with 4 updates in the / directory: [handlebars](https://github.com/handlebars-lang/handlebars.js), [pbkdf2](https://github.com/browserify/pbkdf2), [shell-quote](https://github.com/ljharb/shell-quote) and [smol-toml](https://github.com/squirrelchat/smol-toml). Updates `handlebars` from 4.7.9 to 4.7.10 - [Release notes](https://github.com/handlebars-lang/handlebars.js/releases) - [Changelog](https://github.com/handlebars-lang/handlebars.js/blob/v4.7.10/release-notes.md) - [Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.7.9...v4.7.10) Updates `pbkdf2` from 3.1.5 to 3.1.7 - [Changelog](https://github.com/browserify/pbkdf2/blob/master/CHANGELOG.md) - [Commits](https://github.com/browserify/pbkdf2/compare/v3.1.5...v3.1.7) Updates `shell-quote` from 1.10.0 to 1.12.0 - [Changelog](https://github.com/ljharb/shell-quote/blob/main/CHANGELOG.md) - [Commits](https://github.com/ljharb/shell-quote/compare/v1.10.0...v1.12.0) Updates `smol-toml` from 1.8.0 to 1.9.0 - [Release notes](https://github.com/squirrelchat/smol-toml/releases) - [Commits](https://github.com/squirrelchat/smol-toml/compare/v1.8.0...v1.9.0) --- updated-dependencies: - dependency-name: handlebars dependency-version: 4.7.10 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: pbkdf2 dependency-version: 3.1.7 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: shell-quote dependency-version: 1.12.0 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: smol-toml dependency-version: 1.9.0 dependency-type: indirect dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * [AI] Reduce webassembly heap by freeing all prepared statements (#9131) * [AI] Free sql.js statements prepared by runQuery In the browser, runQuery prepared a new statement for every string query but only freed it for queries that fetch rows. sql.js keeps unfreed statements in the WASM heap until the database closes, so a cold load of a large budget (~500k spreadsheet cache writes) grew the heap by hundreds of MB. Free (or reset) the statement in all paths, including on error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016C82Fnx6cnESa3HUTuM9SE * [AI] Add release note Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016C82Fnx6cnESa3HUTuM9SE * bump --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Michael Clark <5285928+MikesGlitch@users.noreply.github.com> * [AI] Align Gruvbox catalog previews with updated palettes (#9156) * docs: add @tabedzki as a main contributor (#9183) Add @tabedzki to the Main Contributors list in the contributing docs and include an upcoming release note. Assisted-by: ClaudeCode:claude-opus-5-5 * [AI] fix: bump brace-expansion to resolve HIGH CVEs (#9179) Refresh yarn.lock with `yarn up -R brace-expansion` so the 2.x range (^2.0.1, ^2.0.2, pulled in via minimatch@5/9 and shipped in the sync-server image through migrate) resolves to 2.1.7 instead of 2.0.3. This clears CVE-2026-13149, CVE-2026-14257, CVE-2026-69152, CVE-2026-102276 and CVE-2026-102278. The 5.x range moves from 5.0.5 to 5.0.12 as part of the same refresh; 1.x is already at the latest 1.1.21. No resolutions override is needed; both versions are in range of the existing specifiers. Fixes #9166. Assisted-by: ClaudeCode:claude-opus-5-5 * [AI] docs: link experimental features to feedback issues, drop Discord (#9186) Remove "or post a message in the Discord" from the shared ExperimentalFeatureWarning so feedback goes to GitHub issues. - Monte Carlo analysis docs now link to feedback issue #8571. - Multi-currency docs use the shared warning component, linked to the rule action templating feedback issue (#3606). - Budget Analysis toggle in Settings links to /issues/6742 instead of /pull/6742. Assisted-by: ClaudeCode:claude-opus-5-5 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * redesign settings menu, move pages from sidebar into settings (#9110) * [AI] Import from PR #8707 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * clean up the imported settings navigation * move payees, rules and bank sync into a settings menu on mobile * split out appearance settings * remove payees, rules, bank sync and tags from the redesigned sidebar * add accent based colour tokens for the settings nav * match settings card colours to the tables * [AI] update tests for the settings navigation Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Update VRT screenshots Auto-generated by VRT workflow PR: #9110 * move appearance back to general * Update VRT screenshots Auto-generated by VRT workflow PR: #9110 * Update VRT screenshots Auto-generated by VRT workflow PR: #9110 --------- Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * settings card redesign (#9112) * add settings group and row components * move general settings to grouped rows * move appearance settings to grouped rows * move advanced settings to grouped rows * move experimental settings to grouped rows * simplify more settings based on sync state/server * add padding for nested rows, toggle parent/child sensibly * Update VRT screenshots Auto-generated by VRT workflow PR: #9112 * revert some translated strings * Update VRT screenshots Auto-generated by VRT workflow PR: #9112 * Update VRT screenshots Auto-generated by VRT workflow PR: #9112 --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * [AI] Read column names once per (multi row) query (#9178) * [AI] Read column names once per query in the browser runQuery sql.js's getAsObject looks the column names up again for every row. Read them once per query and build each row from its values, which speeds up large reads such as loading the spreadsheet cache. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016C82Fnx6cnESa3HUTuM9SE * [AI] Add release note Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016C82Fnx6cnESa3HUTuM9SE * [AI] Remove comment Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016C82Fnx6cnESa3HUTuM9SE --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * [AI] Clear the spreadsheet cache before resetting it (#9176) * [AI] Clear the spreadsheet cache before resetting it Resetting the budget cache rewrote every row with INSERT OR REPLACE and never removed entries for cells that no longer exist. Clear the cache at the start of the reset, inside the existing cache barrier, so it is rebuilt from empty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016C82Fnx6cnESa3HUTuM9SE * [AI] Add release note Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016C82Fnx6cnESa3HUTuM9SE * [AI] Remove comments Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016C82Fnx6cnESa3HUTuM9SE --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * [AI] Reuse spreadsheet cache prepared statement (#9174) * [AI] Reuse one prepared statement for spreadsheet cache writes A cold build or cache reset writes hundreds of thousands of cache rows, and preparing the same INSERT for each row was about half of that time. Prepare it once per database and reuse it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016C82Fnx6cnESa3HUTuM9SE * [AI] Add release note Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016C82Fnx6cnESa3HUTuM9SE --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * [AI] Don't learn a category rule that a "one of" rule already covers (#9154) Co-authored-by: Kristián Valentín <621478+kikoval@users.noreply.github.com> * [AI] Fix CSV import merging a new transaction after unchecking a matched row (#9135) An unchecked matched row is now sent with skipTransaction, so it keeps its match on import and the other rows match as in the preview. Before, another row with the same amount took the freed match and was merged into the existing transaction. Fixes #8464 Co-authored-by: Kristián Valentín <621478+kikoval@users.noreply.github.com> * [AI] Show commit hash on edge/nightly builds (#8976) * [AI] Show commit hash on edge/nightly builds Fixes #2939. Edge/nightly builds carry the same package.json version between releases, so there was no way to tell which commit is running. Extract the version-computation logic into a testable computeClientVersion(), and forward COMMIT_REF (Netlify) / GITHUB_SHA (GitHub Actions) into REACT_APP_COMMIT_REF via vite.config.mts, mirroring the existing REVIEW_ID/BRANCH forwarding. When present, the short commit ref is appended as semver build metadata, e.g. 26.9.0+e18c8ad. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * [AI] Test preview precedence over commit ref Cover the case where both a review ID and a commit ref are set: PR preview builds must still report `.preview` rather than a commit-tagged version. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * [AI] Keep the plain version for tag (release) builds Release builds (Docker, Netlify, npm) run in GitHub Actions on a version tag with GITHUB_SHA set, so they would have shown vX.Y.Z+sha. Skip the commit ref when GITHUB_REF_TYPE is "tag"; edge and nightly builds keep it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: vatsanbalaji-ossagent <325768530+vatsanbalaji-ossagent@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk> * [AI] Add getScheduleDates to the API (#9020) * [AI] Add getScheduleDates to the API Expand a schedule date (single date or RecurConfig) into the dates it falls on within a window, honouring frequency, interval, monthly patterns, end mode and the weekend move. Also type the return value of recurConfigToRSchedule, and fix the API vitest setup, where inlined UMD builds of @rschedule registered the date adapter and recurrence rules on a second @rschedule/core instance. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CfQCMhYExnAqVNC4gJjF8E * [AI] Build getScheduleDates on getNextDateAfter Walk the schedule with getNextDateAfter, the step the app uses to advance a schedule, instead of expanding the rschedule occurrences. A date that several occurrences move onto (a daily schedule's Saturday and Sunday both landing on Monday with skipWeekend) is now listed once, and count counts distinct dates. Add an API test that pins the first listed date to the next_date the app stores, so the two cannot drift. Drop the recurConfigToRSchedule typing change, which is no longer needed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HRp7PBTgAChF39Dv9iFLyc --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * [AI] Add AI Assistant Integrations (MCP) section to Community Projects (#9171) * Add AI Assistant Integrations section and update entries Added a new section for AI Assistant Integrations (MCP) and reintroduced the actual-budget-mcp entry with detailed descriptions. * [AI] Add release note for MCP community projects section * [AI] Allow click-through on reports split by interval (#9175) Clicking a total-mode custom report split by Interval now opens that interval's transactions. Previously bar, donut and table views blocked clicks for interval splits, and the area chart had no click handling at all. Payment and Deposit reports filter to outflows or inflows only; Net, Net Payment and Net Deposit include all transactions in the interval. Co-authored-by: James N <randomspaceship@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * [AI] Expose tag splitting and scope selection in custom reports (#8948) * [AI] Expose tag splitting and scope selection in custom reports # Conflicts: # packages/desktop-client/src/components/reports/graphs/BarGraph.tsx # packages/desktop-client/src/components/reports/graphs/DonutGraph.tsx * [AI] Show tag scope warnings without transaction filters * [AI] Update tag report test for settings navigation * [AI] Import YNAB4 month notes and category budget notes (#9198) YNAB4 stores a note on each month and on each category's budget entry for that month. Actual already shows those notes, but the YNAB4 importer never copied them. Fixes #9188 Change-Id: zwpozsoyxukrvkvmwrxmxllrkuksxuvu * docs: regenerate all screenshots (#9195) * [AI] docs: regenerate accounts screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate accounts/reconciliation screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate advanced/bank-sync screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate advanced/bank-sync/gocardless screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate advanced/bank-sync/pluggyai screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate advanced/bank-sync/simplefin screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate advanced/restart screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate backup-restore/backup screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate backup-restore/restore screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate budgeting/categories screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate budgeting/credit-cards/carrying-debt screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate budgeting/credit-cards/paying-in-full screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate budgeting screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate budgeting/joint-accounts screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate budgeting/multi-currency screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate budgeting/returns-and-reimbursements screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate budgeting/rules/custom screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate config/multi-user screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate config/oauth-auth screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate experimental/balance-forecast-report screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate experimental/budget-analysis-report screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate experimental/budget-automation screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate experimental/formulas screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate experimental/goal-templates screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate experimental screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate experimental/monte-carlo-analysis screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate experimental/monthly-cleanup screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate experimental/redesigned-sidebar screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate experimental/rule-templating screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate experimental/sankey-report screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate getting-started/starting-fresh screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate getting-started/tips-tricks screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate getting-started/tracking-budget screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate install/desktop-app screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate install/fly screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate install/pikapods screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate migration/nynab screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate notifications screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate reports/custom-reports screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate reports screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate schedules screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate settings screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate tour/accounts screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate tour/budget screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate tour/payees screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate tour/reports screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate tour/rules screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate tour/schedules screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate tour/user-interface screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate transactions/bulk-editing screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate transactions/filters screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate transactions/importing screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate transactions/merging screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate transactions/payee-locations screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate transactions/payees screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate transactions/split-transactions screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate transactions/tags screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: regenerate transactions/transfers screenshots Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [AI] docs: update homepage screenshots and remove replaced image files Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * [autofix.ci] apply automated fixes * [AI] docs: retake the DEMO card payment screenshot before locking the reconciliation Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * make sidebar menu items configurable (#9193) * make toggle switches reachable by keyboard * route sidebar pages from a configurable menu * add a sidebar menu setting * build command bar navigation from the sidebar menu * docs * Update VRT screenshots Auto-generated by VRT workflow PR: #9193 * coderabbit * remove modal * jfdoming feedback * coderabbit * Update VRT screenshots Auto-generated by VRT workflow PR: #9193 * wording tweaks --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * [AI] Allow cash flow and summary cards to resize to 2 columns wide (#9202) Lower the minimum dashboard width of cash flow and summary cards from 3 to 2 columns. Convert the widget min width/height helpers to switch statements, and inline the now single-use custom report type guard. Co-authored-by: James N <randomspaceship@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * [AI] Learn category rules from transactions saved on mobile (#9192) Co-authored-by: Kristián Valentín <621478+kikoval@users.noreply.github.com> * 📈 Monte Carlo pot balances chart (#9158) * monte carlo pot balances * feedback * regenerating screenshots * prune cache (#9203) * prune cache * note * 📈 Monte carlo input focus fix (#9164) * monte carlo - input focus fix * cleaning up a bit * rabbit * [AI] Add ZAR (South African Rand) to currency picker (#9032) - Add ZAR to the currencies list with symbol "R", space-comma number format (e.g. R 1 234,56), symbol before amount - Add ZAR translation entry in Currency settings - Add changelog entry * [AI] preserve category when making transfer between on/off-budget accounts (#9205) * fix: preserve category when making transfer between on/off-budget accounts The batch "Make transfer" action unconditionally cleared the category. Now it only clears when both accounts share the same budget type, matching the server-side clearCategory logic. Fixes #9200 * fix: resolve TS7006 implicit any in account lookup Add explicit type annotations to the accounts.find callbacks. * [autofix.ci] apply automated fixes * Add release note for #9205 --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * [AI] Fix loot-core backend build failing on Windows (#9194) The dev server spawns `yarn` by name to build the loot-core backend. On Windows, `yarn` resolves to a `.cmd` shim, which `child_process.spawn` cannot execute without a shell. The build therefore never starts and the dev server serves an app with no backend: loot-core backend failed to spawn: spawn yarn ENOENT Resolve the Yarn release the repo already pins in `.yarnrc.yml` and launch it through `process.execPath` instead. This avoids a shell entirely, so it also keeps working when the repository path contains spaces. Co-authored-by: grzdev <grzdev@users.noreply.github.com> * remove flatpak asset from release (#9221) * fix moving categories between groups on mobile (#9213) * [AI] Match nothing for an invalid regex instead of failing the query (#9238) The SQLite REGEXP function built the pattern with new RegExp() unguarded, so a "matches" rule condition that is still being typed (e.g. "asdf\") made the rule editor preview query throw and show an error. It now returns no match for an invalid pattern, the same as matches conditions evaluated in memory. The web and Electron databases share the helper. Fixes #6317 * [AI] Fix sidebar rename input closing on arrow keys (#9219) In the redesigned sidebar, react-aria's Tree rows handle Left/Right in a capture-phase listener (keyboardNavigationBehavior "arrow") to move focus between elements in the row. That runs before the rename input's own key handler, pulls focus out of the input, and its blur ends editing. Track the row/group being renamed in AccountTree and switch the Tree to "tab" keyboard navigation while editing, so key presses inside the input are left alone. Arrow navigation is unchanged otherwise. Claude-Session: https://claude.ai/code/session_011PgrqzhD1Li3kJwH74vDnQ Co-authored-by: Claude <noreply@anthropic.com> * [AI] Fix Enable Banking counterparty selection by payment direction (#9204) * [AI] Fix Enable Banking counterparty selection by payment direction * [AI] Add release note for Enable Banking counterparty fix * [AI] Fix schedules skipping a weekend date moved to the next weekday (#9191) * [AI] Keep a weekend date moved onto today or later as a schedule's next date Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PvVofKtxTWe228iQ8r1xiD * [AI] Use the local day when creating a schedule west of UTC `new Date(currentDay())` parses the day as UTC midnight, which is still the previous day west of UTC, so a past occurrence could become the next date. Parse it as a local date instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EogsQHZizXa8kotaRRQXVf --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * ⬆️ october dependency bump (#9244) * @types/node (^22.20.1 → ^22.20.5) * eslint (^10.7.0 → ^10.12.0) * eslint-plugin-perfectionist (^5.10.0 → ^5.12.1) * knip (^6.27.0 → ^6.41.0) * lage (^2.15.17 → ^2.17.0) * minimatch (^10.2.5 → ^10.2.6) * p-limit (^7.3.0 → ^7.3.3) * vitest (^5.0.0 → ^5.0.4) * uuid (^14.0.2 → ^14.0.3) * rollup-plugin-visualizer (^7.0.1 → ^7.1.1) * vite (^8.1.5 → ^8.3.4) * @bufbuild/protobuf, @bufbuild/protoc-gen-es (^2.12.1 → ^2.16.0) * @chromatic-com/storybook (^5.2.1 → ^5.5.1), @storybook/addon-a11y, @storybook/addon-docs, @storybook/react-vite, eslint-plugin-storybook, storybook (^10.5.2 → ^10.6.2) * fix tests * @internationalized/date (^3.12.2 → ^3.12.4), react-aria-components (^1.19.0 → ^1.22.1), react-aria (^3.50.0 → ^3.53.1) * @rolldown/plugin-babel (~0.2.3 → ~0.2.4) * @testing-library/react (16.3.2 → 16.3.3), @testing-library/dom (10.4.1 → 10.4.2), @testing-library/user-event (14.6.1 → 14.6.8) * @types/react (^19.2.17 → ^19.3.0), @types/react-dom (^19.2.3 → ^19.3.0) * @vitejs/plugin-react (^6.0.3 → ^6.1.2) * react, react-dom (19.2.7 → 19.3.0) * @babel/core (^8.0.1 → ^8.0.7) * @codemirror/language (^6.12.4 → ^6.13.1), @codemirror/state (^6.7.1 → ^6.7.6), @codemirror/view (^6.43.6 → ^6.43.14), @lezer/highlight (^1.2.3 → ^1.2.5), @uiw/react-codemirror (^4.25.11 → ^4.25.12) * @fontsource-variable/inter, @fontsource/redacted-script (^5.2.8 → ^5.3.0) * @reduxjs/toolkit (^2.12.0 → ^2.13.0) * @tanstack/react-query (^5.101.2 → ^5.104.2) * csv-stringify (^6.8.1 → ^6.9.0) * es-toolkit (^1.49.0 → ^1.52.0) * hyperformula (^3.3.0 → ^3.5.0) * i18next (^26.3.6 → ^26.4.4) * i18next-resources-to-backend (^1.2.1 → ^1.2.3) * lru-cache (^11.5.2 → ^11.5.3) * react-error-boundary (^6.1.2 → ^6.1.6) * react-grid-layout (^2.2.3 → ^2.3.0) * react-i18next (^17.0.10 → ^17.0.16) * react-router (8.3.0 → 8.4.0) * recharts (^3.9.2 → ^3.10.1) * rolldown (^1.2.0 → ^1.2.13) * sass (^1.101.0 → ^1.105.1) * @easyops-cn/docusaurus-search-local (^0.55.2 → ^0.55.3) * @r74tech/docusaurus-plugin-panzoom (^2.4.2 → ^2.5.0) * eslint-vitest-rule-tester (^3.1.0 → ^3.2.0) * csv-parse (^7.0.2 → ^7.0.3) * fast-check (^4.9.0 → ^4.10.2) * jest-diff (^30.4.1 → ^30.5.2) * yargs (^18.0.0 → ^18.2.0) * @capacitor/android (^8.4.3 → ^8.5.3), @capacitor/core, @capacitor/cli (^8.4.2 → ^8.5.3) * express (^5.2.1 → ^5.3.0) * express-rate-limit (^8.6.0 → ^8.7.1) * ipaddr.js (^2.4.0 → ^2.5.0) * supertest (^7.2.2 → ^7.3.1) * oxlint, @oxlint/plugins (^1.74.0 → ^1.88.0), oxlint-tsgolint (^0.25.0 → ^7.0.2004) * oxfmt (^0.59.0 → ^0.73.0) * cosmiconfig (^9.0.2 → ^10.0.1) * @testing-library/jest-dom (^6.9.1 → ^7.0.1) * jsdom (^29.1.1 → ^30.1.2) * vite-plugin-pwa (^1.3.0 → ^2.0.0) * argon2 (^0.44.0 → ^0.45.1) * pluggy-sdk (^0.89.0 → ^0.92.0) * note * bump crdt * Revert "@capacitor/android (^8.4.3 → ^8.5.3), @capacitor/core, @capacitor/cli (^8.4.2 → ^8.5.3)" * 📈 Monte carlo investment phases (#9233) * investment phases * screenshot update * feedback * [AI] Use CHF as the Swiss franc currency symbol (#9247) Switch the CHF symbol from "Fr." to the ISO 4217 code "CHF", which is the language-neutral abbreviation defined in Art. 1 of the Swiss Coinage Ordinance (SR 941.101) and the symbol CLDR uses for CHF in every locale. When a currency's symbol is its ISO code, default to a space between the symbol and the amount ("CHF 100" rather than "CHF100"). Claude-Session: https://claude.ai/code/session_01UqtpwNYn1im7xXdDawSgF5 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * use sheet style menus for mobile instead of modals (#9127) * add a bottom sheet version of Modal * add mobile sheet building blocks * add budget menu hooks * swap mobile menus to bottom sheets * [AI] Update mobile e2e page models for sheets Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Update VRT screenshots Auto-generated by VRT workflow PR: #9127 * [AI] Update mobile e2e for sheets Sheets are labelled by their title and have no close button, so the page models find them by role alone and dismiss with Escape. Menu actions are rows, deleting a category or group confirms inside the sheet, the Reconcile row is matched exactly, and the budget amount is tapped before it is filled because the sheet no longer autofocuses it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Update VRT screenshots Auto-generated by VRT workflow PR: #9127 * [AI] Make sheet row text one size larger Review feedback: the 13px rows read a touch small on a phone. Rows and disabled menu entries now use the 15px medium text size. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Update VRT screenshots Auto-generated by VRT workflow PR: #9127 * keep the edit notes row in the same place on every sheet * Update VRT screenshots Auto-generated by VRT workflow PR: #9127 --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * enable new lint rules (#9248) * add assertions to tests that only relied on not throwing * derive state during render instead of in effects * move impure calls out of render * fix memoization dependencies * declare state before the effects that set it Calendar also swaps the dirty flag that was reset inside useMemo for a refetch counter, so a refetch recomputes once instead of twice. * remove react-hooks rule suppressions * construct Intl.DateTimeFormat with new * remove the unused EmptyObject type * enable react compiler and vitest lint rules * [AI] Return what a bank sync reconciled from the API's runBankSync (#8998) * Return what a bank sync reconciled from the API's runBankSync `accounts-bank-sync` and `simplefin-batch-sync` already reconcile each account and report the transactions they added and matched, but `api/bank-sync` destructured `{ errors }` out of that and returned nothing. An API consumer could tell a sync had succeeded, not whether it had done anything, so tools built on the API either notify on every scheduled run or diff the transaction table themselves to find out. The handler now collects `newTransactions`, `matchedTransactions` and `updatedAccounts` across both the single-account and batch paths and returns them. Errors still throw, so a resolved promise continues to mean the sync succeeded, and `void` to an object breaks no existing caller. This also makes `runBankSync` consistent with `importTransactions`, which already surfaces its reconciliation result. Verified against a live sync server with a GoCardless-linked account, two runs back to back: published 26.9.0 undefined first run 16 newTransactions, 0 matched, 1 updatedAccount second run 0, 0, 0 All 16 returned ids resolve to real transactions in the ledger. The second run is the case this is for: successful, and nothing happened. * Address review: real error fixtures, existing doc convention, bounded appends Three fixes from the CodeRabbit review. The docs entry named a `BankSyncResult` type that does not exist anywhere in the repo. It now spells the shape inline, the way the neighbouring `importTransactions` entry already does. The test fixtures passed a bare string where a sync error goes, but a real SyncError is an object and the handler reads two fields off it: getBankSyncError(errors[0]) for the message and errors[0].code for the code it attaches. The code half was therefore never exercised. Fixtures are now typed SyncResponseWithErrors with a real error object, the mock reads `.message` like the real helper, and a new test asserts the code reaches the thrown error. Confirmed it fails if `withErrorCode` is dropped. The id lists are appended one at a time instead of spread, since they are as long as the sync is big. `allErrors` keeps its existing spread: it is bounded by the account count, not by the transaction count. * docs(api): type runBankSync's result and mark its argument optional Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YPyenH5t6DHfoqqkXLWfEn * [AI] docs(api): correct runBankSync's updatedAccounts and note partial imports on rejection updatedAccounts only lists accounts that received new transactions, not accounts whose balance was refreshed. When syncing every account, one failing account rejects the call after the others have already imported. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011AwAzzZALs5RFb454j3cMV * [AI] docs(api): say runBankSync's arrays are empty only when nothing was added or updated Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WnubtGYPLiUFthYYeUxMaY --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * Add the Norwegian Krone to the list of available currencies (#9256) * Fix editing notes/account on mobile after a split rule (#9254) * Fix editing notes/account on mobile after a split rule (#9189) * Address review: sync transactions ref in layout effect and preserve child errors * [AI] Restrict SimpleFIN private network access to configured ranges (#9245) * 📈 Fix monte carlo withdrawal rule wording (#9252) * fix wording of withdrawal rule explanation * release note * fix note * fix rounding * test * remove still wording * fix NaN issue * [AI] Add "hide reconciled" toggle to all accounts view (#9257) * Reconciled toggle for all accounts view * Add release note * Add modal for all accounts * Register the modal * Apply filtering * Adjust release note * Proper state handling * Account.id fallback & sync fix * [AI] docs: add Pethost to Additional Installation Options (#9222) * [AI] docs: add Pethost to Additional Installation Options * [AI] docs: link Pethost's template page * [AI] Reject invalid periodic automation intervals (#9021) * [AI] Reject invalid periodic automation intervals * [AI] Address periodic interval review feedback * [AI] Clarify periodic loop error and drop leading-zero intervals Address review feedback on the periodic interval guard: reject repeat intervals with leading zeroes such as "002", and make the stalled-date error say an infinite loop was detected and to check the repeat interval. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XJDNR72i3yEZttwMUuk4cq --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: youngcw <calebyoung94@gmail.com> * Support Zero Based Axis in Balance Forecast Report(s) (#9129) * Added support and a toggle in the report setup to enable Zero Based or Auto Y Axis Generation, sets the default to Zero Based * Release Notes * Added tests to allow the playwright script to use the Axis selector * Update VRT screenshots Auto-generated by VRT workflow PR: #9129 * changed default to be Auto, cleaned up tests to test various combinations and reflect new default status * [autofix.ci] apply automated fixes * Update VRT screenshots Auto-generated by VRT workflow PR: #9129 * Fixed issue With Test Helper choosing the old handle for Auto vs Zero Based as the default changed. Also revised the patch notes * [autofix.ci] apply automated fixes * Update VRT screenshots Auto-generated by VRT workflow PR: #9129 * Change moniker from "Zero Based" to "Zero-Based" to match rest of project. Per coderabbit review as well * Update auto-generate wording at coderabbit's request * Update VRT screenshots Auto-generated by VRT workflow PR: #9129 --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * [AI] Write exact bytes when saving exported files from desktop app (#9286) * [AI] Write exact bytes when saving exported files from desktop app Saving a file from the desktop app wrote the whole pooled ArrayBuffer behind the Buffer, not just its bytes, so exports (e.g. ZIP) were corrupted with extra data. The save handler also resolved before the write finished and never rejected on error, and it wrote even when the save dialog was canceled. Refs #9230 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Change-Id: xmymtqyrwlnukxxtptkvvltsklzrsoto * Show errors in saveFile callers Change-Id: ywznpykluynwxkpulkzsspzptylotmsr * add void operator to onExport Change-Id: ymrstulonpvovwlyspwsoqtmokupqsql --------- Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> * [AI] Fix newline conversion for HTTPS key and cert environment variables (#9265) * [AI] Surface the real GoCardless error reason in the UI (#8778) * [AI] Surface the real GoCardless error reason in the UI When a GoCardless call failed, the reason was lost twice on the way to the user. The sync server's `handleError` collapsed every failure into `error_code: 'INTERNAL_ERROR'` plus the classified message, dropping the `summary`/`detail` GoCardless itself returned; the bank-list modal then ignored the payload entirely and rendered one hardcoded sentence blaming misconfigured credentials. An IP block and a rate limit both surfaced as "GoCardless access credentials might be misconfigured", leaving self-hosters to guess without server logs. Forward a whitelisted `error_details` (`status`, `summary`, `detail`) from the server and render it in the modal, falling back to the existing generic sentence when a failure carries no reason. `error_code`/`error_type` keep their current meaning and values, since `handleError` is shared by every GoCardless route and consumers branch on specific codes. Only `summary`/`detail` travel — never the raw body or the request headers, which carry the bearer token — and both are truncated so a stray HTML error page cannot flood the UI. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] Address review: guard superseded bank-list requests Ignore `gocardless-get-banks` responses for a country the user has already moved on from, so a slow failing request cannot overwrite the banks, error and loading state produced by the request that replaced it. Also replace the `as never` cast in the /get-banks success test with the shared `mockInstitution` fixture. * [AI] Only surface error details from GoCardless responses Drop the fallback that read summary/detail off any error's raw details, so text that did not come from a GoCardless API response is never echoed to the client. --------- Co-authored-by: zannis <1011451+zannis@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * fix: Preserve disappearing split payees (in schedules) after posting transactions (#9288) Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk> * Bump follow-redirects (#9290) Bumps the npm_and_yarn group with 1 update in the / directory: [follow-redirects](https://github.com/follow-redirects/follow-redirects). Updates `follow-redirects` from 1.16.0 to 1.16.1 - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.16.0...v1.16.1) --- updated-dependencies: - dependency-name: follow-redirects dependency-version: 1.16.1 dependency-type: indirect dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump launch-editor (#9292) Bumps the npm_and_yarn group with 1 update in the / directory: [launch-editor](https://github.com/vitejs/launch-editor). Updates `launch-editor` from 2.14.1 to 2.14.2 - [Commits](https://github.com/vitejs/launch-editor/compare/v2.14.1...v2.14.2) --- updated-dependencies: - dependency-name: launch-editor dependency-version: 2.14.2 dependency-type: indirect dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * [AI] Batch dashboard layout updates (#9280) * [AI] Batch dashboard layout updates `updateDashboard` was the only dashboard write that skipped `updateWithSchema` and the only multi-row handler not wrapped in `batchMessages` — so one drag sent each widget as its own set of messages and scheduled its own full sync. Wrap the updates in a single batch, and go through `updateWithSchema` so `convertForUpdate` stringifies `meta`. The only caller sends integers today, but a `meta` payload threw `Unserializable value type` and aborted the whole update; the new test covers that. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [AI] Drop a redundant comment on the dashboard update batch Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * [AI] Refresh the report dashboard when another device syncs changes (#9277) The dashboard and custom reports are read through one-shot react-query queries, but sync-events.ts only invalidated the caches for prefs, categories, payees, accounts and account groups. Nothing invalidated dashboardQueries or reportQueries, so a widget added on another device only surfaced after a window refocus or a remount. This was a regression from the react-query retrofit: the previous hooks were liveQuery-backed and subscribed to sync-event themselves. Add the missing branches for the dashboard, dashboard_pages and custom_reports datasets (which getTablesFromMessages already reports) to both the sync-event and undo-event handlers, and cover the mapping with a test. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * fix: api allow partial category updates without name (#8857) (#9258) * Fix TypeError on partial category updates that omit name (#8857) * Address CodeRabbit review on partial category update * Reject category updates that only carry ignored fields * docs: name release note after the PR * docs: remove issue-numbered release note * [AI] Block bank sync for deleted budgets (#9266) * [AI] Block bank sync for deleted budgets * Update bank sync handling for deleted budgets * [AI] Make budget deletion and secret cleanup atomic * Fix edge case for missing payee name (#9295) * 🐛 Fix multi tab handover after creating a budget (#9272) * fix multi tab handover after creating a budget * announce new budget id on rekey * on resume a tab thats still a member of a roup keeps its place * fix: error disabling formula on date rule (#9296) * fix: error disabling formula on date rule * chore: note * chore: feedback * [AI] Show goal progress in the balance menu and fund goals from To Budget (#9281) * [AI] Show goal funding status and an Assign action in balance menus Use the existing Budget Automation engine to add a panel to category balance menus with the month's recommendation, what is assigned so far, what is still needed, and an Assign action that adds the money still needed (undoable). - budget/category-funding reads the dryRunCategoryTemplate projection without writing goal_def; categories without funding templates get null, and invalid automations fail closed. - budget/fund-category recomputes on the server, assigns the full recommendation and goal in one batch, and is a no-op when already funded. - Funding reads are gated by the goal templates flag, only run for categories with templates, and refresh after data changes and funding. - Covers desktop and mobile envelope and tracking balance menus, including tracking income categories. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XJDNR72i3yEZttwMUuk4cq * [AI] Fold goal funding into the existing goal status and never overbudget Address review feedback on the category funding change: - Replace the separate funding panel with the existing goal status. It now shows at the top of the desktop envelope and tracking balance menus (as it already did on mobile) with a thin progress bar, and the desktop hover tooltip is removed along with its tooltipDisabled workaround. - Replace the Assign button with a "Fund goal from To Budget" balance menu item (envelope only, not for long-term goals) that uses the existing undoable transfer-available action, so it never moves more than To Budget and never lowers a budget. It is hidden when the goal is met or To Budget is empty. - Drop the live dry-run endpoint, the fund-category endpoint, their client queries and refresh wiring, and the tracking income additions. Status, bar, colour and fund amount now all read the stored goal. - Add transferAvailable tests and menu wiring tests, and update the budget automation docs for the moved goal status. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XJDNR72i3yEZttwMUuk4cq * [AI] Move GoalStatus into its own file GoalStatus isn't specific to BalanceWithCarryover, so give it a type-strict module of its own alongside its row helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CpAtycTVCRhkLcuD6AJeTM * [AI] Update goal status screenshots to show the balance menu Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CpAtycTVCRhkLcuD6AJeTM * [AI] Clarify how funding a goal affects next month's To Budget Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CpAtycTVCRhkLcuD6AJeTM --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: youngcw <calebyoung94@gmail.com> * bump node and electron (#9298) * bump node and electron * use nvm node version in CI * note * bump docker node image * [AI] Add per-budget SimpleFIN credentials (#9214) * [AI] Add per-budget SimpleFIN credentials * [AI] Clear budget credentials when switching SimpleFIN to global * [AI] Reject SimpleFIN access to deleted budgets * [AI] Use distinct keys in SimpleFIN isolation test * [AI] Fix iOS mobile app startup and add TestFlight release script (#9311) The Capacitor iOS app was stuck on the splash screen: the splash was configured to never auto-hide and nothing hid it, and underneath the web app failed because the capacitor:// scheme is never cross-origin isolated so SharedArrayBuffer is unavailable. Drop the splash setting and run the native app in the existing no-SharedArrayBuffer fallback mode. Pad the mobile page header by the top safe-area inset so it is not hidden behind the notch (a no-op in the PWA and browsers). iOS project: bundle ID com.actualbudget.actual, arm64 device capability, export-compliance flag, light status bar text. `release:ios` now archives with the team from APPLE_TEAM_ID, derives the build number from a UTC timestamp and the version from the package version, uploads to App Store Connect, and accepts optional xcodebuild auth flags for CI. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> * feat: add Porcelain to custom theme catalog (#9312) Porcelain is a calm light theme with low-saturation blue-grey neutrals and a single soft blue accent. The CSS is hosted at `tostasmistas/actual-budget-porcelain-theme/actual.css` and defines every `--color-*` variable of the built-in Light theme. * [AI] Make file writes atomic (#9014) * [AI] Write budget files atomically (temp file + rename) /upload-user-file and loot-core's file writer both wrote directly to their final path with a single fs.writeFile call. A write that fails partway through (disk full, process killed, etc.) leaves the previous good file truncated/corrupted, and any concurrent reader (the download endpoint, or an external tool like backup software) can observe a torn write mid-upload. All three now write to a uniquely-named temp file and rename it over the target, mirroring the pattern already used elsewhere in this repo (cli/cache.ts, asyncStorage/index.electron.ts, desktop-electron/index.ts). loot-core's electron and api platform implementations share the new atomic-write helper to avoid duplicating the retry/cleanup logic. Co-Authored-By: Claude <noreply@anthropic.com> * [AI] Fix typecheck errors from the atomic-write consolidation promiseRetry's callback inferred a Promise<T | void> return because the catch branch returned retry()'s void result; explicit generic + a trailing throw fixes the inference. writeFile's public signature returns Promise<boolean>, so the thin wrappers need an explicit `return undefined` now that there's no implicit fallthrough. Also reverts an unnecessary narrowing of the mocked fs.promises.writeFile callback param in app-sync.test.ts, which didn't match the real PathLike | FileHandle signature. Co-Authored-By: Claude <noreply@anthropic.com> * [AI] Follow symlinks, keep file mode, and sweep orphaned temp files Writes now resolve symlinks so the rename lands on the real file instead of replacing the link, and carry the previous file's permission mode over to the replacement. Before each write, temp files left for the same target by a crashed process are removed; this process's own in-flight temp files are skipped, assuming a single process owns the data directory. Co-Authored-By: Claude <noreply@anthropic.com> * [AI] Address review findings on atomic writes - atomic-write.ts is a new file, so it is now type-strict. - Preserving the file mode is best-effort: a filesystem that refuses chmod no longer fails the write. sync-server now preserves the mode too. - The orphaned temp-file sweep runs once per directory per run and matches only our own temp names, so crash leftovers for files that are never rewritten (e.g. timestamped backups) are cleaned up without listing the directory on every write. - A symlink to a missing file is followed instead of replaced. - The real lock-retry behaviour is now tested (recover, and give up). - Release note category is Bugfix, per the contributing docs. Co-Authored-By: Claude <noreply@anthropic.com> * [AI] Fix lock-retry tests hanging when file I/O is slow settleWithFakeTimers stopped advancing fake time after 100 event-loop turns. When real file I/O outlasted that budget (as on a loaded CI runner), the next backoff timer was scheduled after the loop gave up and never fired, so the test hung until its 5s timeout. Advance until the write settles instead; a genuine hang still fails on the test timeout. Co-Authored-By: Claude <noreply@anthropic.com> * [AI] Guard user file paths against escaping the user files directory getPathForUserFile now resolves the blob path and refuses anything that lands outside the configured userFiles directory. Callers already validate the id with isValidFileId; this makes the containment explicit where the path is built, in a form CodeQL's path-injection query recognizes as a sanitizer. Co-Authored-By: Claude <noreply@anthropic.com> * [AI] Follow symlinks for sync-server uploads via a shared atomic write helper The upload route renamed its temp file over the blob path directly, so a symlinked blob was replaced by a regular file and its real target left stale. Before the atomic-write change, fs.writeFile followed the link. Move the upload write into atomicWriteUserFile in util/paths.ts, which resolves symlinks (like loot-core's atomicWriteFile), stages the temp file next to the real target, sweeps that directory, preserves the mode and renames into place. The route handler is back to a single call. Co-Authored-By: Claude <noreply@anthropic.com> * [AI] Flush atomic writes to disk and create temp files with the original mode Write the temp file with flush: true so its data reaches disk before the rename; otherwise a power loss can leave the target renamed but empty on some filesystems. Read the existing file's mode first and create the temp file with it, instead of creating it with the default mode and chmod-ing afterwards. The new contents are then never more readable than the file they replace, even briefly or if chmod fails. The chmod stays (best-effort) because the umask can strip bits at creation. Co-Authored-By: Claude <noreply@anthropic.com> * [AI] Type atomicWriteFile contents as string or binary Callers already pass raw bytes (backup zips, db.sqlite from a backup restore or cloud download); it only typechecked because the platform fs files are ts-strict-ignore. Accept string | NodeJS.ArrayBufferView and cover a byte-for-byte binary write. Co-Authored-By: Claude <noreply@anthropic.com> * [AI] Rename concurrent-write tests to match what they check Both tests claimed to cover the sweep's in-flight guard, but the directory is already swept by the time the nested write runs, so they never reach it. What they do verify is that concurrent writes to one target use distinct temp files and the last rename wins. Co-Authored-By: Claude <noreply@anthropic.com> * [AI] Clarify the atomic-write release note Name the affected apps (the browser build is unchanged) and fix the missing article. Co-Authored-By: Claude <noreply@anthropic.com> * [AI] Trim atomic-write tests to the essential cases * [AI] Simplify atomic writes to temp file plus rename * [AI] Keep dangling symlinks and exact file modes in atomic writes Restore the user file path containment guard for CodeQL. * [AI] Resolve dangling symlink targets through the filesystem * [AI] Accept a filesystem root as the user files directory * [AI] Fix root user files test cleanup typing * [AI] Drop the redundant utf8 encoding option from atomicWriteFile * [AI] Tighten atomic-write comments and release note --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Tim Smart <hello@timsmart.co> * [AI] Keep split child fields when updating a transaction through the API (#9307) api/transaction-update passed the caller's partial fields straight to updateTransaction, which replaces a split child wholesale, so a notes-only update cleared the child's category and amount. Merge the fields onto the stored transaction first. The handler also returned before the batch update finished, so the write could land after the API call resolved. Await it. Fixes #5012 * [AI] Fix mobile back navigation after reconciliation (#9303) * fix: exclude income from spending analysis budget (#9318) * fix: exclude income from spending analysis budget * docs: add release note for spending analysis fix * 🔖 (26.11.0) --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: youngcw <calebyoung94@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv> Co-authored-by: Michael Clark <5285928+MikesGlitch@users.noreply.github.com> Co-authored-by: Marcin Radoszewski <moriturius@gmail.com> Co-authored-by: Harsh Mathur <58532371+harshmathurx@users.noreply.github.com> Co-authored-by: tabedzki <35670232+tabedzki@users.noreply.github.com> Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk> Co-authored-by: parapsychic <63157522+parapsychic@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Zannis Kalampoukis <zannis@users.noreply.github.com> Co-authored-by: divinevalley <divinevalley@gmail.com> Co-authored-by: Sanidhya Awasthi <141642661+Islinger19@users.noreply.github.com> Co-authored-by: Sanidhya Awasthi <sanidhya.awasthi@bfhl.in> Co-authored-by: Caleb Burton <ccburton4@gmail.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Tim <hello@timsmart.co> Co-authored-by: Som Samantray <92726151+SomSamantray@users.noreply.github.com> Co-authored-by: JerryNee <37407632+JerryNee@users.noreply.github.com> Co-authored-by: cakeni <2150015994@qq.com> Co-authored-by: cakeni <211545599+cakeni@users.noreply.github.com> Co-authored-by: Patrick Danilevici <patrick@danilevici.ro> Co-authored-by: Anju <168076172+anjupathak03@users.noreply.github.com> Co-authored-by: Shahid <shahid.sarker@gmail.com> Co-authored-by: Junyuan Zheng <53073448+junyuanz1@users.noreply.github.com> Co-authored-by: davidlitmanowicz <51731704+davidlitmanowicz@users.noreply.github.com> Co-authored-by: DavidGrinberg <David@egrinberg.com> Co-authored-by: Tim Smart <tim.smart@effectful.co> Co-authored-by: Haim Gelfenbeyn <haim@g8n.me> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> Co-authored-by: Joel Brenstrum <joel.brenstrum@gmail.com> Co-authored-by: Emil Tveden Bjerglund <emilbp@gmail.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Jansen <chris.jansen@spatialconstrux.com> Co-authored-by: Parman Mohammadalizadeh <prmma23@gmail.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com> Co-authored-by: Julian Dominguez-Schatz <julian.dominguezschatz@gmail.com> Co-authored-by: Henfry De Los Santos <78233072+henfrydls@users.noreply.github.com> Co-authored-by: Onika Dye-Flynn <243370656+Divergent-Code@users.noreply.github.com> Co-authored-by: Spaghettio73 <87806887+Spaghettio73@users.noreply.github.com> Co-authored-by: carru <carru400@gmail.com> Co-authored-by: Ronan Presle <ronan.presle@pm.me> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: dreptschar <56309158+Dreptschar@users.noreply.github.com> Co-authored-by: dreptschar <dreptschar@gmail.com> Co-authored-by: Sam Bai <71630146+sambai-dev@users.noreply.github.com> Co-authored-by: Colin Hebert <active.luck0981@dedicated.contact> Co-authored-by: Artem Ivanov <gh@ivart.xyz> Co-authored-by: Alexander Lopez <115580820+Alex-J-Lopez@users.noreply.github.com> Co-authored-by: Ruben van Duyneveldt <73916005+rvan-duy@users.noreply.github.com> Co-authored-by: flafleur <15882676+flafleur@users.noreply.github.com> Co-authored-by: François Lafleur <francois_lafleur@yahoo.ca> Co-authored-by: Fabian Markert <f.markert87@gmail.com> Co-authored-by: Krzysztof Wójt <97155365+krzysztofwojt@users.noreply.github.com> Co-authored-by: Sreetam Das <sreetamdas@gmail.com> Co-authored-by: Amy Hawkins <ahawkins@cibotechnologies.com> Co-authored-by: Amy Hawkins <aeh1@alum.wustl.edu> Co-authored-by: Albert Bendicho <wiof@users.noreply.github.com> Co-authored-by: danielcovill <daniel.covill@gmail.com> Co-authored-by: J-LCRX <1323011+J-LCRX@users.noreply.github.com> Co-authored-by: Terry Lockett <5551818+terrylockett@users.noreply.github.com> Co-authored-by: John Costa <costajohnt@gmail.com> Co-authored-by: Baris Ari <baris0ari@gmail.com> Co-authored-by: Baris Arı <226740765+rapsoidmorningstar@users.noreply.github.com> Co-authored-by: Baris Ari <226740765+barislabs@users.noreply.github.com> Co-authored-by: Felipe Medici <ic1dem.contato@gmail.com> Co-authored-by: Felipe Medici <femedicidev1@gmail.com> Co-authored-by: Manaf <manaf.aboalrous@gmail.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Maksim Ustinov <madesst@gmail.com> Co-authored-by: jonesfionn101-dotcom <jones.fionn101@gmail.com> Co-authored-by: Fionn Jones <264670603+jonesfionn101-dotcom@users.noreply.github.com> Co-authored-by: Mats Nilsson <matni403@gmail.com> Co-authored-by: Boris Rybalkin <ribalkin@gmail.com> Co-authored-by: Mohammed Yasin Zuhayr <113431400+YasinzHyper@users.noreply.github.com> Co-authored-by: Saurabh Mishra <smsaurabhmishra61@gmail.com> Co-authored-by: Yury Semikhatsky <yurys@chromium.org> Co-authored-by: Soba <57602047+Soba101@users.noreply.github.com> Co-authored-by: Daniel Nieto <daniel@danielnieto.dev> Co-authored-by: Bar M <32390017+galshir@users.noreply.github.com> Co-authored-by: Guo-Yixin <clumsypsc@gmail.com> Co-authored-by: zannis <1011451+zannis@users.noreply.github.com> Co-authored-by: Arbel <arbelonson@proton.me> Co-authored-by: arbelonson-source <269032023+arbelonson-source@users.noreply.github.com> Co-authored-by: cakallie <104972829+cakallie@users.noreply.github.com> Co-authored-by: Carsten <carsten@kallies-net.de> Co-authored-by: Piyush Mehta <me@piyushmehta.com> Co-authored-by: Harshil Patel <1080patelharshil@gmail.com> Co-authored-by: Brad Marshall <bsmarsh333+github@gmail.com> Co-authored-by: bsmarsh333 <2703972+bsmarsh333@users.noreply.github.com> Co-authored-by: Hrushikesh Ramilla <ramillahrushikesh@gmail.com> Co-authored-by: Emil George <emil.george7@gmail.com> Co-authored-by: Bennett Payoyo <75754746+Yahiro025@users.noreply.github.com> Co-authored-by: Bennett Payoyo <Yahiro025@users.noreply.github.com> Co-authored-by: Eric <20937303+ierique@users.noreply.github.com> Co-authored-by: Kristián Valentín <kikoval@users.noreply.github.com> Co-authored-by: Kristián Valentín <621478+kikoval@users.noreply.github.com> Co-authored-by: Alexis Vielma <alexis.vielma.us@gmail.com> Co-authored-by: spatterlight <81454789+spatterIight@users.noreply.github.com> Co-authored-by: Moshe Brevda <mbrevda@gmail.com> Co-authored-by: Sebastián Maluk <samaluk@miuandes.cl> Co-authored-by: Dakyne <23531224+Dakyne@users.noreply.github.com> Co-authored-by: vatsanbalaji-ossagent <itsmesrivathsan@gmail.com> Co-authored-by: vatsanbalaji-ossagent <325768530+vatsanbalaji-ossagent@users.noreply.github.com> Co-authored-by: Andrea <a.gigante@gmail.com> Co-authored-by: bubbletroubles <42738824+bubbletroubles@users.noreply.github.com> Co-authored-by: James N <59348282+RandomSpaceship@users.noreply.github.com> Co-authored-by: James N <randomspaceship@users.noreply.github.com> Co-authored-by: Barend <38956787+bvdwalt@users.noreply.github.com> Co-authored-by: devilking7x <devilhac143@gmail.com> Co-authored-by: Dami Oyeniyi <damilolaoyeniyi13@gmail.com> Co-authored-by: grzdev <grzdev@users.noreply.github.com> Co-authored-by: Jayesh Kugsiya <jkugsiya11@gmail.com> Co-authored-by: Emdadul Haque Apu <emdadulHaqueofficial4@gmail.com> Co-authored-by: ANURAG VERMA <anurag.av57@gmail.com> Co-authored-by: zotabee <89345101+zotabee@users.noreply.github.com> Co-authored-by: Antoine A. <1897695+mirfire@users.noreply.github.com> Co-authored-by: Junaid Khan <khanjunaid80121@gmail.com> Co-authored-by: Ilia Novikov <3877055+ilia-novikov@users.noreply.github.com> Co-authored-by: Stephan <32015025+sadfun@users.noreply.github.com> Co-authored-by: Matty Ice <36863968+mattm4444@users.noreply.github.com> Co-authored-by: Ryan Jinnette <110581203+rjinnette@users.noreply.github.com> Co-authored-by: spa77k <254093522+spa77k@users.noreply.github.com> Co-authored-by: Alireza Shaeri <162827219+AlzShaeri@users.noreply.github.com> Co-authored-by: Koen van Staveren <koenvanstaveren@hotmail.com> Co-authored-by: Margarida Reis <margarida.reis@tecnico.ulisboa.pt> Co-authored-by: Malcolm <90416865+laforcem@users.noreply.github.com> Co-authored-by: Alok Ranjan <alok@omnidim.io> Co-authored-by: Alejandro de Blas <machito2603@gmail.com> Co-authored-by: EMAD BASHARI <hamidleo1984@gmail.com>