Commit Graph
5305 Commits
Author SHA1 Message Date
Matt FiddamanandGitHub 3b2f89edbf decrease time-to-stale (#8539)
* decrease time-to-stale

* note

* post wording

* only consider assigned PRs

* update wording
2026-07-25 20:26:52 +00:00
mooonsandGitHub f00672b5be [AI] Fix bank sync account relinking (#8365) 2026-07-24 11:09:09 +00:00
youngcwandGitHub 6019bf5ac6 [AI] Fix budget templates with a priority not allocating funds in tracking budgets (#8549)
* [AI] Fix budget templates with a priority not allocating funds in tracking budgets

Tracking budgets never define a `to-budget` sheet cell (only envelope budgets
do), so the priority-tier engine always saw $0 available and clamped every
priority>0 template to 0. Seed the pool from `total-saved` instead when in
tracking mode, since it's the structural analog (budgeted income minus
budgeted expenses) and preserves genuine priority-tier competition rather
than disabling the clamp outright.

Fixes #8422

* Trigger Build

* Trigger Build

* Trigger Build
2026-07-23 20:17:24 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
071ec8d328 Bump fast-uri in the npm_and_yarn group across 1 directory (#8554)
Bumps the npm_and_yarn group with 1 update in the / directory: [fast-uri](https://github.com/fastify/fast-uri).


Updates `fast-uri` from 3.1.3 to 3.1.4
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.1.3...v3.1.4)

---
updated-dependencies:
- dependency-name: fast-uri
  dependency-version: 3.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>
2026-07-23 10:14:49 +00:00
Julian Dominguez-SchatzandGitHub 5c774f3d85 [AI] Match mobile app branding to PWA (#8552) 2026-07-23 00:57:49 +00:00
Julian Dominguez-SchatzandGitHub 841f29a598 Update build configuration to support mobile app (#8388)
* Update build configuration to support mobile app

* Add release notes

* Fix build

* Depcheck

* Typo

* Deps

* Typo

* fix

* PR feedback

* Typo

* CodeRabbit

* Changing approach

* Scope changes

* Hot-reload

* Remove mobile update and simplify build

* Fix android build

* Unsigned builds

* Revert compat

* Pull from workspace, not relative path

* Attempt to fix build (run on macos)

* [AI] Diagnose Android SDK on mobile runner

* [AI] Configure Android SDK on mobile runner
2026-07-22 22:10:00 +00:00
BasilThandGitHub 71d802e91f fix: add scoped ErrorBoundaries to mobile schedules, payees, and bank sync pages (#8547) 2026-07-22 17:20:35 +00:00
ShahidandGitHub 3ea01f9eff Change query to typescript (#8545)
* Change query to typescript

* Change query to typescript

* Add release note

* Use string keys for unfilter method
2026-07-22 17:17:30 +00:00
Matt FiddamanandGitHub d461198334 respect reduced motion settings in the new onboarding screen (#8516)
* respect useReducedMotion

* note

* coderabbit
2026-07-22 14:58:23 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
7325498af7 Bump the npm_and_yarn group across 1 directory with 10 updates (#8541)
Bumps the npm_and_yarn group with 10 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [axios](https://github.com/axios/axios) | `1.16.1` | `1.18.1` |
| [body-parser](https://github.com/expressjs/body-parser) | `1.20.5` | `1.20.6` |
| [brace-expansion](https://github.com/juliangruber/brace-expansion) | `1.1.13` | `1.1.16` |
| [dompurify](https://github.com/cure53/DOMPurify) | `3.4.11` | `3.4.12` |
| [fast-uri](https://github.com/fastify/fast-uri) | `3.1.2` | `3.1.3` |
| [immutable](https://github.com/immutable-js/immutable-js) | `5.1.5` | `5.1.9` |
| [shell-quote](https://github.com/ljharb/shell-quote) | `1.8.4` | `1.10.0` |
| [svgo](https://github.com/svg/svgo) | `3.3.3` | `3.3.4` |
| [webpack-dev-server](https://github.com/webpack/webpack-dev-server) | `5.2.5` | `5.2.6` |
| [websocket-driver](https://github.com/faye/websocket-driver-node) | `0.7.4` | `0.7.5` |



Updates `axios` from 1.16.1 to 1.18.1
- [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.16.1...v1.18.1)

Updates `body-parser` from 1.20.5 to 1.20.6
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/1.20.5...1.20.6)

Updates `brace-expansion` from 1.1.13 to 1.1.16
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v1.1.13...v1.1.16)

Updates `dompurify` from 3.4.11 to 3.4.12
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.4.11...3.4.12)

Updates `fast-uri` from 3.1.2 to 3.1.3
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.1.2...v3.1.3)

Updates `immutable` from 5.1.5 to 5.1.9
- [Release notes](https://github.com/immutable-js/immutable-js/releases)
- [Changelog](https://github.com/immutable-js/immutable-js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/immutable-js/immutable-js/compare/v5.1.5...v5.1.9)

Updates `shell-quote` from 1.8.4 to 1.10.0
- [Changelog](https://github.com/ljharb/shell-quote/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/shell-quote/compare/v1.8.4...v1.10.0)

Updates `svgo` from 3.3.3 to 3.3.4
- [Release notes](https://github.com/svg/svgo/releases)
- [Commits](https://github.com/svg/svgo/compare/v3.3.3...v3.3.4)

Updates `webpack-dev-server` from 5.2.5 to 5.2.6
- [Release notes](https://github.com/webpack/webpack-dev-server/releases)
- [Changelog](https://github.com/webpack/webpack-dev-server/blob/v5.2.6/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-server/compare/v5.2.5...v5.2.6)

Updates `websocket-driver` from 0.7.4 to 0.7.5
- [Changelog](https://github.com/faye/websocket-driver-node/blob/main/CHANGELOG.md)
- [Commits](https://github.com/faye/websocket-driver-node/compare/0.7.4...0.7.5)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.18.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: body-parser
  dependency-version: 1.20.6
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: brace-expansion
  dependency-version: 1.1.16
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: dompurify
  dependency-version: 3.4.12
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: fast-uri
  dependency-version: 3.1.3
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: immutable
  dependency-version: 5.1.9
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: shell-quote
  dependency-version: 1.10.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: svgo
  dependency-version: 3.3.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: webpack-dev-server
  dependency-version: 5.2.6
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: websocket-driver
  dependency-version: 0.7.5
  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>
2026-07-21 21:38:06 +00:00
lelemmGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>Matiss Janis Aboltins
0fd6e42718 Bank Sync Provider per budget file [3/3] - Frontend + loot-core - Pluggy wiring for per budget sync (#8318)
* PR 3 for pluggy frontend and loot-core

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8318

* md

* Code Rabbit code review

* fix  for  code review of PR 02

* Code Review

* Code review + VRT revert + new message when file owner is not admin

* [autofix.ci] apply automated fixes

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8318

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8318

* Removing unwanted permission message for no server or offline

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8318

---------

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>
Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
2026-07-21 21:07:55 +00:00
08e2a10f2d [AI] Promote Age of Money report to a stable feature (#8540)
* [AI] Promote Age of Money report to a stable feature

Remove the experimental ageOfMoneyReport feature flag so the report
is always available, matching the recent Payee Locations promotion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Change release note category to Features

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 20:44:06 +00:00
Matt FiddamanandGitHub 2c3797de2f fix maintainer points counting script PR categorisation (#8535)
* fix count points release note finding

* note

* coderabbit
2026-07-21 20:38:26 +00:00
Matt FiddamanGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
a81408595c tweak testing framework for templates/automations (#8528)
* drive automations UI

* use testing date

* add priority label for testing

* note

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8528

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-21 20:38:12 +00:00
Matt FiddamanGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>Matiss Janis Aboltins
ff1b6045a1 ⬆️ mid month dependency update (#8533)
* @types/node (^22.19.21 → ^22.20.1)

* eslint (^10.5.0 → ^10.7.0)

* eslint-plugin-perfectionist (^5.9.0 → ^5.10.0)

* knip (^6.17.1 → ^6.27.0)

* lage (^2.15.13 → ^2.15.17)

* vitest (^4.1.8 → ^4.1.10)

* better-sqlite3 (^12.10.0 → ^12.11.1)

* vite (^8.0.16 → ^8.1.5)

* react-aria-components (^1.18.0 → ^1.19.0)

* @storybook/addon-a11y (^10.4.4 → ^10.5.2)

* @storybook/addon-docs (^10.4.4 → ^10.5.2)

* @storybook/react-vite (^10.4.4 → ^10.5.2)

* @vitejs/plugin-react (^6.0.2 → ^6.0.3)

* eslint-plugin-storybook (^10.4.4 → ^10.5.2)

* storybook (^10.4.4 → ^10.5.2)

* @bufbuild/protobuf (^2.12.0 → ^2.12.1)

* @bufbuild/protoc-gen-es (^2.12.0 → ^2.12.1)

* @codemirror/language (^6.12.3 → ^6.12.4)

* @uiw/react-codemirror (^4.25.10 → ^4.25.11)

* csv-stringify (^6.7.0 → ^6.8.1)

* es-toolkit (^1.47.1 → ^1.49.0)

* i18next (^26.3.1 → ^26.3.6)

* lru-cache (^11.5.1 → ^11.5.2)

* react-aria (^3.49.0 → ^3.50.0)

* react-hotkeys-hook (^5.3.2 → ^5.3.3)

* react-i18next (^17.0.8 → ^17.0.10)

* rolldown (^1.1.1 → ^1.2.0)

* @docusaurus/core (^3.10.1 → ^3.10.2)

* @docusaurus/plugin-content-docs (^3.10.1 → ^3.10.2)

* @docusaurus/plugin-ideal-image (^3.10.1 → ^3.10.2)

* @docusaurus/preset-classic (^3.10.1 → ^3.10.2)

* @docusaurus/theme-common (^3.10.1 → ^3.10.2)

* @docusaurus/theme-mermaid (^3.10.1 → ^3.10.2)

* unist-util-visit-parents (^6.0.0 → ^6.0.2)

* @docusaurus/module-type-aliases (^3.10.1 → ^3.10.2)

* @docusaurus/types (^3.10.1 → ^3.10.2)

* @oxlint/plugins (^1.69.0 → ^1.74.0)

* @types/estree (^1.0.8 → ^1.0.9)

* fast-check (^4.8.0 → ^4.9.0)

* @capacitor/android (^8.0.0 → ^8.4.2)

* @capacitor/core (^8.0.0 → ^8.4.2)

* @capacitor/ios (^8.0.0 → ^8.4.2)

* @capacitor/splash-screen (^8.0.1 → ^8.0.2)

* @capgo/capacitor-updater (^8.50.1 → ^8.51.2)

* @capacitor/cli (^8.0.0 → ^8.4.2)

* @capgo/cli (^8.22.0 → ^8.28.2)

* express-rate-limit (^8.5.2 → ^8.6.0)

* @types/supertest (^7.2.0 → ^7.2.1)

* http-proxy-middleware (^4.1.1 → ^4.2.0)

* oxlint (^1.69.0 → ^1.74.0) & oxlint-tsgolint (^0.23.0 → ^0.25.0)

* @codemirror/state (^6.6.0 → ^6.7.1) & @codemirror/view (^6.43.1 → ^6.43.6)

* @tanstack/react-query (^5.101.0 → ^5.101.2)

* recharts (^3.8.1 → ^3.9.2)

* note

* oxfmt (^0.44.0 → ^0.59.0)

* @babel/core (^7.29.7 → ^8.0.1)

* downshift (9.3.6 → 9.4.0)

* react-router (7.17.0 → 8.2.0)

* csv-parse (^6.2.1 → ^7.0.1)

* pluggy-sdk (^0.85.3 → ^0.89.0)

* yarn (4.13.0 -> 4.17.1)

* yarn dedupe

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8533

* crdt version bump

* [autofix.ci] apply automated fixes

* Update packageManager version to yarn@4.17.1

---------

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>
Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
2026-07-21 20:24:31 +00:00
d45e07a226 [AI] Count filtered-out on-budget transfers in Age of Money (#8503)
* [AI] Count filtered-out on-budget transfers in Age of Money

When account filters are applied, transfers to/from accounts outside the
filtered set should count as expenses/income so filtering to a cash
account reflects age of cash rather than budget-wide AoM.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [AI] Fix Age of Money lint: template expressions and formatting

Co-authored-by: Cursor <cursoragent@cursor.com>

* [AI] Address CodeRabbit feedback on AoM transfer filters

Treat empty notOneOf as a tautology, narrow array values without
assertions, and clarify the release note covers both transfer directions.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-21 20:08:05 +00:00
5980acd5a4 [AI] Fix updateTransaction crash when converting a transaction to a split (#8467)
* [AI] Fix updateTransaction crash when converting a transaction to a split

* [AI] Clear is_child/parent_id when promoting a transaction to a split parent

* [AI] Type-fix: clear parent_id with undefined, not null (parent_id is an optional string)

---------

Co-authored-by: Hossein Moosavi <h.moosavi@kif-devs.com>
2026-07-21 19:57:54 +00:00
NustinuaGitHubautofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
a29d9d81b5 Skip Enable Banking PSU headers for local IPs (#8032)
* Skip Enable Banking PSU headers for local IPs

* Add release note for Enable Banking PSU headers

* Include IPv4 link-local addresses in PSU header filter

* Use trusted proxy resolved IP for Enable Banking PSU headers

* Avoid logging Enable Banking PSU header values

* Use shared SSRF helper for Enable Banking PSU headers

* Fix space

* Add shared SSRF helper for Enable Banking PSU headers

* Use public IP in Enable Banking PSU header test

* [autofix.ci] apply automated fixes

* Add tests for shared SSRF helper

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-07-21 19:47:58 +00:00
Stephen Brown IIandGitHub d36e564e6c [AI] CI: Support parallel e2e shards locally with per-shard port (#8456)
* [AI] ci: support parallel e2e shards with per-shard port

- Add 'Set E2E port' step to functional and vrt jobs (E2E_PORT = 3000 + shard)
- Use vars.E2E_SHARD_TOTAL (with fallback '3') for shard denominator in job
  names and test commands; pass --var E2E_SHARD_TOTAL=1 to run full suite in
  a single local shard without relaunching
- Add .actrc with depot runner image mappings

* [AI] fix(ci): address review feedback on e2e shard guard/injection
2026-07-21 19:00:18 +00:00
Matt FiddamanandGitHub 87a1d48a3e ⬆️ bump playwright (#8534)
* @playwright/test (1.59.1 → 1.61.1)

* playwright docker updates

* note
2026-07-21 18:03:26 +00:00
youngcwandGitHub 7463525f9d No duplicate labels and types (#8536)
* dont add the bug label

* note

* fix note

* add features

* fix fix note
2026-07-21 17:14:02 +00:00
youngcwandGitHub 83da87bc6e fix relinking a bank account (#8525)
* fix relinking an account

* new files
2026-07-20 23:13:35 +00:00
Michael ClarkandGitHub 5eab568ddd :electron: Fix logon not working after signout when using oidc (#8526)
* cleanup oidc server ref after login

* release notes

* feedback
2026-07-20 19:14:36 +00:00
4c970ada2c [AI] improve the first-account creation flow (#8496)
* [AI] Improve the first-account creation flow

Rewrites the empty-account states on desktop and mobile in plain
language, with a centered layout and a visible Add account button on
mobile. When no sync server is configured, the add-account chooser is
skipped in favor of the account creation form directly, which gains a
name placeholder, an explanation of off-budget accounts, and a pointer
to QIF/OFX/QFX file import.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* coderabbit

* add linebreak

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 16:28:24 +00:00
Matt FiddamanGitHubClaude Fable 5github-actions[bot] <github-actions[bot]@users.noreply.github.com>
e029793b68 add bank sync provider setup to the mobile Bank Sync page (#8497)
* [AI] Add bank sync provider setup to the mobile Bank Sync page

The mobile Bank Sync page previously only listed existing accounts,
with no way to configure a provider or link a new bank account, and
its empty state wrongly claimed an account must be created first
(linking creates accounts). It now renders the same provider section
as desktop, corrects the empty-state guidance on both platforms, and
hides the account filter when there are no accounts.

VRT snapshots for the mobile bank sync test were deleted and need
regenerating with yarn vrt:docker.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8497

* collapsible provider

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8497

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-20 16:27:57 +00:00
Matt FiddamanandGitHub 94f9391215 add month picker component to automations UI (#8515)
* add month picker component

* add automation MonthPicker

* wire it up!

* note

* move month input out to utils
2026-07-19 22:36:30 +00:00
Matiss Janis AboltinsGitHubClaudegithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
188dfbe663 [AI] Promote Payee Locations to a stable feature (#8511)
* [AI] Promote Payee Locations to a stable feature

Remove the experimental payeeLocations feature flag and make payee
locations always available on the mobile transaction screen. Move the
documentation out of the experimental section into the transactions area.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C3sk6D2TPLFBBaE17VptxJ

* [AI] Shorten payee locations release note

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C3sk6D2TPLFBBaE17VptxJ

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8511

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-19 20:35:23 +00:00
7cfd55cc20 [AI] Add tooltip with status icon legend to cleared column header (#8470)
* [AI] Add tooltip with status icon legend to cleared column header

Hovering the ✓ header in the transactions table now shows a tooltip
explaining the column, with a legend of each status icon that can
appear there (uncleared, cleared, reconciled, and the upcoming/due/
missed scheduled-transaction states).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Broaden cleared/uncleared tooltip wording beyond bank sync

Reviewer feedback: cleared status doesn't require bank sync — it can be
a manual "I've verified this transaction" mark. Reword the legend to
avoid implying the bank performs the confirmation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 20:09:01 +00:00
a31cfe7514 fix electron GoCardless linking (#8510)
* fix electron invalid origin

* [AI] Add tests for the desktop GoCardless linking fix

Covers the electron fetch wrapper's derived Origin header and the
sync-server /gocardless/create-web-token origin and id validation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TKqNWc6Lbigv8e61uLoopU

* note

* allow old origin and move link so that it exists on electron bundled server

* provide a valid redirect URL to GoCardless

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 07:52:35 +00:00
Michael ClarkandGitHub 05c8493d80 🐛 Fix OIDC login callback on ipad (#8505)
* fix oidc login callback on ipad

* no need to run on electron

* add dep to useeffect

* fix race

* reverting a bit
2026-07-18 20:22:35 +00:00
tabedzkiGitHubrznn7rznn7github-actions[bot] <github-actions[bot]@users.noreply.github.com>github-actions[bot] <matiss@mja.lv>Claude Fable 5
fe363a2643 [AI]: Replacing month picker with day picker (#8427)
* [AI] feat(reports): add browser-independent month range picker

Replace the report date dropdowns with a popover month range picker built
from React and Actual's own components instead of a native month input
(which does not work in Firefox). Includes start/end month grids,
quick-select presets folded into the popover, a draft selection that only
recomputes the report on close, today highlighting, and an unbounded future
range.

Assisted-by: ClaudeCode:claude-opus-4.8
Co-authored-by: rznn7 <160644362+rznn7@users.noreply.github.com>

* [AI] refactor(desktop-client): replace pikaday with react-aria in DateSelect

Swap the Pikaday-based day picker in DateSelect for react-aria-components'
Calendar primitives (backed by @internationalized/date). The user's
firstDayOfWeekIdx preference is encoded via the Unicode 'fw' locale
extension, and the prev/next arrows now use the existing SvgCheveron
icons instead of the two bundled PNGs.

Removes the pikaday and @types/pikaday dependencies, deletes the unused
DateSelect.left/right.png assets, and adds @internationalized/date as a
direct dependency.

Assisted-by: ClaudeCode:claude-opus-4.8

* [AI] fix(desktop-client): resolve CI failures in month picker PR

Remove four devDependencies (@juggle/resize-observer, @types/lodash, @types/promise-retry, memoize-one) that were added but never used, fixing the knip check. Dedupe the split @codemirror/state and @codemirror/view resolutions that the added/removed deps introduced, fixing typecheck. Pin shell-quote (a transitive dependency of launch-editor/npm-run-all) to >=1.8.4 via resolutions, matching the existing socks/serialize-javascript overrides, to clear the critical CVE flagged by Dependency Review.

Assisted-by: ClaudeCode:claude-sonnet-5

* updated yarn.lock

* Updated yarn.lock to address dependency issue

* address more dependency issues

* `yarn up -R '**'` to hopefully address all dependency issues

* fixed donutgraph.tsx typing

* [AI] feat(reports): add Month/Day granularity toggle to range picker

Add a Month/Day toggle above the quick-select column in the report range
picker so users can pick exact days, not just whole months. Switching to Day
expands the range to cover whole days (start -> first of month, end -> last of
month); switching back collapses to months. In day mode the picker emits
yyyy-MM-dd start/end.

Make the month-based reports tolerate day-level values: month range helpers in
monthUtils already coerce day strings to months, and this replaces the few
fragile 'start + "-01"' concatenations (BalanceForecast, BalanceForecastCard,
BudgetAnalysis, balanceForecastChartData) with firstDayOfMonth/getMonth so a
yyyy-MM-dd value is normalized instead of corrupted.

Assisted-by: ClaudeCode:claude-opus-4.8

* [AI] feat(reports): honor day granularity, live end-offset, format-aware picker

Task 3: derive the range picker trigger's display format from the committed
value's shape (yyyy-MM-dd vs yyyy-MM) via a new valueIsDay() helper instead of
the granularity flag, so the label stays correct (day vs month) after the
popover closes.

Task 1: let a live/sliding range end before the current month.
getLatestRange(offset, endOffset=0) and calculateTimeRange's sliding-window
branch derives endOffset from the saved frame (clamped >= 0) so the window's
gap-from-now is preserved as months roll over. Surface it with an 'Exclude
current month' checkbox in the picker popover (shifts the whole range back one
month, keeping its width), enabled for past ranges only.

Task 2: add a maintainer-facing granularities prop on MonthRangePicker (threaded
through Header), defaulting to ['month','day']. Pass ['month'] to hide the Day
toggle on month-only reports; Budget Analysis is locked to month. AgeOfMoney's
granularity stays a distinct concept.

Add unit tests for getLatestRange/calculateTimeRange endOffset and the picker
util helpers. Use named imports throughout.

Assisted-by: ClaudeCode:claude-opus-4.8
Co-authored-by: rznn7 <rznn7@users.noreply.github.com>

* [AI] feat(reports): single-calendar range picker, fix granularity reset on remount

Replaces the two side-by-side Start/End calendar grids in the reports date-range picker with a single calendar (RangeSelector) supporting click-to-pick range selection with a live hover preview, for both Month and Day granularity.

Also fixes the Month/Day toggle silently reverting after a pick: CashFlow, NetWorth, AgeOfMoney, and BalanceForecast unmount their Header/MonthRangePicker subtree on every date change (useReport nulls out data during refetch), which wiped the picker's internal granularity state. Lifted that state into each report so it survives the remount.

Assisted-by: ClaudeCode:claude-sonnet-5

* [AI] fix(reports): persist sliding-window endOffset so live ranges actually slide forward

calculateTimeRange's sliding-window branch re-derived the gap between a saved end date and 'now' on every call, using the stale, decaying persisted end. Since most reports only rewrite that persisted end when the user explicitly saves the widget, the derived gap grew by however much time had passed since the last save — meaning a live range never actually advanced once its gap was nonzero; it stayed pinned to the last-saved dates indefinitely. Caught by useFormulaExecution.test.ts's 'shifts each formula report query window when the current month changes' test, which was failing on this branch.

Adds TimeFrame.endOffset, persisted explicitly alongside start/end/mode instead of re-derived from decaying absolute dates. getLatestRange and calculateTimeRange now return the resolved endOffset as a 4th tuple element; MonthRangePicker computes it at commit time and threads it through Header's onChangeDates into every report that persists a timeFrame (CashFlow, NetWorth, AgeOfMoney, BalanceForecast, Calendar, Sankey, Crossover, Summary, BudgetAnalysis). Legacy saved widgets without endOffset fall back to the old derivation once, as a migration.

Assisted-by: ClaudeCode:claude-sonnet-5

* [AI] fix(reports): route day-grid clicks through pick directly

DayGrid's per-day onSelect was wired to selectMonth, a handler meant for
month-grid clicks and month-navigation day-carry-over. Fed a full day
string it built a malformed candidate (e.g. 2026-07-20-10) that never
string-matched the clicked day, so the range endpoint lost its selected
(primary) styling and only showed as part of the in-range band.

Assisted-by: ClaudeCode:claude-sonnet-5

* [AI] fix(reports): keep day-mode range width when reopening picker

openPopover unconditionally expanded start/end to the full month via
toDayStart/toDayEnd whenever reopening in day mode, even when the range
was already day-shaped from a prior precise pick. Only expand when the
incoming value is still month-shaped (an actual month-to-day switch).

Assisted-by: ClaudeCode:claude-sonnet-5

* [AI] fix(reports): address code review findings on month/day range picker

Fix clamp() returning granularity-mismatched bounds when min/max are month-shaped but the picker is in day mode. Add accessible labels to NavRow's prev/next icon buttons. Deduplicate the Pie shape index coercion in DonutGraph into a shared helper. Drop the unnecessary useMemo around MonthRangePicker's label string.

Skipped: the Header.tsx allMonths[allMonths.length - 1] access can't crash since every caller already guards on allMonths being non-empty before rendering Header. The RangeEndSelector.tsx dead-code finding refers to a file that no longer exists in this codebase. The DateSelect.tsx firstDayOfWeek suggestion doesn't apply: react-aria-components' public CalendarGrid props don't expose firstDayOfWeek in the installed version, so the locale '-u-fw-' extension is still the only way to configure it.

Assisted-by: ClaudeCode:claude-sonnet-5

* [AI] fix(reports): stop widening day-shaped bounds in balance forecast chart data

buildBalanceForecastChartData's Daily branch applied firstDayOfMonth/lastDayOfMonth to start/end unconditionally, which expanded an already day-shaped range (e.g. 2024-03-15) out to its whole month. Only expand when the bound is month-shaped (isValidYearMonth), otherwise use it as-is.

Assisted-by: ClaudeCode:claude-sonnet-5

* [AI] fix(reports): address CodeRabbit findings on month/day range picker (round 2)

Header: fall back minDate to the current month when allMonths is still empty (Summary/Calendar render before their async load populates it). RangeSelector/DayGrid: normalize min/max to day granularity in DayGrid so a month-shaped bound doesn't disable every day in its own month. AgeOfMoney: derive the picker's initial Month/Day mode from the restored range's shape instead of hardcoding month, since that mode isn't persisted separately from timeFrame. BudgetAnalysis: normalize start/end to yyyy-MM inside calculateIsConcise so a legacy day-shaped persisted timeFrame can't corrupt the date-fns parse. DonutGraph: read props.index directly instead of coercing a second shape-callback argument. MonthRangePicker: use && short-circuit JSX per the style guide. AgeOfMoney: drop an unnecessary useCallback with no deps, matching sibling reports. DayGrid/RangeSelector/MonthRangePicker/Header: thread firstDayOfWeekIdx through so the day grid's weekday header and alignment follow the user's configured first day of week instead of hardcoding Sunday-first.

Skipped: the Crossover.tsx 'double shift' finding — the extra -1 month shift is pre-existing, documented, intentional business logic (excludes the current month), applied symmetrically on both load and onChangeDates regardless of whether endOffset was legacy-derived or explicitly persisted; the suggested fix wouldn't touch that code path anyway.

Assisted-by: ClaudeCode:claude-sonnet-5

* Update category and authors in release notes

Address technical debt by introducing a day picker for the calendar.

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8427

* [AI] Address review feedback on month/day range picker and DateSelect

- Crossover: treat legacy widgets' missing endOffset as 0 (no double shift)
- Only offer "Exclude current month" in month mode for ranges ending at the
  current or previous month
- Cap past-oriented reports at the newest month; future stays for forecasts
- Don't commit on Month/Day switch so the popover survives report reloads
- Month-only picker for Calendar, Summary, Sankey and Crossover
- Transaction date picker: visible month-nav arrows that keep focus in the
  input, normal-sized heading, square day cells, tighter popover
- Give unselected grid cells a transparent border so selecting doesn't
  resize the grid
- Derive picker granularity from the committed value shape and drop the
  lifted per-report state and Header props
- Simplify per review: reuse ModeButton/monthUtils/Calendar firstDayOfWeek,
  remove allowFuture, add useLanguage, fix double render on keystroke,
  strip prose comments
- Restore master's yarn.lock (only the pikaday removal remains), drop the
  shell-quote resolution, revert unrelated DonutGraph change
- Release note: describe the feature, add MatissJanis to authors

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8427

* [AI] Cap day-mode picking at today and clamp exclude-toggle shifts

- A month-shaped max that reaches the current month no longer widens to the
  month's last day in day mode; day cells after today are disabled and the
  Month->Day draft widening clamps its end to today
- DayGrid and clamp() keep already day-shaped bounds exact instead of
  widening them back to whole months
- toggleExcludeCurrentMonth clamps the shifted range to minDate/maxDate like
  every other draft mutation
- granFor: function declaration per code style

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Use one SpaceBetween for consistent report header spacing

The header row was two SpaceBetween groups (gap 15 and gap 3) with no gap
between them, and reports wrapped their own controls in ad-hoc gap-10 Views.
Merge the mode toggle, date picker, filter button and report controls into a
single SpaceBetween, render header children through one too, and drop the
per-report wrappers (Balance Forecast, Cash Flow, Budget Analysis, Sankey).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Drop endOffset; sliding windows always anchor to the current month

Remove the persisted TimeFrame.endOffset and its wiring through
reportRanges, the range picker, Header and all report pages. A live
(sliding-window) range now always ends at the current month; "Exclude
current month" still shifts the draft range and commits it as static.

Also address code review findings: guard the "All time" preset while
allMonths is still loading, remount the range selector on granularity
switch so a month-shaped click-anchor can't produce a mixed-shape range,
only offer the exclude toggle when both of its states fit the configured
bounds, and give the month/day grid cells full localized accessible
names and a pressed state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8427

* [AI] Remove the "Exclude current month" checkbox

Its only unique capability was creating a live range ending last month via
endOffset, which is gone; picking the end month in the grid now does the
same thing. Deletes the toggle component, the allowExcludeCurrentMonth
prop and Header wiring, and the now-unused shiftMonths util.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Simplify month-range picker after cleanup review

Drop the util.ts aliases of shared months.ts helpers, derive picker
granularity from the draft value's shape instead of separate state,
normalize min/max bounds once in MonthRangePicker, inline the Header
preset builders, and make month-only the default granularity with
day-capable reports opting in explicitly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Rebuild day-mode picking on react-aria RangeCalendar

Replace the hand-rolled DayGrid with react-aria's RangeCalendar, which
adds keyboard navigation and built-in hover range preview, and use
month/year dropdowns (CalendarMonthPicker plus a bounds-based year
select) in the calendar header for quick jumping. The month grid stays
custom since react-aria has no month-granularity range picker.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Address review feedback and fix picker styling glitches

Validate the full yyyy-MM-dd shape in valueIsDay instead of negating
the year-month check, cap the year dropdown when the report has no
upper date bound (the far-future sentinel would render ~8000 options),
use the bare-button hover color so hovered dates and month/year
selects stay readable, and stretch the day grid to the calendar's
full width.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Simplify month-range picker after cleanup review

- YearSelect reads bounds from RangeCalendarStateContext instead of props
- Dedupe first-day-of-week pref decoding into getFirstDayOfWeek in DateSelect
- Hoist the day-calendar emotion css() call to module scope
- RangeSelector tracks viewYear directly instead of a month it only read the year from
- Inline the single-consumer Grid component into MonthGrid

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Let live report ranges slide by days for day-shaped windows

Day-shaped sliding-window ranges now keep their width in days and
re-anchor to today, so Live works with day granularity in reports.
Formula queries stay month-granular via asMonthSlidingTimeFrame.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8427

* [AI] Move the date range picker into the component library

Extract the reports MonthRangePicker into @actual-app/components as
DateRangePicker: translated strings arrive via a labels prop, date
formatting runs on Intl.DateTimeFormat with a BCP 47 locale string
instead of loot-core's date-fns wrappers, and the first-day-of-week
pref is resolved by the caller. Header.tsx supplies all of these; the
component library gains @internationalized/date and subpath import
mappings for the picker's internals.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Add VRT for the reports date range picker

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Add VRT snapshots for the reports date range picker

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Clean up the date range picker after review

- Move ModeButton into the component library and reuse it for the
  granularity toggle instead of a near-identical SegmentButton copy
- Move the day-shaped value check into loot-core as isValidYearMonthDay
  so reportRanges no longer imports it through the picker component
- Cache Intl.DateTimeFormat instances in formatDate
- Build lastDayOfMonth/currentDay on @internationalized/date
- Pass label subsets as one labels object instead of scalar props

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8427

* [AI] Revert "Update VRT screenshots"

This reverts commit 8cd01e5148.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Hoist DateRangePicker to src root, add ModeButton story, respect app date format

- Move DateRangePicker.tsx and its stories out of the date-range-picker/
  folder so the story sorts alphabetically in the Storybook sidenav; only
  child components remain in the folder.
- Add a Storybook file for ModeButton.
- Add an optional formatDayLabel prop so day-shaped trigger labels can
  follow the app's date format preference; reports Header now passes the
  dateFormat synced pref instead of falling back to locale-numeric dates.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Address review feedback on the date range picker

- Bound scheduled-occurrence counts to the visible chart range so day-mode
  ranges don't count occurrences outside the selected days
- Carry the running balance forward from points before a day-shaped start
  instead of starting the daily chart at zero
- Drop the day granularity from the tracking-budget forecast header, which
  only resolves monthly
- Validate the day against the month and year (incl. leap years) in
  isValidYearMonthDay
- Target the date range picker trigger by test id in the reports e2e test
- Drop a redundant useMemo in useLocale (React Compiler memoizes)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Keep the date range picker open when a quick-select preset is clicked

Presets now preview their range in the open picker draft, like manual
selection, and commit on close so live-range modes are preserved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Improve date range picker colors and highlight the active preset

Style the calendar header dropdown options so they stay readable in dark
themes, introduce a datePickerRangeBackground theme variable so the light
theme's range band is purple like the other themes, and derive the active
quick-select preset from the current range so it stays highlighted when
reopening.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8427

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8427

---------

Co-authored-by: rznn7 <160644362+rznn7@users.noreply.github.com>
Co-authored-by: rznn7 <rznn7@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <matiss@mja.lv>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 16:49:07 +00:00
Matt FiddamanGitHubClaude Fable 5github-actions[bot] <github-actions[bot]@users.noreply.github.com>
e18c8addde [AI] redesign onboarding (#8495)
* [AI] Redesign first-run onboarding for web/PWA users

Fresh web/PWA installs whose origin does not serve a sync server now
start in local-only mode on a redesigned welcome screen instead of the
"Where's the server?" page, with syncing offered as an option. The
server connection page is retitled and redesigned with a back button
and friendlier copy. Transient probe failures (server unreachable)
keep the previous behavior and persist nothing, so a briefly-down
self-hosted server is retried on the next load. Electron keeps its
existing server-config first run, and origins that serve a sync
server still go to bootstrap/login.

VRT snapshots for the onboarding test were deleted and need
regenerating with yarn vrt:docker.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8495

* coderabbit

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-17 08:09:13 +00:00
134e11b705 [AI] Allow null in SQL query parameter types via shared SqlParam type (#8493)
SQLite accepts null as a bound parameter (verifyParamTypes already
allowed it at runtime), but the runQuery/all/first/firstSync/run
signatures only declared string | number, forcing callers to cast.
Extract a shared SqlParam type (string | number | null) from the
sqlite platform modules and use it everywhere. Adds a test covering
null params through all/first.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 07:24:57 +00:00
Abdullah KhawajaandGitHub 79341b3be1 [AI] Fix budget template notification translations (#8478)
* [AI] Fix budget template notification translations

* [AI] Address PR review feedback

* [AI] Preserve cleanup error notifications

* [AI] Use nested cleanup translation keys
2026-07-17 00:56:51 +00:00
distantvaporGitHubcoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
9efaaf8a58 Added new theme "4Rules Blurple - Dark" and renamed existing theme "You Need A Dark Mode" -> "4Rules Cyan - Dark" (#8436)
* Add files via upload

* Update theme entries in customThemeCatalog.json

* Update upcoming-release-notes/add-4rules-blurple-dark-theme.md

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-07-17 00:49:25 +00:00
dcff910de6 [AI] api: support custom dataDir (#8397)
* [AI] Support custom dataDir in API browser build and stop masking worker errors

Two fixes for the browser build of @actual-app/api:

1. A dataDir other than /documents failed with a cryptic DataCloneError:
   the directory didn't exist in the worker's virtual filesystem, and the
   resulting Emscripten ErrnoError (which carries a function-valued
   property) couldn't be structured-cloned, so postMessage's own
   DataCloneError replaced the real error entirely.

   The web worker connection now retries a failed postMessage with a
   serialized, cloneable copy of the message so the client always receives
   the actual failure. Cloneable errors pass through unchanged.

2. The web filesystem registers the configured document dir as a persisted
   root: it is created automatically on init, its files are persisted to
   IndexedDB (sqlite files symlinked into the blocked fs), and files
   recorded in IndexedDB are restored at boot regardless of prefix. The
   API browser worker defaults dataDir to /documents when omitted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Trim trailing slashes without a regex

CodeQL flags the anchored /\/+$/ replace as quadratic on strings with
many repeated slashes; walk the index back instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Address review: single persisted root, guarded fallback post, plain release note

- Replace the accumulating persistedRoots set with the default /documents
  plus a single replaceable custom root, so switching document dirs no
  longer leaves stale roots marked as persisted
- Guard safePost's final fallback post so a broken channel can't escape
  as an unhandled rejection
- Reword the release note in plain user-facing language

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] loot-core: route server push messages through safePost

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 21:44:20 +00:00
Stephen Brown IIandGitHub ffa72fbb9e [AI] fix(loot-core): replace adm-zip with fflate for zip handling (#8393)
* [AI] fix(loot-core): replace adm-zip with fflate for zip handling

Replaces the adm-zip dependency (which requires Node's `process`/`Buffer`
and can break in browser bundle contexts) with fflate, a zero-dependency,
isomorphic zip library that works identically in node and browser builds.

Adds a small `zip-utils.ts` wrapper (`safeZip`/`safeUnzip`) around fflate
to preserve the safety guarantees a hardened zip library provides by
default, since all three call sites parse zips that may cross a trust
boundary (cloud sync downloads, user-supplied budget/import files):

- Rejects zip-slip/path-traversal entry names (absolute paths, `..`
  segments, backslashes, drive letters, NUL bytes).
- Caps total archive size and per-entry uncompressed size, enforced via
  fflate's `filter` callback before it allocates the decompression buffer
  for each entry, to guard against decompression bombs.
- Rejects duplicate/case-variant entry names to avoid ambiguity attacks.

Updated call sites: budgetfiles/backups.ts (local backup zip write/read),
cloud-storage.ts (cloud sync export/import), importers/ynab4.ts (YNAB4
budget import).

* [AI] test(desktop-client): drop export/import perf e2e test

Removes packages/desktop-client/e2e/export-import-performance.test.ts,
added in an earlier commit to benchmark adm-zip vs fflate. Per review
feedback (matt-fidd): this zip-handling logic changes rarely, and a
future library swap would warrant its own fresh benchmarking rather
than maintaining a ~300 line permanent regression test for it.

The benchmark numbers already recorded in the PR description remain
accurate for this change.

* [AI] fix(loot-core): distinguish oversized-zip errors from generic parse failures

cloud-storage.ts's importBuffer (shared by both cloud-sync download and the
local "Import my budget -> Actual" file picker) caught every safeUnzip
failure the same way and threw FileDownloadError('not-zip-file'). Since
safeZip has no size limit but safeUnzip enforces one by default, a
self-generated export that grows past that cap (e.g. a large db.sqlite)
would fail to re-import with a misleading "this isn't a zip file" message,
even though the file is perfectly valid, just larger than the cap.

Catches UnsafeZipError separately and surfaces a new 'zip-too-large' reason,
with matching UI messages in both places that already handle
not-zip-file/invalid-zip-file/invalid-meta-file (loot-core's shared error
mapper and desktop-client's local import modal), reusing the same support-
link phrasing already used for invalid files.

* [AI] fix(loot-core): stop faking runtime config for zip upload size limit

Vite bakes process.env.ACTUAL_UPLOAD_FILE_SIZE_LIMIT_MB into the client
bundle at build time, so it was never actually runtime-configurable in
the browser build despite looking like sync-server's real env var.
Drop the env indirection and hardcode a fixed 20MB entry cap / 60MB
archive cap instead.

* [AI] fix(loot-core): tighten zip entry-name matching in importBuffer

* [AI] feat(loot-core): raise zip size limits to 500MB, warn on oversized/low-memory exports

Matt-fidd's review on #8393 argued 20MB was too tight for real budgets and
asked for (1) a more generous cap, (2) a warning when a backup is created
that won't be re-importable, and (3) test coverage for zip.ts's safety
checks (path traversal, size limits, duplicate entries, unsafe names,
round-trip). Later review passes asked to trim the verbose comments in
zip.ts and to use <Trans> instead of t() for the new warning strings.

- Collapse the three separate default caps into one MAX_ZIP_SIZE (500MB),
  since archive size, per-entry size, and total uncompressed size all
  shared the same value anyway. safeUnzip can run outside the browser
  (Electron, or Node via @actual-app/api) where the whole archive is held
  in memory at once, and some self-hosting providers (PikaPods) run
  containers with as little as 256MB RAM, so the cap stays flat rather
  than compounding into a multi-GB worst case.
- Add exceedsSafeUnzipLimits() to zip.ts so exportBuffer() can warn
  upfront, using the same cap import enforces, instead of drifting.
- Add #platform/server/memory (default/api/electron) exposing
  os.freemem() on node/electron and null in the browser, per the existing
  platform-conditional-export pattern. exportBuffer() warns if the
  uncompressed db.sqlite is bigger than currently-free memory; skipped
  entirely when the platform has no such API.
- Surface both warnings in the Export settings screen via <Trans>.
- Add zip.test.ts covering round trip, path traversal, unsafe names,
  duplicate entries, and all three size caps.
- Trim zip.ts's file-header comment per review feedback that it was too
  verbose to be useful.

* [AI] feat: surface descriptive, translatable errors for unsafe zip imports

UnsafeZipError now carries structured meta (zipReason + entryName/maxSize)
instead of only a free-text English message. The meta rides on
FileDownloadError('zip-too-large', ...) through the download and import-budget
paths, and the desktop-client maps it to detailed, translated messages
(which file, which limit) in getUnsafeZipError(); loot-core's shared
getDownloadError() gets a plain-English equivalent for the headless API.

Why structured meta rather than translating the message itself: i18n must not
live in loot-core (see platform/client/connection/index.ts) - the backend
worker has no i18next instance and doesn't know the user's locale, and a
message translated at throw time would bake a locale into a string that also
feeds the headless @actual-app/api and server logs, which want stable English.
i18next also needs a static key plus interpolation params to extract and look
up translations, so the error must cross the worker boundary as code + params
(zipReason + entryName/maxSize) and be rendered with t() at display time in
the client. The English Error.message is kept for logs and the API.

* Update release note to include safe wrappers

* [AI] fix(loot-core): require db.sqlite and metadata.json from the same archive directory

importBuffer previously located db.sqlite and metadata.json independently,
so an archive with a/db.sqlite and b/metadata.json would pair unrelated
files, and duplicate matches were resolved by archive order. Now prefer the
root-level pair; otherwise both files must come from exactly one shared
directory, and anything ambiguous is rejected as invalid-zip-file.

* [AI] fix(loot-core): restrict safeZip to flat entries so every name is validated

safeZip only ran assertSafeEntryName on top-level keys, but fflate's
Zippable type permits nested directory objects whose child names would
reach zipSync unvalidated. Both callers pass flat records, so narrow the
parameter to Record<string, Uint8Array> and let the compiler rule out
nested input.
2026-07-16 21:13:18 +00:00
2948fd174e [AI] Refresh running balances after transaction edits (#8490)
Co-authored-by: tianrking <tianrking@users.noreply.github.com>
2026-07-16 21:08:06 +00:00
501bece3a1 [AI] migrations: enforce append-only migration files in CI (#8446)
* [AI] Enforce append-only migration files in CI

Shipped migrations are append-only: existing installs never re-run
them, so editing one silently forks the database schema across the user
base, and deleting one breaks the migration check for every existing
budget file. The CI migration check now diffs blob hashes against the
merge base to reject edits/deletions, keeps the existing
timestamp-ordering rule, emits advisory PR annotations for migrations
containing DROP/RENAME statements, and fails loudly when git commands
fail instead of passing on empty output.

First of a series that makes clients on different app versions sync
safely against the same budget file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Derive the newest upstream migration id with an explicit max

The ls-tree listing is alphabetical, so taking the last entry only
worked while every migration id has the same digit count.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Post advisory migration warnings as a PR comment

The risky-SQL warnings were only visible as job annotations, and since
the job passes there was no prompt to look at them. The migrations job
now uploads its findings as an artifact, and a new workflow_run workflow
(trusted base-repo context, so it also works for fork PRs whose tokens
are read-only) validates the artifact against a fixed allowlist and
posts/updates a PR comment — removing it again once the warnings are
resolved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Align overview numbering with the inline check comments

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 06:08:46 +00:00
a862d463d7 [AI] api: attach stable error codes that survive the worker boundary (#8437)
* [AI] Attach stable error codes that survive the worker boundary

Rejections from the backend worker reached consumers as an English
message only: the machine-readable reason was stripped when the error
was serialized for postMessage, so API consumers had to regex the prose
to categorize failures.

- Serialize rejected handler errors through a shared helper that lifts
  the reason slug onto a `code` field (plus `name`) on the envelope, so
  it survives the worker/utility-process boundary. `message` is
  unchanged, so the change is purely additive.
- Tag the common connect failures with codes at their throw sites:
  init auth failures (network-failure, invalid-password, token-expired)
  and api/download-budget, api/load-budget, api/sync, api/bank-sync
  (budget-not-found, missing-key, decrypt-failure, ...).
- Harden the error post: if the original error holds a non-cloneable
  value (e.g. an Emscripten ErrnoError carrying methods), postMessage
  used to throw a DataCloneError in the worker and that clone failure
  replaced the real error. Retry with a plain cloneable shape instead.
- Add an integration test driving the real web client/server connection
  pair across a structured-clone channel, document the codes in the API
  reference, and add a release note.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AG6f77cRxhwPFjsNpQRmYo

* [AI] Remove worker-boundary connection integration test

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AG6f77cRxhwPFjsNpQRmYo

* [AI] Remove prose comment from connection error helpers

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AG6f77cRxhwPFjsNpQRmYo

* [AI] Simplify worker-boundary error posting

- Hoist the three-way reply-envelope dispatch out of the two platform
  connection files into a single postErrorReply helper, so the envelope
  rules and the DataCloneError fallback live in one place.
- Return APIError envelopes from coerceError unchanged, and drop the
  unreachable fallbacks and unused meta field from the cloneable shape.
- Let withErrorCode accept an undefined code, removing the one-off
  ternary at the bank-sync throw site.
- Declare the client-side connection error shape once as
  ConnectionError instead of twice inline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AG6f77cRxhwPFjsNpQRmYo

* [AI] Address review feedback on error-code contract wording and types

- Docs and release note: scope the stable-code promise to the common
  documented failures, and say errors "usually" carry a message.
- ConnectionError: model APIError (message required) and ServerError
  (message may be absent) as separate variants to match the wire shape,
  and adapt the sendCatch consumers in SavedFilterMenuButton.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AG6f77cRxhwPFjsNpQRmYo

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-16 06:08:18 +00:00
Matt FiddamanGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
566ee38290 🔒 reconciliation on mobile (#8384)
* extract shared logic

* entry point for mobile reconciliation

* mobile reconciliation

* note

* e2e tests

* generalise tests more

* allow reconciling closed accounts

* try to stop VRT updating

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8384

* drop line break in reconciling banner

* move reconciled/cleared icon to right side and shrink

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8384

* fix icon spacing

* fix layout shift

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8384

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-15 20:55:53 +00:00
56ee4dfd8b [AI] Add running-vrts skill for visual regression testing (#8477)
* [AI] Add running-vrts skill for visual regression testing

Documents how VRTs work in this repo (toMatchThemeScreenshots matcher,
three-theme snapshots, data-vrt-mask, strict diff tolerance), the
docker-based snapshot generation workflow, and the /update-vrt CI
fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AZAw7AR6LMHTGss1BtnBDT

* [AI] Add codex skill symlink for running-vrts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AZAw7AR6LMHTGss1BtnBDT

* [AI] Add release note for running-vrts skill

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AZAw7AR6LMHTGss1BtnBDT

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-15 18:01:51 +00:00
tabedzkiandGitHub 1dbe1b78a1 [AI] Add tooltips to titlebar theme, privacy, and server status icons (#8440)
* [AI] Add tooltips to titlebar theme, privacy, and server status icons

Explain what the theme switcher, amount-visibility toggle, and server sync/status indicators do on hover, including what having a server connected means when none is configured yet.

Assisted-by: ClaudeCode:claude-sonnet-5

* [AI] Merge duplicate server status branching in LoggedInUser

Combine serverMessage() and serverTooltip() into a single getServerStatus() resolver so the button label and tooltip stay in sync as server states change.

Assisted-by: ClaudeCode:claude-sonnet-5

* Add in requested line break
2026-07-15 13:53:16 +00:00
Matt FiddamanandGitHub 13e2b44c02 fix split transaction error popover from being shrunk vertically (#8479)
* prevent TransactionError from having max-height:0 applied

* note
2026-07-15 01:02:25 +00:00
0c96d7701b [AI] Enable React Compiler for all monorepo packages with React code (#8463)
* [AI] Enable React Compiler for all monorepo packages with React code

The compiler was previously scoped to desktop-client/src only. Now:
- desktop-client's Vite build also compiles component-library source
  (consumed as raw source through the same build), covering web,
  desktop, and mobile bundles
- component-library's Storybook and Vitest pipelines apply the compiler
  so they exercise the same compiled output
- docs (Docusaurus) enables babel-plugin-react-compiler for the site's
  own React components

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdcKq244khwKhedvLCwu7p

* [AI] Declare npm-run-all in component-library so its test script resolves

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdcKq244khwKhedvLCwu7p

* [AI] Generalize React Compiler include to any workspace package source

Match packages/<name>/src instead of enumerating package names, so a
future workspace library with React components is compiled automatically.
Third-party code stays out via the babel plugin's default node_modules
exclude, which applies alongside a custom include.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdcKq244khwKhedvLCwu7p

* [AI] Inline component-library test script instead of using npm-run-all

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdcKq244khwKhedvLCwu7p

* [AI] Drop React Compiler from docs

The docs site is static content with a handful of simple components, so
auto-memoization buys nothing there, and an unscoped babel plugin would
also process Docusaurus theme code and every MDX page. Reverting keeps
the compiler where it pays off: desktop-client and component-library.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdcKq244khwKhedvLCwu7p

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-14 07:15:20 +00:00
ca5ffef067 [AI] Run agent hooks and skills in Codex-based harnesses (ChatGPT desktop app) (#8462)
* [AI] Run agent hooks and skills in Codex-based harnesses (ChatGPT desktop app)

The ChatGPT desktop app (and Codex CLI/IDE) never picked up the repo's
agent skills or lifecycle hooks:

- Skills: Codex surfaces scan .agents/skills, not .claude/skills. Add a
  symlink mirror so both harness families load the same skill files.
- Hooks: Codex lifecycle hooks are feature-flagged; enable them in the
  project .codex/config.toml and document the trust steps (project
  .codex layer trust + one-time /hooks trust) required for them to run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMdDFdaErx9dqieTGh8ETg

* [AI] Drop .agents/skills/README.md; document the mirror in AGENTS.md instead

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMdDFdaErx9dqieTGh8ETg

* [AI] Ignore .agents/** in knip like .claude/**

The .agents/skills symlink mirror exposes skill reference files under a
path knip scanned, flagging highlight-element.js as an unused file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMdDFdaErx9dqieTGh8ETg

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-14 07:14:41 +00:00
Matiss Janis AboltinsandGitHub b3748776ce [AI] Remove grouped option from api/categories-get (#8415)
The categories-get API now always returns a flat list of categories.
Use category-groups-get for grouped data instead.
2026-07-14 07:14:23 +00:00
lelemmandGitHub ed57d9b4ed 🐛 Theme fetch cache bursting (#8448)
* cache bursting

* retrigger actions
2026-07-13 21:01:09 +00:00
Martin LukasandGitHub 1a4f810ecd Disable the shared worker on Android (same as is done for iOS already) (#8457)
* Revert "Reload when PWA shared worker is killed by Android (#8322)"

This reverts commit 62ced9aa2b.

* Disable shared worker in PWA on Android
2026-07-13 20:49:23 +00:00
Ehtasham YasinGitHubautofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>Matiss Janis Aboltins
0146471d0e chore(api): migrate node version validation to TypeScript (#8461)
* chore(api): migrate node version validation to TypeScript

* [autofix.ci] apply automated fixes

* docs: add release note for API TypeScript migration

* Update upcoming-release-notes/wip-chore-api-migrate-node-version-validation-to-typescript.md

Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>

* Update upcoming-release-notes/wip-chore-api-migrate-node-version-validation-to-typescript.md

Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
2026-07-11 20:50:02 +00:00