Compare commits

...

17 Commits

Author SHA1 Message Date
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
61 changed files with 589 additions and 159 deletions

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)

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

@@ -60,8 +60,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
- name: Set up environment
uses: ./.github/actions/setup
with:
node-version: 22
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

@@ -87,8 +87,8 @@ 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/

View File

@@ -44,25 +44,9 @@ yarn start:desktop
### ⚠️ CRITICAL REQUIREMENT: AI-Generated Commit Messages and PR Titles
**THIS IS A MANDATORY REQUIREMENT THAT MUST BE FOLLOWED WITHOUT EXCEPTION:**
**ALL commit messages and PR titles MUST be prefixed with `[AI]`.** No exceptions.
- **ALL commit messages MUST be prefixed with `[AI]`**
- **ALL pull request titles MUST be prefixed with `[AI]`**
**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)
**This requirement applies to:**
- Every single commit message created by AI agents
- Every single pull request title created by AI agents
- No exceptions are permitted
**This is a hard requirement that agents MUST follow. Failure to include the `[AI]` prefix is a violation of these instructions.**
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
@@ -314,6 +298,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
@@ -360,13 +345,7 @@ Always maintain newlines between import groups.
**Git Commands:**
- **MANDATORY: ALL commit messages MUST be prefixed with `[AI]`** - This is a hard requirement with no exceptions
- **MANDATORY: ALL pull request titles MUST be prefixed with `[AI]`** - This is a hard requirement with no exceptions
- 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
@@ -565,7 +544,7 @@ Icons in `packages/component-library/src/icons/` are auto-generated. Don't manua
Before committing changes, ensure:
- [ ] **MANDATORY: Commit message is prefixed with `[AI]`** - This is a hard requirement with no exceptions
- [ ] 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
@@ -578,17 +557,7 @@ Before committing changes, ensure:
## Pull Request Guidelines
When creating pull requests:
- **MANDATORY PREFIX REQUIREMENT**: **ALL pull request titles MUST be prefixed with `[AI]`** - This is a hard requirement that MUST be followed without exception
- ✅ Correct: `[AI] Fix type error in account validation`
- ❌ Incorrect: `Fix type error in account validation` (MISSING PREFIX - NOT ALLOWED)
- **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.
### 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. We expect **humans** 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.
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

View File

@@ -1 +1,2 @@
@AGENTS.md
@.github/agents/pr-and-commit-rules.md

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

@@ -3,9 +3,18 @@
"private": true,
"type": "module",
"scripts": {
"test": "vitest --run"
"tsx": "node --import=extensionless/register --experimental-strip-types",
"test": "vitest --run",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"extensionless": "^2.0.6",
"typescript": "^5.9.3",
"vitest": "^4.0.18"
},
"extensionless": {
"lookFor": [
"ts"
]
}
}

View File

@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": [],
"module": "nodenext",
"moduleResolution": "nodenext",
"skipLibCheck": true,
"strict": true,
"types": ["node"],
"outDir": "dist",
"rootDir": "."
},
"include": ["src/**/*", "bin/**/*"],
"exclude": ["node_modules"]
}

View File

@@ -34,6 +34,7 @@ import {
import { handleGlobalEvents } from '@desktop-client/global-events';
import { useIsTestEnv } from '@desktop-client/hooks/useIsTestEnv';
import { useMetadataPref } from '@desktop-client/hooks/useMetadataPref';
import { useOnVisible } from '@desktop-client/hooks/useOnVisible';
import { SpreadsheetProvider } from '@desktop-client/hooks/useSpreadsheet';
import { setI18NextLanguage } from '@desktop-client/i18n';
import { addNotification } from '@desktop-client/notifications/notificationsSlice';
@@ -179,6 +180,11 @@ export function App() {
);
const dispatch = useDispatch();
useOnVisible(async () => {
console.debug('triggering sync because of visibility change');
await dispatch(sync());
});
useEffect(() => {
function checkScrollbars() {
if (hiddenScrollbars !== hasHiddenScrollbars()) {
@@ -186,25 +192,9 @@ export function App() {
}
}
let isSyncing = false;
async function onVisibilityChange() {
if (!isSyncing) {
console.debug('triggering sync because of visibility change');
isSyncing = true;
await dispatch(sync());
isSyncing = false;
}
}
window.addEventListener('focus', checkScrollbars);
window.addEventListener('visibilitychange', onVisibilityChange);
return () => {
window.removeEventListener('focus', checkScrollbars);
window.removeEventListener('visibilitychange', onVisibilityChange);
};
}, [dispatch, hiddenScrollbars]);
return () => window.removeEventListener('focus', checkScrollbars);
}, [hiddenScrollbars]);
const [theme] = useTheme();

