Files
+123
github-actions[bot]GitHubyoungcwClaude Opus 5Matiss Janis AboltinsMichael ClarkMarcin RadoszewskiHarsh MathurtabedzkiMatt Fiddamanparapsychiccoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>Zannis KalampoukisdivinevalleySanidhya AwasthiSanidhya AwasthiCaleb Burtongithub-actions[bot] <github-actions[bot]@users.noreply.github.com>TimSom SamantrayJerryNeecakenicakeniPatrick DanileviciAnjuShahidJunyuan ZhengdavidlitmanowiczDavidGrinbergTim SmartHaim GelfenbeynStephen Brown IIJoel BrenstrumEmil Tveden Bjerglundautofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>Christian JansenParman Mohammadalizadehgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>github-merge-queueJulian Dominguez-SchatzHenfry De Los SantosOnika Dye-FlynnSpaghettio73carruRonan Presledependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>dreptschardreptscharSam BaiColin HebertArtem IvanovAlexander LopezRuben van DuyneveldtflafleurFrançois LafleurFabian MarkertKrzysztof WójtSreetam DasAmy HawkinsAmy HawkinsAlbert BendichodanielcovillJ-LCRXTerry LockettJohn CostaBaris AriBaris ArıBaris AriFelipe MediciFelipe MediciManafCopilotMaksim Ustinovjonesfionn101-dotcomFionn JonesMats NilssonBoris RybalkinMohammed Yasin ZuhayrSaurabh MishraYury SemikhatskySobaDaniel NietoBar MGuo-YixinzannisArbelarbelonson-sourcecakallieCarstenPiyush MehtaHarshil PatelBrad Marshallbsmarsh333Hrushikesh RamillaEmil GeorgeBennett PayoyoBennett PayoyoEricKristián ValentínKristián ValentínAlexis VielmaspatterlightMoshe BrevdaSebastián MalukDakynevatsanbalaji-ossagentvatsanbalaji-ossagentAndreabubbletroublesJames NJames NBarenddevilking7xDami OyeniyigrzdevJayesh KugsiyaEmdadul Haque ApuANURAG VERMAzotabeeAntoine A.Junaid KhanIlia NovikovStephanMatty IceRyan Jinnettespa77kAlireza ShaeriKoen van StaverenMargarida ReisMalcolmAlok RanjanAlejandro de BlasEMAD BASHARI
04e58dccd3 🔖 update release branch (26.11.0) (#9322)
* [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 commit 89db5a9d8c.

* 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 commit 12b3850ec6.

* 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 commit 35c947ef54.

* [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>
2026-10-26 22:45:10 +00:00
..
2026-10-26 22:45:10 +00:00