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 <[email protected]>
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] 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 <[email protected]>
Claude-Session: https://claude.ai/code/session_016C82Fnx6cnESa3HUTuM9SE
* [AI] Add release note
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_016C82Fnx6cnESa3HUTuM9SE
* bump
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Michael Clark <[email protected]>
* [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 <[email protected]>
* [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 <[email protected]>
---------
Co-authored-by: Claude Opus 5 <[email protected]>
* [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 <[email protected]>
Claude-Session: https://claude.ai/code/session_016C82Fnx6cnESa3HUTuM9SE
* [AI] Use plainer wording in release note
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_016C82Fnx6cnESa3HUTuM9SE
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* [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] 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-2frjhttps://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-L340https://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 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 <[email protected]>
* [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 <[email protected]>
* [AI] Trim comments in the spending range code
Co-Authored-By: Claude Opus 5 <[email protected]>
---------
Co-authored-by: Claude Opus 5 <[email protected]>
* [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 <[email protected]>
* [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 <[email protected]>
* [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 <[email protected]>
* [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 <[email protected]>
* note update
* note update2
---------
Co-authored-by: Claude Opus 5 <[email protected]>
* 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 <[email protected]>
---------
Co-authored-by: Claude Fable 5.1 <[email protected]>
* 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
* [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>
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.
Fixesactualbudget/actual#8522.
Co-authored-by: Bennett Payoyo <[email protected]>
* [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 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 <[email protected]>
* [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 <[email protected]>
* [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 <[email protected]>
* [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 <[email protected]>
---------
Co-authored-by: bsmarsh333 <[email protected]>
Co-authored-by: Claude Fable 5 <[email protected]>
* [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
* 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 <[email protected]>
* [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 <[email protected]>
---------
Co-authored-by: Carsten <[email protected]>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <[email protected]>
* [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 <[email protected]>
* [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 <[email protected]>
---------
Co-authored-by: Claude Opus 5 <[email protected]>
* [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 <[email protected]>
* 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 <[email protected]>
* 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 <[email protected]>
* fix for temp global-store rewrites when running via windows store
* rabbit feedback
* feedback
* more feedback
* cleanup
* last cleanup
* update test
* [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 <[email protected]>
* [AI] Add release note for #8783
Co-Authored-By: Claude Opus 5 <[email protected]>
* [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 <[email protected]>
Co-authored-by: Claude Opus 5 <[email protected]>
* [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.
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.