16 KiB
AGENTS.md - Guide for AI Agents Working with Actual Budget
This guide provides comprehensive information for AI agents (like Cursor) working with the Actual Budget codebase.
Project Overview
Actual Budget is a local-first personal finance tool written in TypeScript/JavaScript. It's 100% free and open-source with synchronization capabilities across devices.
- Repository: https://github.com/actualbudget/actual
- Community Docs: https://github.com/actualbudget/docs or https://actualbudget.org/docs
- License: MIT
- Primary Language: TypeScript (with React)
- Build System: Yarn 4 workspaces (monorepo)
Quick Start Commands
Essential Commands (Run from Root)
# Type checking (ALWAYS run before committing)
yarn typecheck
# Linting and formatting (with auto-fix)
yarn lint:fix
# Run all tests
yarn test
# Start development server (browser)
yarn start
# Start with sync server
yarn start:server-dev
# Start desktop app development
yarn start:desktop
Important Rules
- ALWAYS run yarn commands from the root directory - never run them in child workspaces
- Use
yarn workspace <workspace-name> run <command>for workspace-specific tasks - Tests run once and exit by default (using
vitest --run)
Task Orchestration with Lage
The project uses lage (a task runner for JavaScript monorepos) to efficiently run tests and other tasks across multiple workspaces:
- Parallel execution: Runs tests in parallel across workspaces for faster feedback
- Smart caching: Caches test results to skip unchanged packages (cached in
.lage/directory) - Dependency awareness: Understands workspace dependencies and execution order
- Continues on error: Uses
--continueflag to run all packages even if one fails
Lage Commands:
# Run all tests across all packages
yarn test # Equivalent to: lage test --continue
# Run tests without cache (for debugging/CI)
yarn test:debug # Equivalent to: lage test --no-cache --continue
Configuration is in lage.config.js at the project root.
Architecture & Package Structure
Core Packages
1. loot-core (packages/loot-core/)
The core application logic that runs on any platform.
-
Business logic, database operations, and calculations
-
Platform-agnostic code
-
Exports for both browser and node environments
-
Test commands:
# Run all loot-core tests yarn workspace loot-core run test # Or run tests across all packages using lage yarn test
2. desktop-client (packages/desktop-client/ - aliased as @actual-app/web)
The React-based UI for web and desktop.
-
React components using functional programming patterns
-
E2E tests using Playwright
-
Vite for bundling
-
Commands:
# Development yarn workspace @actual-app/web start:browser # Build yarn workspace @actual-app/web build # E2E tests yarn workspace @actual-app/web e2e # Visual regression tests yarn workspace @actual-app/web vrt
3. desktop-electron (packages/desktop-electron/)
Electron wrapper for the desktop application.
- Window management and native OS integration
- E2E tests for Electron-specific features
4. api (packages/api/ - aliased as @actual-app/api)
Public API for programmatic access to Actual.
-
Node.js API
-
Designed for integrations and automation
-
Commands:
# Build yarn workspace @actual-app/api build # Run tests yarn workspace @actual-app/api test # Or use lage to run all tests yarn test
5. sync-server (packages/sync-server/ - aliased as @actual-app/sync-server)
Synchronization server for multi-device support.
- Express-based server
- Currently transitioning to TypeScript (mostly JavaScript)
- Commands:
yarn workspace @actual-app/sync-server start
6. component-library (packages/component-library/ - aliased as @actual-app/components)
Reusable React UI components.
- Shared components like Button, Input, Menu, etc.
- Theme system and design tokens
- Icons (375+ icons in SVG/TSX format)
7. crdt (packages/crdt/ - aliased as @actual-app/crdt)
CRDT (Conflict-free Replicated Data Type) implementation for data synchronization.
- Protocol buffers for serialization
- Core sync logic
8. plugins-service (packages/plugins-service/)
Service for handling plugins/extensions.
9. eslint-plugin-actual (packages/eslint-plugin-actual/)
Custom ESLint rules specific to Actual.
no-untranslated-strings: Enforces i18n usageprefer-trans-over-t: Prefers Trans component over t() functionprefer-logger-over-console: Enforces using logger instead of consoletypography: Typography rulesprefer-if-statement: Prefers explicit if statements
Development Workflow
1. Making Changes
When implementing changes:
- Read relevant files to understand current implementation
- Make focused, incremental changes
- Run type checking:
yarn typecheck - Run linting:
yarn lint:fix - Run relevant tests
- Fix any linter errors that are introduced
2. Testing Strategy
Unit Tests (Vitest)
The project uses lage for running tests across all workspaces efficiently.
# Run all tests across all packages (using lage)
yarn test
# Run tests without cache (for debugging)
yarn test:debug
# Run tests for a specific package
yarn workspace loot-core run test
# Run a specific test file (watch mode)
yarn workspace loot-core run test path/to/test.test.ts
E2E Tests (Playwright)
# Run E2E tests for web
yarn e2e
# Desktop Electron E2E (includes full build)
yarn e2e:desktop
# Visual regression tests
yarn vrt
# Visual regression in Docker (consistent environment)
yarn vrt:docker
# Run E2E tests for a specific package
yarn workspace @actual-app/web e2e
Testing Best Practices:
- Minimize mocked dependencies - prefer real implementations
- Use descriptive test names
- Vitest globals are available:
describe,it,expect,beforeEach, etc. - For sync-server tests, globals are explicitly defined in config
3. Type Checking
TypeScript configuration uses:
- Incremental compilation
- Strict type checking with
typescript-strict-plugin - Platform-specific exports in
loot-core(node vs browser)
Always run yarn typecheck before committing.
4. Internationalization (i18n)
- Use
Transcomponent instead oft()function when possible - All user-facing strings must be translated
- Generate i18n files:
yarn generate:i18n - Custom ESLint rules enforce translation usage
Code Style & Conventions
TypeScript Guidelines
Type Usage:
- Use TypeScript for all code
- Prefer
typeoverinterface - Avoid
enum- use objects or maps - Avoid
anyorunknownunless absolutely necessary - Look for existing type definitions in the codebase
- Avoid type assertions (
as,!) - prefersatisfies - Use inline type imports:
import { type MyType } from '...'
Naming:
- Use descriptive variable names with auxiliary verbs (e.g.,
isLoaded,hasError) - Named exports for components and utilities (avoid default exports except in specific cases)
Code Structure:
- Functional and declarative programming patterns - avoid classes
- Use the
functionkeyword for pure functions - Prefer iteration and modularization over code duplication
- Structure files: exported component/page, helpers, static content, types
- Create new components in their own files
React Patterns:
- Don't use
React.FunctionComponentorReact.FC- type props directly - Don't use
React.*patterns - use named imports instead - Use
<Link>instead of<a>tags - Use custom hooks from
src/hooks(not react-router directly):useNavigate()fromsrc/hooks(not react-router)useDispatch(),useSelector(),useStore()fromsrc/redux(not react-redux)
- Avoid unstable nested components
- Use
satisfiesfor type narrowing
JSX Style:
- Declarative JSX, minimal and readable
- Avoid unnecessary curly braces in conditionals
- Use concise syntax for simple statements
- Prefer explicit expressions (
condition && <Component />)
Import Organization
Imports are automatically organized by ESLint with the following order:
- React imports (first)
- Built-in Node.js modules
- External packages
- Actual packages (
loot-core,@actual-app/components- legacy patternloot-designmay appear in old code) - Parent imports
- Sibling imports
- Index imports
Always maintain newlines between import groups.
Platform-Specific Code
- Don't directly reference platform-specific imports (
.api,.web,.electron) - Use conditional exports in
loot-corefor platform-specific code - Platform resolution happens at build time via package.json exports
Restricted Patterns
Never:
- Use
console.*(use logger instead - enforced by ESLint) - Import from
uuidwithout destructuring: useimport { v4 as uuidv4 } from 'uuid' - Import colors directly - use theme instead
- Import
@actual-app/web/*inloot-core
Git Commands:
- 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
File Structure Patterns
Typical Component File
import { type ComponentType } from 'react';
// ... other imports
type MyComponentProps = {
// Props definition
};
export function MyComponent({ prop1, prop2 }: MyComponentProps) {
// Component logic
return (
// JSX
);
}
Test File
import { describe, it, expect, beforeEach } from 'vitest';
// ... imports
describe('ComponentName', () => {
it('should behave as expected', () => {
// Test logic
expect(result).toBe(expected);
});
});
Important Directories & Files
Configuration Files
/package.json- Root workspace configuration, scripts/lage.config.js- Lage task runner configuration/eslint.config.mjs- ESLint configuration (flat config format)/tsconfig.json- Root TypeScript configuration/.cursorignore,/.gitignore- Ignored files/yarn.lock- Dependency lockfile (Yarn 4)
Documentation
/README.md- Project overview/CONTRIBUTING.md- Points to community docs/upcoming-release-notes/- Release notes for next version/CODEOWNERS- Code ownership definitions
Build Artifacts (Don't Edit)
packages/*/lib-dist/- Built outputpackages/*/dist/- Built outputpackages/*/build/- Built outputpackages/desktop-client/playwright-report/- Test reportspackages/desktop-client/test-results/- Test results.lage/- Lage task runner cache (improves test performance)
Key Source Directories
packages/loot-core/src/client/- Client-side core logicpackages/loot-core/src/server/- Server-side core logicpackages/loot-core/src/shared/- Shared utilitiespackages/loot-core/src/types/- Type definitionspackages/desktop-client/src/components/- React componentspackages/desktop-client/src/hooks/- Custom React hookspackages/desktop-client/e2e/- End-to-end testspackages/component-library/src/- Reusable componentspackages/component-library/src/icons/- Icon components (auto-generated, don't edit)
Common Development Tasks
Running Specific Tests
# Run all tests across all packages (recommended)
yarn test
# Unit test for a specific file in loot-core (watch mode)
yarn workspace loot-core run test src/path/to/file.test.ts
# E2E test for a specific file
yarn workspace @actual-app/web run playwright test accounts.test.ts --browser=chromium
Building for Production
# Browser build
yarn build:browser
# Desktop build
yarn build:desktop
# API build
yarn build:api
# Sync server build
yarn build:server
Type Checking Specific Packages
TypeScript uses project references. Run yarn typecheck from root to check all packages.
Debugging Tests
# Run tests in debug mode (without parallelization)
yarn test:debug
# Run specific E2E test with headed browser
yarn workspace @actual-app/web run playwright test --headed --debug accounts.test.ts
Working with Icons
Icons in packages/component-library/src/icons/ are auto-generated. Don't manually edit them.
Troubleshooting
Type Errors
- Run
yarn typecheckto see all type errors - Check if types are imported correctly
- Look for existing type definitions in
packages/loot-core/src/types/ - Use
satisfiesinstead ofasfor type narrowing
Linter Errors
- Run
yarn lint:fixto auto-fix many issues - Check ESLint output for specific rule violations
- Custom rules:
actual/no-untranslated-strings- Add i18nactual/prefer-trans-over-t- Use Trans componentactual/prefer-logger-over-console- Use logger- Check
eslint.config.mjsfor complete rules
Test Failures
- Check if test is running in correct environment (node vs web)
- For Vitest: check
vitest.config.tsorvitest.web.config.ts - For Playwright: check
playwright.config.ts - Ensure mock minimization - prefer real implementations
- Lage cache issues: Clear cache with
rm -rf .lageif tests behave unexpectedly - Tests continue on error: With
--continueflag, all packages run even if one fails
Import Resolution Issues
- Check
tsconfig.jsonfor path mappings - Check package.json
exportsfield (especially for loot-core) - Verify platform-specific imports (
.web,.electron,.api) - Use absolute imports in
desktop-client(enforced by ESLint)
Build Failures
- Clean build artifacts:
rm -rf packages/*/dist packages/*/lib-dist packages/*/build - Reinstall dependencies:
yarn install - Check Node.js version (requires >=20)
- Check Yarn version (requires ^4.9.1)
Testing Patterns
Unit Tests
- Located alongside source files or in
__tests__directories - Use
.test.ts,.test.tsx,.spec.jsextensions - Vitest is the test runner
- Minimize mocking - prefer real implementations
E2E Tests
- Located in
packages/desktop-client/e2e/ - Use Playwright test runner
- Visual regression snapshots in
*-snapshots/directories - Page models in
e2e/page-models/for reusable page interactions - Mobile tests have
.mobile.test.tssuffix
Visual Regression Tests (VRT)
- Snapshots stored per test file in
*-snapshots/directories - Use Docker for consistent environment:
yarn vrt:docker
Additional Resources
- Community Documentation: https://actualbudget.org/docs/contributing/
- Discord Community: https://discord.gg/pRYNYr4W5A
- GitHub Issues: https://github.com/actualbudget/actual/issues
- Feature Requests: Label "needs votes" sorted by reactions
Code Quality Checklist
Before committing changes, ensure:
yarn typecheckpassesyarn lint:fixhas been run- Relevant tests pass
- No new console.* usage (use logger)
- User-facing strings are translated
- Prefer
typeoverinterface - Named exports used (not default exports)
- Imports are properly ordered
- Platform-specific code uses proper exports
- No unnecessary type assertions
Pull Request Guidelines
When creating pull requests:
- 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.
Performance Considerations
- Bundle Size: Check with rollup-plugin-visualizer
- Type Checking: Uses incremental compilation
- Testing: Tests run in parallel by default
- Linting: ESLint caches results for faster subsequent runs
Workspace Commands Reference
# List all workspaces
yarn workspaces list
# Run command in specific workspace
yarn workspace <workspace-name> run <command>
# Run command in all workspaces
yarn workspaces foreach --all run <command>
# Install production dependencies only (for server deployment)
yarn install:server
Environment Requirements
- Node.js: >=20
- Yarn: ^4.9.1 (managed by packageManager field)
- Browser Targets: Electron >= 35.0, modern browsers (see browserslist)
Migration Notes
The codebase is actively being migrated:
- JavaScript → TypeScript: sync-server is in progress
- Classes → Functions: Prefer functional patterns
- React.* → Named Imports: Legacy React.* patterns being removed
When working with older code, follow the newer patterns described in this guide.