Compare commits

...

60 Commits

Author SHA1 Message Date
autofix-ci[bot]
74c27ea698 [autofix.ci] apply automated fixes 2025-09-24 12:11:37 +00:00
coderabbitai[bot]
71d8b0be45 📝 Add docstrings to feat/plugins/plugins-core-package
Docstrings generation was requested by @lelemm.

* https://github.com/actualbudget/actual/pull/5786#issuecomment-3328077622

The following files were modified:

* `packages/plugins-core/src/BasicModalComponents.tsx`
* `packages/plugins-core/src/middleware.ts`
* `packages/plugins-core/src/query/index.ts`
* `packages/plugins-core/src/utils.ts`
2025-09-24 12:07:22 +00:00
github-actions[bot]
0c070e5703 Add release notes for PR #5786 2025-09-24 12:03:28 +00:00
autofix-ci[bot]
c6c1fa686e [autofix.ci] apply automated fixes 2025-09-24 12:02:26 +00:00
lelemm
791a2e63f8 Initial commit for plugins-core 2025-09-24 08:52:46 -03:00
lelemm
81160256bc Frontend plugins Support [1/10]: CORS proxy (#5780)
* Frontend plugins Support [1/10]: Cors proxy

* Add release notes for PR #5780

* changed code as CodeQL suggested

* CodeQL improvement for ip validation to bypass dns changes

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* Code Rabbit suggestion

* Update packages/sync-server/src/app-cors-proxy.js

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

* Added env var for cors proxy

* multiple changes

* missed updating yarn.lock

* making code rabbit happy

* Tests

* linter

* Code Rabbit changes

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

* [autofix.ci] apply automated fixes

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
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>
2025-09-24 08:13:24 -03:00
Joel Jeremy Marquez
ca5378c0e8 Optimize scroll provider and replace usage of debounce package with lodash's debounce (#5750)
* Optimize scroll provider and replace usage of debounce package with lodash's debounce

* Coderabbit suggestions
2025-09-23 15:59:55 -07:00
youngcw
08b5b7fdc7 add limit template type (#5775)
* add limit template

* use null to match other tempaltes

* add in directive

* handle no limit preset

* limit periods aren't optional

* make start field the right type

* fix test

* fix looping

* remove unneeded comment
2025-09-23 14:34:36 -07:00
Matiss Janis Aboltins
67c0b6911b Mobile payees: click handlers (#5776) 2025-09-23 22:18:37 +01:00
Matt Fiddaman
4e9e153989 ensure file upload size limits are respected when syncing files (#5779) 2025-09-23 18:06:45 +01:00
Matiss Janis Aboltins
b0321ee265 refactor: update table and import transactions modal components (#5770) 2025-09-23 16:49:03 +01:00
milanalexandre
753a105b3d [WIP] [FIX] Translate Schedule '(No payee)' (#5777) 2025-09-22 22:56:41 +01:00
Matiss Janis Aboltins
5a888d44b9 Create mobile payees list page (#5767) 2025-09-22 18:56:57 +01:00
thromer
7a4799de94 Avoid repeated calls to payee.find in useDisplayPayee.ts (#5761) 2025-09-22 16:26:14 +01:00
Amr Awad
4ad369cd8f add a 'dryRun' option to transactions import API (#5758) 2025-09-22 16:24:35 +01:00
Matt Fiddaman
2c9a66cec6 API quiet mode (#5762)
* initial implementation of api quiet mode

* note

* lint rule

* rollout logger to loot-core

* add group methods

* windows paths

* make fixer more robust

* appease linter

* add debug

* change option name quiet -> verbose
2025-09-22 12:54:08 +01:00
Matiss Janis Aboltins
6e96b81799 fix: add minimum width to value editor in ConditionEditor component (#5766) 2025-09-22 12:04:43 +01:00
milanalexandre
f89d4fd13d [FIX] Translate 'Off budget' and 'Transfer' (#5765)
* translate transaction edit modal (mobile)

* add relese note

* fix warn

---------

Co-authored-by: Alex <Alex>
2025-09-22 09:42:03 +01:00
Matt Fiddaman
cc0812113a improve cleanup when opening multiple budgets through the API (#5760) 2025-09-21 17:47:27 +01:00
Matt Fiddaman
59724d445f fix nuisance timestamp error in API (#5759) 2025-09-21 17:47:10 +01:00
Çağdaş Şenel
6b99497d5d feat(reports): extend report end date to the latest transaction date (#5753)
* add get-latest-transaction function to extend end dates beyond today

* add release notes

* yarn lint

* fix dateEnd and lint warnings

* change getFullRange to return all months

* Update upcoming-release-notes/5753.md

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

---------

Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2025-09-21 13:28:08 +01:00
Matt Fiddaman
5f5457b226 fix scrolling inside modal with an input that autofocuses (#5752) 2025-09-20 20:03:01 +01:00
dependabot[bot]
4bdcb27573 Bump @eslint/plugin-kit from 0.3.1 to 0.3.5 (#5749)
Bumps [@eslint/plugin-kit](https://github.com/eslint/rewrite/tree/HEAD/packages/plugin-kit) from 0.3.1 to 0.3.5.
- [Release notes](https://github.com/eslint/rewrite/releases)
- [Changelog](https://github.com/eslint/rewrite/blob/main/packages/plugin-kit/CHANGELOG.md)
- [Commits](https://github.com/eslint/rewrite/commits/plugin-kit-v0.3.5/packages/plugin-kit)

---
updated-dependencies:
- dependency-name: "@eslint/plugin-kit"
  dependency-version: 0.3.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-20 11:47:11 +01:00
Joel Jeremy Marquez
8ae070ab12 React Compiler (#5562)
* Highlight first suggestion when searching instead of the split option

* Fix error

* Try out react compiler

* Fix typecheck errors

* Increase --max-old-space-size

* Increate --max-old-space-size in package-browser

* Update config and versions

* [autofix.ci] apply automated fixes

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

* Update versions

* Fix typecheck errors

* Revert some changes

* Implement react compiler to take advantage of some performance improvements

* Fixes

* Remove unused packages

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-09-19 14:28:16 -07:00
dependabot[bot]
0ca5bec094 Bump axios from 1.8.3 to 1.12.1 (#5719)
Bumps [axios](https://github.com/axios/axios) from 1.8.3 to 1.12.1.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.8.3...v1.12.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-19 17:32:06 +01:00
dirk-apers
988bc21818 enhancement: add BPER Italy bank parser (BPER_RETAIL_BPMOIT22) (#5741)
* Refine BPER retail parser with anonymized fixtures

* style: streamline bper parser comments

* chore: add release note for BPER Italy parser
2025-09-19 17:31:04 +01:00
Stephen Brown II
f4419b96de Dynamic currency display options (#5744) 2025-09-19 17:06:21 +01:00
passabilities.eth
e30a38ced8 [Feature] Account Net Worth Graph (#5037)
* show net worth graph for each account page

* add release notes

* hide filter button

* fix lint

* import ReactNode type

* find selected accounts based on accountId param

* remove breaks

* can toggle account page net worth graph

* Improves account balance history graph
Refactors the balance history graph to improve its data fetching and rendering.

Removes the `selectedAccounts` state and related logic from the Account component, simplifying its state management. The `accountId` is now passed directly to the BalanceHistoryGraph.

Moves the BalanceHistoryGraph component to the accounts directory and removes the redundant BalanceHistoryGraph in the sidebar.

Updates the queries to directly use the accountId for filtering transactions, leading to more efficient data retrieval.

* mv

* min width and height only for sidebar

* move toggle chart button to account menu

* add fill gradiant to chart

* fix maxWidth

* fix chart hover offset

* responsive graph aspect ratio

* auto hide chart on short view

* revert balance position

* auto hide chart on 15% height

* remove chart boolean from internal state

* Implement account-specific net worth chart preferences

Replaced global net worth chart visibility preference with account-specific preferences. Updated components to use the new dynamic preference keys and added a toggle option in the account menus for better user customization.

* lint

* fix account groups

* Update VRT

* bump

---------

Co-authored-by: youngcw <calebyoung94@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-09-18 13:32:21 -07:00
Michael Süssemilch
98b91cfb8d fix: make sure cumulative value can not be undefined (#5738)
* fix: make sure cumulative value can not be undefined, which results in NaN

* doc: add release notes
2025-09-18 08:20:57 -07:00
Josh Woodward
942d3ea4d5 Schedules with the same amount and date are now handled properly (#5414)
* minor typescript updates

* [autofix.ci] apply automated fixes

* more typescript changes

* [autofix.ci] apply automated fixes

* Another typescript

* fixes

* [autofix.ci] apply automated fixes

* fixed test

* [autofix.ci] apply automated fixes

* renaming a few things

* [autofix.ci] apply automated fixes

* a test

* test 2

* [autofix.ci] apply automated fixes

* test 3

* attempting to add a new test

* [autofix.ci] apply automated fixes

* test update

* additional typing.

* [autofix.ci] apply automated fixes

* Testing first item again

* [autofix.ci] apply automated fixes

* temporarily adding logging

* [autofix.ci] apply automated fixes

* only running rules within a schedule if exists

* swapping if

* removing unused import

* [autofix.ci] apply automated fixes

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

* fixing typecheck

* updated test

* [autofix.ci] apply automated fixes

* Updated screenshots

* further test fixing

* changing test order

* [autofix.ci] apply automated fixes

* Almost there

* new images

* tests may be flaky

* just one image

* 🙏

* 🙏 🙏

* removing all images

* Revert "removing all images"

This reverts commit 4492cb7080.

* small order change

* [autofix.ci] apply automated fixes

* Update VRT

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

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

* reverting unrelated changes

* [autofix.ci] apply automated fixes

* Reverting one other typescript change

* testing completing 1 schedule

* Update VRT

* release wrote rewrite

* Making sure rule application is saved

* Cleaning up get function

* prettier and null catch

* Removed now unused schedule type

* Better falling back if rule / schedule no longer exists

* linting

* Better typing for db request

* camel case to make code rabbit happier

* slightly more accurate comment

* [autofix.ci] apply automated fixes

* Running other rules when linked rule runs

* lint / prettier

* one more lint

* Update 5414.md

---------

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: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-09-16 20:12:31 -07:00
Matiss Janis Aboltins
3c9b70df79 Integrate responsive design for RecurringSchedulePicker component (#5733) 2025-09-16 21:33:30 +01:00
milanalexandre
5c18b53888 [fix] make the StatusLabel text appear on a single line (#5736)
* make the StatusLabel text appear on a single line

* add relese note

---------

Co-authored-by: Alex <Alex>
2025-09-16 13:30:39 -07:00
mgibson-scottlogic
413398531c Fix copy last month's budget including hidden categories (#5735)
* Add check that category is not hidden before copying last months budget

* Add release notes

* Add check that group is also not hidden

---------

Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2025-09-16 11:21:50 -07:00
jgeneaguilar
e4c3d4e12a Fix AccountMenu popover in Italian (#5734) 2025-09-16 16:54:35 +01:00
Michael Clark
91b838c539 🔔 Add setting to disable update notifications (#5725)
* add setting to disable update notifications

* release notes

* fix dependency array

* Update packages/loot-core/src/server/preferences/app.ts

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

* moving latest version info into app state to prevent needless calls to external api

* fix release note

* Update VRT

---------

Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-09-15 09:12:24 +01:00
Joel Jeremy Marquez
9eb0e04c6a [Maintenance] Remove raw variables in render (#5718)
* Remove usage of raw variables in renders

* Release notes

* Update status

* Fix typecheck error
2025-09-12 15:26:09 -07:00
Joel Jeremy Marquez
14bf3d611c [Maintenance] Remove usage of a raw variable in CategoryAutocomplete component (#5686)
* Remove usage of a raw variable in CategoryAutocomplete component

* Cleanup

* Fix typecheck error

* Fix typecheck error

* Highlight first suggestion when searching instead of the split option

* Fix error

* Fix special item highligted index
2025-09-12 15:25:52 -07:00
Joel Jeremy Marquez
34b6599da3 Re-design mobile accounts page to better match the transactions and budget tables/list + add all accounts page (#5610)
* Re-design mobile accounts page to better match the transactions and budget tables/list + add all accounts page

* [autofix.ci] apply automated fixes

* Update to "All accounts" to match desktop text

* Update prop

* Update VRT

* Increase height

* Update VRT

* Update UI

* Update VRT

---------

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: Julian Dominguez-Schatz <julian.dominguezschatz@gmail.com>
2025-09-11 09:17:07 -07:00
Evan Smith
bc1cd9023c Remove auto-scrolling behavior when editing split transactions on mobile (#5572)
* Remove behavior to auto scroll to transaction with 0 amount

* Add release notes
2025-09-11 16:26:21 +01:00
dependabot[bot]
5ae9176f5e Bump vite from 6.3.5 to 6.3.6 (#5707)
* Bump vite from 6.3.5 to 6.3.6

Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.3.5 to 6.3.6.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.3.6/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.3.6/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.3.6
  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>
2025-09-11 16:19:48 +01:00
Joel Jeremy Marquez
2ed908aff4 Remove usage of a raw variable in Account component (#5684) 2025-09-11 08:13:24 -07:00
Matt Fiddaman
3318dd56e9 remove BANKS_WITH_LIMITED_HISTORY override array for GoCardless (#5714)
* remove banks with limited history gocardless array

* note

* [autofix.ci] apply automated fixes

* set 89 in fallback

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-09-11 15:48:43 +01:00
Michael Clark
00ab11cc40 :electron: Update Electron to v38 (#5713)
* update electron to 38

* release notes
2025-09-11 15:40:40 +01:00
youngcw
25c83eb64d Add bank sync option to only import balance (#5711)
* add investment account setting

* note

* disable other options if enabled

* handle 0 balance

* Update packages/loot-core/src/server/accounts/sync.ts

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

* [autofix.ci] apply automated fixes

* dont exit early

---------

Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-09-11 07:15:57 -07:00
Mauro Artizzu
7a420b79f2 Handle detailedAccount null or undefined in gocardless service. Fixes #5664 (#5706)
* fix detailedAccount could be null or undefined

* add release notes
2025-09-11 14:45:43 +01:00
Nalin Gupta
d2cfedf5e4 Fixes #5674 mark transfer issue (#5696)
* Fixes #5674 mark transfer issue

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-09-09 20:32:55 -07:00
Shalan
00a4cfcabf feat: add EGP and SAR currencies to supported list (#5698)
* feat: add EGP and SAR currencies to supported list

* add release notes for currency support

* use yarn generate:release-notes to fix release notes

* Rename file with PR number 5698
2025-09-08 08:04:55 -07:00
Karim Kodera
a18a05f55a Introduction of APIs to handle Schedule + a bit more. (#5584)
* Introduction of APIs to handle Schedule + a bit more. Refer to updated API documentation PR2811 on documentation.

* Fixed lint Error

* Removed unused declarations

* Fixed Bug in Test module

* Avoiding type Coercion fixes and direct assignment on conditions array.

* Lint Error Fixes

* more issues fixed

* lint errors

* Remove with Mutation in Get function. Fixed quotes. updated getIDyName for error handling

* More lint errors

* Minor final fixes.

* One type coercion removed

* One type coercion removed

* Revert back to original working code

* Added Account testing for both get by ID and updating schedules

* [autofix.ci] apply automated fixes

* Added Payee tests as well

* Payee Tests

* [autofix.ci] apply automated fixes

* Optimized condition checking at the beginning of the code and avoid ambiguity in case of corrupt schedule

* Mode debug infor on error in testing

* better bug tracking

* //more trouble shooting

* Bug fixed

* [autofix.ci] apply automated fixes

* Minor mofication to satisfy code rabbit. We should be ready for review.

* [autofix.ci] apply automated fixes

* Removing type coercion from the model

* [autofix.ci] apply automated fixes

* fixed compilation error

* Fixed new bugs

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-09-08 09:03:11 +01:00
Matiss Janis Aboltins
b399f290a6 Add ErrorBoundary around Modals and updating FatalError modal behavior. (#5649)
Fix #4703
2025-09-07 19:14:31 +01:00
Matiss Janis Aboltins
7c07295448 Issues: add issue types (#5648) 2025-09-07 19:13:15 +01:00
Michael Clark
510dd31de6 🔧 Fix heap allocation error when building locally with docker (#5695)
* fix heap allocation error when building locally with docker

* release notes

* clarifying comment
2025-09-07 13:32:40 +01:00
Michael Sanford
8e5a88bc55 Add NO_COLOR standard environment flag to sync-server logging. (#5676)
* Add NO_COLOR standard environment flag to sync-server logging.

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-09-05 15:31:12 -07:00
Joel Jeremy Marquez
bbf91ccbca [Maintenance] Remove usage of a raw variable in PayeeAutocomplete component (#5687)
* Remove usage of a raw variable in PayeeAutocomplete component

* Add note on 100 payees limit
2025-09-05 12:26:44 -07:00
Joel Jeremy Marquez
58bc14e1b3 Optimize usage of useScrollListener and useTransactionsSearch (#5690) 2025-09-05 12:26:05 -07:00
Joel Jeremy Marquez
de2966a06c [Maintenance] Remove usage of a raw variable in AccountAutocomplete component (#5685)
* Remove usage of a raw variable in AccountAutocomplete component

* Remove usage of a raw variable in CategoryAutocomplete component

* Release notes

* Fix typecheck error

* Fix typecheck errors

* Remove CategoryAutocomplete changes
2025-09-05 11:21:59 -07:00
Michael Süssemilch
90b859fd74 feat(currency): add BRL, JMD, RSD, RUB, THB and UAH (#5653)
* feat: add BRL, JMD and THB currencies

* feat: add RSD, RUB and UAH
2025-09-05 10:57:29 -07:00
biolan
fafcee071d Add Romanian and Moldovan currency (#5688)
* * add Romanian Leu currency

* add Moldovan Leu currency

* Add release note

* * Add release note

* * one line release note

---------

Co-authored-by: biolan <admin@biolan.dev>
2025-09-05 07:05:56 -07:00
Bernardo Jordão
ed40901534 Fix range calculator on the MonthPicker component (#5622)
* fix month range

* release notes

* use floor() to match e2e tests
2025-09-05 06:41:23 -07:00
Julian Dominguez-Schatz
338093836b Add tools to migrate/un-migrate to/from UI automations (#5624)
* Add helper to render automations to template notes

* Add modal to un-migrate from the automations UI

* Add import warning to automations modal

* Add release notes

* Use CSSProperties type from React directly
2025-09-05 07:32:17 -04:00
Julian Dominguez-Schatz
4df05aa37c Fix version bump logic to work if the month has rolled over (#5662)
* Fix version bump logic to work if the month has rolled over

* Refactor script to be more testable

* Add tests for regression

* Move tests to dedicated package

* Add release notes

* Coderabbit
2025-09-05 07:31:53 -04:00
331 changed files with 10747 additions and 1569 deletions

View File

@@ -13,6 +13,7 @@ Code Style and Structure
- Prefer iteration and modularization over code duplication.
- Use descriptive variable names with auxiliary verbs (e.g., isLoaded, hasError).
- Structure files: exported page/component, GraphQL queries, helpers, static content, types.
- When creating a new component, place it in its own file rather than grouping multiple components in a single file.
Naming Conventions

View File

@@ -2,6 +2,7 @@ name: Bug Report
description: File a bug report also known as an issue or problem.
title: '[Bug]: '
labels: ['needs triage', 'bug']
type: Bug
body:
- type: markdown
attributes:

View File

@@ -2,6 +2,7 @@ name: Feature request
description: Request a missing feature
title: '[Feature] '
labels: ['feature']
type: Feature
body:
- type: markdown
attributes:

View File

@@ -1,117 +0,0 @@
#!/usr/bin/env node
// 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.
const { parseArgs } = require('node:util');
const fs = require('node:fs');
const args = process.argv;
const options = {
'package-json': {
type: 'string',
short: 'p',
},
type: {
type: 'string', // nightly, hotfix, monthly, auto
short: 't',
},
update: {
type: 'boolean',
short: 'u',
default: false,
},
};
const { values } = parseArgs({
args,
options,
allowPositionals: true,
});
if (!values['package-json']) {
console.error(
'Please specify the path to package.json using --package-json or -p option.',
);
process.exit(1);
}
try {
const packageJsonPath = values['package-json'];
// Read and parse package.json
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
const currentVersion = packageJson.version;
// Parse year and month from version (e.g. 25.5.1 -> year=2025, month=5)
const versionParts = currentVersion.split('.');
const versionYear = parseInt(versionParts[0]);
const versionMonth = parseInt(versionParts[1]);
const versionHotfix = parseInt(versionParts[2]);
// Create date and add 1 month
const versionDate = new Date(2000 + versionYear, versionMonth - 1, 1); // month is 0-indexed
const nextVersionMonthDate = new Date(
versionDate.getFullYear(),
versionDate.getMonth() + 1,
1,
);
// Format back to YY.M format
const nextVersionYear = nextVersionMonthDate
.getFullYear()
.toString()
.slice(nextVersionMonthDate.getFullYear() < 2100 ? -2 : -3);
const nextVersionMonth = nextVersionMonthDate.getMonth() + 1; // Convert back to 1-indexed
// Get current date string
const currentDate = new Date();
const currentDateString = currentDate
.toISOString()
.split('T')[0]
.replaceAll('-', '');
if (values.type === 'auto') {
if (currentDate.getDate() <= 25) {
values.type = 'hotfix';
} else {
values.type = 'monthly';
}
}
let newVersion;
switch (values.type) {
case 'nightly': {
newVersion = `${nextVersionYear}.${nextVersionMonth}.0-nightly.${currentDateString}`;
break;
}
case 'hotfix': {
newVersion = `${versionYear}.${versionMonth}.${versionHotfix + 1}`;
break;
}
case 'monthly': {
newVersion = `${nextVersionYear}.${nextVersionMonth}.0`;
break;
}
default:
console.error(
'Invalid type specified. Use "auto", "nightly", "hotfix", or "monthly".',
);
process.exit(1);
}
process.stdout.write(newVersion); // return the new version to stdout
if (values.update) {
packageJson.version = newVersion;
fs.writeFileSync(
packageJsonPath,
JSON.stringify(packageJson, null, 2) + '\n',
'utf8',
);
}
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}

View File

@@ -50,6 +50,22 @@ jobs:
name: actual-crdt
path: packages/crdt/actual-crdt.tgz
plugins-core:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up environment
uses: ./.github/actions/setup
- name: Build Plugins Core
run: yarn workspace @actual-app/plugins-core build
- name: Create package tgz
run: cd packages/plugins-core && yarn pack && mv package.tgz actual-plugins-core.tgz
- name: Upload Build
uses: actions/upload-artifact@v4
with:
name: actual-plugins-core
path: packages/plugins-core/actual-plugins-core.tgz
web:
runs-on: ubuntu-latest
steps:

View File

@@ -37,7 +37,7 @@ jobs:
if [[ -n "${{ github.event.inputs.version }}" ]]; then
version="${{ github.event.inputs.version }}"
else
version=$(node ./.github/actions/get-next-package-version.js \
version=$(node ./packages/ci-actions/bin/get-next-package-version.js \
--package-json "./packages/$pkg/package.json" \
--type auto \
--update)

View File

@@ -20,9 +20,9 @@ jobs:
- name: Update package versions
run: |
# Get new nightly versions
NEW_WEB_VERSION=$(node ./.github/actions/get-next-package-version.js --package-json ./packages/desktop-client/package.json --type nightly)
NEW_SYNC_VERSION=$(node ./.github/actions/get-next-package-version.js --package-json ./packages/sync-server/package.json --type nightly)
NEW_API_VERSION=$(node ./.github/actions/get-next-package-version.js --package-json ./packages/api/package.json --type nightly)
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

View File

@@ -14,6 +14,8 @@ git pull
popd > /dev/null
packages/desktop-client/bin/remove-untranslated-languages
export NODE_OPTIONS="--max-old-space-size=4096"
yarn workspace loot-core build:browser
yarn workspace @actual-app/web build:browser

View File

@@ -39,6 +39,8 @@ git pull
popd > /dev/null
packages/desktop-client/bin/remove-untranslated-languages
export NODE_OPTIONS="--max-old-space-size=4096"
yarn workspace loot-core build:node
yarn workspace @actual-app/web build --mode=desktop # electron specific build

View File

@@ -98,6 +98,8 @@ export default pluginTypescript.config(
'packages/loot-core/**/lib-dist/*',
'packages/loot-core/**/proto/*',
'packages/sync-server/build/',
'packages/plugins-core/build/',
'packages/plugins-core/node_modules/',
'.yarn/*',
'.github/*',
],
@@ -154,9 +156,6 @@ export default pluginTypescript.config(
{
plugins: {
actual: pluginActual,
'react-hooks': pluginReactHooks,
'jsx-a11y': pluginJSXA11y,
'typescript-paths': pluginTypescriptPaths,
},
rules: {
'actual/no-untranslated-strings': 'error',
@@ -165,6 +164,10 @@ export default pluginTypescript.config(
},
{
files: ['**/*.{js,ts,jsx,tsx}'],
plugins: {
'jsx-a11y': pluginJSXA11y,
'react-hooks': pluginReactHooks,
},
rules: {
// http://eslint.org/docs/rules/
'array-callback-return': 'warn',
@@ -450,6 +453,7 @@ export default pluginTypescript.config(
'actual/typography': 'warn',
'actual/prefer-if-statement': 'warn',
'actual/prefer-logger-over-console': 'error',
// Note: base rule explicitly disabled in favor of the TS one
'no-unused-vars': 'off',
@@ -630,6 +634,9 @@ export default pluginTypescript.config(
},
{
files: ['packages/desktop-client/**/*.{js,ts,jsx,tsx}'],
plugins: {
'typescript-paths': pluginTypescriptPaths,
},
rules: {
'typescript-paths/absolute-parent-import': [
'error',
@@ -771,6 +778,7 @@ export default pluginTypescript.config(
rules: {
'actual/typography': 'off',
'actual/no-untranslated-strings': 'off',
'actual/prefer-logger-over-console': 'off',
},
},
{

View File

@@ -57,15 +57,15 @@
"@octokit/rest": "^22.0.0",
"@types/node": "^22.17.0",
"@types/prompts": "^2.4.9",
"@typescript-eslint/parser": "^8.32.1",
"@typescript-eslint/parser": "^8.42.0",
"cross-env": "^7.0.3",
"eslint": "^9.27.0",
"eslint": "^9.34.0",
"eslint-config-prettier": "^10.1.5",
"eslint-import-resolver-typescript": "^4.3.5",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-hooks": "^6.0.0-rc.2",
"eslint-plugin-typescript-paths": "^0.0.33",
"globals": "^15.15.0",
"html-to-image": "^1.11.13",
@@ -80,7 +80,7 @@
"source-map-support": "^0.5.21",
"ts-node": "^10.9.2",
"typescript": "^5.9.2",
"typescript-eslint": "^8.32.1",
"typescript-eslint": "^8.42.0",
"typescript-strict-plugin": "^2.4.4"
},
"resolutions": {
@@ -99,7 +99,7 @@
},
"packageManager": "yarn@4.9.1",
"browserslist": [
"electron 24.0",
"electron >= 35.0",
"defaults"
]
}

View File

@@ -42,7 +42,11 @@ export async function init(config: InitConfig = {}) {
export async function shutdown() {
if (actualApp) {
await actualApp.send('sync');
try {
await actualApp.send('sync');
} catch (e) {
// most likely that no budget is loaded, so the sync failed
}
await actualApp.send('close-budget');
actualApp = null;
}

View File

@@ -740,3 +740,122 @@ describe('API CRUD operations', () => {
expect(transactions[0].notes).toBeNull();
});
});
//apis: createSchedule, getSchedules, updateSchedule, deleteSchedule
test('Schedules: successfully complete schedules operations', async () => {
await api.loadBudget(budgetName);
//test a schedule with a recuring configuration
const ScheduleId1 = await api.createSchedule({
name: 'test-schedule 1',
posts_transaction: true,
// amount: -5000,
amountOp: 'is',
date: {
frequency: 'monthly',
interval: 1,
start: '2025-06-13',
patterns: [],
skipWeekend: false,
weekendSolveMode: 'after',
endMode: 'never',
},
});
//test the creation of non recurring schedule
const ScheduleId2 = await api.createSchedule({
name: 'test-schedule 2',
posts_transaction: false,
amount: 4000,
amountOp: 'is',
date: '2025-06-13',
});
let schedules = await api.getSchedules();
// Schedules successfully created
expect(schedules).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: 'test-schedule 1',
posts_transaction: true,
// amount: -5000,
amountOp: 'is',
date: {
frequency: 'monthly',
interval: 1,
start: '2025-06-13',
patterns: [],
skipWeekend: false,
weekendSolveMode: 'after',
endMode: 'never',
},
}),
expect.objectContaining({
name: 'test-schedule 2',
posts_transaction: false,
amount: 4000,
amountOp: 'is',
date: '2025-06-13',
}),
]),
);
//check getIDByName works on schedules
expect(await api.getIDByName('schedules', 'test-schedule 1')).toEqual(
ScheduleId1,
);
expect(await api.getIDByName('schedules', 'test-schedule 2')).toEqual(
ScheduleId2,
);
//check getIDByName works on accounts
const schedAccountId1 = await api.createAccount(
{ name: 'sched-test-account1', offbudget: true },
1000,
);
expect(await api.getIDByName('accounts', 'sched-test-account1')).toEqual(
schedAccountId1,
);
//check getIDByName works on payees
const schedPayeeId1 = await api.createPayee({ name: 'sched-test-payee1' });
expect(await api.getIDByName('payees', 'sched-test-payee1')).toEqual(
schedPayeeId1,
);
await api.updateSchedule(ScheduleId1, {
amount: -10000,
account: schedAccountId1,
});
await api.deleteSchedule(ScheduleId2);
// schedules successfully updated, and one of them deleted
await api.updateSchedule(ScheduleId1, {
amount: -10000,
account: schedAccountId1,
payee: schedPayeeId1,
});
await api.deleteSchedule(ScheduleId2);
schedules = await api.getSchedules();
expect(schedules).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: ScheduleId1,
posts_transaction: true,
amount: -10000,
account: schedAccountId1,
payee: schedPayeeId1,
amountOp: 'is',
date: {
frequency: 'monthly',
interval: 1,
start: '2025-06-13',
patterns: [],
skipWeekend: false,
weekendSolveMode: 'after',
endMode: 'never',
},
}),
expect.not.objectContaining({ id: ScheduleId2 }),
]),
);
});

View File

@@ -96,6 +96,7 @@ export function addTransactions(
export interface ImportTransactionsOpts {
defaultCleared?: boolean;
dryRun?: boolean;
}
export function importTransactions(
@@ -103,11 +104,13 @@ export function importTransactions(
transactions: ImportTransactionEntity[],
opts: ImportTransactionsOpts = {
defaultCleared: true,
dryRun: false,
},
) {
return send('api/transactions-import', {
accountId,
transactions,
isPreview: opts.dryRun,
opts,
});
}
@@ -239,3 +242,31 @@ export function holdBudgetForNextMonth(month, amount) {
export function resetBudgetHold(month) {
return send('api/budget-reset-hold', { month });
}
export function createSchedule(schedule) {
return send('api/schedule-create', schedule);
}
export function updateSchedule(id, fields, resetNextDate?: boolean) {
return send('api/schedule-update', {
id,
fields,
resetNextDate,
});
}
export function deleteSchedule(scheduleId) {
return send('api/schedule-delete', scheduleId);
}
export function getSchedules() {
return send('api/schedules-get');
}
export function getIDByName(type, name) {
return send('api/get-id-by-name', { type, name });
}
export function getServerVersion() {
return send('api/get-server-version');
}

View File

@@ -0,0 +1,73 @@
#!/usr/bin/env node
// 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';
// eslint-disable-next-line import/extensions
import { getNextVersion } from '../src/versions/get-next-package-version.js';
const args = process.argv;
const options = {
'package-json': {
type: 'string',
short: 'p',
},
type: {
type: 'string', // nightly, hotfix, monthly, auto
short: 't',
},
update: {
type: 'boolean',
short: 'u',
default: false,
},
};
const { values } = parseArgs({
args,
options,
allowPositionals: true,
});
if (!values['package-json']) {
console.error(
'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'));
const currentVersion = packageJson.version;
let newVersion;
try {
newVersion = getNextVersion({
currentVersion,
type: values.type,
currentDate: new Date(),
});
} catch (e) {
console.error(e.message);
process.exit(1);
}
process.stdout.write(newVersion);
if (values.update) {
packageJson.version = newVersion;
fs.writeFileSync(
packageJsonPath,
JSON.stringify(packageJson, null, 2) + '\n',
'utf8',
);
}
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}

View File

@@ -0,0 +1,11 @@
{
"name": "@actual-app/ci-actions",
"private": true,
"type": "module",
"devDependencies": {
"vitest": "^3.2.4"
},
"scripts": {
"test": "vitest"
}
}

View File

@@ -0,0 +1,72 @@
function parseVersion(version) {
const [y, m, p] = version.split('.');
return {
versionYear: parseInt(y, 10),
versionMonth: parseInt(m, 10),
versionHotfix: 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
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
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;
const inPatchMonth =
currentDate.getFullYear() === 2000 + versionYear &&
currentDate.getMonth() + 1 === versionMonth;
if (inPatchMonth && currentDate.getDate() <= 25) return 'hotfix';
return 'monthly';
}
export function getNextVersion({
currentVersion,
type,
currentDate = new Date(),
}) {
const { versionYear, versionMonth, versionHotfix } =
parseVersion(currentVersion);
const { nextVersionYear, nextVersionMonth } = computeNextMonth(
versionYear,
versionMonth,
);
const resolvedType = resolveType(
type,
currentDate,
versionYear,
versionMonth,
);
// Format date stamp once for nightly
const currentDateString = currentDate
.toISOString()
.split('T')[0]
.replaceAll('-', '');
switch (resolvedType) {
case 'nightly':
return `${nextVersionYear}.${nextVersionMonth}.0-nightly.${currentDateString}`;
case 'hotfix':
return `${versionYear}.${versionMonth}.${versionHotfix + 1}`;
case 'monthly':
return `${nextVersionYear}.${nextVersionMonth}.0`;
default:
throw new Error(
'Invalid type specified. Use “auto”, “nightly”, “hotfix”, or “monthly”.',
);
}
}

View File

@@ -0,0 +1,85 @@
import { describe, it, expect } from 'vitest';
import { getNextVersion } from './get-next-package-version';
describe('getNextVersion (lib)', () => {
it('hotfix increments patch', () => {
expect(
getNextVersion({
currentVersion: '25.8.1',
type: 'hotfix',
currentDate: new Date('2025-08-10'),
}),
).toBe('25.8.2');
});
it('monthly advances month same year', () => {
expect(
getNextVersion({
currentVersion: '25.8.3',
type: 'monthly',
currentDate: new Date('2025-08-15'),
}),
).toBe('25.9.0');
});
it('monthly wraps year December -> January', () => {
expect(
getNextVersion({
currentVersion: '25.12.3',
type: 'monthly',
currentDate: new Date('2025-12-05'),
}),
).toBe('26.1.0');
});
it('nightly format with date stamp', () => {
expect(
getNextVersion({
currentVersion: '25.8.1',
type: 'nightly',
currentDate: new Date('2025-08-22'),
}),
).toBe('25.9.0-nightly.20250822');
});
it('auto before 25th -> hotfix', () => {
expect(
getNextVersion({
currentVersion: '25.8.4',
type: 'auto',
currentDate: new Date('2025-08-20'),
}),
).toBe('25.8.5');
});
it('auto after 25th (same month) -> monthly', () => {
expect(
getNextVersion({
currentVersion: '25.8.4',
type: 'auto',
currentDate: new Date('2025-08-27'),
}),
).toBe('25.9.0');
});
it('auto after 25th (next month) -> monthly', () => {
expect(
getNextVersion({
currentVersion: '25.8.4',
type: 'auto',
currentDate: new Date('2025-09-02'),
}),
).toBe('25.9.0');
});
it('invalid type throws', () => {
expect(() =>
getNextVersion({
currentVersion: '25.8.4',
type: 'unknown',
currentDate: new Date('2025-08-10'),
}),
).toThrow(/Invalid type/);
});
});

View File

@@ -0,0 +1,9 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
include: ['src/**/*.test.(js|jsx|ts|tsx)'],
environment: 'node',
},
});

View File

@@ -13,9 +13,9 @@
},
"devDependencies": {
"@svgr/cli": "^8.1.0",
"@types/react": "^19.1.4",
"react": "19.1.0",
"react-dom": "19.1.0",
"@types/react": "^19.1.12",
"react": "19.1.1",
"react-dom": "19.1.1",
"vitest": "^3.2.4"
},
"exports": {
@@ -49,7 +49,8 @@
"./toggle": "./src/Toggle.tsx",
"./tooltip": "./src/Tooltip.tsx",
"./view": "./src/View.tsx",
"./color-picker": "./src/ColorPicker.tsx"
"./color-picker": "./src/ColorPicker.tsx",
"./props/*": "./src/props/*.ts"
},
"scripts": {
"generate:icons": "rm src/icons/*/*.tsx; cd src/icons && svgr --template template.ts --index-template index-template.ts --typescript --expand-props start -d . .",

View File

@@ -0,0 +1,11 @@
import { CSSProperties } from '../styles';
export type BasicModalProps = {
isLoading?: boolean;
noAnimation?: boolean;
style?: CSSProperties;
onClose?: () => void;
containerProps?: {
style?: CSSProperties;
};
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -85,7 +85,73 @@ test.describe('Schedules', () => {
'Show completed schedules',
);
await expect(page).toMatchThemeScreenshots();
});
test('creates two new schedules, posts both transactions and later completes one', async () => {
test.setTimeout(40000);
// Adding two schedules with the same payee and account and amount, mimicking two different subscriptions
await schedulesPage.addNewSchedule({
payee: 'Apple',
account: 'HSBC',
amount: 5,
});
await schedulesPage.addNewSchedule({
payee: 'Apple',
account: 'HSBC',
amount: 5,
});
const schedule = schedulesPage.getNthSchedule(2);
await expect(schedule.payee).toHaveText('Apple');
await expect(schedule.account).toHaveText('HSBC');
await expect(schedule.amount).toHaveText('~5.00');
await expect(schedule.status).toHaveText('Due');
await expect(page).toMatchThemeScreenshots();
const schedule2 = schedulesPage.getNthSchedule(3);
await expect(schedule2.payee).toHaveText('Apple');
await expect(schedule2.account).toHaveText('HSBC');
await expect(schedule2.amount).toHaveText('~5.00');
await expect(schedule2.status).toHaveText('Due');
await expect(page).toMatchThemeScreenshots();
await schedulesPage.postNthSchedule(2);
await expect(schedulesPage.getNthSchedule(2).status).toHaveText('Paid');
await expect(schedulesPage.getNthSchedule(3).status).toHaveText('Due');
await expect(page).toMatchThemeScreenshots();
await schedulesPage.postNthSchedule(3);
await expect(schedulesPage.getNthSchedule(2).status).toHaveText('Paid');
await expect(schedulesPage.getNthSchedule(3).status).toHaveText('Paid');
await expect(page).toMatchThemeScreenshots();
// Go to transactions page
const accountPage = await navigation.goToAccountPage('HSBC');
const transaction = accountPage.getNthTransaction(0);
await expect(transaction.payee).toHaveText('Apple');
await expect(transaction.category).toHaveText('Categorize');
await expect(transaction.debit).toHaveText('5.00');
await expect(transaction.credit).toHaveText('');
// Go to transactions page
const transaction2 = accountPage.getNthTransaction(1);
await expect(transaction2.payee).toHaveText('Apple');
await expect(transaction2.category).toHaveText('Categorize');
await expect(transaction2.debit).toHaveText('5.00');
await expect(transaction2.credit).toHaveText('');
const icon = transaction.payee.getByTestId('schedule-icon');
await icon.hover();
await expect(page).toMatchThemeScreenshots();
const icon2 = transaction2.payee.getByTestId('schedule-icon');
await icon2.hover();
await expect(page).toMatchThemeScreenshots();
});
test('creates a "full" list of schedules', async () => {
// Schedules search shouldn't shrink with many schedules
for (let i = 0; i < 10; i++) {
await schedulesPage.addNewSchedule({

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Some files were not shown because too many files have changed in this diff Show More