chore: replace Prettier with oxfmt and add oxlint (#6259)

* chore: replace Prettier with oxfmt and add oxlint

- Replace Prettier with oxfmt (Prettier-compatible formatter from OXC project)
- Add oxlint for fast linting alongside ESLint
- Add eslint-plugin-oxlint to disable ESLint rules covered by oxlint
- Update lint scripts to run oxfmt, oxlint, then eslint
- Update lint-staged configuration for pre-commit hooks
- Create .oxfmtrc.json and .oxlintrc.json configuration files
- Remove .prettierrc.json and .prettierignore
- Reformat codebase with oxfmt

* chore: update dependencies in yarn.lock

* chore: update oxlint configuration to disable additional rules

* chore: update oxfmt and oxlint configurations, enhance test readability
This commit is contained in:
Matiss Janis Aboltins
2025-12-04 19:55:10 +00:00
committed by GitHub
parent 76dadfa5fb
commit 879fd1b054
33 changed files with 1133 additions and 1692 deletions
-1
View File
@@ -4,7 +4,6 @@
// 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 fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');
+8
View File
@@ -0,0 +1,8 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"singleQuote": true,
"trailingComma": "all",
"arrowParens": "avoid",
"printWidth": 80
}
+77
View File
@@ -0,0 +1,77 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["react", "typescript", "import", "jsx-a11y"],
"rules": {
// TODO fix all these and re-enable
"import/namespace": "off",
"jsx-a11y/alt-text": "off",
"jsx-a11y/aria-role": "off",
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/label-has-associated-control": "off",
"jsx-a11y/prefer-tag-over-role": "off",
"jsx-a11y/role-has-required-aria-props": "off",
"jsx-a11y/tabindex-no-positive": "off",
"no-async-promise-executor": "off",
"no-autofocus": "off",
"no-constant-condition": "off",
"no-empty-pattern": "off",
"no-extra-boolean-cast": "off",
"no-unused-vars": "off",
"no-useless-catch": "off",
"react/jsx-key": "off",
},
"overrides": [
{
// TODO: fix the issues in these files
"files": [
"packages/component-library/src/Menu.tsx",
"packages/desktop-client/src/components/accounts/Account.jsx",
"packages/desktop-client/src/components/accounts/MobileAccount.jsx",
"packages/desktop-client/src/components/accounts/MobileAccounts.jsx",
"packages/desktop-client/src/components/budget/BudgetCategories.jsx",
"packages/desktop-client/src/components/budget/BudgetSummaries.tsx",
"packages/desktop-client/src/components/budget/DynamicBudgetTable.tsx",
"packages/desktop-client/src/components/budget/envelope/HoldMenu.tsx",
"packages/desktop-client/src/components/budget/envelope/TransferMenu.tsx",
"packages/desktop-client/src/components/budget/index.tsx",
"packages/desktop-client/src/components/budget/MobileBudget.tsx",
"packages/desktop-client/src/components/FinancesApp.tsx",
"packages/desktop-client/src/components/GlobalKeys.ts",
"packages/desktop-client/src/components/LoggedInUser.tsx",
"packages/desktop-client/src/components/manager/ManagementApp.jsx",
"packages/desktop-client/src/components/manager/subscribe/common.tsx",
"packages/desktop-client/src/components/ManageRules.tsx",
"packages/desktop-client/src/components/mobile/MobileAmountInput.jsx",
"packages/desktop-client/src/components/mobile/MobileNavTabs.tsx",
"packages/desktop-client/src/components/Modals.tsx",
"packages/desktop-client/src/components/modals/EditRule.jsx",
"packages/desktop-client/src/components/modals/ImportTransactions.jsx",
"packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.tsx",
"packages/desktop-client/src/components/modals/MergeUnusedPayees.jsx",
"packages/desktop-client/src/components/Notifications.tsx",
"packages/desktop-client/src/components/payees/ManagePayees.jsx",
"packages/desktop-client/src/components/payees/ManagePayeesWithData.jsx",
"packages/desktop-client/src/components/payees/PayeeTable.tsx",
"packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTable.tsx",
"packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableTotals.tsx",
"packages/desktop-client/src/components/reports/reports/CashFlowCard.jsx",
"packages/desktop-client/src/components/reports/reports/CustomReport.jsx",
"packages/desktop-client/src/components/reports/reports/CustomReport.tsx",
"packages/desktop-client/src/components/reports/reports/NetWorthCard.jsx",
"packages/desktop-client/src/components/reports/SaveReportName.tsx",
"packages/desktop-client/src/components/reports/useReport.ts",
"packages/desktop-client/src/components/schedules/ScheduleDetails.jsx",
"packages/desktop-client/src/components/schedules/ScheduleEditModal.tsx",
"packages/desktop-client/src/components/schedules/SchedulesTable.tsx",
"packages/desktop-client/src/components/select/DateSelect.tsx",
"packages/desktop-client/src/components/sidebar/Tools.tsx",
"packages/desktop-client/src/components/sort.tsx",
"packages/desktop-client/src/hooks/useEffectAfterMount.ts",
"packages/desktop-client/src/hooks/useQuery.ts",
],
"rules": {
"react-hooks/exhaustive-deps": "off",
},
},
]
}
-36
View File
@@ -1,36 +0,0 @@
sync_pb.*
packages/api/app/bundle.api.js
packages/api/app/stats.json
packages/api/dist
packages/api/@types
packages/api/migrations
packages/crdt/dist
packages/component-library/src/icons/**/*
packages/desktop-client/bundle.browser.js
packages/desktop-client/stats.json
packages/desktop-client/.swc/
packages/desktop-client/build/
packages/desktop-client/dev-dist/
packages/desktop-client/locale/
packages/desktop-client/build-electron/
packages/desktop-client/build-stats/
packages/desktop-client/public/kcab/
packages/desktop-client/public/data/
packages/desktop-client/**/node_modules/*
packages/desktop-client/node_modules/
packages/desktop-client/test-results/
packages/desktop-client/playwright-report/
packages/desktop-electron/client-build/
packages/desktop-electron/build/
packages/desktop-electron/dist/
packages/loot-core/**/node_modules/*
packages/loot-core/**/lib-dist/*
packages/loot-core/**/proto/*
packages/sync-server/coverage/
packages/sync-server/user-files/
packages/sync-server/server-files/
.yarn/*
upcoming-release-notes/*
# temporary
packages/docs/*
-5
View File
@@ -1,5 +0,0 @@
{
"singleQuote": true,
"trailingComma": "all",
"arrowParens": "avoid"
}
+4 -48
View File
@@ -2,6 +2,7 @@ import tsParser from '@typescript-eslint/parser';
import { defineConfig } from 'eslint/config';
import pluginImport from 'eslint-plugin-import';
import pluginJSXA11y from 'eslint-plugin-jsx-a11y';
import oxlint from 'eslint-plugin-oxlint';
import pluginReact from 'eslint-plugin-react';
import pluginReactHooks from 'eslint-plugin-react-hooks';
import pluginTypescriptPaths from 'eslint-plugin-typescript-paths';
@@ -721,54 +722,6 @@ export default defineConfig(
},
},
{
// TODO: fix the issues in these files
files: [
'packages/desktop-client/src/components/accounts/Account.jsx',
'packages/desktop-client/src/components/accounts/MobileAccount.jsx',
'packages/desktop-client/src/components/accounts/MobileAccounts.jsx',
'packages/desktop-client/src/components/budget/BudgetCategories.jsx',
'packages/desktop-client/src/components/budget/BudgetSummaries.tsx',
'packages/desktop-client/src/components/budget/DynamicBudgetTable.tsx',
'packages/desktop-client/src/components/budget/index.tsx',
'packages/desktop-client/src/components/budget/MobileBudget.tsx',
'packages/desktop-client/src/components/budget/envelope/HoldMenu.tsx',
'packages/desktop-client/src/components/budget/envelope/TransferMenu.tsx',
'packages/component-library/src/Menu.tsx',
'packages/desktop-client/src/components/FinancesApp.tsx',
'packages/desktop-client/src/components/GlobalKeys.ts',
'packages/desktop-client/src/components/LoggedInUser.tsx',
'packages/desktop-client/src/components/manager/ManagementApp.jsx',
'packages/desktop-client/src/components/manager/subscribe/common.tsx',
'packages/desktop-client/src/components/ManageRules.tsx',
'packages/desktop-client/src/components/mobile/MobileAmountInput.jsx',
'packages/desktop-client/src/components/mobile/MobileNavTabs.tsx',
'packages/desktop-client/src/components/Modals.tsx',
'packages/desktop-client/src/components/modals/EditRule.jsx',
'packages/desktop-client/src/components/modals/ImportTransactions.jsx',
'packages/desktop-client/src/components/modals/MergeUnusedPayees.jsx',
'packages/desktop-client/src/components/Notifications.tsx',
'packages/desktop-client/src/components/payees/ManagePayees.jsx',
'packages/desktop-client/src/components/payees/ManagePayeesWithData.jsx',
'packages/desktop-client/src/components/payees/PayeeTable.tsx',
'packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTable.tsx',
'packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableTotals.tsx',
'packages/desktop-client/src/components/reports/reports/CashFlowCard.jsx',
'packages/desktop-client/src/components/reports/reports/CustomReport.jsx',
'packages/desktop-client/src/components/reports/reports/NetWorthCard.jsx',
'packages/desktop-client/src/components/reports/SaveReportName.tsx',
'packages/desktop-client/src/components/reports/useReport.ts',
'packages/desktop-client/src/components/schedules/ScheduleDetails.jsx',
'packages/desktop-client/src/components/schedules/SchedulesTable.tsx',
'packages/desktop-client/src/components/select/DateSelect.tsx',
'packages/desktop-client/src/components/sidebar/Tools.tsx',
'packages/desktop-client/src/components/sort.tsx',
],
rules: {
'react-hooks/exhaustive-deps': 'off',
},
},
{
files: [
'eslint.config.mjs',
@@ -811,4 +764,7 @@ export default defineConfig(
'import/no-default-export': 'off',
},
},
// Disable ESLint rules that are already covered by oxlint
// This must be at the end to override previous rule configurations
...oxlint.configs['flat/recommended'],
);
+11 -7
View File
@@ -51,8 +51,8 @@
"vrt:docker": "./bin/run-vrt",
"rebuild-electron": "./node_modules/.bin/electron-rebuild -m ./packages/loot-core",
"rebuild-node": "yarn workspace loot-core rebuild",
"lint": "prettier --check . && eslint . --max-warnings 0",
"lint:fix": "prettier --check --write . && eslint . --max-warnings 0 --fix",
"lint": "oxfmt --check . && oxlint --deny-warnings && eslint . --max-warnings 0",
"lint:fix": "oxfmt . && oxlint --deny-warnings --fix && eslint . --max-warnings 0 --fix",
"install:server": "yarn workspaces focus @actual-app/sync-server --production",
"typecheck": "yarn tsc --incremental && tsc-strict",
"jq": "./node_modules/node-jq/bin/jq",
@@ -65,10 +65,10 @@
"@typescript-eslint/parser": "^8.46.4",
"cross-env": "^10.1.0",
"eslint": "^9.39.1",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-oxlint": "^1.30.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-typescript-paths": "^0.0.33",
@@ -80,8 +80,9 @@
"minimatch": "^10.1.1",
"node-jq": "^6.3.1",
"npm-run-all": "^4.1.5",
"oxfmt": "^0.15.0",
"oxlint": "^1.30.0",
"p-limit": "^7.2.0",
"prettier": "^3.6.2",
"prompts": "^2.4.2",
"source-map-support": "^0.5.21",
"ts-node": "^10.9.2",
@@ -98,9 +99,12 @@
"yarn": "^4.9.1"
},
"lint-staged": {
"*.{js,mjs,jsx,ts,tsx,md,json,yml}": [
"eslint --fix",
"prettier --write"
"*.{js,mjs,jsx,ts,tsx,md,json,yml,yaml}": [
"oxfmt --no-error-on-unmatched-pattern"
],
"*.{js,mjs,jsx,ts,tsx}": [
"oxlint --deny-warnings --fix",
"eslint --max-warnings 0 --fix"
]
},
"packageManager": "[email protected]",
@@ -1,6 +1,9 @@
import { type Config } from '@svgr/core';
const tmpl: Config['template'] = ({ imports, interfaces, componentName, props, jsx }, { tpl }) => {
const tmpl: Config['template'] = (
{ imports, interfaces, componentName, props, jsx },
{ tpl },
) => {
return tpl`
${imports};
+1 -1
View File
@@ -16,7 +16,7 @@ protoc --plugin="protoc-gen-ts=../../node_modules/.bin/protoc-gen-ts" \
--proto_path=src/proto \
sync.proto
../../node_modules/.bin/prettier --write src/proto/*.d.ts
../../node_modules/.bin/oxfmt src/proto/*.d.ts
for file in src/proto/*.d.ts; do
{ echo "/* eslint-disable @typescript-eslint/no-namespace */"; sed 's/export class/export declare class/g' "$file"; } > "${file%.d.ts}.ts"
File diff suppressed because it is too large Load Diff
@@ -320,9 +320,9 @@ export const downloadBudget = createAppAsyncThunk(
const opts = {
hasExistingKey: Boolean(
error.meta &&
typeof error.meta === 'object' &&
'isMissingKey' in error.meta &&
error.meta.isMissingKey,
typeof error.meta === 'object' &&
'isMissingKey' in error.meta &&
error.meta.isMissingKey,
),
cloudFileId,
onSuccess: () => {
@@ -132,7 +132,7 @@ function AppInner() {
initAll().catch(showErrorBoundary);
// Removed cloudFileId & t from dependencies to prevent hard crash when closing budget in Electron
// eslint-disable-next-line react-hooks/exhaustive-deps
// oxlint-disable-next-line react-hooks/exhaustive-deps
}, [dispatch, showErrorBoundary]);
useEffect(() => {
@@ -70,7 +70,7 @@ export function MobileNavTabs() {
config: canceled ? config.wobbly : config.stiff,
});
},
[api, OPEN_FULL_Y],
[api],
);
const openDefault = useCallback(
@@ -82,7 +82,7 @@ export function MobileNavTabs() {
config: { ...config.stiff, velocity },
});
},
[api, OPEN_DEFAULT_Y],
[api],
);
const hide = useCallback(
@@ -94,7 +94,7 @@ export function MobileNavTabs() {
config: { ...config.stiff, velocity },
});
},
[api, HIDDEN_Y],
[api],
);
const navTabs = [
@@ -381,7 +381,7 @@ function SelectedTransactionsFloatingActionBar({
const canMerge = useMemo(() => {
return Boolean(
twoTransactions &&
twoTransactions[0].amount === twoTransactions[1].amount,
twoTransactions[0].amount === twoTransactions[1].amount,
);
}, [twoTransactions]);
@@ -95,8 +95,7 @@ export function CreateEncryptionKeyModal({
We will generate a key based on a password and use it to
encrypt from now on.{' '}
<strong>This requires a sync reset</strong> and all other
devices will have to revert to this version of your
data.{' '}
devices will have to revert to this version of your data.{' '}
</Trans>
<Link
variant="external"
@@ -746,7 +746,6 @@ export function ImportTransactionsModal({
runImportPreview();
// intentionally exclude runImportPreview from dependencies to avoid infinite rerenders
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
filetype,
flipAmount,
@@ -394,7 +394,6 @@ function CustomReportInner({ report: initialReport }: CustomReportInnerProps) {
run();
// omitted `conditions` and `conditionsOp` from dependencies to avoid infinite loops
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
interval,
dateRange,
@@ -350,7 +350,6 @@ export function ScheduleEditModal({ id, transaction }: ScheduleEditModalProps) {
}
run();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
@@ -138,7 +138,7 @@ export function SelectedTransactionsButton({
const canMerge = useMemo(() => {
return Boolean(
twoTransactions &&
twoTransactions[0].amount === twoTransactions[1].amount,
twoTransactions[0].amount === twoTransactions[1].amount,
);
}, [twoTransactions]);
@@ -19,6 +19,5 @@ export function useEffectAfterMount(
return effect();
}
isFirstRender.current = false;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);
}
@@ -17,7 +17,6 @@ export function useQuery<Response = unknown>(
// Memo the resulting query. We don't care if the function
// that creates the query changes, only the resulting query.
// Safe to ignore the eslint warning here.
// eslint-disable-next-line react-hooks/exhaustive-deps
const query = useMemo(makeQuery, dependencies);
const [data, setData] = useState<ReadonlyArray<Response> | null>(null);
@@ -1,4 +1,3 @@
/* eslint-disable no-empty-pattern */
/* eslint-disable react-hooks/rules-of-hooks */
import path from 'node:path';
+5 -2
View File
@@ -137,7 +137,10 @@ const sidebars = {
type: 'doc',
id: 'budgeting/credit-cards/index',
},
items: ['budgeting/credit-cards/paying-in-full', 'budgeting/credit-cards/carrying-debt'],
items: [
'budgeting/credit-cards/paying-in-full',
'budgeting/credit-cards/carrying-debt',
],
},
'budgeting/multi-currency',
'budgeting/joint-accounts',
@@ -224,7 +227,7 @@ const sidebars = {
'experimental/monthly-cleanup',
'experimental/rule-templating',
'experimental/pluggyai',
'experimental/crossover-point-report'
'experimental/crossover-point-report',
],
},
'getting-started/tips-tricks',
+229 -261
View File
@@ -41,38 +41,24 @@ export let objects = {
{
name: 'serverURL',
type: 'string',
description: (
<span>
The URL of your Actual Budget server.
</span>
),
description: <span>The URL of your Actual Budget server.</span>,
},
{
name: 'password',
type: 'string',
description: (
<span>
The password of your Actual Budget server.
</span>
),
description: <span>The password of your Actual Budget server.</span>,
},
{
name: 'dataDir',
type: 'string',
description: (
<span>
The directory to store locally cached budget files.
</span>
<span>The directory to store locally cached budget files.</span>
),
},
{
name: 'verbose',
type: 'boolean',
description: (
<span>
Enable/disable logging from actual internals
</span>
),
description: <span>Enable/disable logging from actual internals</span>,
},
],
@@ -126,8 +112,8 @@ export let objects = {
type: 'string',
description: (
<span>
If a transfer, the <code>id</code> of the corresponding transaction in the other
account. See <a href="#transfers">transfers</a>.
If a transfer, the <code>id</code> of the corresponding transaction in
the other account. See <a href="#transfers">transfers</a>.
</span>
),
},
@@ -219,190 +205,192 @@ export let objects = {
},
],
schedule : [
{
name: 'id',
type: types.id
},
{
name: 'name',
type: 'string',
description: (
<span>
Not mandatory but schedule names must be unique.
</span>
),
},
{
name: 'rule',
type: 'string',
description: (
<span>
All schedules have an associated underlying rule. Not to be supplied with a new schedule. It will be auto created. Rules can not updated to another rule. You can however edit the rule with the API above for Rule.
</span>
),
},
{
name: 'next_date',
type: 'string',
description: (
<span>
Next occurrence of a schedule. Not to be supplied with a new schedule.
</span>
),
},
{
name: 'completed',
type: 'boolean',
description: (
<span>
Not to be supplied with a new schedule.
</span>
),
},
{
name: 'posts_transaction',
type: 'boolean',
description: (
<span>
Whether the schedule should auto-post transactions on your behalf. Defaults to <code>false</code>.
</span>
),
},
{
name: 'payee',
type: 'id | null',
description: (
<span>
Optional; will default to <code>null</code>.
</span>
),
},
{
name: 'account',
type: 'id | null',
description: (
<span>
Optional; will default to <code>null</code>.
</span>
),
},
{
name: 'amount',
type: 'number | { num1: number; num2: number }',
description: (
<span>
Provide only one number, except if the amount uses a isbetween in amountOp, in this case num1 and 2 should be provided.
</span>
),
},
{
name: 'amountOp',
type: "'is' | 'isapprox' | 'isbetween'",
description: (
<span>
Controls how <code>amount</code> is interpreted.
</span>
),
},
{
name: 'date',
type: 'date | RecurConfig',
required: true,
description: (
<span>
Mandatory field when creating a schedule. If the schedule is a single occurrence just supply the date. otherwise refer to RecurConfig details below.
</span>
),
},
],
schedule: [
{
name: 'id',
type: types.id,
},
{
name: 'name',
type: 'string',
description: (
<span>Not mandatory but schedule names must be unique.</span>
),
},
{
name: 'rule',
type: 'string',
description: (
<span>
All schedules have an associated underlying rule. Not to be supplied
with a new schedule. It will be auto created. Rules can not updated to
another rule. You can however edit the rule with the API above for
Rule.
</span>
),
},
{
name: 'next_date',
type: 'string',
description: (
<span>
Next occurrence of a schedule. Not to be supplied with a new schedule.
</span>
),
},
{
name: 'completed',
type: 'boolean',
description: <span>Not to be supplied with a new schedule.</span>,
},
{
name: 'posts_transaction',
type: 'boolean',
description: (
<span>
Whether the schedule should auto-post transactions on your behalf.
Defaults to <code>false</code>.
</span>
),
},
{
name: 'payee',
type: 'id | null',
description: (
<span>
Optional; will default to <code>null</code>.
</span>
),
},
{
name: 'account',
type: 'id | null',
description: (
<span>
Optional; will default to <code>null</code>.
</span>
),
},
{
name: 'amount',
type: 'number | { num1: number; num2: number }',
description: (
<span>
Provide only one number, except if the amount uses a isbetween in
amountOp, in this case num1 and 2 should be provided.
</span>
),
},
{
name: 'amountOp',
type: "'is' | 'isapprox' | 'isbetween'",
description: (
<span>
Controls how <code>amount</code> is interpreted.
</span>
),
},
{
name: 'date',
type: 'date | RecurConfig',
required: true,
description: (
<span>
Mandatory field when creating a schedule. If the schedule is a single
occurrence just supply the date. otherwise refer to RecurConfig
details below.
</span>
),
},
],
recurConfig : [
{
name: 'frequency',
type: `'daily' | 'weekly' | 'monthly' | 'yearly'`,
required: true,
description: (
<span>
How often the schedule repeats.
</span>
),
},
{
name: 'interval',
type: 'number',
description: (
<span>
The interval at which the recurrence happens. Defaults to <code>1</code> if omitted.
</span>
),
},
{
name: 'patterns',
type: 'RecurPattern[]',
description: (
<span>
Optional patterns to control specific dates for recurrence (e.g. certain weekdays or month days).
</span>
),
},
{
name: 'skipWeekend',
type: 'boolean',
description: (
<span>
If true, skips weekends when calculating recurrence dates.
</span>
),
},
{
name: 'start',
type: 'string',
required: true,
description: (
<span>
The ISO date string indicating the start date of the recurrence.
</span>
),
},
{
name: 'endMode',
type: `'never' | 'after_n_occurrences' | 'on_date'`,
required: true,
description: (
<span>
Specifies how the recurrence ends: never ends, after a number of occurrences, or on a specific date.
</span>
),
},
{
name: 'endOccurrences',
type: 'number',
description: (
<span>
Used when <code>endMode</code> is <code>'after_n_occurrences'</code>. Indicates how many times it should repeat.
</span>
),
},
{
name: 'endDate',
type: 'string',
description: (
<span>
Used when <code>endMode</code> is <code>'on_date'</code>. The ISO date string indicating when the recurrence should end.
</span>
),
},
{
name: 'weekendSolveMode',
type: `'before' | 'after'`,
description: (
<span>
If a calculated date falls on a weekend and <code>skipWeekend</code> is true, this controls whether the date moves to the <code>before</code> or <code>after</code> weekday.
</span>
),
},
],
recurConfig: [
{
name: 'frequency',
type: `'daily' | 'weekly' | 'monthly' | 'yearly'`,
required: true,
description: <span>How often the schedule repeats.</span>,
},
{
name: 'interval',
type: 'number',
description: (
<span>
The interval at which the recurrence happens. Defaults to{' '}
<code>1</code> if omitted.
</span>
),
},
{
name: 'patterns',
type: 'RecurPattern[]',
description: (
<span>
Optional patterns to control specific dates for recurrence (e.g.
certain weekdays or month days).
</span>
),
},
{
name: 'skipWeekend',
type: 'boolean',
description: (
<span>If true, skips weekends when calculating recurrence dates.</span>
),
},
{
name: 'start',
type: 'string',
required: true,
description: (
<span>
The ISO date string indicating the start date of the recurrence.
</span>
),
},
{
name: 'endMode',
type: `'never' | 'after_n_occurrences' | 'on_date'`,
required: true,
description: (
<span>
Specifies how the recurrence ends: never ends, after a number of
occurrences, or on a specific date.
</span>
),
},
{
name: 'endOccurrences',
type: 'number',
description: (
<span>
Used when <code>endMode</code> is <code>'after_n_occurrences'</code>.
Indicates how many times it should repeat.
</span>
),
},
{
name: 'endDate',
type: 'string',
description: (
<span>
Used when <code>endMode</code> is <code>'on_date'</code>. The ISO date
string indicating when the recurrence should end.
</span>
),
},
{
name: 'weekendSolveMode',
type: `'before' | 'after'`,
description: (
<span>
If a calculated date falls on a weekend and <code>skipWeekend</code>{' '}
is true, this controls whether the date moves to the{' '}
<code>before</code> or <code>after</code> weekday.
</span>
),
},
],
payee: [
{ name: 'id', type: types.id },
@@ -429,23 +417,24 @@ recurConfig : [
rule: [
{ name: 'id', type: types.id },
{
name: 'stage',
type: 'string',
required: true,
description: (
<span>
Must be one of <code>pre</code>, <code>default</code>, or <code>post</code>.
</span>
),
name: 'stage',
type: 'string',
required: true,
description: (
<span>
Must be one of <code>pre</code>, <code>default</code>, or{' '}
<code>post</code>.
</span>
),
},
{
name: 'conditionsOp',
type: 'string',
description: (
<span>
Must be one of <code>and</code> or <code>or</code>.
</span>
),
name: 'conditionsOp',
type: 'string',
description: (
<span>
Must be one of <code>and</code> or <code>or</code>.
</span>
),
},
{ name: 'conditions', type: 'ConditionOrAction[]' },
{ name: 'actions', type: 'ConditionOrAction[]' },
@@ -455,23 +444,24 @@ recurConfig : [
{ name: 'id', type: types.id },
{ name: 'payee_id', type: types.id, required: true },
{
name: 'stage',
type: 'string',
required: true,
description: (
<span>
Must be one of <code>pre</code>, <code>default</code>, or <code>post</code>.
</span>
),
name: 'stage',
type: 'string',
required: true,
description: (
<span>
Must be one of <code>pre</code>, <code>default</code>, or{' '}
<code>post</code>.
</span>
),
},
{
name: 'conditionsOp',
type: 'string',
description: (
<span>
Must be one of <code>and</code> or <code>or</code>.
</span>
),
name: 'conditionsOp',
type: 'string',
description: (
<span>
Must be one of <code>and</code> or <code>or</code>.
</span>
),
},
{ name: 'conditions', type: 'ConditionOrAction[]' },
{ name: 'actions', type: 'ConditionOrAction[]' },
@@ -482,11 +472,7 @@ recurConfig : [
name: 'name',
type: 'string',
required: true,
description: (
<span>
The budget's name.
</span>
),
description: <span>The budget's name.</span>,
},
{
name: 'cloudFileId',
@@ -502,48 +488,30 @@ recurConfig : [
name: 'groupId',
type: 'string',
required: true,
description: (
<span>
The group id for the budget.
</span>
),
description: <span>The group id for the budget.</span>,
},
{
name: 'hasKey',
type: 'boolean',
required: true,
description: (
<span>
If the file has an encryption key.
</span>
),
description: <span>If the file has an encryption key.</span>,
},
{
name: 'encryptKeyId',
type: 'string',
description: (
<span>
The encryption key ID for the file, if it is encrypted.
</span>
<span>The encryption key ID for the file, if it is encrypted.</span>
),
},
{
name: 'state',
type: 'string',
description: (
<span>
Remote files have this set to "remote".
</span>
),
description: <span>Remote files have this set to "remote".</span>,
},
{
name: 'id',
type: 'string',
description: (
<span>
The local budget file's local ID.
</span>
),
description: <span>The local budget file's local ID.</span>,
},
],
};
@@ -13,18 +13,20 @@ With budget templates there is no need to manually fill in each category each mo
With one click you can fill in your entire budget based on the templates you have added.
Here are a few examples of what you can do with templates, all with a single click!
* Budget $ 100 every month
* Budget $ 50 every other week
* Refill a category to $ 300 at the start of a month
* Add all leftover funds to a savings category
* Budget 10% of your income for savings or a tithe
* Budget the average you spend over the last 6 months
* Save up for a big purchase many months in the future, and dynamically figure out the budget needs
* And much more!
- Budget $ 100 every month
- Budget $ 50 every other week
- Refill a category to $ 300 at the start of a month
- Add all leftover funds to a savings category
- Budget 10% of your income for savings or a tithe
- Budget the average you spend over the last 6 months
- Save up for a big purchase many months in the future, and dynamically figure out the budget needs
- And much more!
## Using Templates
### How to add a template
Create a template by adding a note to a category and adding a line that contains `#template` or `#goal` with the appropriate syntax.
The example below shows the most basic template syntax `#template 72.99`.
This will budget $ 72.99 when templates are run without having to manually type in the amount.
@@ -39,30 +41,31 @@ In the budget month menu you will see the following options:
![Apply options on month level](/img/goal-template/goal-template-2.png)
* **Check templates** will test all `#template and #goal` lines for proper syntax.
- **Check templates** will test all `#template and #goal` lines for proper syntax.
* **Apply budget template** will run all templates in categories that currently have 0 budgeted.
This will leave any existing budget amounts intact.
- **Apply budget template** will run all templates in categories that currently have 0 budgeted.
This will leave any existing budget amounts intact.
* **Overwrite with budget template** will fill in all budget cells using the templates and ignore any already existing budget amounts.
This is the recommended method if you are using template priorities.
- **Overwrite with budget template** will fill in all budget cells using the templates and ignore any already existing budget amounts.
This is the recommended method if you are using template priorities.
#### Apply only specific templates
You can also apply selections of templates if you want.
* **Single Category**: Use the "Apply budget template" option shown below from the budget field drop down to apply templates to just that category.
This will overwrite any existing budgeted amount.
- **Single Category**: Use the "Apply budget template" option shown below from the budget field drop down to apply templates to just that category.
This will overwrite any existing budgeted amount.
![Apply templates to single category](/img/goal-template/apply-template-category.png)
* **Apply templates to a single category group**: Use the "Apply budget templates" option shown below from the category group drop down to apply all templates to categories in a specific group.
It will apply to the month furthest to the left in your budget view if viewing multiple months.
This will overwrite any existing budgets in the categories in the group.
- **Apply templates to a single category group**: Use the "Apply budget templates" option shown below from the category group drop down to apply all templates to categories in a specific group.
It will apply to the month furthest to the left in your budget view if viewing multiple months.
This will overwrite any existing budgets in the categories in the group.
![Apply templates to a group of categories](/img/goal-template/apply-template-group.png)
### Goal Indicators
After having run the templates in a given month and category, the status of a respective category goal will be indicated as a text color of the category balance.
The image below shows an example of categories in the following states: normal (no goal set), empty (no goal set), goal met(green), goal not met(orange), and a negative balance(red).
@@ -71,9 +74,11 @@ The image below shows an example of categories in the following states: normal (
This goal value is based on the maximum amount that the templates in a category request to be budgeted.
#### Goal Indicator Information
If you hover over the balance value in a templated category, a tooltip will appear with info on the status of that category with respect to its templates.
#### Long term goals
By default, the goal information displayed in the goal indicator is shown on a monthly basis. It's what fully budgeting your templates would require for **this month**. The indicator color is based on the `Budgeted` amount and whether it meets the amount requested by the category templates.
If you would like the goal and associated indication color to be based on the category `Balance` instead of the budgeted amount, see the [Goal Directive option](#goal-directive).
@@ -117,25 +122,23 @@ Templates can be given a priority flag to change the order that the templates ge
- Template application order is based on the database order, not the view order. To guarantee a specific fill order use separate priorities for each category.
- If you have multiple `schedule` or `by` template lines in a single category, they will be forced to match the same priority level as the line run first.
- It is recommended to use the "overwrite with budget template" option when applying templates if you use priorities.
- **Expected budgeted amount is 200**
- **Expected budgeted amount is 200**
**Expected maximum category balance is 200**
```
#template 300
#template-1 150 up to 200
```
- **Expected budgeted amount is 450**
```
#template 300
#template-1 150 up to 200
```
- **Expected budgeted amount is 450**
**No maximum category balance exists**
```
#template 150 up to 500
#template-1 300
```
` #template 150 up to 500
#template-1 300
`
### Notes on all templates
- All templates are a single line in the category notes. Depending on your screen size, they may visually render as multiple lines. When adding a template, do not put a return (or "enter") within a single template, or the parsing will not properly handle your template.
- All templates are a single line in the category notes. Depending on your screen size, they may visually render as multiple lines. When adding a template, do not put a return (or "enter") within a single template, or the parsing will not properly handle your template.
- Don't add a currency symbol to your template amounts.
- Number formats that use comma for the decimal separator are not supported (e.g., 123,45). You must use 123.45.
- Thousands separators are not supported (e.g., 1,234). You must use 1234.
@@ -147,6 +150,7 @@ Templates can be given a priority flag to change the order that the templates ge
There are many types of templates you can use to tailor how categories get filled to match your personal budgeting needs.
### Simple Type
The simple template type is the most basic type of template.
The base template will budget the amount you ask it to.
Simple!
@@ -155,7 +159,6 @@ This template also has a few variations making it likely the most used template
Below is an example of how it works.
The template budgets just what you ask, no matter how much the respective category has in its balance.
<!-- prettier-ignore -->
|Syntax|Budgeted Amount|
|---|:---:|
|`#template 50`| $ 50 |
@@ -164,7 +167,6 @@ There is also a useful variation of a simple template.
This variation will put a **limit** on how much the balance of a category can be that month.
Here are some examples of how this is used
<!-- prettier-ignore -->
|Syntax| Previous Balance | Budgeted Amount | New Balance |
|---|:---:| :---: |:---:|
|`#template 50 up to 100`| $ 80 | $ 20 | $ 100 |
@@ -178,7 +180,6 @@ The last variation of the simple template is sometimes referred to as a "refill"
With this style the template budgets whatever it takes to hit a "full" amount.
Here is how it works:
<!-- prettier-ignore -->
|Syntax| Previous Balance | Budgeted Amount | New Balance |
|---|:---:| :---: |:---:|
|`#template up to 150`| $ 10 | $ 140 | $ 150 |
@@ -190,11 +191,12 @@ This variation along with the previous variation, are probably the most used tem
They are simple enough to use easily, but are robust enough to make budgeting much simpler.
**Notes**:
- A single category with two templates that use `up to` is not supported.
- If any single template contains an `up to`, the whole category will be subject to that limit even if there are later templates and priorities. This excludes remainders which will run after the limit is applied.
#### All Variations
There is more flexibility with the limit part of the template.
By default, the limit (the "up to" part of the template) is based per month.
You can modify the limit to be per week or per day if that matches your needs better.
@@ -202,7 +204,6 @@ You can also modify the limit to not ever remove funds over your limit.
This can be useful if you get refunds or reimbursements that you would like to have remain inside a category even if over your limit.
Below is examples of these different variations of simple templates.
<!-- prettier-ignore -->
|Syntax|Description|Example Application|
|---|---|---|
|`#template 50`|Budget 50 each month|Regular monthly bills, such as internet|
@@ -218,7 +219,6 @@ The **By** type of template is for saving up funds _by_ a certain month.
For example, it is January and you are trying to save for a new car by the end of the year.
If the new car will cost $ 10,000 and you plan to buy it in December, you will use something like the following:
<!-- prettier-ignore -->
|Syntax| Budgeted Amount |
|---|:---:|
|`#template 10000 by 2025-12` | $ 833.33 |
@@ -228,7 +228,6 @@ The by template is smarter than just 10000/12.
Lets say the example is the same, but you already have $ 1,500 saved.
In that case you get this.
<!-- prettier-ignore -->
|Syntax| Previous Balance | Budgeted Amount | New Balance |
|---|:---:| :---: |:---:|
|`#template 10000 by 2025-12` |$ 1,500 | $ 708.33 | $ 2,208.33 |
@@ -242,31 +241,33 @@ If you have extra funds one month and add that to your car savings, the template
**Note**: The date must be in YYYY-MM format.
#### Repeated savings
The By template can also repeat if your savings is cyclical, such as yearly taxes or insurance.
The repeat period can be both a number of months or number of years.
If you need to repeat based on a number of weeks, use the Week template.
In that case use the following variation:
<!-- prettier-ignore -->
|Syntax| Budgeted Amount | Note |
|---|:---:| --- |
|`#template 500 by 2025-03 repeat every year` | $ 166.66 | Assuming starting in January 2025 |
|`#template 500 by 2025-03 repeat every year` | $ 41.66 | All months after March 2025 |
#### By Spend
The By template can be extended to allow some of the funds to be spent along the way.
This is most commonly used for holiday savings where you will spend some of the money in the months leading up to the holiday.
An example of this is buying Christmas gifts in November and December.
The table below shows how this works.
<!-- prettier-ignore -->
|Syntax| Budgeted Amount | Spent so far| New Balance| Note |
|---|:---:| :---: | --- | --- |
|`#template 500 by 2025-12 spend from 2025-11`| $ 41.66 | 0 | previous + $ 41.66| Assuming starting in January 2025, all months before December |
|`#template 500 by 2025-12 spend from 2025-11`| $ 41.66 | $ 100 | $ 400 | Assuming the beginning of December, but have not spent anything in December yet |
#### Available Variations
Below is a table of the variations of the By template.
<!-- prettier-ignore -->
|Syntax|Description|Example Application|
|---|---|---|
|`#template 500 by 2027-03`|Break down large, less-frequent expenses into manageable monthly expenses|Saving for a replacement car in a few years
@@ -278,13 +279,13 @@ Below is a table of the variations of the By template.
|`#template 500 by 2024-12 spend from 2024-03 repeat every 2 years`| |
### Periodic Type
If you have bills that are due on a regular interval, such as biweekly, every 6 weeks, quarterly, or once per year, this is the template for you!
This template is similar to the simple template, but it will be based on a defined period rather than being budgeted on a monthly basis.
You set a period and the start date, and every period, starting from your start date, you will receive the requested budgeted amount.
Periods can be set for any number of days, weeks, months, or years.
See the table below for examples.
<!-- prettier-ignore -->
|Syntax| Budgeted Amount | Note |
|---|:---:| :---: |
|`#template 10 repeat every week starting 2025-01-06` | $ 50 | When budgeting in March 2025 |
@@ -295,14 +296,15 @@ As you can see, the template will be budgeted based on the defined period.
The Periodic template also supports limits the same way the simple template does.
For example, if you budget in January with a limited template:
<!-- prettier-ignore -->
|Syntax| Previous Balance | Budgeted Amount | New Balance |
|---|:---:| :---: |:---:|
|`#template 10 repeat every week starting 2025-01-06 up to 55` | $ 20 | $ 35 | $ 55 |
#### Available Variations
Below is a table of the variations of the Week template.
<!-- prettier-ignore -->
|Syntax|Description|Example Application|
|---|---|---|
|`#template 10 repeat every day starting 2025-01-01` | Budget 10 per day | Budget for lunches |
@@ -317,21 +319,22 @@ Below is a table of the variations of the Week template.
|`#template 1500 repeat every 2 years starting 2025-01-01` | Budget 1500 in January every other year |
**Notes**:
- The starting date must be in YYYY-MM-DD format.
- A single category with two templates that use `up to` is not supported.
- All limit variations described in the [Simple template](#simple-type) are supported.
- If any single template contains an `up to`, the whole category will be subject to that limit even if there are later templates and priorities.
### Percent Type
The percent template allows you to assign a percent of your income or available funds to a certain category.
Below are the most basic examples.
All the examples assume the following amounts of income in the listed income categories or to budget amount:
* Paycheck - $ 1900
* Dividends - $ 100
* To Budget - $ 1500
<!-- prettier-ignore -->
- Paycheck - $ 1900
- Dividends - $ 100
- To Budget - $ 1500
|Syntax| Budgeted Amount | Note |
|---|:---:| :---: |
|`#template 10% of all income` | $ 200 | Use the total of your income categories in the calculation |
@@ -339,10 +342,10 @@ All the examples assume the following amounts of income in the listed income cat
|`#template 10% of available funds` | $ 150 | Budget 10% of the current to budget funds |
#### Previous Flag
The percent template can also be pointed to the previous month's income if you are using a month ahead budgeting strategy.
Below is an example of how to do that.
<!-- prettier-ignore -->
|Syntax| Budgeted Amount | Note |
|---|:---:| :---: |
|`#template 10% of previous all income` | $ 200 | Use the total of your income categories from the previous month |
@@ -351,23 +354,24 @@ Below is an example of how to do that.
The previous option is not available when using the percent of available funds template.
#### Available Variations
Below is a table of the variations of the Percent template.
|Syntax|Description|Example Application|
|---|---|---|
|`#template 15% of all income`|Budget 15% of all income categories| Using a "pay yourself first" strategy|
|`#template 10% of Paycheck`|Budget 10% of the "Paycheck" income category| Using a "pay yourself first" strategy, but have income categories you want to ignore|
|`#template 15% of previous all income`|Budget 15% of all income categories using last month's income|Using a "pay yourself first" strategy in conjunction with a "month ahead" strategy |
|`#template 10% of previous Paycheck`|Budget 10% of last month's "Paycheck" income category| Using a "pay yourself first" strategy in conjunction with a "month ahead" strategy, but have income categories you want to ignore|
|`#template 12% of available funds`|Budget 12% of your "To Budget" funds available at the current priority level| |
| Syntax | Description | Example Application |
| -------------------------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `#template 15% of all income` | Budget 15% of all income categories | Using a "pay yourself first" strategy |
| `#template 10% of Paycheck` | Budget 10% of the "Paycheck" income category | Using a "pay yourself first" strategy, but have income categories you want to ignore |
| `#template 15% of previous all income` | Budget 15% of all income categories using last month's income | Using a "pay yourself first" strategy in conjunction with a "month ahead" strategy |
| `#template 10% of previous Paycheck` | Budget 10% of last month's "Paycheck" income category | Using a "pay yourself first" strategy in conjunction with a "month ahead" strategy, but have income categories you want to ignore |
| `#template 12% of available funds` | Budget 12% of your "To Budget" funds available at the current priority level | |
### Schedule Type
The Schedule template allows you to automatically budget based on the schedules you have added to Actual.
This includes sub-monthly, month, extra-monthly schedules.
This is the most common template outside of the Simple template.
Below is an example of the syntax for a $ 100 per month schedule called "Internet", and a $ 2,400 per year schedule called "Taxes".
<!-- prettier-ignore -->
|Syntax| Budgeted Amount | Note |
|---|:---:| :---: |
|`#template schedule Internet` | $ 100 | Budget for the "Internet" schedule |
@@ -379,13 +383,13 @@ You can adjust the schedule in the schedule editor and the template will stay up
**Note** The schedule name is defined in the **Schedules** editor. **Take great care to copy across these schedule names EXACTLY** or the template will not be able to find the schedule.
#### Full Flag
There is one additional option to the Schedule template, the "Full" flag.
The "Full" flag tells the template to not build up funds over time and budget the full schedule amount in the needed month.
This is useful for small schedules that you don't need to build up over time since the amount is small.
This can also help make stacking Schedule templates easier to track.
Below is an example of using the "Full" flag assuming a once per year schedule for $15 called "Simplefin" due in May.
<!-- prettier-ignore -->
|Syntax| Budgeted Amount | Note |
|---|:---:| :---: |
|`#template schedule full Simplefin` | $ 0 | Budget in all months except May |
@@ -397,19 +401,20 @@ Yearly expenses (e.g. insurance, property rates, etc.) increase year on year. Of
This feature adds percentage adjustments to templates, letting you gradually save the expected increase throughout the year. By proactively budgeting a percentage change for these yearly increases, you avoid last-minute scrambling when renewal notices arrive with higher amounts.
|Syntax|Description|
|---|---|
| `#template schedule {SCHEDULE NAME} [{increase/decrease} {number}%]` | Fund the upcoming scheduled transaction over time, increasing or decreasing the amount by the given percentage
| Syntax | Description |
| -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `#template schedule {SCHEDULE NAME} [{increase/decrease} {number}%]` | Fund the upcoming scheduled transaction over time, increasing or decreasing the amount by the given percentage |
As an example, assuming the amount Scheduled for 'Insurance' the prior year was $1000, the below would apply.
| Category | Template line | Amount applied |
|---|---|:---:|
| Insurance | `#template schedule Insurance [increase 20%]` | $1200 |
| Category | Template line | Amount applied |
| --------- | --------------------------------------------- | :------------: |
| Insurance | `#template schedule Insurance [increase 20%]` | $1200 |
#### Available Variations
Below is a table of the variations of the Schedule template.
<!-- prettier-ignore -->
|Syntax|Description|Example Application|
|---|---|---|
|`#template schedule {SCHEDULE NAME}`|Fund upcoming scheduled transactions over time|Monthly schedules, or larger non-monthly scheduled transactions|
@@ -417,20 +422,21 @@ Below is a table of the variations of the Schedule template.
|`#template schedule {SCHEDULE NAME} [{increase/decrease} {number}%]`|Fund upcoming scheduled transaction over time, increasing or decreasing the amount by the given percentage|Yearly renewals where the amount changes|
### Average Type
The Average template allows you to budget the average amount spend over a number of months.
This is the same function provided by the menu in the budget table but it can be used in a single category automatically where the menu option must be applied to the whole budget or a single category.
The table below shows how to use the Average template.
<!-- prettier-ignore -->
|Syntax|Description|Example Application|
|---|---|---|
|`#template average 6 months` | Budget the average amount spent over the last 6 months. Can set the number to any number > 0. Matches the existing option on the budget page but with flexible month ranges | Try to budget only what you need to spend based on the last 6 months of spending data |
### Copy Type
The Copy template will copy the budget amount from some number of months prior.
This is useful if your spending is inconsistent per month, but cyclical over a period of months.
The table below shows how to use the Copy template.
<!-- prettier-ignore -->
|Syntax|Description|Example Application|
|---|---|---|
|`#template copy from 12 months ago` | Budget the same amount as was budgeted 12 months ago. Number of months is adjustable | Your power bill fluctuates throughout the year, but is about the same in equivalent months between years. |
@@ -475,11 +481,11 @@ All of the examples below use the case of 100 leftover when the remainder pass i
4. Spread funds over many categories, but some have limits.
| Category | Template line | Amount applied |
| --------------- | ----------------------- | -------------- |
| Category | Template line | Amount applied |
| --------------- | -------------------------------- | -------------- |
| Snack Fund | `#template remainder 3 up to 40` | 40 |
| Vacation Fund | `#template remainder` | 21.66 |
| Investment Fund | `#template remainder 2` | 38.34 |
| Vacation Fund | `#template remainder` | 21.66 |
| Investment Fund | `#template remainder 2` | 38.34 |
Notes on using the remainder template:
@@ -501,6 +507,7 @@ This shifts the indication to a long-term goal you are saving up to instead of j
A few examples have been given to illustrate this difference.
#### Examples
All examples assume that 400 was carried over from the previous month
**1. I'm saving for a large purchase, but I'm still determining how much I can allocate each month.**
@@ -508,34 +515,34 @@ In this case, a balance greater than or equal to 500 will set the balance green,
If you run the template, you get the following:
| Template Line(s) | Amount budgeted | Balance(color) |
|:---|:--:|---:|
| `#goal 500` | 0 | 400(yellow) |
| :--------------- | :-------------: | -------------: |
| `#goal 500` | 0 | 400(yellow) |
If you were able to budget 100 this month, you would then hit your goal and get a green indication.
| Template Line(s) | Amount budgeted | Balance(color) |
|:---|:--:|---:|
| `#goal 500` | 100 | 500(green) |
| :--------------- | :-------------: | -------------: |
| `#goal 500` | 100 | 500(green) |
**2. I'm saving for a purchase, but I will budget 50 a month until I reach my goal.**
In this example, a template is used to automatically budget 50 into the category when templates are run.
The `#goal` line will override the goal indication from the `#template` line, and only go green when a balance of 500 is reached.
If you run templates, you get the following:
| Template Line(s) | Amount budgeted | Balance(indication color) |
|:---|:---:|---:|
| `#template 50` <br /> `#goal 500` | 50 | 450(yellow) |
| Template Line(s) | Amount budgeted | Balance(indication color) |
| :-------------------------------- | :-------------: | ------------------------: |
| `#template 50` <br /> `#goal 500` | 50 | 450(yellow) |
If you have some extra funds after templates are run and can budget that last 50, you get the following:
| Template Line(s) | Amount budgeted | Balance(indication color) |
|:---|:---:|---:|
| `#template 50` <br /> `#goal 500` | 100 | 500(green) |
| Template Line(s) | Amount budgeted | Balance(indication color) |
| :-------------------------------- | :-------------: | ------------------------: |
| `#template 50` <br /> `#goal 500` | 100 | 500(green) |
#### Notes on The Goal Directive
* The `#goal` templates are run the same way as the regular `#templates`.
* If there is a `#goal` directive in a category, the goal indicator for that category will be based on the goal, not the templates.
* The `#goal` directive will not budget any funds, and funds budgeted by hand will not get reset by running templates.
* A `#goal` line can be stacked with `#templates` to automatically budget the category (via the templates) but override how the category goal is indicated (the goal template).
* There is no priority on a `#goal`.
- The `#goal` templates are run the same way as the regular `#templates`.
- If there is a `#goal` directive in a category, the goal indicator for that category will be based on the goal, not the templates.
- The `#goal` directive will not budget any funds, and funds budgeted by hand will not get reset by running templates.
- A `#goal` line can be stacked with `#templates` to automatically budget the category (via the templates) but override how the category goal is indicated (the goal template).
- There is no priority on a `#goal`.
@@ -18,7 +18,6 @@ Enable this feature alongside the **Goals** experimental feature by enabling **G
There are different ways to interact with the cleanup script, and a few of the examples will be given. But first, let's explore the syntax.
<!-- prettier-ignore -->
### Global Source and Sinks
Global source and sink definitions can affect the whole budget.
+2 -5
View File
@@ -11,9 +11,7 @@
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"format": "prettier --write .",
"lint": "prettier --check ."
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@docusaurus/core": "^3.9.2",
@@ -32,8 +30,7 @@
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^3.9.2",
"@types/react": "^19.2.5",
"prettier": "^3.6.2"
"@types/react": "^19.2.5"
},
"browserslist": {
"production": [
@@ -2,5 +2,4 @@ export const captureException = function (exc: Error) {
console.error('[Exception]', exc);
};
// eslint-disable-next-line
export const captureBreadcrumb = function (crumb: unknown) {};
@@ -144,8 +144,6 @@ async function execTransactionsGrouped(
rows = await db.all<db.DbViewTransactionInternal>(rowSql, params);
} else {
// TODO: phew, what a doozy. write docs why it works this way
//
// prettier-ignore
const rowSql = `
SELECT group_id, matched FROM (
SELECT
@@ -121,7 +121,6 @@ export type DbSchedule = {
tombstone: 1 | 0;
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type DbScheduleJsonPath = {
schedule_id: DbSchedule['id'];
payee: string;
@@ -140,7 +139,7 @@ export type DbScheduleNextDate = {
};
// This is unused in the codebase.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type DbPendingTransaction = {
id: string;
acct: number;
+2 -1
View File
@@ -22,7 +22,8 @@ import type { ApiHandlers } from './api-handlers';
import type { ServerHandlers } from './server-handlers';
export interface Handlers
extends ServerHandlers,
extends
ServerHandlers,
ApiHandlers,
BudgetHandlers,
DashboardHandlers,
+6
View File
@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [MatissJanis]
---
Move prettier and eslint to oxc
+236 -683
View File
File diff suppressed because it is too large Load Diff