View File

@@ -32,6 +32,7 @@ import { accountQueries } from '@desktop-client/accounts';
import { getLatestAppVersion, sync } from '@desktop-client/app/appSlice';
import { ProtectedRoute } from '@desktop-client/auth/ProtectedRoute';
import { Permissions } from '@desktop-client/auth/types';
import { useAccounts } from '@desktop-client/hooks/useAccounts';
import { useGlobalPref } from '@desktop-client/hooks/useGlobalPref';
import { useLocalPref } from '@desktop-client/hooks/useLocalPref';
import { useMetaThemeColor } from '@desktop-client/hooks/useMetaThemeColor';
@@ -91,10 +92,7 @@ export function FinancesApp() {
const dispatch = useDispatch();
const { t } = useTranslation();
// TODO: Replace with `useAccounts` hook once it's updated to return the useQuery results.
const { data: accounts, isFetching: isAccountsFetching } = useQuery(
accountQueries.list(),
);
const { data: accounts, isFetching: isAccountsFetching } = useAccounts();
const versionInfo = useSelector(state => state.app.versionInfo);
const [notifyWhenUpdateIsAvailable] = useGlobalPref(

View File

@@ -12,6 +12,7 @@ import { t } from 'i18next';
import { send } from 'loot-core/platform/client/connection';
import type { Handlers } from 'loot-core/types/handlers';
import { useOnVisible } from '@desktop-client/hooks/useOnVisible';
import { addNotification } from '@desktop-client/notifications/notificationsSlice';
import { useDispatch } from '@desktop-client/redux';
@@ -110,6 +111,16 @@ export function ServerProvider({ children }: { children: ReactNode }) {
void run();
}, []);
useOnVisible(
async () => {
const version = await getServerVersion();
setVersion(version);
},
{
isEnabled: !!serverURL,
},
);
const refreshLoginMethods = useCallback(async () => {
if (serverURL) {
const data: Awaited<ReturnType<Handlers['subscribe-get-login-methods']>> =

View File

@@ -313,7 +313,7 @@ export function ConfigServer() {
switch (error) {
case 'network-failure':
return t(
'Server is not running at this URL. Make sure you have HTTPS set up properly.',
'Connection failed. If you use a self-signed certificate or were recently offline, try refreshing the page. Otherwise ensure you have HTTPS set up properly.',
);
default:
return t(

View File

@@ -49,7 +49,7 @@ function TransactionListWithPreviews() {
} = useTransactions({
query: transactionsQuery,
});
const offBudgetAccounts = useOffBudgetAccounts();
const { data: offBudgetAccounts = [] } = useOffBudgetAccounts();
const offBudgetAccountsFilter = useCallback(
(schedule: ScheduleEntity) =>
offBudgetAccounts.some(a => a.id === schedule._account),

View File

@@ -49,7 +49,7 @@ function TransactionListWithPreviews() {
} = useTransactions({
query: transactionsQuery,
});
const onBudgetAccounts = useOnBudgetAccounts();
const { data: onBudgetAccounts = [] } = useOnBudgetAccounts();
const onBudgetAccountsFilter = useCallback(
(schedule: ScheduleEntity) =>
onBudgetAccounts.some(a => a.id === schedule._account),

View File

@@ -34,10 +34,7 @@ import type { AccountEntity, TransactionEntity } from 'loot-core/types/models';
import { lookupName, Status } from './TransactionEdit';
import {
makeAmountFullStyle,
makeBalanceAmountStyle,
} from '@desktop-client/components/budget/util';
import { makeAmountFullStyle } from '@desktop-client/components/budget/util';
import { useAccount } from '@desktop-client/hooks/useAccount';
import { useCachedSchedules } from '@desktop-client/hooks/useCachedSchedules';
import { useCategories } from '@desktop-client/hooks/useCategories';
@@ -283,7 +280,11 @@ export function TransactionListItem({
<Text
style={{
...styles.tnum,
...makeAmountFullStyle(amount),
...makeAmountFullStyle(amount, {
positiveColor: theme.tableText,
negativeColor: theme.tableText,
zeroColor: theme.numberNeutral,
}),
...textStyle,
}}
>
@@ -295,7 +296,11 @@ export function TransactionListItem({
fontSize: 11,
fontWeight: '400',
...styles.tnum,
...makeBalanceAmountStyle(runningBalance),
...makeAmountFullStyle(runningBalance, {
positiveColor: theme.numberPositive,
negativeColor: theme.numberNegative,
zeroColor: theme.numberNeutral,
}),
}}
>
{integerToCurrency(runningBalance)}

View File

@@ -49,6 +49,7 @@ export function FormulaResult({
containerRef,
}: FormulaResultProps) {
const [fontSize, setFontSize] = useState<number>(initialFontSize);
const [hasSized, setHasSized] = useState(false);
const refDiv = useRef<HTMLDivElement>(null);
const previousFontSizeRef = useRef<number>(initialFontSize);
const format = useFormat();
@@ -89,7 +90,10 @@ export function FormulaResult({
height, // Ensure the text fits vertically by using the height as the limiting factor
);
setFontSize(calculatedFontSize);
if (calculatedFontSize > 0) {
setFontSize(calculatedFontSize);
setHasSized(true);
}
// Only call fontSizeChanged if the font size actually changed
if (
@@ -143,6 +147,7 @@ export function FormulaResult({
useEffect(() => {
if (fontSizeMode === 'static') {
setFontSize(staticFontSize);
setHasSized(true);
}
}, [fontSizeMode, staticFontSize]);
@@ -153,6 +158,8 @@ export function FormulaResult({
? theme.errorText
: theme.pageText;
const showContent = hasSized || fontSizeMode === 'static';
return (
<View style={{ flex: 1 }}>
{loading && <LoadingIndicator />}
@@ -175,9 +182,13 @@ export function FormulaResult({
color,
}}
>
<span aria-hidden="true">
<PrivacyFilter>{displayValue}</PrivacyFilter>
</span>
{!showContent ? (
<LoadingIndicator />
) : (
<span aria-hidden="true">
<PrivacyFilter>{displayValue}</PrivacyFilter>
</span>
)}
</View>
)}
</View>

View File

@@ -38,6 +38,7 @@ export function SummaryNumber({
}: SummaryNumberProps) {
const { t } = useTranslation();
const [fontSize, setFontSize] = useState<number>(initialFontSize);
const [hasSized, setHasSized] = useState(false);
const refDiv = useRef<HTMLDivElement>(null);
const format = useFormat();
const isNumericValue = Number.isFinite(value);
@@ -61,7 +62,10 @@ export function SummaryNumber({
height, // Ensure the text fits vertically by using the height as the limiting factor
);
setFontSize(calculatedFontSize);
if (calculatedFontSize > 0) {
setFontSize(calculatedFontSize);
setHasSized(true);
}
if (calculatedFontSize !== initialFontSize && fontSizeChanged) {
fontSizeChanged(calculatedFontSize);
@@ -107,9 +111,13 @@ export function SummaryNumber({
: theme.reportsNumberPositive,
}}
>
<FinancialText aria-hidden="true">
<PrivacyFilter>{displayAmount}</PrivacyFilter>
</FinancialText>
{!hasSized ? (
<LoadingIndicator />
) : (
<FinancialText aria-hidden="true">
<PrivacyFilter>{displayAmount}</PrivacyFilter>
</FinancialText>
)}
</View>
)}
</>

View File

@@ -18,6 +18,8 @@ import {
import * as monthUtils from 'loot-core/shared/months';
import { computePadding } from './util/computePadding';
import { FinancialText } from '@desktop-client/components/FinancialText';
import { Container } from '@desktop-client/components/reports/Container';
import { useFormat } from '@desktop-client/hooks/useFormat';
@@ -220,11 +222,22 @@ export function BudgetAnalysisGraph({
return monthUtils.format(date, 'MMM d', locale);
};
const allValues = graphData.flatMap(item => [
item.budgeted,
item.spent,
item.balance,
item.overspendingAdjustment,
]);
const leftPadding = computePadding(allValues, value =>
format(value, 'financial-no-decimals'),
);
const commonProps = {
width: 0,
height: 0,
data: graphData,
margin: { top: 5, right: 5, left: 5, bottom: 5 },
margin: { top: 5, right: 5, left: 5 + leftPadding, bottom: 5 },
};
return (

View File

@@ -246,7 +246,9 @@ export function ThemeInstaller({
return null;
}
const catalogItems = catalog ?? [];
const catalogItems = [...(catalog ?? [])].sort((a, b) =>
a.name.localeCompare(b.name),
);
const itemsPerRow = getItemsPerRow(width);
const rows: CatalogTheme[][] = [];
for (let i = 0; i < catalogItems.length; i += itemsPerRow) {

View File

@@ -28,9 +28,9 @@ export function Accounts() {
const { data: accounts = [] } = useAccounts();
const failedAccounts = useFailedAccounts();
const updatedAccounts = useUpdatedAccounts();
const offbudgetAccounts = useOffBudgetAccounts();
const onBudgetAccounts = useOnBudgetAccounts();
const closedAccounts = useClosedAccounts();
const { data: offbudgetAccounts = [] } = useOffBudgetAccounts();
const { data: onBudgetAccounts = [] } = useOnBudgetAccounts();
const { data: closedAccounts = [] } = useClosedAccounts();
const syncingAccountIds = useSelector(state => state.account.accountsSyncing);
const getAccountPath = (account: AccountEntity) => `/accounts/${account.id}`;

View File

@@ -0,0 +1 @@
import 'loot-core/typings/window';

View File

@@ -3,8 +3,5 @@ import { useQuery } from '@tanstack/react-query';
import { accountQueries } from '@desktop-client/accounts';
export function useClosedAccounts() {
const query = useQuery(accountQueries.listClosed());
// TODO: Update to return query states (e.g. isFetching, isError, etc)
// so clients can handle loading and error states appropriately.
return query.data ?? [];
return useQuery(accountQueries.listClosed());
}

View File

@@ -3,6 +3,5 @@ import { useQuery } from '@tanstack/react-query';
import { accountQueries } from '@desktop-client/accounts';
export function useOffBudgetAccounts() {
const query = useQuery(accountQueries.listOffBudget());
return query.data ?? [];
return useQuery(accountQueries.listOffBudget());
}

View File

@@ -3,6 +3,5 @@ import { useQuery } from '@tanstack/react-query';
import { accountQueries } from '@desktop-client/accounts';
export function useOnBudgetAccounts() {
const query = useQuery(accountQueries.listOnBudget());
return query.data ?? [];
return useQuery(accountQueries.listOnBudget());
}

View File

@@ -0,0 +1,108 @@
import { renderHook } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { useOnVisible } from './useOnVisible';
function setVisibilityState(value: DocumentVisibilityState) {
Object.defineProperty(document, 'visibilityState', {
value,
configurable: true,
writable: true,
});
}
function dispatchVisibilityChange() {
document.dispatchEvent(new Event('visibilitychange'));
}
describe('useOnVisible', () => {
const originalVisibilityState = document.visibilityState;
beforeEach(() => {
setVisibilityState('visible');
});
afterEach(() => {
setVisibilityState(originalVisibilityState);
vi.clearAllMocks();
});
it('invokes callback when document becomes visible', () => {
const callback = vi.fn();
renderHook(() => useOnVisible(callback));
dispatchVisibilityChange();
expect(callback).toHaveBeenCalledTimes(1);
});
it('does not invoke callback when visibilityState is hidden', () => {
const callback = vi.fn();
renderHook(() => useOnVisible(callback));
setVisibilityState('hidden');
dispatchVisibilityChange();
expect(callback).not.toHaveBeenCalled();
});
it('does not attach listener when isEnabled is false', () => {
const callback = vi.fn();
renderHook(() => useOnVisible(callback, { isEnabled: false }));
dispatchVisibilityChange();
expect(callback).not.toHaveBeenCalled();
});
it('stops invoking callback after unmount', () => {
const callback = vi.fn();
const { unmount } = renderHook(() => useOnVisible(callback));
unmount();
dispatchVisibilityChange();
expect(callback).not.toHaveBeenCalled();
});
it('invokes callback on every visibilitychange when visibilityState is visible', async () => {
const callback = vi.fn();
renderHook(() => useOnVisible(callback));
dispatchVisibilityChange();
expect(callback).toHaveBeenCalledTimes(1);
await Promise.resolve();
dispatchVisibilityChange();
expect(callback).toHaveBeenCalledTimes(2);
});
it('does not invoke callback again until previous async callback completes', async () => {
let resolve: () => void;
const callback = vi.fn().mockImplementation(
() =>
new Promise<void>(r => {
resolve = r;
}),
);
renderHook(() => useOnVisible(callback));
dispatchVisibilityChange();
dispatchVisibilityChange();
expect(callback).toHaveBeenCalledTimes(1);
resolve();
await Promise.resolve();
dispatchVisibilityChange();
expect(callback).toHaveBeenCalledTimes(2);
});
it('invokes callback when isEnabled is true by default', () => {
const callback = vi.fn();
renderHook(() => useOnVisible(callback));
dispatchVisibilityChange();
expect(callback).toHaveBeenCalledTimes(1);
});
});

View File

@@ -0,0 +1,47 @@
import { useEffect, useEffectEvent, useRef } from 'react';
type UseOnVisibleOptions = {
/** When false, the visibility listener is not attached. Default true. */
isEnabled?: boolean;
};
/**
* Runs the given callback when the document becomes visible (e.g. user
* switches back to the tab). Uses a guard so the callback is not invoked
* again until the previous invocation has finished (handles async callbacks).
*/
export function useOnVisible(
callback: () => void | Promise<void>,
options: UseOnVisibleOptions = {},
) {
const { isEnabled = true } = options;
const inProgress = useRef(false);
const runCallback = useEffectEvent(async () => {
if (inProgress.current) {
return;
}
inProgress.current = true;
try {
await callback();
} finally {
inProgress.current = false;
}
});
useEffect(() => {
if (!isEnabled) {
return;
}
function onVisibilityChange() {
if (document.visibilityState !== 'visible') {
return;
}
void runCallback();
}
document.addEventListener('visibilitychange', onVisibilityChange);
return () =>
document.removeEventListener('visibilitychange', onVisibilityChange);
}, [isEnabled]);
}

View File

@@ -1,4 +1,4 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { send } from 'loot-core/platform/client/connection';
import { computeSchedulePreviewTransactions } from 'loot-core/shared/schedules';
@@ -47,18 +47,9 @@ export function usePreviewTransactions({
} = useCachedSchedules();
const [isLoading, setIsLoading] = useState(isSchedulesLoading);
const [error, setError] = useState<Error | undefined>(undefined);
const [runningBalances, setRunningBalances] = useState<
Map<TransactionEntity['id'], IntegerAmount>
>(new Map());
const [upcomingLength] = useSyncedPref('upcomingScheduledTransactionLength');
// We don't want to re-render if options changes.
// Putting options in a ref will prevent that and
// allow us to use the latest options on next render.
const optionsRef = useRef(options);
optionsRef.current = options;
const scheduleTransactions = useMemo(() => {
if (isSchedulesLoading) {
return [];
@@ -109,21 +100,6 @@ export function usePreviewTransactions({
const ungroupedTransactions = ungroupTransactions(withDefaults);
setPreviewTransactions(ungroupedTransactions);
if (optionsRef.current?.calculateRunningBalances) {
setRunningBalances(
// We always use the bottom up calculation for preview transactions
// because the hook controls the order of the transactions. We don't
// need to provide a custom way for consumers to calculate the running
// balances, at least as of writing.
calculateRunningBalancesBottomUp(
ungroupedTransactions,
// Preview transactions are behaves like 'all' splits
'all',
optionsRef.current?.startingBalance,
),
);
}
setIsLoading(false);
}
})
@@ -139,6 +115,24 @@ export function usePreviewTransactions({
};
}, [scheduleTransactions, schedules, statuses, upcomingLength]);
const runningBalances = useMemo(() => {
if (!options?.calculateRunningBalances) {
return new Map<TransactionEntity['id'], IntegerAmount>();
}
// We always use the bottom up calculation for preview transactions
// because the hook controls the order of the transactions.
return calculateRunningBalancesBottomUp(
previewTransactions,
'all',
options?.startingBalance,
);
}, [
previewTransactions,
options?.calculateRunningBalances,
options?.startingBalance,
]);
const returnError = error || scheduleQueryError;
return {
previewTransactions,

View File

@@ -19,13 +19,7 @@
{ "path": "../loot-core" },
{ "path": "../component-library" }
],
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.js",
// TODO: remove loot-core dependency
"../../packages/loot-core/typings/window.ts"
],
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.js"],
"exclude": [
"node_modules",
"build",

View File

@@ -49,6 +49,7 @@
"./shared/*": "./src/shared/*.ts",
"./types/models": "./src/types/models/index.ts",
"./types/*": "./src/types/*.ts",
"./typings/*": "./typings/*.ts",
"./lib-dist/electron/bundle.desktop.js": "./lib-dist/electron/bundle.desktop.js"
},
"scripts": {

View File

@@ -72,6 +72,15 @@ describe('utility functions', () => {
expect(looselyParseAmount('(1 500.99)')).toBe(-1500.99);
});
test('looseParseAmount handles trailing whitespace', () => {
expect(looselyParseAmount('1055 ')).toBe(1055);
expect(looselyParseAmount('$1,055 ')).toBe(1055);
expect(looselyParseAmount('$1,055.00 ')).toBe(1055);
expect(looselyParseAmount(' $1,055 ')).toBe(1055);
expect(looselyParseAmount('3.45 ')).toBe(3.45);
expect(looselyParseAmount(' 3.45 ')).toBe(3.45);
});
test('number formatting works with comma-dot format', () => {
setNumberFormat({ format: 'comma-dot', hideFraction: false });
let formatter = getNumberFormat().formatter;

View File

@@ -550,6 +550,8 @@ export function looselyParseAmount(amount: string) {
return v.replace(/[^0-9-]/g, '');
}
amount = amount.trim();
if (amount.startsWith('(') && amount.endsWith(')')) {
// Remove Unicode minus inside parentheses before converting to ASCII minus
amount = amount.replace(/\u2212/g, '');

View File

@@ -38,7 +38,7 @@
"date-fns": "^4.1.0",
"debug": "^4.4.3",
"express": "^5.2.1",
"express-rate-limit": "^8.2.1",
"express-rate-limit": "^8.3.0",
"express-winston": "^4.2.0",
"ipaddr.js": "^2.3.0",
"jws": "^4.0.1",

View File

@@ -28,7 +28,9 @@ export async function run(direction: 'up' | 'down' = 'up'): Promise<void> {
> = {};
for (const f of files
.filter(f => f.endsWith('.js') || f.endsWith('.ts'))
.filter(
f => (f.endsWith('.js') || f.endsWith('.ts')) && !f.endsWith('.d.ts'),
)
.sort()) {
migrationsModules[f] = await import(
pathToFileURL(path.join(migrationsDir, f)).href

View File

@@ -0,0 +1,6 @@
---
category: Bugfixes
authors: [LeviBorodenko]
---
[Mobile] Show running balance on upcoming transactions when respective setting is toggled

View File

@@ -0,0 +1,6 @@
---
category: Bugfixes
authors: [Juulz]
---
Make mobile account page colors more consistent

View File

@@ -0,0 +1,6 @@
---
category: Enhancements
authors: [joel-jeremy]
---
Refactor account hooks to return full React Query states, enhancing data handling and component integration.

View File

@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [jfdoming]
---
Move migrations CI script to typescript + ci-actions

View File

@@ -0,0 +1,6 @@
---
category: Bugfixes
authors: [matt-fidd]
---
Stop font size fluctuations showing in summary cards

View File

@@ -0,0 +1,6 @@
---
category: Enhancements
authors: [MatissJanis]
---
Reload server version when visibility to the page changes

View File

@@ -0,0 +1,6 @@
---
category: Bugfixes
authors: [mnil]
---
Fix budget analysis report padding when the value is large.

View File

@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [dependabot[bot]]
---
Bump `express-rate-limit` dependency version from 8.2.1 to 8.3.0 for improvements.

View File

@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [MikesGlitch]
---
Adding more retries to the Docker test in the pipeline

View File

@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [MatissJanis]
---
Refactor TypeScript typings by moving window import to globals.ts for cleaner configuration.

View File

@@ -0,0 +1,6 @@
---
category: Enhancements
authors: [MatissJanis]
---
Sort theme catalog items alphabetically by name for improved user interface organization.

View File

@@ -0,0 +1,6 @@
---
category: Bugfix
authors: [MikesGlitch]
---
Fix migrations retrieval when running the docker images

View File

@@ -0,0 +1,6 @@
---
category: Bugfixes
authors: [mibragimov]
---
Fix CSV import incorrectly parsing transaction amounts that contain trailing whitespace (e.g. amounts from Excel-saved CSV files).

View File

@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [MatissJanis]
---
Establish centralized AI governance documentation for commit and pull request standards.

View File

@@ -39,6 +39,8 @@ __metadata:
version: 0.0.0-use.local
resolution: "@actual-app/ci-actions@workspace:packages/ci-actions"
dependencies:
extensionless: "npm:^2.0.6"
typescript: "npm:^5.9.3"
vitest: "npm:^4.0.18"
languageName: unknown
linkType: soft
@@ -118,7 +120,7 @@ __metadata:
date-fns: "npm:^4.1.0"
debug: "npm:^4.4.3"
express: "npm:^5.2.1"
express-rate-limit: "npm:^8.2.1"
express-rate-limit: "npm:^8.3.0"
express-winston: "npm:^4.2.0"
http-proxy-middleware: "npm:^3.0.5"
ipaddr.js: "npm:^2.3.0"
@@ -8852,13 +8854,6 @@ __metadata:
languageName: node
linkType: hard
"@trysound/sax@npm:0.2.0":
version: 0.2.0
resolution: "@trysound/sax@npm:0.2.0"
checksum: 10/7379713eca480ac0d9b6c7b063e06b00a7eac57092354556c81027066eb65b61ea141a69d0cc2e15d32e05b2834d4c9c2184793a5e36bbf5daf05ee5676af18c
languageName: node
linkType: hard
"@tsconfig/node10@npm:^1.0.7":
version: 1.0.11
resolution: "@tsconfig/node10@npm:1.0.11"
@@ -14699,14 +14694,14 @@ __metadata:
linkType: hard
"dompurify@npm:^3.2.5":
version: 3.3.0
resolution: "dompurify@npm:3.3.0"
version: 3.3.2
resolution: "dompurify@npm:3.3.2"
dependencies:
"@types/trusted-types": "npm:^2.0.7"
dependenciesMeta:
"@types/trusted-types":
optional: true
checksum: 10/d8782b10a0454344476936c91038d06c9450b3e3ada2ceb8f722525e6b54e64d847939b9f35bf385facd4139f0a2eaf7f5553efce351f8e9295620570875f002
checksum: 10/3ca02559677ce6d9583a500f21ffbb6b9e88f1af99f69fa0d0d9442cddbac98810588c869f8b435addb5115492d6e49870024bca322169b941bafedb99c7f281
languageName: node
linkType: hard
@@ -15917,14 +15912,14 @@ __metadata:
languageName: node
linkType: hard
"express-rate-limit@npm:^8.2.1":
version: 8.2.1
resolution: "express-rate-limit@npm:8.2.1"
"express-rate-limit@npm:^8.3.0":
version: 8.3.0
resolution: "express-rate-limit@npm:8.3.0"
dependencies:
ip-address: "npm:10.0.1"
ip-address: "npm:10.1.0"
peerDependencies:
express: ">= 4.11"
checksum: 10/7cbf70df2e88e590e463d2d8f93380775b2ea181d97f2c50c2ff9f2c666c247f83109a852b21d9c99ccc5762119101f281f54a27252a2f1a0a918be6d71f955b
checksum: 10/e896a66fecc10639e65873186fdfb71f19d6af650220eb7ea5450725215c3eed8dc6ddcfa1e68a9db8c9facc3326fbc281512ad3ccd8f107f42a2466ce12c18c
languageName: node
linkType: hard
@@ -16038,6 +16033,13 @@ __metadata:
languageName: node
linkType: hard
"extensionless@npm:^2.0.6":
version: 2.0.6
resolution: "extensionless@npm:2.0.6"
checksum: 10/4a264600d9ff811534b35a66ff59eb075ca5c1ae4f25213bfa71d26a5e28ba5188a0d743f4e1dc8255cf9739258d5d374c6f757957faf4fe0d4ec5f57f51034f
languageName: node
linkType: hard
"extract-zip@npm:^2.0.1":
version: 2.0.1
resolution: "extract-zip@npm:2.0.1"
@@ -18010,9 +18012,9 @@ __metadata:
linkType: hard
"immutable@npm:^5.0.2":
version: 5.1.3
resolution: "immutable@npm:5.1.3"
checksum: 10/6d29b29036143e7ea1e7f7be581c71bca873ea77c175d33c6c839bf4017265a58c41ec269e3ffcd7b483797fc7fa9c928b4ed3d6edfeeb1b5711d84f60d04090
version: 5.1.5
resolution: "immutable@npm:5.1.5"
checksum: 10/7aec2740239772ec8e92e793c991bd809203a97694f4ff3a18e50e28f9a6b02393ad033d87b458037bdf8c0ea37d4446d640e825f6171df3405cf6cf300ce028
languageName: node
linkType: hard
@@ -18145,7 +18147,14 @@ __metadata:
languageName: node
linkType: hard
"ip-address@npm:10.0.1, ip-address@npm:^10.0.1":
"ip-address@npm:10.1.0":
version: 10.1.0
resolution: "ip-address@npm:10.1.0"
checksum: 10/a6979629d1ad9c1fb424bc25182203fad739b40225aebc55ec6243bbff5035faf7b9ed6efab3a097de6e713acbbfde944baacfa73e11852bb43989c45a68d79e
languageName: node
linkType: hard
"ip-address@npm:^10.0.1":
version: 10.0.1
resolution: "ip-address@npm:10.0.1"
checksum: 10/09731acda32cd8e14c46830c137e7e5940f47b36d63ffb87c737331270287d631cf25aa95570907a67d3f919fdb25f4470c404eda21e62f22e0a55927f4dd0fb
@@ -25613,6 +25622,13 @@ __metadata:
languageName: node
linkType: hard
"sax@npm:^1.5.0":
version: 1.5.0
resolution: "sax@npm:1.5.0"
checksum: 10/9012ff37dda7a7ac5da45db2143b04036103e8bef8d586c3023afd5df6caf0ebd7f38017eee344ad2e2247eded7d38e9c42cf291d8dd91781352900ac0fd2d9f
languageName: node
linkType: hard
"saxes@npm:^6.0.0":
version: 6.0.0
resolution: "saxes@npm:6.0.0"
@@ -26938,19 +26954,19 @@ __metadata:
linkType: hard
"svgo@npm:^3.0.2, svgo@npm:^3.2.0":
version: 3.3.2
resolution: "svgo@npm:3.3.2"
version: 3.3.3
resolution: "svgo@npm:3.3.3"
dependencies:
"@trysound/sax": "npm:0.2.0"
commander: "npm:^7.2.0"
css-select: "npm:^5.1.0"
css-tree: "npm:^2.3.1"
css-what: "npm:^6.1.0"
csso: "npm:^5.0.5"
picocolors: "npm:^1.0.0"
sax: "npm:^1.5.0"
bin:
svgo: ./bin/svgo
checksum: 10/82fdea9b938884d808506104228e4d3af0050d643d5b46ff7abc903ff47a91bbf6561373394868aaf07a28f006c4057b8fbf14bbd666298abdd7cc590d4f7700
checksum: 10/f3c1b4d05d1704483e53515d5995af5f06a2718df85e3a8320f57bb256b8dc926b84c87a1a9b98e9d3ca1224314cc0676a803bdd03163508292f2d45c7077096
languageName: node
linkType: hard