Compare commits

..

11 Commits

Author SHA1 Message Date
github-actions[bot]
7846d2e787 🔖 (25.10.0) (#5834)
* 🔖 (25.10.0)

* Remove used release notes

* Remove used release notes

---------

Co-authored-by: matt-fidd <81489167+matt-fidd@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
2025-10-02 11:55:47 +01:00
youngcw
ca6d80461a 🐛 fix limit checker (#5835) 2025-10-01 09:04:34 -07:00
Matt Fiddaman
fa14cbb697 fix decimal input in amount input boxes (#5831)
* preserve decimal seperators while typing in input boxes

* note
2025-10-01 16:27:51 +01:00
Matt Fiddaman
1210a74b4a fix error handling for simplefin batch sync (#5822)
* fix error handling for batch simplefin sync

* note
2025-09-30 23:51:17 +01:00
Matt Fiddaman
534c1e6680 fix crash when switching reports (#5823)
* fix report autocomplete

* note
2025-09-30 21:24:46 +01:00
Matt Fiddaman
14d436712a move balance history graph to live queries (#5821)
* move balance history graph to live queries

* note

* [autofix.ci] apply automated fixes

* coderabbit suggestion

* fix null starting balance

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-09-30 20:27:22 +01:00
Matt Fiddaman
e9f3925124 prevent the account balance graph from showing on small screen sizes (#5816)
* prevent account balance graph from showing on small screen sizes

* note

* lint
2025-09-30 00:46:26 +01:00
Matt Fiddaman
f28229be99 fix payee autocomplete hovering randomly (#5817)
* use correct index for payee autocomplete hover states

* note

* coderabbit
2025-09-30 00:46:07 +01:00
Matt Fiddaman
1fc922c672 skip running the schedule service if the database is not loaded (#5810) 2025-09-29 14:29:17 +01:00
Matiss Janis Aboltins
c712217a7c Update PayeesList component to use flex styling for improved layout consistency (#5803) 2025-09-28 06:31:30 +01:00
Matiss Janis Aboltins
3559b2df3a Mobile Payees - move to react-aria GridList to improve performance (#5802) 2025-09-27 21:54:16 +01:00
75 changed files with 318 additions and 596 deletions

View File

@@ -1,5 +1,5 @@
---
description:
description:
globs: *.ts,*.tsx
alwaysApply: false
---
@@ -21,7 +21,7 @@ Naming Conventions
TypeScript Usage
- Use TypeScript for all code; prefer interfaces over types.
- Use TypeScript for all code; prefer types over interfaces.
- Avoid enums; use objects or maps instead.
- Avoid using `any` or `unknown` unless absolutely necessary. Look for type definitions in the codebase instead.
- Avoid type assertions with `as` or `!`; prefer using `satisfies`.

View File

@@ -1,6 +1,6 @@
{
"name": "@actual-app/api",
"version": "25.9.0",
"version": "25.10.0",
"license": "MIT",
"description": "An API for Actual",
"engines": {

View File

@@ -154,4 +154,10 @@ export const styles: Record<string, any> = {
borderRadius: 4,
padding: '3px 5px',
},
mobileListItem: {
borderBottom: `1px solid ${theme.tableBorder}`,
backgroundColor: theme.tableBackground,
padding: 16,
cursor: 'pointer',
},
};

View File

@@ -1,6 +1,6 @@
{
"name": "@actual-app/web",
"version": "25.9.0",
"version": "25.10.0",
"license": "MIT",
"files": [
"build"

View File

@@ -22,7 +22,7 @@ import { PrivacyFilter } from '@desktop-client/components/PrivacyFilter';
import { LoadingIndicator } from '@desktop-client/components/reports/LoadingIndicator';
import { useLocale } from '@desktop-client/hooks/useLocale';
import * as query from '@desktop-client/queries';
import { aqlQuery } from '@desktop-client/queries/aqlQuery';
import { liveQuery } from '@desktop-client/queries/liveQuery';
const LABEL_WIDTH = 70;
@@ -32,11 +32,6 @@ type BalanceHistoryGraphProps = {
ref?: Ref<HTMLDivElement>;
};
type Balance = {
date: string;
balance: number;
};
export function BalanceHistoryGraph({
accountId,
style,
@@ -51,6 +46,11 @@ export function BalanceHistoryGraph({
date: string;
balance: number;
} | null>(null);
const [startingBalance, setStartingBalance] = useState<number | null>(null);
const [monthlyTotals, setMonthlyTotals] = useState<Array<{
date: string;
balance: number;
}> | null>(null);
const percentageChange = useMemo(() => {
if (balanceData.length < 2) return 0;
@@ -65,7 +65,70 @@ export function BalanceHistoryGraph({
);
useEffect(() => {
async function fetchBalanceHistory() {
// Reset state when accountId changes
setStartingBalance(null);
setMonthlyTotals(null);
setLoading(true);
const endDate = new Date();
const startDate = subMonths(endDate, 12);
const startingBalanceQuery = query
.transactions(accountId)
.filter({
date: { $lt: monthUtils.firstDayOfMonth(startDate) },
})
.calculate({ $sum: '$amount' });
const monthlyTotalsQuery = query
.transactions(accountId)
.filter({
$and: [
{ date: { $gte: monthUtils.firstDayOfMonth(startDate) } },
{ date: { $lte: monthUtils.lastDayOfMonth(endDate) } },
],
})
.groupBy({ $month: '$date' })
.select([{ date: { $month: '$date' } }, { amount: { $sum: '$amount' } }]);
const startingBalanceLive: ReturnType<typeof liveQuery<number>> = liveQuery(
startingBalanceQuery,
{
onData: (data: number[]) => {
setStartingBalance(data[0] || 0);
},
onError: error => {
console.error('Error fetching starting balance:', error);
setLoading(false);
},
},
);
const monthlyTotalsLive: ReturnType<
typeof liveQuery<{ date: string; amount: number }>
> = liveQuery(monthlyTotalsQuery, {
onData: (data: Array<{ date: string; amount: number }>) => {
setMonthlyTotals(
data.map(d => ({
date: d.date,
balance: d.amount,
})),
);
},
onError: error => {
console.error('Error fetching monthly totals:', error);
setLoading(false);
},
});
return () => {
startingBalanceLive?.unsubscribe();
monthlyTotalsLive?.unsubscribe();
};
}, [accountId, locale]);
// Process data when both startingBalance and monthlyTotals are available
useEffect(() => {
if (startingBalance !== null && monthlyTotals !== null) {
const endDate = new Date();
const startDate = subMonths(endDate, 12);
const months = eachMonthOfInterval({
@@ -73,99 +136,70 @@ export function BalanceHistoryGraph({
end: endDate,
}).map(m => format(m, 'yyyy-MM'));
const [starting, totals]: [number, Balance[]] = await Promise.all([
aqlQuery(
query
.transactions(accountId)
.filter({
date: { $lt: monthUtils.firstDayOfMonth(startDate) },
})
.calculate({ $sum: '$amount' }),
).then(({ data }) => data),
aqlQuery(
query
.transactions(accountId)
.filter({
$and: [
{ date: { $gte: monthUtils.firstDayOfMonth(startDate) } },
{ date: { $lte: monthUtils.lastDayOfMonth(endDate) } },
],
})
.groupBy({ $month: '$date' })
.select([
{ date: { $month: '$date' } },
{ amount: { $sum: '$amount' } },
]),
).then(({ data }) =>
data.map((d: { date: string; amount: number }) => {
return {
date: d.date,
balance: d.amount,
};
}),
),
]);
// calculate balances from sum of transactions
let currentBalance = starting;
totals.reverse().forEach(month => {
currentBalance = currentBalance + month.balance;
month.balance = currentBalance;
});
// if the account doesn't have recent transactions
// then the empty months will be missing from our data
// so add in entries for those here
if (totals.length === 0) {
//handle case of no transactions in the last year
months.forEach(expectedMonth =>
totals.push({
date: expectedMonth,
balance: starting,
}),
);
} else if (totals.length < months.length) {
// iterate through each array together and add in missing data
let totalsIndex = 0;
let mostRecent = starting;
months.forEach(expectedMonth => {
if (totalsIndex > totals.length - 1) {
// fill in the data at the end of the window
totals.push({
date: expectedMonth,
balance: mostRecent,
});
} else if (totals[totalsIndex].date === expectedMonth) {
// a matched month
mostRecent = totals[totalsIndex].balance;
totalsIndex += 1;
} else {
// a missing month in the middle
totals.push({
date: expectedMonth,
balance: mostRecent,
});
}
function processData(
startingBalanceValue: number,
monthlyTotalsValue: Array<{ date: string; balance: number }>,
) {
let currentBalance = startingBalanceValue;
const totals = [...monthlyTotalsValue];
totals.reverse().forEach(month => {
currentBalance = currentBalance + month.balance;
month.balance = currentBalance;
});
// if the account doesn't have recent transactions
// then the empty months will be missing from our data
// so add in entries for those here
if (totals.length === 0) {
//handle case of no transactions in the last year
months.forEach(expectedMonth =>
totals.push({
date: expectedMonth,
balance: startingBalanceValue,
}),
);
} else if (totals.length < months.length) {
// iterate through each array together and add in missing data
let totalsIndex = 0;
let mostRecent = startingBalanceValue;
months.forEach(expectedMonth => {
if (totalsIndex > totals.length - 1) {
// fill in the data at the end of the window
totals.push({
date: expectedMonth,
balance: mostRecent,
});
} else if (totals[totalsIndex].date === expectedMonth) {
// a matched month
mostRecent = totals[totalsIndex].balance;
totalsIndex += 1;
} else {
// a missing month in the middle
totals.push({
date: expectedMonth,
balance: mostRecent,
});
}
});
}
const balances = totals
.sort((a, b) => monthUtils.differenceInCalendarMonths(a.date, b.date))
.map(t => {
return {
balance: t.balance,
date: monthUtils.format(t.date, 'MMM yyyy', locale),
};
});
setBalanceData(balances);
setHoveredValue(balances[balances.length - 1]);
setLoading(false);
}
const balances = totals
.sort((a, b) => monthUtils.differenceInCalendarMonths(a.date, b.date))
.map(t => {
return {
balance: t.balance,
date: monthUtils.format(t.date, 'MMM yyyy', locale),
};
});
setBalanceData(balances);
setHoveredValue(balances[balances.length - 1]);
setLoading(false);
processData(startingBalance, monthlyTotals);
}
fetchBalanceHistory();
}, [accountId, locale]);
}, [startingBalance, monthlyTotals, locale]);
// State to track if the chart is hovered (used to conditionally render PrivacyFilter)
const [isHovered, setIsHovered] = useState(false);

View File

@@ -1,7 +1,6 @@
import React, {
type ComponentProps,
type ReactNode,
useEffect,
useRef,
useState,
} from 'react';
@@ -204,7 +203,7 @@ export function AccountHeader({
const isUsingServer = syncServerStatus !== 'no-server';
const isServerOffline = syncServerStatus === 'offline';
const [_, setExpandSplitsPref] = useLocalPref('expand-splits');
const [showNetWorthChartPref, setShowNetWorthChartPref] = useSyncedPref(
const [showNetWorthChartPref, _setShowNetWorthChartPref] = useSyncedPref(
`show-account-${accountId}-net-worth-chart`,
);
const showNetWorthChart = showNetWorthChartPref === 'true';
@@ -233,30 +232,6 @@ export function AccountHeader({
}
const graphRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleResize = () => {
const ele = graphRef.current;
if (!ele) return;
const clone = ele.cloneNode(true) as HTMLDivElement;
Object.assign(clone.style, {
visibility: 'hidden',
display: 'flex',
});
ele.after(clone);
if (clone.clientHeight < window.innerHeight * 0.15) {
setShowNetWorthChartPref('true');
} else {
setShowNetWorthChartPref('false');
}
clone.remove();
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, [setShowNetWorthChartPref]);
useHotkeys(
'ctrl+f, cmd+f, meta+f',

View File

@@ -171,34 +171,54 @@ function PayeeList({
// entered
const { newPayee, suggestedPayees, payees, transferPayees } = useMemo(() => {
return items.reduce(
(acc, item, index) => {
let currentIndex = 0;
const result = items.reduce(
(acc, item) => {
if (item.id === 'new') {
acc.newPayee = { ...item, highlightedIndex: index };
acc.newPayee = { ...item };
} else if (item.itemType === 'common_payee') {
acc.suggestedPayees.push({ ...item, highlightedIndex: index });
acc.suggestedPayees.push({ ...item });
} else if (item.itemType === 'payee') {
acc.payees.push({ ...item, highlightedIndex: index });
acc.payees.push({ ...item });
} else if (item.itemType === 'account') {
acc.transferPayees.push({ ...item, highlightedIndex: index });
acc.transferPayees.push({ ...item });
}
return acc;
},
{
newPayee: null as PayeeAutocompleteItem & {
highlightedIndex: number;
},
suggestedPayees: [] as Array<
PayeeAutocompleteItem & { highlightedIndex: number }
>,
payees: [] as Array<
PayeeAutocompleteItem & { highlightedIndex: number }
>,
transferPayees: [] as Array<
PayeeAutocompleteItem & { highlightedIndex: number }
>,
newPayee: null as PayeeAutocompleteItem | null,
suggestedPayees: [] as Array<PayeeAutocompleteItem>,
payees: [] as Array<PayeeAutocompleteItem>,
transferPayees: [] as Array<PayeeAutocompleteItem>,
},
);
// assign indexes in render order
const newPayeeWithIndex = result.newPayee
? { ...result.newPayee, highlightedIndex: currentIndex++ }
: null;
const suggestedPayeesWithIndex = result.suggestedPayees.map(item => ({
...item,
highlightedIndex: currentIndex++,
}));
const payeesWithIndex = result.payees.map(item => ({
...item,
highlightedIndex: currentIndex++,
}));
const transferPayeesWithIndex = result.transferPayees.map(item => ({
...item,
highlightedIndex: currentIndex++,
}));
return {
newPayee: newPayeeWithIndex,
suggestedPayees: suggestedPayeesWithIndex,
payees: payeesWithIndex,
transferPayees: transferPayeesWithIndex,
};
}, [items]);
// We limit the number of payees shown to 100.

View File

@@ -1,4 +1,4 @@
import React, { Fragment, type ComponentProps } from 'react';
import React, { type ComponentProps } from 'react';
import { useTranslation } from 'react-i18next';
import { theme } from '@actual-app/components/theme';
@@ -27,7 +27,7 @@ export function ReportList<T extends { id: string; name: string }>({
...(!embedded && { maxHeight: 175 }),
}}
>
<Fragment>{ItemHeader({ title: t('Saved Reports') })}</Fragment>
<ItemHeader title={t('Saved Reports')} />
{items.map((item, idx) => {
return [
<div

View File

@@ -1,4 +1,5 @@
import { Trans } from 'react-i18next';
import { GridList } from 'react-aria-components';
import { Trans, useTranslation } from 'react-i18next';
import { AnimatedLoading } from '@actual-app/components/icons/AnimatedLoading';
import { Text } from '@actual-app/components/text';
@@ -24,6 +25,8 @@ export function PayeesList({
isLoading = false,
onPayeePress,
}: PayeesListProps) {
const { t } = useTranslation();
if (isLoading && payees.length === 0) {
return (
<View
@@ -63,22 +66,30 @@ export function PayeesList({
}
return (
<View
style={{ flex: 1, paddingBottom: MOBILE_NAV_HEIGHT, overflow: 'auto' }}
>
{payees.map(payee => (
<PayeesListItem
key={payee.id}
payee={payee}
ruleCount={ruleCounts.get(payee.id) ?? 0}
onPress={() => onPayeePress(payee)}
/>
))}
<View style={{ flex: 1 }}>
<GridList
aria-label={t('Payees')}
aria-busy={isLoading || undefined}
items={payees}
style={{
flex: 1,
paddingBottom: MOBILE_NAV_HEIGHT,
overflow: 'auto',
}}
>
{payee => (
<PayeesListItem
value={payee}
ruleCount={ruleCounts.get(payee.id) ?? 0}
onAction={() => onPayeePress(payee)}
/>
)}
</GridList>
{isLoading && (
<View
style={{
alignItems: 'center',
paddingVertical: 20,
paddingTop: 20,
}}
>
<AnimatedLoading style={{ width: 20, height: 20 }} />

View File

@@ -1,9 +1,10 @@
import React from 'react';
import React, { memo } from 'react';
import { GridListItem, type GridListItemProps } from 'react-aria-components';
import { useTranslation } from 'react-i18next';
import { Button } from '@actual-app/components/button';
import { SvgBookmark } from '@actual-app/components/icons/v1';
import { SpaceBetween } from '@actual-app/components/space-between';
import { styles } from '@actual-app/components/styles';
import { theme } from '@actual-app/components/theme';
import { type PayeeEntity } from 'loot-core/types/models';
@@ -11,84 +12,82 @@ import { type PayeeEntity } from 'loot-core/types/models';
import { PayeeRuleCountLabel } from '@desktop-client/components/payees/PayeeRuleCountLabel';
type PayeesListItemProps = {
payee: PayeeEntity;
value: PayeeEntity;
ruleCount: number;
onPress: () => void;
};
} & Omit<GridListItemProps<PayeeEntity>, 'value'>;
export function PayeesListItem({
payee,
export const PayeesListItem = memo(function PayeeListItem({
value: payee,
ruleCount,
onPress,
...props
}: PayeesListItemProps) {
const { t } = useTranslation();
return (
<Button
variant="bare"
style={{
minHeight: 56,
width: '100%',
borderRadius: 0,
borderWidth: '0 0 1px 0',
borderColor: theme.tableBorder,
borderStyle: 'solid',
backgroundColor: theme.tableBackground,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
padding: '12px 16px',
gap: 5,
}}
onPress={onPress}
>
{payee.favorite && (
<SvgBookmark
width={15}
height={15}
style={{
color: theme.pageText,
flexShrink: 0,
}}
/>
)}
<SpaceBetween
style={{
justifyContent: 'space-between',
flex: 1,
alignItems: 'flex-start',
}}
>
<span
style={{
fontSize: 15,
fontWeight: 500,
color: payee.transfer_acct ? theme.pageTextSubdued : theme.pageText,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
flex: 1,
textAlign: 'left',
}}
title={payee.name}
>
{(payee.transfer_acct ? t('Transfer: ') : '') + payee.name}
</span>
const label = payee.transfer_acct
? t('Transfer: {{name}}', { name: payee.name })
: payee.name;
<span
return (
<GridListItem
id={payee.id}
value={payee}
textValue={label}
style={styles.mobileListItem}
{...props}
>
<SpaceBetween gap={5}>
{payee.favorite && (
<SvgBookmark
aria-hidden
focusable={false}
width={15}
height={15}
style={{
color: theme.pageText,
flexShrink: 0,
}}
/>
)}
<SpaceBetween
style={{
borderRadius: 4,
padding: '3px 6px',
backgroundColor: theme.noticeBackground,
border: '1px solid ' + theme.noticeBackground,
color: theme.noticeTextDark,
fontSize: 12,
flexShrink: 0,
justifyContent: 'space-between',
flex: 1,
alignItems: 'flex-start',
}}
>
<PayeeRuleCountLabel count={ruleCount} style={{ fontSize: 12 }} />
</span>
<span
style={{
fontSize: 15,
fontWeight: 500,
color: payee.transfer_acct
? theme.pageTextSubdued
: theme.pageText,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
flex: 1,
textAlign: 'left',
}}
title={label}
>
{label}
</span>
<span
style={{
borderRadius: 4,
padding: '3px 6px',
backgroundColor: theme.noticeBackground,
border: '1px solid ' + theme.noticeBackground,
color: theme.noticeTextDark,
fontSize: 12,
flexShrink: 0,
}}
>
<PayeeRuleCountLabel count={ruleCount} style={{ fontSize: 12 }} />
</span>
</SpaceBetween>
</SpaceBetween>
</Button>
</GridListItem>
);
}
});

View File

@@ -180,7 +180,7 @@ export class LiveQuery<TResponse = unknown> {
protected fetchData = async (
runQuery: () => Promise<{
data: Data<TResponse>;
data: Data<TResponse> | TResponse;
dependencies: string[];
}>,
) => {
@@ -205,7 +205,15 @@ export class LiveQuery<TResponse = unknown> {
// still subscribed (`this.unsubscribeSyncEvent` will exist)
if (this.inflightRequestId === reqId && this._unsubscribeSyncEvent) {
const previousData = this.data;
this.data = data;
// For calculate queries, data is a raw value, not an array
// Convert it to an array format to maintain consistency
if (this._query.state.calculation) {
this.data = [data as TResponse];
} else {
this.data = data as Data<TResponse>;
}
this.onData(this.data, previousData);
this.inflightRequestId = null;
}

View File

@@ -3,7 +3,7 @@
"author": "Actual",
"productName": "Actual",
"description": "A simple and powerful personal finance system",
"version": "25.9.0",
"version": "25.10.0",
"scripts": {
"clean": "rm -rf dist",
"update-client": "bin/update-client",

View File

@@ -434,10 +434,6 @@ export class CategoryTemplateContext {
t.type === 'limit' ||
t.type === 'remainder',
)) {
if (this.limitCheck) {
throw new Error('Only one `up to` allowed per category');
}
let limitDef;
if (template.type === 'limit') {
limitDef = template;
@@ -449,6 +445,10 @@ export class CategoryTemplateContext {
}
}
if (this.limitCheck) {
throw new Error('Only one `up to` allowed per category');
}
if (limitDef.period === 'daily') {
const numDays = monthUtils.differenceInCalendarDays(
monthUtils.addMonths(this.month, 1),

View File

@@ -5,6 +5,7 @@ import { v4 as uuidv4 } from 'uuid';
import { captureBreadcrumb } from '../../platform/exceptions';
import * as connection from '../../platform/server/connection';
import { logger } from '../../platform/server/log';
import { currentDay, dayFromDate, parseDate } from '../../shared/months';
import { q } from '../../shared/query';
import {
@@ -559,8 +560,12 @@ app.events.on('sync', ({ type }) => {
type === 'success' || type === 'error' || type === 'unauthorized';
if (completeEvent && prefs.getPrefs()) {
const { lastScheduleRun } = prefs.getPrefs();
if (!db.getDatabase()) {
logger.info('database is not available, skipping schedule service');
return;
}
const { lastScheduleRun } = prefs.getPrefs();
if (lastScheduleRun !== currentDay()) {
runMutator(() => advanceSchedulesService(type === 'success'));

View File

@@ -3,7 +3,12 @@ import { t } from 'i18next';
import { FieldValueTypes, RuleConditionOp } from '../types/models';
import { integerToAmount, amountToInteger, currencyToAmount } from './util';
import {
integerToAmount,
amountToInteger,
currencyToAmount,
getNumberFormat,
} from './util';
// For now, this info is duplicated from the backend. Figure out how
// to share it later.
@@ -374,10 +379,22 @@ export function makeValue(value, cond) {
switch (cond.type) {
case 'number': {
if (cond.op !== 'isbetween') {
const stringValue = String(value);
const { decimalSeparator } = getNumberFormat();
// preserve trailing decimal separator to allow decimal input during typing
if (stringValue && stringValue.endsWith(decimalSeparator)) {
return {
...cond,
error: null,
value: stringValue,
};
}
return {
...cond,
error: null,
value: value ? currencyToAmount(String(value)) || 0 : 0,
value: value ? currencyToAmount(stringValue) || 0 : 0,
};
}
break;

View File

@@ -1,6 +1,6 @@
{
"name": "@actual-app/sync-server",
"version": "25.9.0",
"version": "25.10.0",
"license": "MIT",
"description": "actual syncing server",
"bin": {

View File

@@ -162,8 +162,8 @@ function getAccountResponse(results, accountId, startDate) {
return;
}
const needsAttention = results.sferrors.find(
e => e === `Connection to ${account.org.name} may need attention`,
const needsAttention = results.sferrors.find(e =>
e.startsWith(`Connection to ${account.org.name} may need attention`),
);
if (needsAttention) {
logAccountError(results, accountId, {

View File

@@ -1,6 +0,0 @@
---
category: Features
authors: [passabilities]
---
Adds net worth graph for each account page.

View File

@@ -1,6 +0,0 @@
---
category: Bugfix
authors: [Triscal]
---
Improved the handling of schedules that have the same payee, amount, account, and date when posted by the schedule automatically or manually via the previews.

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [joel-jeremy]
---
Implement react compiler to take advantage of some performance improvements

View File

@@ -1,6 +0,0 @@
---
category: Bugfix
authors: [e13h]
---
Remove auto-scrolling behavior when editing split transactions on mobile

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [karimkodera]
---
Introduction of APIs to handle Schedule + a bit more. Refer to updated API documentation PR2811 on documentation.

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [joel-jeremy]
---
Re-design mobile accounts page to better match the transactions and budget tables/list + add all accounts page

View File

@@ -1,6 +0,0 @@
---
category: Bugfix
authors: [itsbekas]
---
Fix range calculator on the MonthPicker component

View File

@@ -1,6 +0,0 @@
---
category: Features
authors: [jfdoming]
---
Add tools to migrate/un-migrate to/from UI automations

View File

@@ -1,7 +0,0 @@
---
category: Maintenance
authors: [MatissJanis]
---
Add issue types for bug reports and feature requests in GitHub issue templates.

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [MatissJanis]
---
Add error boundary for modal windows

View File

@@ -1,6 +0,0 @@
---
category: Features
authors: [misu-dev]
---
Add BRL, JMD, RSD, RUB, THB and UAH currencies

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [jfdoming]
---
Fix version bump logic to work if the month has rolled over

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [michaelsanford]
---
Add NO_COLOR standard environment flag to sync-server logging (https://no-color.org/).

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [joel-jeremy]
---
Remove usage of a raw variable in Account component

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [joel-jeremy]
---
Remove usage of a raw variable in AccountAutocomplete component

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [joel-jeremy]
---
Remove usage of a raw variable in CategoryAutocomplete component

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [joel-jeremy]
---
Remove usage of a raw variable in PayeeAutocomplete component

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [biolan]
---
Add Romanian and Moldovan Leu currencies

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [joel-jeremy]
---
Optimize usage of useScrollListener and useTransactionsSearch

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [MikesGlitch]
---
Fix local dockerfile build memory allocation

View File

@@ -1,6 +0,0 @@
---
category: Bugfix
authors: [Nalin-Gupta]
---
Fix issue where marking existing transactions as transfer switches the date and notes of the two transactions

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [xshalan]
---
Added support for Egyptian Pound (EGP) and Saudi Riyal (SAR) currencies.

View File

@@ -1,6 +0,0 @@
---
category: Bugfix
authors: [mauroartizzu]
---
Fixes detailedAccounts that might me null or undefined with some Italian Banks (Widiba, Poste, Intesa)

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [matt-fidd]
---
Bump vite from 6.3.5 to 6.3.6

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [youngcw]
---
Add bank sync option to only import account balance

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [MikesGlitch]
---
Update Electron to the latest stable version

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [matt-fidd]
---
Remove `BANKS_WITH_LIMITED_HISTORY` override array for GoCardless

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [joel-jeremy]
---
Remove usage of raw variables in renders

View File

@@ -1,6 +0,0 @@
---
category: Features
authors: [MikesGlitch]
---
Add a setting to disable update notifications

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [MatissJanis]
---
Mobile: open recurring schedule picker in modal

View File

@@ -1,6 +0,0 @@
---
category: Bugfix
authors: [jgeneaguilar]
---
Change account menu popover width to minWidth to accommodate different text lengths when switching languages

View File

@@ -1,6 +0,0 @@
---
category: Bugfix
authors: [mgibson-scottlogic]
---
Exclude hidden categories from "Copy last month's budget" when copying whole month

View File

@@ -1,6 +0,0 @@
---
category: Bugfix
authors: [milanalexandre]
---
Force the display of the 'Schedules transaction' badge on a single line

View File

@@ -1,6 +0,0 @@
---
category: Bugfix
authors: [misu-dev]
---
Fixes Spending Card crash

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [dirk-apers]
---
Add BPER Italy bank parser (BPER_RETAIL_BPMOIT22)

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [StephenBrown2]
---
Dynamically update currency display options

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [joel-jeremy]
---
Optimize scroll provider and replace usage of debounce package with lodash's debounce

View File

@@ -1,6 +0,0 @@
---
category: Bugfix
authors: [matt-fidd]
---
Fix scrolling inside modals on iOS 26

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [csenel]
---
Extend report end date to the latest transaction date

View File

@@ -1,6 +0,0 @@
---
category: Features
authors: [amrawadk]
---
Add 'dryRun' option to importTransactions API.

View File

@@ -1,6 +0,0 @@
---
category: Bugfix
authors: [matt-fidd]
---
Fix timestamp error when shutting down API with no budget loaded

View File

@@ -1,6 +0,0 @@
---
category: Bugfix
authors: [matt-fidd]
---
Improve cleanup when opening multiple budgets through the API

View File

@@ -1,6 +0,0 @@
---
category: Bugfix
authors: [thromer]
---
Optimise the way payee information is fetched.

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [matt-fidd]
---
Add quiet mode to API

View File

@@ -1,6 +0,0 @@
---
category: Bugfix
authors: [milanalexandre]
---
Translations of category labels in the TransactionEdit component (mobile)

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [MatissJanis]
---
Mobile rules page - set min width for value editor

View File

@@ -1,7 +0,0 @@
---
category: Features
authors: [MatissJanis]
---
Create a mobile payees list page with search and filtering capabilities.

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [MatissJanis]
---
TypeScript: move ImportTransactionsModal to ts

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [youngcw]
---
Add limit type template for use in future template GUI

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [MatissJanis]
---
Mobile payees - clicking takes to appropriate pages

View File

@@ -1,6 +0,0 @@
---
category: Bugfix
authors: [milanalexandre]
---
Translated (No payee) for a scheduled transaction

View File

@@ -1,6 +0,0 @@
---
category: Bugfix
authors: [matt-fidd]
---
Ensure file upload size limits are respected when syncing files

View File

@@ -1,7 +0,0 @@
---
category: Features
authors: [lelemm]
---
Add Express-based CORS proxy with rate limiting, logging, and GitHub token authentication support.

View File

@@ -1,7 +0,0 @@
---
category: Bugfix
authors: [MatissJanis]
---
Fix token expiration parsing to accept numeric strings and validate special string formats.

View File

@@ -1,7 +0,0 @@
---
category: Enhancements
authors: [MatissJanis]
---
Add data-1p-ignore attribute to transaction amount inputs to prevent 1Password autofill.

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [MikesGlitch]
---
Downgrade Ubuntu image in electron build runner for more compatibility with older distros

View File

@@ -1,6 +0,0 @@
---
category: Bugfix
authors: [matt-fidd]
---
Fix live report time ranges

View File

@@ -1,6 +0,0 @@
---
category: Bugfix
authors: [matt-fidd]
---
Fix deprecation warning in count-points script