The peter-evans/create-or-update-comment action is unnecessary since GitHub's gh CLI (pre-installed on all GitHub-hosted runners) provides the same functionality natively via 'gh issue comment' and 'gh issue close' commands. This change addresses the zizmor security scanner warning about using an action when the functionality is already included by the runner.
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
Convert the external Discord "Support" contact link into a proper GitHub issue
form so users who skip the redirect still land somewhere useful. The new form
has a single "Describe your problem" field and a prominent notice that tech
support tickets are auto-closed and Discord is the place to get help. A new
workflow watches for the `tech-support` label, posts a friendly Discord pointer
and closes the issue, mirroring the existing feature-request auto-close flow.
* [AI] Make TypeScript work in test files across packages
Match the CRDT package's tsconfig pattern in loot-core, desktop-client,
api, and desktop-electron so test files participate in the project graph
(IDE intellisense, project-wide typecheck) while production builds still
emit clean declaration files.
- Remove test-file exclusions from each package's main tsconfig
- Add tsconfig.build.json for loot-core and api with test exclusions,
used by the build scripts
- Add e2e/tsconfig.json for desktop-client and desktop-electron with
Playwright types
- Fix latent type errors in test files now caught by typecheck
- Disable typescript/unbound-method for test files (mock matcher pattern)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Release notes
* [AI] Address review feedback on test type fixes
- goal-template.test.ts: extract amounts to typed locals so single-value
assertions no longer compare against unknown
- category-template-context.test.ts: replace `as unknown as DbCategory`
double-cast with a fully-typed object using `satisfies DbCategory`
(the previous mock had `is_income: true` which doesn't match the
`1 | 0` shape the cast was hiding)
- api/tsconfig.build.json: broaden test exclude pattern to `**/*.test.ts`
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [AI] Widen toMatchThemeScreenshots matcher to accept Page
The matcher's runtime impl already handled both Page and Locator
(via `typeof locator.page === 'function'` branch), but the type only
declared Locator. Call sites pass a Page (`expect(page).toMatchThemeScreenshots()`),
which now compiles cleanly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [AI] Type window.Actual in e2e fixtures and refactor matcher
- Pull loot-core/typings/window.ts into the e2e tsconfig include so
the ambient `window.Actual` augmentation is visible.
- Refactor toMatchThemeScreenshots to derive a Page once via
`'page' in target`, then call evaluate on the page consistently.
The previous union-typed access (locator.evaluate, locator.page)
didn't typecheck on Locator | Page.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [AI] Drop tsconfig.build.json for loot-core and api
The build-config indirection was incomplete protection: typecheck
(`tsgo -b` against the main tsconfig, which now includes test files)
already emits `*.test.d.ts` into `@types/`, and the build step does
not clean before re-emitting. The same is observable in crdt's
`dist/`, which currently contains test declarations on disk.
What actually keeps test declarations out of the npm tarball is the
`files` field in package.json — and loot-core already uses that
mechanism for source files (`\!src/**/*.test.ts`). Extending the same
pattern to `@types/` is more direct than maintaining a duplicate
tsconfig that doesn't reliably do its job.
- Delete loot-core/tsconfig.build.json; revert build to `tsgo -b`;
add `\!@types/**/*.test.d.ts*`, `\!@types/**/__tests__/**`,
`\!@types/**/__mocks__/**` to `files`.
- Delete api/tsconfig.build.json; revert build to
`vite build && tsgo --emitDeclarationOnly`; add
`\!@types/**/*.test.d.ts*` to `files`.
Verified: `yarn pack --dry-run` excludes all test declarations from
both packages while production declarations still pack (428 .d.ts
files for loot-core, methods.d.ts for api).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [AI] Standardise crdt to drop tsconfig.build.json
Apply the same simplification as loot-core and api: a single tsconfig
per package, with `files`-field negations preventing test
declarations from being published.
Note: this also fixes a pre-existing issue where crdt was shipping
`crdt/timestamp.test.d.ts` and `crdt/merkle.test.d.ts` to npm. The
old `tsconfig.build.json` excluded test files from declaration emit,
but the `typecheck` script (`tsgo -b` via the main tsconfig) had
already emitted them into `dist/` and the build did not clean
first, so they were packed via `"files": ["dist"]`.
After this change, `yarn pack --dry-run` packs only production
declarations (10 .d.ts files) and excludes the test ones.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [AI] Add iconography, placeholders, and dropdown chevrons to mobile transaction form
Brings the mobile new-transaction screen in line with the design mockup so
each field shows a leading icon, dropdown affordances surface on the right,
and empty pickers display a placeholder hint.
- Extend TapField with optional icon/placeholder props (placeholder uses
formInputTextPlaceholder when value is empty).
- Extend InputField with an optional icon prop that wraps the input in a
bordered row when present.
- Wire SvgUser/SvgTag/SvgWallet/SvgCalendar/SvgNotesPaper into Payee,
Category, Account, Date, and Notes for both the main form and split
child rows; add a SvgCheveronDown rightContent on Category and Account.
* Add release notes for PR #7639
* [AI] Forward consumer style to inner Input in icon path of InputField
CodeRabbit flagged that consumer-supplied `style` was being applied to the
wrapper View in the icon branch, so input-targeted properties like
`appearance: 'none'` and `minWidth: '150px'` on the Date field never reached
the underlying `<input type="date">`. Move the style spread onto the inner
Input so those styles take effect.
* [AI] Add dropdown chevron to mobile Payee field
Show the chevron on the Payee TapField in both the main form (as the
fallback when neither the Save-location nor Nearby-payee button applies)
and in split child rows, matching Category and Account.
* Update VRT screenshots
Auto-generated by VRT workflow
PR: #7639
* [AI] Hide native date picker icon on mobile transaction form
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Update VRT screenshots
Auto-generated by VRT workflow
PR: #7639
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* Simplify desktop client browser build
* [AI] Move browser build orchestration into vite config and lage
Moves loot-core worker build, public/ staging (migrations, default-db,
sql-wasm, data-file-index), and build-stats wiring from the deleted
packages/desktop-client/bin/build-browser shell script into a
lootCoreBackend vite plugin in packages/desktop-client/vite.config.mts.
Adds a build:browser target to lage.config.js so bin/package-browser
runs as a single `lage build:browser --to=@actual-app/web` call, with
crdt + loot-core built via lage's ^build dependency before the
desktop-client build.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Refactor e2e-test workflow and update desktop-client configurations
* [AI] Move plugins-service staging into desktop-client vite config
Declares plugins-service as a workspace devDependency of @actual-app/web
so lage's ^build edge picks it up automatically in the build:browser
pipeline, and moves the cross-package file staging (production copy +
dev serving) into vite.config.mts, mirroring the lootCoreBackend
pattern. Drops the plugins-service shell wrapper script and simplifies
its package.json scripts to invoke vite build directly. Updates root
start:browser to run plugins-service watch in parallel with the dev
server instead of pre-building once.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [AI] Sync tsconfig project references for plugins-service edge
Follow-up to the plugins-service workspace edge: adds the
../plugins-service project reference in packages/desktop-client/tsconfig.json
via yarn sync:tsconfig-references.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Release notes
* [AI] Ignore .venv/ so lage's git hasher skips Electron CI's Python venv
Electron CI provisions a Python virtualenv at the repo root for
setuptools. With browser builds now routed through lage, lage's
git hash-object pass walks untracked-not-ignored files and fails on
the venv's broken lib64 symlink ("fatal: Unable to hash .../.venv/lib64").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [AI] Bake Weblate translations back into VRT/e2e bundle
build-web set download-translations: false and relied on bin/package-browser's
ad-hoc git clone + git pull. That path is fragile inside the playwright
container, so vite's import.meta.glob('/locale/*.json') frequently produced an
empty languages map and the bundle shipped with no en.json. VRTs then rendered
source-code English and diffed against snapshots authored from Weblate strings.
Route translation provisioning back through actions/checkout (download-translations: true)
in build-web and vrt-update-generate, and add --skip-translations to bin/package-browser
(mirroring bin/package-electron) so the in-script git pull is bypassed when CI
has already staged the locale dir.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [AI] Skip translation cloning in build-web bundle for VRT determinism
bin/package-browser used to unconditionally clone actualbudget/translations
before vite ran, baking Weblate en.json into the build artifact. With the
e2e-test pipeline now serving that artifact via serve-build.mjs, VRT
screenshots ended up rendering Weblate strings — drifting from the snapshots,
which were authored against source-code English (master VRTs ran on vite dev
without a locale dir).
Pass --skip-translations to bin/package-browser from build-web so the bundle
ships with no locale chunks. download-translations stays 'false' across the
e2e-test and vrt-update-generate workflows, matching the prior behavior.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: disable contextual alternates in Inter font to prevent unwanted character substitution
Fixes#6351
The Inter font calt feature replaces x between digits with a
multiplication sign. Disable it while preserving ss01 and ss04.
* [autofix.ci] apply automated fixes
* Add release notes for PR #7375
* [autofix.ci] apply automated fixes
* Fix release notes category casing
* Add authors field to release notes
* Update upcoming-release-notes/7375.md
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
* fix: show consistent 'Date is a required field' error in add transaction
When adding a new transaction with a missing date, the UI surfaced the
generic 'Something internally went wrong' error instead of a specific
message, while missing account showed 'Account is a required field'.
Add an analogous date validation mirroring the existing account check,
so both required-field errors are consistent.
Fixes#7424
* fix: prevent empty date from reaching server when clearing date input
When adding a transaction, clearing the date field and clicking Add
caused DateSelect's blur handler to save an empty string as the date
via onSelect(''). The server rejected '' as an invalid date, emitting
a generic "Something internally went wrong" error.
The previous fix (adding a date check in the shouldAdd guard) didn't
work because the cell save fires on blur BEFORE shouldAdd runs — by
the time shouldAdd checks the date, it's either already sent to the
server or reverted by DateSelect's clearOnBlur logic.
Fix: change DateSelect's empty-input blur path to restore the previous
valid date instead of propagating ''. This prevents the invalid value
from ever reaching the cell save handler or the server.
Verified locally: clear date → click Add → date restores to previous
value and transaction saves normally. No generic error.
Fixes#7424
* [AI] Keep mobile transaction amount field at a consistent height when empty
When the user backspaced every digit while editing the amount on the
mobile transaction form, the inner <Text> span had no content and
collapsed the pill to zero height. Fall back to a non-breaking space so
the line box keeps its natural font-driven height even when the input
is momentarily empty.
* Add release notes for PR #7638
* [AI] Use amountToCurrency(0) instead of nbsp when amount input is empty
Mirrors the unfocused-zero display rather than rendering a blank pill,
and respects the user's hideFraction pref and locale separators
through amountToCurrency.
* Simplify description of mobile transaction field height
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* [AI] cover existing template engine logic with regression tests
Adds tests for goal template behavior that predates this PR so the
suite can be cherry-picked onto master to confirm no regressions. No
production code changes.
Covers:
- init() validation: schedule names, by/schedule priority match, past
by-target with and without annual/repeat, percentage source not
found, special source aliases, duplicate limit/spend/goal
directives, weekly limit missing start date, invalid limit period,
unrecognized periodic period
- runRemainder cap clamping and hideDecimal fraction removal
- Income-category branch in runTemplatesForPriority
- getLimitExcess against an aggregate weekly cap
- Past by-target rolling forward via the annual period
- runSchedule full=true (no sinking accumulation), percent and fixed
adjustments, completed-schedule filtering, past-date error for
non-repeating schedules, monthly/weekly/daily sinking contribution
branches when interval exceeds the pay-month-of cap, surplus
absorption when last-month balance exceeds the target, and
tracking-budget mode forcing all schedules pay-month-of
- applyMultipleCategoryTemplates orchestration: per-category writes,
cross-category priority clamping when funds run out, error
notification path
- applyTemplate force=false skipping already-budgeted categories
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* note
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [AI] Cache the CLI's local budget between invocations
Every `actual <cmd>` call currently delta-syncs the budget with the
sync server via `api.downloadBudget`, which hits the server's
500-req/min rate limit on scripted workflows. Actual is local-first:
once the budget is on disk, most read commands do not need fresh
server data.
Introduce a CLI-only cache layer inside `withConnection` that
decides per invocation whether to skip, sync, or re-download:
- Cache state lives at `{dataDir}/.actual-cli/{syncId}/state.json`,
keyed by `syncId` to avoid the chicken-and-egg of not knowing the
on-disk `budgetId` before the first download. The on-disk id is
resolved via `api.getBudgets()` and persisted after first download.
- Read commands (list, balance, query run, …) skip the `/sync`
call while `now - lastSyncedAt < cacheTtl`. Write commands
(create, update, delete, set-*, etc.) sync before and after the
operation to keep server state consistent.
- Encrypted budgets force a sync per call since `api/load-budget`
does not re-verify the password.
- New `proper-lockfile`-backed shared/exclusive lock serializes
writes while allowing parallel reads. Reader markers live in
`{meta}/readers/`; writers sweep stale markers by PID.
New `actual sync` command with three modes: default (sync now),
`--status` (print cache age, TTL, stale flag), `--clear` (delete
cache, holding the exclusive lock to avoid racing writers).
New config surface, following the existing flag → env → config file
→ default precedence chain:
- `--cache-ttl <s>` / `ACTUAL_CACHE_TTL` / `cacheTtl` (default 60)
- `--refresh` / `--no-cache`
- `--lock-timeout <s>` / `ACTUAL_LOCK_TIMEOUT` / `lockTimeout` (10)
- `--no-lock` / `ACTUAL_NO_LOCK` / `noLock`
Every `withConnection` call site now passes an explicit
`{ mutates: boolean, skipBudget?: boolean }` so read/write intent is
visible at the edge.
The old `budgets sync` subcommand is removed — it silently diverged
from the new top-level `actual sync`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [AI] Simplify CLI cache/lock internals
Use a discriminated SyncDecision union so connection.ts no longer needs
non-null assertions on the cached state. Thread the resolved CliConfig
through withConnection's callback to drop duplicate resolveConfig calls
in the sync and budgets commands. Extract an errorCode helper and
replace the existsSync+readdirSync TOCTOU pattern in the reader-wait
polling loop with a single readdir that tolerates ENOENT.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [AI] Address PR #7539 review comments
- cache.ts: use a unique per-writer tmp filename so concurrent shared-
lock writers (encrypted budgets, --refresh, stale TTL) don't clobber
each other's publish and silently drop state updates.
- index.ts/config.ts: fix --no-cache and --no-lock flags. Commander
stores --no-foo under the positive key (cache/lock) and an explicit
false default makes the flag a no-op; the previous code also read
the wrong keys (noCache/noLock) so the flags had no effect at all.
Derive refresh/noLock from the correct keys.
- budgets.ts: invert encryption password precedence so the subcommand
flag (--encryption-password) wins over env/config-file values.
- sync.ts: report stale=true in --status when lastSyncedAt is in the
future, matching decideSyncAction's clock-skew handling.
- connection.ts: drop unnecessary `as` cast on api.getBudgets() now
that the return type is Promise<APIFileEntity[]>.
- utils.ts: parseBoolEnv throws on unrecognized values instead of
silently returning undefined so typos like ACTUAL_NO_LOCK=yes fail
loudly.
- Shorten 7539 release note to a single user-facing sentence.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: restored default functionality from v26.3.0 for reimport deleted transactions. import-reimport-deleted is now a synced pref that persists between imports
* release notes
* release note update
---------
Co-authored-by: Alec Bakholdin <alecbakholdin.com>
* Refactor to use directed, weighted graph as datamodel
* Fix percentage labels
* Reimplement sorting and topN handling
* Fix typing. Show toBudget on graph.
* Implement better DAG model
* Fix Other-grouping with new datamodel
* Add global sorting
* Reorder spreadsheet code for clarity
* Add percentageLabels back
* Fix all sorting modes
* Better color handling
* Handle if overbudgeted
* Fix filtering issue related to hidden nodes for Spent report
* Implement enums for special names
* Linting and typechecking
* Add layer selectors
* Trim SankeyCard
* Fix issue with empty nodes making the graph unreadable
* Add release note
* Update release note
* Reorder code
* Address coderabbit comments
* Ensure that layer-from and layer-to cannot be equal
* Update layer selectors to match selected view mode
* Fix wrong graph object reference
* Cap regex length
* Fixed wrong layer assignment for budget income categories
* Make translation not optional in createSpreadsheet
* Use predefined suffix for 'Other'
* Avoid invalid layer selection for Budgeted
* Update VRT screenshots
Auto-generated by VRT workflow
PR: #7582
* Import translation in spreadsheet, instead of passing as argument
* Remove all non-null assertions and handle safely
* Fix most uses of 'as'
* Fix issues hiding Other categories and giving wrong toBudget value
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* [AI] Notify Discord when nightly theme catalog scan fails
Adds an if: failure() step to the validate-theme-catalog job that posts a
minimal alert to the DISCORD_WEBHOOK_URL webhook with a link back to the
failing workflow run. Fires on both theme validation failures (script exits
1) and earlier step failures (checkout/setup), so infrastructure breakage
is also surfaced. nofail: true keeps a Discord outage from cascading into
a red job.
* [AI] Drop setup comment from Discord notify step
* [AI] Move Discord notify to its own job gated by an environment
Splits the notify step into a separate notify-failure job that depends on
validate-theme-catalog and runs only on failure. The new job binds to the
nightly-alerts GitHub Environment so the DISCORD_WEBHOOK_URL secret is
scoped to a dedicated environment rather than inherited at the repo level
(zizmor secrets-without-environment).
* [AI] Add release notes for 7595
---------
Co-authored-by: Claude <noreply@anthropic.com>
* [AI] Fix Docker build for workspace:* dependencies
Since @actual-app/crdt became a workspace:* dep, `yarn workspaces focus
--production` creates relative symlinks in node_modules that dangle when
only node_modules is copied into the prod image, breaking local Docker
builds with ERR_MODULE_NOT_FOUND: @actual-app/crdt.
Dereference yarn's workspace symlinks in the builder stage with `cp -RL`
so the prod stage can copy a self-contained node_modules without needing
to enumerate which workspace:* deps exist. Adding a new workspace:* dep
now requires zero Dockerfile changes.
Also move the sync-server .dockerignore to the repo root (and drop stray
local node_modules / .git / .yarn caches from the build context), since
docker builds use the repo root as context — the old sync-server-level
file was no longer being applied.
Fixes#7561.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [AI] Strip dev-only dirs from dereferenced workspace packages
The generic `cp -RL` step copies full workspace package trees into the
image (src/, e2e/, tests, build-stats, etc.). Remove them after the
dereference — they're not needed at runtime, and skipping them recovers
~67MB from the final image on both alpine and ubuntu variants.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [AI] Rephrase 7564 release note to be user-facing
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [AI] Add per-schedule custom upcoming length override
* [AI] Add release notes for PR #7434
* [AI] Add custom length option to per-schedule upcoming length selector
* [AI] Deduplicate preset values and guard against malformed custom upcoming length
* [autofix.ci] apply automated fixes
* [AI] Retrigger CI (flaky accounts E2E test)
* [AI] Improve schedule editor layout for upcoming length field
Restructure the custom upcoming length from a checkbox + conditional
dropdown into a proper FormField with a single Select that includes
"Use global default" as the null option. Place it in a responsive
side-by-side row with the auto-post checkbox, consistent with the
form's existing layout patterns.
* [AI] Address CodeRabbit review feedback
Tighten custom upcoming length validation with a proper regex
instead of a loose hyphen check. Replace fixed height with minHeight
on the auto-post checkbox row to avoid clipping translated labels.
* Update VRT screenshots
Auto-generated by VRT workflow
PR: #7434
* [AI] Fix custom_upcoming_length not persisting on mobile
The mobile schedule save handler was missing the custom_upcoming_length
field from the payload sent to schedule/create and schedule/update.
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: youngcw <calebyoung94@gmail.com>
* [AI] Expose API entity types via @actual-app/api/models
Adds a new `./models` subpath export on `@actual-app/api` that re-exports
the public API entity types (`APIAccountEntity`, `APICategoryEntity`,
`APICategoryGroupEntity`, `APIFileEntity`, `APIPayeeEntity`,
`APIScheduleEntity`, `APITagEntity`, `AmountOPType`) from
`@actual-app/core/server/api-models`. Consumers can now import these types
from a stable public entry point instead of reaching into core internals:
import type {
APICategoryEntity,
APICategoryGroupEntity,
} from '@actual-app/api/models';
Uses `export type *` so the compiled `dist/models.js` is empty and no
runtime code is added. The Vite lib config is expanded to a multi-entry
map (`index`, `models`) so both bundles are produced, and tsgo already
emits `@types/models.d.ts` via the existing `declarationDir` setup.
* Add release notes for PR #7581
* Modify release notes for API model exports
Updated category from 'Features' to 'Enhancements' and added API export details.
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* [AI] Add nightly CI scan for custom theme catalog
Adds a scheduled GitHub Actions workflow that fetches `actual.css` from
every repo in `customThemeCatalog.json` and runs it through the same
`embedThemeFonts` + `validateThemeCss` pipeline the app uses at install
time. Failing themes fail the job so maintainers get an alert when a
third-party repo introduces a regression.
The scan treats fetched CSS as opaque text: never executed, never
injected into a DOM, size-capped at 512 KB per file, 15s per fetch,
restricted to raw.githubusercontent.com with redirects disabled, and
run with `contents: read` permissions only. Each catalog `repo` is
schema-checked against `owner/repo` before being interpolated into
the URL.
* [AI] Simplify theme catalog scan
- Reuse `CatalogTheme` type from customThemes instead of duplicating as
`CatalogEntry` in the script.
- Hoist `appendFileSync` to the static `node:fs` import; drop the dynamic
import inside `writeStepSummary`.
- Drop the narrative header docstring and the trailing `// ...` comments
that just restated constant names.
- Drop the redundant URL-prefix re-check inside the CSS fetch helper;
the single call site constructs the URL from a pinned literal.
- Drop the 250 ms inter-request delay (GitHub Raw rate limits are not
relevant for 21 requests, and the trailing delay was idle wall-clock
against the 10-min job budget).
- Give each font fetch inside `embedThemeFonts` its own 15 s timeout
via `AbortSignal.any`, instead of sharing one signal across every
font in a theme. Drop the now-unnecessary caller-supplied signal
from the CI call site.
* [AI] Fix lint on theme catalog scan imports
* [AI] Speed up and stabilize Playwright e2e tests
- Serve the prebuilt browser bundle via bin/serve-build.mjs in CI to
skip per-shard Vite startup; 3-shard matrix with 4 workers each.
- Disable CSS animations in non-VRT runs via a fixture-level init
script; bump expect timeout to 10s for AutoSizer-bound assertions.
- Use page.evaluate() for React Aria button clicks and a native value
setter + single input event for controlled-input fills to eliminate
React Aria re-render races in createAccount and Payee/Category
autocompletes.
- Click the matching option directly (instead of Enter on a not-yet-
highlighted list) in mobile transaction and schedule autocompletes.
- FocusableAmountInput.applyText reads the DOM input value so the
typed amount survives a blur that fires before React flushes the
onChange state update under CPU contention.
- MobileTransactionEntryPage.fillAmount waits for the outer display
button (reads parent props.value) so async rules-run completes
before the next fillField snapshots the transaction.
- MobileNavigation dispatches nav link clicks through evaluate() to
bypass Playwright's viewport-stability check against the navbar's
react-spring transforms.
- MobileBudgetPage summary-button lookups use locator.or().waitFor()
instead of an isVisible() cascade.
- ConfigurationPage.startFresh/createTestFile wait for the account
header / budget table to mount before returning.
- Workflow hardening: persist-credentials=false on all actions/checkout
and top-level permissions: contents: read (zizmor findings).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [AI] Apply animation-disable init script to browser.newPage pages
The previous implementation extended the test-scoped `page` fixture,
but every test creates its own page via `browser.newPage()` and never
uses the fixture-provided page — so the init script was a no-op in
every test.
Move the wrap to the worker-scoped `browser` fixture: intercept
`browser.newPage` so each page created that way has `addInitScript`
applied before the caller can navigate to it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>