Compare commits
17 Commits
matiss/fix
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
448da13cf5 | ||
|
|
41679235be | ||
|
|
73fa068fe9 | ||
|
|
1fe588c143 | ||
|
|
edce092ae8 | ||
|
|
77411394f6 | ||
|
|
235d94478f | ||
|
|
7e0edd43ec | ||
|
|
fdf5c8d0a9 | ||
|
|
a8ec84ceac | ||
|
|
b727124603 | ||
|
|
8bb7f207f2 | ||
|
|
6e0c15eb12 | ||
|
|
4e2cec2c7a | ||
|
|
078603cadf | ||
|
|
b3a86b5392 | ||
|
|
295a565e55 |
74
.cursor/rules/pr-and-commit.mdc
Normal 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
@@ -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)
|
||||
7
.github/workflows/check.yml
vendored
@@ -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
|
||||
|
||||
4
.github/workflows/docker-edge.yml
vendored
@@ -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/
|
||||
|
||||
43
AGENTS.md
@@ -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
|
||||
|
||||
|
||||
@@ -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',
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
15
packages/ci-actions/tsconfig.json
Normal 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"]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
@@ -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();
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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']>> =
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}`;
|
||||
|
||||
1
packages/desktop-client/src/globals.ts
Normal file
@@ -0,0 +1 @@
|
||||
import 'loot-core/typings/window';
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
108
packages/desktop-client/src/hooks/useOnVisible.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
47
packages/desktop-client/src/hooks/useOnVisible.ts
Normal 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]);
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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, '');
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
6
upcoming-release-notes/7041.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Bugfixes
|
||||
authors: [LeviBorodenko]
|
||||
---
|
||||
|
||||
[Mobile] Show running balance on upcoming transactions when respective setting is toggled
|
||||
6
upcoming-release-notes/7047.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Bugfixes
|
||||
authors: [Juulz]
|
||||
---
|
||||
|
||||
Make mobile account page colors more consistent
|
||||
6
upcoming-release-notes/7071.md
Normal 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.
|
||||
6
upcoming-release-notes/7075.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [jfdoming]
|
||||
---
|
||||
|
||||
Move migrations CI script to typescript + ci-actions
|
||||
6
upcoming-release-notes/7092.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Bugfixes
|
||||
authors: [matt-fidd]
|
||||
---
|
||||
|
||||
Stop font size fluctuations showing in summary cards
|
||||
6
upcoming-release-notes/7111.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Enhancements
|
||||
authors: [MatissJanis]
|
||||
---
|
||||
|
||||
Reload server version when visibility to the page changes
|
||||
6
upcoming-release-notes/7118.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Bugfixes
|
||||
authors: [mnil]
|
||||
---
|
||||
|
||||
Fix budget analysis report padding when the value is large.
|
||||
6
upcoming-release-notes/7140.md
Normal 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.
|
||||
6
upcoming-release-notes/7141.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [MikesGlitch]
|
||||
---
|
||||
|
||||
Adding more retries to the Docker test in the pipeline
|
||||
6
upcoming-release-notes/7142.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [MatissJanis]
|
||||
---
|
||||
|
||||
Refactor TypeScript typings by moving window import to globals.ts for cleaner configuration.
|
||||
6
upcoming-release-notes/7144.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Enhancements
|
||||
authors: [MatissJanis]
|
||||
---
|
||||
|
||||
Sort theme catalog items alphabetically by name for improved user interface organization.
|
||||
6
upcoming-release-notes/7146.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Bugfix
|
||||
authors: [MikesGlitch]
|
||||
---
|
||||
|
||||
Fix migrations retrieval when running the docker images
|
||||
6
upcoming-release-notes/7149.md
Normal 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).
|
||||
6
upcoming-release-notes/7153.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [MatissJanis]
|
||||
---
|
||||
|
||||
Establish centralized AI governance documentation for commit and pull request standards.
|
||||
64
yarn.lock
@@ -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
|
||||
|
||||
|
||||