* [AI] Emit bundle stats from the crdt package
The crdt package was the only published library without a stats.json
artifact. Migrate its build to Vite (mirroring the api/cli setup), wire
in rollup-plugin-visualizer to emit dist/stats.json, and upload it from
the CRDT CI job. Declarations are still produced by tsgo via
--emitDeclarationOnly.
https://claude.ai/code/session_01CDVAGLGu49q5YMHsRLkYLQ
* Add release notes for PR #7537
* [AI] crdt: drop redundant rm -rf dist from build script
Vite's build.emptyOutDir: true already clears the output directory
before writing, so the leading rm -rf dist is unnecessary.
https://claude.ai/code/session_01CDVAGLGu49q5YMHsRLkYLQ
* [AI] Include crdt in the size-compare bundle stats table
Wait for the crdt build check on both the base branch and the PR,
download the crdt-build-stats artifact for each, and pass it to
bundle-stats-comment.mjs so the summary table rendered on the PR
includes a row for the crdt package alongside desktop-client,
loot-core, api, and cli.
https://claude.ai/code/session_01CDVAGLGu49q5YMHsRLkYLQ
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* Apply suggested fix to packages/desktop-client/src/components/modals/EditFieldModal.tsx from Copilot Autofix
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
* Apply suggested fix to packages/desktop-client/src/components/modals/EditFieldModal.tsx from Copilot Autofix
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
* note
* fix typo in note
* remove useless conditions
---------
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
* [AI] Fix regex checkbox label contrast in notes modal (#7514)
Apply theme.menuAutoCompleteText color to the "Use Regular Expressions"
checkbox label in the find-and-replace tab so it contrasts with the
menuAutoCompleteBackground modal background.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: rename release note file to match PR number
The CI expects the release note file to be named after the PR number
(7515), not the issue number (7514).
---------
Co-authored-by: liuren.lcy <liuren.lcy@antgroup.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* [AI] Refactor IS_GENERIC_BROWSER env var to --mode=browser
Replace the IS_GENERIC_BROWSER environment variable with Vite's built-in
--mode=browser flag to distinguish browser builds from Electron builds.
This aligns with the existing --mode=desktop pattern used for Electron
production builds.
Also fix build-shims.js to derive NODE_ENV from import.meta.env.DEV
instead of import.meta.env.MODE, so custom modes don't leak into
process.env.NODE_ENV.
https://claude.ai/code/session_014HvkpR59Ke4eUoiUzsUruv
* Add release notes for PR #7466
* [AI] Fix COOP/COEP headers not set with --mode=browser
The server.headers config was gated on mode === 'development', which
excluded --mode=browser. Since server.headers only applies during
vite serve (not builds), always set the COOP/COEP headers. These are
required for SharedArrayBuffer support used by the SQLite backend.
https://claude.ai/code/session_014HvkpR59Ke4eUoiUzsUruv
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* [AI] Add scoped ErrorBoundary to Rules page to contain rendering crashes
* [autofix.ci] apply automated fixes
* docs: add release notes for ErrorBoundary PR
* fix(rules): add resetKeys to ErrorBoundary for route navigation reset
In react-error-boundary v6, the boundary does not auto-reset when the
user navigates away and back. Adding resetKeys={[location.pathname]}
ensures the error state clears on route changes.
This contribution was developed with AI assistance (Claude Code).
* fix(rules): show error message in fallback UI and log to console
Addresses reviewer feedback on #7437: surface error.message in the
FeatureErrorFallback so users can copy-paste when reporting issues,
and log the error to the console via useEffect so the error isn't
swallowed by the boundary.
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* [AI] Add publishConfig.imports sync validator with pre-commit integration
Add a TypeScript script that validates publishConfig.imports stays in sync
with imports in all packages/*/package.json files. Runs automatically in
the pre-commit hook via lint-staged with --fix mode.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [AI] Add release notes for #7469
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [AI] Guard main() with require.main and respect lint-staged file args
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [AI] Handle non-string imports targets in derivePublishImports
- Add type guard to check for non-string values in imports
- Throw descriptive error when conditional imports are encountered
- Update type signature to accept Record<string, string | object>
- Add test case for non-string imports error handling
This prevents TypeError when packages have conditional imports (e.g., #browser-preload in desktop-client)
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
* [AI] Limit resolvePackageJsonPaths scope to packages directory
- Add packagesRoot constant to restrict path resolution
- Update while loop condition to only traverse within packages directory
- Add additional check to ensure candidate paths are under packages/
- Prevents resolution to repo root package.json for missing/deleted files
This ensures the validator only processes package.json files under packages/* and avoids accidentally targeting the monorepo root manifest.
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
* [AI] Fix type errors for API consumers by shipping .d.ts declarations from loot-core
Downstream consumers of @actual-app/api with strict: true get type errors
because @actual-app/core exports raw .ts source files. Consumers' tsc
follows the import chain into core's source (compiled with strict: false),
and skipLibCheck doesn't help since it only skips .d.ts files.
Add "types" conditions to all imports/exports entries in loot-core's
package.json, pointing to the pre-built declarations in lib-dist/decl/.
Add .npmignore to include lib-dist/decl/ in the published package.
Fixes#7410
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [AI] Use prepack/postpack scripts instead of inflating package.json
Replace the inline "types" conditions in imports/exports with a prepack
script that adds them at pack/publish time. This keeps the checked-in
package.json clean while still shipping .d.ts declarations to npm
consumers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [AI] Convert prepack/postpack scripts to TypeScript
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [AI] Add release notes for #7468
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [AI] Fix recursive ExportValue type and remove redundant comment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [AI] Rename scripts to .mts and inline types conditions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [AI] Make backup/restore scripts safer
- Check if backup exists before creating it in prepack
- Make restore idempotent by checking if backup exists in postpack
- Prevents overwriting existing backups from interrupted runs
- Addresses CodeRabbit review feedback
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
* [AI] Type api-handlers.ts fields to drop implicit any
The `fields` / export-args slots in the ApiHandlers contract were
untyped, surfacing as TS7008 errors in strict consumers. Replace them
with the `Partial<APIXxxEntity>` shapes the `@actual-app/api` wrappers
already pass, and annotate the matching call sites in `api.ts` with
`@ts-expect-error` where the legacy helpers still declare full-entity
parameters despite accepting partial updates at runtime.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [AI] Replace vite-plugin-dts with tsgo for api types
Drops vite-plugin-dts in favor of running tsgo --emitDeclarationOnly
after the vite bundle, eliminating a heavy dev dependency tree
(api-extractor, volar, vue language-core) from the api package build.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [AI] Add build script to loot-core to emit declarations via lage
`yarn build:cli` failed in CI with TS6305 because api's
`tsgo --emitDeclarationOnly` depends on loot-core's pre-built
`lib-dist/decl/*.d.ts`, but loot-core had no `build` script, so lage's
`^build` cascade silently skipped it. Add `"build": "tsgo -b"` so loot-core
slots into the dependency chain; its tsconfig already has
`emitDeclarationOnly: true`, so the output is declarations only.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Simplify API build
* [AI] Document TypeScript moduleResolution requirement for @actual-app/api
The published declarations rely on package.json exports conditions, which
classic node / node10 resolvers don't honor. Document the supported modes
(bundler / nodenext / node16) in the package README and in the Getting
Started section of the API docs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [AI] Type-guard default value in add-types-conditions prepack
`value.default` is typed `ExportValue | undefined`, which allows nested
conditional objects. The previous truthy check fell through to
`shouldSkip(defaultValue)` and would crash on `.endsWith()` if that shape
ever appeared. Replace with a `typeof === 'string'` narrowing and drop a
now-redundant "Insert types as the first key" comment.
No runtime change on current package.json — no nested `default` values
exist today — but the script is not covered by loot-core's tsconfig
include, so the latent type issue was silent.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [AI] Allow "nodenext" in docs spellcheck expect list
Referenced in the new TypeScript moduleResolution note in the API docs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [AI] Move loot-core declarations to @types and whitelist publish with files
Relocate loot-core's composite TypeScript output from lib-dist/decl to the
top-level @types directory, matching the api package's convention. Replace
the old .npmignore blacklist with an explicit package.json files whitelist.
- tsconfig.json: outDir @types, exclude test/mock dirs from decl emission
- scripts/add-types-conditions.mts: rewrite paths to ./@types/src/...
- package.json: files whitelist shipping only src, @types, migrations,
typings, default-db.sqlite; drop legacy typesVersions (docs now require
moduleResolution bundler/nodenext/node16, so the classic-resolution
fallback is unused)
- .gitignore: ignore the new @types build artifact
- lage.config.js: factor outputGlob into a shared BUILD_OUTPUT_GLOBS
constant and add @types/** so lage caches loot-core's decl output
- root tsconfig.json: tighten exclude from packages/api/@types to
packages/*/@types to cover both api and loot-core
- delete .npmignore entirely
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [AI] Build loot-core declarations inside prepack
yarn workspace @actual-app/core pack is the first non-setup step in the
publish workflow, running before any build. Without a build chained into
prepack the @types/ tree is empty at pack time, so the tarball shipped a
transformed package.json pointing at ./@types/src/... paths that didn't
exist. npm publish doesn't re-run hooks on a pre-packed tarball, so the
frozen snapshot must be self-contained; prepack now runs yarn build first
to populate @types/ before add-types-conditions rewrites the exports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
* Update customThemeCatalog.json
* Update customThemeCatalog.json
* [autofix.ci] apply automated fixes
* Update customThemeCatalog.json
* 🎨 You Need A Theme Dark, based on 2026 nYNAB dark
* YNA Theme Dark
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* [AI] Add no-extraneous-dependencies lint rule to prevent transitive dependency usage
Closes#7479. Adds a custom ESLint rule that flags imports of packages not
explicitly listed in the workspace's dependencies or devDependencies. Also
fixes all existing violations by adding missing deps and removes unused
deps (@reduxjs/toolkit, @rschedule/json-tools) from loot-core.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [AI] Fix builtin subpath detection and improve cache in no-extraneous-dependencies
Fix false positives for Node.js builtin subpaths (fs/promises, path/posix)
by checking the package name portion against builtins. Also cache all
visited directories during walk-up, not just the starting directory.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Release notes
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [AI] Fix sync-server build not resolving subpath imports
The add-import-extensions build script only handled relative imports
(./ ../), leaving #-prefixed subpath imports unresolved in the build
output. At runtime Node.js resolved them via package.json's imports
map back to source files, which have extensionless imports that fail
in ESM.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [AI] Add release notes for sync-server subpath imports fix
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [AI] Simplify subpath import resolution using publishConfig.imports
Use publishConfig.imports which already has ./build/src/ paths with .js
extensions, eliminating manual src->build and .ts->.js conversions.
Also sort wildcard patterns by specificity and extract shared helper.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [AI] Use replaceAll for wildcard substitution to satisfy CodeQL
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [AI] Fix path traversal vulnerability in uploadFileWeb
Sanitize user-supplied filename with fs.basename() to strip directory
components (e.g. ../../) before writing to /uploads/, preventing
arbitrary file writes outside the intended directory.
https://claude.ai/code/session_01UgQANWBxqkqVT7xGyWNAXB
* [AI] Harden path traversal fix and correct broken basename in web fs
The browser fs.basename implementation was returning the directory part
instead of the filename, making the previous fs.basename() fix
ineffective on the web platform. Replace with inline sanitization that
works regardless of platform: split on path separators, strip null
bytes, reject . and .., and use fs.join for safe path construction.
Also fix the browser fs.basename to actually return the last path
segment, matching the behavior of Node's path.basename.
https://claude.ai/code/session_01UgQANWBxqkqVT7xGyWNAXB
* [AI] Revert browser fs.basename change per user request
The browser fs.basename implementation is restored to its original
behavior. The path traversal fix in uploadFileWeb does not depend on
fs.basename.
https://claude.ai/code/session_01UgQANWBxqkqVT7xGyWNAXB
* [AI] Add release notes for path traversal fix (#7428)
https://claude.ai/code/session_01UgQANWBxqkqVT7xGyWNAXB
* [AI] Suppress no-control-regex lint for null byte sanitization
The \0 regex is intentional to strip null bytes from filenames as part
of the path traversal fix.
https://claude.ai/code/session_01UgQANWBxqkqVT7xGyWNAXB
* [AI] Use replaceAll for null-byte stripping instead of regex
Replace /\0/g regex with replaceAll('\0', '') to avoid triggering
the no-control-regex ESLint rule, removing the need for the
eslint-disable comment.
https://claude.ai/code/session_01UgQANWBxqkqVT7xGyWNAXB
---------
Co-authored-by: Claude <noreply@anthropic.com>