Compare commits

...

412 Commits

Author SHA1 Message Date
Julian
4c62e2a75d Empty commit to bump CI 2026-05-03 03:39:27 +00:00
Julian Dominguez-Schatz
922f0b8a53 Update docs release date 2026-05-02 13:47:55 -04:00
Julian Dominguez-Schatz
cfd527b446 Fix shared worker resumption after tab suspend (#7656)
* [AI] Fix SharedWorker tab resume recovery

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* [AI] Fix SharedWorker reload readiness

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add release notes

* Update packages/desktop-client/src/shared-browser-server-core.ts

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
2026-05-02 13:46:31 -04:00
Julian Dominguez-Schatz
56ea6cba68 Update author
Updated author information in the release notes.
2026-04-27 23:38:40 -04:00
github-actions[bot]
60c0796a92 Generate release notes for v26.5.0 2026-04-27 21:52:09 +00:00
Matt Fiddaman
7b0460d7e9 fix infinite loop when remainder is impossible to solve (#7623)
* fix infinite loop when remainder is impossible to solve

* note
2026-04-27 22:51:20 +01:00
Matt Fiddaman
46ba63f370 increase test coverage for budget templates (#7620)
* [AI] cover existing template engine logic with regression tests

Adds tests for goal template behavior that predates this PR so the
suite can be cherry-picked onto master to confirm no regressions. No
production code changes.

Covers:
- init() validation: schedule names, by/schedule priority match, past
  by-target with and without annual/repeat, percentage source not
  found, special source aliases, duplicate limit/spend/goal
  directives, weekly limit missing start date, invalid limit period,
  unrecognized periodic period
- runRemainder cap clamping and hideDecimal fraction removal
- Income-category branch in runTemplatesForPriority
- getLimitExcess against an aggregate weekly cap
- Past by-target rolling forward via the annual period
- runSchedule full=true (no sinking accumulation), percent and fixed
  adjustments, completed-schedule filtering, past-date error for
  non-repeating schedules, monthly/weekly/daily sinking contribution
  branches when interval exceeds the pay-month-of cap, surplus
  absorption when last-month balance exceeds the target, and
  tracking-budget mode forcing all schedules pay-month-of
- applyMultipleCategoryTemplates orchestration: per-category writes,
  cross-category priority clamping when funds run out, error
  notification path
- applyTemplate force=false skipping already-budgeted categories

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* note

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 22:51:20 +01:00
github-actions[bot]
4926cd5d76 Generate release notes for v26.5.0 2026-04-27 21:48:23 +00:00
Emil Tveden Bjerglund
05efe9ceee Fix Sankey income bug, when payee it not set (#7632)
* Ensure income categories are shown correct, even if payee is not set

* Add release note
2026-04-27 22:47:35 +01:00
Matt Fiddaman
da522f3e3e add release note highlights 2026-04-27 22:35:34 +01:00
github-actions[bot]
8636591ddf Generate release notes for v26.5.0 2026-04-27 21:30:22 +00:00
Matt Fiddaman
b5d59c7428 fix lint (#7643) 2026-04-27 22:29:28 +01:00
Matt Fiddaman
c02e308739 fix cherrypicked commits not being respected and lint race in release note generation workflow (#7640)
* fix cherrypicked commits not being respected and lint race

* note

* coderabbit suggestions

* fix lint

* make double restore possibility safe
2026-04-27 21:59:57 +01:00
Matt Fiddaman
a5e80edd32 fix release note generation script (#7635)
* fix release note generation script

* note
2026-04-27 20:48:40 +01:00
github-merge-queue
9beeae54e0 🔖 (26.5.0) 2026-04-25 17:13:14 +00:00
Matt Fiddaman
eb922fd191 sankey card should follow report settings (#7619)
* sankey card should follow report rules

* note
2026-04-25 16:17:45 +00:00
Julian Dominguez-Schatz
2b584e1ad0 Make double ctrl-f trigger browser-native search (#7605)
* Make double Ctrl-f trigger browser find

* Add release notes

* Invert condition

* Rename release note
2026-04-25 00:53:25 +00:00
dependabot[bot]
4f1bc3fcdd Bump postcss from 8.5.8 to 8.5.10 (#7613)
* Bump postcss from 8.5.8 to 8.5.10

Bumps [postcss](https://github.com/postcss/postcss) from 8.5.8 to 8.5.10.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.8...8.5.10)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.10
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* Add release notes for postcss version bump

Updated postcss version for maintenance.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Julian Dominguez-Schatz <julian.dominguezschatz@gmail.com>
2026-04-25 00:23:54 +00:00
Julian Dominguez-Schatz
ea50db524b Enable stricter electron build options (#7609)
* Enable stricter electron build options

* Add release notes

* Fix build signatures when no signing credentials are provided

* Attempt fix again
2026-04-24 23:59:28 +00:00
Julian Dominguez-Schatz
da1d0a94b9 Migrate file service to TypeScript (#7606)
* Migrate file service to TypeScript

* Add release notes

* Rabbit

* Stricter types
2026-04-24 22:50:43 +00:00
Alec Bakholdin
daab7f737e Import Transactions - Persist and set reimport deleted transactions config to true (#7610)
* fix: restored default functionality from v26.3.0 for reimport deleted transactions. import-reimport-deleted is now a synced pref that persists between imports

* release notes

* release note update

---------

Co-authored-by: Alec Bakholdin <alecbakholdin.com>
2026-04-24 18:21:45 +00:00
Emil Tveden Bjerglund
686f10247d Enhance Sankey chart datamodel, show income and allow layer filtering (#7582)
* Refactor to use directed, weighted graph as datamodel

* Fix percentage labels

* Reimplement sorting and topN handling

* Fix typing. Show toBudget on graph.

* Implement better DAG model

* Fix Other-grouping with new datamodel

* Add global sorting

* Reorder spreadsheet code for clarity

* Add percentageLabels back

* Fix all sorting modes

* Better color handling

* Handle if overbudgeted

* Fix filtering issue related to hidden nodes for Spent report

* Implement enums for special names

* Linting and typechecking

* Add layer selectors

* Trim SankeyCard

* Fix issue with empty nodes making the graph unreadable

* Add release note

* Update release note

* Reorder code

* Address coderabbit comments

* Ensure that layer-from and layer-to cannot be equal

* Update layer selectors to match selected view mode

* Fix wrong graph object reference

* Cap regex length

* Fixed wrong layer assignment for budget income categories

* Make translation not optional in createSpreadsheet

* Use predefined suffix for 'Other'

* Avoid invalid layer selection for Budgeted

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #7582

* Import translation in spreadsheet, instead of passing as argument

* Remove all non-null assertions and handle safely

* Fix most uses of 'as'

* Fix issues hiding Other categories and giving wrong toBudget value

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-24 18:20:15 +00:00
Julian Dominguez-Schatz
227c995155 Disallow reconfiguring OpenID after initialization (#7608)
* Disallow reconfiguring OpenID after initialization

* Add release notes
2026-04-24 15:12:01 +00:00
dependabot[bot]
c8224d24be Bump @xmldom/xmldom from 0.8.12 to 0.8.13 (#7596)
Bumps [@xmldom/xmldom](https://github.com/xmldom/xmldom) from 0.8.12 to 0.8.13.
- [Release notes](https://github.com/xmldom/xmldom/releases)
- [Changelog](https://github.com/xmldom/xmldom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/xmldom/xmldom/compare/0.8.12...0.8.13)

---
updated-dependencies:
- dependency-name: "@xmldom/xmldom"
  dependency-version: 0.8.13
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-23 20:06:30 +00:00
Dakyne
29a06b23ea Add Gruvbox Light and Dark themes to custom theme catalog (#7571)
* Add Gruvbox Light and Dark custom themes to catalog

* Add release note for PR #7571

* [autofix.ci] apply automated fixes

* Update packages/desktop-client/src/data/customThemeCatalog.json

Co-authored-by: Joel Jeremy Marquez <joeljeremy.marquez@gmail.com>

---------

Co-authored-by: Dakyne <fawn_salable_73@icloud.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Joel Jeremy Marquez <joeljeremy.marquez@gmail.com>
2026-04-23 14:25:05 +00:00
Matiss Janis Aboltins
aeb28d3b87 Add Discord notification for nightly theme catalog scan failures (#7595)
* [AI] Notify Discord when nightly theme catalog scan fails

Adds an if: failure() step to the validate-theme-catalog job that posts a
minimal alert to the DISCORD_WEBHOOK_URL webhook with a link back to the
failing workflow run. Fires on both theme validation failures (script exits
1) and earlier step failures (checkout/setup), so infrastructure breakage
is also surfaced. nofail: true keeps a Discord outage from cascading into
a red job.

* [AI] Drop setup comment from Discord notify step

* [AI] Move Discord notify to its own job gated by an environment

Splits the notify step into a separate notify-failure job that depends on
validate-theme-catalog and runs only on failure. The new job binds to the
nightly-alerts GitHub Environment so the DISCORD_WEBHOOK_URL secret is
scoped to a dedicated environment rather than inherited at the repo level
(zizmor secrets-without-environment).

* [AI] Add release notes for 7595

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 06:58:21 +00:00
dependabot[bot]
bd1da27404 Bump dompurify from 3.3.2 to 3.4.1 (#7591)
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.3.2 to 3.4.1.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.3.2...3.4.1)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-version: 3.4.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
2026-04-22 20:51:12 +00:00
Matiss Janis Aboltins
7501674613 [AI] Fix Docker build for workspace:* dependencies (#7564)
* [AI] Fix Docker build for workspace:* dependencies

Since @actual-app/crdt became a workspace:* dep, `yarn workspaces focus
--production` creates relative symlinks in node_modules that dangle when
only node_modules is copied into the prod image, breaking local Docker
builds with ERR_MODULE_NOT_FOUND: @actual-app/crdt.

Dereference yarn's workspace symlinks in the builder stage with `cp -RL`
so the prod stage can copy a self-contained node_modules without needing
to enumerate which workspace:* deps exist. Adding a new workspace:* dep
now requires zero Dockerfile changes.

Also move the sync-server .dockerignore to the repo root (and drop stray
local node_modules / .git / .yarn caches from the build context), since
docker builds use the repo root as context — the old sync-server-level
file was no longer being applied.

Fixes #7561.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [AI] Strip dev-only dirs from dereferenced workspace packages

The generic `cp -RL` step copies full workspace package trees into the
image (src/, e2e/, tests, build-stats, etc.). Remove them after the
dereference — they're not needed at runtime, and skipping them recovers
~67MB from the final image on both alpine and ubuntu variants.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [AI] Rephrase 7564 release note to be user-facing

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 19:20:51 +00:00
Jaime R Calzada
c3717e7036 [AI] Add per-schedule custom upcoming length override (#7434)
* [AI] Add per-schedule custom upcoming length override

* [AI] Add release notes for PR #7434

* [AI] Add custom length option to per-schedule upcoming length selector

* [AI] Deduplicate preset values and guard against malformed custom upcoming length

* [autofix.ci] apply automated fixes

* [AI] Retrigger CI (flaky accounts E2E test)

* [AI] Improve schedule editor layout for upcoming length field

Restructure the custom upcoming length from a checkbox + conditional
dropdown into a proper FormField with a single Select that includes
"Use global default" as the null option. Place it in a responsive
side-by-side row with the auto-post checkbox, consistent with the
form's existing layout patterns.

* [AI] Address CodeRabbit review feedback

Tighten custom upcoming length validation with a proper regex
instead of a loose hyphen check. Replace fixed height with minHeight
on the auto-post checkbox row to avoid clipping translated labels.

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #7434

* [AI] Fix custom_upcoming_length not persisting on mobile

The mobile schedule save handler was missing the custom_upcoming_length
field from the payload sent to schedule/create and schedule/update.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: youngcw <calebyoung94@gmail.com>
2026-04-22 16:33:21 +00:00
Matt Fiddaman
9d91da77ec move from tsgo dev preview to beta (#7587)
* move from tsgo dev preview to beta

* note
2026-04-22 15:22:14 +00:00
Copilot
1c97388654 [AI] Consolidate npm release and nightly publishing into one workflow (#7583)
* [AI] Unify npm release and nightly publish workflows

Agent-Logs-Url: https://github.com/actualbudget/actual/sessions/3f8de051-a9a7-4527-88d8-5c44bc06a562

Co-authored-by: jfdoming <9922514+jfdoming@users.noreply.github.com>

* [AI] Harden unified npm publish workflow conditionals

Agent-Logs-Url: https://github.com/actualbudget/actual/sessions/3f8de051-a9a7-4527-88d8-5c44bc06a562

Co-authored-by: jfdoming <9922514+jfdoming@users.noreply.github.com>

* [AI] Clarify nightly install step and add concise release note

Agent-Logs-Url: https://github.com/actualbudget/actual/sessions/af3d68aa-d217-47be-addb-1b40b08f533b

Co-authored-by: jfdoming <9922514+jfdoming@users.noreply.github.com>

* [AI] Revert release note edit and make npm publish workflow ACT-compatible (#7584)

* Initial plan

* [AI] Revert release note edit and validate workflow with act

Agent-Logs-Url: https://github.com/actualbudget/actual/sessions/df98a192-197a-4df4-a804-80b69116f742

Co-authored-by: jfdoming <9922514+jfdoming@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jfdoming <9922514+jfdoming@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jfdoming <9922514+jfdoming@users.noreply.github.com>
2026-04-22 15:18:29 +00:00
Julian Dominguez-Schatz
8323a7d27c Reduce permissions in stale workflow (#7555)
* Restrict permissions on stale workflow

* Add release notes for reducing permissions in stale workflow
2026-04-22 15:17:35 +00:00
Matiss Janis Aboltins
7d4e28041c [AI] Export API models as separate entry point (#7581)
* [AI] Expose API entity types via @actual-app/api/models

Adds a new `./models` subpath export on `@actual-app/api` that re-exports
the public API entity types (`APIAccountEntity`, `APICategoryEntity`,
`APICategoryGroupEntity`, `APIFileEntity`, `APIPayeeEntity`,
`APIScheduleEntity`, `APITagEntity`, `AmountOPType`) from
`@actual-app/core/server/api-models`. Consumers can now import these types
from a stable public entry point instead of reaching into core internals:

    import type {
      APICategoryEntity,
      APICategoryGroupEntity,
    } from '@actual-app/api/models';

Uses `export type *` so the compiled `dist/models.js` is empty and no
runtime code is added. The Vite lib config is expanded to a multi-entry
map (`index`, `models`) so both bundles are produced, and tsgo already
emits `@types/models.d.ts` via the existing `declarationDir` setup.

* Add release notes for PR #7581

* Modify release notes for API model exports

Updated category from 'Features' to 'Enhancements' and added API export details.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-22 10:45:54 +00:00
Matiss Janis Aboltins
3c77b3d0d5 [AI] Add enforce-boundaries ESLint rule for architectural boundaries (#7467)
* [AI] Add enforce-boundaries ESLint rule for architectural boundaries

Disallow tsconfig compilerOptions.paths, Vite resolve.alias, and
backtracked imports (../../) to enforce the use of package.json
subpath imports (#path) as the canonical aliasing mechanism.

The rule is enabled globally as an error without autofixes.

https://claude.ai/code/session_01T7VCnq5Kid7co9vBDPHWmR

* [AI] Fix enforce-boundaries lint violations

Replace backtracked imports (../../) with subpath imports (#path):
- migrations.ts: use #migrations/* mapping
- Formula.tsx: use #components/* mapping
- TransactionsTable.test.tsx: use #mocks/* mapping

Suppress unavoidable violations with oxlint-disable comments:
- preview.tsx: cross-package theme imports (pre-existing TODO)
- vite.desktop.config.ts: handlebars resolve.alias (types require root entry)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Clean up enforce-boundaries rule: remove redundant comments, optimize Property visitor, add edge-case tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add release notes for PR #7467

* Update category for release notes

Changed category from Enhancements to Maintenance.

* [AI] Fix JSON syntax error after merge

Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>

* [AI] Merge master and fix lint errors in enforce-boundaries rule

Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
2026-04-21 21:18:42 +00:00
Matiss Janis Aboltins
846b6a6b7a [AI] Add nightly CI scan for custom theme catalog (#7566)
* [AI] Add nightly CI scan for custom theme catalog

Adds a scheduled GitHub Actions workflow that fetches `actual.css` from
every repo in `customThemeCatalog.json` and runs it through the same
`embedThemeFonts` + `validateThemeCss` pipeline the app uses at install
time. Failing themes fail the job so maintainers get an alert when a
third-party repo introduces a regression.

The scan treats fetched CSS as opaque text: never executed, never
injected into a DOM, size-capped at 512 KB per file, 15s per fetch,
restricted to raw.githubusercontent.com with redirects disabled, and
run with `contents: read` permissions only. Each catalog `repo` is
schema-checked against `owner/repo` before being interpolated into
the URL.

* [AI] Simplify theme catalog scan

- Reuse `CatalogTheme` type from customThemes instead of duplicating as
  `CatalogEntry` in the script.
- Hoist `appendFileSync` to the static `node:fs` import; drop the dynamic
  import inside `writeStepSummary`.
- Drop the narrative header docstring and the trailing `// ...` comments
  that just restated constant names.
- Drop the redundant URL-prefix re-check inside the CSS fetch helper;
  the single call site constructs the URL from a pinned literal.
- Drop the 250 ms inter-request delay (GitHub Raw rate limits are not
  relevant for 21 requests, and the trailing delay was idle wall-clock
  against the 10-min job budget).
- Give each font fetch inside `embedThemeFonts` its own 15 s timeout
  via `AbortSignal.any`, instead of sharing one signal across every
  font in a theme. Drop the now-unnecessary caller-supplied signal
  from the CI call site.

* [AI] Fix lint on theme catalog scan imports
2026-04-21 21:18:21 +00:00
Julian Dominguez-Schatz
07c71154c9 Enable trusted publishing for releases (#7579)
* Enable trusted publishing for releases

* Add release notes for PR #7579

* Update 7579.md

* [autofix.ci] apply automated fixes

---------

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>
2026-04-21 18:34:38 +00:00
Matt Fiddaman
2f49c5c400 add repository details to package.json files (#7578)
* add package URLs

* note
2026-04-21 17:40:11 +00:00
Matt Fiddaman
4b28a8146e fix error when clearing the payee field of a transaction (#7532)
* treat undefined values as missing in updates

* note
2026-04-21 17:30:45 +00:00
Matiss Janis Aboltins
362d8d60e4 [AI] Optimize CI e2e tests with pre-built bundle serving (#7503)
* [AI] Speed up and stabilize Playwright e2e tests

- Serve the prebuilt browser bundle via bin/serve-build.mjs in CI to
  skip per-shard Vite startup; 3-shard matrix with 4 workers each.
- Disable CSS animations in non-VRT runs via a fixture-level init
  script; bump expect timeout to 10s for AutoSizer-bound assertions.
- Use page.evaluate() for React Aria button clicks and a native value
  setter + single input event for controlled-input fills to eliminate
  React Aria re-render races in createAccount and Payee/Category
  autocompletes.
- Click the matching option directly (instead of Enter on a not-yet-
  highlighted list) in mobile transaction and schedule autocompletes.
- FocusableAmountInput.applyText reads the DOM input value so the
  typed amount survives a blur that fires before React flushes the
  onChange state update under CPU contention.
- MobileTransactionEntryPage.fillAmount waits for the outer display
  button (reads parent props.value) so async rules-run completes
  before the next fillField snapshots the transaction.
- MobileNavigation dispatches nav link clicks through evaluate() to
  bypass Playwright's viewport-stability check against the navbar's
  react-spring transforms.
- MobileBudgetPage summary-button lookups use locator.or().waitFor()
  instead of an isVisible() cascade.
- ConfigurationPage.startFresh/createTestFile wait for the account
  header / budget table to mount before returning.
- Workflow hardening: persist-credentials=false on all actions/checkout
  and top-level permissions: contents: read (zizmor findings).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [AI] Apply animation-disable init script to browser.newPage pages

The previous implementation extended the test-scoped `page` fixture,
but every test creates its own page via `browser.newPage()` and never
uses the fixture-provided page — so the init script was a no-op in
every test.

Move the wrap to the worker-scoped `browser` fixture: intercept
`browser.newPage` so each page created that way has `addInitScript`
applied before the caller can navigate to it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 17:03:56 +00:00
Julian Dominguez-Schatz
664cfdf244 Force node version 24 for trusted publishing (#7577)
* Force node version 24 for trusted publishing

* Add release notes for PR #7577

* Enable check-latest for npm setup action

* Update nightly package publishing workflow to Node.js 24

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-21 16:44:03 +00:00
Stephen Brown II
880bb67423 [AI] Fix transaction row drag breaking inline text edits (#7572)
* [AI] fix: disable transaction row drag while editing input cells

Row-level useDrag competed with notes/payee/amount fields (GH #7567).
Disable reorder drag when the row is in edit mode except for select/cleared columns.

* Add release notes
2026-04-21 16:04:17 +00:00
Julian Dominguez-Schatz
3e35d3b6f5 fix: trusted publishing requires npm version >= 11.5.1 (#7574)
* fix: trusted publishing requires npm version >= 11.5.1

* Add release notes for PR #7574

* Update .github/workflows/publish-nightly-npm-packages.yml

Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>

* Update release notes for trusted publishing fix

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2026-04-21 16:03:37 +00:00
Matiss Janis Aboltins
75da8f1851 [AI] fix: ensure crdt builds before loot-core is packed (#7565)
The `Publish nightly npm packages` workflow started failing at the
"Pack the core package" step with:

    Cannot find module '@actual-app/crdt' or its corresponding type declarations.

PR #7541 switched `@actual-app/crdt`'s package.json to conditional
exports (`types` → `./dist/index.d.ts`). `yarn pack` for
`@actual-app/core` triggers a prepack that runs `tsgo -b`, which now
resolves `@actual-app/crdt` via the `types` condition and expects
`packages/crdt/dist/index.d.ts`. Nothing was building crdt first
because loot-core's tsconfig didn't declare it as a project
reference.

Fix: declare the project reference so `tsgo -b` walks the graph and
builds crdt before loot-core. Sibling packages already do this.

Also adopt `@monorepo-utils/workspaces-to-typescript-project-references`
to keep each package's tsconfig `references` in sync with its
`workspace:*` deps, and wire it into a new `yarn check:tsconfig-references`
step in the `check` CI job plus lint-staged. Running the tool added
`../desktop-client` references to sync-server and desktop-electron
(both declare `@actual-app/web` as a workspace dep even though they
only use it at runtime via `require.resolve`); the extra references
are harmless — in CI the corresponding build is already cached by
earlier steps.

https://claude.ai/code/session_01AA2gEMqX24GWeq5BovNmaz
2026-04-20 22:07:27 +00:00
Julian Dominguez-Schatz
29275a573d Run zizmor auto-fix tool (#7533)
* Run `zizmor` auto-fix tool

* Add release notes

* Enable credential persistence for string extraction

Updated workflow to allow pushing extracted strings.

* Enable credential persistence for release notes

Enable credential persistence to allow committing release notes.
2026-04-20 19:40:04 +00:00
Copilot
ead1b8e39d Remove redundant inline type import guideline (#7553)
* Initial plan

* [AI] Remove inline type import guideline (handled by oxfmt/oxlint)

Agent-Logs-Url: https://github.com/actualbudget/actual/sessions/7891fb33-668f-444e-bd69-5806181dcecd

Co-authored-by: MatissJanis <886567+MatissJanis@users.noreply.github.com>

* Add release notes for PR #7553

* Update author and remove redundant TypeScript guidance

Updated author credit in release notes and removed outdated guidance.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: MatissJanis <886567+MatissJanis@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
2026-04-20 19:38:57 +00:00
Matiss Janis Aboltins
3c361fdabf [AI] Add AI Usage Policy for contributors (#7548)
* [AI] Add AI Usage Policy for contributors

Add a contributor-facing AI Usage Policy page modeled on ESLint's version,
covering disclosure, human-only interaction with maintainers, and author
responsibility. Wire it into the docs sidebar, link it from the contributing
index and the root CONTRIBUTING.md.

https://claude.ai/code/session_012RspFcLedoUjbEYknJYPiL

* [AI] Unwrap AI policy paragraphs, renumber release note

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-20 19:37:44 +00:00
James Skinner
8691766fb8 Fix reconciled value of children of split transactions (#7453)
* Fix reconciled value of children of split transactions

* Update release note

* Set mock transactions to include reconciled field
2026-04-20 17:14:26 +00:00
Julian Dominguez-Schatz
e896ce408a Enable trusted publishing for nightly npm packages (#7556)
* Enable trusted publishing for nightly `npm` packages

Ref: https://docs.npmjs.com/trusted-publishers

* Add release notes for PR #7556

* Change category to Maintenance and update description

* Fix formatting of id-token permission comment

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-20 14:38:06 +00:00
Matiss Janis Aboltins
3373154b40 Refactor CI workflows to use shared setup job (#7551)
* [AI] Run setup once per workflow and fan out via needs

Add a prep `setup` job at the top of `check.yml` and `build.yml`, and
make every other job in those workflows declare `needs: setup`.

The composite action in `.github/actions/setup` caches `node_modules`
keyed on `yarn.lock`. When that hash changes (dep-bump PRs, master after
a merge), the cache is cold and every fan-out job races to run
`yarn --immutable` in parallel — one wins the cache save, the rest do
redundant work. Serialising through a single `setup` job warms the
cache once so downstream jobs restore instantly and skip yarn install
via the existing `if: steps.cache.outputs.cache-hit != 'true'` guard.

No changes to the composite action or cache keys. `e2e-test.yml` is
intentionally left alone.

* [AI] Harden setup jobs and add release note

Address zizmor code-scanning findings on the new `setup` jobs added in
the previous commit:

- Scope `permissions: contents: read` so the job no longer inherits
  workflow-default write permissions.
- Pass `persist-credentials: false` to `actions/checkout` so the GitHub
  token isn't left on disk for later steps that don't need it.

Add `upcoming-release-notes/7551.md` to satisfy the release-notes PR
check.

* [AI] Disable credential persistence on build.yml checkouts

Each of `api`, `crdt`, `web`, `cli`, `server` in build.yml does
`actions/checkout` (which writes the GitHub token into `.git/config`)
and then uploads build artifacts in the same job. Zizmor flags this as
"credential persistence through GitHub Actions artifacts" because a
misconfigured upload path could capture `.git/config` and leak the
token.

None of these jobs push or write to git, so drop the credential
persistence via `persist-credentials: false` on the checkout.

* [AI] Disable credential persistence on check.yml checkouts

None of the jobs in check.yml (`constraints`, `lint`, `typecheck`,
`validate-cli`, `test`, `migrations`) push or write to git, so pass
`persist-credentials: false` to their `actions/checkout` calls to
resolve the zizmor "credential persistence" finding. Mirrors the fix
just applied to build.yml.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-19 21:22:49 +00:00
Matiss Janis Aboltins
f85627dcf6 [AI] Disable bundle minification for readable error messages (#7538)
* [AI] Disable bundle minification for readable production error messages

The desktop-client had dead terserOptions (no `minify: 'terser'` was set, so
Vite's default esbuild minifier ran with name mangling). The loot-core and
plugins-service workers used Terser with mangle:false but still compressed.
Set `minify: false` across all three browser build configs so production
stack traces are human-readable.

https://claude.ai/code/session_01VEywxebiNYAgJia35fygQx

* [AI] Rename release note to match PR number

https://claude.ai/code/session_01VEywxebiNYAgJia35fygQx

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-19 20:35:26 +00:00
Matt Fiddaman
695fd0e7e0 fix bank sync account linking modal being disabled when relinking existing accounts (#7487)
* fix link account modal button disabled

* note
2026-04-18 22:25:28 +00:00
Matiss Janis Aboltins
9682f6d8c9 ci: disable fail-fast for Electron build workflows (#7547)
* [AI] Disable fail-fast for Electron build matrices

Prevents cancellation of in-progress platform builds when one fails, so
Windows/macOS/Linux results are all visible on a single run.

* Add release notes for PR #7547

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-18 21:32:21 +00:00
Matiss Janis Aboltins
4b940423ee [AI] Persist custom CSS override across theme changes (#7495)
* [AI] Persist custom CSS overrides as a standalone global pref

Moves the custom CSS override out of the InstalledTheme JSON blob into
a dedicated customCssOverride global pref so that overrides survive
switching themes, clearing installed themes, or toggling auto/light/dark
mode. Includes a one-time migration that lifts the legacy overrideCss
field out of installedCustomLightTheme / installedCustomDarkTheme JSON.

- Add customCssOverride global pref (loot-core types + server defaults)
- Inject the override as a trailing style layer in CustomThemeStyle so
  it layers on top of any installed custom theme
- Drop overrideCss from the InstalledTheme type; extractLegacyOverride
  + migrateLegacyOverride handle the one-time lift with whitespace trim
- Run the migration from CustomThemeStyle with an idempotent effect that
  re-runs safely once prefs hydrate
- Bind the ThemeInstaller textarea directly to the new pref
- Add a "Custom CSS is active" indicator button next to the theme
  selector that opens the installer for editing the override without
  flipping auto mode to light
- Pre-switch out of auto when the user picks "Custom theme" from the
  main selector, so the flag that used to distinguish entry points goes
  away and handleInstall collapses to a pure slot dispatch
- Tests: hermetic Themes settings tests, expanded customThemes unit
  tests covering extraction/migration/trim edge cases, updated
  ThemeInstaller tests for the new pref binding

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Address review feedback for custom CSS override installer

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Defer auto-mode theme switch and guard stale installer callbacks

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 15:29:32 +00:00
Matiss Janis Aboltins
9fb6876f3b [AI] sync-server: use workspace reference for @actual-app/crdt (#7541)
* [AI] sync-server: use workspace reference for @actual-app/crdt

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Update build script in sync-server package to use TypeScript's build mode

* Package electron

* [AI] crdt: add conditional exports so Node can load the built bundle

Before this change the root exports entry pointed at `./src/index.ts`, so
any pure-Node consumer (notably the sync server that Electron forks as a
utility process) failed to import `@actual-app/crdt` — Node can't execute
TypeScript source directly. Sync-server had been masking this by pulling
`@actual-app/crdt@npm:2.1.0` where `publishConfig.exports` resolves to
`./dist/index.js`; once sync-server switched to `workspace:*`, the
Functional Desktop App CI job timed out waiting for the sync server to
boot.

Switch to conditional exports in the same shape `@actual-app/api` already
uses:

- `types` → `./dist/index.d.ts` for TypeScript tooling
- `development` → `./src/index.ts` for Vite/Vitest (HMR, fast feedback)
- `default` → `./dist/index.js` for Node runtime

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 15:27:34 +00:00
kenkuo
210422f61a [AI] Add 'Last 30 days' date range option to custom reports (#7217)
* Add 'Last 30 days' date range option to custom reports

Add a rolling 30-day date range to the Live mode date filter in custom
reports. The option appears between 'Last month' and 'Last 3 months'
and is available for Daily, Weekly, and Monthly intervals.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* [autofix.ci] apply automated fixes

* Fix release note filename and author to match PR #7217

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: youngcw <calebyoung94@gmail.com>
2026-04-18 15:10:35 +00:00
Aadhith
6941cc9e01 Add Ilavenil theme to custom theme catalog (#7543)
* Add theme Ilavenil to custom catalog

* Add release notes
2026-04-18 14:38:08 +00:00
tempiz
13abe0cb00 Addition of scoped ErrorBoundarys per #7391 (#7497)
* Addition of scoped ErrorBoundarys per 7391

* Adjusted to use FeatureErrorfallback from #7437
2026-04-17 20:52:57 +00:00
Matt Fiddaman
a4e401bc8b fix build error with typescript v6 (#7524)
* fix tsconfig error

* note

* fix strict mode violation

* fix another type issue
2026-04-17 20:52:44 +00:00
Matiss Janis Aboltins
ff7f81ac06 [AI] Emit bundle stats from the crdt package (#7537)
* [AI] Emit bundle stats from the crdt package

The crdt package was the only published library without a stats.json
artifact. Migrate its build to Vite (mirroring the api/cli setup), wire
in rollup-plugin-visualizer to emit dist/stats.json, and upload it from
the CRDT CI job. Declarations are still produced by tsgo via
--emitDeclarationOnly.

https://claude.ai/code/session_01CDVAGLGu49q5YMHsRLkYLQ

* Add release notes for PR #7537

* [AI] crdt: drop redundant rm -rf dist from build script

Vite's build.emptyOutDir: true already clears the output directory
before writing, so the leading rm -rf dist is unnecessary.

https://claude.ai/code/session_01CDVAGLGu49q5YMHsRLkYLQ

* [AI] Include crdt in the size-compare bundle stats table

Wait for the crdt build check on both the base branch and the PR,
download the crdt-build-stats artifact for each, and pass it to
bundle-stats-comment.mjs so the summary table rendered on the PR
includes a row for the crdt package alongside desktop-client,
loot-core, api, and cli.

https://claude.ai/code/session_01CDVAGLGu49q5YMHsRLkYLQ

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-17 20:38:06 +00:00
Michael Clark
5bf160463c 🎨 Update storybook fonts and brand image (#7536)
* update storybook fonts and brand image

* release notes
2026-04-17 19:26:24 +00:00
Matiss Janis Aboltins
598bf81da1 [AI] crdt: typecheck test files and clean up lint issues (#7534)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 17:33:02 +00:00
Julian Dominguez-Schatz
995670476e Add GitHub Actions check step via zizmor (#7465)
* Add GitHub Actions check step via `zizmor`

* Add security-events permissions to check-gh-actions

Added permissions for security events in GitHub Actions.

* Add persist-credentials option to checkout action

* Add release notes for PR #7465

* Change category to Maintenance and update action step

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-17 11:55:51 +00:00
Matt Fiddaman
711939f71c replace uuid and fs-extra with builtins (#7529)
* fs-extra

* uuid

* note
2026-04-16 20:42:27 +00:00
Matt Fiddaman
8486dca33a clean up more unused dependencies (#7528)
* remove unused deps

* note

* remove babel

* fix lint
2026-04-16 20:23:13 +00:00
Matt Fiddaman
359c1fe9ce consolidate internal naming patterns used for budget types (#7527)
* consolidate budget naming pattern

* note

* coderabbit feedback
2026-04-16 17:37:21 +00:00
Matt Fiddaman
7a4b43e7a4 fix some more GitHub code quality issues (#7520)
* Apply suggested fix to packages/desktop-client/src/components/modals/EditFieldModal.tsx from Copilot Autofix

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* Apply suggested fix to packages/desktop-client/src/components/modals/EditFieldModal.tsx from Copilot Autofix

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* note

* fix typo in note

* remove useless conditions

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-04-16 17:07:22 +00:00
sk10727-a11y
94ea408303 Fix filter operator switching behavior (#7304)
* [autofix.ci] apply automated fixes

* Fix render condition (code rabbit)

* [autofix.ci] apply automated fixes

* Fix filter operator switching behavior

updated to allow conversion from multi-ID to text if only one array value and to handle no-value operators for account filter

final fixes before upstream sync
Fix UUID leakage when switching filter operators
Fix UUID leakage when switching filter operators#

* fixed merge conflicts

* [autofix.ci] apply automated fixes

* Fix Account filter input not rendering due to incorrect conditional logic

* [autofix.ci] apply automated fixes

* Rename isPayeeIdOp to isIdOp for clarity

* [autofix.ci] apply automated fixes

* Trigger CI rerun

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-04-16 16:47:28 +00:00
Matt Fiddaman
dc8694cc3a fix potentially inconsistent state updates (#7523)
* fix potentially inconsistent state updates

* note
2026-04-16 09:41:00 +00:00
Matt Fiddaman
89b442bc74 fix runImport failing when ACTUAL_DATA_DIR environment variable is not set (#7522)
* Fix ACTUAL_DATA_DIR being used instead of fetching it from config

* note
2026-04-16 09:40:43 +00:00
Matiss Janis Aboltins
3ca0bcbc13 Remove inactive community repository links from docs (#7500)
* [AI] Remove outdated community project links from docs

* Add release notes for PR #7500

* [AI] Add back actualplaid link to community repos

Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>

* Delete 7500.md

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
2026-04-16 07:35:20 +00:00
Matt Fiddaman
0e5e641aae ⬆️ eslint v10 & migrate eslint plugins to oxc performant API (#7508)
* eslint (^9.39.3 -> ^10.2.0)

* migrate plugins to oxc alternative API

* note
2026-04-15 17:57:04 +00:00
Matt Fiddaman
8c47374b9d ⬆️ mid-month dependency bump (#7506)
* typescript (^5.9.3 -> ^6.0.2)

* eslint-plugin-perfectionist (^5.6.0 → ^5.8.0)

* @types/node (^22.19.15 → ^22.19.17)

* @typescript/native-preview (^7.0.0-dev.20260309.1 → ^7.0.0-dev.20260404.1)

* eslint (^9.39.3 → ^9.39.4)

* lage (^2.14.19 → ^2.15.5)

* lint-staged (^16.3.2 → ^16.4.0)

* minimatch (^10.2.4 → ^10.2.5)

* vitest (^4.1.0 → ^4.1.2)

* better-sqlite3 (^12.6.2 → ^12.8.0)

* commander (^13.0.0 → ^13.1.0)

* cosmiconfig (^9.0.0 → ^9.0.1)

* @chromatic-com/storybook (^5.0.1 → ^5.1.1)

* @storybook/addon-a11y (^10.2.16 → ^10.3.4)

* @storybook/addon-docs (^10.2.16 → ^10.3.4)

* @storybook/react-vite (^10.2.16 → ^10.3.4)

* eslint-plugin-storybook (^10.2.16 → ^10.3.4)

* storybook (^10.2.16 → ^10.3.4)

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

* @react-aria/interactions (^3.27.0 → ^3.27.1)

* @swc/core (^1.15.18 → ^1.15.24)

* @swc/helpers (^0.5.19 → ^0.5.21)

* @tanstack/react-query (^5.90.21 → ^5.96.2)

* @uiw/react-codemirror (^4.25.7 → ^4.25.9)

* @vitejs/plugin-basic-ssl (^2.2.0 → ^2.3.0)

* i18next (^25.8.14 → ^25.10.10)

* lru-cache (^11.2.6 → ^11.2.7)

* react-grid-layout (^2.2.2 → ^2.2.3)

* react-i18next (^16.5.6 → ^16.6.6)

* rolldown (^1.0.0-rc.12 → ^1.0.0-rc.13)

* sass (^1.97.3 → ^1.99.0)

* adm-zip (^0.5.16 → ^0.5.17)

* csv-parse (^6.1.0 → ^6.2.1)

* csv-stringify (^6.6.0 → ^6.7.0)

* jest-diff (^30.2.0 → ^30.3.0)

* express-rate-limit (^8.3.0 → ^8.3.2)

* upgrade yarn to 4.13.0

* react-aria-components (^1.15.1 → ^1.16.0)

* @vitejs/plugin-react (^6.0.0 → ^6.0.1)

* @codemirror/state (^6.5.4 → ^6.6.0), @codemirror/view (^6.38.7 → ^6.41.0)

* react-aria (^3.46.0 → ^3.47.0)

* react-error-boundary (^6.0.3 → ^6.1.1)

* recharts (^3.7.0 → ^3.8.1)

* fast-check (4.5.3 → ^4.6.0)

* rollup-plugin-visualizer (^6.0.11 → ^7.0.1)

* commander (^13.1.0 → ^14.0.3)

* note

* coderabbit feedback, and a test for good measure

* typescript (^5.9.3 -> ^6.0.2)

* @playwright/test (1.58.2 -> 1.59.1)

* yarn dedupe
2026-04-15 17:05:26 +00:00
Stephen Brown II
f8d5d38d0a Skip release notes generation for docs-only PRs (#6815) 2026-04-15 14:52:16 +00:00
Matt Fiddaman
023f34814c ⬆️ bump gh actions (#7507)
* upload-artifact

* codeql-action

* create-pr

* docker

* github-script

* sticky-pull-request-comment

* action-download-artifact

* note
2026-04-15 14:36:28 +00:00
JasmineLCY
2aa2e49df9 [AI] Fix regex checkbox label contrast in notes modal (#7515)
* [AI] Fix regex checkbox label contrast in notes modal (#7514)

Apply theme.menuAutoCompleteText color to the "Use Regular Expressions"
checkbox label in the find-and-replace tab so it contrasts with the
menuAutoCompleteBackground modal background.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: rename release note file to match PR number

The CI expects the release note file to be named after the PR number
(7515), not the issue number (7514).

---------

Co-authored-by: liuren.lcy <liuren.lcy@antgroup.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 13:25:41 +00:00
Aadhith
c9f5f6deb2 Add Nord theme to custom theme catalog (#7513)
* Add nord theme to catalog

* Add release notes
2026-04-15 10:08:21 +00:00
Matiss Janis Aboltins
e109d652b4 [AI] Refactor IS_GENERIC_BROWSER env var to --mode=browser (#7466)
* [AI] Refactor IS_GENERIC_BROWSER env var to --mode=browser

Replace the IS_GENERIC_BROWSER environment variable with Vite's built-in
--mode=browser flag to distinguish browser builds from Electron builds.
This aligns with the existing --mode=desktop pattern used for Electron
production builds.

Also fix build-shims.js to derive NODE_ENV from import.meta.env.DEV
instead of import.meta.env.MODE, so custom modes don't leak into
process.env.NODE_ENV.

https://claude.ai/code/session_014HvkpR59Ke4eUoiUzsUruv

* Add release notes for PR #7466

* [AI] Fix COOP/COEP headers not set with --mode=browser

The server.headers config was gated on mode === 'development', which
excluded --mode=browser. Since server.headers only applies during
vite serve (not builds), always set the COOP/COEP headers. These are
required for SharedArrayBuffer support used by the SQLite backend.

https://claude.ai/code/session_014HvkpR59Ke4eUoiUzsUruv

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-15 07:39:31 +00:00
tempiz
4878c0f333 Add maketransfer to uncategorized transactions (#7496)
* Add maketransfer to uncategorized transactions

Add maketransfer to uncategorized transactions

* retrigger checks

* retrigger checks 2
2026-04-14 23:52:34 +00:00
dependabot[bot]
18886fe166 Bump tar from 7.5.1 to 7.5.13 (#7505)
Bumps [tar](https://github.com/isaacs/node-tar) from 7.5.1 to 7.5.13.
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-tar/compare/v7.5.1...v7.5.13)

---
updated-dependencies:
- dependency-name: tar
  dependency-version: 7.5.13
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-14 22:32:49 +00:00
dependabot[bot]
081b43cf99 Bump jws from 3.2.2 to 3.2.3 (#7504)
Bumps [jws](https://github.com/brianloveswords/node-jws) from 3.2.2 to 3.2.3.
- [Release notes](https://github.com/brianloveswords/node-jws/releases)
- [Changelog](https://github.com/auth0/node-jws/blob/master/CHANGELOG.md)
- [Commits](https://github.com/brianloveswords/node-jws/compare/v3.2.2...v3.2.3)

---
updated-dependencies:
- dependency-name: jws
  dependency-version: 3.2.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-14 22:17:18 +00:00
dependabot[bot]
29827b9be8 Bump electron from 39.8.4 to 39.8.5 (#7413)
Bumps [electron](https://github.com/electron/electron) from 39.8.4 to 39.8.5.
- [Release notes](https://github.com/electron/electron/releases)
- [Commits](https://github.com/electron/electron/compare/v39.8.4...v39.8.5)

---
updated-dependencies:
- dependency-name: electron
  dependency-version: 39.8.5
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-14 21:57:49 +00:00
dependabot[bot]
b5b422f4c8 Bump lodash from 4.17.21 to 4.18.1 (#7502)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.21 to 4.18.1.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.18.1)

---
updated-dependencies:
- dependency-name: lodash
  dependency-version: 4.18.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-14 21:40:42 +00:00
dependabot[bot]
21408f1e66 Bump vite from 8.0.0 to 8.0.8 (#7501)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.0.0 to 8.0.8.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.8/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.8
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-14 21:27:47 +00:00
Trevin Chow
c77b4cc220 Add scoped ErrorBoundary to Rules page to contain rendering crashes (#7437)
* [AI] Add scoped ErrorBoundary to Rules page to contain rendering crashes

* [autofix.ci] apply automated fixes

* docs: add release notes for ErrorBoundary PR

* fix(rules): add resetKeys to ErrorBoundary for route navigation reset

In react-error-boundary v6, the boundary does not auto-reset when the
user navigates away and back. Adding resetKeys={[location.pathname]}
ensures the error state clears on route changes.

This contribution was developed with AI assistance (Claude Code).

* fix(rules): show error message in fallback UI and log to console

Addresses reviewer feedback on #7437: surface error.message in the
FeatureErrorFallback so users can copy-paste when reporting issues,
and log the error to the console via useEffect so the error isn't
swallowed by the boundary.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-04-14 21:06:32 +00:00
Matiss Janis Aboltins
eac26fa4ef Remove duplicate excludes.txt entry (#7499)
* Remove duplicate excludes.txt entry

Remove package-lock.json from spelling exclusions

* Add release notes for PR #7499

* Fix duplicate exclusion in spelling checks

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-14 20:48:32 +00:00
dependabot[bot]
d27db77164 Bump follow-redirects from 1.15.11 to 1.16.0 (#7498)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.11 to 1.16.0.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.11...v1.16.0)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-version: 1.16.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-14 20:35:48 +00:00
Matiss Janis Aboltins
c7876a58cb Revert "[AI] Add project-level hook to enforce PR creation rules" (#7491)
* Revert "[AI] Add project-level hook to enforce PR creation rules (#7471)"

This reverts commit d6253c86eb.

* Add release notes for PR #7491

* Delete upcoming-release-notes/7491.md

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-14 17:12:53 +00:00
Stephen Brown II
54a26ae199 [AI] Add BALANCE_OF() function to fetch balance of an arbitrary account (#7335)
* Add BALANCE_OF() function to fetch balance of an arbitrary account

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #7335

* Adjust docs

* [autofix.ci] apply automated fixes

* Update tests to verify sidebar balances

* Add comments to schedule-template.ts

* Update index.ts to recompute sidebar balances

* [autofix.ci] apply automated fixes

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #7335

* Address CodeRabbit comments

* [AI] Implement BALANCE_OF as HyperFormula custom function via context

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* Remove protective if statement when adding _balanceOfPrefetched

* [autofix.ci] apply automated fixes

---------

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>
2026-04-14 13:40:12 +00:00
Michael Clark
bf513ad11c :electron: Desktop app tests for exporting budget file (#7494)
* tests for exporting budget file

* release notes

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #7494

* ditch the vrt, it's done in the cdesktop-client

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-14 07:51:45 +00:00
Matiss Janis Aboltins
4bc8ec876a [AI] Add publishConfig.imports sync validator with pre-commit integration (#7469)
* [AI] Add publishConfig.imports sync validator with pre-commit integration

Add a TypeScript script that validates publishConfig.imports stays in sync
with imports in all packages/*/package.json files. Runs automatically in
the pre-commit hook via lint-staged with --fix mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Add release notes for #7469

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Guard main() with require.main and respect lint-staged file args

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Handle non-string imports targets in derivePublishImports

- Add type guard to check for non-string values in imports
- Throw descriptive error when conditional imports are encountered
- Update type signature to accept Record<string, string | object>
- Add test case for non-string imports error handling

This prevents TypeError when packages have conditional imports (e.g., #browser-preload in desktop-client)

Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>

* [AI] Limit resolvePackageJsonPaths scope to packages directory

- Add packagesRoot constant to restrict path resolution
- Update while loop condition to only traverse within packages directory
- Add additional check to ensure candidate paths are under packages/
- Prevents resolution to repo root package.json for missing/deleted files

This ensures the validator only processes package.json files under packages/* and avoids accidentally targeting the monorepo root manifest.

Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
2026-04-14 07:40:36 +00:00
Matiss Janis Aboltins
c4f3fb0b93 [AI] Fix type errors for API consumers by shipping .d.ts declarations (#7468)
* [AI] Fix type errors for API consumers by shipping .d.ts declarations from loot-core

Downstream consumers of @actual-app/api with strict: true get type errors
because @actual-app/core exports raw .ts source files. Consumers' tsc
follows the import chain into core's source (compiled with strict: false),
and skipLibCheck doesn't help since it only skips .d.ts files.

Add "types" conditions to all imports/exports entries in loot-core's
package.json, pointing to the pre-built declarations in lib-dist/decl/.
Add .npmignore to include lib-dist/decl/ in the published package.

Fixes #7410

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Use prepack/postpack scripts instead of inflating package.json

Replace the inline "types" conditions in imports/exports with a prepack
script that adds them at pack/publish time. This keeps the checked-in
package.json clean while still shipping .d.ts declarations to npm
consumers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Convert prepack/postpack scripts to TypeScript

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Add release notes for #7468

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Fix recursive ExportValue type and remove redundant comment

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Rename scripts to .mts and inline types conditions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Make backup/restore scripts safer

- Check if backup exists before creating it in prepack
- Make restore idempotent by checking if backup exists in postpack
- Prevents overwriting existing backups from interrupted runs
- Addresses CodeRabbit review feedback

Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>

* [AI] Type api-handlers.ts fields to drop implicit any

The `fields` / export-args slots in the ApiHandlers contract were
untyped, surfacing as TS7008 errors in strict consumers. Replace them
with the `Partial<APIXxxEntity>` shapes the `@actual-app/api` wrappers
already pass, and annotate the matching call sites in `api.ts` with
`@ts-expect-error` where the legacy helpers still declare full-entity
parameters despite accepting partial updates at runtime.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Replace vite-plugin-dts with tsgo for api types

Drops vite-plugin-dts in favor of running tsgo --emitDeclarationOnly
after the vite bundle, eliminating a heavy dev dependency tree
(api-extractor, volar, vue language-core) from the api package build.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Add build script to loot-core to emit declarations via lage

`yarn build:cli` failed in CI with TS6305 because api's
`tsgo --emitDeclarationOnly` depends on loot-core's pre-built
`lib-dist/decl/*.d.ts`, but loot-core had no `build` script, so lage's
`^build` cascade silently skipped it. Add `"build": "tsgo -b"` so loot-core
slots into the dependency chain; its tsconfig already has
`emitDeclarationOnly: true`, so the output is declarations only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Simplify API build

* [AI] Document TypeScript moduleResolution requirement for @actual-app/api

The published declarations rely on package.json exports conditions, which
classic node / node10 resolvers don't honor. Document the supported modes
(bundler / nodenext / node16) in the package README and in the Getting
Started section of the API docs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Type-guard default value in add-types-conditions prepack

`value.default` is typed `ExportValue | undefined`, which allows nested
conditional objects. The previous truthy check fell through to
`shouldSkip(defaultValue)` and would crash on `.endsWith()` if that shape
ever appeared. Replace with a `typeof === 'string'` narrowing and drop a
now-redundant "Insert types as the first key" comment.

No runtime change on current package.json — no nested `default` values
exist today — but the script is not covered by loot-core's tsconfig
include, so the latent type issue was silent.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Allow "nodenext" in docs spellcheck expect list

Referenced in the new TypeScript moduleResolution note in the API docs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Move loot-core declarations to @types and whitelist publish with files

Relocate loot-core's composite TypeScript output from lib-dist/decl to the
top-level @types directory, matching the api package's convention. Replace
the old .npmignore blacklist with an explicit package.json files whitelist.

- tsconfig.json: outDir @types, exclude test/mock dirs from decl emission
- scripts/add-types-conditions.mts: rewrite paths to ./@types/src/...
- package.json: files whitelist shipping only src, @types, migrations,
  typings, default-db.sqlite; drop legacy typesVersions (docs now require
  moduleResolution bundler/nodenext/node16, so the classic-resolution
  fallback is unused)
- .gitignore: ignore the new @types build artifact
- lage.config.js: factor outputGlob into a shared BUILD_OUTPUT_GLOBS
  constant and add @types/** so lage caches loot-core's decl output
- root tsconfig.json: tighten exclude from packages/api/@types to
  packages/*/@types to cover both api and loot-core
- delete .npmignore entirely

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Build loot-core declarations inside prepack

yarn workspace @actual-app/core pack is the first non-setup step in the
publish workflow, running before any build. Without a build chained into
prepack the @types/ tree is empty at pack time, so the tarball shipped a
transformed package.json pointing at ./@types/src/... paths that didn't
exist. npm publish doesn't re-run hooks on a pre-packed tarball, so the
frozen snapshot must be self-contained; prepack now runs yarn build first
to populate @types/ before add-types-conditions rewrites the exports.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
2026-04-14 07:40:17 +00:00
youngcw
69cd4fc13a Update sync error message (#7493)
* format

* note

* update test

* change message

* note

* fix
2026-04-13 19:07:22 +00:00
Juulz
35a48cbf15 🎨 You Need A - Theme Dark (#7447)
* Update customThemeCatalog.json

* Update customThemeCatalog.json

* [autofix.ci] apply automated fixes

* Update customThemeCatalog.json

* 🎨 You Need A Theme Dark, based on 2026 nYNAB dark

* YNA Theme Dark

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-04-13 15:17:53 +00:00
Emil Tveden Bjerglund
213185661f [AI] Add Sankey report toggle to view values as percentages (#7476)
* Add Options button. Add toggle for showing values as percentages.

* Add release note

* [autofix.ci] apply automated fixes

* Ensure 0's are also reported as percentages for consistency

* Shorten release note

* Remove reduncant type assertions

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-04-13 14:26:26 +00:00
Michael Clark
4fa658b91e :electron: Playwright test to verify sync server starts (#7484)
* test for opening sync server

* urelease note

* lint

* remove console logs from test

* wait on navigation

* works locally... Pipelines failing

* testing

* more

* rebuild electron before testing

* jeeezoo

* ipv4 issue?

* [autofix.ci] apply automated fixes

* add rebuild step to vrt

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #7484

---------

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: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-13 09:58:20 +00:00
Michael Clark
95b2925be6 :electron: Fix desktop app playwright vrt setup (#7490)
* fix desktop app playwright vrt tests

* release notes

* add bcrypt in as well

* install build tools for bcrypt....
2026-04-13 08:04:15 +00:00
Matt Fiddaman
db72948d7c change release process to use a branch and add automation (#7418)
* release automation

* note

* harden tokens for release notes workflow

* fix yarn install on release notes check

* amend release docs

* address coderabbit

* change back to auto version resolution

* more coderabbit

* clarify a poorly worded comment

* drop cherry pick workflows

* fix docker tag

* coderabbit

* drop `v` from release branch name

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

* simplify release note workflows

---------

Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
2026-04-12 22:17:14 +00:00
Matt Fiddaman
2c4d64eaac fix docker tags in docs (#7486) 2026-04-12 20:06:08 +00:00
Matiss Janis Aboltins
78b1fc2713 [AI] Add no-extraneous-dependencies lint rule (#7480)
* [AI] Add no-extraneous-dependencies lint rule to prevent transitive dependency usage

Closes #7479. Adds a custom ESLint rule that flags imports of packages not
explicitly listed in the workspace's dependencies or devDependencies. Also
fixes all existing violations by adding missing deps and removes unused
deps (@reduxjs/toolkit, @rschedule/json-tools) from loot-core.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Fix builtin subpath detection and improve cache in no-extraneous-dependencies

Fix false positives for Node.js builtin subpaths (fs/promises, path/posix)
by checking the package name portion against builtins. Also cache all
visited directories during walk-up, not just the starting directory.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Release notes

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 16:11:26 +00:00
Juulz
d2475ebb02 [Docs] Update tags doc for clarity - fixes #7475 (#7485)
* Update tags.md

* [autofix.ci] apply automated fixes

* Update tags.md

* Update tags.md

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-04-12 13:46:47 +00:00
Michael Clark
78dad7c91b Docs - fonts (#7477)
* making the docs fonts match the app fonts

* quote the blink mac sys font
2026-04-12 08:43:14 +00:00
Matiss Janis Aboltins
410658009e [AI] Fix sync-server build not resolving subpath imports (#7478)
* [AI] Fix sync-server build not resolving subpath imports

The add-import-extensions build script only handled relative imports
(./  ../), leaving #-prefixed subpath imports unresolved in the build
output. At runtime Node.js resolved them via package.json's imports
map back to source files, which have extensionless imports that fail
in ESM.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Add release notes for sync-server subpath imports fix

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Simplify subpath import resolution using publishConfig.imports

Use publishConfig.imports which already has ./build/src/ paths with .js
extensions, eliminating manual src->build and .ts->.js conversions.
Also sort wildcard patterns by specificity and extract shared helper.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Use replaceAll for wildcard substitution to satisfy CodeQL

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 05:36:55 +00:00
Matiss Janis Aboltins
a69e33bd7b [AI] Upgrade oxlint, oxfmt, and oxlint-tsgolint to latest versions (#7463)
- oxlint ^1.51.0 → ^1.59.0
- oxfmt ^0.32.0 → ^0.44.0
- oxlint-tsgolint ^0.13.0 → ^0.20.0
- Remove rules dropped by oxlint: import/no-unresolved, import/no-unused-modules,
  import/no-useless-path-segments, react/no-unstable-nested-components
- Rename eslint/no-use-isnan → eslint/use-isnan
- Remove deprecated moduleResolution: "node10" from crdt and desktop-electron tsconfigs

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 05:36:29 +00:00
Juulz
0025010382 [Docs] Update text and image in restore doc to Switch file from Close file. (#7473)
* Update restore.md

* Add files via upload
2026-04-11 22:57:59 +00:00
Matiss Janis Aboltins
d6253c86eb [AI] Add project-level hook to enforce PR creation rules (#7471)
* [AI] Add project-level hook to enforce PR creation rules

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Fix hook JSON output format to use hookSpecificOutput

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Rename 7470.md to 7471.md

* Update upcoming-release-notes/7471.md

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-04-11 15:37:26 +00:00
Matiss Janis Aboltins
5220dff75b Prevent path traversal in file upload by using basename (#7428)
* [AI] Fix path traversal vulnerability in uploadFileWeb

Sanitize user-supplied filename with fs.basename() to strip directory
components (e.g. ../../) before writing to /uploads/, preventing
arbitrary file writes outside the intended directory.

https://claude.ai/code/session_01UgQANWBxqkqVT7xGyWNAXB

* [AI] Harden path traversal fix and correct broken basename in web fs

The browser fs.basename implementation was returning the directory part
instead of the filename, making the previous fs.basename() fix
ineffective on the web platform. Replace with inline sanitization that
works regardless of platform: split on path separators, strip null
bytes, reject . and .., and use fs.join for safe path construction.

Also fix the browser fs.basename to actually return the last path
segment, matching the behavior of Node's path.basename.

https://claude.ai/code/session_01UgQANWBxqkqVT7xGyWNAXB

* [AI] Revert browser fs.basename change per user request

The browser fs.basename implementation is restored to its original
behavior. The path traversal fix in uploadFileWeb does not depend on
fs.basename.

https://claude.ai/code/session_01UgQANWBxqkqVT7xGyWNAXB

* [AI] Add release notes for path traversal fix (#7428)

https://claude.ai/code/session_01UgQANWBxqkqVT7xGyWNAXB

* [AI] Suppress no-control-regex lint for null byte sanitization

The \0 regex is intentional to strip null bytes from filenames as part
of the path traversal fix.

https://claude.ai/code/session_01UgQANWBxqkqVT7xGyWNAXB

* [AI] Use replaceAll for null-byte stripping instead of regex

Replace /\0/g regex with replaceAll('\0', '') to avoid triggering
the no-control-regex ESLint rule, removing the need for the
eslint-disable comment.

https://claude.ai/code/session_01UgQANWBxqkqVT7xGyWNAXB

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-11 11:28:13 +00:00
Matiss Janis Aboltins
65b154151f [AI] Enable subpath imports across all packages (#7462)
* [AI] Enable subpath imports across all packages

Generalize the prefer-subpath-imports ESLint rule to work with any
package (not just loot-core) and enable it globally. Add subpath import
mappings to cli, component-library, and sync-server package.json files.
Auto-fix all backtracked relative imports to use #-prefixed subpath
imports.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Add release notes for #7462

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Fix mock specifiers in accounts.test.ts to use aliased imports

Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>

* [AI] Fix mock specifiers in query.test.ts to use aliased imports

Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>

* [AI] Fix ESLint rule to properly validate src/ directory paths

Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>

* [AI] Add publishConfig.imports for sync-server to remap aliases to build directory

Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
2026-04-11 08:56:49 +00:00
Michael Clark
4aa6ac76b4 Update docs logo and cleanup some unused files (#7444)
* update docs logo and cleanup some unused files

* remove icon.png

* keep old logo around in archived
2026-04-11 08:03:37 +00:00
Kennedy242
a87676bd84 Fix bug where total selected balance is not shown when it is zero (#7460)
* Write a test to catch this case

* Handle falsey values better in 'Selected balance' logic
2026-04-10 22:49:54 +00:00
Matiss Janis Aboltins
e322b0319c Update pre-commit hook configuration (#7461)
* [AI] Set .husky/pre-commit hook as executable

The pre-commit hook was being ignored by git because it lacked the
executable permission bit, producing a warning on every commit attempt.

https://claude.ai/code/session_016jLmTo6L5PxMKK8wJMptCP

* Add release notes for PR #7461

* Update 7461.md

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-10 22:46:51 +00:00
Matiss Janis Aboltins
4960363de6 [AI] Stop using .browser extension; removing "resolve.extensions" - prefer conditions via package.json (#7254)
* [AI] Consolidate loot-core connection: default web path, electron split, drop .browser

* [autofix.ci] apply automated fixes

* [AI] Replace browser-preload .browser extension with package.json subpath imports

Use the imports field in desktop-client/package.json with conditional
resolution (electron → empty stub, default → real implementation) to
eliminate the last .browser file extension from the codebase.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Refactor connection imports to use @actual-app/core

* Implement connection mock for desktop-client tests and update import path

* [AI] Fix formatting and update imports after master merge

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Fix connection mock in TransactionsTable tests and use electron-renderer condition

Wire up the manual connection mock for TransactionsTable tests since the
__mocks__ directory was removed, and restore electron-renderer condition
in loot-core package.json exports.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [autofix.ci] apply automated fixes

* [AI] Remove redundant resolveExtensions from vite configs

These arrays were identical to Vite's built-in default and served no purpose.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Remove remaining resolveExtensions from vite/vitest configs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Fix build failures: update browser-preload import path and condition

- Change loot-core/shared/platform to @actual-app/core/shared/platform
- Use electron-renderer condition for #browser-preload to match vite resolve

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Remove redundant resolveExtensions from api and loot-core desktop configs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Remove '*.browser.ts' extension and alias resolutions

Removed the special '*.browser.ts' file extension and file resolutions via alias, preferring conditions.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 21:32:51 +00:00
Matiss Janis Aboltins
d76d7d3204 Security hardening: validate release notes and workflow inputs (#7448)
* [AI] Harden GitHub Actions workflows against low-severity security issues

- generate-release-pr.yml: replace `eval` with an associative array for
  per-package version tracking. The version input was already moved to an
  env var in #7433, so this removes the remaining defense-in-depth concern
  of `eval`ing subshell output.
- create-release-notes-file.js: validate the OpenAI-returned category
  against the known allow-list (Features, Bugfixes, Enhancements,
  Maintenance), validate the author against the GitHub username regex,
  and collapse the summary to a single line before embedding it in the
  markdown body. Prevents indirect prompt-injection via CodeRabbit
  comments from producing malformed YAML frontmatter.
- generate-summary.js: stop logging the full CodeRabbit comment body to
  CI logs.
- netlify-release.yml, i18n-string-extract-master.yml: pass secrets via
  `env:` blocks rather than as CLI arguments, so they do not appear in
  argv / process listings.

https://claude.ai/code/session_012pZSkUBbabmmuaxbwysW33

* Add release notes for PR #7448

* [AI] Address review feedback on security hardening

- create-release-notes-file.js: stop logging the full fileContent body.
  Only log the target filename plus the (already-validated) category and
  author metadata, so the model-generated release-note text doesn't end
  up in CI logs.
- create-release-notes-file.js: validate summaryData.prNumber as a
  positive integer before using it in the file path or commit message,
  and switch both usages to the validated numeric value.
- i18n-string-extract-master.yml: write the Weblate API key into
  ~/.config/weblate under a [keys] section in a new "Configure Weblate
  API credentials" step, then drop the per-step env blocks and the
  --key CLI flag from every wlc invocation so the secret is no longer
  visible in process listings at all.

https://claude.ai/code/session_012pZSkUBbabmmuaxbwysW33

* [AI] Remove debug console.log statements for category in release notes script

Remove the four "Debug - ..." console.log calls that printed the raw
category env var (value/type/JSON-stringified form) plus the cleanCategory
value. They were clutter in CI logs; the existing info-level
"Creating release notes file: ... (category: ..., author: ...)" log
already surfaces the sanitized category.

https://claude.ai/code/session_012pZSkUBbabmmuaxbwysW33

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-10 17:11:52 +00:00
Matiss Janis Aboltins
d8317c44b7 [AI] Migrate desktop-client to subpath imports (#7446)
* [AI] Migrate desktop-client to subpath imports

Replace the `@desktop-client/*` path alias with Node.js subpath
imports (`#*`) across packages/desktop-client:

- Declare the full `imports` map in packages/desktop-client/package.json
  (bare index entries, root-level files, and per-subdirectory wildcards
  with explicit extension overrides where `.ts` and `.tsx` mix).
- Update all source files to import from `#...` specifiers.
- Drop the `@desktop-client` group from .oxfmtrc.json.
- Enable `actual/prefer-subpath-imports` for desktop-client in
  .oxlintrc.json so future code keeps using the subpath form.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Drop legacy desktop-client aliases

Remove the `@desktop-client/*` and `loot-core/*` path aliases from
vite.config.ts and tsconfig.json now that every desktop-client source
file imports via subpath imports / `@actual-app/core`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Release notes

* [AI] Use electron-renderer condition for renderer-only exports

Desktop-client's Vite build used the `electron` resolve condition, which
overlapped with loot-core exports where `electron` means the Node/main
variant (e.g. `shared/platform.electron.ts` using `os`,
`platform/server/asyncStorage/index.electron.ts` using `fs`). Once the
`loot-core` Vite alias was removed, the renderer bundle started pulling
those Node variants and crashed at runtime with
`It.default.platform is not a function` inside `platform.electron.ts`.

Introduce a distinct `electron-renderer` condition used only by
desktop-client's Vite config, and rename the `electron` key to
`electron-renderer` on the sole loot-core export whose `electron` branch
is the Electron renderer variant (`#/./platform/client/connection`, the
IPC `global.Actual.ipcConnect` file). Every other `electron`-conditioned
export keeps its Node semantics and is still matched by loot-core's own
`vite.desktop.config.ts` (`conditions: ['electron']`).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Drop .electron.* extensions from loot-core desktop resolver

Now that every Node/main variant is selected via the `electron` subpath
import condition in `packages/loot-core/package.json`, Vite's
`resolveExtensions` list no longer needs the `.electron.js`,
`.electron.ts`, `.electron.tsx` entries. Remove them to keep resolution
explicit and avoid implicit extension picking.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Align desktop-client TS resolution with Vite

- Set `customConditions: ["electron-renderer"]` in
  `packages/desktop-client/tsconfig.json` so TypeScript resolves
  conditional imports (notably `@actual-app/core/platform/client/connection`)
  to the same file Vite picks at runtime. Today the surfaces happen to
  match because both variants import from a shared `index-types.ts`,
  but the alignment prevents a latent drift bug.
- Fix typo in the release note (`Standartise` -> `Standardise`).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 17:03:45 +00:00
Michael Clark
c2c6d49afa Upgrade docusaurus (#7445)
* upgrade docusaurus

* release notes
2026-04-10 08:06:31 +00:00
Emil Tveden Bjerglund
6a96231c1a Add more filtering options for transactions in the Sankey report (#7442)
* Allow more filters in Spent mode

* Add release note

* Add translation do displayed fields. Fix typo.
2026-04-09 18:59:17 +00:00
Juulz
085355b467 🎨 Add You Need A (YNA) Theme Light (#7441)
* Update customThemeCatalog.json

Add YNA Theme Light

* [autofix.ci] apply automated fixes

* 🎨 You Need A Theme Light

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-04-09 18:55:21 +00:00
Juulz
23313b3ac5 Change formatting of reconcile form for clarity and ease of use (#7423)
* Update Reconcile.tsx

* Update Reconcile.tsx

* Update Reconcile.tsx

* Update Reconcile.tsx

* Update Reconcile.tsx

* Update Reconcile.tsx

* Update Reconcile.tsx

* [autofix.ci] apply automated fixes

* Update Reconcile.tsx

* Update Reconcile.tsx

* [autofix.ci] apply automated fixes

* Change formatting of reconcile form for clarity and ease of use.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-04-09 14:09:58 +00:00
Matiss Janis Aboltins
f364d5a9d8 [AI] Add subpath import wildcard patterns to loot-core (#7429)
* [AI] Add subpath import wildcard patterns and explicit directory entries to loot-core

Extend the package.json imports field with prefix-specific wildcard patterns
(#server/*, #shared/*, #types/*, #mocks/*, #platform/*) and explicit entries
for directory imports (#server/db, #server/sync, etc.) to support the ongoing
migration from relative imports to subpath imports.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Add release notes for #7429

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
2026-04-09 08:02:25 +00:00
Tamás Szelei
e6bd684812 [AI] Update Age of Money Report discussion link to issue #7006 (#7421) 2026-04-08 23:05:56 +00:00
Matiss Janis Aboltins
4d0f0f740d Update browserlist (caniuse-lite) (#7431)
* [AI] Update browserslist caniuse-lite database

Update caniuse-lite from 1.0.30001751 to 1.0.30001787 and
baseline-browser-mapping from 2.9.14 to 2.10.16.

https://claude.ai/code/session_01Q4x7bPh2tkKDKcbM4kfHNo

* [AI] Add release notes for browserslist update

https://claude.ai/code/session_01Q4x7bPh2tkKDKcbM4kfHNo

* Update 7431.md

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-08 22:35:21 +00:00
Matiss Janis Aboltins
20ba076a51 Add rate limiting to authentication endpoints (#7432)
* [AI] Add rate limiting to authentication endpoints

Add strict rate limiting (5 attempts per 15 minutes) to /account/login,
/account/bootstrap, and /account/change-password endpoints to prevent
brute-force password attacks. Uses express-rate-limit as route-level
middleware on auth-sensitive routes only.

https://claude.ai/code/session_017SHnNCn93RzxpvEEPJAZUZ

* [AI] Add release notes and remove rate limit from /change-password

Add upcoming release notes file for the auth rate limiting feature.
Remove rate limiting from /change-password since it already requires
a valid admin session token.

https://claude.ai/code/session_017SHnNCn93RzxpvEEPJAZUZ

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-08 22:21:04 +00:00
Matiss Janis Aboltins
4efa8bba04 Fix script injection patterns in GitHub Actions workflows (#7433)
* [AI] Fix script injection in vrt-update-apply.yml workflow

Use environment variables instead of direct expression interpolation
in the github-script step to prevent potential script injection via
artifact-sourced values (steps.apply.outputs.error and
steps.metadata.outputs.pr_number).

https://claude.ai/code/session_01V28NTQAXTvSfwyoDhWpWo9

* [AI] Fix script injection in generate-release-pr.yml workflow

Use environment variable instead of direct expression interpolation
for github.event.inputs.version in the shell script context to
prevent potential command injection.

https://claude.ai/code/session_01V28NTQAXTvSfwyoDhWpWo9

* [AI] Add release notes for #7433

https://claude.ai/code/session_01V28NTQAXTvSfwyoDhWpWo9

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-08 22:19:45 +00:00
Matiss Janis Aboltins
1f3b4e613d Pin check-spelling action versions to specific commits (#7430)
* [AI] Pin check-spelling actions to commit SHAs in docs-spelling.yml

Pin check-spelling/check-spelling@main and check-spelling/spell-check-this@prerelease
to specific commit SHAs to prevent supply chain attacks. The update job is especially
sensitive as it has contents:write, pull-requests:write permissions and access to the
CHECK_SPELLING SSH deploy key.

https://claude.ai/code/session_01FK9KT4VRxvm24bb18Q9rvM

* [AI] Fix spell-check-this prerelease pin to correct commit SHA

https://claude.ai/code/session_01FK9KT4VRxvm24bb18Q9rvM

* [AI] Add release notes for pinning check-spelling action versions

https://claude.ai/code/session_01FK9KT4VRxvm24bb18Q9rvM

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-08 21:38:07 +00:00
youngcw
926f7193f9 fix formula date variable in rules (#7373)
* format

* note

* update test
2026-04-08 15:47:19 +00:00
Eduardo Pio
092b85e075 Fix/5840 escape search wildcards (#7270)
* Escape LIKE wildcards; support ? in unicodeLike

* add release notes

* [autofix.ci] apply automated fixes

* Revert "[autofix.ci] apply automated fixes"

This reverts commit d8a2843a5b.

* [autofix.ci] apply automated fixes

* fix: address bot review feedback for escapes

* Support escaped backslash

* Add tests for escaped characters in unicodeLike

* [autofix.ci] apply automated fixes

* Remove underscore from escaped search regex

* Refactor regex escaping to use REGEX_SPECIAL constant

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-04-08 15:37:41 +00:00
gust0717
2bbcbaee73 Fixes query for tag starting in $ (#7324)
* Fixes query for tag starting in $

* Update packages/loot-core/src/server/transactions/transaction-rules.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply coderabbitai suggestion

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-08 14:45:00 +00:00
tabedzki
446fde6cd9 Add Category Group filtering in Budget Analysis Report (#7116)
* feat: add FilterInclude to match FilterExclude

* feat: enhance category filtering to support category groups in budget analysis

* add release notes

* fix: applied corabbit's suggestion

* applied coderabbit's fixes

* removed duped code

* applied more coderabbit comments

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes

* fix: include docs about include parameter

* fix: contains and matches now properly filter category names instead of UUID

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: youngcw <calebyoung94@gmail.com>
2026-04-08 14:34:26 +00:00
James Skinner
7ce44c2e56 Migrate add-attribute plugin to TypeScript (#7417)
* Migrate add-attribute plugin to TypeScript

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-04-08 14:02:45 +00:00
Matiss Janis Aboltins
e0772e24cd [AI] Add ErrorBoundary around dashboard widgets (#7382)
* [AI] Add ErrorBoundary around dashboard widgets (#7273)

Wraps each dashboard widget in an ErrorBoundary so a faulty widget
degrades to an error card instead of crashing the entire Reports page.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add release notes for PR #7382

* [autofix.ci] apply automated fixes

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-04-08 06:41:31 +00:00
Emil Tveden Bjerglund
3d5881ea57 Fix Net Worth graph not showing correct number of intervals (#7296)
* Fix Net Worth graph showing N-1 intervals, resulting in inconsistent totalChange value.

* Fix starting date being wrong for 'daily' and 'weekly'

* Linting

* Add release note

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #7296

* Remove manipulation of startDate for 'yearly'

The result was not consistent with the reports previous behavior when yearly was selected (it went back too far).

* Remove empty datapoint at beginning when start equals earliest transaction

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-08 02:40:28 +00:00
Matiss Janis Aboltins
2295e6d464 [AI] Add electron conditions to loot-core platform/server exports (#7383)
* trim down some unused/unnecessary dependencies (#7350)

* fix github actions inconsistencies

* fix pinning of transitive deps in eslint-plugin

* drop use of node-fetch in api

* drop md5 dependency in favour of node:crypto

* drop slash

* drop unused top level packages

* add note about node-polyfills warning

* remove unused deps from desktop-client

* drop pegjs types

* note

* drop node-jq

* [Doc] More tour image (mostly) updates & a hotkey fix (#7328)

* Fix keyboard shortcut Mac key for undo operations

Updated keyboard shortcut instructions for Mac & make consistent.

* Add files via upload

* Fix undo shortcut from 'K' to 'Z'

Updated keyboard shortcut for undo operation in payees guide. COFFEE!

* Revise budget section for clarity and consistency

Updated category descriptions and improved Markdown support details.

* Add files via upload

* Fix grammatical error in budget.md

* Fix typo and clarify Markdown description in budget.md

Corrected a typo in the documentation regarding the chevrons and clarified the description of rendered Markdown.

* Fix spelling error in budget documentation

Corrected the spelling of 'cheverons' to 'chevrons'.

* Add files via upload

* Remove redundant text in budget.md

* Fix formatting issues in payees.md

* count points script should fetch the release note from the PR directly (#7309)

* get pr release note from PR, not top of master

* note

* [AI] Mobile: Post transaction today on global account lists (#7311) (#7322)

* [AI] Mobile: pass today for Post transaction today on global account lists (#7311)

All Accounts, On budget, and Off budget transaction lists now forward the
today flag to schedule/post-transaction, matching single-account mobile
and desktop behavior.

Made-with: Cursor

* [AI] Add release note for PR 7322 (#7311)

Made-with: Cursor

* [AI] Tighten release note wording for PR 7322 (imperative)

Made-with: Cursor

---------

Co-authored-by: Pranay Mac M1 <pranayseela@yahoo.com>

---------

Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
Co-authored-by: Pranay S <pranayritvik@gmail.com>
Co-authored-by: Pranay Mac M1 <pranayseela@yahoo.com>
Co-authored-by: youngcw <calebyoung94@gmail.com>

* Implement Sankey report for spent and budgeted money (#7220)

* Implement Sankey graph report

* Add release notes

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6068

* Remove local debug settings

* [autofix.ci] apply automated fixes

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6068

* Improve graphs from comments

* Fix lints

* coderabit fixes

* Fix filtering and UI enhancements

* remove pngs

* Fix typecheck

* Another type issue

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6068

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6068

* Fix strict typing issues

* Update report page

Now better conforms with components from other reports, e.g. by reusing Header
Makes it possible to display a period longer than one month.

* Change view description order

* Formatting and cleanup

* Removed difference section, as it will be difficult to get a reliable view across months

* Introduce the Timeframe param, similar to Spending report, to allow saving a Live sliding window.

* Allow filtering just the last month

* Fix linting errors

* Remove all information about income

* Remove debugging statement

* Sort categories and subcategories by amount

* Move compact mode to spreadsheet to fix Card view more easily

* Update tests file

* Add release notes

* Rename release notes to match PR#

* Fix autofix.ci issues

* Update packages/desktop-client/e2e/sankey.test.ts

Enable experimental feature fall all tests, pr. coderabbit recommendation

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

* Add sankey-card to isWidgetType

* Gate Sankey routes to prevent direct URL bypass

* Fix typo

* Change node transformation to work by key instead of name, to remove risk of duplicate issues

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

* Prevent false-positive pass in month-change test.

* Translate mode to a proper label

* Fix message for empty data

* Enabled LoadingIndicator until data is ready

* Change card default mode

* More robust filtering

* Fixed issue with budgeted spreadsheet not using 'end' date

* Allow copying SankeyCard to dashboard

* Fix typing and linting issues

* Remove e2e tests

I cannot currently get them to pass, because I dont fully understand playwright and how they are supposed to work. I can see that they don't exist for other reports. We can add them later if required.

* Remove unecessary sankey reference

* Refactor spreadsheet

* Remove dead code from SankeyGraph

* Collect to Other if too many subcategories

* Edit wrong comment

* Linting and typechecking

* Show remaining amount to budget

* Hide description on narrow device

* Add visual clue if 'To budget' is larger than 'Budgeted' and would extend below the edge of the graph

* Add colors to the links

* Fix report card showing subcategories instead of main categories

* Add tooltip info to Other on SankeyCard

* Create globalOther flag and implement greedy category reduction algorithm

* Allow user to select between Global or Per category Other

* Allow user to choose number of subcategories to show

* Allow user to select how subcategories are sorted

* Fix budget filtering

* [autofix.ci] apply automated fixes

* Condense sorting and Other-grouping to one option

* Implement Sort as budget option

* Dynamically adjust topN based on SankeyCard height

* Remove old feature flags from previous PR

---------

Co-authored-by: andrewhumble <43395285+andrewhumble@users.noreply.github.com>
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: youngcw <calebyoung94@gmail.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Fix yarn generate:icons command (#7281)

* fix icon templates with `module.exports` to `export default`

* Add `@svgr/babel-plugin-add-jsx-attribute` to dependencies

* Run `yarn generate:icons`, and set prettier singleQuote to reduce changes

* Add release note

* Add temporary fix for `SvgChartArea`

* Add `ChartArea` svg from the existing tsx

* CI rerun

* Standardise ledger scrolling when using keyboard shortcuts (#7283)

* Standardise table keyboard navigation by preventing browser scroll with arrow keys

* Add release note

* Apply the preventDefault() in specific cases so that it is not applied to default

---------

Co-authored-by: youngcw <calebyoung94@gmail.com>

* Fix updateTransaction corrupting split parents with partial updates (#7242)

* [AI] Fix updateTransaction corrupting split parents with partial updates

When `api.updateTransaction(id, { notes: '...' })` is called on a split
parent, the `updateTransaction` helper replaces the parent with the
sparse update object (`{ id, notes }`) instead of merging it with
the existing transaction data.  This causes `recalculateSplit` to see
`amount` as `undefined` (→ 0), which doesn't match the children's
total and sets a `SplitTransactionError` on the parent.  `makeChild`
also inherits undefined `account`, `date`, and `cleared` values,
potentially creating broken child rows.

Fix: merge the incoming partial fields (`{ ...trans, ...transaction }`)
so all existing properties are preserved.

Add a test that performs a notes-only update on a split parent and
asserts no error is set and the amount stays intact.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* [AI] Add release notes for PR #7242

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Address review feedback: remove verbose comment and simplify release note

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: L. Warren Thompson <lwarrenthompson@Warren-MBP.local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* [AI] Add electron conditions to loot-core platform/server exports and fix imports

- Add "electron" condition to platform/server exports (asyncStorage,
  connection, fetch, fs, sqlite) so they resolve to .electron.ts files
  when using the electron export condition
- Remove broken ./client/platform export referencing non-existent files
- Convert deep relative imports in electron files to subpath imports
  (#types/prefs, #server/errors, #server/mutators)

https://claude.ai/code/session_01FPpKnozt42Mf79YHAT6ytM

* [AI] Convert remaining relative imports to subpath imports in electron files

- Convert ../fs, ../log, ../../exceptions to subpath imports
  (#platform/server/fs, #platform/server/log, #platform/exceptions)
- Add electron-conditional entries to the imports field in package.json
  for all 5 platform/server modules with electron variants
- Add resolve.conditions: ['electron'] to vite.desktop.config.ts so the
  electron condition is recognized during desktop builds

https://claude.ai/code/session_01FPpKnozt42Mf79YHAT6ytM

* Add release notes for PR #7383

* [AI] Fix API build and test failures from conditional exports

- Add "api" condition to all 5 platform/server exports and imports
  entries so the API build resolves to .api.ts variants correctly
- Add resolve.conditions and ssr.resolve.conditions: ['api'] to
  packages/api/vite.config.ts
- Add explicit #platform/server/log and #platform/exceptions entries
  to the imports field (array fallback in #* wildcard doesn't work for
  directory modules)
- Revert #platform/server/fs back to relative ../fs import in
  asyncStorage/index.electron.ts — subpath imports for platform modules
  with electron variants don't work in vitest because the test runner
  doesn't propagate resolve.conditions to Node's import resolution

https://claude.ai/code/session_01FPpKnozt42Mf79YHAT6ytM

* fix: apply CodeRabbit auto-fixes

Fixed 2 file(s) based on 2 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>

* [autofix.ci] apply automated fixes

* Enhance package.json and Vite configurations for Electron support; refactor imports to use new path aliases. Added new paths for server and shared modules, updated SSR settings, and improved test configurations for better module resolution.

* [AI] Merge electron condition with default Vite conditions in vitest config

Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>

* [AI] Move @ts-strict-ignore comment to first line in reset.ts

Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>

---------

Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
Co-authored-by: Juulz <julesmcn@gmail.com>
Co-authored-by: Pranay S <pranayritvik@gmail.com>
Co-authored-by: Pranay Mac M1 <pranayseela@yahoo.com>
Co-authored-by: youngcw <calebyoung94@gmail.com>
Co-authored-by: Emil Tveden Bjerglund <emilbp@gmail.com>
Co-authored-by: andrewhumble <43395285+andrewhumble@users.noreply.github.com>
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: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: James Skinner <56730344+JSkinnerUK@users.noreply.github.com>
Co-authored-by: L. Warren Thompson <warren.thompson@zuirail.com>
Co-authored-by: L. Warren Thompson <lwarrenthompson@Warren-MBP.local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
2026-04-07 22:17:43 +00:00
Matt Fiddaman
1e8ad9a89f fix loot-core version bump in generate script (#7414) 2026-04-07 22:17:18 +00:00
Matt Fiddaman
5009f01218 generate docs from release notes directly (#7408)
* move workflows to use the local actions

* generate docs from release notes directly

* note

* fix create PR workflow from SHA
2026-04-07 21:00:32 +00:00
Michael Clark
6decd9d0f6 Move view styles into view component (#7412)
* move view styles into view component

* release notes

* use viewstyles on autocomplete
2026-04-07 20:39:37 +00:00
JkBoyo
5809292579 Capture sync time for initial account linking Fixes #7282 (#7347)
* Capture sync time for initial account linking

* Fix id to point to correct id

* Add release notes

* fix indenting
2026-04-07 20:31:00 +00:00
Matiss Janis Aboltins
edc0242203 [AI] Fix CLI accounts list: compute balances, hide closed, sort by group (#7378)
* [AI] Fix accounts list: compute balances, hide closed, sort by budget group

- Replace empty `balance_current` (bank-sync field) with computed `balance`
  from transaction history via `getAccountBalance`
- Filter out closed accounts by default; add `--include-closed` flag
- Stable-sort on-budget accounts before off-budget
- Add `balance_current` to AMOUNT_FIELDS for table/csv formatting
- Update docs and tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Extract duplicate isRecord type guard to shared utils

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Add types condition to api package exports for tsc resolution

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Update packages/cli/src/commands/query.ts

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

* [AI] Add balance_available/balance_limit to AMOUNT_FIELDS, validate query result.data

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
2026-04-07 19:28:46 +00:00
Matiss Janis Aboltins
4fe4421ab7 [AI] Fix crash viewing account ledger with expired recurring schedules (#7381)
* [AI] Fix crash viewing account ledger with expired recurring schedules

Guard against null return from getNextDate() when a recurring schedule
has an end date in the past and no future occurrences exist.

Fixes #7285

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add release notes for PR #7381

* [AI] Address code review feedback for PR #7381

Revert schedule-template.ts changes and fix test names/assertions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Simplify bugfix description for account ledger crash

Removed redundant information about null checks in the bugfix description.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-07 18:14:28 +00:00
Matiss Janis Aboltins
78739b926b [AI] Clarify that E2E encryption does not cover bank sync tokens (#7392)
* [AI] Clarify that E2E encryption does not cover bank sync tokens (#5550)

Update docs and in-app text to make clear that end-to-end encryption
only applies to budget data, not bank sync tokens stored on the server.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add release notes for PR #7392

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
2026-04-07 17:16:19 +00:00
Matt Fiddaman
d42f6c7437 improve release notes actions (#7407)
* consolidate find-comment, post-comment into single action

* use release notes in current branch for both generation and deletion

* note
2026-04-07 17:16:14 +00:00
Matt Fiddaman
ba780514f6 prevent messages being dropped after closing last budget (#7411)
* prevent messages being dropped after closing last budget

* note
2026-04-07 17:14:28 +00:00
Trevin Chow
a84fb3dae1 Fix custom report editor retaining unsaved settings (#7356)
* Fix custom report editor retaining unsaved settings

The session storage clear condition only fired when navigating from
the /reports dashboard. Since the URL tracking runs inside the report
component, the stored URL always pointed to the last report path, so
revisiting the same report never triggered the clear.

Changed the condition to clear session storage whenever the stored URL
differs from the current path. This handles navigating from the
dashboard, from another report, or any other page.

Fixes #7332

* Add release notes for PR #7356
2026-04-07 16:37:43 +00:00
Tamás Szelei
e3dd3d1d5a Add Age of Money report (#2994) (#6685) 2026-04-07 15:05:45 +00:00
James Skinner
477b1873e2 Extend normalisation utility for non-latin diacritic character handling (#7284)
* Extend normalisation utility for non-latin diacritic character handling

* Add release note

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-04-07 14:54:32 +00:00
dependabot[bot]
59192c9b02 Bump lodash from 4.17.23 to 4.18.1 (#7380)
* trim down some unused/unnecessary dependencies (#7350)

* fix github actions inconsistencies

* fix pinning of transitive deps in eslint-plugin

* drop use of node-fetch in api

* drop md5 dependency in favour of node:crypto

* drop slash

* drop unused top level packages

* add note about node-polyfills warning

* remove unused deps from desktop-client

* drop pegjs types

* note

* drop node-jq

* [Doc] More tour image (mostly) updates & a hotkey fix (#7328)

* Fix keyboard shortcut Mac key for undo operations

Updated keyboard shortcut instructions for Mac & make consistent.

* Add files via upload

* Fix undo shortcut from 'K' to 'Z'

Updated keyboard shortcut for undo operation in payees guide. COFFEE!

* Revise budget section for clarity and consistency

Updated category descriptions and improved Markdown support details.

* Add files via upload

* Fix grammatical error in budget.md

* Fix typo and clarify Markdown description in budget.md

Corrected a typo in the documentation regarding the chevrons and clarified the description of rendered Markdown.

* Fix spelling error in budget documentation

Corrected the spelling of 'cheverons' to 'chevrons'.

* Add files via upload

* Remove redundant text in budget.md

* Fix formatting issues in payees.md

* count points script should fetch the release note from the PR directly (#7309)

* get pr release note from PR, not top of master

* note

* [AI] Mobile: Post transaction today on global account lists (#7311) (#7322)

* [AI] Mobile: pass today for Post transaction today on global account lists (#7311)

All Accounts, On budget, and Off budget transaction lists now forward the
today flag to schedule/post-transaction, matching single-account mobile
and desktop behavior.

Made-with: Cursor

* [AI] Add release note for PR 7322 (#7311)

Made-with: Cursor

* [AI] Tighten release note wording for PR 7322 (imperative)

Made-with: Cursor

---------

Co-authored-by: Pranay Mac M1 <pranayseela@yahoo.com>

---------

Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
Co-authored-by: Pranay S <pranayritvik@gmail.com>
Co-authored-by: Pranay Mac M1 <pranayseela@yahoo.com>
Co-authored-by: youngcw <calebyoung94@gmail.com>

* Implement Sankey report for spent and budgeted money (#7220)

* Implement Sankey graph report

* Add release notes

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6068

* Remove local debug settings

* [autofix.ci] apply automated fixes

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6068

* Improve graphs from comments

* Fix lints

* coderabit fixes

* Fix filtering and UI enhancements

* remove pngs

* Fix typecheck

* Another type issue

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6068

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6068

* Fix strict typing issues

* Update report page

Now better conforms with components from other reports, e.g. by reusing Header
Makes it possible to display a period longer than one month.

* Change view description order

* Formatting and cleanup

* Removed difference section, as it will be difficult to get a reliable view across months

* Introduce the Timeframe param, similar to Spending report, to allow saving a Live sliding window.

* Allow filtering just the last month

* Fix linting errors

* Remove all information about income

* Remove debugging statement

* Sort categories and subcategories by amount

* Move compact mode to spreadsheet to fix Card view more easily

* Update tests file

* Add release notes

* Rename release notes to match PR#

* Fix autofix.ci issues

* Update packages/desktop-client/e2e/sankey.test.ts

Enable experimental feature fall all tests, pr. coderabbit recommendation

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

* Add sankey-card to isWidgetType

* Gate Sankey routes to prevent direct URL bypass

* Fix typo

* Change node transformation to work by key instead of name, to remove risk of duplicate issues

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

* Prevent false-positive pass in month-change test.

* Translate mode to a proper label

* Fix message for empty data

* Enabled LoadingIndicator until data is ready

* Change card default mode

* More robust filtering

* Fixed issue with budgeted spreadsheet not using 'end' date

* Allow copying SankeyCard to dashboard

* Fix typing and linting issues

* Remove e2e tests

I cannot currently get them to pass, because I dont fully understand playwright and how they are supposed to work. I can see that they don't exist for other reports. We can add them later if required.

* Remove unecessary sankey reference

* Refactor spreadsheet

* Remove dead code from SankeyGraph

* Collect to Other if too many subcategories

* Edit wrong comment

* Linting and typechecking

* Show remaining amount to budget

* Hide description on narrow device

* Add visual clue if 'To budget' is larger than 'Budgeted' and would extend below the edge of the graph

* Add colors to the links

* Fix report card showing subcategories instead of main categories

* Add tooltip info to Other on SankeyCard

* Create globalOther flag and implement greedy category reduction algorithm

* Allow user to select between Global or Per category Other

* Allow user to choose number of subcategories to show

* Allow user to select how subcategories are sorted

* Fix budget filtering

* [autofix.ci] apply automated fixes

* Condense sorting and Other-grouping to one option

* Implement Sort as budget option

* Dynamically adjust topN based on SankeyCard height

* Remove old feature flags from previous PR

---------

Co-authored-by: andrewhumble <43395285+andrewhumble@users.noreply.github.com>
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: youngcw <calebyoung94@gmail.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Fix yarn generate:icons command (#7281)

* fix icon templates with `module.exports` to `export default`

* Add `@svgr/babel-plugin-add-jsx-attribute` to dependencies

* Run `yarn generate:icons`, and set prettier singleQuote to reduce changes

* Add release note

* Add temporary fix for `SvgChartArea`

* Add `ChartArea` svg from the existing tsx

* CI rerun

* Standardise ledger scrolling when using keyboard shortcuts (#7283)

* Standardise table keyboard navigation by preventing browser scroll with arrow keys

* Add release note

* Apply the preventDefault() in specific cases so that it is not applied to default

---------

Co-authored-by: youngcw <calebyoung94@gmail.com>

* Bump lodash from 4.17.23 to 4.18.1

Bumps [lodash](https://github.com/lodash/lodash) from 4.17.23 to 4.18.1.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.23...4.18.1)

---
updated-dependencies:
- dependency-name: lodash
  dependency-version: 4.18.1
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>

* note

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
Co-authored-by: Juulz <julesmcn@gmail.com>
Co-authored-by: Pranay S <pranayritvik@gmail.com>
Co-authored-by: Pranay Mac M1 <pranayseela@yahoo.com>
Co-authored-by: youngcw <calebyoung94@gmail.com>
Co-authored-by: Emil Tveden Bjerglund <emilbp@gmail.com>
Co-authored-by: andrewhumble <43395285+andrewhumble@users.noreply.github.com>
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: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: James Skinner <56730344+JSkinnerUK@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
2026-04-07 10:55:46 +00:00
Matt Fiddaman
8511687da4 replace nordigen-node with our own GoCardless implementation (#7352)
* implement our own GoCardless api class

* switch the service to use the new api

* drop deps

* note

* guard against request forgery

* strip empty params from the request body, add error logging

* coderabbit suggestions

* fix test, make institution nullable
2026-04-07 10:33:04 +00:00
Matt Fiddaman
a394aa1a57 remove useless assignments to local variables (#7354)
* fixes

* note

* enforce with lint

* identical operands

* unused state

* fix unused imports in new sankey report
2026-04-07 10:32:30 +00:00
dependabot[bot]
5eaf0be744 Bump vite from 8.0.0 to 8.0.5 (#7398)
* Bump vite from 8.0.0 to 8.0.5

Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.0.0 to 8.0.5.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.5/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.5
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>

* note

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2026-04-07 10:28:57 +00:00
Matt Fiddaman
bb7d7275a6 migrate actualbudget/actions to the main repo (#7406)
* migrate release note actions

* move workflows to use the local actions

* note

* fix failing cleanup in release notes action

* fix codeQL violation
2026-04-07 10:28:30 +00:00
Walter
4fe79e890b Issue 6856 crypt missing due to protocol isn't marked as secure (#7368)
* crypt missing due to protocol isn't marked as secure

* [autofix.ci] apply automated fixes

* release notes

* remove binary files

* Update upcoming-release-notes/7368.md

Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>

---------

Co-authored-by: Walter <walter@72:bf:48:9d:a9:b6.home>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2026-04-07 08:04:43 +00:00
Matiss Janis Aboltins
7af0910d4e [AI] Improve theme catalog responsive layout in ThemeInstaller (#7253)
* [AI] Improve theme catalog responsive layout in ThemeInstaller

* Refactor ColorPalette and ThemeInstaller components for improved layout and responsiveness

* Fix typo in Custom Reports description

* Correct category name from 'Reports' to 'Themes'

* [AI] Fix theme catalog scrollbar overlapping content

Reserve space for the scrollbar by adding right padding to catalog rows
so items don't get clipped when the list overflows.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 07:41:48 +00:00
Matiss Janis Aboltins
093d869bba [AI] Add .claude/worktrees directory to .gitignore (#7344)
* [AI] Add .claude/ directory to .gitignore

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Modify .gitignore for Claude worktrees

Update .gitignore to include Claude worktrees and exclude settings.json

* Update .gitignore to include claude worktree folder

* Add .claude/settings.local.json to .gitignore

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 07:41:33 +00:00
Matiss Janis Aboltins
fc5f598098 docs: Add guide for self-signed SSL certificates in CLI (#7360)
* [AI] Add self-signed SSL certificate documentation to CLI docs

Add a section explaining how to use NODE_TLS_REJECT_UNAUTHORIZED=0
to allow the CLI to connect to servers with self-signed SSL certificates,
with a security caution about the implications.

Closes #7327

https://claude.ai/code/session_01Mwsuc9By67uzSiMLxvPsMq

* Add release notes for PR #7360

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-07 07:41:22 +00:00
Matiss Janis Aboltins
799db6c496 [AI] Improve post-checkout hook to handle git worktree creation (#7393)
* [AI] Add worktree creation hook to run yarn install

Add a WorktreeCreate hook in .claude/settings.json that runs a setup
script after creating a new git worktree. The script creates a detached
worktree and runs yarn install to ensure dependencies are available.

https://claude.ai/code/session_01MjuJLWcxNU6nbWQDrgGpPr

* [AI] Move worktree setup hook to husky post-checkout for universal support

Instead of a Claude Code-specific hook, detect new worktree creation in
the existing husky post-checkout hook by checking for missing node_modules.
This works with any tool that creates git worktrees (Claude Code, Cursor,
CLI, etc.) since git runs post-checkout after worktree creation.

https://claude.ai/code/session_01MjuJLWcxNU6nbWQDrgGpPr

* Add release notes for PR #7393

* Update 7393.md

* [AI] Fix post-checkout hook to propagate yarn install failures

Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
2026-04-07 07:40:47 +00:00
Kennedy242
477fed1607 Add documentation for self-signed cert healthchecks (#7397)
* Add documentation for self-signed cert healthchecks

Adds instruction to set NODE_EXTRA_CA_CERTS in docker-compose healthcheck commands so Node.js can trust the certificate.

* [autofix.ci] apply automated fixes

* fixup! Add documentation for self-signed cert healthchecks

* [autofix.ci] apply automated fixes

* Update Docker health checks with self-signed certs

If using self signed certs, comment the first test line and uncomment
the second test line.

* fixup! Update Docker health checks with self-signed certs

* fixup! Add documentation for self-signed cert healthchecks

* fixup! Update Docker health checks with self-signed certs

* [autofix.ci] apply automated fixes

* fixup! Add documentation for self-signed cert healthchecks

* fixup! Update Docker health checks with self-signed certs

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-04-07 00:09:35 +00:00
Juulz
64b2d9b31a [Docs] Tour: Update reports, payees, schedules and rules pages. (#7405)
* Improve clarity of reports documentation

Revised descriptions for built-in and custom reports for clarity and conciseness.

* Revise schedules documentation for clarity and conciseness

* Update payees management documentation for clarity

* Update wording and formatting in rules documentation

* Fix formatting issue in schedules.md

* Clarify Payees management overview

Revised the description to clarify the overview of Payees management.

* [autofix.ci] apply automated fixes

* Fix hyphenation in reports documentation

* [autofix.ci] apply automated fixes

* Update packages/docs/docs/tour/reports.md

Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>

* Apply suggestions from code review

Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>

* Update transaction rule example in documentation

* Apply suggestions from code review

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

* [autofix.ci] apply automated fixes

* Add 'PAYPAL' to spelling expectations

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-04-06 22:30:38 +00:00
Juulz
5511d508ba [Docs] Tour: Move the Tour to website top level and add a sidebar for the tour (#7399)
* Add tourSidebar with various tour documentation

Added a new sidebar for the tour section with multiple entries.

* Add 'Tour Actual' sidebar to Docusaurus config

* Remove 'A Tour of Actual' from docs sidebar

Removed 'A Tour of Actual' category and its items from the sidebar.

* Rename 'Tour Actual' to 'Tour' in sidebar
2026-04-06 22:25:53 +00:00
Victor
59839f83e3 Proportional distribute for split transaction (#7257)
* Add button to proportionally distribute remaining amount of split transaction among child transactions

* Added release note

* Increased min width for split error popover so all buttons are visible

* Updated release note

* Merge proportional distribution into even distribution button

* Added docs on split transactions

* [autofix.ci] apply automated fixes

* Fixed spelling

* [autofix.ci] apply automated fixes

* Change split transaction popover hack to use resize event

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2026-04-06 21:51:18 +00:00
Matt Fiddaman
3ec6eeabb1 add confirmation dialog when changing half-reconciled transfers (#7269)
* check paired transfer transactions for reconciliation status

* add confirm messages

* note

* bulk edit

* improve types

* add checks to individual transaction edits

* add to mobile

* wabbit
2026-04-06 21:15:23 +00:00
Lucas Alvarez
ceaf13f271 feat: Add CLP currency (#7346) 2026-04-06 21:14:54 +00:00
dependabot[bot]
8e1d4a8b27 Bump electron from 39.2.7 to 39.8.4 (#7367)
* Bump electron from 39.2.7 to 39.8.4

Bumps [electron](https://github.com/electron/electron) from 39.2.7 to 39.8.4.
- [Release notes](https://github.com/electron/electron/releases)
- [Commits](https://github.com/electron/electron/compare/v39.2.7...v39.8.4)

---
updated-dependencies:
- dependency-name: electron
  dependency-version: 39.8.4
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>

* note

* tidy up electron dependencies

* note

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2026-04-06 21:23:27 +00:00
Matt Fiddaman
3bbcb60fe6 pin minimatch versions to resolve vulnerability reports (#7355)
* pin minimatch versions

* note
2026-04-06 21:14:38 +00:00
Juulz
c2319cdcb5 [Docs] Tour: update tour budgeting page for clarity (#7403)
* Improve clarity and formatting in budget.md

* Fix grammatical error in budget documentation

* Update packages/docs/docs/tour/budget.md

Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>

* Update packages/docs/docs/tour/budget.md

Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>

---------

Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2026-04-06 21:14:32 +00:00
Juulz
d3895042bb Update account documentation for clarity and accuracy (#7404) 2026-04-06 20:51:54 +00:00
Juulz
533cbed106 [Docs] Tour: Update user interface page for clarity (#7402)
* Refine user interface documentation for clarity

Updated text for clarity and corrected typos in the user interface documentation.

* Clarify server status and sync icon details

Updated descriptions for server status and sync icon interactions.

* Clarify server status and sidebar account display

Updated server status descriptions for clarity and improved wording in the sidebar section.
2026-04-06 20:49:23 +00:00
Juulz
221a57e218 [Docs] Tour: remove old files (#7400)
* Delete packages/docs/docs/tour/settings.md

NO longer in use.

* Delete packages/docs/docs/tour/sidebar.md

No longer in use.
2026-04-06 20:48:53 +00:00
Juulz
23bad279a0 [Docs] Tour: Update tour landing page for clarity and accuracy (#7401)
* Update tour documentation for clarity and accuracy

* Fix formatting and phrasing in tour documentation

* Fix link to documentation in tour index
2026-04-06 20:47:06 +00:00
L. Warren Thompson
d262f7d8b2 Fix updateTransaction corrupting split parents with partial updates (#7242)
* [AI] Fix updateTransaction corrupting split parents with partial updates

When `api.updateTransaction(id, { notes: '...' })` is called on a split
parent, the `updateTransaction` helper replaces the parent with the
sparse update object (`{ id, notes }`) instead of merging it with
the existing transaction data.  This causes `recalculateSplit` to see
`amount` as `undefined` (→ 0), which doesn't match the children's
total and sets a `SplitTransactionError` on the parent.  `makeChild`
also inherits undefined `account`, `date`, and `cleared` values,
potentially creating broken child rows.

Fix: merge the incoming partial fields (`{ ...trans, ...transaction }`)
so all existing properties are preserved.

Add a test that performs a notes-only update on a split parent and
asserts no error is set and the amount stays intact.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* [AI] Add release notes for PR #7242

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Address review feedback: remove verbose comment and simplify release note

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: L. Warren Thompson <lwarrenthompson@Warren-MBP.local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 18:12:51 +01:00
James Skinner
c75a94e8b0 Standardise ledger scrolling when using keyboard shortcuts (#7283)
* Standardise table keyboard navigation by preventing browser scroll with arrow keys

* Add release note

* Apply the preventDefault() in specific cases so that it is not applied to default

---------

Co-authored-by: youngcw <calebyoung94@gmail.com>
2026-04-05 18:12:51 +01:00
James Skinner
cb50930d0b Fix yarn generate:icons command (#7281)
* fix icon templates with `module.exports` to `export default`

* Add `@svgr/babel-plugin-add-jsx-attribute` to dependencies

* Run `yarn generate:icons`, and set prettier singleQuote to reduce changes

* Add release note

* Add temporary fix for `SvgChartArea`

* Add `ChartArea` svg from the existing tsx

* CI rerun
2026-04-05 18:12:51 +01:00
Emil Tveden Bjerglund
10b7385ad4 Implement Sankey report for spent and budgeted money (#7220)
* Implement Sankey graph report

* Add release notes

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6068

* Remove local debug settings

* [autofix.ci] apply automated fixes

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6068

* Improve graphs from comments

* Fix lints

* coderabit fixes

* Fix filtering and UI enhancements

* remove pngs

* Fix typecheck

* Another type issue

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6068

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6068

* Fix strict typing issues

* Update report page

Now better conforms with components from other reports, e.g. by reusing Header
Makes it possible to display a period longer than one month.

* Change view description order

* Formatting and cleanup

* Removed difference section, as it will be difficult to get a reliable view across months

* Introduce the Timeframe param, similar to Spending report, to allow saving a Live sliding window.

* Allow filtering just the last month

* Fix linting errors

* Remove all information about income

* Remove debugging statement

* Sort categories and subcategories by amount

* Move compact mode to spreadsheet to fix Card view more easily

* Update tests file

* Add release notes

* Rename release notes to match PR#

* Fix autofix.ci issues

* Update packages/desktop-client/e2e/sankey.test.ts

Enable experimental feature fall all tests, pr. coderabbit recommendation

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

* Add sankey-card to isWidgetType

* Gate Sankey routes to prevent direct URL bypass

* Fix typo

* Change node transformation to work by key instead of name, to remove risk of duplicate issues

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

* Prevent false-positive pass in month-change test.

* Translate mode to a proper label

* Fix message for empty data

* Enabled LoadingIndicator until data is ready

* Change card default mode

* More robust filtering

* Fixed issue with budgeted spreadsheet not using 'end' date

* Allow copying SankeyCard to dashboard

* Fix typing and linting issues

* Remove e2e tests

I cannot currently get them to pass, because I dont fully understand playwright and how they are supposed to work. I can see that they don't exist for other reports. We can add them later if required.

* Remove unecessary sankey reference

* Refactor spreadsheet

* Remove dead code from SankeyGraph

* Collect to Other if too many subcategories

* Edit wrong comment

* Linting and typechecking

* Show remaining amount to budget

* Hide description on narrow device

* Add visual clue if 'To budget' is larger than 'Budgeted' and would extend below the edge of the graph

* Add colors to the links

* Fix report card showing subcategories instead of main categories

* Add tooltip info to Other on SankeyCard

* Create globalOther flag and implement greedy category reduction algorithm

* Allow user to select between Global or Per category Other

* Allow user to choose number of subcategories to show

* Allow user to select how subcategories are sorted

* Fix budget filtering

* [autofix.ci] apply automated fixes

* Condense sorting and Other-grouping to one option

* Implement Sort as budget option

* Dynamically adjust topN based on SankeyCard height

* Remove old feature flags from previous PR

---------

Co-authored-by: andrewhumble <43395285+andrewhumble@users.noreply.github.com>
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: youngcw <calebyoung94@gmail.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-04-05 18:12:51 +01:00
Juulz
78ce7da1b4 [Doc] More tour image (mostly) updates & a hotkey fix (#7328)
* Fix keyboard shortcut Mac key for undo operations

Updated keyboard shortcut instructions for Mac & make consistent.

* Add files via upload

* Fix undo shortcut from 'K' to 'Z'

Updated keyboard shortcut for undo operation in payees guide. COFFEE!

* Revise budget section for clarity and consistency

Updated category descriptions and improved Markdown support details.

* Add files via upload

* Fix grammatical error in budget.md

* Fix typo and clarify Markdown description in budget.md

Corrected a typo in the documentation regarding the chevrons and clarified the description of rendered Markdown.

* Fix spelling error in budget documentation

Corrected the spelling of 'cheverons' to 'chevrons'.

* Add files via upload

* Remove redundant text in budget.md

* Fix formatting issues in payees.md

* count points script should fetch the release note from the PR directly (#7309)

* get pr release note from PR, not top of master

* note

* [AI] Mobile: Post transaction today on global account lists (#7311) (#7322)

* [AI] Mobile: pass today for Post transaction today on global account lists (#7311)

All Accounts, On budget, and Off budget transaction lists now forward the
today flag to schedule/post-transaction, matching single-account mobile
and desktop behavior.

Made-with: Cursor

* [AI] Add release note for PR 7322 (#7311)

Made-with: Cursor

* [AI] Tighten release note wording for PR 7322 (imperative)

Made-with: Cursor

---------

Co-authored-by: Pranay Mac M1 <pranayseela@yahoo.com>

---------

Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
Co-authored-by: Pranay S <pranayritvik@gmail.com>
Co-authored-by: Pranay Mac M1 <pranayseela@yahoo.com>
Co-authored-by: youngcw <calebyoung94@gmail.com>
2026-04-05 18:12:51 +01:00
Matt Fiddaman
b03080b246 trim down some unused/unnecessary dependencies (#7350)
* fix github actions inconsistencies

* fix pinning of transitive deps in eslint-plugin

* drop use of node-fetch in api

* drop md5 dependency in favour of node:crypto

* drop slash

* drop unused top level packages

* add note about node-polyfills warning

* remove unused deps from desktop-client

* drop pegjs types

* note

* drop node-jq
2026-04-05 18:12:51 +01:00
Matt Fiddaman
a12b971670 🔖 (26.4.0) (#7389)
* bump versions

* Remove used release notes

* add docs pages

* Update check-spelling metadata

* bump cli

* change release date

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-05 18:12:51 +01:00
youngcw
475272adce Revert "[AI] fix: preserve explicit category on imported transactions" (#7388)
* Revert "[AI] fix: preserve explicit category on imported transactions (#7185)"

This reverts commit 91e839353f.

* Add release notes for PR #7388

* Delete upcoming-release-notes/7388.md

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-05 15:22:45 +01:00
youngcw
353e12a009 🐛 fix split popover (#7372)
* pull in change

* note
2026-04-04 12:47:42 +00:00
Matiss Janis Aboltins
9a30a14bf9 [AI] Fix unrecoverable UI error after login (#7341) (#7361)
* [AI] Fix unrecoverable UI error after login (#7341)

The crash was in CommandBar.tsx where item.name.toLowerCase() was called
on items with null/undefined names (from custom reports or dashboard
pages with null name columns in the database). Added optional chaining.

Also hardened React Query data hooks to always return safe defaults even
in error states, and guarded $oneof AQL operator against empty arrays
generating invalid SQL.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [autofix.ci] apply automated fixes

* [AI] Revert hook/compiler changes, keep CommandBar fix, add release notes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Default null names to empty string at data source

Instead of optional chaining at the consumer, ensure names are never
null by defaulting at the data source: reportModel.toJS for custom
reports and dashboardQueries for dashboard pages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-04-03 20:41:32 +00:00
Matiss Janis Aboltins
c0c2d1630e [AI] Fix password login broken when OIDC is enabled (#7334)
* [AI] Fix password login broken when OIDC is enabled (#7331)

The security fix in PR #7155 added an `active = 1` check to
`getLoginMethod()`, which prevented password login when OIDC was the
active auth method. This caused the server to silently reroute password
login requests to the OpenID flow, which fails.

Remove the `active` requirement when the client explicitly requests a
login method — only require the method to exist in the auth table. The
`active` flag still governs the default method via `getActiveLoginMethod()`.

The `/change-password` endpoint security protections (admin role +
password auth_method checks) remain intact.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add release notes for PR #7334

* Update getLoginMethod to validate client-requested login methods against the auth database

Modified the getLoginMethod function to check if the requested login method exists in the auth table before returning it. Updated the corresponding test to reflect that a client-requested method not found in the database will be ignored, ensuring proper handling of login methods.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-02 18:34:36 +00:00
dependabot[bot]
556bea0953 Bump @xmldom/xmldom from 0.8.11 to 0.8.12 (#7348)
* Bump @xmldom/xmldom from 0.8.11 to 0.8.12

Bumps [@xmldom/xmldom](https://github.com/xmldom/xmldom) from 0.8.11 to 0.8.12.
- [Release notes](https://github.com/xmldom/xmldom/releases)
- [Changelog](https://github.com/xmldom/xmldom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/xmldom/xmldom/compare/0.8.11...0.8.12)

---
updated-dependencies:
- dependency-name: "@xmldom/xmldom"
  dependency-version: 0.8.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* note

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2026-04-01 19:41:21 +00:00
Matiss Janis Aboltins
4b5c0a79a7 [AI] Pin axios to 1.14.0 to avoid vulnerable 1.14.1 (#7343)
* [AI] Pin axios to 1.14.0 to avoid vulnerable 1.14.1

Add yarn resolution to prevent axios from being upgraded to 1.14.1,
which contains a critical vulnerability.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add release notes for PR #7343

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-31 22:04:54 +00:00
Pranay S
3b14fd08c3 [AI] Mobile: Post transaction today on global account lists (#7311) (#7322)
* [AI] Mobile: pass today for Post transaction today on global account lists (#7311)

All Accounts, On budget, and Off budget transaction lists now forward the
today flag to schedule/post-transaction, matching single-account mobile
and desktop behavior.

Made-with: Cursor

* [AI] Add release note for PR 7322 (#7311)

Made-with: Cursor

* [AI] Tighten release note wording for PR 7322 (imperative)

Made-with: Cursor

---------

Co-authored-by: Pranay Mac M1 <pranayseela@yahoo.com>
2026-03-30 15:46:59 +00:00
Matt Fiddaman
a729b9a4a0 count points script should fetch the release note from the PR directly (#7309)
* get pr release note from PR, not top of master

* note
2026-03-30 15:44:57 +00:00
Juulz
4820331be9 [Doc] A few new images in the tour to fix spelling errors (#7325)
* Add files via upload

* Add files via upload

* Add files via upload
2026-03-29 22:48:41 +01:00
L. Warren Thompson
f7e9ced9e3 Fix balance_current not syncing to API clients after bank sync (#7243)
* [AI] Fix balance_current not syncing to API clients after bank sync

updateAccountBalance() used db.runQuery() (raw SQL) to set
balance_current, which bypasses the CRDT sync layer.  This means
when SimpleFIN or GoCardless updates an account's bank balance on
the server, API clients calling api.sync() never receive the change —
balance_current stays null or stale in their local database.

Fix: use db.update() which goes through sendMessages/CRDT, ensuring
balance_current propagates to all synced clients just like other
account fields (name, closed, etc.).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* [AI] Add release notes for PR #7243

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Address review feedback: remove verbose comment and simplify release note

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: L. Warren Thompson <lwarrenthompson@Warren-MBP.local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 22:19:19 +00:00
dependabot[bot]
4dfba02cba Bump axios from 1.12.2 to 1.14.0 (#7308)
* Bump axios from 1.12.2 to 1.14.0

Bumps [axios](https://github.com/axios/axios) from 1.12.2 to 1.14.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.12.2...v1.14.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.14.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* note

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2026-03-27 21:38:19 +00:00
dependabot[bot]
29f55a18ce Bump brace-expansion from 1.1.12 to 1.1.13 (#7306)
* Bump brace-expansion from 1.1.12 to 1.1.13

Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.12 to 1.1.13.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v1.1.12...v1.1.13)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.13
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* note

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2026-03-27 20:44:21 +00:00
dependabot[bot]
1a5dfc4692 Bump node-forge from 1.3.2 to 1.4.0 (#7307)
* Bump node-forge from 1.3.2 to 1.4.0

Bumps [node-forge](https://github.com/digitalbazaar/forge) from 1.3.2 to 1.4.0.
- [Changelog](https://github.com/digitalbazaar/forge/blob/main/CHANGELOG.md)
- [Commits](https://github.com/digitalbazaar/forge/compare/v1.3.2...v1.4.0)

---
updated-dependencies:
- dependency-name: node-forge
  dependency-version: 1.4.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* note

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2026-03-27 20:47:28 +00:00
dependabot[bot]
756320ecb7 Bump yaml from 1.10.2 to 1.10.3 (#7286)
* Bump yaml from 1.10.2 to 1.10.3

Bumps [yaml](https://github.com/eemeli/yaml) from 1.10.2 to 1.10.3.
- [Release notes](https://github.com/eemeli/yaml/releases)
- [Commits](https://github.com/eemeli/yaml/compare/v1.10.2...v1.10.3)

---
updated-dependencies:
- dependency-name: yaml
  dependency-version: 1.10.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* note

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2026-03-27 20:20:04 +00:00
dependabot[bot]
216fc747d1 Bump picomatch from 2.3.1 to 2.3.2 (#7287)
* Bump picomatch from 2.3.1 to 2.3.2

Bumps [picomatch](https://github.com/micromatch/picomatch) from 2.3.1 to 2.3.2.
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/2.3.1...2.3.2)

---
updated-dependencies:
- dependency-name: picomatch
  dependency-version: 2.3.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* note

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2026-03-27 20:08:02 +00:00
dependabot[bot]
82509b053f Bump handlebars from 4.7.8 to 4.7.9 (#7298)
* Bump handlebars from 4.7.8 to 4.7.9

Bumps [handlebars](https://github.com/handlebars-lang/handlebars.js) from 4.7.8 to 4.7.9.
- [Release notes](https://github.com/handlebars-lang/handlebars.js/releases)
- [Changelog](https://github.com/handlebars-lang/handlebars.js/blob/v4.7.9/release-notes.md)
- [Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.7.8...v4.7.9)

---
updated-dependencies:
- dependency-name: handlebars
  dependency-version: 4.7.9
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* note

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2026-03-27 19:57:42 +00:00
dependabot[bot]
24382e0e14 Bump convict from 6.2.4 to 6.2.5 (#7293)
* Bump convict from 6.2.4 to 6.2.5

Bumps [convict](https://github.com/mozilla/node-convict) from 6.2.4 to 6.2.5.
- [Changelog](https://github.com/mozilla/node-convict/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mozilla/node-convict/commits)

---
updated-dependencies:
- dependency-name: convict
  dependency-version: 6.2.5
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* note

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2026-03-27 19:55:04 +00:00
Matt Fiddaman
798fcc9eee amend maintainer points counting script docs file filter (#7305)
* fix

* note
2026-03-27 19:31:29 +00:00
Matt Fiddaman
8504a5a385 fix release note causing CI failure (#7291) 2026-03-26 17:10:28 +00:00
Tyler Davis
acb339be90 fix a typo in the Repair Transactions paragraph (#7275)
* fix typo in the Repair Transcations paragraph

* add release-notes

* add release-notes

* add release-notes

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-03-25 16:22:51 +00:00
Ian
c5fe29d50c Fix Scheduled Transactions Not Being Included in Selected Balance (#7274)
* create failing tests to identify schedule bug

* Export SelectedBalance to allow for testing
 #Please enter the commit message for your changes. Lines startin

* fix handling of scheduleIds

* create release notes

* add tests for normal transactions to ensure no regressions introduced

* capitalize first letter of release notes
2026-03-24 16:19:51 +00:00
James Skinner
53db33a2b2 Fix leftover balance usage in budget covering logic (#7131) (#7272)
* Fix leftover balance usage in budget covering logic (#7131)

* [autofix.ci] apply automated fixes

* Add regression unit tests for `coverOverbudgeted` fixes

* Update release note for a more user-facing sentence

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-03-24 15:29:12 +00:00
Matt Fiddaman
9232e0d910 ensure transaction values are saved before adding (#7268)
* ensure transaction values are saved before adding

* note

* fix afterSave deadlock
2026-03-23 16:22:39 +00:00
Matt Fiddaman
335392dae0 fix mobile transactions view hiding older transactions with hide reconciled enabled (#7267)
* fix mobile hide reconcilied transactions from hiding older transactions

* note
2026-03-23 16:19:07 +00:00
Diego Palacios
f88009a166 Add Budgeted type to custom report widgets (#6903)
* feat(reports): add Budgeted type to custom report widgets

* chore(release-notes): add entry for budgeted custom reports

* chore: add feedback link

* fix: address coderabbit review comments

* fix: address coderabbit review comments

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6903

* fix vrt images

* fixes

* update vrt

* update release category and remove feature flag

* Update VRT screenshots

* fix: also retrieve budgeted values in tracking budgeting mode

* [autofix.ci] apply automated fixes

* show budgeted type in tracking mode

* rename to original budgetAnalysisReportEnabled flag

* fix

* remove unused variable

* remove old unused variables

* implement review comments

* fix navigate filters

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: youngcw <calebyoung94@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-03-23 15:06:38 +00:00
sys044
cded36f083 docs: update formulas.md for BUDGET_QUERY and QUERY_EXTRACT functions (#7244)
* docs: update formulas.md for BUDGET_QUERY and QUERY_EXTRACT functions

* [autofix.ci] apply automated fixes

* update spellings

---------

Co-authored-by: sys044 <tomgriffin@localhost>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2026-03-23 14:57:13 +00:00
api2062
91e839353f [AI] fix: preserve explicit category on imported transactions (#7185)
* [AI] fix: preserve explicit transaction fields in addTransactions

* [AI] add release notes for PR #7185

---------

Co-authored-by: Aditya Inamdar <api2062@Adityas-MacBook-Air.local>
2026-03-23 13:35:52 +00:00
J-LCRX
429b189edd [AI] Improve autocomplete sorting with tiered ranking (#6972)
Replace binary match/no-match sorting in payee and category dropdowns
with a 4-tier ranking: exact match, prefix match, word-boundary match,
and substring match. This surfaces the most relevant results first
(e.g. typing "me" shows "Me" before "Memory Express" before "Framework").

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 13:27:57 +00:00
Nathália Couto
d8af1a7ae7 Fix transactions table context menu bug (#7264)
* fix: transactions table menu

* fix: release notes
2026-03-23 13:24:21 +00:00
dependabot[bot]
b729e547d2 Bump flatted from 3.3.3 to 3.4.2 (#7249)
Bumps [flatted](https://github.com/WebReflection/flatted) from 3.3.3 to 3.4.2.
- [Commits](https://github.com/WebReflection/flatted/compare/v3.3.3...v3.4.2)

---
updated-dependencies:
- dependency-name: flatted
  dependency-version: 3.4.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-23 13:23:06 +00:00
Igor Loskutov
a00103ff67 fix: retry schedules after sync error (#7245)
* fix: retry schedules after sync error

* add release notes
2026-03-23 13:20:25 +00:00
Matiss Janis Aboltins
b6fbc7dd1e [AI] Custom Themes: custom font family (#7239)
* [AI] Add secure custom font support for custom themes

Implement safe font-family references in custom themes via CSS variables
(--font-body, --font-mono, --font-heading, etc.) validated against a
curated allowlist of system-installed and web-safe fonts.

Security approach: Only fonts already present on the user's OS or bundled
with the app are allowed. No @font-face, no url(), no external font
loading — this prevents third-party tracking via font requests while
still enabling meaningful font customization in themes.

Key changes:
- Add SAFE_FONT_FAMILIES allowlist (~80 fonts: generic families, bundled
  fonts, and common system fonts across platforms)
- Add validateFontFamilyValue() for comma-separated font stack validation
- Route --font-{body,mono,heading,family,ui,display,code} properties
  through the font validator instead of the color validator
- Update index.html to use var(--font-body, ...) with current Inter
  Variable stack as fallback
- Add comprehensive tests for valid/invalid font values and security
  edge cases (url injection, javascript:, expression(), etc.)

https://claude.ai/code/session_01D4ASLpcBCvWF1nzPLz9Tw5

* [AI] Add @font-face support with data: URI embedding for custom themes

Enable truly custom fonts in themes while maintaining zero runtime network
requests. Theme authors can include font files in their GitHub repos, and
fonts are automatically downloaded and embedded as data: URIs at install
time — the same approach used for theme CSS itself.

Security model:
- @font-face blocks only allow data: URIs (no http/https/relative URLs)
- Font MIME types are validated (font/woff2, font/ttf, etc.)
- Individual font files capped at 2MB, total at 10MB
- @font-face properties are allowlisted (font-family, src, font-weight,
  font-style, font-display, font-stretch, unicode-range only)
- Font-family names from @font-face are available in --font-* variables
- No runtime network requests — all fonts stored locally after install

Key additions:
- extractFontFaceBlocks(): parse @font-face from theme CSS
- validateFontFaceBlock(): validate properties and data: URIs
- splitDeclarations(): semicolon-aware parser that respects data: URIs
- embedThemeFonts(): fetch font files from GitHub, convert to data: URIs
- ThemeInstaller calls embedThemeFonts() during catalog theme installation
- 30+ new test cases for @font-face validation and security edge cases

Example theme CSS with custom fonts:
  @font-face {
    font-family: 'My Font';
    src: url('./MyFont.woff2') format('woff2');
  }
  :root { --font-body: 'My Font', sans-serif; }

https://claude.ai/code/session_01D4ASLpcBCvWF1nzPLz9Tw5

* [AI] Rename --font-body CSS variable to --font-family

https://claude.ai/code/session_01D4ASLpcBCvWF1nzPLz9Tw5

* [AI] Remove font-family allowlist and broaden --font-* regex

- Remove SAFE_FONT_FAMILIES allowlist and SAFE_FONT_FAMILIES_LOWER lookup.
  Any font name is now valid in --font-* properties. Referencing a font
  that isn't installed simply triggers the browser's normal fallback — no
  network requests, no security risk. Function calls (url(), expression(),
  etc.) are still blocked.

- Change the --font-* property regex from a specific list
  (family|mono|heading|...) to match all --font-* variables, so theme
  authors can use any --font-prefixed custom property.

https://claude.ai/code/session_01D4ASLpcBCvWF1nzPLz9Tw5

* [AI] Simplify and improve custom font validation code

Code quality improvements from review:

- Remove dead `declaredFonts` Set (was populated but never read after
  allowlist removal)
- Extract `stripQuotes()` helper to deduplicate quote-stripping logic
  between `validateFontFamilyValue` and `validateFontFaceBlock`
- Replace confusing `const searchFrom = 0` loop with `for (;;)` idiom
  in `extractFontFaceBlocks`
- Use index tracking (`content.substring(start, i)`) instead of
  character-by-character string concatenation in `splitDeclarations`
- Use `splitDeclarations` in `validateRootContent` instead of naive
  `split(';')` for consistency and correctness
- Parallelize font fetches in `embedThemeFonts` with `Promise.all`
  instead of sequential awaits
- Replace byte-by-byte base64 conversion with chunked
  `arrayBufferToBase64()` helper (8KB chunks)
- Reuse indexOf-based @font-face parsing in `embedThemeFonts` instead
  of fragile `[^}]*` regex that can't handle large data URIs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Enhance font validation in customThemes.ts

* Add custom release notes for upcoming feature: support for custom fonts in themes

* [AI] Simplify @font-face validation to only block external URLs

Remove ~210 lines of overly thorough font validation (MIME type allowlists,
base64 encoding checks, format hint validation, @font-face property allowlists,
font-family name regex) and replace with a single function that enforces the
actual security goal: rejecting non-data: URIs to prevent external resource
loading. Size limits for DoS prevention are preserved.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Update Content Security Policy to include font-src directive

Enhance the Content Security Policy in both the desktop client and sync server to allow font loading from data URIs. This change ensures that custom fonts can be embedded securely while maintaining the existing security measures for other resources.

* Enhance font-family validation to disallow empty values

Update the `validateFontFamilyValue` function to throw an error for empty font-family values, improving security and validation accuracy. Adjust tests to reflect this change, ensuring that empty values are properly handled as invalid.

* Enhance validation for CSS custom properties in customThemes.ts

Add comprehensive checks in the `validateRootContent` function to ensure CSS custom properties start with '--', contain valid characters, and do not end with a dash. This improves error handling for invalid property names, ensuring better compliance with CSS standards.

* [AI] Fix path traversal, spaces in font URLs, and add embedThemeFonts tests

Reject path-traversal (../) and root-anchored (/) font paths in
embedThemeFonts to prevent URL manipulation. Fix URL regex to handle
quoted filenames with spaces (e.g. "Inter Variable.woff2"). Add unit
tests covering both security validations and normal embedding flow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Implement font size budget enforcement in embedThemeFonts function

* Add global unstubbing in afterEach for embedThemeFonts tests

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-22 07:58:17 +00:00
Matiss Janis Aboltins
23adf06cb0 Add post-merge hook to auto-install dependencies (#7248)
* [AI] Add post-merge hook to auto-install when yarn.lock changes

Mirrors the existing post-checkout hook behavior: after a git pull or
merge, if yarn.lock changed between ORIG_HEAD and HEAD, automatically
runs yarn install to keep dependencies in sync.

https://claude.ai/code/session_01JHoMhGANLTc1q67s1dUHrt

* Add release notes for PR #7248

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-20 22:59:05 +00:00
Matiss Janis Aboltins
a8a2d23e63 [AI] Improve CLI query command with new flags, subcommands, and docs (#7240)
* [AI] Improve CLI query command with new flags, subcommands, and docs

Add --last, --count, --where, --offset, --group-by flags and field:desc
order-by syntax to make the query command more usable for both humans
and AI agents. Add query tables/fields subcommands for schema discovery.
Expand CLI and ActualQL documentation with comprehensive examples.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [AI] Fix parseOrderBy empty field validation and misleading aggregate docs

Validate that order-by field names are non-empty (e.g. ":desc" now throws
a clear error). Move AVAILABLE_TABLES before first use. Update group-by
examples in CLI help text and docs to use --file with proper aggregate
JSON instead of misleading --select with plain field names.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [autofix.ci] apply automated fixes

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-03-20 19:01:20 +00:00
Matiss Janis Aboltins
f5a72448bd [AI] Refactor ThemeInstaller to handle pasted CSS more gracefully (#7236)
* [AI] Add baseTheme and overrideCss support to custom theme system

Add baseTheme field to InstalledTheme allowing users to choose which
built-in theme (light/dark/midnight) serves as the base for custom
themes. Add overrideCss field for layering additional CSS overrides
on top of a catalog theme's CSS.

ThemeStyle now respects the baseTheme field when rendering base
variables. CustomThemeStyle renders both cssContent and overrideCss
layers.

https://claude.ai/code/session_01PPAkAQB4xfeFCQbmNwvn2k

* [AI] Add base theme selection and CSS override layering for custom themes

- Add baseTheme field to CatalogTheme and InstalledTheme types, allowing
  catalog themes to declare which built-in theme (light/dark/midnight) they
  are based on
- Add overrideCss field to InstalledTheme for layering additional CSS
  overrides on top of a catalog theme
- Update ThemeStyle to render the correct base theme colors when a custom
  theme specifies a baseTheme
- Update CustomThemeStyle to render both cssContent and overrideCss layers
- Update ThemeInstaller UI: catalog selection and free-text CSS now coexist
  so users can pick a catalog theme (e.g. Matrix) and apply extra overrides
- Add baseTheme to all entries in customThemeCatalog.json
- Dynamic label: shows "Additional CSS overrides:" when a catalog theme is
  selected, "or paste CSS directly:" otherwise

https://claude.ai/code/session_01PPAkAQB4xfeFCQbmNwvn2k

* [AI] Remove baseTheme from catalog; derive base from mode instead

Base theme is now automatically determined from the catalog theme's
mode field: light mode themes use "light" as base, dark mode themes
use "dark" as base. No separate baseTheme field needed in catalog.

https://claude.ai/code/session_01PPAkAQB4xfeFCQbmNwvn2k

* Refactor ThemeInstaller to handle pasted CSS more gracefully

* Enhance ThemeInstaller and CustomThemeStyle to support CSS validation for both content and overrides. Refactor pasted CSS handling for improved clarity and efficiency.

* Implement validateAndCombineThemeCss function to streamline CSS validation and combination for light and dark themes in CustomThemeStyle. Refactor existing CSS handling to improve clarity and efficiency.

* Add cachedCatalogCss state to ThemeInstaller for improved CSS handling

* Update ThemeInstaller tests to ensure pasted CSS is preserved when a catalog theme is selected and modify onInstall behavior to correctly handle empty CSS content. Refactor test cases for clarity and accuracy.

* Enhance ThemeInstaller to support dynamic baseTheme selection based on catalog theme or user preference. Refactor CSS installation logic to prioritize selected catalog themes and improve handling of pasted CSS. Update dependencies in the installTheme function for better clarity and functionality.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 18:48:02 +00:00
erwannc
0793eb5927 Add Notes to Monthly Budget Cell (#6620)
* Add Notes to Monthly Budget Cell
Changed Modal menus layout to follow month menu on mobile

* Fixed rebase errors

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6620

* Addressed youngcw's comments (notes id format, notesButton defaultColor and modal layout)

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6620

* Updated mobile budget menu modal page model

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: youngcw <calebyoung94@gmail.com>
2026-03-19 00:42:30 +00:00
Matiss Janis Aboltins
a43b6f5c47 [AI] Experimental CLI tool for Actual (#7208)
* [AI] Add @actual-app/cli package

New CLI tool wrapping the full @actual-app/api surface for interacting with
Actual Budget from the command line. Connects to a sync server and supports
all CRUD operations across accounts, budgets, categories, transactions,
payees, tags, rules, schedules, and AQL queries.

* Refactor CLI options: replace `--quiet` with `--verbose` for improved message control. Update related configurations and tests to reflect this change. Adjust build command in workflow for consistency.

* Refactor tests: streamline imports in connection and accounts test files for improved clarity and consistency. Remove dynamic imports in favor of static imports.

* Enhance package.json: Add exports configuration for module resolution and publish settings. This includes specifying types and default files for better compatibility and clarity in package usage.

* Update package.json exports configuration to support environment-specific module resolution. Added 'development' and 'default' entries for improved clarity in file usage.

* Enhance CLI functionality: Update configuration loading to support additional search places for config files. Refactor error handling in command options to improve validation and user feedback. Introduce new utility functions for parsing boolean flags and update related commands to utilize these functions. Add comprehensive tests for new utility functions to ensure reliability.

* Update CLI TypeScript configuration to include Vitest globals and streamline test imports across multiple test files for improved clarity and consistency.

* Update CLI dependencies and build workflow

- Upgrade Vite to version 8.0.0 and Vitest to version 4.1.0 in package.json.
- Add rollup-plugin-visualizer for bundle analysis.
- Modify build workflow to prepare and upload CLI bundle stats.
- Update size comparison workflow to include CLI stats.
- Remove obsolete vitest.config.ts file as its configuration is now integrated into vite.config.ts.

* Enhance size comparison workflow to include CLI build checks and artifact downloads

- Added steps to wait for CLI build success in both base and PR workflows.
- Included downloading of CLI build artifacts for comparison between base and PR branches.
- Updated failure reporting to account for CLI build status.

* Update documentation to replace "CLI tool" with "Server CLI" for consistency across multiple files. This change clarifies the distinction between the command-line interface for the Actual Budget application and the sync-server CLI tool.

* Refactor configuration to replace "budgetId" with "syncId" across CLI and documentation

* Enhance configuration validation by adding support for 'ACTUAL_ENCRYPTION_PASSWORD' and implementing a new validation function for config file content. Update documentation to clarify error output format for the CLI tool.

* Enhance configuration tests to include 'encryptionPassword' checks for CLI options and environment variables, ensuring proper priority handling in the configuration resolution process.

* Update nightly versioning script to use yarn

* Align versions

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-18 18:22:38 +00:00
Matt Fiddaman
1f821d2849 ⬆️ bump github actions (#7234)
* actions/setup-node

* actions/cache

* actions/checkout

* docker/*

* actions/*-artifact

* actions/stale

* others

* note
2026-03-18 08:53:03 +00:00
Matt Fiddaman
beee16bc8c ⬆️ march dependency updates (#7222)
* @types/node (^22.19.10 → ^22.19.15)

* baseline-browser-mapping (^2.9.19 → ^2.10.0)

* eslint (^9.39.2 → ^9.39.3)

* lage (^2.14.17 → ^2.14.19)

* lint-staged (^16.2.7 → ^16.3.2)

* minimatch (^10.1.2 → ^10.2.4)

* oxlint (^1.47.0 → ^1.51.0)

* rollup-plugin-visualizer (^6.0.5 → ^6.0.11)

* @chromatic-com/storybook (^5.0.0 → ^5.0.1)

* @storybook/addon-a11y (^10.2.7 → ^10.2.16)

* @storybook/addon-docs (^10.2.7 → ^10.2.16)

* @storybook/react-vite (^10.2.7 → ^10.2.16)

* eslint-plugin-storybook (^10.2.7 → ^10.2.16)

* storybook (^10.2.7 → ^10.2.16)

* @codemirror/autocomplete (^6.20.0 → ^6.20.1)

* @codemirror/lang-javascript (^6.2.4 → ^6.2.5)

* @codemirror/language (^6.12.1 → ^6.12.2)

* @rolldown/plugin-babel (~0.1.7 → ~0.1.8)

* @swc/core (^1.15.11 → ^1.15.18)

* @swc/helpers (^0.5.18 → ^0.5.19)

* @tanstack/react-query (^5.90.20 → ^5.90.21)

* @uiw/react-codemirror (^4.25.4 → ^4.25.7)

* hyperformula (^3.1.1 → ^3.2.0)

* i18next (^25.8.4 → ^25.8.14)

* i18next-parser (^9.3.0 → ^9.4.0)

* react-i18next (^16.5.4 → ^16.5.6)

* react-virtualized-auto-sizer (^2.0.2 → ^2.0.3)

* fs-extra (^11.3.3 → ^11.3.4)

* @r74tech/docusaurus-plugin-panzoom (^2.4.0 → ^2.4.2)

* lru-cache (^11.2.5 → ^11.2.6)

* nodemon (^3.1.11 → ^3.1.14)

* eslint-plugin-perfectionist (^4.15.1 → ^5.6.0)

* downshift (9.0.10 → 9.3.2)

* react-router (7.13.0 → 7.13.1)

* @easyops-cn/docusaurus-search-local (^0.52.3 → ^0.55.1)

* peggy (5.0.6 → 5.1.0)

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

* note
2026-03-18 08:37:04 +00:00
Karim Kodera
4cdb26f9a7 Adding Concentric Donut Pie Chart type to custom report charts (#7038)
* Initial commit for concentric donut chart implementation

* [autofix.ci] apply automated fixes

* Update upcoming-release-notes/7038.md

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

* Fix coderabbit comments (Recalculated total to avoid hidden cats, remove tooltip, add proper types)

* Update packages/desktop-client/src/components/reports/graphs/DonutGraph.tsx

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

* Fix zero total group

* lint issues fix

* Fix lint issues

* Empty commit to retriger the process

* [autofix.ci] apply automated fixes

* Removed line betweeen arc and label. I beleive the view is cleaner this way.

* Fixed line for outer donut

* split active shape for concentric circles to avoid impacting original chart

* [autofix.ci] apply automated fixes

* Fixing mid point to align with mid point on inner circle

* 1- make line always start inside the core circle
2- fix bug where inner circle label was showing below the outer circle

* - Fixed Dashboard issue when height too low.
- Rewrite of the activeShape part for simplicity.
- Centralize radius calculation.
- Provide differnt dimensions for compact vs standard rendering
- fix mid line point to auto fix at 70% of the inner radius
- More readable code

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #7038

* Fixed distance issue for arc on single ring.

* [autofix.ci] apply automated fixes

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #7038

* Update packages/desktop-client/src/components/reports/graphs/DonutGraph.tsx

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

* Fixing Code Rabbit Comments

* rerunning tests

* Added Group click through passing all categories iwth a workaorund on showActivity

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-17 20:10:30 +00:00
Matiss Janis Aboltins
15358b6b54 [AI] Remove development theme (#7232)
* [AI] Remove development theme

Delete the development theme that was only available in non-production
environments. Removes the theme file, its registration in the theme
registry, type definitions, preference validation, ThemeSelector icon
mapping, and Storybook configuration.

https://claude.ai/code/session_01A1aEippeWppuwoRSCBPwby

* Add release notes for PR #7232

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-17 19:18:47 +00:00
J B
6d9b1a1d72 Duplicate reimport fix in ui and API (#6926)
* add options to override reimportDeleted

* doc and default in ui to false

* pr note

* period

* wording

* [autofix.ci] apply automated fixes

* docs clarity

* actually test default behavior

* Update upcoming-release-notes/6926.md

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

* use new ImportTransactionOpts type for consistency

* Release note wording

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-17 17:25:22 +00:00
sys044
108ccc8aba Formula Card: Add budget analysis functions (#7078)
* feat(formula): Add QUERY_BUDGET function for budget-aware formula reporting

* docs(release): Add QUERY_BUDGET feature release notes

* refactor: decompose into multiple functions and support goal dimension

* [autofix.ci] apply automated fixes

* refactor: simplified code

* [autofix.ci] apply automated fixes

* updated release notes

---------

Co-authored-by: Your Name <tomgriffin@localhost>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-03-17 16:37:10 +00:00
scojo
ee8f8bfbba Add Formula Rule function to set-split-amount (#6414)
* Add Formula Rule function to set-split-amount fixed-amount and fixed-percent actions.

* Hide formula UI for remainder splits and simplify icon logic

* Guard against undefined options in set-split-amount else‑branch

* Show split amount formula in rules list

* Update tests based on changes introduced in 7fa1ff230e

* Change set-split-amount formula feature to work as a separate allocation method.

* [autofix.ci] apply automated fixes

* Update types for parent_amount

* Use a semantic non-interactive element instead of Button

* import organization

* [autofix.ci] apply automated fixes

* Fix tests

* Correctly hide when feature is disabled. Update release notes.

* Add tooltip documenting parent_amount formula variable
Change parent_amount to return in cents so it is consistent with amount
variable
Ensure balance variable is available in set-split-amount formula
Clean up parent_amount deletion to be consistent with balance

* Delete balance and parent_amount from subtransactions

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: youngcw <calebyoung94@gmail.com>
2026-03-17 16:28:14 +00:00
Matiss Janis Aboltins
c4ee71409e [AI] Add Yarn constraints to enforce consistent dependency versions (#7229)
* [AI] Add yarn constraints to enforce consistent dependency versions

Adds a `yarn.config.cjs` that uses Yarn 4's built-in constraints feature
to detect when the same dependency is declared with different version
ranges across workspaces. Workspace protocol references and
peerDependencies are excluded from the check.

Also adds a `yarn constraints` convenience script and the `@yarnpkg/types`
dev dependency for type-checked constraint authoring.

https://claude.ai/code/session_01B1xRjZXn6b18anZjo8cbqb

* Add release notes for PR #7229

* Add constraints job to GitHub Actions workflow

* Fix constraints

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-17 16:00:34 +00:00
Matt Fiddaman
dfd6e468a6 ⬆️ react-spring 10.0.3 (#7224)
* react-spring (10.0.0 -> ^10.0.3)

* note

* fix VRT

* fix more animations

* fix budget month colouring
2026-03-17 16:00:27 +00:00
Matiss Janis Aboltins
5b227f5fa1 feat: Add post-checkout hook to run yarn install if yarn.lock changes (#7230) 2026-03-17 15:58:02 +00:00
Julian Dominguez-Schatz
e1606b31ab Migrate get-next-package-version.js to TypeScript (#7227)
* Migrate `get-next-package-version.js` to TypeScript

* Add release notes

* Stronger type check

* Fix step ordering

* Fix typo

* Fix missed ordering
2026-03-17 13:56:47 +00:00
Michael Clark
4f7c3c51a5 🐛 Using a shared worker to coordinate multiple tabs (#7172)
* attempt to enable sync when multiple tabs are open

* allow multiple tabs to work

* release notes

* rehome the host if the tab closes

* ensure new tabs always receive failure  messages by broadcasting them on interval

* reject after retries are exhausted

* forwarding the logs from the worker to the main browser

* [autofix.ci] apply automated fixes

* add preflight fetch from main thread to server endpoint to trigger permission prompt if required

* remove the log prefix for cleaner logs

* adding heardbeat to detect closed tabs so they can be removed from the list

* store failure payload and broadcast for new tabs after timeout is cleared

* if a tab closes a budget, force other tabs to go to the budget list screen

* fix safari by detecting crossoriginisolated as a dependency for shared worker

* all ios to fallback to non-shared-worker implemenation

* coordinator and all backend work going through a leader tab to enable ios

* electing new leader tab when oone tab closes or is refreshed

* logic for standalone tabs to rejoin shared workers when on same budget

* remove the preflight request, shouldnt be needed now the code runs on the main process

* handling brand new tabs going to open budgets that are current standalone with no leader

* allowing budgets to be closed  without kickother others by transfering leadership to remaining oopened tabs

* remove unnedd comments

* change approach slightly - no more standalone, now every budget gets leader promotion automatically)

* adding tests and fixed minor bug to do with deleting budget with multiple tabs open

* fix worker not loading

* trouble with ts - moving to js

* reintroduce ts for the worker

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-03-17 09:30:34 +00:00
Matt Fiddaman
0e1fc07bf3 ⬆️ @types/react (#7223)
* bump @types/react

* note
2026-03-17 08:17:48 +00:00
Matiss Janis Aboltins
53cdc6fa48 [AI] Further hardening of "/change-password" endpoint (#7207)
* [AI] Fix OIDC privilege escalation in /change-password endpoint

Add admin role check and password auth_method session check to prevent
non-admin or OIDC-authenticated users from changing the server password.
Previously, any authenticated user could overwrite the password hash and
then login via password method to obtain an ADMIN session.

https://claude.ai/code/session_01Wne9FY2QnKp6JF7g61B1Sn

* Add release notes for PR #7207

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-17 08:16:46 +00:00
okxint
1d0281025d fix: preserve schedule link when merging transactions (#7177)
* [AI] fix: preserve schedule link when merging transactions

When merging two transactions where one is linked to a schedule,
the schedule field was not included in the merge update, causing
the schedule association to be silently dropped. This resulted in
duplicate transactions and incorrect "Due" status for scheduled
transactions.

Add `schedule: keep.schedule || drop.schedule` to both the normal
merge path and the subtransaction merge path, matching the existing
fallback pattern used for payee, category, notes, etc.

Add three test cases covering:
- Schedule preserved from dropped transaction when kept has none
- Kept transaction's schedule takes priority when both have one
- Schedule preserved when merging manual scheduled with banksynced

Fixes #6997

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add release notes for PR #7177

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 05:14:16 +00:00
Matiss Janis Aboltins
f73c5e9210 [AI] Fix adm-zip dependency resolution in loot-core (#7219) 2026-03-16 21:11:51 +00:00
Julian Dominguez-Schatz
a4eb17eff2 Upgrade to Vite 8 (#7184)
* Upgrade to Vite 8

* Add release notes

* PR feedback

* [autofix.ci] apply automated fixes

* PR feedback

* fix: inject process.env

* Restore deleted release note

* Clean up and typecheck

* Fix dev server

* Fix type error

* Fix tests

* PR feedback

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-03-15 23:16:39 +00:00
Matiss Janis Aboltins
8a3db77cff [AI] api: simplify bundling by removing loot-core type inlining (#7209) 2026-03-15 20:07:36 +00:00
Matiss Janis Aboltins
f5a62627f0 [AI] Remove @actual-app/crdt Vite aliases and redundant config (#7195)
* [AI] Remove @actual-app/crdt Vite aliases and redundant config

* Release notes

* Enhance CRDT package configuration and clean up Vite settings

* Added `publishConfig` to `crdt/package.json` to specify exports for types and default files.
* Removed unused `crdtDir` references from `vite.config.ts` and `vite.desktop.config.ts` to streamline configuration.
2026-03-15 17:41:27 +00:00
Matiss Janis Aboltins
6c150cf28a [AI] Publish loot-core (@actual-app/core) nightly first in workflow (#7200)
* [AI] Publish loot-core (@actual-app/core) nightly first in workflow

* [autofix.ci] apply automated fixes

* Refactor imports and update configuration

- Updated .oxfmtrc.json to change "parent" to ["parent", "subpath"].
- Removed unnecessary blank lines in various TypeScript files to improve code readability.
- Adjusted import order in reports and rules files for consistency.

* Add workflow steps to pack and publish the core package nightly

* Remove nightly tag from npm publish command in workflow for core package

* Update post-build script comment to reflect correct workspace command for loot-core declarations

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-03-15 17:35:01 +00:00
Asger Mogensen
e069312ac3 Feature: Add a confirmation modal when users merge payees in /payees (#7188)
* Add a confirmation model when merging payees in /payee

* Added a confirmation modal when users merge payees in /payees

* Address coderabbit comments

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-03-15 15:59:57 +00:00
Pranay S
f266b761c2 [AI] Fix Spending Analysis budget table for tracking budgeting (#7191)
* [AI] Fix Spending Analysis budget table for tracking budgeting

Made-with: Cursor

* [autofix.ci] apply automated fixes

* [AI] Add release note for Spending Analysis tracking budgeting fix

Made-with: Cursor

* [autofix.ci] apply automated fixes

* [AI] Address CodeRabbit nitpicks: use typed narrowing instead of assertion for budgetType

Made-with: Cursor

---------

Co-authored-by: Pranay Mac M1 <pranayseela@yahoo.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-03-15 15:58:22 +00:00
Matiss Janis Aboltins
328b36f124 [AI] Fix navigator is not defined error in @actual-app/api for Node.js environments (#7202)
* [AI] Fix navigator is not defined error in @actual-app/api for Node.js environments

Add platform.api.ts to provide Node.js-safe defaults for platform detection,
which the API's Vite config resolves before the browser-only platform.ts.
Also guard navigator access in environment.ts isElectron() function.

Fixes #7201

https://claude.ai/code/session_015Xz2nHC12pNkADGjGZnSXd

* Add release notes for PR #7202

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-15 08:13:55 +00:00
Derek Gaffney
8934df0cb5 fix(docs/accounts): remove duplicated content (#7199)
* fix(docs): remove duplicated content

* release note file
2026-03-14 21:39:33 +00:00
dependabot[bot]
ab269fa4ea Bump undici from 7.18.2 to 7.24.1 (#7197)
Bumps [undici](https://github.com/nodejs/undici) from 7.18.2 to 7.24.1.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v7.18.2...v7.24.1)

---
updated-dependencies:
- dependency-name: undici
  dependency-version: 7.24.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-14 21:19:47 +00:00
Matiss Janis Aboltins
9c61cfc145 [AI] Switch typecheck from tsc to tsgo and fix Menu type narrowing (#7183)
* [AI] Switch typecheck from tsc to tsgo and fix Menu type narrowing

* [autofix.ci] apply automated fixes

* Add .gitignore for dist directory, update typecheck script in package.json to use -b flag, and remove noEmit option from tsconfig.json files in ci-actions and desktop-electron packages. Introduce typesVersions in loot-core package.json for improved type handling.

* Refactor SelectedTransactionsButton to improve type safety and readability. Updated items prop to use spread operator for conditional rendering of menu items, ensuring proper type annotations with MenuItem. This change enhances the clarity of the component's structure and maintains TypeScript compliance.

* Update tsconfig.json in desktop-electron package to maintain consistent formatting for plugins section. No functional changes made.

* [autofix.ci] apply automated fixes

* Update package.json and yarn.lock to add TypeScript 5.8.0 dependency. Adjust typesVersions in loot-core package.json for improved type handling. Enhance tsconfig.json in sync-server package to enable strictFunctionTypes for better type safety.

* Enhance tsconfig.json in ci-actions package by adding composite option for improved project references and build performance.

* [AI] Revert typescript to 5.9.3 for ts-node compatibility

Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>

* [AI] Update yarn.lock after TypeScript version change

Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>

* Refactor Menu component for improved type safety and readability. Updated type assertions for Menu.line and Menu.label, simplified type checks in filtering and selection logic, and enhanced conditional rendering of menu items. This change ensures better TypeScript compliance and maintains clarity in the component's structure.

* Refactor Select and OpenIdForm components to improve type safety and simplify logic. Updated item mapping to handle Menu.line more effectively, enhancing clarity in selection processes. Adjusted SelectedTransactionsButton to streamline item creation and improve readability.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
2026-03-14 21:03:10 +00:00
Matiss Janis Aboltins
d86c9cf735 Add theme mode filtering to custom theme catalog (#7194)
* [AI] Add mode field to custom theme catalog for dark/light filtering

Each catalog theme now has a `mode: 'dark' | 'light'` field. When
installing a custom theme in auto mode, the ThemeInstaller filters the
catalog to only show themes matching the selected mode (light or dark).

https://claude.ai/code/session_01PtSEMRv3SpAEtdGzvYxzpa

* Add release notes for PR #7194

* Change category from Features to Enhancements

* [AI] Rename filter parameter to avoid shadowing useTranslation t()

Rename `t` to `catalogTheme` in the catalogItems filter to avoid
shadowing the `t` translation function from useTranslation().

https://claude.ai/code/session_01PtSEMRv3SpAEtdGzvYxzpa

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-14 14:57:47 +00:00
Stephen Brown II
f95cfbf82c Add drag&drop reordering for transactions within the same day (#6653)
* feat(desktop-client): add transaction drag-and-drop reordering

Add the ability to manually reorder transactions within a date group using
drag-and-drop in the TransactionsTable. This is useful for correcting the
order of transactions that occurred on the same day.

Prevent transaction reordering from falling back to the end of the day list
when dropping before a split parent. Parent-level moves now target parent
rows only, so regular transactions can be inserted between split parents on
the same date.

Key changes:

Frontend:
- Migrate drag-and-drop from react-dnd to react-aria hooks
- Add transaction row drag handles and drop indicators
- Implement onReorder handler in TransactionList
- Restrict reordering to same-date transactions only
- Disable drag-drop on aggregate views (categories, etc.)

Backend:
- Add transaction-move handler with validation
- Implement moveTransaction in db layer with midpoint/shove algorithm
- Add TRANSACTION_SORT_INCREMENT constant for consistent spacing
- Handle split transaction subtransactions automatically
- Inherit parent sort_order in child transactions during import

Refactoring:
- Remove useDragRef hook (replaced by react-aria)
- Remove DndProvider wrapper from App.tsx
- Update budget and sidebar components for new drag-drop API
- Fix sort.tsx @ts-strict-ignore by adding proper types

configure allowReorder for TransactionTable consumers

- Account.tsx: Enable reordering for single-account views, add sort_order
  as tiebreaker for stable ordering when sorted by other columns
- Calendar.tsx: Disable reordering in calendar report view (read-only context)

* void promises
2026-03-14 13:35:45 +00:00
Matiss Janis Aboltins
767f77fea3 [AI] Enable more lint rules as warn for gradual fix (7196) (#7196) 2026-03-14 01:47:17 +00:00
Matiss Janis Aboltins
d6dcc30e44 [AI] Promote typescript/no-for-in-array lint rule from warn to error (#7193)
* [AI] Promote typescript/no-for-in-array lint rule from warn to error

Convert the oxlint rule from "warn" to "error" as noted by the existing
TODO comment, and fix the three violations by replacing for-in loops
with for-of using .entries().

https://claude.ai/code/session_01N6F8DMzUVDxNJC56jMGknf

* Add release notes for PR #7193

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-13 21:06:06 +00:00
Matiss Janis Aboltins
541df52441 Enable restrict-template-expressions linting rule (#7181)
* [AI] Promote typescript/restrict-template-expressions to error and fix violations

Convert the oxlint rule from "warn" to "error" and fix all 42 violations
by wrapping non-string template expressions with String(). This ensures
type safety in template literals across the codebase.

https://claude.ai/code/session_01Uk8SwFbD6HuUuo3SSMwU9z

* Add release notes for PR #7181

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-13 18:36:58 +00:00
Julian Dominguez-Schatz
85e3166495 [AI] Remove deep-equal package (#7187)
* Remove `deep-equal` package

* Add release notes

* Add a few more tests

* Add release notes
2026-03-13 16:36:52 +00:00
Mats Nilsson
5d4bbc9ebb fix: mobile autocomplete modals (#6741)
When filtering for accounts in e.g. the net worth graph the modal closes
the filter the tooltip so it's impossible to add e.g. accounts to the
filter.
2026-03-13 15:03:38 +00:00
dependabot[bot]
031aac9799 bump ajv from 6.12.6 to 6.14.0 (#7044)
* Bump ajv from 6.12.6 to 6.14.0

Bumps [ajv](https://github.com/ajv-validator/ajv) from 6.12.6 to 6.14.0.
- [Release notes](https://github.com/ajv-validator/ajv/releases)
- [Commits](https://github.com/ajv-validator/ajv/compare/v6.12.6...v6.14.0)

---
updated-dependencies:
- dependency-name: ajv
  dependency-version: 6.14.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* note

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2026-03-13 01:53:32 +00:00
Stephen Brown II
c53c5c2f36 [AI] Normalize apostrophe-dot thousandsSeparator for consistency (#7179)
* Normalize apostrophe-dot thousandsSeparator for consistency

* Also normalize keyboard apostrophes on the input path
2026-03-13 01:47:53 +00:00
Matiss Janis Aboltins
e968213977 Use TypeScript project references for incremental builds (#7180)
* [AI] Fix duplicate typechecking by consolidating into lage

Previously `yarn typecheck` ran:
1. `tsc -b` (type-checks all packages via project references)
2. `tsc -p tsconfig.root.json --noEmit` (checks root bin/*.ts)
3. `lage typecheck` (runs `tsc --noEmit` per package - duplicate!)

Now it runs:
1. `tsc -p tsconfig.root.json --noEmit` (checks root bin/*.ts)
2. `lage typecheck` (handles everything via dependency ordering)

Changes:
- Remove `tsc -b` from root typecheck script
- Add `dependsOn: ["^typecheck"]` to lage config for correct ordering
- Change per-package typecheck from `tsc --noEmit` to `tsc -b` so
  declarations are emitted for dependent packages

https://claude.ai/code/session_01P7mtAHphD6f1FsnQRwWBaW

* Add release notes for PR #7180

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-12 21:54:29 +00:00
Matiss Janis Aboltins
6b996c11d8 [AI] Add --quiet flag to oxlint commands (#7055)
* [AI] Separate lint and format into distinct commands

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

* Update lint-staged configuration to use 'oxfmt' for formatting instead of 'yarn format:fix'

* [AI] Add format checks to CI workflows

Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>

* Refactor linting and formatting commands in package.json and GitHub workflows to streamline processes and add quiet mode for linting

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
2026-03-12 18:01:10 +00:00
Matiss Janis Aboltins
282a99db2f lint: promote no-floating-promises and require-array-sort-compare to error and fix violations (#7168)
* lint: promote no-floating-promises and require-array-sort-compare to error and fix violations

- Set typescript/no-floating-promises and typescript/require-array-sort-compare to error in .oxlintrc.json
- Add explicit compare functions to all .sort() calls: migrations (localeCompare), crdt/merkle (localeCompare), FiltersMenu (tuple key), useScheduleEdit (two-arg comparator), exec.test (localeCompare), goal-template and category-template-context (numeric), main.test and transactions.test (localeCompare / amount+id)
- Fix invalid single-arg sort in useScheduleEdit to proper two-arg comparator

Made-with: Cursor

* refactor: update sorting functions to use two-argument comparator

* Update index.ts

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
2026-03-12 17:50:37 +00:00
Matiss Janis Aboltins
3a22f1a153 refactor(api): fix cyclic dependencies (#6809)
* refactor(api): defineConfig vitest, api-helpers, drop vite.api build

- Wrap api vitest.config with defineConfig for typing/IDE
- Add loot-core api-helpers, use in YNAB4/YNAB5 importers
- Remove vite.api.config, build-api, injected.js; simplify api package

* refactor(api): update package structure and build scripts

- Change main entry point and types definition paths in package.json to reflect new structure.
- Simplify build script by removing migration and default database copy commands.
- Adjust tsconfig.dist.json to maintain declaration directory.
- Add typings for external modules in a new typings.ts file.
- Update comments in schedules.ts to improve clarity and maintainability.

* chore(api): update dependencies and build configuration

- Replace tsc-alias with rollup-plugin-visualizer in package.json.
- Update build script to use vite for building the API package.
- Add vite configuration file for improved build process and visualization.
- Adjust tsconfig.dist.json to exclude additional configuration files from the build.

* fix(api): update visualizer output path in vite configuration

- Change the output filename for the visualizer plugin from 'dist/stats.json' to 'app/stats.json' to align with the new directory structure.

* refactor(api): streamline Vite configuration and remove vitest.config.ts

- Remove vitest.config.ts as its configuration is now integrated into vite.config.ts.
- Update vite.config.ts to include sourcemap generation and adjust CRDT path resolution.
- Modify vitest.setup.ts to correct the import path for the CRDT proto file.

* feat(api): enhance build scripts and add file system utilities

- Update build scripts in package.json to include separate commands for building node, migrations, and default database.
- Introduce a new file system utility module in loot-core to handle file operations such as reading, writing, and directory management.
- Implement error handling and logging for file operations to improve robustness.

* Refactor typecheck script in api package and enhance api-helpers with new schedule and rule update functions. The typecheck command was simplified by removing the strict check, and new API methods for creating schedules and updating rules were added to improve functionality.

* Refactor API integration in loot-core by removing api-helpers and directly invoking handlers. Update typecheck script in api package to include strict checks, and refine TypeScript configurations across multiple packages for improved type safety and build processes.

* Refactor imports and enhance code readability across multiple files in loot-core. Simplified import statements in the API and adjusted formatting in YNAB importers for consistency. Updated type annotations to improve type safety and maintainability.

* Refactor handler invocation in YNAB importers to use the new send function from main-app. This change improves code consistency and readability by standardizing the method of invoking handlers across different modules.

* Refactor schedule configuration in loot-core to enhance type safety by introducing a new ScheduleRuleOptions type. This change improves the clarity of the recurring schedule configuration and ensures better type checking for frequency and interval properties.

* Update TypeScript configuration in api package to include path mapping for loot-core. This change enhances module resolution and improves type safety by allowing direct imports from the loot-core source directory.

* Update TypeScript configuration in api package to reposition the typescript-strict-plugin entry. This change improves the organization of the tsconfig.json file while maintaining the existing path mapping for loot-core, ensuring consistent type checking across the project.

* Update TypeScript configurations across multiple packages to enable noEmit option. This change enhances build processes by preventing unnecessary output files during compilation. Additionally, remove the obsolete tsconfig.api.json file from loot-core to streamline project structure.

* Update TypeScript configuration in sync-server package to enable noEmit option. This change allows for the generation of output files during compilation, facilitating the build process.

* Update api package configuration to streamline build process and enhance type safety. Removed unnecessary build scripts, integrated vite-plugin-dts for type declaration generation, and added migration and default database copying functionality. Adjusted vitest setup to comment out CRDT proto file import for improved test isolation.

* Update TypeScript configurations in desktop-client and desktop-electron packages to enable noEmit option, allowing for output file generation during compilation. Additionally, add ts-strict-ignore comments in YNAB importers to suppress strict type checking, improving compatibility with embedded API usage.

* Refactor api package configuration to update type declaration paths and enhance build process. Changed type definitions reference in package.json, streamlined tsconfig.json exclusions, and added functionality to copy inlined types during the build. Removed obsolete vitest setup file for improved test isolation.

* Revert to solution without types

* Update TypeScript configuration in API package to use ES2022 module and bundler resolution. This change enhances compatibility with modern JavaScript features and improves the build process.

* Update yarn.lock and API package to enhance TypeScript build process and add new dependencies

* Refactor inline-loot-core-types script to streamline TypeScript declaration handling and improve output organization. Remove legacy code and directly copy loot-core declaration tree, updating index.d.ts to reference local imports.

* Add internal export to API and enhance Vite configuration for migration handling

* Update Vite configuration in API package to target Node 18, enhancing compatibility with the latest Node features.

* Enhance inline-loot-core-types script to improve TypeScript declaration handling by separating source and typings directories. Update the copy process to include emitted typings, ensuring no declarations are dropped and maintaining better organization of loot-core types.

* Enhance migration handling by allowing both .sql and .js files to be copied during the migration process. Refactor file system operations in loot-core to improve error handling and streamline file management, including new methods for reading, writing, and removing files and directories.

* Refactor rootPath determination in Electron file system module by removing legacy case for 'bundle.api.js'. This simplifies the path management for the Electron app.

* Update API tests to mock file system paths for migration handling and change Vite configuration to target Node 20 for improved compatibility.

* Add promise-retry dependency to loot-core package and update yarn.lock

* Fix lint

* Refactor build script order in package.json for improved execution flow

* Feedback: API changes for "internal"
2026-03-12 17:43:39 +00:00
Tifan Dwi Avianto
d30162672c [AI] Correct API docs to match actual implementation (#7096)
* [AI] docs(api): fix API reference discrepancies in reference.md

Amp-Thread-ID: https://ampcode.com/threads/T-019ca316-33e2-75db-a333-baf62bb55f6c

* [autofix.ci] apply automated fixes

* docs: update API reference for init and updateRule methods

* docs(7096.md): add release notes for API reference documentation fix

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-03-11 19:04:19 +00:00
Matiss Janis Aboltins
db03d77e81 Handle missing accounts in SimpleFin batch sync (#7152)
* [AI] Fix SimpleFin batch sync crash when accounts are missing from response

When SimpleFin doesn't return data for all requested accounts during
batch sync, the code crashed with a TypeError accessing properties on
undefined, resulting in a generic "internal error" message for users.

This fix:
- Adds a guard in simpleFinBatchSync for missing account data, returning
  an ACCOUNT_MISSING error instead of crashing
- Propagates error entries from the SimpleFin response's errors map for
  accounts that have no data entry
- Adds a user-friendly ACCOUNT_MISSING error message in the UI suggesting
  to unlink and relink the account
- Adds test cases covering both scenarios

https://claude.ai/code/session_01XbHgxxrXYR3UTyW6VmYj47

* Add release notes for PR #7152

* [AI] Fix SimpleFIN batch sync error_code TypeError

Fix "Cannot read properties of undefined (reading 'error_code')" that
occurs during SimpleFIN batch sync by:

1. Adding null check for downloadSimpleFinTransactions result in
   simpleFinBatchSync (sync.ts) - the function can return undefined
   when user token is missing

2. Adding .catch() handler on individual processBankSyncDownload
   promises so a single account failure doesn't crash the entire
   batch via Promise.all rejection

3. Using optional chaining on syncResponse.res?.error_code in app.ts
   and handling the case where res is undefined with proper error
   reporting

https://claude.ai/code/session_01XbHgxxrXYR3UTyW6VmYj47

* [AI] Fix SimpleFin batch sync to emit ACCOUNT_MISSING for empty payloads

In the batch sync path, if a per-account download payload is an empty
object or is missing the transactions array, processBankSyncDownload
would crash and the error would be caught as INTERNAL_ERROR. Now we
check for these cases explicitly and emit ACCOUNT_MISSING instead,
while still allowing entries with error_code to propagate their
specific error.

https://claude.ai/code/session_01XbHgxxrXYR3UTyW6VmYj47

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-11 18:07:42 +00:00
Matiss Janis Aboltins
8a8fb2da51 [AI] Custom Themes - ability to define separate light/dark theme (#7145)
* [AI] Consolidate custom theme prefs and improve auto-mode UX

- Merge `installedCustomTheme` into `installedCustomLightTheme` so only
  two prefs exist (light + dark). The legacy asyncStorage key
  `installed-custom-theme` is preserved for backwards compatibility.
- In auto (System default) mode, the main Theme dropdown no longer
  surfaces the installed custom-light theme as an option; custom themes
  for light/dark are managed exclusively via the sub-selectors.
- Selecting "System default" resets both light and dark custom themes.
- Installing a custom theme from the main dropdown while in auto mode
  switches the base theme to "Light" so it applies directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add release notes for PR #7145

* Change category from Features to Enhancement

Custom Themes: separate light and dark theme options when selecting 'system default' theme.

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #7145

* Enhance ThemeSettings and UI components by adding maxWidth styling for better responsiveness. This change ensures that buttons and columns adapt to the full width of their containers.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-11 18:07:06 +00:00
Juulz
a65ab2b4ce Change Titlebar.tsx 'Sync' to Syncing icon only (#7005)
* Update Titlebar.tsx sync name to Server Sync

* Update Titlebar.tsx

* Create 7005.md

Change title Bar 'Sync' to 'Server Sync'.

* Update packages/desktop-client/src/components/Titlebar.tsx

Co-authored-by: Joel Jeremy Marquez <joeljeremy.marquez@gmail.com>

* Update packages/desktop-client/src/components/Titlebar.tsx

Co-authored-by: Joel Jeremy Marquez <joeljeremy.marquez@gmail.com>

* Update Titlebar.tsx

* Update Titlebar.tsx

* Update Titlebar.tsx

* [autofix.ci] apply automated fixes

* Update 7005.md

* Disable Server Sync button when offline in Titlebar component

* fix lint

* Add aria-disabled attribute to Server Sync button in Titlebar component

* Update titlebar sync icon and improve accessibility

Add disabled state and aria disabled label for offline mode.

---------

Co-authored-by: Joel Jeremy Marquez <joeljeremy.marquez@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: youngcw <calebyoung94@gmail.com>
2026-03-11 13:43:56 +00:00
Sylvercode
f29c031735 Add an option to swap payee/memo when importing transaction form file (#7101)
* Add swap payee-memo to ofx import

* Add mock files to test payee-memo swap

* Add swap payee-memo to qif import

* Add swap payee-memo to camt import

* Minor code cleanup for swap payee-memo on import

* Add release note

* lint fixing

* Fixe Payee and Memo capitalization

* change swapPayeeAndMemo to ofxSwapPayeeAndMemo

* correct  release note typo

* Add getSwapOption base on file type

* Support qfx

* Add CheckboxToggle to simplify ImportTransactionsModal options

* Fix split reac import

* Fix react import lint
2026-03-11 13:40:26 +00:00
Asherah Connor
c06f96f015 Add "only import transactions since" (#7139)
* Add "only import transactions since"

If specified, we filter out transactions before the given date.

* Address 🐰 comments

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #7139

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-11 10:52:21 +00:00
Alieksieiev0
0b21b572fe fix item selection requiring double tap on mobile (#7166)
* fix item selection requiring double tap on mobile

* Update upcoming-release-notes/7166.md

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

* extract getItemProps wrapper

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-03-11 10:34:31 +00:00
Matiss Janis Aboltins
bf1d220ced [AI] Lint: fix typescript/unbound-method problems (#7163)
* [AI] Refactor modal components and server modules

Made-with: Cursor

* Release notes
2026-03-10 18:06:50 +00:00
Vicente Cruz
94dd8f73c0 Add "Notion Dark Mode" custom theme (#7151)
* Update customThemeCatalog.json

Added custom theme inspired by Notion's Dark Mode

* [autofix.ci] apply automated fixes

* Create 7151.md release notes file

* Add Notion Dark Mode custom theme

Pushed a no-op change to get the tests to re-run

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-03-10 17:00:12 +00:00
Matiss Janis Aboltins
85e08b2e9e [AI] Fix privilege escalation in sync-server /change-password and getLoginMethod (#7155)
* [AI] Fix privilege escalation in sync-server /change-password and getLoginMethod

Made-with: Cursor

* Update upcoming-release-notes/7155.md

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

* Fix privilege escalation issue in change-password endpoint

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-03-09 20:33:20 +00:00
Dustin Brewer
60e2665fcc MVP for Payee Locations (#6157)
* Phase 1: Add payee locations database schema/types

 * Add migration to create payee_locations table with proper indexes
 * Add PayeeLocationEntity type definition
 * Update database schema to include payee_locations table
 * Export PayeeLocationEntity from models index

* Phase 2: Add payee location API/services

  * Add constants for default location behavior
  * Implement location service with geolocation adapters
  * Add new API handlers for payee location operations

* Phase 3: Add location-aware UI components/hooks

 * Update mobile transaction editing with location integration
 * Enhance PayeeAutocomplete with nearby payee suggestions and forget
   functionality
 * Implement location permission and placeholder unit of measurement hooks

* Phase 4: Add YNAB5 payee location import support

 * Extend YNAB5 types to include location data from payees
 * Implement location import logic in YNAB5 importer

* Phase 5: Add unit of measurement support

 * Add unit of measurement preference setting in Format.tsx
 * Implement distance formatting utilities for imperial/metric units
 * Add useUnitOfMeasurementFormat hook for accessing preferences

* Required release note about the PR

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6157

* Actually get syncing working

This was not obvious to me, esp. with 13 years of data, but the
locations I've been inserting were local only.

Everything appeared to work.

What I failed to notice is that the locations did not sync
across devices. Of course all the location data that was imported worked
fine, but nothing new unless it was created on device.

This changes the schema and uses the proper insert/delete methods such
that syncing works.

* Remove unit of measurement preference

Display feet and meters automatically, and don't bother to format based on miles/kilometers.

* Add payeeLocations feature flag

Place the location permissions check and thus user-facing functionality behind the feature flag

* Missed adding tombstone to payee location query

* Adjust migration name to pass CI

Adjust the indexes as well

* Unify location.ts

If CodeRabbit complains again, reply that we are actively choosing a unified file

* Add bounds testing

The validation is straightforward range-checking — if it's wrong, it'll be obvious quickly. Unless there's a plan to start adding broader test coverage for that file, I'd leave it untested for now

* Prefer camelCase for the method params

* Fix the nested interactive containers

* Fix the majority of CodeRabbit nits

The remainder seem to not be related to my code change (just a lint), outdated (sql migration comment), or infeasible (sql haversine query)

* More CodeRabbit nits

* Revert unnecessary YNAB5 zip import

Turns out the payee_locations were inside the exported budget all along!

* Additional guards and other CR fixes

* Match the pattern used elsewhere in file

* YNAB5.Budget -> Budget

Missed in the merge conflict

* ci: trigger rerun

* Change import from fetch to connection module

* Correct invalid border property

Ah. I never noticed this property wasn't working. I guess the button
looked OK to me!

* Only hide the button on success

* Update packages/loot-core/src/shared/location-utils.ts

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

* Update packages/loot-core/src/server/payees/app.ts

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

* Fully fix typo

Guess I shouldn't commit a suggestion after waking up

* Attempting to address feedback

Manual select nearby payee and save payee location buttons to make the UX more obvi

* Remove stale file that was moved

* Additional cleanup of remnant change

Removed the references to location from a few existing entities

* Additional cleanup of remnant change

* Show the Nearby payees button even when the field is disabled

If there are nearby payees, there's not a payee already selected, and the save button isn't needed

* runQuery is not async

* Add mockNearbyPayeesResult to test

Trying to utilize the real type in the mock

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
2026-03-09 20:26:48 +00:00
Matiss Janis Aboltins
102be1c54d Fix shared error array in SimpleFin batch sync error handling (#7125)
* Add failing tests for SimpleFin batch sync shared error array bug

Tests prove two bugs in simpleFinBatchSync() catch block (app.ts:1100-1115):
1. All accounts share the same errors array reference
2. Errors accumulate across accounts instead of being isolated

Related: #6623, #6651, #7114

https://claude.ai/code/session_011ebiiXRMmbiKxYMohVXL6o

* Fix shared error array in SimpleFin batch sync catch block

When simpleFinBatchSync() threw an error, all accounts received the
same errors array by reference and errors accumulated across accounts.
Each account now gets its own isolated errors array with a single error
specific to that account, matching the pattern used by accountsBankSync().

Fixes #6623

https://claude.ai/code/session_011ebiiXRMmbiKxYMohVXL6o

* Remove @ts-strict-ignore from bank sync tests

Use proper non-null assertions instead of disabling strict mode.

https://claude.ai/code/session_011ebiiXRMmbiKxYMohVXL6o

* Add release notes for PR #7125

* [AI] Replace test() with it() to follow repo convention

Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
2026-03-09 19:50:18 +00:00
Julian Dominguez-Schatz
448da13cf5 Move migrations script to typescript (#7075)
* Move migrations script to typescript

* Add release notes

* Setup

* Fix

* PR feedback

* Make imports work as expected

* Rabbit
2026-03-09 07:58:03 +00:00
LeviBorodenko
41679235be [Mobile] Fix preview running balances not displaying on toggle (#7041)
* refactor(usePreviewTransactions): Move running balances to useMemo

* docs(relnotes): Add note for mobile running balance fix

* refactor(hooks): Remove unnecessary options ref
2026-03-08 22:04:52 +00:00
Matiss Janis Aboltins
73fa068fe9 [AI] Establish AI agent commit and PR guidelines (#7153)
* [AI] Extract PR/commit rules into shared agent skill

Deduplicate PR and commit instructions from AGENTS.md into a standalone
skill file at .github/agents/pr-and-commit-rules.md. This single source
of truth is consumed by both Claude Code (via CLAUDE.md @-import) and
Cursor (via .cursor/rules/pr-and-commit.mdc with alwaysApply: true).

AGENTS.md now references the shared file instead of repeating the rules
in three separate sections.

https://claude.ai/code/session_01KkHg7MYXrTyDkTw6u98Vam

* Add release notes for PR #7153

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-08 18:16:46 +00:00
Juulz
1fe588c143 🐞 Fix mobile transactions colors - fixes #7042 (#7047)
* Update amount styling with theme colors

* Clean up imports in TransactionListItem.tsx

Removed unused import of makeBalanceAmountStyle.

* Add release notes for bugfix in color variables

Fix color variables for mobile transaction list items.

* Change positiveColor in amount to use theme.tableText

* Change negative color style for running balance

* Fix negative color style in TransactionListItem

* Update color styles for transaction amount display

* Update upcoming-release-notes/7047.md

Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #7047

---------

Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-08 15:04:30 +00:00
mibragimov
edce092ae8 fix(csv-import): trim whitespace from amount strings before parsing (#7149)
* fix(csv-import): trim whitespace from amount strings before parsing

looselyParseAmount relies on a regex anchored at $ to detect decimal
markers. Trailing whitespace (e.g. from Excel-saved CSVs) shifts the
pattern match so a thousands separator is misidentified as a decimal
point, producing wildly wrong values.

Adding trim() at the top of the function eliminates trailing/leading
whitespace before any regex logic runs.

Fixes actualbudget/actual#7121

* chore: add release notes for #7149
2026-03-07 20:41:47 +00:00
dependabot[bot]
77411394f6 Bump dompurify from 3.3.0 to 3.3.2 (#7143)
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.3.0 to 3.3.2.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.3.0...3.3.2)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-version: 3.3.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
2026-03-07 20:24:41 +00:00
dependabot[bot]
235d94478f Bump express-rate-limit from 8.2.1 to 8.2.2 (#7140)
* Bump express-rate-limit from 8.2.1 to 8.2.2

Bumps [express-rate-limit](https://github.com/express-rate-limit/express-rate-limit) from 8.2.1 to 8.2.2.
- [Release notes](https://github.com/express-rate-limit/express-rate-limit/releases)
- [Commits](https://github.com/express-rate-limit/express-rate-limit/compare/v8.2.1...v8.2.2)

---
updated-dependencies:
- dependency-name: express-rate-limit
  dependency-version: 8.2.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* [AI] Update express-rate-limit to 8.3.0 to fix GHSA-46wh-pxpv-q5gq vulnerability

Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>

* Add release notes for PR #7140

* [AI] Update release notes to reflect version 8.3.0

Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-07 19:48:56 +00:00
Matiss Janis Aboltins
7e0edd43ec Sort theme catalog items alphabetically by name (#7144)
* [AI] Sort custom theme catalog options alphabetically in the UI

Sort catalog themes by name using localeCompare before rendering,
without modifying the underlying JSON data file.

https://claude.ai/code/session_01Y5SGaVYqsVWVsvXV8ZFXj3

* Add release notes for PR #7144

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-06 23:01:07 +00:00
Matiss Janis Aboltins
fdf5c8d0a9 [AI] Move window typings import to globals.ts (#7142)
* [AI] Remove parent path import of window.ts from desktop-client tsconfig

Replace the `../../packages/loot-core/typings/window.ts` include in
desktop-client's tsconfig.json with a proper package import. This adds
a `./typings/*` export to loot-core's package.json and creates a
globals.ts file in desktop-client that imports the window types via
the package name.

https://claude.ai/code/session_01GrgAzjWd3XvqwBTfXLerxc

* [autofix.ci] apply automated fixes

* Add release notes for PR #7142

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-06 23:00:50 +00:00
Matt Fiddaman
a8ec84ceac stop font size fluctuations showing in summary cards (#7092)
* stop summary cards from showing until font size settled

* note
2026-03-06 23:00:41 +00:00
Michael Clark
b727124603 Fix docker images (#7146)
* fix docker images

* release notes
2026-03-06 22:55:21 +00:00
dependabot[bot]
8bb7f207f2 Bump svgo from 3.3.2 to 3.3.3 (#7130)
Bumps [svgo](https://github.com/svg/svgo) from 3.3.2 to 3.3.3.
- [Release notes](https://github.com/svg/svgo/releases)
- [Commits](https://github.com/svg/svgo/compare/v3.3.2...v3.3.3)

---
updated-dependencies:
- dependency-name: svgo
  dependency-version: 3.3.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-06 21:56:01 +00:00
dependabot[bot]
6e0c15eb12 Bump immutable from 5.1.3 to 5.1.5 (#7129)
Bumps [immutable](https://github.com/immutable-js/immutable-js) from 5.1.3 to 5.1.5.
- [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.3...v5.1.5)

---
updated-dependencies:
- dependency-name: immutable
  dependency-version: 5.1.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
2026-03-06 21:51:14 +00:00
Michael Clark
4e2cec2c7a 🧪 Improving docker image test resiliency (#7141)
* improving test resiliency

* release notes
2026-03-06 21:02:31 +00:00
Matiss Janis Aboltins
078603cadf [AI] Implement sync recovery (#7111)
* [AI] Fix iOS/Safari sync recovery (fixes #7026): useOnVisible hook, re-fetch server version on visible, improved network-failure message

Made-with: Cursor

* Feedback: coderabbitai

* Refactor useOnVisible test: remove unnecessary resolve check and simplify callback definition
2026-03-04 23:27:15 +00:00
Joel Jeremy Marquez
b3a86b5392 Update remaining accounts hooks to return react query states (#7071)
* Update remaining accounts hooks to return react query states

* Add release notes for PR #7071

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-04 23:13:55 +00:00
Mats Nilsson
295a565e55 Fix budget analysis report padding (#7118)
* Fix budget analysis report padding

The padding on the report is too small when the value is large.

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-03-04 22:08:30 +00:00
Matiss Janis Aboltins
387c8fce16 [AI] Enable TypeScript composite project references across monorepo (#7062)
* [AI] Enable TypeScript composite project references across monorepo

- Add composite and declaration emit to all package tsconfigs
- Wire root and per-package project references in dependency order
- Replace cross-package include-based typing with referenced outputs
- Fix api TS5055 by emitting declarations to decl-output
- Add desktop-client alias for tests; fix oxlint import order in vite.config
- Add UsersState.data null type and openDatabase return type for strict emit

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

* Remove obsolete TypeScript configuration for API and update build script to emit declarations directly to the output directory. This streamlines the build process and ensures compatibility with the new project structure.

* Refactor TypeScript configuration in API package to remove obsolete decl-output directory and update build scripts. The changes streamline the build process by directing declaration outputs to the @types directory, ensuring better organization and compatibility with the new project structure.

* Add TypeScript declaration emission for loot-core in desktop-electron build process

* Refactor TypeScript configuration in API package to utilize composite references and streamline build scripts. Update include and exclude patterns for improved file management, ensuring better organization of declaration outputs and migration SQL files.

* Refactor TypeScript configuration in loot-core and desktop-client packages to streamline path management and remove obsolete dependencies. Update paths in tsconfig.json files for better organization and compatibility, and adjust yarn.lock to reflect changes in workspace dependencies.

* Update desktop-electron package to utilize loot-core as a workspace dependency. Adjust TypeScript import paths and tsconfig references for improved organization and compatibility across packages.

* Enhance Vite configuration for desktop-client to support Electron-specific conditions and update loot-core package.json to include Electron as a platform for client connection. This improves compatibility for Electron builds.

* Refactor TypeScript configuration across multiple packages to streamline path management. Update tsconfig.json files in root, api, and loot-core packages to improve import paths and maintain compatibility with internal typings.

* Update package dependencies and Vite configuration across component-library and desktop-client. Add vite-tsconfig-paths to component-library and remove it from desktop-client. Refactor Storybook preview file to include a TODO for future refactoring.

* Remove Node-specific path from loot-core package.json for client connection, streamlining platform configuration for Electron.

* Remove loot-core as a workspace dependency from desktop-electron package.json

* Update tsconfig.json to remove reference to desktop-client

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-03-04 20:57:06 +00:00
YHC
c7ebfd8ad4 Add TWD Currency (#7095)
* Add New Taiwan Dollar to currency list

* Add New Taiwan Dollar to currency list

* Fix decimalPlaces for New Taiwan Dollar

Updated decimalPlaces for New Taiwan Dollar from 0 to 2, as suggested by coderabbitai, "Line 62 introduces TWD as zero-decimal, but this codebase currently has an unresolved zero-decimal conversion/storage issue. This risks incorrect persisted amounts for TWD transactions."

* Add upcoming-releass-notes

* Add New Taiwan Dollar (NT$) to available currency

* Add New Taiwan Dollar (NT$) to available currency
2026-03-04 18:46:20 +00:00
Matiss Janis Aboltins
e1f834371b [AI] Refactor YNAB importers to use server-side send() and handler API (#7050)
* [AI] Refactor YNAB importers to use server-side send() and handler API

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

* Rename 7049.md to 7050.md

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-03-04 18:38:33 +00:00
Matiss Janis Aboltins
4caee99955 [AI] Strengthen schedule rule types in loot-core (remove ts-expect-error) (#7051)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-03-04 18:38:20 +00:00
Matiss Janis Aboltins
286d05d187 [AI] Move ImportTransactionsOpts from api package to loot-core (#7053)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-03-04 18:38:09 +00:00
Matiss Janis Aboltins
cf05a7ea01 [AI] Typescript: low hanging fruit (#7091)
* [AI] Use loot-core workspace in desktop-electron and fix related types

- Add loot-core as workspace dependency in desktop-electron
- Import GlobalPrefsJson from loot-core package in desktop-electron
- Allow null in usersSlice data type (UsersState)
- Add explicit SQL.Database return type to openDatabase in sqlite electron

Made-with: Cursor

* Re-add loot-core as a workspace dependency in desktop-electron
2026-03-04 18:32:01 +00:00
Michael Clark
b373b612a4 :electron: Electron backups converted to zip for easy importing (#7069)
* e:electron: electron backups converted to zip for easy importing

* release notes

* fix lint

* suggestion from rabbit

* Change category from Maintenance to Enhancements
2026-03-04 18:00:07 +00:00
Copilot
3797cff716 [AI] Skip AI-generated release notes for release/ branch PRs (#7107)
* Initial plan

* [AI] Make AI-generated release notes not run on release/ branches

Co-authored-by: jfdoming <9922514+jfdoming@users.noreply.github.com>

* [AI] Use explicit startsWith(github.head_ref) if conditions instead of JS changes

Co-authored-by: jfdoming <9922514+jfdoming@users.noreply.github.com>

* Delete extra condition

* Add release notes for PR #7107

* [autofix.ci] apply automated fixes

* FIx note

* Add head branch information to PR details

* Refactor conditions for release notes generation

* Sec

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jfdoming <9922514+jfdoming@users.noreply.github.com>
Co-authored-by: Julian Dominguez-Schatz <julian.dominguezschatz@gmail.com>
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>
2026-03-04 17:40:32 +00:00
Michael Clark
9e2793d413 :electron: Flathub pr on publish of release assets (#7117)
* flathub pr on publish of release

* docs

* release notes

* rabbit feedback
2026-03-04 17:05:47 +00:00
Julian Dominguez-Schatz
3201819df9 Display/save limit/refill templates (#6693)
* Handle refill templates in automations modal

* Add release notes

* Fix typecheck

* Fix icon style

* Style fixes

* Fix migration

* Fix typecheck

* Rabbit

* Add tests
2026-03-04 16:41:07 +00:00
Josh Woodward
eca50f28b0 Fix skipping schedules that move before weekend (#7057)
* Pushing before weekend to Monday

* release notes

* lint

* Guard against null

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

* fixing AI commit and addressing second comment

* addressing nitpicks

* first attempt at a test

* [autofix.ci] apply automated fixes

* refactor to use condition and fix tests

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-03-04 16:40:41 +00:00
dependabot[bot]
c82ee91b12 bump bn.js from 4.12.2 to 4.12.3 (#7073)
* Bump bn.js from 4.12.2 to 4.12.3

Bumps [bn.js](https://github.com/indutny/bn.js) from 4.12.2 to 4.12.3.
- [Release notes](https://github.com/indutny/bn.js/releases)
- [Changelog](https://github.com/indutny/bn.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/indutny/bn.js/compare/v4.12.2...v4.12.3)

---
updated-dependencies:
- dependency-name: bn.js
  dependency-version: 4.12.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* note

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2026-03-04 16:40:25 +00:00
Julian Dominguez-Schatz
cb8ff337dc Migrate sync server general utils to typescript (#7074)
* Migrate sync server general utils to typescript

* Add release notes

* Rabbit

* Fix
2026-03-04 16:40:24 +00:00
Matiss Janis Aboltins
c37a5a02aa [AI] Add CLAUDE.md with reference to AGENTS.md (#7105)
* [AI] Add CLAUDE.md with reference to AGENTS.md

Made-with: Cursor

* Add release notes for PR #7105

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-04 16:40:14 +00:00
Matiss Janis Aboltins
f9e09ca59b 🔖 (26.3.0) (#7097)
* 🔖 (26.3.0)

* Remove used release notes

* Add release notes for PR #7097

* Remove used release notes

* Remove used release notes

* Add release notes for version 26.3.0

* Add new terms to spelling expectation list

* Fix spelling and capitalization in release notes

Corrected spelling of 'reorganisation' to 'reorganization' and updated 'coderabbit' to 'CodeRabbit' for consistency.

* Update patterns.txt to allowlist 'CodeRabbit'

Add 'CodeRabbit' to allowlist of proper nouns.

* Clarify chart theming support in release notes

Updated the release notes to specify bar/pie chart theming support and added details about theme variables for customization.

* Remove 'CodeRabbit' from spelling expectations

* Refactor release notes and improve formatting

Reorganize release notes for clarity and update content.

* Create 2026-03-02-release-26-3-0.md

* Change release date to 2026-03-02

Updated the release date for version 26.3.0.

* Update release notes for version 26.3.0

---------

Co-authored-by: jfdoming <9922514+jfdoming@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Julian Dominguez-Schatz <julian.dominguezschatz@gmail.com>
2026-03-03 01:23:12 +00:00
Matiss Janis Aboltins
8081b8829e [AI] Make merge-freeze-unfreeze workflow work on fork PRs (#7104)
* [AI] Make merge-freeze-unfreeze workflow work on fork PRs via pull_request_target

Made-with: Cursor

* Add release notes for the "unfreeze" workflow functionality in fork PRs

* Add empty permissions block to unfreeze job in merge-freeze-unfreeze workflow
2026-03-01 20:46:58 +00:00
Matt Fiddaman
f2f79d378c fix bugfix categorisation in contributor points counting script (#7103)
* s/bugfix/bugfixes

* note

* add alias functionality

* note update
2026-03-01 16:33:58 +00:00
Julian Dominguez-Schatz
c5cca67399 Revert "feat(currency): Add Vietnamese Dong (VND) currency" (#7100)
* Revert "feat(currency): Add Vietnamese Dong (VND) currency (#6902)"

This reverts commit 7fa9fa900b.

* Add release notes for PR #7100

* Delete 7100.md

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-01 07:31:29 +00:00
Matiss Janis Aboltins
eabf09587f [AI] Github action for unfreezing PRs (#7094)
* [AI] Add GitHub Action to add PR to Merge Freeze unblocked list when unfreeze label is added

Made-with: Cursor

* Rename 7093.md to 7094.md

* Add concurrency control to unfreeze job in merge-freeze-unfreeze workflow

- Introduced concurrency settings to prevent overlapping executions of the unfreeze job based on labels.
- Updated error handling to abort the process if fetching the current merge freeze status fails, ensuring unblocked PRs are not overwritten.

* Refactor Merge Freeze workflow to simplify PR unblocking process

- Updated the workflow to directly post the PR to the unblocked list without fetching the current freeze status.
- Improved error handling by ensuring the access token is set before proceeding with the API call.
2026-02-28 21:15:11 +00:00
Matiss Janis Aboltins
6022929551 [Cursor] Development environment setup (#7088)
* [AI] Add Cursor Cloud specific instructions to AGENTS.md

Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>

* [autofix.ci] apply automated fixes

* Add release notes for PR #7088

* [AI] Fix Node.js minimum version requirement in AGENTS.md (#7089)

* [AI] Fix outdated Node.js version requirement in Build Failures section

Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>

* [AI] Add test budget tip to Cursor Cloud instructions

Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
2026-02-27 20:38:37 +00:00
Copilot
e65429497d [AI] Remove 'suspect ai generated' label and associated workflow (#7087)
* Initial plan

* [AI] Remove 'suspect ai generated' label and associated workflow

Co-authored-by: MatissJanis <886567+MatissJanis@users.noreply.github.com>

* [AI] Remove 'suspect ai generated' label from coderabbit config

Co-authored-by: MatissJanis <886567+MatissJanis@users.noreply.github.com>

* Add release notes for PR #7087

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: MatissJanis <886567+MatissJanis@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-26 20:51:47 +00:00
Matiss Janis Aboltins
3758d72b65 Mobile rules item alignment (#7081)
* [AI] Fix mobile rules list items to be full width and left-aligned

- Override Button's default justifyContent/alignItems centering in
  ActionableGridListItem to use flex-start alignment
- Add width: 100% to RulesListItem's SpaceBetween to fill the item width

Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>

* Add release notes for PR #7081

* Change category from Enhancements to Bugfix

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-26 20:22:29 +00:00
Matiss Janis Aboltins
032d10ac42 [AI] Fix API build output path (dist/index.js instead of dist/api/index.js) (#7084)
* [AI] Fix API build output path (dist/index.js instead of dist/api/index.js)

- Set rootDir in packages/api/tsconfig.json so output is under dist/ not dist/api/
- Remove loot-core pegjs.ts from include; add local typings/pegjs.d.ts
- Use mkdir -p in build:migrations for idempotent build
- Exclude **/@types/** so declaration output does not conflict with input

Made-with: Cursor

* Update TypeScript configuration in api package to refine exclude patterns
2026-02-26 20:20:38 +00:00
Michael Clark
f97a89dc28 🐛 Fix file path on windows (#7076)
* fix file path on windows

* file path in migrations

* release notes
2026-02-25 15:00:10 +00:00
Juulz
a4bd301ec6 🐞 Midnight theme: Change menuAutoCompleteTextHover color - Fixes #7029 (#7048)
* Change menuAutoCompleteTextHover color to green400

* Change menuAutoCompleteTextHover color to green400 in Midnight theme.

Change menuAutoCompleteTextHover color to green400 in Midnight theme.

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-02-24 17:29:09 +00:00
Julian Dominguez-Schatz
18072e1d8b Validate file IDs for correctness (#7067)
* Validate file IDs for correctness

* Add release notes
2026-02-24 15:32:50 +00:00
Juulz
a1e0b3f45d Rename theme 'Okabe Ito' to 'Color-blind (dark)' (#7058)
* Rename theme 'Okabe Ito' to 'Color-blind (dark)'

* Rename 'Okabe Ito' theme to 'Color-blind (dark)'

* Fix capitalization in theme name for consistency
2026-02-23 18:49:45 +00:00
Matiss Janis Aboltins
0b361e45b4 [AI] Bump version to 26.2.1 (#7052)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-23 15:52:58 +00:00
Matiss Janis Aboltins
b3052dda05 v26.2.1: critical security fix for simplefin, pluggy and multi-user (#7043)
* Add release notes for version 26.2.1, including critical security fixes for SimpleFin, Pluggy, and multi-user setups. Remove outdated upcoming release notes for related bugfixes.

* Add release notes for PR #7043

* Delete upcoming-release-notes/7043.md

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-22 14:43:50 +00:00
Matiss Janis Aboltins
31a027fc64 [AI] Add per-package tsconfigs and typescript-strict-plugin for typecheck (#7019)
* [AI] Add per-package tsconfigs and typescript-strict-plugin for typecheck

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

* Update TypeScript configuration across multiple packages to correct plugin path key from "path" to "paths" and add reference to process-worker typings in index.electron.ts.

* Remove reference to process-worker typings in index.electron.ts and add new process-worker typings file for global Process augmentation.

* Refactor TypeScript build configurations across multiple packages by removing tsconfig.dist.json files and updating build scripts to use default TypeScript compilation. Adjusted compiler options to target ES2021 and enable declaration generation.

* Update TypeScript configuration in api package to refine include and exclude patterns for better file management.

* Update build script in api package to ensure migration SQL files are copied to the correct directory by creating the destination folder if it doesn't exist.

* Update TypeScript configurations in crdt and desktop-electron packages to refine include and exclude patterns for improved file management.

* Update TypeScript dependencies across multiple packages to include typescript-strict-plugin for enhanced type checking and maintain consistency in package.json files.

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-21 21:26:22 +00:00
Julian Dominguez-Schatz
cfc18c240a Add limit/refill automation types (#6692)
* Add limit/refill automation components

* Add release note

* Fix typecheck

* Rabbit PR feedback

* Review
2026-02-21 20:58:54 +00:00
Matiss Janis Aboltins
a68b2acac3 [AI] Enforce file access authorization on sync API endpoints (#7040)
* [AI] Enforce file access authorization on sync API endpoints

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

* Refactor file deletion authorization to return error message as text

* Refactor file upload validation to improve error handling

* Add tests to allow admin users to retrieve encryption keys and sync files for other users

- Implemented a test for admin access to retrieve encryption keys for another user's file in the /user-get-key endpoint.
- Added a test for admin users to sync another user's file in the /sync endpoint, ensuring proper response and headers.

These changes enhance the authorization checks for admin actions on user files.

* Refactor file cleanup in tests to use onTestFinished for better error handling

* Enhance admin capabilities in file management tests

* Add migration to backfill file owners with admin ID

* Enhance file access authorization in sync API

* Update migration to backfill file owners with admin ID to ensure consistent ordering in the query

* Refactor access control tests for file downloads in sync API

* Add test for non-owner file download access via user_access in sync API

This test verifies that users with appropriate access can download files owned by others, utilizing the requireFileAccess logic and UserService.countUserAccess. It ensures correct response headers and content delivery for shared files.

* Refactor file cleanup in upload and download tests to utilize onTestFinished for improved error handling

This update consolidates file cleanup logic in the test suite, ensuring that temporary files are removed after each test execution. The changes enhance the reliability of tests by consistently managing file state across various scenarios.

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-21 08:45:48 +00:00
Matiss Janis Aboltins
a25be5c95c [AI] Remove usage of 'web' file types (#7033)
* [AI] Desktop client, E2E, loot-core, sync-server and tooling updates

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

* Refactor database handling in various modules to use async/await for improved readability and error handling. This includes updates to database opening and closing methods across multiple files, ensuring consistent asynchronous behavior. Additionally, minor adjustments were made to encryption functions to support async operations.

* Refactor sync migration tests to utilize async/await for improved readability. Updated transaction handling to streamline event expectations and cleanup process.

* Refactor various functions to utilize async/await for improved readability and error handling. Updated service stopping, encryption, and file upload/download methods to ensure consistent asynchronous behavior across the application.

* Refactor BudgetFileSelection component to use async/await for onSelect method, enhancing error handling and readability. Update merge tests to utilize async/await for improved clarity in transaction merging expectations.

* Refactor filesystem module to use async/await for init function and related database operations, enhancing error handling and consistency across file interactions. Updated tests to reflect asynchronous behavior in database operations and file writing.

* Fix typo in init function declaration to ensure it returns a Promise<void> instead of Proise<void>.

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6987

* Update tests to use async/await for init function in web filesystem, ensuring consistent asynchronous behavior in database operations.

* Update VRT screenshot for payees filter test to reflect recent changes

* Update filesystem module to remove web-specific implementations and streamline path handling. Refactor file operations to enhance type safety and consistency across different environments. Add tests for SQLite interactions and ensure proper handling of database transactions.

* Add release notes for maintenance: Remove usage of 'web' file types

* Refactor filesystem module to use type annotations for exports and improve consistency across methods. Remove deprecated web file handling and enhance encryption functions for better browser compatibility.

* Trigger CI

* Add asyncStorage API file to export Electron index module

* Trigger CI

* Feedback: typo

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-20 18:01:36 +00:00
Pratik Silwal
bf1947a119 Prevent single-slash paths from being parsed as filepaths (#6966)
* fix: prevent single-slash paths from being parsed as filepaths

* add release notes

* [autofix.ci] apply automated fixes

* test: add tests related to filepath

* additonal test from coderabbit

---------

Co-authored-by: Pratik Silwal <pratiksilwal@Pratiks-MacBook-Air.local>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-02-20 12:38:29 +00:00
Mats Nilsson
37ad0ed563 Hide selected accounts in the mobile filter (#7030)
When filtering for accounts in the mobile view of reports, hide the
already selected accounts.
2026-02-20 12:34:57 +00:00
Daniel Bates
89d68ea2f8 Add tooltip to imported payee in rule result window (#7031)
* Add tooltip to imported payee column in rule result window

The imported payee column in SimpleTransactionsTable was missing a
title attribute, so truncated text had no tooltip on hover. Other
columns (category, account, notes) already pass title for this purpose.

Fixes #7003

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add release notes for #7031

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Your Name <your-email@example.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 12:32:13 +00:00
Kenny McCormick
1ad7b6f781 Add ACTUAL_USER_CREATION_MODE documentation to oauth-auth.md (#6935)
* Add ACTUAL_USER_CREATION_MODE documentation to oauth-auth.md

* [autofix.ci] apply automated fixes

* add note that first external auth user is admin and owner

Added details about admin permissions and server ownership for users authenticating with OpenID/OAuth2.

* improve ACTUAL_USER_CREATION_MODE environment documentation

clarify warning about server owner

* [autofix.ci] apply automated fixes

* move first user admin warning to "after setup" section of OIDC documentation

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-02-20 02:24:43 +00:00
Matiss Janis Aboltins
fd9ee868a6 Enhance PR template with structured sections (#6989)
* Enhance PR template with description, type of change, and checklist sections

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

* Update PR template to streamline instructions for writing release notes

* Remove unnecessary lines from the PR template to streamline instructions for writing release notes

* Add release notes for PR #6989

* Update category in release notes

Changed category from Enhancements to Maintenance.

* Update PULL_REQUEST_TEMPLATE.md

* Update AGENTS.md and PULL_REQUEST_TEMPLATE.md to clarify PR submission guidelines

* Update 6989.md

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-19 22:46:30 +00:00
Matiss Janis Aboltins
0de44af1de Require authentication for SimpleFIN and Pluggy.ai endpoints (#7034)
* Add authentication middleware to SimpleFIN and Pluggy.ai endpoints

Protect /simplefin/* and /pluggyai/* routes with validateSessionMiddleware
so only authenticated users can access bank account and transaction data.

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

* Release notes

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-19 22:46:10 +00:00
Juulz
cf58712bf1 🎨 High Contrast Light theme for Actual (#7032)
* Update customThemeCatalog.json

* [autofix.ci] apply automated fixes

* Create 7032.md

🎨 High contrast light theme.

* Update customThemeCatalog.json

* Update 7032.md

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-02-19 21:39:25 +00:00
Dagur Páll Ammendrup
6460af3de4 Set inital focus on category when covering overspending (#7012)
* Set inital focus on category when covering overspending

* Fixup: Make sure that the amount is set

* Unused import

* Fix bug where typing an amount and pressing enter uses previous value

---------

Co-authored-by: Dagur Ammendrup <dagurp@vivaldi.com>
2026-02-19 18:51:07 +00:00
Pratik Silwal
ce890faeeb Fix Net Worth Calculations (#6968)
* fix: computed priorPeriodNetWorth and use it as a baseline for net worth calculations

* add release notes

* correct spelling

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6968

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6968

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6968

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6968

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6968

* note

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: youngcw <calebyoung94@gmail.com>
2026-02-19 17:06:21 +00:00
Matt Fiddaman
848b86cd59 ⬆️ recharts (3.4.0 → 3.7.1) (#7022)
* recharts (3.4.1 -> 3.7.0)

* Cell & activeShape deprecation

* note

* fix textAnchor

* remove Cell in BarGraph
2026-02-19 16:28:41 +00:00
Crhistopher Suriel
ec22923f18 feat(currency): Add Dominican Peso (DOP) currency (#7028)
* feat(currency): Add Dominican Peso (DOP) currency

* Add release notes
2026-02-19 14:50:14 +00:00
dependabot[bot]
27402ee2b3 Bump @isaacs/brace-expansion from 5.0.0 to 5.0.1 (#7020)
* Bump @isaacs/brace-expansion from 5.0.0 to 5.0.1

Bumps @isaacs/brace-expansion from 5.0.0 to 5.0.1.

---
updated-dependencies:
- dependency-name: "@isaacs/brace-expansion"
  dependency-version: 5.0.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* note

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2026-02-19 14:48:58 +00:00
Matiss Janis Aboltins
0472211925 [AI] lint: await-thenable, no-floating-promises (#6987)
* [AI] Desktop client, E2E, loot-core, sync-server and tooling updates

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

* Refactor database handling in various modules to use async/await for improved readability and error handling. This includes updates to database opening and closing methods across multiple files, ensuring consistent asynchronous behavior. Additionally, minor adjustments were made to encryption functions to support async operations.

* Refactor sync migration tests to utilize async/await for improved readability. Updated transaction handling to streamline event expectations and cleanup process.

* Refactor various functions to utilize async/await for improved readability and error handling. Updated service stopping, encryption, and file upload/download methods to ensure consistent asynchronous behavior across the application.

* Refactor BudgetFileSelection component to use async/await for onSelect method, enhancing error handling and readability. Update merge tests to utilize async/await for improved clarity in transaction merging expectations.

* Refactor filesystem module to use async/await for init function and related database operations, enhancing error handling and consistency across file interactions. Updated tests to reflect asynchronous behavior in database operations and file writing.

* Fix typo in init function declaration to ensure it returns a Promise<void> instead of Proise<void>.

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6987

* Update tests to use async/await for init function in web filesystem, ensuring consistent asynchronous behavior in database operations.

* Update VRT screenshot for payees filter test to reflect recent changes

* [AI] Fix no-floating-promises lint error in desktop-electron

Wrapped queuedClientWinLogs.map() with Promise.all and void operator to properly handle the array of promises for executing queued logs.

Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>

* Refactor promise handling in global and sync event handlers

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6987

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
2026-02-19 14:22:05 +00:00
Matt Fiddaman
a38104244a ⬆️ @playwright/test (1.57.0 → 1.58.2) (#7021)
* @playwright/test (1.57.0 → 1.58.2)

* note

* disable moving nav bar animations

* vrt
2026-02-19 00:19:54 +00:00
Matiss Janis Aboltins
77b848ca84 [AI] Allow var(--name) in custom theme CSS (no fallbacks) (#7018)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-18 22:16:25 +00:00
Matt Fiddaman
5179ac7c2d ⬆️ mid month dependency bump (#7013)
* baseline-browser-mapping (^2.9.14 → ^2.9.19)

* minimatch (^10.1.1 → ^10.1.2)

* lage (^2.14.15 → ^2.14.17)

* react-aria-components (^1.14.0 → ^1.15.1)

* @storybook/addon-a11y (^10.2.0 → ^10.2.7)

* p-limit (^7.2.0 → ^7.3.0)

* better-sqlite3 (^12.5.0 → ^12.6.2)

* vitest (^4.0.16 → ^4.0.18)

* @storybook/addon-docs (^10.2.0 → ^10.2.7)

* @storybook/react-vite (^10.2.0 → ^10.2.7)

* eslint-plugin-storybook (^10.2.0 → ^10.2.7)

* storybook (^10.2.0 → ^10.2.7)

* @codemirror/state (^6.5.3 → ^6.5.4)

* @swc/core (^1.15.8 → ^1.15.11)

* @tanstack/react-query (^5.90.19 → ^5.90.20)

* @vitejs/plugin-basic-ssl (^2.1.3 → ^2.1.4)

* @vitejs/plugin-react (^5.1.2 → ^5.1.3)

* i18next (^25.7.4 → ^25.8.4)

* react-aria (^3.45.0 → ^3.46.0)

* react-hotkeys-hook (^5.2.1 → ^5.2.4)

* react-i18next (^16.5.1 → ^16.5.4)

* sass (^1.97.2 → ^1.97.3)

* @easyops-cn/docusaurus-search-local (^0.52.2 → ^0.52.3)

* react (^19.2.3 → ^19.2.4)

* react-dom (^19.2.3 → ^19.2.4)

* component lib peer deps

* eslint-vitest-rule-tester (^3.0.1 → ^3.1.0)

* lru-cache (^11.2.4 → ^11.2.5)

* ua-parser-js (^2.0.7 → ^2.0.9)

* cors (^2.8.5 → ^2.8.6)

* @babel/core (^7.28.5 → ^7.29.0)

* @types/node (^22.19.3 → ^22.19.10)

* react (mixed → 19.2.4)

* @testing-library/react (16.3.0 → 16.3.2)

* react-router (7.12.0 → 7.13.0)

* vite-plugin-node-polyfills (^0.24.0 → ^0.25.0)

* pluggy-sdk (^0.79.0 → ^0.83.0)

* note
2026-02-18 22:09:28 +00:00
youngcw
2bb5a861c1 📖 start of reports dashboard updates (#6976)
* start of reports dashboard updates

* spelling

* Add release notes for PR #6976

* don't need one of these

* consistent naming

* bunny fixes

* images

* image again

* extenion

* fix

* consistent naming

* naming

* fix

* last one

* Ill be done with this someday

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-18 22:07:01 +00:00
Karim Kodera
bc32f4fcde Document graph color variables for custom themes (#7011)
* Document graph color variables for custom themes

Added documentation for graph color variables in custom themes.

* [autofix.ci] apply automated fixes

* Update custom themes documentation for clarity

Clarify the impact of color palettes on custom report widget graphs and format the list of color variables.

* Update packages/docs/docs/experimental/custom-themes.md

Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>

* Fix color variable syntax in custom themes documentation

Updated color variable syntax for chart colors in documentation.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2026-02-18 18:24:52 +00:00
Joel Jeremy Marquez
848eaadb0f Move redux state to react-query - payees states (#6880)
* Move redux state to react-query - account states

* Fix onbudget and offbudget displaying closed accounts

* Move redux state to react-query - payees states

* Add release notes for PR #6880

* Replace usage of logger in desktop-client with console

* Address feedback on adding default data to usePayees (#6931)

* Initial plan

* Add default data to usePayees usages using inline destructuring

Co-authored-by: joel-jeremy <20313680+joel-jeremy@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: joel-jeremy <20313680+joel-jeremy@users.noreply.github.com>

* Fix imports

* Update empty payees list test

* Cleanup and simplify AccountEntity definition to fix `satisfies` syntax

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: joel-jeremy <20313680+joel-jeremy@users.noreply.github.com>
2026-02-18 17:04:26 +00:00
Joel Jeremy Marquez
5ac2947342 Update useAccounts to return react query states (#7009)
* Fix redirect to accounts page when no accounts exists

* Add release notes for PR #7007

* Use isFetching

* Update useAccounts to return react query states (e.g. isPending, isFetching, etc.)

* Add release notes for PR #7009

* Delete upcoming-release-notes/7007.md

* Change category from Enhancements to Maintenance

Refactor `useAccounts` to improve data handling.

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-18 05:32:42 +00:00
Joel Jeremy Marquez
da0154a41b Fix redirect to accounts page when no accounts exists (#7007)
* Fix redirect to accounts page when no accounts exists

* Add release notes for PR #7007

* Use isFetching

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-17 22:33:49 +00:00
Joel Jeremy Marquez
180a38890c Improve category server app and react query mutation error handling (#6958)
* Improve category server app and react query mutation error handling

* Add release notes for PR #6958

* Fix test

* Fix throwing async test

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

* Do not swallow exceptions when batching messages - propagate instead

* Update error type to make 'cause' optional

Make 'cause' property optional in error type.

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-02-17 20:30:57 +00:00
Roy
d3f2f1f7ae Add reports to command bar (#7002)
* Add reports to command bar

* Add release notes
2026-02-17 20:03:44 +00:00
Karim Kodera
c7efb61b84 Add theming to charts and hence allowing custom themes on charts (#6909)
* Add theming to charts and hence allowing custom themes on charts

* Removing additional color scales for charts.

* Fixed return fail over value.
2026-02-17 20:00:21 +00:00
Pieter Ouwerkerk
d605d59d01 Add Tag API docs (#6979)
* Add Tag object type definition for API docs

* Add Tags nav entry to API reference sidebar

* Add Tags methods and examples to API reference
2026-02-17 15:54:25 +00:00
HadiAyache
f7227f4e62 Fix operator precedence grouping for */ and +/- (#6993)
* Fix operator precedence grouping for */ and +/-

* Add release note for #6993

* Fix exponent associativity and add regression test

---------

Co-authored-by: Hadi Ayache <hadiayache@Hadis-Mac-mini.local>
2026-02-17 09:23:15 +00:00
Joel Jeremy Marquez
253530e239 Retrofit dashboard hooks to use react-query (#6957)
* Retrofit useReports to use react-query under the hood

* Add release notes for PR #6951

* Update 6951.md

* Report mutations

* Fix react query cache not being cleared when switching budgets (#6953)

* Fix react query cache not being cleared when switching budgets

* React does not want to export function from src/index

* Release note

* Use react-query is dashboard queries and mutations

* Add release notes for PR #6957

* [autofix.ci] apply automated fixes

* Fix typecheck errors

* Coderabbit feedback

* Make error cause optional

* Rename useDashboardWidgetCopyMenu and update useDashboardWidget to accept object to prevent need to default id to empty string

---------

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>
2026-02-16 22:26:32 +00:00
Julian Dominguez-Schatz
e8f6ceeb98 Address some more low-hanging fruit for ts-strict-ignore (#6992)
* Address some more low-hanging fruit for ts-strict-ignore

* Add release notes

* Fix small issues

* Rabbit
2026-02-16 15:30:27 +00:00
Matiss Janis Aboltins
c031d9aa4f fix(ios): restore status bar color on iOS 26.2 (Safari 26) (#6983)
* fix(ios): restore status bar color on iOS 26.2 (Safari 26)

Safari 26 no longer uses the theme-color meta tag for the status bar
and instead derives the tint from the body background. Set body
background-color in HTML and sync it with the app theme so the status
bar shows purple (or the active theme) instead of white.

Fixes #6946

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

* [autofix.ci] apply automated fixes

* refactor(theme): update useMetaThemeColor to accept theme values directly

Modified the useMetaThemeColor hook to accept theme color values instead of predefined keys. Updated FinancesApp and ManagementApp components to utilize the new implementation, ensuring proper theme color handling based on screen width.

* [autofix.ci] apply automated fixes

* refactor(theme): remove unused body background color in index.html and add tests for useMetaThemeColor hook

Deleted the commented-out body background color in index.html to clean up the code. Added comprehensive tests for the useMetaThemeColor hook to ensure proper handling of theme colors, including support for CSS variables and reactivity to theme changes.

* refactor(theme): improve getPropertyValueFromVarString function in useMetaThemeColor hook

* [autofix.ci] apply automated fixes

* Add release notes for PR #6983

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-15 19:19:50 +00:00
Matiss Janis Aboltins
d4e25f4047 [AI] Introduce type-aware oxlint and disable no-duplicate-type-constituents rule (#6984)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-15 19:05:57 +00:00
Matiss Janis Aboltins
a7f96a59fa [AI] Update CodeRabbit config for suspect AI generated labels (#6985)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-15 18:53:08 +00:00
youngcw
8af64ddd5e 📖 Move pluggy.ai doc out of experimental (#6975)
* move pluggy out of experimental

* remove note

* Add release notes for PR #6975

* fixes

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-14 22:04:18 +00:00
Julian Dominguez-Schatz
26dbb219aa Implement missing logic for limit template type (#6690)
* core: support limit refill templates

* notes: refill templates

* core: apply refill limits during runs

* core: prioritize refill limits

* Patch

* Update release note

* Fix typecheck

* rework.  Tests and template notes still need reworked

* fix parser syntax

* Fix type issue

* Fix after rebase, support merging limit+refill

* PR feedback

---------

Co-authored-by: youngcw <calebyoung94@gmail.com>
2026-02-14 21:52:48 +00:00
Alvin Zhao
c6656a2815 Include category group in transaction export (#6960)
* include category group in transaction export

* Apply suggestion from @yzAlvin

Co-authored-by: Alvin Zhao <yzalvin@duck.com>

---------

Co-authored-by: youngcw <calebyoung94@gmail.com>
2026-02-14 16:31:27 +00:00
Julian Dominguez-Schatz
6358345286 Fix some low-hanging-fruit @ts-strict-ignore (#6969)
* Fix low-hanging-fruit `@ts-strict-ignore`

* Add release notes

* A few more
2026-02-14 15:58:25 +00:00
Roy
5943ae3df5 Add filter option for category groups (#6834)
* Add filter by category groups

* Add tests

* Add release notes

* [autofix.ci] apply automated fixes

* Fix typecheck findings

* Fix modal

* Address nitpick comment (filterBy)

* Fix e2e tests

* Make group a subfield of category

* Fix test by typing in autocomplete

* Replace testId with a11y lookups

* Apply new type import style rules

* Apply feedback

* Improve typing on array reduce, remove manual type coercion

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-02-14 15:03:11 +00:00
Matiss Janis Aboltins
7e8a118411 [AI] lint: convert oxlint warnings to errors (#6970)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-14 13:48:50 +00:00
Joel Jeremy Marquez
465608c76b Move redux state to react-query - account states (#6140)
* Fix typecheck errors

* Move redux state to react-query - account states

* TestProviders

* Add release notes for PR #6140

* Fix lint error

* Fix TestProviders

* Coderabbit feedback

* Cleanup

* [autofix.ci] apply automated fixes

* Fix TestProviders

* Fix onbudget and offbudget displaying closed accounts

* [skip ci] Change category to Maintenance and update migration text

* Replace logger calls in desktop-client to console

* Fix lint errors

* Clear react query on closing of budget file similar to redux resetApp action

* [autofix.ci] apply automated fixes

* Remove sendThrow

* Code review feedback

* [autofix.ci] apply automated fixes

* Fix import

* Fix import

* Coderabbit feedback

---------

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>
2026-02-14 09:32:37 +00:00
Matiss Janis Aboltins
5062fa78a8 Agent instructions for commit messages and PR titles (#6964)
* [AI] Add mandatory [AI] prefix requirement for commit messages and PR titles

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

* [autofix.ci] apply automated fixes

* Add release notes for PR #6964

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-14 07:54:48 +00:00
Matiss Janis Aboltins
e178396e48 Docs: add Claude Code Pro subscription benefit for core contributors (#6963)
* Add Claude Code Pro subscription benefit for core contributors

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

* Add release notes for PR #6963

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-14 03:00:36 +00:00
Matiss Janis Aboltins
09d85bbdc5 docs: add Architecture Decision Records page with bank sync credential ADR (#6965)
* docs: add Architecture Decision Records page for controversial decisions

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

* Add release notes for PR #6965

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-14 01:14:15 +00:00
Joel Jeremy Marquez
a0378c10a9 Move redux state to react-query - tags states (#6941)
* Move redux state to react-query - tags states

* Add release notes for PR #6941

* Cleanup sendThrow

* Cleanup

* Update import

* Fix import

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-13 19:33:21 +00:00
Adam Stück
ca944baee5 feat: show/hide reconciled transactions on mobile (#6896)
* feat: show/hide reconciled transactions on mobile

Resolves #2969

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-02-13 17:25:55 +00:00
Joel Jeremy Marquez
cb5237359c Rename loot-core/platform/client/fetch to connection to match server-side package (#6943)
* Rename loot-core/platform/client/fetch package to connection to match the server side package name. Also to avoid confusion with the native fetch package.

* Update connection/init method to not receive any parameter to so browser and default implementation have the same signature

* Add release notes for PR #6943

* Fix names

* Fix imports

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-13 16:53:51 +00:00
Alexis Vielma
68bb33e5e6 feat: add back button to reports pages (#6702)
* feat: add back button to reports pages

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6702

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-13 02:35:08 +00:00
J B
cf5fe67e7b API Account Object (#6915)
* api change

* docs

* lint

* release notes

* spelling

* [autofix.ci] apply automated fixes

* spelling

* whoopsie, thanks rabbit

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-02-13 00:18:49 +00:00
Joel Jeremy Marquez
f8b4e87a67 Update send function to propagate any errors and fix catchErrors to return the error in result when an unknown command/method is sent to the browser server (#6942)
* Fix send not returning error when catchErrors option is enabled and an unknown method error is encountered

* Add release notes for PR #6942

* Fix send to properly propagate errors from the server

* Update release note

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-12 23:47:21 +00:00
Joel Jeremy Marquez
8ae90a7ad1 Retrofit useReports to use react-query under the hood (#6951)
* Retrofit useReports to use react-query under the hood

* Add release notes for PR #6951

* Update 6951.md

* Report mutations

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-12 23:47:05 +00:00
Matiss Janis Aboltins
6f7af102a6 Upgrade oxfmt and oxlint, update .oxfmtrc.json import patterns (#6955)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 23:08:35 +00:00
Piyush Katkar
96a3128305 Fix mobile budget amount inputs when hide decimal places is enabled (#6945) 2026-02-12 22:59:23 +00:00
Joel Jeremy Marquez
003efecc23 Fix react query cache not being cleared when switching budgets (#6953)
* Fix react query cache not being cleared when switching budgets

* React does not want to export function from src/index

* Release note
2026-02-12 20:12:51 +00:00
Michael Clark
155e4df219 🎨 Add remaining component stories to storybook (#6940)
* final storybook stories

* release notes

* spelling mistake
2026-02-12 08:46:24 +00:00
Joel Jeremy Marquez
67d6592333 Add refetchOnSync option to useTransactions to refetch when a server sync event is emitted (#6936)
* Migrate setupTests.js to TypeScript with proper types (#6871)

* Initial plan

* Rename setupTests.js to setupTests.ts and add proper types

Co-authored-by: joel-jeremy <20313680+joel-jeremy@users.noreply.github.com>

* Extract Size type to avoid duplication

Co-authored-by: joel-jeremy <20313680+joel-jeremy@users.noreply.github.com>

* Add release note for setupTests TypeScript migration

Co-authored-by: joel-jeremy <20313680+joel-jeremy@users.noreply.github.com>

* Rename release note file to match PR number 6871

Co-authored-by: joel-jeremy <20313680+joel-jeremy@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: joel-jeremy <20313680+joel-jeremy@users.noreply.github.com>

* Delete setupTests PR release note

* Add refetchOnSync to useTransactions to refetch when a server sync event is emitted

* Add release note for useTransactions refetchOnSync feature (#6937)

* Initial plan

* Add release note for PR 6936

Co-authored-by: joel-jeremy <20313680+joel-jeremy@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: joel-jeremy <20313680+joel-jeremy@users.noreply.github.com>

* Coderabbit feedback

---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: joel-jeremy <20313680+joel-jeremy@users.noreply.github.com>
2026-02-12 00:16:37 +00:00
An, Tran Cong Viet
7fa9fa900b feat(currency): Add Vietnamese Dong (VND) currency (#6902)
* feat(currency): add support for vietnamese dong currency

* release: add upcoming release note

* fix(currency): change the number format for vietnamese currency

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-02-11 20:04:06 +00:00
jintakhan
9798c26462 feat(currency): Add South Korean Won (#6846)
* Add South Korean Won

* Update currencies.ts

* Add release notes
2026-02-11 17:00:41 +00:00
Joel Jeremy Marquez
37a7d0eccd Retrofit useTransactions to use react-query under the hood (#6757)
* Retrofit useTransactions to use react-query under the hood

* Add release notes for PR #6757

* Update packages/desktop-client/src/transactions/queries.ts

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

* Disable when there is no query parameter

* Fix typecheck errors

* Remove space

* Update tests

* Coderabbit: Add pageSize to query key

* Use isPending instead of isFetching

* Unexport mockStore

* Revert variables

* Change category from Enhancements to Maintenance

Refactor the useTransactions hook to improve data fetching with react-query.

* Fix lint errors

* Fix lint errors

* Migrate setupTests.js to TypeScript with proper types (#6871)

* Initial plan

* Rename setupTests.js to setupTests.ts and add proper types

Co-authored-by: joel-jeremy <20313680+joel-jeremy@users.noreply.github.com>

* Extract Size type to avoid duplication

Co-authored-by: joel-jeremy <20313680+joel-jeremy@users.noreply.github.com>

* Add release note for setupTests TypeScript migration

Co-authored-by: joel-jeremy <20313680+joel-jeremy@users.noreply.github.com>

* Rename release note file to match PR number 6871

Co-authored-by: joel-jeremy <20313680+joel-jeremy@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: joel-jeremy <20313680+joel-jeremy@users.noreply.github.com>

* [autofix.ci] apply automated fixes

* Update transactionQueries

* Delete setupTests PR release note

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: joel-jeremy <20313680+joel-jeremy@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-02-11 16:58:07 +00:00
Joel Jeremy Marquez
e3e4b13d2b Move redux state to react-query - category states [Part 2 - expose react-query states e.g. isPending, isSuccess, etc] (#6882)
* Move redux state to react-query - category states [Part 2 - expose react-query states]

* Add release notes for PR #6882

* Add default values to useCategories destructuring to prevent undefined crashes (#6884)

* Initial plan

* Add missing defaults to all useCategories usages

Co-authored-by: joel-jeremy <20313680+joel-jeremy@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: joel-jeremy <20313680+joel-jeremy@users.noreply.github.com>

* Fix lint errors

* Fix rebase mistake

* Change category from Enhancements to Maintenance

Migrate state management for category from Redux to React Query and update related hooks and components.

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: joel-jeremy <20313680+joel-jeremy@users.noreply.github.com>
2026-02-11 16:44:32 +00:00
Michael Clark
138ea810d6 🎨 Reorganising Storybook and more component stories (#6924)
* reorg of storybook docs and add some new components

* releaes notes

* Update meta tags for Actual Budget Design System

* Increase sidebar item font size from 14px to 16px
2026-02-11 09:01:11 +00:00
youngcw
07ff514c12 [Goals] fix tracking budget balance carryover for templates (#6922)
* fix tracking budget balance carryover for templates

* Add release notes for PR #6922

* fix note

* fix tests

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-11 01:52:49 +00:00
Gabriel J.
2ca352aaa7 fix(schedules): prevent past missed schedule dates from being marked as upcoming (#6925)
Fixes #6872
2026-02-11 01:04:42 +00:00
Gabriel J.
078da08ad5 Fix/6885 crash when rule has empty date field (#6905)
* Fix crash when rule date field loses focus while empty

Fixes #6885

* Remove ts-strict-ignore and fix types in DateSelect

* Generate release note 6905
2026-02-10 23:05:37 +00:00
samekh248
edcf893a27 Add butterfly custom theme (#6900)
* Added butterfly custom theme

* Added release notes
2026-02-10 23:02:52 +00:00
Juulz
38a72656df [FIX] Update DesktopLinkedNotes so links stay inline (#6858)
* Update DesktopLinkedNotes.tsx

* Update DesktopLinkedNotes.tsx

* Update DesktopLinkedNotes so links stay inline

* Update TransactionsTable.tsx

make sure flexDirection is row.

* [autofix.ci] apply automated fixes

* Update DesktopLinkedNotes.tsx

* Update TransactionsTable.tsx

* Update DesktopLinkedNotes.tsx

* Update NotesTagFormatter.tsx trial

* Update DesktopLinkedNotes.tsx

* Update NotesTagFormatter.tsx

* Update NotesTagFormatter.tsx

* Update DesktopLinkedNotes.tsx

* [autofix.ci] apply automated fixes

* Update TransactionsTable.tsx

* Update DesktopLinkedNotes.tsx

Add role for accessibility

* Update DesktopLinkedNotes.tsx

* Update DesktopLinkedNotes.tsx

Revert to original

* Update DesktopLinkedNotes.tsx

Try to style the bare button to make it work.

* [autofix.ci] apply automated fixes

* Update DesktopLinkedNotes.tsx

Reverting to current master

* Update DesktopLinkedNotes.tsx

Add nowrap styling.

* Update DesktopLinkedNotes.tsx

* Update TransactionsTable.tsx

* Update TransactionsTable.tsx

* [autofix.ci] apply automated fixes

* Update DesktopLinkedNotes.tsx

* Update TransactionsTable.tsx

* Update DesktopLinkedNotes.tsx

* Update DesktopLinkedNotes.tsx

* Update DesktopLinkedNotes.tsx

* [autofix.ci] apply automated fixes

* Update DesktopLinkedNotes.tsx

* Update DesktopLinkedNotes.tsx

* Update DesktopLinkedNotes.tsx

* Update DesktopLinkedNotes.tsx

* Update DesktopLinkedNotes.tsx

* Update DesktopLinkedNotes.tsx

* revert TransactionsTable.tsx

* Update TransactionsTable.tsx

* Update TransactionsTable.tsx

* Update TransactionsTable.tsx

* Update DesktopLinkedNotes.tsx

* Update TransactionsTable.tsx

* Update TransactionsTable.tsx

* Update DesktopLinkedNotes.tsx

* Update DesktopLinkedNotes.tsx

* Revert DesktopLinkedNotes.tsx

* Update DesktopLinkedNotes.tsx

* Update DesktopLinkedNotes.tsx

* Update DesktopLinkedNotes.tsx

* [autofix.ci] apply automated fixes

* Update DesktopLinkedNotes.tsx

* [autofix.ci] apply automated fixes

* Update DesktopLinkedNotes.tsx

* Update DesktopLinkedNotes.tsx

* [autofix.ci] apply automated fixes

* Update DesktopLinkedNotes.tsx

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: youngcw <calebyoung94@gmail.com>
2026-02-10 20:33:39 +00:00
Pieter Ouwerkerk
24f698910a Add Tag API (#6746)
* Add Tag API

* Add Tag API tests

* Add Release Note for #6746

* Make release note more user-facing

* Remove unnecessary type coercion in tagModel.fromExternal

Since APITagEntity picks all properties from TagEntity, the types are
structurally identical and TypeScript can verify compatibility without
manual coercion.

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

---------

Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
2026-02-10 17:22:55 +00:00
Jonathon Jongsma
cdaf06abee Remove duplication of CrossoverData type (#6928)
* Remove duplication of CrossoverData type

Move the CrossoverData type definition to crossover-spreadsheet.tsx and
import it from the Crossover and CrossoverCard files instead of having
duplicate definitions in each file.

Signed-off-by: Jonathon Jongsma <jonathon@quotidian.org>

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

---------

Signed-off-by: Jonathon Jongsma <jonathon@quotidian.org>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-02-10 16:06:30 +00:00
Matiss Janis Aboltins
c8aa0cf1d3 Refactor: extract tooltip components and clean up lint suppressions (#6721)
* Refactor: extract tooltip components and clean up lint suppressions

Extract CustomTooltip components from CrossoverGraph and NetWorthGraph
to module level to fix unstable nested components lint warnings. Also
consolidate theme file lint rule into oxlintrc.json and add proper
typing to styles object.

* Add release notes for maintenance updates addressing lint violations

* Remove style prop from CustomTooltip to prevent container layout styles from affecting tooltip

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

* Refactor NetWorthGraph component by extracting TrendTooltip and StackedTooltip into separate functions for improved readability and maintainability. Update tooltip props to include necessary parameters for rendering. Clean up unused code and enhance tooltip styling.

* Refactor NetWorthGraph component to streamline tooltip handling

- Removed unnecessary prop passing for translation function in TrendTooltip.
- Adjusted import statements for better clarity and consistency.
- Cleaned up code to enhance readability and maintainability.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-02-10 15:12:22 +00:00
Matiss Janis Aboltins
84cebed20b Enforce consistent TypeScript type import style (#6805) 2026-02-10 13:33:20 +00:00
Matiss Janis Aboltins
0c3b54ee7d Points: pay for contributions (#6481)
* Add point totals display to all statistics sections in count-points script

* Update funding contributors documentation and add release notes for contributor point counting script

* Refactor contributor points calculation and enhance PR category determination

- Updated point values for PR contributions: reduced points for Features, Enhancements, Bugfix, and Maintenance, and added a new category for Unknown contributions.
- Introduced a new function to retrieve the last commit SHA before a specified date to improve accuracy in reading release notes.
- Modified the getPRCategoryAndPoints function to accept a monthEnd parameter for better context in point assignment.

* Update contributor points values in count-points script to reflect new scoring system

* Add new blog post on funding contributors' next steps

This post outlines plans to expand the contributor compensation system, including broader rewards for project involvement, targeted donations, and a points-based system for feature work. It emphasizes transparency and community feedback in shaping future funding strategies.

* Increase Bugfix points from 2 to 3

* Change points awarded for Features to 2
2026-02-10 12:42:52 +00:00
Michael Clark
eb9f9b3a73 :electron: Flathub PR to be draft on release (#6910)
* make flathub pr draft so that we dont attract attention

* release notes

* grammar
2026-02-09 19:10:37 +00:00
Diego Palacios
5c31aa03ba Fix feedback link for budget analysis report experimental flag (#6914) 2026-02-09 18:28:43 +00:00
xaviuzz
266e7f9cac Fix Ctrl+Enter losing amount value when adding transaction (#6911)
* Fix Ctrl+Enter losing amount value when adding transaction

Fixes #6901

When using Ctrl+Enter to add a transaction immediately after typing
in the amount field, the value wasn't being committed before the
transaction was saved, resulting in a zero amount.

The fix wraps the add-and-close logic in an afterSave() callback
to ensure field values are committed before adding the transaction.

Added regression tests for both debit and credit fields to verify
the fix works correctly.

* Add release notes for PR #6911

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-08 21:39:25 +00:00
Michael Clark
e951e21fe1 🎨 Storybook docs for block, card, colorpicker, formerror (#6874)
* storybook docs for block, card, colorpicker, formerror

* release notes
2026-02-07 09:40:30 +00:00
Stephen Brown II
1a26253457 Update the Create Linked Account workflow to prompt for Starting Date and Balance (#6629)
* feat: Add optional starting date and balance for bank sync accounts

Adds the ability to specify a custom starting date and balance when
linking new bank sync accounts in the Select Linked Accounts modal.

Addresses: https://discord.com/channels/937901803608096828/1402270361625563186

Changes:
- Frontend: Added inline date and amount input fields in the account
  linking table for new accounts
- Redux: Extended link account actions to accept startingDate and
  startingBalance parameters
- Backend: Updated account linking handlers to pass custom values to
  sync logic
- Sync: Modified syncAccount and processBankSyncDownload to use custom
  starting date/balance for initial sync transactions

Features:
- Only displays starting options when creating new accounts (not upgrades)
- AmountInput with smart sign detection based on account balance
  (negative for credit cards/loans)
- Defaults to 90 days ago for date and 0 for balance
- Mobile-responsive with separate AccountCard layout
- Works across all sync providers: GoCardless, SimpleFIN, Pluggy.ai

The custom starting balance is used directly for the starting balance
transaction, and the custom starting date determines both the sync
start date and the transaction date for the starting balance entry.

* refactor: Extract shared types and components for starting balance inputs

- Create CustomStartingSettings type to replace repeated inline type definitions
- Extract StartingOptionsInput component to consolidate duplicate UI between mobile/desktop views
- Create LinkAccountBasePayload type shared across GoCardless, SimpleFIN, and PluggyAI link functions
- Apply same base type pattern to server-side link account handlers

This simplifies the code introduced for custom starting date/balance when linking bank accounts.

[autofix.ci] apply automated fixes

* allow explicit zero values

* refactor: add type guard for BankSyncError to remove oxlint-disable

- Create isBankSyncError() type guard function with proper type narrowing
- Remove oxlint-disable-next-line comment that suppressed the no-explicit-any rule
- Add JSDoc comments for both isBankSyncError and handleSyncError functions
- Remove redundant type assertion now that type guard narrows correctly

* refactor: address code review nitpicks for SelectLinkedAccountsModal

- Use locale-aware date formatting instead of toISOString()
- Extract isNewAccountOption helper to reduce duplication
- Align AccountCardProps type definition pattern with TableRowProps

* Add placeholder date/balance for already linked accounts

* [autofix.ci] apply automated fixes

* Use StartingBalanceInfo only, and add mobile view

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-02-06 21:44:30 +00:00
distantvapor
e72f18c5db Add new theme 'You Need A Dark Mode' to catalog (#6891)
* Add new theme 'You Need A Dark Mode' to catalog

* Add 'You Need A Dark Mode' theme to catalog
2026-02-06 20:18:12 +00:00
Christian Speich
5deb2cf790 Add bank sync option to update dates. (#6850)
Signed-off-by: Christian Speich <christian@spei.ch>
2026-02-06 20:16:32 +00:00
Joel Jeremy Marquez
111e01449d Add build-electron to tsconfig excludes (#6883)
* Add build-electron to tsconfig excludes

* Add release notes for PR #6883

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-06 19:20:03 +00:00
Joel Jeremy Marquez
c0bd920c26 Fix react-hooks/exhaustive-deps in DateSelect (#6864)
* Fix react-hooks/exhaustive-deps in DateSelect

* Add release notes for PR #6864

* Fix remaining suppressions

* Change category to Maintenance and fix linting issues

Updated category from Enhancements to Maintenance and fixed linting issues related to react-hooks/exhaustive-deps in DateSelect.

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-06 18:03:31 +00:00
Stephen Brown II
b695af66c0 Avoid duplicate category import errors in YNAB5 importer (#6878)
* avoid duplicate category import errors

Add normalizeError helper function

* Add release notes file
2026-02-06 16:15:56 +00:00
Noah
650521f05b Add Catppuccin Themes to custom theme catalog (#6857)
* Add Catppuccin Themes to theme catalog

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-02-06 16:07:45 +00:00
tabedzki
738a8cda7c Fix date range calculation in BudgetAnalysisCard (#6875)
* fix: corrected date range calculation in BudgetAnalysisCard using calculateTimeRange

* add release note

* fix: ensure correct date formatting in BudgetAnalysisCard for start and end dates

* fix: rename release note file
2026-02-06 15:58:31 +00:00
Stephen Brown II
deadd9aefc Apply tag colors to YNAB flag tags (#6866)
* Apply tag colors to match YNAB flags

* Update tag colors to match YNAB, add description on import

* Tighten types

* Use custom colors

* Use Actual palette equivalents for tag colors

* Nitpick fixes

* Fix nitpick 'fix'

* Handle YNAB flag tag conflicts

* Handle YNAB flag tag conflicts without creating separate color tags

* Simplify

* Reorganize
2026-02-05 22:22:01 +00:00
Joel Jeremy Marquez
16ec636358 Fix react-hooks/exhaustive-deps in ImportTransactionsModal (#6868)
* Fix react-hooks/exhaustive-deps in ImportTransactionsModal

* [autofix.ci] apply automated fixes

* Add release notes for PR #6868

* Update category to Maintenance and fix warnings

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-05 17:09:04 +00:00
Joel Jeremy Marquez
b271de32b6 Fix react-hooks/exhaustive-deps in CustomReport (#6867)
* Fix react-hooks/exhaustive-deps in CustomReport

* Add release notes for PR #6867

* Fix typecheck errors

* [autofix.ci] apply automated fixes

* Change category to Maintenance and update description

* [autofix.ci] apply automated fixes

---------

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>
2026-02-05 17:06:54 +00:00
Joel Jeremy Marquez
2fb98156f6 Fix react/exhaustive-deps in PayeeTable (#6863)
* Fix react/exhaustive-deps in Modals

* Fix react/exhaustive-deps in PayeeTable

* Add release notes for PR #6863

* Change category and fix dependency management in PayeeTable

Updated category from 'Enhancements' to 'Maintenance' and fixed dependency management in PayeeTable.

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-05 17:05:23 +00:00
Joel Jeremy Marquez
2f86bafd1f Fix react/exhaustive-deps in Modals (#6862)
* Fix react/exhaustive-deps in Modals

* Add release notes for PR #6862

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-05 17:05:09 +00:00
Joel Jeremy Marquez
7f6f4d5def Move redux state to react-query - category states (#5977)
* Move redux state to react query - category states

* Fix typecheck errors

* Fix typecheck errors

* Fix typecheck errors

* Remove t argument

* [autofix.ci] apply automated fixes

* Coderabbot suggestion

* Code review feedback

* Fix type

* Coderabbit

* Delete useCategoryActions

* Fix lint

* Use categories from react query cache

* Fix typecheck error

* Update to use useDeleteCategoryGroupMutation

* Coderabbit feedback

* Break up useCategoryActions

* [autofix.ci] apply automated fixes

* Fix typecheck errors

* Fix typecheck error

* Fix typecheck error

* await nested mutations

* Await deleteCategory

* Rename to sendThrow

* Fix lint errors

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-02-05 00:42:49 +00:00
Tyler Durr
c57260a504 Remove comma from all instances of month-year strings (#6748)
* Remove comma from all instances of month-year strings, e.g., "January 2026" rather than "January, 2026"

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6748

* All updated calls now include `locale` as an argument

* Update dependencies in `useEffect` to include `locale`

* Reorganized new import

* Remove double colon

* Consistent trailing commas

* Include locale in other dependency arrays

* Reorder imports

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6748

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2026-02-05 00:27:00 +00:00
Saahil Jaffer
1452ecfeb7 changes token expiry handling to be automatic sign out (#6798)
* changes token expiry handling to be automatic sign out

* add release notes

---------

Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2026-02-05 00:01:53 +00:00
Matthias Benaets
264cc9fb0e fix: isConcise state handling for CashFlow report (#6765) 2026-02-04 23:50:22 +00:00
Juulz
554d0b6150 Use consistent color variables on Budget Page. (#6820)
* Change color of budget table scrollbar.

* Update budget sidebar to use budget colors.

* Update fontWeight for 'Category' to match rest of table

* Update to use budget background

* Update ExpenseGroup to use budget color

* Update IncomeGroup to use budget color

* Update SidebarCategory colors

Change drag/drop to buttonPrimaryBackground so it will always be visible in any custom theme.

Background to budget color.

* Update SidebarGroup background to budget color

* Update EnvelopeBudgetComponents

Add 'budgetNumberNeutral' to cheveronDown in budgeted field instead of using default bare button text incases where bare button is set to normal button text and normal button is inverted. remove mobile color variable for shadow

* Update BudgetTotals.tsx

Use tableHeaderText for header.

* Update BudgetSummary.tsx

Use budget colors

* Update MonthPicker.tsx

change selected months at top from tableBorderHover color to buttonPrimaryBackground.

* [autofix.ci] apply automated fixes

* Update EnvelopeBudgetComponents.tsx

* Update BudgetTotals.tsx

revert

* Update SidebarCategory.tsx

* Update TrackingBudgetComponents to use budget colors

* [autofix.ci] apply automated fixes

* Update BudgetSummary to use budget colors

* Update BudgetTotal.tsx

* Update ExpenseProgress.tsx

* Update IncomeProgress.tsx

* Update Saved.tsx

* Use consistent color variables on budget pages.

* Update IncomeProgress.tsx

* Update ExpenseProgress.tsx

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6820

* Update EnvelopeBudgetComponents

Budget total header to follow current/other month style.

* Update EnvelopeBudgetComponents.tsx

* [autofix.ci] apply automated fixes

* Update EnvelopeBudgetComponents.tsx

* Update EnvelopeBudgetComponents.tsx

* [autofix.ci] apply automated fixes

* Update EnvelopeBudgetComponents.tsx

* Revert EnvelopeBudgetComponents.tsx

* [autofix.ci] apply automated fixes

* Update EnvelopeBudgetComponents.tsx

* Update EnvelopeBudgetComponents.tsx

* Update EnvelopeBudgetComponents.tsx

* Revert again :) EnvelopeBudgetComponents.tsx

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-04 23:26:18 +00:00
Joel Jeremy Marquez
11d0b9d824 Update findSortUp and findSortDown parameter to be more generic (#6861)
* findSortUp and findSortDown is also used for CategoryEntity. Updating type to be more generic.

* Generics

* Add release notes for PR #6861

* Adjust category to Maintenance based on review feedback (#6865)

* Initial plan

* Change release notes category to Maintenance

Co-authored-by: joel-jeremy <20313680+joel-jeremy@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: joel-jeremy <20313680+joel-jeremy@users.noreply.github.com>

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: joel-jeremy <20313680+joel-jeremy@users.noreply.github.com>
2026-02-04 22:50:10 +00:00
Stephen Brown II
323c2beb0a Include scheduled transactions in nYNAB imports (#6844)
* Include scheduled transactions in nYNAB imports

* Remove logs and restore schedule name from transaction memo

* Simplify rule actions

* Create schedules with unique names

* Set the note rather than append

* Update ynab5 demo budget and e2e test
2026-02-04 21:40:40 +00:00
Matt Fiddaman
dc5ce6ae96 switch to node alpine docker image (#6840)
* switch to node alpine image

* note
2026-02-04 20:08:17 +00:00
Adam
d8afc6b2be fix(i18n): respect browser preferred languages when supported (#6812)
* fix(i18n): respect browser preferred languages when supported

Instead of relying on the first browser language, the application now
iterates through navigator.languages and selects the first supported
locale, with a fallback to English.

* chore: add the release notes related file

* fix(i18n): check region locales support before falling back to base locale

* fix(i18n): make the unit test aligned with the region locales checking
2026-02-04 20:04:28 +00:00
Joshua Granick
7732fac8b6 Fix sync server migrations (#6346)
* Fix sync-server migrations to use ESM loader

* Add release notes

* Apply CodeRabbit suggestions

* [autofix.ci] apply automated fixes

* Add file extension filter to sync-server migrations import

* [autofix.ci] apply automated fixes

* Ensure migrations occur synchronously

* [autofix.ci] apply automated fixes

* Minor cleanup

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2026-02-04 20:00:42 +00:00
Matthias Benaets
6da6f505e6 fix: report widget tooltip z-index (#6849) 2026-02-04 19:38:04 +00:00
Stephen Brown II
4674916d3e Avoid negative zero in budget summary amounts (#6843) 2026-02-04 19:36:55 +00:00
Nam
5388a115e9 Fix translation issue #6828 (#6845)
* Fix translation issue #6828

* Add release note

* [autofix.ci] apply automated fixes

* update release number

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-02-04 19:36:37 +00:00
Matiss Janis Aboltins
06d31ce035 Improve bug report template with better structure and requirements (#6784)
* Improve bug report template with better structure and requirements

* Fix: Remove empty value fields from textarea inputs in bug report template

* Add release notes for PR #6784

* Update 6784.md

* Update bug report template to request a screenshot of the import screen along with a redacted file version for better issue resolution.

* Update bug report template for import issues

Clarified instructions for reporting import issues.

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-04 19:29:21 +00:00
Matiss Janis Aboltins
9585a92cda Add oxlint rule against direct theme imports; fix Login OpenID button styles (#6796)
Co-authored-by: Michael Clark <5285928+MikesGlitch@users.noreply.github.com>
2026-02-04 19:29:20 +00:00
Matiss Janis Aboltins
a0a490c14c Typescript: make arithmetic.ts strict (#6801)
* Make arithmetic.ts strict TypeScript compliant

- Add type definitions for ParserState, Operator, OperatorNode, and AstNode
- Add explicit type annotations to all function parameters
- Fix null/undefined handling in parsePrimary function
- Remove type assertion in makeOperatorParser by using explicit Operator type
- Handle null return from currencyToAmount function
- All functions now have proper return type annotations

* Add test for ignoring leftover characters in evalArithmetic function
2026-02-04 19:29:19 +00:00
1901 changed files with 60859 additions and 25572 deletions

View File

@@ -13,8 +13,6 @@ reviews:
mode: off
enabled: false
labeling_instructions:
- label: 'suspect ai generated'
instructions: 'This issue or PR is suspected to be generated by AI.'
- label: 'API'
instructions: 'This issue or PR updates the API in `packages/api`.'
- label: 'documentation'

View File

@@ -0,0 +1,74 @@
---
description: Rules for AI-generated commits and pull requests
globs:
alwaysApply: true
---
# PR and Commit Rules for AI Agents
Canonical source: `.github/agents/pr-and-commit-rules.md`
## Commit Rules
### [AI] Prefix Requirement
**ALL commit messages MUST be prefixed with `[AI]`.** This is a mandatory requirement with no exceptions.
**Examples:**
- `[AI] Fix type error in account validation`
- `[AI] Add support for new transaction categories`
- `Fix type error in account validation` (MISSING PREFIX - NOT ALLOWED)
- `Add support for new transaction categories` (MISSING PREFIX - NOT ALLOWED)
### Git Safety Rules
- **Never** update git config
- **Never** run destructive git operations (force push, hard reset) unless the user explicitly requests it
- **Never** skip hooks (`--no-verify`, `--no-gpg-sign`)
- **Never** force push to `main`/`master`
- **Never** commit unless explicitly asked by the user
## Pre-Commit Quality Checklist
Before committing, ensure all of the following:
- [ ] Commit message is prefixed with `[AI]`
- [ ] `yarn typecheck` passes
- [ ] `yarn lint:fix` has been run
- [ ] Relevant tests pass
- [ ] User-facing strings are translated
- [ ] Code style conventions followed (see `AGENTS.md` for full style guide)
## Pull Request Rules
### [AI] Prefix Requirement
**ALL pull request titles MUST be prefixed with `[AI]`.** This is a mandatory requirement with no exceptions.
**Examples:**
- `[AI] Fix type error in account validation`
- `[AI] Add support for new transaction categories`
- `Fix type error in account validation` (MISSING PREFIX - NOT ALLOWED)
### Labels
Add the **"AI generated"** label to all AI-created pull requests.
### PR Template: Do Not Fill In
- **NEVER fill in the PR template** (`.github/PULL_REQUEST_TEMPLATE.md`). Leave all blank spaces and placeholder comments as-is.
- **Exception**: If a human **explicitly asks** you to fill out the PR template, then fill it out **in Chinese** (简体中文).
## Quick-Reference Workflow
1. Make your changes
2. Run `yarn typecheck` — fix any errors
3. Run `yarn lint:fix` — fix any remaining lint errors
4. Run relevant tests (`yarn test` for all, or workspace-specific)
5. Stage files and commit with `[AI]` prefix — do not skip hooks
6. When creating a PR:
- Use `[AI]` prefix in the title
- Add the `"AI generated"` label
- Leave the PR template blank (do not fill it in)

View File

@@ -1,8 +1,6 @@
node_modules
user-files
server-files
# Yarn
**/node_modules
.git
.lage
.pnp.*
.yarn/*
!.yarn/patches

View File

@@ -8,35 +8,66 @@ body:
attributes:
value: |
Thanks for taking the time to fill out this bug report! Please ensure you provide as much information as possible to better assist in confirming and identifying a fix for the bug.
⚠️ **CRITICAL:** Bug reports without clear, step-by-step reproduction instructions will be closed. We cannot investigate or fix bugs without being able to reproduce them. Please take the time to provide detailed reproduction steps.
- type: markdown
attributes:
value: |
**IMPORTANT:** we use GitHub Issues only for BUG REPORTS and FEATURE REQUESTS. If you are looking for help/support - please reach out to the [community on Discord](https://discord.gg/pRYNYr4W5A). All non-bug and non-feature-request issues will be closed.
**Bank-sync problems (SimpleFin / GoCardless)?** Reach out via the [community Discord](https://discord.gg/pRYNYr4W5A) first and open an issue only if the community deems the issue to be a legitimate bug in Actual.
- type: checkboxes
id: existing-issue
attributes:
label: 'Verified issue does not already exist?'
description: 'Please search to see if an issue already exists for the issue you encountered.'
options:
- label: 'I have searched and found no existing issue'
required: true
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Also tell us, what did you expect to happen? If you're reporting an issue with imports, please attach a (redacted) version of the file you're having trouble importing. You may need to zip it before uploading.
placeholder: Tell us what you see!
value: 'A bug happened!'
description: |
Describe the bug clearly and concisely. Include:
- What you were trying to do
- What you expected to happen
- What actually happened instead
- Any error messages (copy/paste the exact text)
If you're reporting an issue with imports, please include a (redacted) version of the file, and a screenshot of the import screen. You may need to zip it before uploading.
placeholder: |
I was trying to [action] when [context].
Expected: [expected behavior]
Actual: [actual behavior]
Error message: [if any]
validations:
required: true
- type: markdown
attributes:
value: |
## Reproduction Steps
**REQUIRED:** Without clear reproduction steps, we cannot investigate or fix the bug. Please provide detailed, step-by-step instructions that anyone can follow to reproduce the issue.
- type: textarea
id: reproduction
attributes:
label: How can we reproduce the issue?
description: Please give step-by-step instructions on how to reproduce the issue. In most cases this might also require uploading a sample budget/import file.
value: 'How can we reproduce the issue?'
description: |
**This field is mandatory and must be filled out completely.**
Provide numbered, step-by-step instructions that allow us to reproduce the bug. Include:
- Specific actions you took (e.g., "Click on the Budget tab", "Enter $100 in the amount field")
- What you expected to happen
- What actually happened instead
Example format:
1. Navigate to [specific page/section]
2. Click on [specific button/link]
3. Enter [specific data] in [specific field]
4. Click [action]
5. Observe [expected vs actual behavior]
If the issue involves importing data, please attach a (redacted) sample file. You may need to zip it before uploading.
placeholder: |
1. Go to [specific location]
2. Click [specific element]
3. Enter [specific data]
4. Click [action]
5. Expected: [what should happen]
Actual: [what actually happens]
validations:
required: true
- type: markdown

View File

@@ -1 +1,21 @@
<!-- Thank you for submitting a pull request! Make sure to follow the instructions to write release notes for your PR — it should only take a minute or two: https://github.com/actualbudget/docs#writing-good-release-notes. Try running yarn generate:release-notes *before* pushing your PR for an interactive experience. -->
## Description
<!-- What does this PR do? Why is it needed? Please give context on the "why?": why do we need this change? What problem is it solving for you?-->
## Related issue(s)
<!-- e.g. Fixes #123, Relates to #456 -->
## Testing
<!-- What did you test? How can we reproduce the issue you are fixing or how can we test the feature you built? -->
## Checklist
- [ ] Release notes added (see link above)
- [ ] No obvious regressions in affected areas
- [ ] Self-review has been performed - I understand what each change in the code does and why it is needed
<!--- actual-bot-sections --->

View File

@@ -74,4 +74,4 @@ async function checkReleaseNotesExists() {
}
}
checkReleaseNotesExists();
void checkReleaseNotesExists();

View File

@@ -74,4 +74,4 @@ async function commentOnPR() {
}
}
commentOnPR();
void commentOnPR();

View File

@@ -16,14 +16,19 @@ if (!token || !repo || !issueNumber || !summaryDataJson || !category) {
const [owner, repoName] = repo.split('/');
const octokit = new Octokit({ auth: token });
const VALID_CATEGORIES = [
'Features',
'Bugfixes',
'Enhancements',
'Maintenance',
];
const GITHUB_USERNAME_RE =
/^[a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38}$/;
async function createReleaseNotesFile() {
try {
const summaryData = JSON.parse(summaryDataJson);
console.log('Debug - Category value:', category);
console.log('Debug - Category type:', typeof category);
console.log('Debug - Category JSON stringified:', JSON.stringify(category));
if (!summaryData) {
console.log('No summary data available, cannot create file');
return;
@@ -34,26 +39,62 @@ async function createReleaseNotesFile() {
return;
}
// Create file content - ensure category is not quoted
// Normalize category - strip surrounding quotes and validate against allow-list
const cleanCategory =
typeof category === 'string'
? category.replace(/^["']|["']$/g, '')
: category;
console.log('Debug - Clean category:', cleanCategory);
if (!VALID_CATEGORIES.includes(cleanCategory)) {
console.log(
`Invalid category "${cleanCategory}". Must be one of: ${VALID_CATEGORIES.join(', ')}`,
);
return;
}
// Validate author is a plausible GitHub username
const author = String(summaryData.author || '');
if (!GITHUB_USERNAME_RE.test(author)) {
console.log(
`Invalid author "${author}", aborting release notes creation`,
);
return;
}
// Normalize summary: collapse whitespace to a single line so it cannot
// introduce extra YAML frontmatter or break the markdown structure.
const cleanSummary = String(summaryData.summary || '')
.replace(/\s+/g, ' ')
.trim();
if (!cleanSummary) {
console.log('Empty summary, aborting release notes creation');
return;
}
// Validate PR number - must be a positive integer. The value comes from
// the GitHub API, but we harden it because it's used to build a file path
// and a commit message.
const validatedPrNumber = Number(summaryData.prNumber);
if (!Number.isInteger(validatedPrNumber) || validatedPrNumber <= 0) {
console.log(
`Invalid PR number "${summaryData.prNumber}", aborting release notes creation`,
);
return;
}
const fileContent = `---
category: ${cleanCategory}
authors: [${summaryData.author}]
authors: [${author}]
---
${summaryData.summary}
${cleanSummary}
`;
const fileName = `upcoming-release-notes/${summaryData.prNumber}.md`;
const fileName = `upcoming-release-notes/${validatedPrNumber}.md`;
console.log(`Creating release notes file: ${fileName}`);
console.log('File content:');
console.log(fileContent);
console.log(
`Creating release notes file: ${fileName} (category: ${cleanCategory}, author: ${author})`,
);
// Get PR info
const { data: pr } = await octokit.rest.pulls.get({
@@ -75,7 +116,7 @@ ${summaryData.summary}
owner: headOwner,
repo: headRepo,
path: fileName,
message: `Add release notes for PR #${summaryData.prNumber}`,
message: `Add release notes for PR #${validatedPrNumber}`,
content: Buffer.from(fileContent).toString('base64'),
branch: prBranch,
committer: {
@@ -94,4 +135,4 @@ ${summaryData.summary}
}
}
createReleaseNotesFile();
void createReleaseNotesFile();

View File

@@ -25,8 +25,6 @@ try {
process.exit(0);
}
console.log('CodeRabbit comment body:', commentBody);
const data = JSON.stringify({
model: 'gpt-4o-mini',
messages: [

View File

@@ -37,19 +37,57 @@ async function getPRDetails() {
console.log('- PR Author:', pr.user.login);
console.log('- PR Title:', pr.title);
console.log('- Base Branch:', pr.base.ref);
console.log('- Head Branch:', pr.head.ref);
// Fetch all changed files to detect docs-only PRs
const files = await octokit.paginate(octokit.rest.pulls.listFiles, {
owner,
repo: repoName,
pull_number: issueNumber,
per_page: 100,
});
const changedFiles = files.map(f => f.filename);
const isDocsOnly =
changedFiles.length > 0 &&
changedFiles.every(file => file.startsWith('packages/docs/'));
console.log('- Changed Files:', changedFiles.length);
console.log('- Is Docs Only:', isDocsOnly);
const result = {
number: pr.number,
author: pr.user.login,
title: pr.title,
baseBranch: pr.base.ref,
headBranch: pr.head.ref,
};
let eligible = true;
if (pr.base.ref !== 'master') {
console.log(
'PR does not target master branch, skipping release notes generation',
);
eligible = false;
} else if (pr.head.ref.startsWith('release/')) {
console.log(
'PR head branch is a release branch, skipping release notes generation',
);
eligible = false;
} else if (isDocsOnly) {
console.log(
'PR only changes documentation, skipping release notes generation',
);
eligible = false;
}
setOutput('result', JSON.stringify(result));
setOutput('eligible', JSON.stringify(eligible));
} catch (error) {
console.log('Error getting PR details:', error.message);
console.log('Stack:', error.stack);
setOutput('result', 'null');
setOutput('eligible', 'false');
process.exit(1);
}
}
@@ -58,5 +96,6 @@ getPRDetails().catch(error => {
console.log('Unhandled error:', error.message);
console.log('Stack:', error.stack);
setOutput('result', 'null');
setOutput('eligible', 'false');
process.exit(1);
});

View File

@@ -68,7 +68,6 @@ ignore$
^\Qsrc/\E$
^\Qstatic/\E$
^\Q.github/\E$
(?:^|/)package(?:-lock|)\.json$
(?:^|/)yarn\.lock$
(?:^|/)(?i)docusaurus.config.js
(?:^|/)(?i)README.md

View File

@@ -2,7 +2,9 @@ Abanca
ABNAMRO
ABNANL
Activo
actualrc
AESUDEF
ajv
ALZEY
Anglais
ANZ
@@ -31,6 +33,7 @@ CAGLPTPL
Caixa
CAMT
cashflow
Catppuccin
Cetelem
cimode
Citi
@@ -123,6 +126,7 @@ Moldovan
murmurhash
NETWORKDAYS
nginx
nodenext
OIDC
Okabe
overbudgeted
@@ -130,6 +134,8 @@ overbudgeting
oxc
Paribas
passwordless
PAYPAL
picomatch
pluggyai
Poste
PPABPLPK
@@ -170,8 +176,11 @@ tada
taskbar
templating
THB
TIMEFRAME
touchscreen
triaging
tsgo
TWD
UAH
ubuntu
undici

View File

@@ -79,3 +79,6 @@
# allowlist specific non-English words with non-ASCII characters
\b(Länsförsäkringar|München|Złoty)\b
# allowlist specific proper nouns
\b(CodeRabbit)\b

View File

@@ -0,0 +1,17 @@
name: Check release notes
description: Validate that a PR includes a properly formatted release note file
runs:
using: composite
steps:
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22
- name: Install dependencies
shell: bash
run: yarn workspaces focus @actual-app/ci-actions
- name: Check release notes
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
shell: bash
run: node packages/ci-actions/bin/release-notes-check.mjs

View File

@@ -0,0 +1,17 @@
name: Generate release notes
description: Generate release documentation from release note files
runs:
using: composite
steps:
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22
- name: Install dependencies
shell: bash
run: yarn workspaces focus actual @actual-app/ci-actions
- name: Generate release notes
shell: bash
env:
GITHUB_TOKEN: ${{ github.token }}
run: node packages/ci-actions/bin/release-notes-generate.mjs

View File

@@ -15,7 +15,7 @@ runs:
using: composite
steps:
- name: Install node
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22
- name: Install yarn
@@ -27,7 +27,7 @@ runs:
run: echo "version=$(node -v)" >> "$GITHUB_OUTPUT"
shell: bash
- name: Cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
id: cache
with:
path: ${{ format('{0}/**/node_modules', inputs.working-directory) }}
@@ -36,7 +36,7 @@ runs:
run: mkdir -p ${{ format('{0}/.lage', inputs.working-directory) }}
shell: bash
- name: Cache Lage
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: ${{ format('{0}/.lage', inputs.working-directory) }}
key: lage-${{ runner.os }}-${{ github.sha }}
@@ -48,12 +48,13 @@ runs:
shell: bash
if: steps.cache.outputs.cache-hit != 'true'
- name: Download translations
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: actualbudget/translations
path: ${{ inputs.working-directory }}/packages/desktop-client/locale
if: ${{ inputs.download-translations == 'true' }}
persist-credentials: false
if: ${{ inputs.download-translations == 'true' && !env.ACT }}
- name: Remove untranslated languages
run: packages/desktop-client/bin/remove-untranslated-languages
shell: bash
if: ${{ inputs.download-translations == 'true' }}
if: ${{ inputs.download-translations == 'true' && !env.ACT }}

70
.github/agents/pr-and-commit-rules.md vendored Normal file
View File

@@ -0,0 +1,70 @@
# PR and Commit Rules for AI Agents
This is the single source of truth for all commit and pull request rules that AI agents must follow when working with Actual Budget.
## Commit Rules
### [AI] Prefix Requirement
**ALL commit messages MUST be prefixed with `[AI]`.** This is a mandatory requirement with no exceptions.
**Examples:**
- `[AI] Fix type error in account validation`
- `[AI] Add support for new transaction categories`
- `Fix type error in account validation` (MISSING PREFIX - NOT ALLOWED)
- `Add support for new transaction categories` (MISSING PREFIX - NOT ALLOWED)
### Git Safety Rules
- **Never** update git config
- **Never** run destructive git operations (force push, hard reset) unless the user explicitly requests it
- **Never** skip hooks (`--no-verify`, `--no-gpg-sign`)
- **Never** force push to `main`/`master`
- **Never** commit unless explicitly asked by the user
## Pre-Commit Quality Checklist
Before committing, ensure all of the following:
- [ ] Commit message is prefixed with `[AI]`
- [ ] `yarn typecheck` passes
- [ ] `yarn lint:fix` has been run
- [ ] Relevant tests pass
- [ ] User-facing strings are translated
- [ ] Code style conventions followed (see `AGENTS.md` for full style guide)
## Pull Request Rules
### [AI] Prefix Requirement
**ALL pull request titles MUST be prefixed with `[AI]`.** This is a mandatory requirement with no exceptions.
**Examples:**
- `[AI] Fix type error in account validation`
- `[AI] Add support for new transaction categories`
- `Fix type error in account validation` (MISSING PREFIX - NOT ALLOWED)
### Labels
Add the **"AI generated"** label to all AI-created pull requests. This helps maintainers understand the nature of the contribution.
### PR Template: Do Not Fill In
- **NEVER fill in the PR template** (`.github/PULL_REQUEST_TEMPLATE.md`). Leave all blank spaces and placeholder comments as-is. Humans are expected to fill in the Description, Related issue(s), Testing, and Checklist sections.
- **Exception**: If a human **explicitly asks** you to fill out the PR template, then fill it out **in Chinese**, using Chinese characters (简体中文) for all content you add.
## Quick-Reference Workflow
Follow these steps when committing and creating PRs:
1. Make your changes
2. Run `yarn typecheck` — fix any errors
3. Run `yarn lint:fix` — fix any remaining lint errors
4. Run relevant tests (`yarn test` for all, or workspace-specific)
5. Stage files and commit with `[AI]` prefix — do not skip hooks
6. When creating a PR:
- Use `[AI]` prefix in the title
- Add the `"AI generated"` label
- Leave the PR template blank (do not fill it in)

View File

@@ -8,6 +8,13 @@ const CONFIG = {
POINTS_PER_ISSUE_TRIAGE_ACTION: 1,
POINTS_PER_ISSUE_CLOSING_ACTION: 1,
POINTS_PER_RELEASE_PR: 4, // Awarded to whoever merges the release PR
PR_CONTRIBUTION_POINTS: [
{ categories: ['Features'], points: 2 },
{ categories: ['Enhancements'], points: 2 },
{ categories: ['Bugfixes', 'Bugfix'], points: 3 },
{ categories: ['Maintenance'], points: 2 },
{ categories: ['Unknown'], points: 2 },
],
// Point tiers for code changes (non-docs)
CODE_PR_REVIEW_POINT_TIERS: [
{ minChanges: 500, points: 8 },
@@ -28,9 +35,80 @@ const CONFIG = {
'release-notes/**/*',
'upcoming-release-notes/**/*',
],
DOCS_FILES_PATTERN: 'packages/docs/**/*',
DOCS_FILES_PATTERNS: [
'packages/docs/**/*',
'!packages/docs/package.json',
'.github/actions/docs-spelling/*',
],
};
/**
* Parse category from release notes file content.
* @param {string} content - The content of the release notes file.
* @returns {string|null} The category or null if not found.
*/
function parseReleaseNotesCategory(content) {
if (!content) return null;
// Extract YAML front matter
const frontMatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
if (!frontMatterMatch) return null;
// Extract category from front matter
const categoryMatch = frontMatterMatch[1].match(/^category:\s*(.+)$/m);
if (!categoryMatch) return null;
return categoryMatch[1].trim();
}
/**
* Get the category and points for a PR by reading its release notes file.
* @param {Octokit} octokit - The Octokit instance.
* @param {string} owner - Repository owner.
* @param {string} repo - Repository name.
* @param {string|null} releaseNoteBlobSha - The blob SHA of the release notes file, or null if not found.
* @returns {Promise<Object>} Object with category and points.
*/
async function getPRCategoryAndPoints(
octokit,
owner,
repo,
releaseNoteBlobSha,
) {
try {
if (releaseNoteBlobSha) {
const { data: blob } = await octokit.git.getBlob({
owner,
repo,
file_sha: releaseNoteBlobSha,
});
const content = Buffer.from(blob.content, 'base64').toString('utf-8');
const category = parseReleaseNotesCategory(content);
const tier = CONFIG.PR_CONTRIBUTION_POINTS.find(e =>
e.categories.includes(category),
);
if (tier) {
return {
category,
points: tier.points,
};
}
}
} catch {
// Do nothing
}
const unknownTier = CONFIG.PR_CONTRIBUTION_POINTS.find(e =>
e.categories.includes('Unknown'),
);
return {
category: 'Unknown',
points: unknownTier.points,
};
}
/**
* Get the start and end dates for the last month.
* @returns {Object} An object containing the start and end dates.
@@ -89,6 +167,7 @@ async function countContributorPoints() {
{
codeReviews: [], // Will store objects with PR number and points for main repo changes
docsReviews: [], // Will store objects with PR number and points for docs changes
prContributions: [], // Will store objects with PR number, category, and points for PR author contributions
labelRemovals: [],
issueClosings: [],
points: 0,
@@ -152,13 +231,25 @@ async function countContributorPoints() {
),
);
const docsFiles = filteredFiles.filter(file =>
minimatch(file.filename, CONFIG.DOCS_FILES_PATTERN, { dot: true }),
);
const codeFiles = filteredFiles.filter(
file =>
!minimatch(file.filename, CONFIG.DOCS_FILES_PATTERN, { dot: true }),
);
const isDocsFile = file => {
const positivePatterns = CONFIG.DOCS_FILES_PATTERNS.filter(
p => !p.startsWith('!'),
);
const negativePatterns = CONFIG.DOCS_FILES_PATTERNS.filter(p =>
p.startsWith('!'),
);
return (
positivePatterns.some(p =>
minimatch(file.filename, p, { dot: true }),
) &&
negativePatterns.every(p =>
minimatch(file.filename, p, { dot: true }),
)
);
};
const docsFiles = filteredFiles.filter(isDocsFile);
const codeFiles = filteredFiles.filter(file => !isDocsFile(file));
const docsChanges = docsFiles.reduce(
(sum, file) => sum + file.additions + file.deletions,
@@ -202,6 +293,31 @@ async function countContributorPoints() {
mergerStats.points += CONFIG.POINTS_PER_RELEASE_PR;
}
} else {
// Award points to PR author if they are a core maintainer
const prAuthor = pr.user?.login;
if (prAuthor && orgMemberLogins.has(prAuthor)) {
const releaseNoteFile = modifiedFiles.find(
file =>
file.filename === `upcoming-release-notes/${pr.number}.md`,
);
const categoryAndPoints = await getPRCategoryAndPoints(
octokit,
owner,
repo,
releaseNoteFile?.sha ?? null,
);
if (categoryAndPoints) {
const authorStats = stats.get(prAuthor);
authorStats.prContributions.push({
pr: pr.number.toString(),
category: categoryAndPoints.category,
points: categoryAndPoints.points,
});
authorStats.points += categoryAndPoints.points;
}
}
const uniqueReviewers = new Set();
reviews.data.forEach(review => {
if (
@@ -293,7 +409,7 @@ async function countContributorPoints() {
// Print all statistics
printStats(
'Code Review Statistics',
stats => stats.codeReviews.length,
stats => stats.codeReviews.reduce((sum, r) => sum + r.points, 0),
(user, count) =>
`${user}: ${count} (PRs: ${stats
.get(user)
@@ -308,7 +424,7 @@ async function countContributorPoints() {
printStats(
'Docs Review Statistics',
stats => stats.docsReviews.length,
stats => stats.docsReviews.reduce((sum, r) => sum + r.points, 0),
(user, count) =>
`${user}: ${count} (PRs: ${stats
.get(user)
@@ -316,16 +432,27 @@ async function countContributorPoints() {
.join(', ')})`,
);
printStats(
'PR Contribution Statistics',
stats => stats.prContributions.reduce((sum, r) => sum + r.points, 0),
(user, count) =>
`${user}: ${count} (PRs: ${stats
.get(user)
.prContributions.map(r => `#${r.pr} (${r.points}pts - ${r.category})`)
.join(', ')})`,
);
printStats(
'"Needs Triage" Label Removal Statistics',
stats => stats.labelRemovals.length,
stats => stats.labelRemovals.length * CONFIG.POINTS_PER_ISSUE_TRIAGE_ACTION,
(user, count) =>
`${user}: ${count} (Issues: ${stats.get(user).labelRemovals.join(', ')})`,
);
printStats(
'Issue Closing Statistics',
stats => stats.issueClosings.length,
stats =>
stats.issueClosings.length * CONFIG.POINTS_PER_ISSUE_CLOSING_ACTION,
(user, count) =>
`${user}: ${count} (Issues: ${stats.get(user).issueClosings.join(', ')})`,
);

View File

@@ -17,7 +17,9 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
with:
@@ -41,21 +43,8 @@ jobs:
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
- name: Check if PR targets master branch
if: steps.check-first-comment.outputs.result == 'true' && steps.pr-details.outputs.result != 'null'
id: check-base-branch
run: |
BASE_BRANCH=$(echo '${{ steps.pr-details.outputs.result }}' | jq -r '.baseBranch')
echo "Base branch: $BASE_BRANCH"
if [ "$BASE_BRANCH" = "master" ]; then
echo "targets_master=true" >> $GITHUB_OUTPUT
else
echo "targets_master=false" >> $GITHUB_OUTPUT
echo "PR does not target master branch, skipping release notes generation"
fi
- name: Check if release notes file already exists
if: steps.check-first-comment.outputs.result == 'true' && steps.pr-details.outputs.result != 'null' && steps.check-base-branch.outputs.targets_master == 'true'
if: steps.pr-details.outputs.eligible == 'true'
id: check-release-notes-exists
run: node .github/actions/ai-generated-release-notes/check-release-notes-exists.js
env:
@@ -65,7 +54,7 @@ jobs:
PR_DETAILS: ${{ steps.pr-details.outputs.result }}
- name: Generate summary with OpenAI
if: steps.check-first-comment.outputs.result == 'true' && steps.check-release-notes-exists.outputs.result == 'false'
if: steps.check-release-notes-exists.outputs.result == 'false'
id: generate-summary
run: node .github/actions/ai-generated-release-notes/generate-summary.js
env:
@@ -74,7 +63,7 @@ jobs:
PR_DETAILS: ${{ steps.pr-details.outputs.result }}
- name: Determine category with OpenAI
if: steps.check-first-comment.outputs.result == 'true' && steps.check-release-notes-exists.outputs.result == 'false' && steps.generate-summary.outputs.result != 'null'
if: steps.generate-summary.outputs.result != 'null' && steps.generate-summary.outputs.result != ''
id: determine-category
run: node .github/actions/ai-generated-release-notes/determine-category.js
env:
@@ -84,7 +73,7 @@ jobs:
SUMMARY_DATA: ${{ steps.generate-summary.outputs.result }}
- name: Create and commit release notes file via GitHub API
if: steps.check-first-comment.outputs.result == 'true' && steps.check-release-notes-exists.outputs.result == 'false' && steps.generate-summary.outputs.result != 'null' && steps.determine-category.outputs.result != 'null' && steps.determine-category.outputs.result != ''
if: steps.determine-category.outputs.result != 'null' && steps.determine-category.outputs.result != ''
run: node .github/actions/ai-generated-release-notes/create-release-notes-file.js
env:
GITHUB_TOKEN: ${{ secrets.ACTIONS_UPDATE_TOKEN }}
@@ -94,7 +83,7 @@ jobs:
CATEGORY: ${{ steps.determine-category.outputs.result }}
- name: Comment on PR
if: steps.check-first-comment.outputs.result == 'true' && steps.check-release-notes-exists.outputs.result == 'false' && steps.generate-summary.outputs.result != 'null' && steps.determine-category.outputs.result != 'null' && steps.determine-category.outputs.result != ''
if: steps.determine-category.outputs.result != 'null' && steps.determine-category.outputs.result != ''
run: node .github/actions/ai-generated-release-notes/comment-on-pr.js
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -15,11 +15,13 @@ jobs:
autofix:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
with:
download-translations: 'false'
- name: Format code
run: yarn lint:fix
- uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27
- uses: autofix-ci/action@7a166d7532b277f34e16238930461bf77f9d7ed8 # v1.3.3

View File

@@ -19,35 +19,54 @@ concurrency:
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
setup:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
with:
download-translations: 'false'
api:
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
with:
download-translations: 'false'
- name: Build API
run: cd packages/api && yarn build
run: yarn build:api
- name: Create package tgz
run: cd packages/api && yarn pack && mv package.tgz actual-api.tgz
- name: Prepare bundle stats artifact
run: cp packages/api/app/stats.json api-stats.json
- name: Upload Build
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: actual-api
path: packages/api/actual-api.tgz
- name: Upload API bundle stats
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: api-build-stats
path: api-stats.json
crdt:
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
with:
@@ -56,35 +75,76 @@ jobs:
run: cd packages/crdt && yarn build
- name: Create package tgz
run: cd packages/crdt && yarn pack && mv package.tgz actual-crdt.tgz
- name: Prepare bundle stats artifact
run: cp packages/crdt/dist/stats.json crdt-stats.json
- name: Upload Build
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: actual-crdt
path: packages/crdt/actual-crdt.tgz
- name: Upload CRDT bundle stats
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: crdt-build-stats
path: crdt-stats.json
web:
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
- name: Build Web
run: yarn build:browser
- name: Upload Build
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: actual-web
path: packages/desktop-client/build
- name: Upload Build Stats
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: build-stats
path: packages/desktop-client/build-stats
server:
cli:
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
with:
download-translations: 'false'
- name: Build CLI
run: yarn build:cli
- name: Create package tgz
run: cd packages/cli && yarn pack && mv package.tgz actual-cli.tgz
- name: Prepare bundle stats artifact
run: cp packages/cli/dist/stats.json cli-stats.json
- name: Upload Build
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: actual-cli
path: packages/cli/actual-cli.tgz
- name: Upload CLI bundle stats
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: cli-build-stats
path: cli-stats.json
server:
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
with:
@@ -92,7 +152,7 @@ jobs:
- name: Build Server
run: yarn workspace @actual-app/sync-server build
- name: Upload Build
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sync-server
path: packages/sync-server/build

View File

@@ -12,10 +12,40 @@ concurrency:
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
lint:
setup:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
with:
download-translations: 'false'
constraints:
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
with:
download-translations: 'false'
- name: Check dependency version consistency
run: yarn constraints
- name: Check tsconfig project references are in sync
run: yarn check:tsconfig-references
lint:
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
with:
@@ -23,9 +53,12 @@ jobs:
- name: Lint
run: yarn lint
typecheck:
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
with:
@@ -33,9 +66,12 @@ jobs:
- name: Typecheck
run: yarn typecheck
validate-cli:
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
with:
@@ -45,23 +81,39 @@ jobs:
- name: Check that the built CLI works
run: node packages/sync-server/build/bin/actual-server.js --version
test:
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
with:
download-translations: 'false'
- name: Test
run: yarn test
check-gh-actions:
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
migrations:
needs: setup
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
node-version: 22
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
with:
download-translations: 'false'
- name: Check migrations
run: node ./.github/actions/check-migrations.js
run: yarn workspace @actual-app/ci-actions tsx bin/check-migrations.ts

View File

@@ -22,14 +22,16 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
with:
languages: javascript
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
with:
category: '/language:javascript'

View File

@@ -16,7 +16,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
with:

101
.github/workflows/cut-release-branch.yml vendored Normal file
View File

@@ -0,0 +1,101 @@
name: Cut release branch
on:
schedule:
# 17:00 UTC on the 25th of each month
- cron: '0 17 25 * *'
workflow_dispatch:
inputs:
ref:
description: 'Commit or branch to release'
required: true
default: 'master'
version:
description: 'Version number for the release (optional)'
required: false
default: ''
release-date:
description: 'Expected release date, YYYY-MM-DD (optional)'
required: false
default: ''
permissions:
contents: write
pull-requests: write
jobs:
cut-release-branch:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.inputs.ref || 'master' }}
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
with:
download-translations: 'false'
- name: Bump package versions
id: bump_package_versions
shell: bash
env:
INPUT_VERSION: ${{ github.event.inputs.version }}
run: |
declare -A packages=(
[web]="desktop-client"
[electron]="desktop-electron"
[sync]="sync-server"
[api]="api"
[cli]="cli"
[core]="loot-core"
)
declare -A new_versions
for key in "${!packages[@]}"; do
pkg="${packages[$key]}"
if [[ -n "$INPUT_VERSION" ]]; then
version=$(yarn workspace @actual-app/ci-actions tsx bin/get-next-package-version.ts \
--package-json "./packages/$pkg/package.json" \
--version "$INPUT_VERSION" \
--update)
else
version=$(yarn workspace @actual-app/ci-actions tsx bin/get-next-package-version.ts \
--package-json "./packages/$pkg/package.json" \
--type auto \
--update)
fi
new_versions[$key]="$version"
done
echo "version=${new_versions[web]}" >> "$GITHUB_OUTPUT"
- name: Compute release date
id: release_date
shell: bash
env:
INPUT_DATE: ${{ github.event.inputs['release-date'] }}
run: |
if [[ -n "$INPUT_DATE" ]]; then
echo "date=$INPUT_DATE" >> "$GITHUB_OUTPUT"
else
# default to the 1st of next month
echo "date=$(date -d '+1 month' '+%Y-%m-01')" >> "$GITHUB_OUTPUT"
fi
- name: Create release branch and PR
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ secrets.ACTIONS_UPDATE_TOKEN }}
commit-message: '🔖 (${{ steps.bump_package_versions.outputs.version }})'
title: '🔖 (${{ steps.bump_package_versions.outputs.version }})'
body: |
Generated by [cut-release-branch.yml](../tree/master/.github/workflows/cut-release-branch.yml)
<!-- release-date:${{ steps.release_date.outputs.date }} -->
branch: 'release/${{ steps.bump_package_versions.outputs.version }}'
base: master

View File

@@ -36,17 +36,19 @@ jobs:
matrix:
os: [ubuntu, alpine]
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Docker meta
id: meta
uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
with:
# Push to both Docker Hub and Github Container Registry
images: ${{ env.IMAGES }}
@@ -54,14 +56,14 @@ jobs:
tags: ${{ env.TAGS }}
- name: Login to Docker Hub
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
if: github.event_name != 'pull_request' && !github.event.repository.fork
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
if: github.event_name != 'pull_request'
with:
registry: ghcr.io
@@ -76,7 +78,7 @@ jobs:
run: yarn build:server
- name: Build image for testing
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
push: false
@@ -87,13 +89,13 @@ jobs:
- name: Test that the docker image boots
run: |
docker run --detach --network=host actualbudget/actual-server-testing
sleep 5
curl --fail -sS -LI -w '%{http_code}\n' --retry 10 --retry-delay 1 --retry-connrefused localhost:5006
sleep 10
curl --fail -sS -LI -w '%{http_code}\n' --retry 20 --retry-delay 1 --retry-connrefused localhost:5006
# This will use the cache from the earlier build step and not rebuild the image
# https://docs.docker.com/build/ci/github-actions/test-before-push/
- name: Build and push images
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
push: ${{ github.event_name != 'pull_request' }}

View File

@@ -28,17 +28,19 @@ jobs:
name: Build Docker image
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Docker meta
id: meta
uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
with:
# Push to both Docker Hub and Github Container Registry
images: ${{ env.IMAGES }}
@@ -48,7 +50,7 @@ jobs:
- name: Docker meta for Alpine image
id: alpine-meta
uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
with:
images: ${{ env.IMAGES }}
# Automatically update :latest
@@ -58,13 +60,13 @@ jobs:
tags: ${{ env.TAGS }}
- name: Login to Docker Hub
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -78,7 +80,7 @@ jobs:
run: yarn build:server
- name: Build and push ubuntu image
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
push: true
@@ -87,7 +89,7 @@ jobs:
tags: ${{ steps.meta.outputs.tags }}
- name: Build and push alpine image
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
push: true

View File

@@ -79,12 +79,12 @@ jobs:
steps:
- name: check-spelling
id: spelling
uses: check-spelling/check-spelling@main
uses: check-spelling/check-spelling@cfb6f7e75bbfc89c71eaa30366d0c166f1bd9c8c # main
with:
suppress_push_for_open_pull_request: 1
checkout: true
check_file_names: 1
spell_check_this: check-spelling/spell-check-this@prerelease
spell_check_this: check-spelling/spell-check-this@92453f59cac8985482dfc301a8ece4c6d7de8a0c # prerelease
post_comment: 0
use_magic_file: 1
experimental_apply_changes_via_bot: 1
@@ -114,10 +114,10 @@ jobs:
if: (success() || failure()) && needs.spelling.outputs.followup && github.event_name == 'push'
steps:
- name: comment
uses: check-spelling/check-spelling@main
uses: check-spelling/check-spelling@cfb6f7e75bbfc89c71eaa30366d0c166f1bd9c8c # main
with:
checkout: true
spell_check_this: check-spelling/spell-check-this@prerelease
spell_check_this: check-spelling/spell-check-this@92453f59cac8985482dfc301a8ece4c6d7de8a0c # prerelease
task: ${{ needs.spelling.outputs.followup }}
config: .github/actions/docs-spelling
@@ -131,10 +131,10 @@ jobs:
if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request')
steps:
- name: comment
uses: check-spelling/check-spelling@main
uses: check-spelling/check-spelling@cfb6f7e75bbfc89c71eaa30366d0c166f1bd9c8c # main
with:
checkout: true
spell_check_this: check-spelling/spell-check-this@prerelease
spell_check_this: check-spelling/spell-check-this@92453f59cac8985482dfc301a8ece4c6d7de8a0c # prerelease
task: ${{ needs.spelling.outputs.followup }}
experimental_apply_changes_via_bot: 1
config: .github/actions/docs-spelling
@@ -156,7 +156,7 @@ jobs:
cancel-in-progress: false
steps:
- name: apply spelling updates
uses: check-spelling/check-spelling@main
uses: check-spelling/check-spelling@cfb6f7e75bbfc89c71eaa30366d0c166f1bd9c8c # main
with:
experimental_apply_changes_via_bot: 1
checkout: true

View File

@@ -17,32 +17,81 @@ on:
env:
GITHUB_PR_NUMBER: ${{github.event.pull_request.number}}
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
functional:
name: Functional (shard ${{ matrix.shard }}/5)
build-web:
name: Build web bundle
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5]
container:
image: mcr.microsoft.com/playwright:v1.57.0-jammy
image: mcr.microsoft.com/playwright:v1.59.1-jammy
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
with:
download-translations: 'false'
- name: Trust the repository directory
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Build browser bundle
# REACT_APP_NETLIFY=true flips isNonProductionEnvironment() on in the
# bundle so the "Create test file" button (used by every e2e beforeEach
# via ConfigurationPage.createTestFile()) is still rendered in a
# production build. Without it, e2e tests would time out waiting for
# a button that was tree-shaken out.
env:
REACT_APP_NETLIFY: 'true'
run: |
yarn workspace plugins-service build
yarn workspace @actual-app/crdt build
yarn workspace @actual-app/core build:browser
yarn workspace @actual-app/web build:browser
- name: Upload build artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: desktop-client-build
path: packages/desktop-client/build/
retention-days: 1
overwrite: true
functional:
name: Functional (shard ${{ matrix.shard }}/3)
runs-on: ubuntu-latest
needs: build-web
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3]
container:
image: mcr.microsoft.com/playwright:v1.59.1-jammy
env:
E2E_USE_BUILD: '1'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
with:
download-translations: 'false'
- name: Trust the repository directory
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Download web build
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: desktop-client-build
path: packages/desktop-client/build/
- name: Run E2E Tests
run: yarn e2e --shard=${{ matrix.shard }}/5
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
if: always()
run: yarn e2e --shard=${{ matrix.shard }}/3
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: failure()
with:
name: desktop-client-test-results-shard-${{ matrix.shard }}
path: packages/desktop-client/test-results/
@@ -53,19 +102,27 @@ jobs:
name: Functional Desktop App
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright:v1.57.0-jammy
image: mcr.microsoft.com/playwright:v1.59.1-jammy
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
with:
download-translations: 'false'
- name: Trust the repository directory
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
# Build tools are needed to rebuild native modules like better-sqlite3 used by the Desktop app, which is required to run E2E tests on the Desktop app.
- name: Install build tools
run: apt-get update && apt-get install -y build-essential python3
- name: Run Desktop app E2E Tests
run: |
yarn rebuild-electron
xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x24" -- yarn e2e:desktop
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
with:
name: desktop-app-test-results
@@ -74,23 +131,35 @@ jobs:
overwrite: true
vrt:
name: Visual regression (shard ${{ matrix.shard }}/5)
name: Visual regression (shard ${{ matrix.shard }}/3)
runs-on: ubuntu-latest
needs: build-web
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5]
shard: [1, 2, 3]
container:
image: mcr.microsoft.com/playwright:v1.57.0-jammy
image: mcr.microsoft.com/playwright:v1.59.1-jammy
env:
E2E_USE_BUILD: '1'
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
with:
download-translations: 'false'
- name: Trust the repository directory
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Download web build
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: desktop-client-build
path: packages/desktop-client/build/
- name: Run VRT Tests
run: yarn vrt --shard=${{ matrix.shard }}/5
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
run: yarn vrt --shard=${{ matrix.shard }}/3
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
with:
name: vrt-blob-report-${{ matrix.shard }}
@@ -104,13 +173,15 @@ jobs:
runs-on: ubuntu-latest
if: ${{ !cancelled() }}
container:
image: mcr.microsoft.com/playwright:v1.57.0-jammy
image: mcr.microsoft.com/playwright:v1.59.1-jammy
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
- name: Download all blob reports
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: packages/desktop-client/all-blob-reports
pattern: vrt-blob-report-*
@@ -118,7 +189,7 @@ jobs:
- name: Merge reports
id: merge-reports
run: yarn workspace @actual-app/web run playwright merge-reports --reporter html ./all-blob-reports
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
id: playwright-report-vrt
with:
name: html-report--attempt-${{ github.run_attempt }}
@@ -131,10 +202,12 @@ jobs:
mkdir -p vrt-metadata
echo "${{ github.event.pull_request.number }}" > vrt-metadata/pr-number.txt
echo "${{ needs.vrt.result }}" > vrt-metadata/vrt-result.txt
echo "${{ steps.playwright-report-vrt.outputs.artifact-url }}" > vrt-metadata/artifact-url.txt
echo "${STEPS_PLAYWRIGHT_REPORT_VRT_OUTPUTS_ARTIFACT_URL}" > vrt-metadata/artifact-url.txt
env:
STEPS_PLAYWRIGHT_REPORT_VRT_OUTPUTS_ARTIFACT_URL: ${{ steps.playwright-report-vrt.outputs.artifact-url }}
- name: Upload VRT metadata
if: github.event_name == 'pull_request'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: vrt-comment-metadata
path: vrt-metadata/

View File

@@ -18,7 +18,7 @@ jobs:
if: github.event.workflow_run.event == 'pull_request'
steps:
- name: Download VRT metadata
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
@@ -53,7 +53,7 @@ jobs:
- name: Comment on PR with VRT report link
if: steps.metadata.outputs.should_comment == 'true'
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4
with:
number: ${{ steps.metadata.outputs.pr_number }}
header: vrt-comment

View File

@@ -22,6 +22,7 @@ jobs:
permissions:
contents: write
strategy:
fail-fast: false
matrix:
os:
- ubuntu-22.04
@@ -29,7 +30,9 @@ jobs:
- macos-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- if: ${{ startsWith(matrix.os, 'windows') }}
run: pip.exe install setuptools
- if: ${{ ! startsWith(matrix.os, 'windows') }}
@@ -56,9 +59,11 @@ jobs:
METAINFO_FILE="packages/desktop-electron/extra-resources/linux/com.actualbudget.actual.metainfo.xml"
TODAY=$(date +%Y-%m-%d)
VERSION=${{ steps.process_version.outputs.version }}
VERSION=${STEPS_PROCESS_VERSION_OUTPUTS_VERSION}
sed -i "s/%RELEASE_VERSION%/$VERSION/g; s/%RELEASE_DATE%/$TODAY/g" "$METAINFO_FILE"
flatpak run --command=flatpak-builder-lint org.flatpak.Builder appstream "$METAINFO_FILE"
env:
STEPS_PROCESS_VERSION_OUTPUTS_VERSION: ${{ steps.process_version.outputs.version }}
- name: Set up environment
uses: ./.github/actions/setup
- name: Build Electron for Mac
@@ -74,7 +79,7 @@ jobs:
if: ${{ ! startsWith(matrix.os, 'macos') }}
run: ./bin/package-electron
- name: Upload Build
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: actual-electron-${{ matrix.os }}
path: |
@@ -85,13 +90,13 @@ jobs:
packages/desktop-electron/dist/*.flatpak
- name: Upload Windows Store Build
if: ${{ startsWith(matrix.os, 'windows') }}
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: actual-electron-${{ matrix.os }}-appx
path: |
packages/desktop-electron/dist/*.appx
- name: Add to new release
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
with:
draft: true
body: |
@@ -126,7 +131,7 @@ jobs:
Install-Module -Name StoreBroker -AcceptLicense -Force -Scope CurrentUser -Verbose
- name: Download Microsoft Store artifacts
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: actual-electron-windows-latest-appx
@@ -156,52 +161,3 @@ jobs:
-NoStatus `
-AutoCommit `
-Force
publish-flathub:
needs: build
runs-on: ubuntu-22.04
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
steps:
- name: Download Linux artifacts
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with:
name: actual-electron-ubuntu-22.04
- name: Calculate AppImage SHA256
id: appimage_sha256
run: |
APPIMAGE_X64_SHA256=$(sha256sum Actual-linux-x86_64.AppImage | awk '{ print $1 }')
APPIMAGE_ARM64_SHA256=$(sha256sum Actual-linux-arm64.AppImage | awk '{ print $1 }')
echo "APPIMAGE_X64_SHA256=$APPIMAGE_X64_SHA256" >> "$GITHUB_ENV"
echo "APPIMAGE_ARM64_SHA256=$APPIMAGE_ARM64_SHA256" >> "$GITHUB_ENV"
- name: Checkout Flathub repo
uses: actions/checkout@v6
with:
repository: flathub/com.actualbudget.actual
token: ${{ secrets.FLATHUB_GITHUB_TOKEN }}
- name: Update manifest with new SHA256
run: |
# Replace x86_64 entry
sed -i "/x86_64.AppImage/{n;s|sha256:.*|sha256: ${{ env.APPIMAGE_X64_SHA256 }}|}" com.actualbudget.actual.yml
sed -i "/x86_64.AppImage/s|url:.*|url: https://github.com/actualbudget/actual/releases/download/v${{ needs.build.outputs.version }}/Actual-linux-x86_64.AppImage|" com.actualbudget.actual.yml
# Replace arm64 entry
sed -i "/arm64.AppImage/{n;s|sha256:.*|sha256: ${{ env.APPIMAGE_ARM64_SHA256 }}|}" com.actualbudget.actual.yml
sed -i "/arm64.AppImage/s|url:.*|url: https://github.com/actualbudget/actual/releases/download/v${{ needs.build.outputs.version }}/Actual-linux-arm64.AppImage|" com.actualbudget.actual.yml
cat com.actualbudget.actual.yml
- name: Create PR in Flathub repo
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.FLATHUB_GITHUB_TOKEN }}
commit-message: 'Update Actual flatpak to version ${{ needs.build.outputs.version }}'
branch: 'release/${{ needs.build.outputs.version }}'
title: 'Update Actual flatpak to version ${{ needs.build.outputs.version }}'
body: |
This PR updates the Actual desktop flatpak to version ${{ needs.build.outputs.version }}.
:link: [View release notes](https://actualbudget.org/blog/release-${{ needs.build.outputs.version }})
reviewers: 'jfdoming,MatissJanis,youngcw' # The core team that have accepted the collaborator access to the Flathub repo

View File

@@ -26,6 +26,7 @@ concurrency:
jobs:
build:
strategy:
fail-fast: false
matrix:
os:
- ubuntu-22.04
@@ -33,7 +34,9 @@ jobs:
- macos-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- if: ${{ startsWith(matrix.os, 'windows') }}
run: pip.exe install setuptools
- if: ${{ ! startsWith(matrix.os, 'windows') }}
@@ -42,6 +45,8 @@ jobs:
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install setuptools
- name: Set up environment
uses: ./.github/actions/setup
- if: ${{ startsWith(matrix.os, 'ubuntu') }}
name: Setup Flatpak dependencies
run: |
@@ -56,65 +61,63 @@ jobs:
METAINFO_FILE="packages/desktop-electron/extra-resources/linux/com.actualbudget.actual.metainfo.xml"
TODAY=$(date +%Y-%m-%d)
VERSION=$(node ./packages/ci-actions/bin/get-next-package-version.js --package-json ./packages/desktop-electron/package.json --type nightly)
VERSION=$(yarn workspace @actual-app/ci-actions tsx bin/get-next-package-version.ts --package-json ./packages/desktop-electron/package.json --type nightly)
sed -i "s/%RELEASE_VERSION%/$VERSION/g; s/%RELEASE_DATE%/$TODAY/g" "$METAINFO_FILE"
flatpak run --command=flatpak-builder-lint org.flatpak.Builder appstream "$METAINFO_FILE"
- name: Set up environment
uses: ./.github/actions/setup
- name: Build Electron
run: ./bin/package-electron
- name: Upload Linux x64 AppImage
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Actual-linux-x86_64.AppImage
if-no-files-found: ignore
path: packages/desktop-electron/dist/Actual-linux-x86_64.AppImage
- name: Upload Linux arm64 AppImage
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Actual-linux-arm64.AppImage
if-no-files-found: ignore
path: packages/desktop-electron/dist/Actual-linux-arm64.AppImage
- name: Upload Linux x64 flatpak
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Actual-linux-x86_64.flatpak
if-no-files-found: ignore
path: packages/desktop-electron/dist/Actual-linux-x86_64.flatpak
- name: Upload Windows x32 exe
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Actual-windows-ia32.exe
if-no-files-found: ignore
path: packages/desktop-electron/dist/Actual-windows-ia32.exe
- name: Upload Windows x64 exe
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Actual-windows-x64.exe
if-no-files-found: ignore
path: packages/desktop-electron/dist/Actual-windows-x64.exe
- name: Upload Windows arm64 exe
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Actual-windows-arm64.exe
if-no-files-found: ignore
path: packages/desktop-electron/dist/Actual-windows-arm64.exe
- name: Upload Mac x64 dmg
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Actual-mac-x64.dmg
if-no-files-found: ignore
path: packages/desktop-electron/dist/Actual-mac-x64.dmg
- name: Upload Mac arm64 dmg
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Actual-mac-arm64.dmg
if-no-files-found: ignore
@@ -122,7 +125,7 @@ jobs:
- name: Upload Windows Store Build
if: ${{ startsWith(matrix.os, 'windows') }}
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: actual-electron-${{ matrix.os }}-appx
path: |

View File

@@ -25,7 +25,7 @@ jobs:
if: github.event.pull_request.head.repo.full_name != github.repository
steps:
- name: Post welcome comment
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
number: ${{ github.event.pull_request.number }}

View File

@@ -1,60 +0,0 @@
name: Generate release PR
on:
workflow_dispatch:
inputs:
ref:
description: 'Commit or branch to release'
required: true
default: 'master'
version:
description: 'Version number for the release (optional)'
required: false
default: ''
jobs:
generate-release-pr:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
ref: ${{ github.event.inputs.ref }}
- name: Bump package versions
id: bump_package_versions
shell: bash
run: |
declare -A packages=(
[web]="desktop-client"
[electron]="desktop-electron"
[sync]="sync-server"
[api]="api"
)
for key in "${!packages[@]}"; do
pkg="${packages[$key]}"
if [[ -n "${{ github.event.inputs.version }}" ]]; then
version=$(node ./packages/ci-actions/bin/get-next-package-version.js \
--package-json "./packages/$pkg/package.json" \
--version "${{ github.event.inputs.version }}" \
--update)
else
version=$(node ./packages/ci-actions/bin/get-next-package-version.js \
--package-json "./packages/$pkg/package.json" \
--type auto \
--update)
fi
eval "NEW_${key^^}_VERSION=\"$version\""
done
echo "version=$NEW_WEB_VERSION" >> "$GITHUB_OUTPUT"
- name: Create PR
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
with:
token: ${{ secrets.ACTIONS_UPDATE_TOKEN }}
commit-message: '🔖 (${{ steps.bump_package_versions.outputs.version }})'
title: '🔖 (${{ steps.bump_package_versions.outputs.version }})'
body: 'Generated by [generate-release-pr.yml](../tree/master/.github/workflows/generate-release-pr.yml)'
branch: 'release/v${{ steps.bump_package_versions.outputs.version }}'

View File

@@ -12,9 +12,10 @@ jobs:
if: github.repository == 'actualbudget/actual'
steps:
- name: Check out main repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
path: actual
persist-credentials: false
- name: Set up environment
uses: ./actual/.github/actions/setup
with:
@@ -27,12 +28,23 @@ jobs:
- name: Configure i18n client
run: |
pip install wlc
- name: Configure Weblate API credentials
env:
WEBLATE_API_KEY: ${{ secrets.WEBLATE_API_KEY_CI_STRINGS }}
run: |
# Write the API key to wlc's config file instead of passing it on
# the command line, so the secret doesn't appear in process listings.
mkdir -p "$HOME/.config"
umask 077
cat > "$HOME/.config/weblate" <<EOF
[keys]
https://hosted.weblate.org/api/ = ${WEBLATE_API_KEY}
EOF
- name: Lock translations
run: |
wlc \
--url https://hosted.weblate.org/api/ \
--key "${{ secrets.WEBLATE_API_KEY_CI_STRINGS }}" \
lock \
actualbudget/actual
@@ -40,15 +52,16 @@ jobs:
run: |
wlc \
--url https://hosted.weblate.org/api/ \
--key "${{ secrets.WEBLATE_API_KEY_CI_STRINGS }}" \
push \
actualbudget/actual
- name: Check out updated translations
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ssh-key: ${{ secrets.STRING_IMPORT_DEPLOY_KEY }}
repository: actualbudget/translations
path: translations
# Need to be able to push back extracted strings
persist-credentials: true
- name: Generate i18n strings
working-directory: actual
run: |
@@ -73,7 +86,6 @@ jobs:
run: |
wlc \
--url https://hosted.weblate.org/api/ \
--key "${{ secrets.WEBLATE_API_KEY_CI_STRINGS }}" \
pull \
actualbudget/actual
@@ -82,6 +94,5 @@ jobs:
run: |
wlc \
--url https://hosted.weblate.org/api/ \
--key "${{ secrets.WEBLATE_API_KEY_CI_STRINGS }}" \
unlock \
actualbudget/actual

View File

@@ -24,8 +24,10 @@ jobs:
runs-on: ubuntu-latest
steps:
# This is not a security concern because we have approved & merged the PR
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22
- name: Handle feature requests

View File

@@ -0,0 +1,37 @@
# When the "unfreeze" label is added to a PR, add that PR to Merge Freeze's unblocked list
# so it can be merged during a freeze. Uses pull_request_target so the workflow runs in
# the base repo and has access to MERGEFREEZE_ACCESS_TOKEN for fork PRs; it does not
# checkout or run any PR code. Requires MERGEFREEZE_ACCESS_TOKEN repo secret
# (project-specific token from Merge Freeze Web API panel for actualbudget/actual / master).
# See: https://docs.mergefreeze.com/web-api#post-freeze-status
name: Merge Freeze add PR to unblocked list
on:
pull_request_target:
types: [labeled]
jobs:
unfreeze:
if: ${{ github.event.label.name == 'unfreeze' }}
runs-on: ubuntu-latest
permissions: {}
concurrency:
group: merge-freeze-unfreeze-${{ github.ref }}-labels
cancel-in-progress: false
steps:
- name: POST to Merge Freeze add PR to unblocked list
env:
MERGEFREEZE_ACCESS_TOKEN: ${{ secrets.MERGEFREEZE_ACCESS_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
USER_NAME: ${{ github.actor }}
run: |
set -e
if [ -z "$MERGEFREEZE_ACCESS_TOKEN" ]; then
echo "::error::MERGEFREEZE_ACCESS_TOKEN secret is not set"
exit 1
fi
url="https://www.mergefreeze.com/api/branches/actualbudget/actual/master/?access_token=${MERGEFREEZE_ACCESS_TOKEN}"
payload=$(jq -n --arg user_name "$USER_NAME" --argjson pr "$PR_NUMBER" '{frozen: true, user_name: $user_name, unblocked_prs: [$pr]}')
curl -sf -X POST "$url" -H "Content-Type: application/json" -d "$payload"
echo "Merge Freeze updated: PR #$PR_NUMBER added to unblocked list."

View File

@@ -21,7 +21,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Repository Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
@@ -34,10 +36,11 @@ jobs:
- name: Deploy to Netlify
id: netlify_deploy
env:
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_API_TOKEN }}
run: |
netlify deploy \
--dir packages/desktop-client/build \
--site ${{ secrets.NETLIFY_SITE_ID }} \
--auth ${{ secrets.NETLIFY_API_TOKEN }} \
--filter @actual-app/web \
--prod

View File

@@ -0,0 +1,47 @@
name: Nightly theme catalog scan
on:
schedule:
# 05:15 UTC daily — runs after the i18n extract job (04:00) and well
# before the nightly Electron/npm publishes (00:00 UTC the next day).
- cron: '15 5 * * *'
workflow_dispatch:
permissions:
contents: read
jobs:
validate-theme-catalog:
name: Validate custom theme catalog
runs-on: ubuntu-latest
if: github.repository == 'actualbudget/actual'
timeout-minutes: 10
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
with:
download-translations: 'false'
- name: Validate themes
run: yarn workspace @actual-app/web validate:theme-catalog
notify-failure:
name: Notify Discord on failure
needs: validate-theme-catalog
if: failure() && github.repository == 'actualbudget/actual'
runs-on: ubuntu-latest
environment: nightly-alerts
timeout-minutes: 5
steps:
- name: Notify Discord
uses: sarisia/actions-status-discord@eb045afee445dc055c18d3d90bd0f244fd062708 # v1.16.0
with:
webhook: ${{ secrets.DISCORD_WEBHOOK_URL }}
status: Failure
title: Nightly theme catalog scan failed
description: The nightly scan failed. One or more themes may be broken, or the scan itself did not complete.
username: Actual Nightly
nofail: true

View File

@@ -1,25 +0,0 @@
name: Remove 'suspect ai generated' label when 'AI generated' is present
on:
pull_request_target:
types: [labeled]
permissions:
pull-requests: write
jobs:
remove-suspect-label:
if: >-
${{ contains(github.event.pull_request.labels.*.name, 'AI generated') &&
contains(github.event.pull_request.labels.*.name, 'suspect ai generated') }}
runs-on: ubuntu-slim
steps:
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
name: 'suspect ai generated'
});

132
.github/workflows/publish-flathub.yml vendored Normal file
View File

@@ -0,0 +1,132 @@
name: Publish Flathub
defaults:
run:
shell: bash
on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: 'Release tag (e.g. v25.3.0)'
required: true
type: string
concurrency:
group: publish-flathub
cancel-in-progress: false
jobs:
publish-flathub:
runs-on: ubuntu-22.04
steps:
- name: Resolve version
id: resolve_version
env:
EVENT_NAME: ${{ github.event_name }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
INPUT_TAG: ${{ inputs.tag }}
run: |
if [[ "$EVENT_NAME" == "release" ]]; then
TAG="$RELEASE_TAG"
else
TAG="$INPUT_TAG"
fi
if [[ -z "$TAG" ]]; then
echo "::error::No tag provided"
exit 1
fi
# Validate tag format (v-prefixed semver, e.g. v25.3.0 or v1.2.3-beta.1)
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
echo "::error::Invalid tag format: $TAG (expected v-prefixed semver, e.g. v25.3.0)"
exit 1
fi
VERSION="${TAG#v}"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Resolved tag=$TAG version=$VERSION"
- name: Verify release assets exist
env:
GH_TOKEN: ${{ github.token }}
STEPS_RESOLVE_VERSION_OUTPUTS_TAG: ${{ steps.resolve_version.outputs.tag }}
run: |
TAG="${STEPS_RESOLVE_VERSION_OUTPUTS_TAG}"
echo "Checking release assets for tag $TAG..."
ASSETS=$(gh api "repos/${{ github.repository }}/releases/tags/$TAG" --jq '.assets[].name')
echo "Found assets:"
echo "$ASSETS"
if ! echo "$ASSETS" | grep -qx "Actual-linux-x86_64.AppImage"; then
echo "::error::Missing asset: Actual-linux-x86_64.AppImage"
exit 1
fi
if ! echo "$ASSETS" | grep -qx "Actual-linux-arm64.AppImage"; then
echo "::error::Missing asset: Actual-linux-arm64.AppImage"
exit 1
fi
echo "All required AppImage assets found."
- name: Calculate AppImage SHA256 (streamed)
run: |
VERSION="${STEPS_RESOLVE_VERSION_OUTPUTS_VERSION}"
BASE_URL="https://github.com/${{ github.repository }}/releases/download/v${VERSION}"
echo "Streaming x86_64 AppImage to compute SHA256..."
APPIMAGE_X64_SHA256=$(curl -fSL "${BASE_URL}/Actual-linux-x86_64.AppImage" | sha256sum | awk '{ print $1 }')
echo "x86_64 SHA256: $APPIMAGE_X64_SHA256"
echo "Streaming arm64 AppImage to compute SHA256..."
APPIMAGE_ARM64_SHA256=$(curl -fSL "${BASE_URL}/Actual-linux-arm64.AppImage" | sha256sum | awk '{ print $1 }')
echo "arm64 SHA256: $APPIMAGE_ARM64_SHA256"
echo "APPIMAGE_X64_SHA256=$APPIMAGE_X64_SHA256" >> "$GITHUB_ENV"
echo "APPIMAGE_ARM64_SHA256=$APPIMAGE_ARM64_SHA256" >> "$GITHUB_ENV"
env:
STEPS_RESOLVE_VERSION_OUTPUTS_VERSION: ${{ steps.resolve_version.outputs.version }}
- name: Checkout Flathub repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: flathub/com.actualbudget.actual
token: ${{ secrets.FLATHUB_GITHUB_TOKEN }}
persist-credentials: false
- name: Update manifest with new version
run: |
VERSION="${STEPS_RESOLVE_VERSION_OUTPUTS_VERSION}"
# Replace x86_64 entry
sed -i "/x86_64.AppImage/{n;s|sha256:.*|sha256: ${APPIMAGE_X64_SHA256}|}" com.actualbudget.actual.yml
sed -i "/x86_64.AppImage/s|url:.*|url: https://github.com/actualbudget/actual/releases/download/v${VERSION}/Actual-linux-x86_64.AppImage|" com.actualbudget.actual.yml
# Replace arm64 entry
sed -i "/arm64.AppImage/{n;s|sha256:.*|sha256: ${APPIMAGE_ARM64_SHA256}|}" com.actualbudget.actual.yml
sed -i "/arm64.AppImage/s|url:.*|url: https://github.com/actualbudget/actual/releases/download/v${VERSION}/Actual-linux-arm64.AppImage|" com.actualbudget.actual.yml
echo "Updated manifest:"
cat com.actualbudget.actual.yml
env:
STEPS_RESOLVE_VERSION_OUTPUTS_VERSION: ${{ steps.resolve_version.outputs.version }}
- name: Create PR in Flathub repo
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ secrets.FLATHUB_GITHUB_TOKEN }}
commit-message: 'Update Actual flatpak to version ${{ steps.resolve_version.outputs.version }}'
branch: 'release/${{ steps.resolve_version.outputs.version }}'
title: 'Update Actual flatpak to version ${{ steps.resolve_version.outputs.version }}'
body: |
This PR updates the Actual desktop flatpak to version ${{ steps.resolve_version.outputs.version }}.
:link: [View release notes](https://actualbudget.org/blog/release-${{ steps.resolve_version.outputs.version }})
reviewers: 'jfdoming,MatissJanis,youngcw'

View File

@@ -20,6 +20,7 @@ concurrency:
jobs:
build:
strategy:
fail-fast: false
matrix:
os:
- ubuntu-22.04
@@ -28,7 +29,9 @@ jobs:
runs-on: ${{ matrix.os }}
if: github.event.repository.fork == false
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- if: ${{ startsWith(matrix.os, 'windows') }}
run: pip.exe install setuptools
@@ -39,6 +42,9 @@ jobs:
source .venv/bin/activate
python3 -m pip install setuptools
- name: Set up environment
uses: ./.github/actions/setup
- if: ${{ startsWith(matrix.os, 'ubuntu') }}
name: Setup Flatpak dependencies
run: |
@@ -53,16 +59,14 @@ jobs:
METAINFO_FILE="packages/desktop-electron/extra-resources/linux/com.actualbudget.actual.metainfo.xml"
TODAY=$(date +%Y-%m-%d)
VERSION=$(node ./packages/ci-actions/bin/get-next-package-version.js --package-json ./packages/desktop-electron/package.json --type nightly)
VERSION=$(yarn workspace @actual-app/ci-actions tsx bin/get-next-package-version.ts --package-json ./packages/desktop-electron/package.json --type nightly)
sed -i "s/%RELEASE_VERSION%/$VERSION/g; s/%RELEASE_DATE%/$TODAY/g" "$METAINFO_FILE"
flatpak run --command=flatpak-builder-lint org.flatpak.Builder appstream "$METAINFO_FILE"
- name: Set up environment
uses: ./.github/actions/setup
- name: Update package versions
run: |
# Get new nightly version
NEW_DESKTOP_APP_VERSION=$(node ./packages/ci-actions/bin/get-next-package-version.js --package-json ./packages/desktop-electron/package.json --type nightly)
NEW_DESKTOP_APP_VERSION=$(yarn workspace @actual-app/ci-actions tsx bin/get-next-package-version.ts --package-json ./packages/desktop-electron/package.json --type nightly)
# Set package version
npm version $NEW_DESKTOP_APP_VERSION --no-git-tag-version --workspace=desktop-electron --no-workspaces-update
@@ -82,49 +86,49 @@ jobs:
run: ./bin/package-electron
- name: Upload Linux x64 AppImage
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Actual-linux-x86_64.AppImage
if-no-files-found: ignore
path: packages/desktop-electron/dist/Actual-linux-x86_64.AppImage
- name: Upload Linux arm64 AppImage
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Actual-linux-arm64.AppImage
if-no-files-found: ignore
path: packages/desktop-electron/dist/Actual-linux-arm64.AppImage
- name: Upload Windows x32 exe
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Actual-windows-ia32.exe
if-no-files-found: ignore
path: packages/desktop-electron/dist/Actual-windows-ia32.exe
- name: Upload Windows x64 exe
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Actual-windows-x64.exe
if-no-files-found: ignore
path: packages/desktop-electron/dist/Actual-windows-x64.exe
- name: Upload Windows arm64 exe
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Actual-windows-arm64.exe
if-no-files-found: ignore
path: packages/desktop-electron/dist/Actual-windows-arm64.exe
- name: Upload Mac x64 dmg
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Actual-mac-x64.dmg
if-no-files-found: ignore
path: packages/desktop-electron/dist/Actual-mac-x64.dmg
- name: Upload Mac arm64 dmg
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Actual-mac-arm64.dmg
if-no-files-found: ignore
@@ -132,7 +136,7 @@ jobs:
- name: Upload Windows Store Build
if: ${{ startsWith(matrix.os, 'windows') }}
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: actual-electron-${{ matrix.os }}-appx
path: |

View File

@@ -1,95 +0,0 @@
name: Publish nightly npm packages
# Nightly npm packages are built daily at midnight UTC
on:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
jobs:
build-and-pack:
runs-on: ubuntu-latest
name: Build and pack npm packages
if: github.event.repository.fork == false
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up environment
uses: ./.github/actions/setup
- name: Update package versions
run: |
# Get new nightly versions
NEW_WEB_VERSION=$(node ./packages/ci-actions/bin/get-next-package-version.js --package-json ./packages/desktop-client/package.json --type nightly)
NEW_SYNC_VERSION=$(node ./packages/ci-actions/bin/get-next-package-version.js --package-json ./packages/sync-server/package.json --type nightly)
NEW_API_VERSION=$(node ./packages/ci-actions/bin/get-next-package-version.js --package-json ./packages/api/package.json --type nightly)
# Set package versions
npm version $NEW_WEB_VERSION --no-git-tag-version --workspace=@actual-app/web --no-workspaces-update
npm version $NEW_SYNC_VERSION --no-git-tag-version --workspace=@actual-app/sync-server --no-workspaces-update
npm version $NEW_API_VERSION --no-git-tag-version --workspace=@actual-app/api --no-workspaces-update
- name: Yarn install
run: |
yarn install
- name: Build Server & Web
run: yarn build:server
- name: Pack the web and server packages
run: |
yarn workspace @actual-app/web pack --filename @actual-app/web.tgz
yarn workspace @actual-app/sync-server pack --filename @actual-app/sync-server.tgz
- name: Build API
run: yarn build:api
- name: Pack the api package
run: |
yarn workspace @actual-app/api pack --filename @actual-app/api.tgz
- name: Upload package artifacts
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: npm-packages
path: |
packages/desktop-client/@actual-app/web.tgz
packages/sync-server/@actual-app/sync-server.tgz
packages/api/@actual-app/api.tgz
publish:
runs-on: ubuntu-latest
name: Publish Nightly npm packages
needs: build-and-pack
permissions:
contents: read
packages: write
steps:
- name: Download the artifacts
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with:
name: npm-packages
- name: Setup node and npm registry
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 22
registry-url: 'https://registry.npmjs.org'
- name: Publish Web
run: |
npm publish desktop-client/@actual-app/web.tgz --access public --tag nightly
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish Sync-Server
run: |
npm publish sync-server/@actual-app/sync-server.tgz --access public --tag nightly
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish API
run: |
npm publish api/@actual-app/api.tgz --access public --tag nightly
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -1,22 +1,55 @@
name: Publish npm packages
# # Npm packages are published for every new tag
# Npm packages are published for every new tag and nightly schedule
on:
push:
tags:
- 'v*.*.*'
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
jobs:
build-and-pack:
runs-on: ubuntu-latest
name: Build and pack npm packages
if: github.event_name == 'push' || (github.event.repository.fork == false)
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
- name: Build Web
- name: Update package versions
if: github.event_name != 'push'
run: |
# Get new nightly versions
NEW_CORE_VERSION=$(yarn workspace @actual-app/ci-actions tsx bin/get-next-package-version.ts --package-json ./packages/loot-core/package.json --type nightly)
NEW_WEB_VERSION=$(yarn workspace @actual-app/ci-actions tsx bin/get-next-package-version.ts --package-json ./packages/desktop-client/package.json --type nightly)
NEW_SYNC_VERSION=$(yarn workspace @actual-app/ci-actions tsx bin/get-next-package-version.ts --package-json ./packages/sync-server/package.json --type nightly)
NEW_API_VERSION=$(yarn workspace @actual-app/ci-actions tsx bin/get-next-package-version.ts --package-json ./packages/api/package.json --type nightly)
NEW_CLI_VERSION=$(yarn workspace @actual-app/ci-actions tsx bin/get-next-package-version.ts --package-json ./packages/cli/package.json --type nightly)
# Set package versions
npm version $NEW_CORE_VERSION --no-git-tag-version --workspace=@actual-app/core --no-workspaces-update
npm version $NEW_WEB_VERSION --no-git-tag-version --workspace=@actual-app/web --no-workspaces-update
npm version $NEW_SYNC_VERSION --no-git-tag-version --workspace=@actual-app/sync-server --no-workspaces-update
npm version $NEW_API_VERSION --no-git-tag-version --workspace=@actual-app/api --no-workspaces-update
npm version $NEW_CLI_VERSION --no-git-tag-version --workspace=@actual-app/cli --no-workspaces-update
- name: Yarn install
if: github.event_name != 'push'
run: |
# Required after nightly `npm version` updates workspace manifests.
yarn install
- name: Pack the core package
run: |
yarn workspace @actual-app/core pack --filename @actual-app/core.tgz
- name: Build Server & Web
run: yarn build:server
- name: Pack the web and server packages
@@ -31,14 +64,24 @@ jobs:
run: |
yarn workspace @actual-app/api pack --filename @actual-app/api.tgz
- name: Build CLI
run: yarn workspace @actual-app/cli build
- name: Pack the cli package
run: |
yarn workspace @actual-app/cli pack --filename @actual-app/cli.tgz
- name: Upload package artifacts
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !env.ACT }}
with:
name: npm-packages
path: |
packages/loot-core/@actual-app/core.tgz
packages/desktop-client/@actual-app/web.tgz
packages/sync-server/@actual-app/sync-server.tgz
packages/api/@actual-app/api.tgz
packages/cli/@actual-app/cli.tgz
publish:
runs-on: ubuntu-latest
@@ -47,32 +90,38 @@ jobs:
permissions:
contents: read
packages: write
id-token: write # Required for OIDC
env:
NPM_DIST_TAG: ${{ github.event_name != 'push' && 'nightly' || '' }}
steps:
- name: Download the artifacts
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: npm-packages
- name: Setup node and npm registry
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22
node-version: 24
check-latest: true
registry-url: 'https://registry.npmjs.org'
- name: Publish Core
run: |
npm publish loot-core/@actual-app/core.tgz --access public ${NPM_DIST_TAG:+--tag "$NPM_DIST_TAG"}
- name: Publish Web
run: |
npm publish desktop-client/@actual-app/web.tgz --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
npm publish desktop-client/@actual-app/web.tgz --access public ${NPM_DIST_TAG:+--tag "$NPM_DIST_TAG"}
- name: Publish Sync-Server
run: |
npm publish sync-server/@actual-app/sync-server.tgz --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
npm publish sync-server/@actual-app/sync-server.tgz --access public ${NPM_DIST_TAG:+--tag "$NPM_DIST_TAG"}
- name: Publish API
run: |
npm publish api/@actual-app/api.tgz --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
npm publish api/@actual-app/api.tgz --access public ${NPM_DIST_TAG:+--tag "$NPM_DIST_TAG"}
- name: Publish CLI
run: |
npm publish cli/@actual-app/cli.tgz --access public ${NPM_DIST_TAG:+--tag "$NPM_DIST_TAG"}

View File

@@ -3,6 +3,10 @@ name: Release notes
on:
pull_request:
permissions:
contents: write
pull-requests: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
@@ -11,15 +15,37 @@ jobs:
release-notes:
runs-on: ubuntu-latest
steps:
- name: Check if triggered by bot
id: bot-check
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { data: commit } = await github.rest.git.getCommit({
owner: context.repo.owner,
repo: context.repo.repo,
commit_sha: context.payload.pull_request.head.sha,
});
const skip = commit.author.name === 'github-actions[bot]'
&& commit.message.startsWith('Generate release notes');
console.log(`Head commit by "${commit.author.name}": ${commit.message.split('\n')[0]}`);
console.log(`Skip: ${skip}`);
core.setOutput('skip', String(skip));
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
if: steps.bot-check.outputs.skip != 'true'
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
token: ${{ secrets.ACTIONS_UPDATE_TOKEN || github.token }}
# Need to be able to commit release notes after generation
persist-credentials: true
- name: Get changed files
if: steps.bot-check.outputs.skip != 'true'
id: changed-files
run: |
git fetch origin ${{ github.base_ref }}
CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD)
git fetch origin ${GITHUB_BASE_REF}
CHANGED_FILES=$(git diff --name-only origin/${GITHUB_BASE_REF}...HEAD)
NON_DOCS_FILES=$(echo "$CHANGED_FILES" | grep -v -e "^packages/docs/" -e "^\.github/actions/docs-spelling/" || true)
if [ -z "$NON_DOCS_FILES" ] && [ -n "$CHANGED_FILES" ]; then
@@ -28,9 +54,17 @@ jobs:
else
echo "only_docs=false" >> $GITHUB_OUTPUT
fi
- name: Check release notes
if: startsWith(github.head_ref, 'release/') == false && steps.changed-files.outputs.only_docs != 'true'
uses: actualbudget/actions/release-notes/check@main
if: >-
steps.bot-check.outputs.skip != 'true'
&& startsWith(github.head_ref, 'release/') == false
&& steps.changed-files.outputs.only_docs != 'true'
uses: ./.github/actions/release-notes/check
- name: Generate release notes
if: startsWith(github.head_ref, 'release/') == true
uses: actualbudget/actions/release-notes/generate@main
if: >-
steps.bot-check.outputs.skip != 'true'
&& startsWith(github.head_ref, 'release/') == true
&& github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name
uses: ./.github/actions/release-notes/generate

View File

@@ -35,9 +35,10 @@ jobs:
contents: read
steps:
- name: Checkout base branch
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.base_ref }}
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
with:
@@ -57,6 +58,20 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
checkName: api
ref: ${{github.base_ref}}
- name: Wait for ${{github.base_ref}} CLI build to succeed
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
id: master-cli-build
with:
token: ${{ secrets.GITHUB_TOKEN }}
checkName: cli
ref: ${{github.base_ref}}
- name: Wait for ${{github.base_ref}} CRDT build to succeed
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
id: master-crdt-build
with:
token: ${{ secrets.GITHUB_TOKEN }}
checkName: crdt
ref: ${{github.base_ref}}
- name: Wait for PR build to succeed
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
@@ -72,15 +87,29 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
checkName: api
ref: ${{github.event.pull_request.head.sha}}
- name: Wait for CLI PR build to succeed
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
id: wait-for-cli-build
with:
token: ${{ secrets.GITHUB_TOKEN }}
checkName: cli
ref: ${{github.event.pull_request.head.sha}}
- name: Wait for CRDT PR build to succeed
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
id: wait-for-crdt-build
with:
token: ${{ secrets.GITHUB_TOKEN }}
checkName: crdt
ref: ${{github.event.pull_request.head.sha}}
- name: Report build failure
if: steps.wait-for-web-build.outputs.conclusion == 'failure' || steps.wait-for-api-build.outputs.conclusion == 'failure'
if: steps.wait-for-web-build.outputs.conclusion == 'failure' || steps.wait-for-api-build.outputs.conclusion == 'failure' || steps.wait-for-cli-build.outputs.conclusion == 'failure' || steps.wait-for-crdt-build.outputs.conclusion == 'failure'
run: |
echo "Build failed on PR branch or ${{github.base_ref}}"
echo "Build failed on PR branch or ${GITHUB_BASE_REF}"
exit 1
- name: Download web build artifact from ${{github.base_ref}}
uses: dawidd6/action-download-artifact@ac66b43f0e6a346234dd65d4d0c8fbb31cb316e5 # v11
uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20
id: pr-web-build
with:
branch: ${{github.base_ref}}
@@ -89,7 +118,7 @@ jobs:
name: build-stats
path: base
- name: Download API build artifact from ${{github.base_ref}}
uses: dawidd6/action-download-artifact@ac66b43f0e6a346234dd65d4d0c8fbb31cb316e5 # v11
uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20
id: pr-api-build
with:
branch: ${{github.base_ref}}
@@ -98,7 +127,7 @@ jobs:
name: api-build-stats
path: base
- name: Download build stats from PR
uses: dawidd6/action-download-artifact@ac66b43f0e6a346234dd65d4d0c8fbb31cb316e5 # v11
uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20
with:
pr: ${{github.event.pull_request.number}}
workflow: build.yml
@@ -107,7 +136,7 @@ jobs:
path: head
allow_forks: true
- name: Download API stats from PR
uses: dawidd6/action-download-artifact@ac66b43f0e6a346234dd65d4d0c8fbb31cb316e5 # v11
uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20
with:
pr: ${{github.event.pull_request.number}}
workflow: build.yml
@@ -115,6 +144,40 @@ jobs:
name: api-build-stats
path: head
allow_forks: true
- name: Download CLI build artifact from ${{github.base_ref}}
uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20
with:
branch: ${{github.base_ref}}
workflow: build.yml
workflow_conclusion: '' # ignore the conclusion of the workflow, since we already checked it
name: cli-build-stats
path: base
- name: Download CLI stats from PR
uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20
with:
pr: ${{github.event.pull_request.number}}
workflow: build.yml
workflow_conclusion: '' # ignore the conclusion of the workflow, since we already checked it
name: cli-build-stats
path: head
allow_forks: true
- name: Download CRDT build artifact from ${{github.base_ref}}
uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20
with:
branch: ${{github.base_ref}}
workflow: build.yml
workflow_conclusion: '' # ignore the conclusion of the workflow, since we already checked it
name: crdt-build-stats
path: base
- name: Download CRDT stats from PR
uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20
with:
pr: ${{github.event.pull_request.number}}
workflow: build.yml
workflow_conclusion: '' # ignore the conclusion of the workflow, since we already checked it
name: crdt-build-stats
path: head
allow_forks: true
- name: Strip content hashes from stats files
run: |
if [ -f ./head/web-stats.json ]; then
@@ -136,9 +199,13 @@ jobs:
--base desktop-client=./base/web-stats.json \
--base loot-core=./base/loot-core-stats.json \
--base api=./base/api-stats.json \
--base cli=./base/cli-stats.json \
--base crdt=./base/crdt-stats.json \
--head desktop-client=./head/web-stats.json \
--head loot-core=./head/loot-core-stats.json \
--head api=./head/api-stats.json \
--head cli=./head/cli-stats.json \
--head crdt=./head/crdt-stats.json \
--identifier combined \
--format pr-body > bundle-stats-comment.md
- name: Post combined bundle stats comment

View File

@@ -3,12 +3,15 @@ on:
schedule:
- cron: '30 1 * * *'
workflow_dispatch: # Allow manual triggering
permissions: {}
jobs:
stale:
permissions:
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
with:
stale-pr-message: 'This PR is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
close-pr-message: 'This PR was closed because it has been stalled for 5 days with no activity.'
@@ -16,9 +19,11 @@ jobs:
days-before-close: 5
days-before-issue-stale: -1
stale-wip:
permissions:
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
with:
stale-pr-message: ':wave: Hi! It looks like this PR has not had any changes for a week now. Would you like someone to review this PR? If so - please remove the "[WIP]" prefix from the PR title. That will let the community know that this PR is open for a review.'
days-before-stale: 7
@@ -27,9 +32,11 @@ jobs:
days-before-issue-stale: -1
stale-needs-info:
permissions:
issues: write
runs-on: ubuntu-latest
steps:
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
with:
stale-issue-label: 'needs info'
days-before-stale: -1

View File

@@ -19,7 +19,7 @@ jobs:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- name: Download patch artifact
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
@@ -27,7 +27,7 @@ jobs:
path: /tmp/artifacts
- name: Download metadata artifact
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
@@ -54,7 +54,7 @@ jobs:
- name: Checkout fork branch
if: steps.metadata.outputs.pr_number != ''
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: ${{ steps.metadata.outputs.head_repo }}
ref: ${{ steps.metadata.outputs.head_ref }}
@@ -107,7 +107,7 @@ jobs:
fi
# Commit
git commit -m "Update VRT screenshots" -m "Auto-generated by VRT workflow" -m "PR: #${{ steps.metadata.outputs.pr_number }}"
git commit -m "Update VRT screenshots" -m "Auto-generated by VRT workflow" -m "PR: #${STEPS_METADATA_OUTPUTS_PR_NUMBER}"
echo "applied=true" >> "$GITHUB_OUTPUT"
else
@@ -116,6 +116,8 @@ jobs:
echo "error=Patch conflicts with current branch state" >> "$GITHUB_OUTPUT"
exit 1
fi
env:
STEPS_METADATA_OUTPUTS_PR_NUMBER: ${{ steps.metadata.outputs.pr_number }}
- name: Push changes
if: steps.apply.outputs.applied == 'true'
@@ -133,12 +135,15 @@ jobs:
- name: Comment on PR - Failure
if: failure() && steps.metadata.outputs.pr_number != ''
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
APPLY_ERROR: ${{ steps.apply.outputs.error }}
PR_NUMBER: ${{ steps.metadata.outputs.pr_number }}
with:
script: |
const error = `${{ steps.apply.outputs.error }}` || 'Unknown error occurred';
const error = process.env.APPLY_ERROR || 'Unknown error occurred';
await github.rest.issues.createComment({
issue_number: ${{ steps.metadata.outputs.pr_number }},
issue_number: parseInt(process.env.PR_NUMBER, 10),
owner: context.repo.owner,
repo: context.repo.repo,
body: `❌ Failed to apply VRT updates: ${error}\n\nPlease check the workflow logs for details.`

View File

@@ -26,7 +26,7 @@ jobs:
pull-requests: write
steps:
- name: Add 👀 reaction to comment
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
await github.rest.reactions.createForIssueComment({
@@ -44,11 +44,11 @@ jobs:
github.event.issue.pull_request &&
startsWith(github.event.comment.body, '/update-vrt')
container:
image: mcr.microsoft.com/playwright:v1.57.0-jammy
image: mcr.microsoft.com/playwright:v1.59.1-jammy
steps:
- name: Get PR details
id: pr
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { data: pr } = await github.rest.pulls.get({
@@ -60,18 +60,24 @@ jobs:
core.setOutput('head_ref', pr.head.ref);
core.setOutput('head_repo', pr.head.repo.full_name);
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ steps.pr.outputs.head_sha }}
persist-credentials: false
- name: Set up environment
uses: ./.github/actions/setup
with:
download-translations: 'false'
# Build tools are needed to rebuild native modules like better-sqlite3 used by the Desktop app, which is required to run VRT tests on the Desktop app and generate updated snapshots.
- name: Install build tools
run: apt-get update && apt-get install -y build-essential python3
- name: Run VRT Tests on Desktop app
continue-on-error: true
run: |
yarn rebuild-electron
xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x24" -- yarn e2e:desktop --update-snapshots
- name: Run VRT Tests
@@ -113,7 +119,7 @@ jobs:
- name: Upload patch artifact
if: steps.create-patch.outputs.has_changes == 'true'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: vrt-patch-${{ github.event.issue.number }}
path: vrt-update.patch
@@ -124,12 +130,15 @@ jobs:
run: |
mkdir -p pr-metadata
echo "${{ github.event.issue.number }}" > pr-metadata/pr-number.txt
echo "${{ steps.pr.outputs.head_ref }}" > pr-metadata/head-ref.txt
echo "${{ steps.pr.outputs.head_repo }}" > pr-metadata/head-repo.txt
echo "${STEPS_PR_OUTPUTS_HEAD_REF}" > pr-metadata/head-ref.txt
echo "${STEPS_PR_OUTPUTS_HEAD_REPO}" > pr-metadata/head-repo.txt
env:
STEPS_PR_OUTPUTS_HEAD_REF: ${{ steps.pr.outputs.head_ref }}
STEPS_PR_OUTPUTS_HEAD_REPO: ${{ steps.pr.outputs.head_repo }}
- name: Upload PR metadata
if: steps.create-patch.outputs.has_changes == 'true'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: vrt-metadata-${{ github.event.issue.number }}
path: pr-metadata/

13
.gitignore vendored
View File

@@ -33,7 +33,9 @@ packages/desktop-electron/dist
packages/desktop-electron/loot-core
packages/desktop-client/service-worker
packages/plugins-service/dist
packages/component-library/dist
packages/loot-core/lib-dist
**/.tsbuildinfo
packages/sync-server/coverage
bundle.desktop.js
bundle.desktop.js.map
@@ -56,6 +58,10 @@ bundle.mobile.js.map
# IntelliJ IDEA
.idea
# Claude Code
.claude/worktrees/*
.claude/settings.local.json
# Misc
.#*
@@ -79,3 +85,10 @@ build/
*storybook.log
storybook-static
# cli config when testing locally
.actualrc.json
.actualrc
.actualrc.yaml
.actualrc.yml
actual.config.js

21
.husky/post-checkout Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/sh
# Run yarn install when switching branches (if yarn.lock changed)
# or when creating a new worktree (node_modules won't exist yet)
# $3 is 1 for branch checkout, 0 for file checkout
if [ "$3" != "1" ]; then
exit 0
fi
# Worktree creation: node_modules doesn't exist yet, always install
if [ ! -d "node_modules" ]; then
echo "New worktree detected — running yarn install..."
yarn install || exit 1
exit 0
fi
# Check if yarn.lock changed between the old and new HEAD
if git diff --name-only "$1" "$2" | grep -q "^yarn.lock$"; then
echo "yarn.lock changed — running yarn install..."
yarn install
fi

7
.husky/post-merge Executable file
View File

@@ -0,0 +1,7 @@
#!/bin/sh
# Run yarn install after pulling/merging (if yarn.lock changed)
if git diff --name-only ORIG_HEAD HEAD | grep -q "^yarn.lock$"; then
echo "yarn.lock changed — running yarn install..."
yarn install
fi

0
.husky/pre-commit Normal file → Executable file
View File

View File

@@ -9,24 +9,14 @@
"react",
"builtin",
"external",
"loot-core",
"parent",
["parent", "subpath"],
"sibling",
"index",
"desktop-client"
"index"
],
"customGroups": [
{
"groupName": "react",
"elementNamePattern": ["react"]
},
{
"groupName": "loot-core",
"elementNamePattern": ["loot-core"]
},
{
"groupName": "desktop-client",
"elementNamePattern": ["@desktop-client"]
"elementNamePattern": ["react", "react-dom/*", "react-*"]
}
],
"newlinesBetween": true

View File

@@ -20,72 +20,75 @@
"rules": {
// Import sorting
"perfectionist/sort-named-imports": [
"warn",
"error",
{
"groups": ["value-import", "type-import"]
}
],
// Actual rules
"actual/typography": "warn",
"actual/typography": "error",
"actual/no-untranslated-strings": "error",
"actual/prefer-trans-over-t": "error",
"actual/prefer-if-statement": "warn",
"actual/prefer-if-statement": "error",
"actual/prefer-logger-over-console": "error",
"actual/object-shorthand-properties": "warn",
"actual/prefer-const": "warn",
"actual/no-anchor-tag": "warn",
"actual/no-react-default-import": "warn",
"actual/object-shorthand-properties": "error",
"actual/prefer-const": "error",
"actual/no-anchor-tag": "error",
"actual/no-react-default-import": "error",
"actual/prefer-subpath-imports": "error",
"actual/enforce-boundaries": "error",
"actual/no-extraneous-dependencies": "error",
// JSX A11y rules
"jsx-a11y/no-autofocus": [
"warn",
"error",
{
"ignoreNonDOM": true
}
],
"jsx-a11y/alt-text": "warn",
"jsx-a11y/anchor-has-content": "warn",
"jsx-a11y/alt-text": "error",
"jsx-a11y/anchor-has-content": "error",
"jsx-a11y/anchor-is-valid": [
"warn",
"error",
{
"aspects": ["noHref", "invalidHref"]
}
],
"jsx-a11y/aria-activedescendant-has-tabindex": "warn",
"jsx-a11y/aria-props": "warn",
"jsx-a11y/aria-proptypes": "warn",
"jsx-a11y/aria-activedescendant-has-tabindex": "error",
"jsx-a11y/aria-props": "error",
"jsx-a11y/aria-proptypes": "error",
"jsx-a11y/aria-role": [
"warn",
"error",
{
"ignoreNonDOM": true
}
],
"jsx-a11y/aria-unsupported-elements": "warn",
"jsx-a11y/heading-has-content": "warn",
"jsx-a11y/iframe-has-title": "warn",
"jsx-a11y/img-redundant-alt": "warn",
"jsx-a11y/no-access-key": "warn",
"jsx-a11y/no-distracting-elements": "warn",
"jsx-a11y/no-redundant-roles": "warn",
"jsx-a11y/role-has-required-aria-props": "warn",
"jsx-a11y/role-supports-aria-props": "warn",
"jsx-a11y/scope": "warn",
"jsx-a11y/aria-unsupported-elements": "error",
"jsx-a11y/heading-has-content": "error",
"jsx-a11y/iframe-has-title": "error",
"jsx-a11y/img-redundant-alt": "error",
"jsx-a11y/no-access-key": "error",
"jsx-a11y/no-distracting-elements": "error",
"jsx-a11y/no-redundant-roles": "error",
"jsx-a11y/role-has-required-aria-props": "error",
"jsx-a11y/role-supports-aria-props": "error",
"jsx-a11y/scope": "error",
// Typescript rules
"typescript/ban-ts-comment": ["warn"],
"typescript/consistent-type-definitions": ["warn", "type"],
"typescript/ban-ts-comment": ["error"],
"typescript/consistent-type-definitions": ["error", "type"],
"typescript/consistent-type-imports": [
"warn",
"error",
{
"prefer": "type-imports",
"fixStyle": "inline-type-imports"
}
],
"typescript/no-implied-eval": "warn",
"typescript/no-explicit-any": "warn",
"typescript/no-implied-eval": "error",
"typescript/no-explicit-any": "error",
"typescript/no-restricted-types": [
"warn",
"error",
{
"types": {
// forbid FC as superfluous
@@ -98,141 +101,152 @@
}
}
],
"typescript/no-var-requires": "warn",
"typescript/no-var-requires": "error",
// we want to allow unions such as "{ name: DbAccount['name'] | DbPayee['name'] }"
"typescript/no-duplicate-type-constituents": "off",
// we want to allow unions such as "string | 'network' | 'file-key-mismatch'"
"typescript/no-redundant-type-constituents": "off",
"typescript/await-thenable": "error",
"typescript/no-floating-promises": "error",
"typescript/require-array-sort-compare": "error",
"typescript/unbound-method": "error",
"typescript/no-for-in-array": "error",
"typescript/restrict-template-expressions": "error",
"typescript/no-misused-spread": "warn", // TODO: enable this
"typescript/no-base-to-string": "warn", // TODO: enable this
"typescript/no-unsafe-unary-minus": "warn", // TODO: enable this
"typescript/no-unsafe-type-assertion": "warn", // TODO: enable this
// Import rules
"import/consistent-type-specifier-style": "error",
"import/first": "error",
"import/no-amd": "error",
"import/no-default-export": "warn",
"import/no-default-export": "error",
"import/no-webpack-loader-syntax": "error",
"import/no-useless-path-segments": "warn",
"import/no-unresolved": "warn",
"import/no-unused-modules": "warn",
"import/no-duplicates": [
"warn",
"error",
{
"prefer-inline": true
"prefer-inline": false
}
],
// React rules
"react/exhaustive-deps": [
"warn",
"error",
{
"additionalHooks": "(useQuery|useEffectAfterMount)"
"additionalHooks": "(^useQuery$|^useEffectAfterMount$)"
}
],
"react/jsx-curly-brace-presence": "warn",
"react/jsx-curly-brace-presence": "error",
"react/jsx-filename-extension": [
"warn",
"error",
{
"extensions": [".jsx", ".tsx"],
"allow": "as-needed"
}
],
"react/jsx-no-comment-textnodes": "warn",
"react/jsx-no-duplicate-props": "warn",
"react/jsx-no-target-blank": "warn",
"react/jsx-no-comment-textnodes": "error",
"react/jsx-no-duplicate-props": "error",
"react/jsx-no-target-blank": "error",
"react/jsx-no-undef": "error",
"react/jsx-no-useless-fragment": "warn",
"react/jsx-no-useless-fragment": "error",
"react/jsx-pascal-case": [
"warn",
"error",
{
"allowAllCaps": true,
"ignore": []
}
],
"react/no-danger-with-children": "warn",
"react/no-direct-mutation-state": "warn",
"react/no-is-mounted": "warn",
"react/no-unstable-nested-components": "warn",
"react/no-danger-with-children": "error",
"react/no-direct-mutation-state": "error",
"react/no-is-mounted": "error",
"react/require-render-return": "error",
"react/rules-of-hooks": "error",
"react/self-closing-comp": "warn",
"react/style-prop-object": "warn",
"react/jsx-boolean-value": "warn",
"react/self-closing-comp": "error",
"react/style-prop-object": "error",
"react/jsx-boolean-value": "error",
// ESLint rules
"eslint/array-callback-return": "warn",
"eslint/curly": ["warn", "multi-line", "consistent"],
"eslint/array-callback-return": "error",
"eslint/curly": ["error", "multi-line", "consistent"],
"eslint/default-case": [
"warn",
"error",
{
"commentPattern": "^no default$"
}
],
"eslint/eqeqeq": ["warn", "smart"],
"eslint/no-array-constructor": "warn",
"eslint/no-caller": "warn",
"eslint/no-cond-assign": ["warn", "except-parens"],
"eslint/no-const-assign": "warn",
"eslint/no-control-regex": "warn",
"eslint/no-delete-var": "warn",
"eslint/no-dupe-class-members": "warn",
"eslint/no-dupe-keys": "warn",
"eslint/no-duplicate-case": "warn",
"eslint/no-empty-character-class": "warn",
"eslint/no-empty-function": "warn",
"eslint/no-empty-pattern": "warn",
"eslint/no-eval": "warn",
"eslint/no-ex-assign": "warn",
"eslint/no-extend-native": "warn",
"eslint/no-extra-bind": "warn",
"eslint/no-extra-label": "warn",
"eslint/no-fallthrough": "warn",
"eslint/no-func-assign": "warn",
"eslint/no-invalid-regexp": "warn",
"eslint/no-iterator": "warn",
"eslint/no-label-var": "warn",
"eslint/no-var": "warn",
"eslint/eqeqeq": ["error", "smart"],
"eslint/no-array-constructor": "error",
"eslint/no-caller": "error",
"eslint/no-cond-assign": ["error", "except-parens"],
"eslint/no-const-assign": "error",
"eslint/no-control-regex": "error",
"eslint/no-delete-var": "error",
"eslint/no-dupe-class-members": "error",
"eslint/no-dupe-keys": "error",
"eslint/no-duplicate-case": "error",
"eslint/no-empty-character-class": "error",
"eslint/no-empty-function": "error",
"eslint/no-empty-pattern": "error",
"eslint/no-eval": "error",
"eslint/no-ex-assign": "error",
"eslint/no-extend-native": "error",
"eslint/no-extra-bind": "error",
"eslint/no-extra-label": "error",
"eslint/no-fallthrough": "error",
"eslint/no-func-assign": "error",
"eslint/no-invalid-regexp": "error",
"eslint/no-iterator": "error",
"eslint/no-label-var": "error",
"eslint/no-var": "error",
"eslint/no-labels": [
"warn",
"error",
{
"allowLoop": true,
"allowSwitch": false
}
],
"eslint/no-new-func": "warn",
"eslint/no-script-url": "warn",
"eslint/no-self-assign": "warn",
"eslint/no-self-compare": "warn",
"eslint/no-sequences": "warn",
"eslint/no-shadow-restricted-names": "warn",
"eslint/no-sparse-arrays": "warn",
"eslint/no-template-curly-in-string": "warn",
"eslint/no-this-before-super": "warn",
"eslint/no-throw-literal": "warn",
"eslint/no-unreachable": "warn",
"eslint/no-obj-calls": "warn",
"eslint/no-new-wrappers": "warn",
"eslint/no-unsafe-negation": "warn",
"eslint/no-multi-str": "warn",
"eslint/no-global-assign": "warn",
"eslint/no-lone-blocks": "warn",
"eslint/no-unused-labels": "warn",
"eslint/no-object-constructor": "warn",
"eslint/no-new-native-nonconstructor": "warn",
"eslint/no-redeclare": "warn",
"eslint/no-useless-computed-key": "warn",
"eslint/no-useless-concat": "warn",
"eslint/no-useless-escape": "warn",
"eslint/require-yield": "warn",
"eslint/getter-return": "warn",
"eslint/unicode-bom": ["warn", "never"],
"eslint/no-use-isnan": "warn",
"eslint/valid-typeof": "warn",
"eslint/no-new-func": "error",
"eslint/no-script-url": "error",
"eslint/no-self-assign": "error",
"eslint/no-self-compare": "error",
"eslint/no-sequences": "error",
"eslint/no-shadow-restricted-names": "error",
"eslint/no-sparse-arrays": "error",
"eslint/no-template-curly-in-string": "error",
"eslint/no-this-before-super": "error",
"eslint/no-throw-literal": "error",
"eslint/no-unreachable": "error",
"eslint/no-obj-calls": "error",
"eslint/no-new-wrappers": "error",
"eslint/no-unsafe-negation": "error",
"eslint/no-multi-str": "error",
"eslint/no-global-assign": "error",
"eslint/no-lone-blocks": "error",
"eslint/no-unused-labels": "error",
"eslint/no-object-constructor": "error",
"eslint/no-new-native-nonconstructor": "error",
"eslint/no-redeclare": "error",
"eslint/no-useless-computed-key": "error",
"eslint/no-useless-concat": "error",
"eslint/no-useless-escape": "error",
"eslint/require-yield": "error",
"eslint/getter-return": "error",
"eslint/unicode-bom": ["error", "never"],
"eslint/use-isnan": "error",
"eslint/valid-typeof": "error",
"eslint/no-useless-rename": [
"warn",
"error",
{
"ignoreDestructuring": false,
"ignoreImport": false,
"ignoreExport": false
}
],
"eslint/no-with": "warn",
"eslint/no-regex-spaces": "warn",
"eslint/no-with": "error",
"eslint/no-regex-spaces": "error",
"eslint/no-restricted-globals": [
"warn",
"error",
// https://github.com/facebook/create-react-app/tree/main/packages/confusing-browser-globals
"addEventListener",
@@ -294,7 +308,7 @@
"top"
],
"eslint/no-restricted-imports": [
"warn",
"error",
{
"paths": [
{
@@ -320,19 +334,18 @@
],
"patterns": [
{
"group": ["**/*.api", "**/*.web", "**/*.electron"],
"group": ["**/*.api", "**/*.electron"],
"message": "Don't directly reference imports from other platforms"
},
{
"group": ["uuid"],
"importNames": ["*"],
"message": "Use `import { v4 as uuidv4 } from 'uuid'` instead"
},
{
"group": ["**/style", "**/colors"],
"importNames": ["colors"],
"message": "Please use themes instead of colors"
},
{
"group": ["**/style/themes/*"],
"message": "Please do not import theme files directly"
},
{
"group": ["@actual-app/web/**/*"],
"message": "Please do not import `@actual-app/web` in `loot-core`"
@@ -340,9 +353,11 @@
]
}
],
"eslint/no-useless-constructor": "warn",
"eslint/no-undef": "warn",
"eslint/no-unused-expressions": "warn"
"eslint/no-useless-constructor": "error",
"eslint/no-undef": "error",
"eslint/no-unused-expressions": "error",
"eslint/no-return-assign": "error",
"eslint/no-unused-vars": "error"
},
"overrides": [
{
@@ -358,6 +373,12 @@
"actual/prefer-logger-over-console": "off"
}
},
{
"files": ["packages/eslint-plugin-actual/lib/rules/__tests__/**/*"],
"rules": {
"actual/enforce-boundaries": "off"
}
},
{
"files": [
"packages/api/migrations/*",
@@ -385,6 +406,12 @@
"typescript-paths/absolute-import": ["error", { "enableAlias": false }]
}
},
{
"files": ["packages/desktop-client/src/style/themes/*"],
"rules": {
"eslint/no-restricted-imports": "off"
}
},
// TODO: enable these
{
"files": [
@@ -396,6 +423,16 @@
"rules": {
"eslint/no-empty-function": "off"
}
},
// crdt enforces the repo's "TODO: enable this" typescript rules as errors
{
"files": ["packages/crdt/**/*"],
"rules": {
"typescript/no-misused-spread": "error",
"typescript/no-base-to-string": "error",
"typescript/no-unsafe-unary-minus": "error",
"typescript/no-unsafe-type-assertion": "error"
}
}
]
}

File diff suppressed because one or more lines are too long

940
.yarn/releases/yarn-4.13.0.cjs vendored Executable file

File diff suppressed because one or more lines are too long

View File

@@ -6,4 +6,4 @@ enableTransparentWorkspaces: false
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.10.3.cjs
yarnPath: .yarn/releases/yarn-4.13.0.cjs

View File

@@ -42,6 +42,12 @@ yarn start:desktop
- Use `yarn workspace <workspace-name> run <command>` for workspace-specific tasks
- Tests run once and exit by default (using `vitest --run`)
### ⚠️ CRITICAL REQUIREMENT: AI-Generated Commit Messages and PR Titles
**ALL commit messages and PR titles MUST be prefixed with `[AI]`.** No exceptions.
See [PR and Commit Rules](.github/agents/pr-and-commit-rules.md) for the full specification, including git safety rules, pre-commit checklist, and PR workflow.
### Task Orchestration with Lage
The project uses **[lage](https://microsoft.github.io/lage/)** (a task runner for JavaScript monorepos) to efficiently run tests and other tasks across multiple workspaces:
@@ -78,7 +84,7 @@ The core application logic that runs on any platform.
```bash
# Run all loot-core tests
yarn workspace loot-core run test
yarn workspace @actual-app/core run test
# Or run tests across all packages using lage
yarn test
@@ -213,7 +219,7 @@ yarn test
yarn test:debug
# Run tests for a specific package
yarn workspace loot-core run test
yarn workspace @actual-app/core run test
```
**E2E Tests (Playwright)**
@@ -275,7 +281,6 @@ Always run `yarn typecheck` before committing.
- Avoid `any` or `unknown` unless absolutely necessary
- Look for existing type definitions in the codebase
- Avoid type assertions (`as`, `!`) - prefer `satisfies`
- Use inline type imports: `import { type MyType } from '...'`
**Naming:**
@@ -292,6 +297,7 @@ Always run `yarn typecheck` before committing.
**React Patterns:**
- The project uses **React Compiler** (`babel-plugin-react-compiler`) in the desktop-client. The compiler auto-memoizes component bodies, so you can omit manual `useCallback`, `useMemo`, and `React.memo` when adding or refactoring code; prefer inline callbacks and values unless a stable identity is required by a non-compiled dependency.
- Don't use `React.FunctionComponent` or `React.FC` - type props directly
- Don't use `React.*` patterns - use named imports instead
- Use `<Link>` instead of `<a>` tags
@@ -324,7 +330,7 @@ Always maintain newlines between import groups.
### Platform-Specific Code
- Don't directly reference platform-specific imports (`.api`, `.web`, `.electron`)
- Don't directly reference platform-specific imports (`.api`, `.electron`)
- Use conditional exports in `loot-core` for platform-specific code
- Platform resolution happens at build time via package.json exports
@@ -338,11 +344,7 @@ Always maintain newlines between import groups.
**Git Commands:**
- Never update git config
- Never run destructive git operations (force push, hard reset) unless explicitly requested
- Never skip hooks (--no-verify, --no-gpg-sign)
- Never force push to main/master
- Never commit unless explicitly asked
See [PR and Commit Rules](.github/agents/pr-and-commit-rules.md) for complete git safety rules, commit message requirements, and PR workflow.
## File Structure Patterns
@@ -498,14 +500,14 @@ Icons in `packages/component-library/src/icons/` are auto-generated. Don't manua
1. Check `tsconfig.json` for path mappings
2. Check package.json `exports` field (especially for loot-core)
3. Verify platform-specific imports (`.web`, `.electron`, `.api`)
3. Verify platform-specific imports (`.electron`, `.api`)
4. Use absolute imports in `desktop-client` (enforced by ESLint)
### Build Failures
1. Clean build artifacts: `rm -rf packages/*/dist packages/*/lib-dist packages/*/build`
2. Reinstall dependencies: `yarn install`
3. Check Node.js version (requires >=20)
3. Check Node.js version (requires >=22)
4. Check Yarn version (requires ^4.9.1)
## Testing Patterns
@@ -541,6 +543,7 @@ Icons in `packages/component-library/src/icons/` are auto-generated. Don't manua
Before committing changes, ensure:
- [ ] Commit and PR rules followed (see [PR and Commit Rules](.github/agents/pr-and-commit-rules.md))
- [ ] `yarn typecheck` passes
- [ ] `yarn lint:fix` has been run
- [ ] Relevant tests pass
@@ -553,9 +556,7 @@ Before committing changes, ensure:
## Pull Request Guidelines
When creating pull requests:
- **AI-Generated PRs**: If you create a PR using AI assistance, add the **"AI generated"** label to the pull request. This helps maintainers understand the nature of the contribution.
See [PR and Commit Rules](.github/agents/pr-and-commit-rules.md) for complete PR creation rules, including title prefix requirements, labeling, and PR template handling.
## Code Review Guidelines
@@ -586,7 +587,7 @@ yarn install:server
## Environment Requirements
- **Node.js**: >=20
- **Node.js**: >=22
- **Yarn**: ^4.9.1 (managed by packageManager field)
- **Browser Targets**: Electron >= 35.0, modern browsers (see browserslist)
@@ -599,3 +600,40 @@ The codebase is actively being migrated:
- **React.\* → Named Imports**: Legacy React.\* patterns being removed
When working with older code, follow the newer patterns described in this guide.
## Cursor Cloud specific instructions
### Services overview
| Service | Command | Port | Required |
| ------------------- | ----------------------- | ---- | ----------------------------- |
| Web Frontend (Vite) | `yarn start` | 3001 | Yes |
| Sync Server | `yarn start:server-dev` | 5006 | Optional (sync features only) |
All storage is **SQLite** (file-based via `better-sqlite3`). No external databases or services are needed.
### Running the app
- `yarn start` builds the plugins-service worker, loot-core browser backend, and starts the Vite dev server on port **3001**.
- `yarn start:server-dev` starts both the sync server (port 5006) and the web frontend together.
- The Vite HMR dev server serves many unbundled modules. In constrained environments, the browser may hit `ERR_INSUFFICIENT_RESOURCES`. If that happens, use `yarn build:browser` followed by serving the built output from `packages/desktop-client/build/` with proper COOP/COEP headers (`Cross-Origin-Opener-Policy: same-origin`, `Cross-Origin-Embedder-Policy: require-corp`).
### Lint, test, typecheck
Standard commands documented in `package.json` scripts and the Quick Start section above:
- `yarn lint` / `yarn lint:fix` (uses oxlint + oxfmt)
- `yarn test` (lage across all workspaces)
- `yarn typecheck` (tsgo + lage typecheck)
### Testing and previewing the app
When running the app for manual testing or demos, use **"View demo"** on the initial setup screen (after selecting "Don't use a server"). This creates a test budget pre-populated with realistic sample data (accounts, transactions, categories, and budgeted amounts), which is far more useful than starting with an empty budget.
### Gotchas
- The `engines` field requires **Node.js >=22** and **Yarn ^4.9.1**. The `.nvmrc` specifies `v22/*`.
- Pre-commit hook runs `lint-staged` (oxfmt + oxlint) via Husky. Run `yarn prepare` once after install to set up hooks.
- Lage caches test results in `.lage/`. If tests behave unexpectedly, clear with `rm -rf .lage`.
- Native modules (`better-sqlite3`, `bcrypt`) require build tools (`gcc`, `make`, `python3`). These are pre-installed in the Cloud VM.
- All yarn commands must be run from the repository root, never from child workspaces.

2
CLAUDE.md Normal file
View File

@@ -0,0 +1,2 @@
@AGENTS.md
@.github/agents/pr-and-commit-rules.md

View File

@@ -1 +1,3 @@
Please review the contributing documentation on our website: https://actualbudget.org/docs/contributing/
If you plan to use AI tools when contributing, please also read our [AI Usage Policy](https://actualbudget.org/docs/contributing/ai-usage-policy).

View File

@@ -0,0 +1,218 @@
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import {
derivePublishImports,
validatePackage,
} from '../validate-publish-imports.js';
describe('derivePublishImports', () => {
it('prepends ./build/ to .js paths', () => {
const imports = {
'#account-db': './src/account-db.js',
};
expect(derivePublishImports(imports)).toEqual({
'#account-db': './build/src/account-db.js',
});
});
it('converts .ts extension to .js and prepends ./build/', () => {
const imports = {
'#migrations': './src/migrations.ts',
};
expect(derivePublishImports(imports)).toEqual({
'#migrations': './build/src/migrations.js',
});
});
it('converts .tsx extension to .js and prepends ./build/', () => {
const imports = {
'#component': './src/component.tsx',
};
expect(derivePublishImports(imports)).toEqual({
'#component': './build/src/component.js',
});
});
it('preserves wildcard patterns', () => {
const imports = {
'#accounts/*': './src/accounts/*.js',
'#services/*': './src/app-gocardless/services/*.ts',
};
expect(derivePublishImports(imports)).toEqual({
'#accounts/*': './build/src/accounts/*.js',
'#services/*': './build/src/app-gocardless/services/*.js',
});
});
it('handles multiple entries with mixed extensions', () => {
const imports = {
'#account-db': './src/account-db.js',
'#migrations': './src/migrations.ts',
'#app-gocardless/errors': './src/app-gocardless/errors.ts',
'#util/*': './src/util/*.ts',
'#scripts/*': './src/scripts/*.js',
};
expect(derivePublishImports(imports)).toEqual({
'#account-db': './build/src/account-db.js',
'#migrations': './build/src/migrations.js',
'#app-gocardless/errors': './build/src/app-gocardless/errors.js',
'#util/*': './build/src/util/*.js',
'#scripts/*': './build/src/scripts/*.js',
});
});
it('returns empty object for empty imports', () => {
expect(derivePublishImports({})).toEqual({});
});
it('throws error for non-string imports values', () => {
const imports = {
'#foo': './src/foo.js',
'#conditional': {
browser: './src/browser.js',
node: './src/node.js',
},
};
expect(() => derivePublishImports(imports)).toThrow(
'Unsupported imports target for "#conditional". Expected a string path.',
);
});
it('handles paths with /index.js suffix', () => {
const imports = {
'#util/title': './src/util/title/index.js',
};
expect(derivePublishImports(imports)).toEqual({
'#util/title': './build/src/util/title/index.js',
});
});
});
describe('validatePackage', () => {
let tmpDir: string;
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'validate-imports-'));
});
afterEach(() => {
fs.rmSync(tmpDir, { recursive: true, force: true });
});
function writePackageJson(content: Record<string, unknown>) {
const filePath = path.join(tmpDir, 'package.json');
fs.writeFileSync(filePath, JSON.stringify(content, null, 2) + '\n');
return filePath;
}
it('skips packages with no publishConfig', () => {
const filePath = writePackageJson({
name: 'test-pkg',
imports: { '#foo': './src/foo.js' },
});
const { result, warnings } = validatePackage(filePath);
expect(result).toBeNull();
expect(warnings).toEqual([]);
});
it('skips packages with publishConfig but no publishConfig.imports', () => {
const filePath = writePackageJson({
name: 'test-pkg',
imports: { '#foo': './src/foo.js' },
publishConfig: { access: 'public' },
});
const { result, warnings } = validatePackage(filePath);
expect(result).toBeNull();
expect(warnings).toEqual([]);
});
it('warns when publishConfig.imports exists but imports does not', () => {
const filePath = writePackageJson({
name: 'test-pkg',
publishConfig: {
imports: { '#foo': './build/src/foo.js' },
},
});
const { result, warnings } = validatePackage(filePath);
expect(result).toBeNull();
expect(warnings).toHaveLength(1);
expect(warnings[0]).toContain('orphaned');
});
it('returns no errors when publishConfig.imports matches', () => {
const filePath = writePackageJson({
name: 'test-pkg',
imports: {
'#foo': './src/foo.js',
'#bar': './src/bar.ts',
},
publishConfig: {
imports: {
'#foo': './build/src/foo.js',
'#bar': './build/src/bar.js',
},
},
});
const { result } = validatePackage(filePath);
expect(result).not.toBeNull();
expect(result!.missingKeys).toEqual([]);
expect(result!.extraKeys).toEqual([]);
expect(result!.wrongValues).toEqual([]);
});
it('detects missing keys in publishConfig.imports', () => {
const filePath = writePackageJson({
name: 'test-pkg',
imports: {
'#foo': './src/foo.js',
'#bar': './src/bar.ts',
},
publishConfig: {
imports: {
'#foo': './build/src/foo.js',
},
},
});
const { result } = validatePackage(filePath);
expect(result!.missingKeys).toEqual(['#bar']);
});
it('detects extra keys in publishConfig.imports', () => {
const filePath = writePackageJson({
name: 'test-pkg',
imports: {
'#foo': './src/foo.js',
},
publishConfig: {
imports: {
'#foo': './build/src/foo.js',
'#orphan': './build/src/orphan.js',
},
},
});
const { result } = validatePackage(filePath);
expect(result!.extraKeys).toEqual(['#orphan']);
});
it('detects wrong values in publishConfig.imports', () => {
const filePath = writePackageJson({
name: 'test-pkg',
imports: {
'#foo': './src/foo.ts',
},
publishConfig: {
imports: {
'#foo': './src/foo.ts',
},
},
});
const { result } = validatePackage(filePath);
expect(result!.wrongValues).toEqual([
{ key: '#foo', expected: './build/src/foo.js', actual: './src/foo.ts' },
]);
});
});

View File

@@ -16,8 +16,9 @@ packages/desktop-client/bin/remove-untranslated-languages
export NODE_OPTIONS="--max-old-space-size=4096"
yarn workspace @actual-app/crdt build
yarn workspace plugins-service build
yarn workspace loot-core build:browser
yarn workspace @actual-app/core build:browser
yarn workspace @actual-app/web build:browser
echo "packages/desktop-client/build"

View File

@@ -43,6 +43,7 @@ if [ $SKIP_TRANSLATIONS == false ]; then
pushd packages/desktop-client/locale > /dev/null
git checkout .
git pull
popd > /dev/null
packages/desktop-client/bin/remove-untranslated-languages
@@ -50,15 +51,19 @@ fi
export NODE_OPTIONS="--max-old-space-size=4096"
yarn workspace @actual-app/crdt build
yarn workspace plugins-service build
yarn workspace loot-core build:node
yarn workspace @actual-app/core build:node
yarn workspace @actual-app/web build --mode=desktop # electron specific build
# required for running the sync-server server
yarn workspace loot-core build:browser
yarn workspace @actual-app/core build:browser
yarn workspace @actual-app/web build:browser
yarn workspace @actual-app/sync-server build
# Emit @actual-app/core declarations so desktop-electron (which includes typings/window.ts) can build
yarn workspace @actual-app/core exec tsgo -p tsconfig.json
yarn workspace desktop-electron update-client
(

View File

@@ -178,4 +178,4 @@ async function execAsync(cmd: string, errorLog?: string): Promise<string> {
});
}
run();
void run();

View File

@@ -28,5 +28,5 @@ echo "Running VRT tests with the following parameters:"
echo "E2E_START_URL: $E2E_START_URL"
echo "VRT_ARGS: $VRT_ARGS"
MSYS_NO_PATHCONV=1 docker run --rm --network host -v "$(pwd)":/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.57.0-jammy /bin/bash \
MSYS_NO_PATHCONV=1 docker run --rm --network host -v "$(pwd)":/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.59.1-jammy /bin/bash \
-c "E2E_START_URL=$E2E_START_URL yarn vrt $VRT_ARGS"

View File

@@ -0,0 +1,216 @@
import fs from 'node:fs';
import path from 'node:path';
/**
* Derives publishConfig.imports from imports by:
* 1. Prepending ./build/ to each value path
* 2. Replacing .ts/.tsx extensions with .js
*/
export function derivePublishImports(
imports: Record<string, string | object>,
): Record<string, string> {
const result: Record<string, string> = {};
for (const [key, value] of Object.entries(imports)) {
if (typeof value !== 'string') {
throw new Error(
`Unsupported imports target for "${key}". Expected a string path.`,
);
}
const withBuildPrefix = value.replace(/^\.\//, './build/');
const withJsExtension = withBuildPrefix.replace(/\.tsx?$/, '.js');
result[key] = withJsExtension;
}
return result;
}
export type ValidationResult = {
packagePath: string;
packageName: string;
missingKeys: string[];
extraKeys: string[];
wrongValues: Array<{ key: string; expected: string; actual: string }>;
};
/**
* Validates publishConfig.imports against imports for a single package.json.
* Returns null if the package should be skipped (no publishConfig.imports).
* Returns a ValidationResult if the package has both fields.
*/
export function validatePackage(packageJsonPath: string): {
result: ValidationResult | null;
warnings: string[];
} {
const warnings: string[] = [];
const content = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
const packageName: string = content.name ?? packageJsonPath;
const imports: Record<string, string | object> | undefined = content.imports;
const publishImports: Record<string, string> | undefined =
content.publishConfig?.imports;
// No publishConfig.imports → skip
if (!publishImports) {
return { result: null, warnings };
}
// Has publishConfig.imports but no imports → warn
if (!imports) {
warnings.push(
`${packageName}: orphaned publishConfig.imports (no imports field)`,
);
return { result: null, warnings };
}
const expected = derivePublishImports(imports);
const expectedKeys = new Set(Object.keys(expected));
const actualKeys = new Set(Object.keys(publishImports));
const missingKeys = [...expectedKeys].filter(k => !actualKeys.has(k));
const extraKeys = [...actualKeys].filter(k => !expectedKeys.has(k));
const wrongValues: ValidationResult['wrongValues'] = [];
for (const key of expectedKeys) {
if (actualKeys.has(key) && publishImports[key] !== expected[key]) {
wrongValues.push({
key,
expected: expected[key],
actual: publishImports[key],
});
}
}
return {
result: {
packagePath: packageJsonPath,
packageName,
missingKeys,
extraKeys,
wrongValues,
},
warnings,
};
}
export function fixPackage(packageJsonPath: string): boolean {
const raw = fs.readFileSync(packageJsonPath, 'utf-8');
const content = JSON.parse(raw);
if (!content.imports || !content.publishConfig?.imports) {
return false;
}
const expected = derivePublishImports(content.imports);
// Check if already correct
if (
JSON.stringify(content.publishConfig.imports) === JSON.stringify(expected)
) {
return false;
}
content.publishConfig.imports = expected;
fs.writeFileSync(packageJsonPath, JSON.stringify(content, null, 2) + '\n');
return true;
}
function findPackageJsonFiles(): string[] {
const packagesDir = path.resolve(__dirname, '..', 'packages');
const entries = fs.readdirSync(packagesDir, { withFileTypes: true });
const results: string[] = [];
for (const entry of entries) {
if (entry.isDirectory()) {
const pkgPath = path.join(packagesDir, entry.name, 'package.json');
if (fs.existsSync(pkgPath)) {
results.push(pkgPath);
}
}
}
return results;
}
function resolvePackageJsonPaths(filePaths: string[]): string[] {
const packagesRoot = path.resolve(__dirname, '..', 'packages');
const seen = new Set<string>();
for (const filePath of filePaths) {
const resolvedPath = path.resolve(filePath);
let dir = path.dirname(resolvedPath);
while (dir.startsWith(packagesRoot + path.sep)) {
const candidate = path.join(dir, 'package.json');
if (
fs.existsSync(candidate) &&
candidate.startsWith(packagesRoot + path.sep)
) {
seen.add(candidate);
break;
}
dir = path.dirname(dir);
}
}
return [...seen];
}
function main() {
const args = process.argv.slice(2);
const fixMode = args.includes('--fix');
const filePaths = args.filter(arg => !arg.startsWith('--'));
const packageJsonFiles =
filePaths.length > 0
? resolvePackageJsonPaths(filePaths)
: findPackageJsonFiles();
let hasErrors = false;
const allWarnings: string[] = [];
for (const pkgPath of packageJsonFiles) {
if (fixMode) {
const fixed = fixPackage(pkgPath);
if (fixed) {
const name = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')).name;
console.log(`Fixed publishConfig.imports in ${name}`);
}
} else {
const { result, warnings } = validatePackage(pkgPath);
allWarnings.push(...warnings);
if (result) {
const hasIssues =
result.missingKeys.length > 0 ||
result.extraKeys.length > 0 ||
result.wrongValues.length > 0;
if (hasIssues) {
hasErrors = true;
console.error(`\n${result.packageName}:`);
for (const key of result.missingKeys) {
console.error(` Missing key: ${key}`);
}
for (const key of result.extraKeys) {
console.error(` Extra key: ${key}`);
}
for (const { key, expected, actual } of result.wrongValues) {
console.error(` Wrong value for ${key}:`);
console.error(` expected: ${expected}`);
console.error(` actual: ${actual}`);
}
}
}
}
}
for (const warning of allWarnings) {
console.warn(`Warning: ${warning}`);
}
if (hasErrors) {
console.error(
'\npublishConfig.imports is out of sync. Run with --fix to auto-fix.',
);
process.exit(1);
}
}
if (require.main === module) {
main();
}

9
bin/vitest.config.mts Normal file
View File

@@ -0,0 +1,9 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
include: ['__tests__/**/*.test.ts'],
environment: 'node',
},
});

View File

@@ -1,6 +1,12 @@
const BUILD_OUTPUT_GLOBS = ['lib-dist/**', 'dist/**', 'build/**', '@types/**'];
/** @type {import('lage').ConfigOptions} */
module.exports = {
pipeline: {
typecheck: {
type: 'npmScript',
dependsOn: ['^typecheck'],
},
test: {
type: 'npmScript',
options: {
@@ -13,16 +19,17 @@ module.exports = {
},
build: {
type: 'npmScript',
dependsOn: ['^build'],
cache: true,
options: {
outputGlob: ['lib-dist/**', 'dist/**', 'build/**'],
outputGlob: BUILD_OUTPUT_GLOBS,
},
},
},
cacheOptions: {
cacheStorageConfig: {
provider: 'local',
outputGlob: ['lib-dist/**', 'dist/**', 'build/**'],
outputGlob: BUILD_OUTPUT_GLOBS,
},
},
npmClient: 'yarn',

View File

@@ -25,21 +25,23 @@
"start:desktop": "yarn desktop-dependencies && npm-run-all --parallel 'start:desktop-*'",
"start:docs": "yarn workspace docs start",
"desktop-dependencies": "npm-run-all --parallel rebuild-electron build:browser-backend build:plugins-service",
"start:desktop-node": "yarn workspace loot-core watch:node",
"start:desktop-node": "yarn workspace @actual-app/core watch:node",
"start:desktop-client": "yarn workspace @actual-app/web watch",
"start:desktop-server-client": "yarn workspace @actual-app/web build:browser",
"start:desktop-electron": "yarn workspace desktop-electron watch",
"start:browser": "yarn workspace plugins-service build-dev && npm-run-all --parallel 'start:browser-*'",
"start:service-plugins": "yarn workspace plugins-service watch",
"start:browser-backend": "yarn workspace loot-core watch:browser",
"start:browser-backend": "yarn workspace @actual-app/core watch:browser",
"start:browser-frontend": "yarn workspace @actual-app/web start:browser",
"start:storybook": "yarn workspace @actual-app/components start:storybook",
"build:browser-backend": "yarn workspace loot-core build:browser",
"build": "lage build",
"build:browser-backend": "yarn workspace @actual-app/core build:browser",
"build:server": "yarn build:browser && yarn workspace @actual-app/sync-server build",
"build:browser": "./bin/package-browser",
"build:desktop": "./bin/package-electron",
"build:plugins-service": "yarn workspace plugins-service build",
"build:api": "yarn workspace @actual-app/api build",
"build:api": "yarn build --scope=@actual-app/api",
"build:cli": "yarn build --scope=@actual-app/cli",
"build:docs": "yarn workspace docs build",
"build:storybook": "yarn workspace @actual-app/components build:storybook",
"deploy:docs": "yarn workspace docs deploy",
@@ -52,50 +54,62 @@
"playwright": "yarn workspace @actual-app/web run playwright",
"vrt": "yarn workspace @actual-app/web run vrt",
"vrt:docker": "./bin/run-vrt",
"rebuild-electron": "./node_modules/.bin/electron-rebuild -m ./packages/loot-core",
"rebuild-node": "yarn workspace loot-core rebuild",
"lint": "oxfmt --check . && oxlint --deny-warnings",
"lint:fix": "oxfmt . && oxlint --deny-warnings --fix",
"rebuild-electron": "./node_modules/.bin/electron-rebuild -m ./packages/loot-core && ./node_modules/.bin/electron-rebuild -m ./packages/desktop-electron -o better-sqlite3,bcrypt",
"rebuild-node": "yarn workspace @actual-app/core rebuild",
"lint": "oxfmt --check . && oxlint --type-aware --quiet",
"lint:fix": "oxfmt . && oxlint --fix --type-aware --quiet",
"install:server": "yarn workspaces focus @actual-app/sync-server --production",
"typecheck": "yarn tsc --incremental && tsc-strict",
"jq": "./node_modules/node-jq/bin/jq",
"constraints": "yarn constraints",
"typecheck": "tsgo -p tsconfig.root.json --noEmit && lage typecheck",
"check:tsconfig-references": "workspaces-to-typescript-project-references --check",
"sync:tsconfig-references": "workspaces-to-typescript-project-references",
"prepare": "husky"
},
"devDependencies": {
"@monorepo-utils/workspaces-to-typescript-project-references": "^2.10.3",
"@octokit/rest": "^22.0.1",
"@types/node": "^22.19.3",
"@types/node": "^22.19.17",
"@types/prompts": "^2.4.9",
"baseline-browser-mapping": "^2.9.14",
"cross-env": "^10.1.0",
"eslint": "^9.39.2",
"eslint-plugin-perfectionist": "^4.15.1",
"@typescript/native-preview": "beta",
"@yarnpkg/types": "^4.0.1",
"eslint": "^10.2.0",
"eslint-plugin-perfectionist": "^5.8.0",
"eslint-plugin-typescript-paths": "^0.0.33",
"html-to-image": "^1.11.13",
"husky": "^9.1.7",
"lage": "^2.14.15",
"lint-staged": "^16.2.7",
"minimatch": "^10.1.1",
"node-jq": "^6.3.1",
"lage": "^2.15.5",
"lint-staged": "^16.4.0",
"minimatch": "^10.2.5",
"npm-run-all": "^4.1.5",
"oxfmt": "^0.26.0",
"oxlint": "^1.41.0",
"p-limit": "^7.2.0",
"oxfmt": "^0.44.0",
"oxlint": "^1.59.0",
"oxlint-tsgolint": "^0.20.0",
"p-limit": "^7.3.0",
"prompts": "^2.4.2",
"source-map-support": "^0.5.21",
"ts-node": "^10.9.2",
"typescript": "^5.9.3",
"typescript-strict-plugin": "^2.4.4"
"typescript": "^6.0.2",
"vitest": "^4.1.2"
},
"resolutions": {
"adm-zip": "patch:adm-zip@npm%3A0.5.16#~/.yarn/patches/adm-zip-npm-0.5.16-4556fea098.patch",
"minimatch@10.2.1": "10.2.5",
"minimatch@3.1.2": "3.1.5",
"minimatch@>=10.0.0 <11.0.0": "10.2.5",
"minimatch@>=3.0.0 <4.0.0": "3.1.5",
"minimatch@>=5.0.0 <6.0.0": "5.1.9",
"minimatch@>=9.0.0 <10.0.0": "9.0.9",
"rollup": "4.40.1",
"socks": ">=2.8.3"
},
"lint-staged": {
"packages/*/{package.json,tsconfig.json}": [
"ts-node ./bin/validate-publish-imports.ts --fix",
"yarn sync:tsconfig-references"
],
"*.{js,mjs,jsx,ts,tsx,md,json,yml,yaml}": [
"oxfmt --no-error-on-unmatched-pattern"
],
"*.{js,mjs,jsx,ts,tsx}": [
"oxlint --deny-warnings --fix"
"oxlint --fix --type-aware --quiet"
]
},
"browserslist": [
@@ -106,5 +120,5 @@
"node": ">=22",
"yarn": "^4.9.1"
},
"packageManager": "yarn@4.10.3"
"packageManager": "yarn@4.13.0"
}

View File

@@ -3,3 +3,7 @@ npm install @actual-app/api
```
View docs here: https://actualbudget.org/docs/api/
## TypeScript
`@actual-app/api` publishes TypeScript declarations. Consumers using TypeScript must set `moduleResolution` to `"bundler"`, `"nodenext"`, or `"node16"` in their `tsconfig.json`. Legacy `"node"` / `"node10"` / `"classic"` resolution is not supported in strict mode — the published declarations rely on package.json `exports` conditions that older resolvers don't honor.

View File

@@ -1,4 +1,7 @@
class Query {
/** @type {import('@actual-app/core/shared/query').QueryState} */
state;
constructor(state) {
this.state = {
filterExpressions: state.filterExpressions || [],

View File

@@ -1,53 +1,30 @@
import type {
RequestInfo as FetchInfo,
RequestInit as FetchInit,
} from 'node-fetch';
import { init as initLootCore } from '@actual-app/core/server/main';
import type { InitConfig, lib } from '@actual-app/core/server/main';
// loot-core types
import type { InitConfig } from 'loot-core/server/main';
// oxlint-disable-next-line typescript/ban-ts-comment
// @ts-ignore: bundle not available until we build it
import * as bundle from './app/bundle.api.js';
import * as injected from './injected';
import { validateNodeVersion } from './validateNodeVersion';
let actualApp: null | typeof bundle.lib;
export const internal = bundle.lib;
export * from './methods';
export * as utils from './utils';
export async function init(config: InitConfig = {}) {
if (actualApp) {
return;
}
/** @deprecated Please use return value of `init` instead */
export let internal: typeof lib | null = null;
export async function init(config: InitConfig = {}) {
validateNodeVersion();
if (!globalThis.fetch) {
globalThis.fetch = (url: URL | RequestInfo, init?: RequestInit) => {
return import('node-fetch').then(({ default: fetch }) =>
fetch(url as unknown as FetchInfo, init as unknown as FetchInit),
) as unknown as Promise<Response>;
};
}
await bundle.init(config);
actualApp = bundle.lib;
injected.override(bundle.lib.send);
return bundle.lib;
internal = await initLootCore(config);
return internal;
}
export async function shutdown() {
if (actualApp) {
if (internal) {
try {
await actualApp.send('sync');
await internal.send('sync');
} catch {
// most likely that no budget is loaded, so the sync failed
}
await actualApp.send('close-budget');
actualApp = null;
await internal.send('close-budget');
internal = null;
}
}

View File

@@ -1,7 +0,0 @@
// TODO: comment on why it works this way
export let send;
export function override(sendImplementation) {
send = sendImplementation;
}

View File

@@ -1,10 +1,28 @@
import * as fs from 'fs/promises';
import * as path from 'path';
import { type RuleEntity } from 'loot-core/types/models';
import type { RuleEntity } from '@actual-app/core/types/models';
import { vi } from 'vitest';
import * as api from './index';
// In tests we run from source; loot-core's API fs uses __dirname (for the built dist/).
// Mock the fs so path constants point at loot-core package root where migrations live.
vi.mock(
'../loot-core/src/platform/server/fs/index.api',
async importOriginal => {
const actual = (await importOriginal()) as Record<string, unknown>;
const pathMod = await import('path');
const lootCoreRoot = pathMod.join(__dirname, '..', 'loot-core');
return {
...actual,
migrationsPath: pathMod.join(lootCoreRoot, 'migrations'),
bundledDatabasePath: pathMod.join(lootCoreRoot, 'default-db.sqlite'),
demoBudgetPath: pathMod.join(lootCoreRoot, 'demo-budget'),
};
},
);
const budgetName = 'test-budget';
global.IS_TESTING = true;
@@ -356,6 +374,143 @@ describe('API CRUD operations', () => {
);
});
// apis: createTag, getTags, updateTag, deleteTag
test('Tags: successfully complete tag operations', async () => {
// Create tags
const tagId1 = await api.createTag({ tag: 'test-tag1', color: '#ff0000' });
const tagId2 = await api.createTag({
tag: 'test-tag2',
description: 'A test tag',
});
let tags = await api.getTags();
expect(tags).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: tagId1,
tag: 'test-tag1',
color: '#ff0000',
}),
expect.objectContaining({
id: tagId2,
tag: 'test-tag2',
description: 'A test tag',
}),
]),
);
// Update tag
await api.updateTag(tagId1, { tag: 'updated-tag', color: '#00ff00' });
tags = await api.getTags();
expect(tags).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: tagId1,
tag: 'updated-tag',
color: '#00ff00',
}),
]),
);
// Delete tag
await api.deleteTag(tagId2);
tags = await api.getTags();
expect(tags).not.toEqual(
expect.arrayContaining([expect.objectContaining({ id: tagId2 })]),
);
});
test('Tags: create tag with minimal fields', async () => {
const tagId = await api.createTag({ tag: 'minimal-tag' });
const tags = await api.getTags();
expect(tags).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: tagId,
tag: 'minimal-tag',
color: null,
description: null,
}),
]),
);
});
test('Tags: update single field only', async () => {
const tagId = await api.createTag({ tag: 'original', color: '#ff0000' });
// Update only color, tag and description should remain unchanged
await api.updateTag(tagId, { color: '#00ff00' });
const tags = await api.getTags();
expect(tags).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: tagId,
tag: 'original',
color: '#00ff00',
description: null,
}),
]),
);
});
test('Tags: handle null values correctly', async () => {
const tagId = await api.createTag({
tag: 'with-nulls',
color: null,
description: null,
});
const tags = await api.getTags();
expect(tags).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: tagId,
color: null,
description: null,
}),
]),
);
});
test('Tags: clear optional field', async () => {
const tagId = await api.createTag({
tag: 'clearable',
color: '#ff0000',
description: 'will be cleared',
});
// Clear color by setting to null
await api.updateTag(tagId, { color: null });
let tags = await api.getTags();
expect(tags).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: tagId,
tag: 'clearable',
color: null,
description: 'will be cleared',
}),
]),
);
// Clear description by setting to null
await api.updateTag(tagId, { description: null });
tags = await api.getTags();
expect(tags).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: tagId,
tag: 'clearable',
color: null,
description: null,
}),
]),
);
});
// apis: getRules, getPayeeRules, createRule, updateRule, deleteRule
test('Rules: successfully update rules', async () => {
await api.createPayee({ name: 'test-payee' });
@@ -740,6 +895,73 @@ describe('API CRUD operations', () => {
);
expect(transactions[0].notes).toBeNull();
});
test('Transactions: reimportDeleted=false prevents reimporting deleted transactions', async () => {
const accountId = await api.createAccount({ name: 'test-account' }, 0);
// Import a transaction
const result1 = await api.importTransactions(accountId, [
{
date: '2023-11-03',
imported_id: 'reimport-test-1',
amount: 100,
account: accountId,
},
]);
expect(result1.added).toHaveLength(1);
// Delete the transaction
await api.deleteTransaction(result1.added[0]);
// Reimport the same transaction with reimportDeleted=false
const result2 = await api.importTransactions(
accountId,
[
{
date: '2023-11-03',
imported_id: 'reimport-test-1',
amount: 100,
account: accountId,
},
],
{ reimportDeleted: false },
);
// Should match the deleted transaction and not create a new one
expect(result2.added).toHaveLength(0);
expect(result2.updated).toHaveLength(0);
});
test('Transactions: reimportDeleted=true reimports deleted transactions', async () => {
const accountId = await api.createAccount({ name: 'test-account' }, 0);
// Import a transaction
const result1 = await api.importTransactions(accountId, [
{
date: '2023-11-03',
imported_id: 'reimport-test-2',
amount: 200,
account: accountId,
},
]);
expect(result1.added).toHaveLength(1);
// Delete the transaction
await api.deleteTransaction(result1.added[0]);
// Reimport the same transaction relying on reimportDeleted=true default
const result2 = await api.importTransactions(accountId, [
{
date: '2023-11-03',
imported_id: 'reimport-test-2',
amount: 200,
account: accountId,
},
]);
// Should create a new transaction since deleted ones are ignored
expect(result2.added).toHaveLength(1);
});
});
//apis: createSchedule, getSchedules, updateSchedule, deleteSchedule

View File

@@ -5,16 +5,17 @@ import type {
APIFileEntity,
APIPayeeEntity,
APIScheduleEntity,
} from 'loot-core/server/api-models';
import type { Query } from 'loot-core/shared/query';
import type { Handlers } from 'loot-core/types/handlers';
APITagEntity,
} from '@actual-app/core/server/api-models';
import { lib } from '@actual-app/core/server/main';
import type { Query } from '@actual-app/core/shared/query';
import type { ImportTransactionsOpts } from '@actual-app/core/types/api-handlers';
import type { Handlers } from '@actual-app/core/types/handlers';
import type {
ImportTransactionEntity,
RuleEntity,
TransactionEntity,
} from 'loot-core/types/models';
import * as injected from './injected';
} from '@actual-app/core/types/models';
export { q } from './app/query';
@@ -22,7 +23,7 @@ function send<K extends keyof Handlers, T extends Handlers[K]>(
name: K,
args?: Parameters<T>[0],
): Promise<Awaited<ReturnType<T>>> {
return injected.send(name, args);
return lib.send(name, args);
}
export async function runImport(
@@ -125,11 +126,6 @@ export function addTransactions(
});
}
export type ImportTransactionsOpts = {
defaultCleared?: boolean;
dryRun?: boolean;
};
export function importTransactions(
accountId: APIAccountEntity['id'],
transactions: ImportTransactionEntity[],
@@ -274,6 +270,25 @@ export function deletePayee(id: APIPayeeEntity['id']) {
return send('api/payee-delete', { id });
}
export function getTags() {
return send('api/tags-get');
}
export function createTag(tag: Omit<APITagEntity, 'id'>) {
return send('api/tag-create', { tag });
}
export function updateTag(
id: APITagEntity['id'],
fields: Partial<Omit<APITagEntity, 'id'>>,
) {
return send('api/tag-update', { id, fields });
}
export function deleteTag(id: APITagEntity['id']) {
return send('api/tag-delete', { id });
}
export function mergePayees(
targetId: APIPayeeEntity['id'],
mergeIds: APIPayeeEntity['id'][],

1
packages/api/models.ts Normal file
View File

@@ -0,0 +1 @@
export type * from '@actual-app/core/server/api-models';

View File

@@ -1,35 +1,61 @@
{
"name": "@actual-app/api",
"version": "26.2.0",
"version": "26.5.0",
"description": "An API for Actual",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/actualbudget/actual.git",
"directory": "packages/api"
},
"files": [
"@types",
"dist"
],
"main": "dist/index.js",
"types": "@types/index.d.ts",
"exports": {
".": {
"types": "./@types/index.d.ts",
"development": "./index.ts",
"default": "./dist/index.js"
},
"./models": {
"types": "./@types/models.d.ts",
"development": "./models.ts",
"default": "./dist/models.js"
}
},
"publishConfig": {
"exports": {
".": {
"types": "./@types/index.d.ts",
"default": "./dist/index.js"
},
"./models": {
"types": "./@types/models.d.ts",
"default": "./dist/models.js"
}
}
},
"scripts": {
"build:app": "yarn workspace loot-core build:api",
"build:crdt": "yarn workspace @actual-app/crdt build",
"build:node": "tsc --p tsconfig.dist.json && tsc-alias -p tsconfig.dist.json",
"build:migrations": "cp migrations/*.sql dist/migrations",
"build:default-db": "cp default-db.sqlite dist/",
"build": "yarn run clean && yarn run build:app && yarn run build:node && yarn run build:migrations && yarn run build:default-db",
"test": "yarn run clean && yarn run build:app && yarn run build:crdt && vitest --run",
"clean": "rm -rf dist @types"
"build": "vite build && tsgo --emitDeclarationOnly",
"test": "vitest --run",
"typecheck": "tsgo -b && tsc-strict"
},
"dependencies": {
"@actual-app/crdt": "workspace:^",
"better-sqlite3": "^12.5.0",
"compare-versions": "^6.1.1",
"node-fetch": "^3.3.2",
"uuid": "^13.0.0"
"@actual-app/core": "workspace:*",
"@actual-app/crdt": "workspace:*",
"better-sqlite3": "^12.8.0",
"compare-versions": "^6.1.1"
},
"devDependencies": {
"tsc-alias": "^1.8.16",
"typescript": "^5.9.3",
"vitest": "^4.0.16"
"@typescript/native-preview": "beta",
"rollup-plugin-visualizer": "^7.0.1",
"typescript-strict-plugin": "^2.4.4",
"vite": "^8.0.5",
"vite-plugin-peggy-loader": "^2.0.1",
"vitest": "^4.1.2"
},
"engines": {
"node": ">=20"

View File

@@ -1,19 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
// Using ES2021 because that's the newest version where
// the latest Node 16.x release supports all of the features
"target": "ES2021",
"module": "CommonJS",
"moduleResolution": "node10",
"noEmit": false,
"declaration": true,
"outDir": "dist",
"declarationDir": "@types",
"paths": {
"loot-core/*": ["./@types/loot-core/src/*"]
}
},
"include": ["."],
"exclude": ["**/node_modules/*", "dist", "@types", "*.test.ts"]
}

View File

@@ -0,0 +1,42 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"composite": true,
// Using ES2021 because that's the newest version where
// the latest Node 16.x release supports all of the features
"target": "ES2021",
"module": "es2022",
"moduleResolution": "bundler",
"customConditions": ["api"],
"noEmit": false,
"declaration": true,
"declarationMap": true,
"outDir": "dist",
"rootDir": ".",
"declarationDir": "@types",
"tsBuildInfoFile": "dist/.tsbuildinfo",
"plugins": [
{
"name": "typescript-strict-plugin",
"paths": ["."]
}
]
},
"references": [
{
"path": "../loot-core"
},
{
"path": "../crdt"
}
],
"include": ["."],
"exclude": [
"**/node_modules/*",
"dist",
"@types",
"*.test.ts",
"*.config.ts",
"*.config.mts"
]
}

2
packages/api/typings.ts Normal file
View File

@@ -0,0 +1,2 @@
declare module 'hyperformula/i18n/languages/enUS';
declare module '*.pegjs';

1
packages/api/typings/pegjs.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
declare module '*.pegjs';

View File

@@ -1,6 +1,4 @@
// oxlint-disable-next-line typescript/ban-ts-comment
// @ts-ignore: bundle not available until we build it
import * as bundle from './app/bundle.api.js';
import { lib } from '@actual-app/core/server/main';
export const amountToInteger = bundle.lib.amountToInteger;
export const integerToAmount = bundle.lib.integerToAmount;
export const amountToInteger = lib.amountToInteger;
export const integerToAmount = lib.integerToAmount;

View File

@@ -0,0 +1,94 @@
import fs from 'fs';
import path from 'path';
import { visualizer } from 'rollup-plugin-visualizer';
import { defineConfig } from 'vite';
import peggyLoader from 'vite-plugin-peggy-loader';
const lootCoreRoot = path.resolve(__dirname, '../loot-core');
const distDir = path.resolve(__dirname, 'dist');
const typesDir = path.resolve(__dirname, '@types');
function cleanOutputDirs() {
return {
name: 'clean-output-dirs',
buildStart() {
if (fs.existsSync(distDir)) fs.rmSync(distDir, { recursive: true });
if (fs.existsSync(typesDir)) fs.rmSync(typesDir, { recursive: true });
},
};
}
function copyMigrationsAndDefaultDb() {
return {
name: 'copy-migrations-and-default-db',
closeBundle() {
const migrationsSrc = path.join(lootCoreRoot, 'migrations');
const defaultDbPath = path.join(lootCoreRoot, 'default-db.sqlite');
if (!fs.existsSync(migrationsSrc)) {
throw new Error(`migrations directory not found at ${migrationsSrc}`);
}
const migrationsStat = fs.statSync(migrationsSrc);
if (!migrationsStat.isDirectory()) {
throw new Error(`migrations path is not a directory: ${migrationsSrc}`);
}
const migrationsDest = path.join(distDir, 'migrations');
fs.mkdirSync(migrationsDest, { recursive: true });
for (const name of fs.readdirSync(migrationsSrc)) {
if (name.endsWith('.sql') || name.endsWith('.js')) {
fs.copyFileSync(
path.join(migrationsSrc, name),
path.join(migrationsDest, name),
);
}
}
if (!fs.existsSync(defaultDbPath)) {
throw new Error(`default-db.sqlite not found at ${defaultDbPath}`);
}
fs.copyFileSync(defaultDbPath, path.join(distDir, 'default-db.sqlite'));
},
};
}
export default defineConfig({
ssr: {
noExternal: true,
external: ['better-sqlite3'],
resolve: { conditions: ['api'] },
},
build: {
ssr: true,
target: 'node20',
outDir: distDir,
emptyOutDir: true,
sourcemap: true,
lib: {
entry: {
index: path.resolve(__dirname, 'index.ts'),
models: path.resolve(__dirname, 'models.ts'),
},
formats: ['cjs'],
fileName: (_format, entryName) => `${entryName}.js`,
},
},
plugins: [
cleanOutputDirs(),
peggyLoader(),
copyMigrationsAndDefaultDb(),
visualizer({ template: 'raw-data', filename: 'app/stats.json' }),
],
resolve: {
conditions: ['api'],
},
test: {
globals: true,
onConsoleLog(log: string, type: 'stdout' | 'stderr'): boolean | void {
// print only console.error
return type === 'stderr';
},
maxWorkers: 2,
},
});

View File

@@ -1,10 +0,0 @@
export default {
test: {
globals: true,
onConsoleLog(log: string, type: 'stdout' | 'stderr'): boolean | void {
// print only console.error
return type === 'stderr';
},
maxWorkers: 2,
},
};

1
packages/ci-actions/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
dist/*

View File

@@ -1,14 +1,14 @@
#!/usr/bin/env node
// overview:
// 1. Identify the migrations in packages/loot-core/migrations/* on `master` and HEAD
// 2. Make sure that any new migrations on HEAD are dated after the latest migration on `master`.
const { spawnSync } = require('child_process');
const path = require('path');
import { spawnSync } from 'child_process';
import path from 'path';
import { fileURLToPath } from 'url';
const migrationsDir = path.join(
__dirname,
path.dirname(fileURLToPath(import.meta.url)),
'..',
'..',
'..',
'packages',
@@ -16,7 +16,7 @@ const migrationsDir = path.join(
'migrations',
);
function readMigrations(ref) {
function readMigrations(ref: string) {
const { stdout } = spawnSync('git', [
'ls-tree',
'--name-only',

View File

@@ -2,13 +2,13 @@
// This script is used in GitHub Actions to get the next version based on the current package.json version.
// It supports three types of versioning: nightly, hotfix, and monthly.
import fs from 'node:fs';
import { parseArgs } from 'node:util';
import { getNextVersion } from '../src/versions/get-next-package-version.js';
const args = process.argv;
import {
getNextVersion,
isValidVersionType,
} from '../src/versions/get-next-package-version';
const options = {
'package-json': {
@@ -28,40 +28,53 @@ const options = {
short: 'u',
default: false,
},
};
} as const;
function fail(message: string): never {
console.error(message);
process.exit(1);
}
const { values } = parseArgs({
args,
options,
allowPositionals: true,
});
if (!values['package-json']) {
console.error(
const packageJsonPath = values['package-json'];
if (!packageJsonPath) {
fail(
'Please specify the path to package.json using --package-json or -p option.',
);
process.exit(1);
}
try {
const packageJsonPath = values['package-json'];
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
if (!('version' in packageJson) || typeof packageJson.version !== 'string') {
fail('The specified package.json does not contain a valid version field.');
}
const currentVersion = packageJson.version;
const explicitVersion = values.version;
let newVersion;
if (explicitVersion) {
newVersion = explicitVersion;
} else {
const type = values.type;
if (!type || !isValidVersionType(type)) {
fail('Please specify the release type using --type or -t.');
}
try {
newVersion = getNextVersion({
currentVersion,
type: values.type,
type,
currentDate: new Date(),
});
} catch (e) {
console.error(e.message);
process.exit(1);
} catch (error) {
fail(error instanceof Error ? error.message : String(error));
}
}
@@ -76,6 +89,5 @@ try {
);
}
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
fail(`Error: ${error instanceof Error ? error.message : String(error)}`);
}

View File

@@ -0,0 +1,68 @@
import * as fs from 'node:fs';
import matter from 'gray-matter';
import {
categoryAutocorrections,
categoryOrder,
} from '../src/release-notes/util.mjs';
console.log('Looking in ' + fs.realpathSync('upcoming-release-notes'));
const expectedPath = `upcoming-release-notes/${process.env.PR_NUMBER}.md`;
function reportError(message) {
console.log(`::error::${message}`);
process.stdout.write('::notice::');
fs.createReadStream('upcoming-release-notes/README.md').pipe(process.stdout);
fs.createReadStream('upcoming-release-notes/README.md')
.pipe(fs.createWriteStream(process.env.GITHUB_STEP_SUMMARY))
.on('close', () => {
process.exit(1);
});
}
(() => {
if (!fs.existsSync(expectedPath)) {
reportError(`Release note file ${expectedPath} not found`);
return;
}
const { data, content } = matter(fs.readFileSync(expectedPath, 'utf-8'));
if (!data.category) {
reportError(`Release note is missing a category.`);
return;
}
if (categoryAutocorrections[data.category]) {
data.category = categoryAutocorrections[data.category];
}
if (!categoryOrder.includes(data.category)) {
reportError(
`Release note category "${data.category}" is not one of ${categoryOrder
.map(JSON.stringify)
.join(', ')}`,
);
return;
}
if (!data.authors) {
reportError(`Release note is missing authors.`);
return;
}
if (!Array.isArray(data.authors)) {
reportError(`Release note authors should be a list.`);
return;
}
if (content.trim().split('\n').length !== 1) {
reportError(
`Release note file ${expectedPath} body should contain exactly one line`,
);
return;
}
console.log('Everything looks good! \u{1f389}');
})();

View File

@@ -0,0 +1,312 @@
import * as childProcess from 'node:child_process';
import * as fs from 'node:fs/promises';
import { join } from 'node:path';
import { inspect, promisify } from 'node:util';
import matter from 'gray-matter';
import listify from 'listify';
import {
categoryAutocorrections,
categoryOrder,
} from '../src/release-notes/util.mjs';
const exec = promisify(childProcess.exec);
const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/');
const apiResult = await fetch('https://api.github.com/graphql', {
method: 'POST',
headers: {
Authorization: `bearer ${process.env.GITHUB_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: /* GraphQL */ `
query GetPRMetadata(
$name: String!
$owner: String!
$headRefName: String!
) {
repository(name: $name, owner: $owner) {
pullRequests(headRefName: $headRefName, first: 1) {
edges {
node {
number
headRefName
body
}
}
}
}
}
`,
variables: {
name: repo,
owner,
headRefName: process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME,
},
}),
}).then(res => res.json());
await collapsedLog('API Response', apiResult);
const prData = apiResult.data.repository.pullRequests.edges[0].node;
const version = prData.headRefName.split('/')[1].replace(/^v/, '');
const slug = version.replace(/\./g, '-');
const author = process.env.GITHUB_ACTOR || 'TODO';
const commitMessage = `Generate release notes for v${version}`;
const releaseDateMatch = (prData.body || '').match(
/<!-- release-date:(\d{4}-\d{2}-\d{2}) -->/,
);
const releaseDate = releaseDateMatch ? releaseDateMatch[1] : 'TODO';
const botName = 'github-actions[bot]';
const botEmail = '41898282+github-actions[bot]@users.noreply.github.com';
await exec(`git config user.name '${botName}'`);
await exec(`git config user.email '${botEmail}'`);
const AUTOGEN_MARKER = '<!-- release-notes:auto-generated -->';
await group('Prepare branch', async () => {
if (process.env.GITHUB_HEAD_REF) {
await exec(`git fetch origin ${process.env.GITHUB_HEAD_REF}`, {
stdio: 'inherit',
});
await exec(`git checkout ${process.env.GITHUB_HEAD_REF}`, {
stdio: 'inherit',
});
}
// recover deleted release note files from previous generation commits
const baseRef = process.env.GITHUB_BASE_REF || 'master';
await exec(`git fetch origin ${baseRef}`, { stdio: 'inherit' });
const { stdout: mergeBase } = await exec(
`git merge-base HEAD origin/${baseRef}`,
);
const base = mergeBase.trim();
const { stdout: genLog } = await exec(
`git log --grep='${commitMessage}' --format=%H ${base}..HEAD`,
);
const genCommits = genLog.split('\n').filter(Boolean);
console.log(
`Reversing upcoming-release-notes deletions from ${genCommits.length} prior generation commit(s)`,
);
const tmpDir = process.env.RUNNER_TEMP || '/tmp';
for (const sha of genCommits) {
const patchPath = join(tmpDir, `revert-${sha}.patch`);
try {
await exec(
`git diff --diff-filter=D ${sha}~1..${sha} -- upcoming-release-notes > ${patchPath}`,
);
const { size } = await fs.stat(patchPath);
if (size > 0) {
await exec(`git apply -R --3way ${patchPath}`, { stdio: 'inherit' });
}
} finally {
await fs.unlink(patchPath).catch(() => undefined);
}
}
});
const { notesByCategory, files } = await parseReleaseNotes(
'upcoming-release-notes',
);
const categorizedNotes = formatNotes(notesByCategory);
await collapsedLog('Release Notes', categorizedNotes);
if (files.length === 0) {
console.log('No release notes found, nothing to generate');
process.exit(0);
}
const highlights = '- TODO: Add release highlights';
const blogPath = join(
'packages/docs/blog',
`${releaseDate}-release-${slug}.md`,
);
const releasesPath = 'packages/docs/docs/releases.md';
await group('Generate blog post', async () => {
const template = `---
title: Release ${version}
description: New release of Actual.
date: ${releaseDate}T10:00
slug: release-${version}
tags: [announcement, release]
hide_table_of_contents: false
authors: ${author}
---
${highlights}
<!--truncate-->
**Docker Tag: ${version}**
${AUTOGEN_MARKER}
${categorizedNotes}
`;
let blogContent;
try {
const existing = await fs.readFile(blogPath, 'utf-8');
const idx = existing.indexOf(AUTOGEN_MARKER);
if (idx === -1) {
console.log(
`WARNING: ${blogPath} missing ${AUTOGEN_MARKER}, rewriting from template`,
);
blogContent = template;
} else {
blogContent =
existing.slice(0, idx + AUTOGEN_MARKER.length) +
'\n' +
categorizedNotes +
'\n';
}
} catch (e) {
if (e.code !== 'ENOENT') throw e;
blogContent = template;
}
await fs.writeFile(blogPath, blogContent);
console.log(`Wrote ${blogPath}`);
});
await group('Update releases.md', async () => {
const existing = await fs.readFile(releasesPath, 'utf-8');
const sectionRe = new RegExp(
`(^|\\n)## ${escapeRegExp(version)}\\n[\\s\\S]*?(?=\\n## |$)`,
);
const match = existing.match(sectionRe);
let updated;
if (match) {
const section = match[0];
const idx = section.indexOf(AUTOGEN_MARKER);
if (idx === -1) {
console.log(
`WARNING: section for ${version} in ${releasesPath} missing ${AUTOGEN_MARKER}, leaving as-is`,
);
updated = existing;
} else {
const newSection =
section.slice(0, idx + AUTOGEN_MARKER.length) + '\n' + categorizedNotes;
updated = existing.replace(section, newSection);
}
} else {
const newSection = `## ${version}
Release date: ${releaseDate}
${highlights}
**Docker Tag: ${version}**
${AUTOGEN_MARKER}
${categorizedNotes}`;
updated = existing.replace(
'# Release Notes\n',
`# Release Notes\n\n${newSection}\n`,
);
}
await fs.writeFile(releasesPath, updated);
console.log(`Updated ${releasesPath}`);
});
await group('Remove used release notes', async () => {
await Promise.all(
files.map(f => fs.unlink(join('upcoming-release-notes', f))),
);
});
await group('Format generated files', async () => {
await exec(`yarn exec oxfmt ${blogPath} ${releasesPath}`, {
stdio: 'inherit',
});
});
await group('Commit and push', async () => {
await exec(
'git add upcoming-release-notes packages/docs/blog packages/docs/docs/releases.md',
{ stdio: 'inherit' },
);
try {
await exec('git diff --cached --quiet');
console.log('No changes to commit');
return;
} catch {
// there are staged changes
}
await exec(`git commit -m '${commitMessage}'`);
await exec('git push origin', { stdio: 'inherit' });
});
async function parseReleaseNotes(dir) {
const files = (await fs.readdir(dir)).filter(f => f.match(/^\d+\.md$/));
const notes = files.map(async name => {
const content = await fs.readFile(join(dir, name), 'utf-8');
const { data, content: body } = matter(content);
const number = name.replace('.md', '');
const authors = listify(
data.authors.map(a => `@${a}`),
{ finalWord: '&' },
);
return {
category: categoryAutocorrections[data.category] ?? data.category,
value: `- [#${number}](https://github.com/actualbudget/${repo}/pull/${number}) ${body.trim()} — thanks ${authors}`,
};
});
const notesByCategory = (await Promise.all(notes)).reduce(
(acc, note) => {
if (!acc[note.category]) {
console.log(`WARNING: Unrecognized category "${note.category}"`);
acc[note.category] = [];
}
acc[note.category].push(note.value);
return acc;
},
Object.fromEntries(categoryOrder.map(c => [c, []])),
);
return { notesByCategory, files };
}
function escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function formatNotes(notes) {
return Object.entries(notes)
.filter(([_, values]) => values.length > 0)
.map(([category, values]) => `#### ${category}\n\n${values.join('\n')}`)
.join('\n\n');
}
async function collapsedLog(name, value) {
await group(name, () => {
if (typeof value === 'string') {
console.log(value);
} else {
console.log(inspect(value, { depth: null }));
}
});
}
async function group(name, cb) {
console.log(`::group::${name}`);
await cb();
console.log('::endgroup::');
}

8
packages/ci-actions/bin/tsx Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/bash
set -euo pipefail
cd ../../
script="$1"
shift
exec node --import=extensionless/register --experimental-strip-types packages/ci-actions/"$script" "$@"

View File

@@ -3,9 +3,21 @@
"private": true,
"type": "module",
"scripts": {
"test": "vitest --run"
"tsx": "bin/tsx",
"test": "vitest --run",
"typecheck": "tsgo -b"
},
"devDependencies": {
"vitest": "^4.0.16"
"@octokit/rest": "^22.0.1",
"@typescript/native-preview": "beta",
"extensionless": "^2.0.6",
"gray-matter": "^4.0.3",
"listify": "^1.0.3",
"vitest": "^4.1.2"
},
"extensionless": {
"lookFor": [
"ts"
]
}
}

View File

@@ -0,0 +1,12 @@
export const categoryAutocorrections = {
Feature: 'Features',
Enhancement: 'Enhancements',
Bugfix: 'Bugfixes',
};
export const categoryOrder = [
'Features',
'Enhancements',
'Bugfixes',
'Maintenance',
];

View File

@@ -77,7 +77,7 @@ describe('getNextVersion (lib)', () => {
expect(() =>
getNextVersion({
currentVersion: '25.8.4',
type: 'unknown',
type: 'unknown' as never,
currentDate: new Date('2025-08-10'),
}),
).toThrow(/Invalid type/);

View File

@@ -1,35 +1,69 @@
function parseVersion(version) {
export const versionTypeArray = [
'auto',
'hotfix',
'monthly',
'nightly',
] as const;
export type VersionType = (typeof versionTypeArray)[number];
type ParsedVersion = {
versionYear: number;
versionMonth: number;
versionHotfix: number;
};
type GetNextVersionOptions = {
currentVersion: string;
type: VersionType;
currentDate?: Date;
};
function parseVersion(version: string): ParsedVersion {
const [y, m, p] = version.split('.');
return {
versionYear: parseInt(y, 10),
versionMonth: parseInt(m, 10),
versionHotfix: parseInt(p, 10),
versionYear: Number.parseInt(y, 10),
versionMonth: Number.parseInt(m, 10),
versionHotfix: Number.parseInt(p, 10),
};
}
function computeNextMonth(versionYear, versionMonth) {
// Create date and add 1 month
const versionDate = new Date(2000 + versionYear, versionMonth - 1, 1); // month is 0-indexed
function computeNextMonth(versionYear: number, versionMonth: number) {
const versionDate = new Date(2000 + versionYear, versionMonth - 1, 1);
const nextVersionMonthDate = new Date(
versionDate.getFullYear(),
versionDate.getMonth() + 1,
1,
);
// Format back to YY.M format
const fullYear = nextVersionMonthDate.getFullYear();
const nextVersionYear = fullYear.toString().slice(fullYear < 2100 ? -2 : -3);
const nextVersionMonth = nextVersionMonthDate.getMonth() + 1; // Convert back to 1-indexed
const nextVersionMonth = nextVersionMonthDate.getMonth() + 1;
return { nextVersionYear, nextVersionMonth };
}
// Determine logical type from 'auto' based on the current date and version
function resolveType(type, currentDate, versionYear, versionMonth) {
if (type !== 'auto') return type;
export function isValidVersionType(value: string): value is VersionType {
return versionTypeArray.includes(value as VersionType);
}
function resolveType(
type: VersionType,
currentDate: Date,
versionYear: number,
versionMonth: number,
) {
if (type !== 'auto') {
return type;
}
const inPatchMonth =
currentDate.getFullYear() === 2000 + versionYear &&
currentDate.getMonth() + 1 === versionMonth;
if (inPatchMonth && currentDate.getDate() <= 25) return 'hotfix';
if (inPatchMonth && currentDate.getDate() < 25) {
return 'hotfix';
}
return 'monthly';
}
@@ -37,7 +71,7 @@ export function getNextVersion({
currentVersion,
type,
currentDate = new Date(),
}) {
}: GetNextVersionOptions) {
const { versionYear, versionMonth, versionHotfix } =
parseVersion(currentVersion);
const { nextVersionYear, nextVersionMonth } = computeNextMonth(
@@ -51,11 +85,10 @@ export function getNextVersion({
versionMonth,
);
// Format date stamp once for nightly
const currentDateString = currentDate
.toISOString()
.split('T')[0]
.replaceAll('-', '');
.replace(/-/g, '');
switch (resolvedType) {
case 'nightly':
@@ -66,7 +99,7 @@ export function getNextVersion({
return `${nextVersionYear}.${nextVersionMonth}.0`;
default:
throw new Error(
'Invalid type specified. Use "auto", "nightly", "hotfix", or "monthly".',
`Invalid type ${String(resolvedType satisfies never)} specified. Use "auto", "nightly", "hotfix", or "monthly".`,
);
}
}

View File

@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": [],
"module": "es2022",
"moduleResolution": "bundler",
"skipLibCheck": true,
"strict": true,
"types": ["node"],
"outDir": "dist",
"rootDir": ".",
"composite": true
},
"include": ["src/**/*", "bin/**/*"],
"exclude": ["node_modules"]
}

7
packages/cli/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
dist
coverage
.actualrc.json
.actualrc
.actualrc.yaml
.actualrc.yml
actual.config.js

177
packages/cli/README.md Normal file
View File

@@ -0,0 +1,177 @@
# @actual-app/cli
> **WARNING:** This CLI is experimental.
Command-line interface for [Actual Budget](https://actualbudget.org). Query and modify your budget data from the terminal — accounts, transactions, categories, payees, rules, schedules, and more.
> **Note:** This CLI connects to a running [Actual sync server](https://actualbudget.org/docs/install/). It does not operate on local budget files directly.
## Installation
```bash
npm install -g @actual-app/cli
```
Requires Node.js >= 22.
## Quick Start
```bash
# Set connection details
export ACTUAL_SERVER_URL=http://localhost:5006
export ACTUAL_PASSWORD=your-password
export ACTUAL_SYNC_ID=your-sync-id # Found in Settings → Advanced → Sync ID
# List your accounts
actual accounts list
# Check a balance
actual accounts balance <account-id>
# View this month's budget
actual budgets month 2026-03
```
## Configuration
Configuration is resolved in this order (highest priority first):
1. **CLI flags** (`--server-url`, `--password`, etc.)
2. **Environment variables**
3. **Config file** (via [cosmiconfig](https://github.com/cosmiconfig/cosmiconfig))
4. **Defaults** (`dataDir` defaults to `~/.actual-cli/data`)
### Environment Variables
| Variable | Description |
| ---------------------- | --------------------------------------------- |
| `ACTUAL_SERVER_URL` | URL of the Actual sync server (required) |
| `ACTUAL_PASSWORD` | Server password (required unless using token) |
| `ACTUAL_SESSION_TOKEN` | Session token (alternative to password) |
| `ACTUAL_SYNC_ID` | Budget Sync ID (required for most commands) |
| `ACTUAL_DATA_DIR` | Local directory for cached budget data |
### Config File
Create an `.actualrc.json` (or `.actualrc`, `.actualrc.yaml`, `actual.config.js`):
```json
{
"serverUrl": "http://localhost:5006",
"password": "your-password",
"syncId": "1cfdbb80-6274-49bf-b0c2-737235a4c81f"
}
```
**Security:** Do not store plaintext passwords in config files (e.g. `.actualrc.json`, `.actualrc`, `.actualrc.yaml`, `actual.config.js`). Add these files to `.gitignore` if they contain secrets. Prefer the `ACTUAL_SESSION_TOKEN` environment variable instead of the `password` field. See [Environment Variables](#environment-variables) for using a session token.
### Global Flags
| Flag | Description |
| ------------------------- | ----------------------------------------------- |
| `--server-url <url>` | Server URL |
| `--password <pw>` | Server password |
| `--session-token <token>` | Session token |
| `--sync-id <id>` | Budget Sync ID |
| `--data-dir <path>` | Data directory |
| `--format <format>` | Output format: `json` (default), `table`, `csv` |
| `--verbose` | Show informational messages |
## Commands
| Command | Description |
| ----------------- | ------------------------------ |
| `accounts` | Manage accounts |
| `budgets` | Manage budgets and allocations |
| `categories` | Manage categories |
| `category-groups` | Manage category groups |
| `transactions` | Manage transactions |
| `payees` | Manage payees |
| `tags` | Manage tags |
| `rules` | Manage transaction rules |
| `schedules` | Manage scheduled transactions |
| `query` | Run an ActualQL query |
| `server` | Server utilities and lookups |
Run `actual <command> --help` for subcommands and options.
### Examples
```bash
# List all accounts (as a table; excludes closed by default)
actual accounts list [--include-closed] --format table
# Find an entity ID by name
actual server get-id --type accounts --name "Checking"
# Add a transaction (amount in integer cents: -2500 = -$25.00)
actual transactions add --account <id> \
--data '[{"date":"2026-03-14","amount":-2500,"payee_name":"Coffee Shop"}]'
# Export transactions to CSV
actual transactions list --account <id> \
--start 2026-01-01 --end 2026-12-31 --format csv > transactions.csv
# Set budget amount ($500 = 50000 cents)
actual budgets set-amount --month 2026-03 --category <id> --amount 50000
# Run an ActualQL query
actual query run --table transactions \
--select "date,amount,payee" --filter '{"amount":{"$lt":0}}' --limit 10
```
### Amount Convention
All monetary amounts are **integer cents** when passed as input (flags, JSON):
| CLI Value | Dollar Amount |
| --------- | ------------- |
| `5000` | $50.00 |
| `-12350` | -$123.50 |
**Output formatting:** Table (`--format table`) and CSV (`--format csv`) output automatically converts cent values to decimal (e.g. `1665.00` instead of `166500`). JSON output always returns raw cents for programmatic use.
### Tips & Common Pitfalls
- **Split transactions:** When summing or counting transactions, filter `"is_parent": false` to avoid double-counting. A split parent holds the total amount, and its children hold the individual parts — including both would count the total twice.
- **Avoid rapid sequential requests:** Each CLI invocation opens a new server connection. Running queries in a tight loop (e.g. one per month) may trigger rate limiting or authentication failures. Instead, fetch all data in a single query with a date range filter and process locally:
```bash
# Good: single query for the full year
actual query run --table transactions \
--filter '{"$and":[{"date":{"$gte":"2025-01-01"}},{"date":{"$lte":"2025-12-31"}}]}' \
--limit 5000
# Bad: one query per month in a loop (may fail with auth errors)
for month in 01 02 03 ...; do actual query run ...; done
```
- **Uncategorized transactions:** `category.name` is `null` for transactions without a category. Account for this when filtering or grouping by category.
- **No date sub-fields in AQL:** `date.month`, `date.year`, etc. are not supported as query fields. To group by month, fetch raw transactions with a date range filter and aggregate locally in a script.
## Running Locally (Development)
If you're working on the CLI within the monorepo:
```bash
# 1. Build the CLI
yarn build:cli
# 2. Start a local sync server (in a separate terminal)
yarn start:server-dev
# 3. Open http://localhost:5006 in your browser, create a budget,
# then find the Sync ID in Settings → Advanced → Sync ID
# 4. Run the CLI directly from the build output
ACTUAL_SERVER_URL=http://localhost:5006 \
ACTUAL_PASSWORD=your-password \
ACTUAL_SYNC_ID=your-sync-id \
node packages/cli/dist/cli.js accounts list
# Or use a shorthand alias for convenience
alias actual-dev="node $(pwd)/packages/cli/dist/cli.js"
actual-dev budgets list
```

48
packages/cli/package.json Normal file
View File

@@ -0,0 +1,48 @@
{
"name": "@actual-app/cli",
"version": "26.5.0",
"description": "CLI for Actual Budget",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/actualbudget/actual.git",
"directory": "packages/cli"
},
"bin": {
"actual": "./dist/cli.js",
"actual-cli": "./dist/cli.js"
},
"files": [
"dist"
],
"type": "module",
"imports": {
"#commands/*": "./src/commands/*.ts",
"#config": "./src/config.ts",
"#connection": "./src/connection.ts",
"#input": "./src/input.ts",
"#output": "./src/output.ts",
"#utils": "./src/utils.ts"
},
"scripts": {
"build": "vite build",
"test": "vitest --run",
"typecheck": "tsgo -b"
},
"dependencies": {
"@actual-app/api": "workspace:*",
"cli-table3": "^0.6.5",
"commander": "^14.0.3",
"cosmiconfig": "^9.0.1"
},
"devDependencies": {
"@types/node": "^22.19.17",
"@typescript/native-preview": "beta",
"rollup-plugin-visualizer": "^7.0.1",
"vite": "^8.0.5",
"vitest": "^4.1.2"
},
"engines": {
"node": ">=22"
}
}

View File

@@ -0,0 +1,326 @@
import * as api from '@actual-app/api';
import { Command } from 'commander';
import { printOutput } from '#output';
import { registerAccountsCommand } from './accounts';
vi.mock('@actual-app/api', () => ({
getAccounts: vi.fn().mockResolvedValue([]),
createAccount: vi.fn().mockResolvedValue('new-id'),
updateAccount: vi.fn().mockResolvedValue(undefined),
closeAccount: vi.fn().mockResolvedValue(undefined),
reopenAccount: vi.fn().mockResolvedValue(undefined),
deleteAccount: vi.fn().mockResolvedValue(undefined),
getAccountBalance: vi.fn().mockResolvedValue(10000),
}));
vi.mock('#connection', () => ({
withConnection: vi.fn((_opts, fn) => fn()),
}));
vi.mock('#output', () => ({
printOutput: vi.fn(),
}));
function createProgram(): Command {
const program = new Command();
program.option('--format <format>');
program.option('--server-url <url>');
program.option('--password <pw>');
program.option('--session-token <token>');
program.option('--sync-id <id>');
program.option('--data-dir <dir>');
program.option('--verbose');
program.exitOverride();
registerAccountsCommand(program);
return program;
}
async function run(args: string[]) {
const program = createProgram();
await program.parseAsync(['node', 'test', ...args]);
}
describe('accounts commands', () => {
let stderrSpy: ReturnType<typeof vi.spyOn>;
let stdoutSpy: ReturnType<typeof vi.spyOn>;
beforeEach(() => {
vi.clearAllMocks();
stderrSpy = vi
.spyOn(process.stderr, 'write')
.mockImplementation(() => true);
stdoutSpy = vi
.spyOn(process.stdout, 'write')
.mockImplementation(() => true);
});
afterEach(() => {
stderrSpy.mockRestore();
stdoutSpy.mockRestore();
});
describe('list', () => {
it('calls api.getAccounts and prints result with computed balance', async () => {
const accounts = [
{ id: '1', name: 'Checking', offbudget: false, closed: false },
];
vi.mocked(api.getAccounts).mockResolvedValue(accounts);
await run(['accounts', 'list']);
expect(api.getAccounts).toHaveBeenCalled();
expect(api.getAccountBalance).toHaveBeenCalledWith('1');
expect(printOutput).toHaveBeenCalledWith(
[
{
id: '1',
name: 'Checking',
offbudget: false,
closed: false,
balance: 10000,
},
],
undefined,
);
});
it('passes format option to printOutput', async () => {
vi.mocked(api.getAccounts).mockResolvedValue([]);
await run(['--format', 'csv', 'accounts', 'list']);
expect(printOutput).toHaveBeenCalledWith([], 'csv');
});
it('filters out closed accounts by default', async () => {
vi.mocked(api.getAccounts).mockResolvedValue([
{ id: '1', name: 'Open', offbudget: false, closed: false },
{ id: '2', name: 'Closed', offbudget: false, closed: true },
]);
await run(['accounts', 'list']);
expect(printOutput).toHaveBeenCalledWith(
[
{
id: '1',
name: 'Open',
offbudget: false,
closed: false,
balance: 10000,
},
],
undefined,
);
});
it('includes closed accounts when --include-closed is passed', async () => {
vi.mocked(api.getAccounts).mockResolvedValue([
{ id: '1', name: 'Open', offbudget: false, closed: false },
{ id: '2', name: 'Closed', offbudget: false, closed: true },
]);
await run(['accounts', 'list', '--include-closed']);
expect(printOutput).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({ id: '2', closed: true }),
]),
undefined,
);
});
it('sorts on-budget accounts before off-budget', async () => {
vi.mocked(api.getAccounts).mockResolvedValue([
{ id: '1', name: 'OffBudget', offbudget: true, closed: false },
{ id: '2', name: 'OnBudget', offbudget: false, closed: false },
]);
await run(['accounts', 'list']);
const output = vi.mocked(printOutput).mock.calls[0][0] as Array<{
id: string;
}>;
expect(output[0].id).toBe('2'); // on-budget first
expect(output[1].id).toBe('1'); // off-budget second
});
});
describe('create', () => {
it('passes name and defaults to api.createAccount', async () => {
await run(['accounts', 'create', '--name', 'Savings']);
expect(api.createAccount).toHaveBeenCalledWith(
{ name: 'Savings', offbudget: false },
0,
);
expect(printOutput).toHaveBeenCalledWith({ id: 'new-id' }, undefined);
});
it('passes offbudget and balance options', async () => {
await run([
'accounts',
'create',
'--name',
'Investments',
'--offbudget',
'--balance',
'50000',
]);
expect(api.createAccount).toHaveBeenCalledWith(
{ name: 'Investments', offbudget: true },
50000,
);
});
});
describe('update', () => {
it('passes fields to api.updateAccount', async () => {
await run(['accounts', 'update', 'acct-1', '--name', 'NewName']);
expect(api.updateAccount).toHaveBeenCalledWith('acct-1', {
name: 'NewName',
});
expect(printOutput).toHaveBeenCalledWith(
{ success: true, id: 'acct-1' },
undefined,
);
});
it('passes offbudget true', async () => {
await run([
'accounts',
'update',
'acct-1',
'--name',
'X',
'--offbudget',
'true',
]);
expect(api.updateAccount).toHaveBeenCalledWith('acct-1', {
name: 'X',
offbudget: true,
});
});
it('passes offbudget false', async () => {
await run([
'accounts',
'update',
'acct-1',
'--name',
'X',
'--offbudget',
'false',
]);
expect(api.updateAccount).toHaveBeenCalledWith('acct-1', {
name: 'X',
offbudget: false,
});
});
it('rejects invalid offbudget value', async () => {
await expect(
run(['accounts', 'update', 'acct-1', '--offbudget', 'yes']),
).rejects.toThrow(
'Invalid --offbudget: "yes". Expected "true" or "false".',
);
});
it('rejects empty name', async () => {
await expect(
run(['accounts', 'update', 'acct-1', '--name', ' ']),
).rejects.toThrow('Invalid --name: must be a non-empty string.');
});
it('rejects update with no fields', async () => {
await expect(run(['accounts', 'update', 'acct-1'])).rejects.toThrow(
'No update fields provided. Use --name or --offbudget.',
);
});
});
describe('close', () => {
it('passes transfer options to api.closeAccount', async () => {
await run([
'accounts',
'close',
'acct-1',
'--transfer-account',
'acct-2',
]);
expect(api.closeAccount).toHaveBeenCalledWith(
'acct-1',
'acct-2',
undefined,
);
});
it('passes transfer category', async () => {
await run([
'accounts',
'close',
'acct-1',
'--transfer-category',
'cat-1',
]);
expect(api.closeAccount).toHaveBeenCalledWith(
'acct-1',
undefined,
'cat-1',
);
});
});
describe('reopen', () => {
it('calls api.reopenAccount', async () => {
await run(['accounts', 'reopen', 'acct-1']);
expect(api.reopenAccount).toHaveBeenCalledWith('acct-1');
expect(printOutput).toHaveBeenCalledWith(
{ success: true, id: 'acct-1' },
undefined,
);
});
});
describe('delete', () => {
it('calls api.deleteAccount', async () => {
await run(['accounts', 'delete', 'acct-1']);
expect(api.deleteAccount).toHaveBeenCalledWith('acct-1');
expect(printOutput).toHaveBeenCalledWith(
{ success: true, id: 'acct-1' },
undefined,
);
});
});
describe('balance', () => {
it('calls api.getAccountBalance without cutoff', async () => {
await run(['accounts', 'balance', 'acct-1']);
expect(api.getAccountBalance).toHaveBeenCalledWith('acct-1', undefined);
expect(printOutput).toHaveBeenCalledWith(
{ id: 'acct-1', balance: 10000 },
undefined,
);
});
it('calls api.getAccountBalance with cutoff date', async () => {
await run(['accounts', 'balance', 'acct-1', '--cutoff', '2025-01-15']);
expect(api.getAccountBalance).toHaveBeenCalledWith(
'acct-1',
new Date('2025-01-15'),
);
});
});
});

Some files were not shown because too many files have changed in this diff Show More