[PR #7261] [AI] Replace payee and category autocomplete filter/sort with fzf fuzy search #14100

Open
opened 2026-04-10 22:13:09 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/actualbudget/actual/pull/7261
Author: @RiiD
Created: 3/22/2026
Status: 🔄 Open

Base: masterHead: feat/fzf-autocomplete


📝 Commits (7)

  • 5a3c8bc [AI] Replace payee and category autocomplete filter/sort with fzf fuzzy search
  • c2d47b6 [AI] Add release notes for fzf autocomplete change
  • 6a2bb06 [autofix.ci] apply automated fixes
  • 69d6f7b [AI] Move fzf to devDependencies
  • c2ab761 [AI] Fix fzf consistency: add limit to category autocomplete, use fzf for nearby payees
  • a6bd3b5 Update VRT screenshots
  • b64180a Merge branch 'master' into feat/fzf-autocomplete

📊 Changes

8 files changed (+215 additions, -205 deletions)

View changed files

📝 packages/desktop-client/e2e/transactions.test.ts-snapshots/Transactions-filters-transactions-by-category-7-chromium-linux.png (+0 -0)
📝 packages/desktop-client/e2e/transactions.test.ts-snapshots/Transactions-filters-transactions-by-category-8-chromium-linux.png (+0 -0)
📝 packages/desktop-client/e2e/transactions.test.ts-snapshots/Transactions-filters-transactions-by-category-9-chromium-linux.png (+0 -0)
📝 packages/desktop-client/package.json (+1 -0)
📝 packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx (+15 -39)
📝 packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.tsx (+31 -40)
upcoming-release-notes/7261.md (+6 -0)
📝 yarn.lock (+162 -126)

📄 Description

Description

This PR replaces the custom substring-based filter and sort logic in PayeeAutocomplete and CategoryAutocomplete with the fzf fuzzy search library.

Why is this needed?

When adding transactions manually, finding the right payee or category quickly matters a lot. The existing autocomplete only matches substrings — so if you have many payees starting with "Home" (Home Depot, Home Hardware, Home Insurance...), you have to type almost the full name before the right one surfaces. There's no way to narrow down by word initials or non-contiguous characters.

With fzf, you can type hdep and immediately get Home Depot as the top result — the search understands word boundaries and ranks results by match quality. This is the kind of fuzzy search users are accustomed to from editors, terminals, and most modern search UIs.

This benefits anyone who adds transactions manually and has a large payee list, which is a common workflow for Actual users who prefer hands-on budgeting.

Changes:

  • Payee search: fuzzy matching with smart-case (lowercase = case-insensitive, uppercase = case-sensitive)
  • Category search: fuzzy matching across both group name and category name (e.g. food groc matches Food › Groceries)
  • Results ranked by match quality instead of a fixed substring priority

Before/After

Payee autocomplete - before Payee autocomplete - after Category autocomplete - before Category autocomplete - after

None

Testing

  1. Open the app and navigate to any account
  2. Add or edit a transaction and focus the Payee field
  3. Type a partial query and verify fuzzy results appear ranked by relevance (e.g. ms should surface MS Office before Mostafa Supermarket before Maxims Bakery)
  4. Type an uppercase query (e.g. MS) and verify only case-matching results appear
  5. Focus the Category field and type a group name prefix (e.g. food) — verify categories from that group appear
  6. Type food groc and verify Food › Groceries matches

Checklist

  • Release notes added (see link above)
  • No obvious regressions in affected areas
  • Self-review has been performed - I understand what each change in the code does and why it is needed

Bundle Stats

Bundle Files count Total bundle size % Changed
desktop-client 27 12.51 MB → 12.09 MB (-431.71 kB) -3.37%
loot-core 1 4.83 MB → 4.83 MB (+302 B) +0.01%
api 4 4.06 MB → 4.06 MB (+304 B) +0.01%
cli 1 7.88 MB 0%
View detailed bundle stats

desktop-client

Total

Files count Total bundle size % Changed
27 12.51 MB → 12.09 MB (-431.71 kB) -3.37%
Changeset
File Δ Size
node_modules/fzf/dist/fzf.es.js 🆕 +21.09 kB 0 B → 21.09 kB
node_modules/@tanstack/query-core/build/modern/environmentManager.js 🆕 +296 B 0 B → 296 B
node_modules/@babel/runtime/helpers/esm/inheritsLoose.js 📈 +23 B (+10.70%) 215 B → 238 B
node_modules/@babel/runtime/helpers/esm/setPrototypeOf.js 📈 +23 B (+8.33%) 276 B → 299 B
node_modules/@babel/runtime/helpers/esm/objectWithoutPropertiesLoose.js 📈 +23 B (+7.42%) 310 B → 333 B
node_modules/@babel/runtime/helpers/esm/extends.js 📈 +23 B (+6.22%) 370 B → 393 B
node_modules/react-i18next/dist/es/useTranslation.js 📈 +278 B (+5.13%) 5.3 kB → 5.57 kB
node_modules/@tanstack/query-core/build/modern/removable.js 📈 +21 B (+3.51%) 598 B → 619 B
node_modules/@tanstack/query-core/build/modern/query.js 📈 +236 B (+2.36%) 9.77 kB → 10 kB
node_modules/@tanstack/query-core/build/modern/onlineManager.js 📈 +20 B (+1.64%) 1.19 kB → 1.21 kB
node_modules/@tanstack/query-core/build/modern/focusManager.js 📈 +20 B (+1.55%) 1.26 kB → 1.28 kB
src/build-shims.js 📈 +4 B (+1.08%) 370 B → 374 B
node_modules/@tanstack/react-query/build/modern/useBaseQuery.js 📈 +21 B (+0.93%) 2.19 kB → 2.21 kB
node_modules/@tanstack/query-core/build/modern/retryer.js 📈 +21 B (+0.70%) 2.92 kB → 2.94 kB
package.json 📈 +18 B (+0.55%) 3.22 kB → 3.24 kB
node_modules/i18next/dist/esm/i18next.js 📈 +328 B (+0.44%) 72.61 kB → 72.93 kB
src/util/versions.ts 📈 +2 B (+0.22%) 904 B → 906 B
src/components/transactions/TransactionMenu.tsx 📈 +16 B (+0.19%) 8.29 kB → 8.3 kB
node_modules/@use-gesture/core/dist/use-gesture-core.esm.js 📈 +4 B (+0.05%) 7.45 kB → 7.45 kB
node_modules/@tanstack/query-core/build/modern/queryObserver.js 📉 -2 B (-0.01%) 13.71 kB → 13.71 kB
node_modules/downshift/dist/downshift.esm.mjs 📉 -36 B (-0.03%) 105.83 kB → 105.79 kB
src/components/autocomplete/CategoryAutocomplete.tsx 📉 -240 B (-1.53%) 15.36 kB → 15.13 kB
View detailed bundle breakdown

Added
No assets were added

Removed
No assets were removed

Bigger

Asset File Size % Changed
static/js/useTransactionBatchActions.js 4.29 MB → 4.31 MB (+21.59 kB) +0.49%
static/js/FormulaEditor.js 846.44 kB → 847.11 kB (+680 B) +0.08%
static/js/index.js 3.23 MB → 3.23 MB (+18 B) +0.00%

Smaller

Asset File Size % Changed
static/js/theme.js 484.67 kB → 30.68 kB (-453.99 kB) -93.67%

Unchanged

Asset File Size % Changed
static/js/BackgroundImage.js 119.98 kB 0%
static/js/ReportRouter.js 1021.25 kB 0%
static/js/TransactionList.js 81.29 kB 0%
static/js/ca.js 185.57 kB 0%
static/js/da.js 104.66 kB 0%
static/js/de.js 177.58 kB 0%
static/js/en-GB.js 7.16 kB 0%
static/js/en.js 170.68 kB 0%
static/js/es.js 172.13 kB 0%
static/js/fr.js 177.57 kB 0%
static/js/indexeddb-main-thread-worker-e59fee74.js 13.46 kB 0%
static/js/it.js 168.97 kB 0%
static/js/narrow.js 354.12 kB 0%
static/js/nb-NO.js 154.72 kB 0%
static/js/nl.js 111.58 kB 0%
static/js/pl.js 88.34 kB 0%
static/js/pt-BR.js 180.5 kB 0%
static/js/resize-observer.js 18.03 kB 0%
static/js/sv.js 80.58 kB 0%
static/js/th.js 179.94 kB 0%
static/js/uk.js 213.14 kB 0%
static/js/wide.js 418 B 0%
static/js/workbox-window.prod.es5.js 7.28 kB 0%

loot-core

Total

Files count Total bundle size % Changed
1 4.83 MB → 4.83 MB (+302 B) +0.01%
Changeset
File Δ Size
node_modules/i18next/dist/esm/i18next.js 📈 +302 B (+0.40%) 74.53 kB → 74.83 kB
View detailed bundle breakdown

Added

Asset File Size % Changed
kcab.worker.By_0WaVr.js 0 B → 4.83 MB (+4.83 MB) -

Removed

Asset File Size % Changed
kcab.worker.Dmj0rSrb.js 4.83 MB → 0 B (-4.83 MB) -100%

Bigger
No assets were bigger

Smaller
No assets were smaller

Unchanged
No assets were unchanged


api

Total

Files count Total bundle size % Changed
4 4.06 MB → 4.06 MB (+304 B) +0.01%
Changeset
File Δ Size
node_modules/i18next/dist/esm/i18next.js 📈 +304 B (+0.41%) 72.62 kB → 72.92 kB
View detailed bundle breakdown

Added
No assets were added

Removed
No assets were removed

Bigger

Asset File Size % Changed
index.js 3.84 MB → 3.84 MB (+304 B) +0.01%

Smaller
No assets were smaller

Unchanged

Asset File Size % Changed
from-Bl-Hslp4.js 167.73 kB 0%
multipart-parser-BnDysoMr.js 8.1 kB 0%
src-iMkUmuwR.js 43.64 kB 0%

cli

Total

Files count Total bundle size % Changed
1 7.88 MB 0%
View detailed bundle breakdown

Added
No assets were added

Removed
No assets were removed

Bigger
No assets were bigger

Smaller
No assets were smaller

Unchanged

Asset File Size % Changed
cli.js 7.88 MB 0%

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/actualbudget/actual/pull/7261 **Author:** [@RiiD](https://github.com/RiiD) **Created:** 3/22/2026 **Status:** 🔄 Open **Base:** `master` ← **Head:** `feat/fzf-autocomplete` --- ### 📝 Commits (7) - [`5a3c8bc`](https://github.com/actualbudget/actual/commit/5a3c8bccd02d03659fbf62f70632aa871c4cac45) [AI] Replace payee and category autocomplete filter/sort with fzf fuzzy search - [`c2d47b6`](https://github.com/actualbudget/actual/commit/c2d47b6c045b23bd3585faf07544afeac5c1f5b7) [AI] Add release notes for fzf autocomplete change - [`6a2bb06`](https://github.com/actualbudget/actual/commit/6a2bb0669c7a6021db9c5dc56adb703bc0343fd1) [autofix.ci] apply automated fixes - [`69d6f7b`](https://github.com/actualbudget/actual/commit/69d6f7beff16f92d17086084298ad0ca8ada157b) [AI] Move fzf to devDependencies - [`c2ab761`](https://github.com/actualbudget/actual/commit/c2ab761fec51b481c0b4f2074524a035a5c35b28) [AI] Fix fzf consistency: add limit to category autocomplete, use fzf for nearby payees - [`a6bd3b5`](https://github.com/actualbudget/actual/commit/a6bd3b5547797a6b6d9807ff9cb7fb38b4b373bb) Update VRT screenshots - [`b64180a`](https://github.com/actualbudget/actual/commit/b64180afeb34e5ce82841001d74fec0df23e8234) Merge branch 'master' into feat/fzf-autocomplete ### 📊 Changes **8 files changed** (+215 additions, -205 deletions) <details> <summary>View changed files</summary> 📝 `packages/desktop-client/e2e/transactions.test.ts-snapshots/Transactions-filters-transactions-by-category-7-chromium-linux.png` (+0 -0) 📝 `packages/desktop-client/e2e/transactions.test.ts-snapshots/Transactions-filters-transactions-by-category-8-chromium-linux.png` (+0 -0) 📝 `packages/desktop-client/e2e/transactions.test.ts-snapshots/Transactions-filters-transactions-by-category-9-chromium-linux.png` (+0 -0) 📝 `packages/desktop-client/package.json` (+1 -0) 📝 `packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx` (+15 -39) 📝 `packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.tsx` (+31 -40) ➕ `upcoming-release-notes/7261.md` (+6 -0) 📝 `yarn.lock` (+162 -126) </details> ### 📄 Description <!-- Thank you for submitting a pull request! Make sure to follow the instructions to write release notes for your PR — it should only take a minute or two: https://github.com/actualbudget/docs#writing-good-release-notes. Try running yarn generate:release-notes *before* pushing your PR for an interactive experience. --> ## Description <!-- What does this PR do? Why is it needed? Please give context on the "why?": why do we need this change? What problem is it solving for you?--> This PR replaces the custom substring-based filter and sort logic in `PayeeAutocomplete` and `CategoryAutocomplete` with the [`fzf`](https://fzf.netlify.app/docs/latest) fuzzy search library. **Why is this needed?** When adding transactions manually, finding the right payee or category quickly matters a lot. The existing autocomplete only matches substrings — so if you have many payees starting with "Home" (Home Depot, Home Hardware, Home Insurance...), you have to type almost the full name before the right one surfaces. There's no way to narrow down by word initials or non-contiguous characters. With fzf, you can type `hdep` and immediately get `Home Depot` as the top result — the search understands word boundaries and ranks results by match quality. This is the kind of fuzzy search users are accustomed to from editors, terminals, and most modern search UIs. This benefits anyone who adds transactions manually and has a large payee list, which is a common workflow for Actual users who prefer hands-on budgeting. **Changes:** - Payee search: fuzzy matching with smart-case (lowercase = case-insensitive, uppercase = case-sensitive) - Category search: fuzzy matching across both group name and category name (e.g. `food groc` matches `Food › Groceries`) - Results ranked by match quality instead of a fixed substring priority **Before/After** <img src="https://github.com/user-attachments/assets/1d25a054-3781-46d3-b1a0-41f6b2693856" width="45%" title="Payee autocomplete - before" alt="Payee autocomplete - before"/> <img src="https://github.com/user-attachments/assets/bcda7e3b-cce1-4c49-9a6b-8da3b258404f" width="45%" title="Payee autocomplete - after" alt="Payee autocomplete - after"/> <img src="https://github.com/user-attachments/assets/4eb4cfbe-9960-42dc-98dd-46249b5a0471" width="45%" title="Category autocomplete - before" alt="Category autocomplete - before"/> <img src="https://github.com/user-attachments/assets/c704c699-2fdd-4746-ba7e-fa8818cf2d8e" width="45%" title="Category autocomplete - after" alt="Category autocomplete - after"/> ## Related issue(s) <!-- e.g. Fixes #123, Relates to #456 --> None ## Testing <!-- What did you test? How can we reproduce the issue you are fixing or how can we test the feature you built? --> 1. Open the app and navigate to any account 2. Add or edit a transaction and focus the **Payee** field 3. Type a partial query and verify fuzzy results appear ranked by relevance (e.g. `ms` should surface `MS Office` before `Mostafa Supermarket` before `Maxims Bakery`) 4. Type an uppercase query (e.g. `MS`) and verify only case-matching results appear 5. Focus the **Category** field and type a group name prefix (e.g. `food`) — verify categories from that group appear 6. Type `food groc` and verify `Food › Groceries` matches ## Checklist - [x] Release notes added (see link above) - [x] No obvious regressions in affected areas - [x] Self-review has been performed - I understand what each change in the code does and why it is needed <!--- actual-bot-sections ---> <!--- bundlestats-action-comment key:combined start ---> ### Bundle Stats Bundle | Files count | Total bundle size | % Changed ------ | ----------- | ----------------- | --------- desktop-client | 27 | 12.51 MB → 12.09 MB (-431.71 kB) | -3.37% loot-core | 1 | 4.83 MB → 4.83 MB (+302 B) | +0.01% api | 4 | 4.06 MB → 4.06 MB (+304 B) | +0.01% cli | 1 | 7.88 MB | 0% <details> <summary>View detailed bundle stats</summary> #### desktop-client **Total** Files count | Total bundle size | % Changed ----------- | ----------------- | --------- 27 | 12.51 MB → 12.09 MB (-431.71 kB) | -3.37% <details> <summary>Changeset</summary> File | Δ | Size ---- | - | ---- `node_modules/fzf/dist/fzf.es.js` | 🆕 +21.09 kB | 0 B → 21.09 kB `node_modules/@tanstack/query-core/build/modern/environmentManager.js` | 🆕 +296 B | 0 B → 296 B `node_modules/@babel/runtime/helpers/esm/inheritsLoose.js` | 📈 +23 B (+10.70%) | 215 B → 238 B `node_modules/@babel/runtime/helpers/esm/setPrototypeOf.js` | 📈 +23 B (+8.33%) | 276 B → 299 B `node_modules/@babel/runtime/helpers/esm/objectWithoutPropertiesLoose.js` | 📈 +23 B (+7.42%) | 310 B → 333 B `node_modules/@babel/runtime/helpers/esm/extends.js` | 📈 +23 B (+6.22%) | 370 B → 393 B `node_modules/react-i18next/dist/es/useTranslation.js` | 📈 +278 B (+5.13%) | 5.3 kB → 5.57 kB `node_modules/@tanstack/query-core/build/modern/removable.js` | 📈 +21 B (+3.51%) | 598 B → 619 B `node_modules/@tanstack/query-core/build/modern/query.js` | 📈 +236 B (+2.36%) | 9.77 kB → 10 kB `node_modules/@tanstack/query-core/build/modern/onlineManager.js` | 📈 +20 B (+1.64%) | 1.19 kB → 1.21 kB `node_modules/@tanstack/query-core/build/modern/focusManager.js` | 📈 +20 B (+1.55%) | 1.26 kB → 1.28 kB `src/build-shims.js` | 📈 +4 B (+1.08%) | 370 B → 374 B `node_modules/@tanstack/react-query/build/modern/useBaseQuery.js` | 📈 +21 B (+0.93%) | 2.19 kB → 2.21 kB `node_modules/@tanstack/query-core/build/modern/retryer.js` | 📈 +21 B (+0.70%) | 2.92 kB → 2.94 kB `package.json` | 📈 +18 B (+0.55%) | 3.22 kB → 3.24 kB `node_modules/i18next/dist/esm/i18next.js` | 📈 +328 B (+0.44%) | 72.61 kB → 72.93 kB `src/util/versions.ts` | 📈 +2 B (+0.22%) | 904 B → 906 B `src/components/transactions/TransactionMenu.tsx` | 📈 +16 B (+0.19%) | 8.29 kB → 8.3 kB `node_modules/@use-gesture/core/dist/use-gesture-core.esm.js` | 📈 +4 B (+0.05%) | 7.45 kB → 7.45 kB `node_modules/@tanstack/query-core/build/modern/queryObserver.js` | 📉 -2 B (-0.01%) | 13.71 kB → 13.71 kB `node_modules/downshift/dist/downshift.esm.mjs` | 📉 -36 B (-0.03%) | 105.83 kB → 105.79 kB `src/components/autocomplete/CategoryAutocomplete.tsx` | 📉 -240 B (-1.53%) | 15.36 kB → 15.13 kB </details> <details> <summary>View detailed bundle breakdown</summary> <div> **Added** No assets were added **Removed** No assets were removed **Bigger** Asset | File Size | % Changed ----- | --------- | --------- static/js/useTransactionBatchActions.js | 4.29 MB → 4.31 MB (+21.59 kB) | +0.49% static/js/FormulaEditor.js | 846.44 kB → 847.11 kB (+680 B) | +0.08% static/js/index.js | 3.23 MB → 3.23 MB (+18 B) | +0.00% **Smaller** Asset | File Size | % Changed ----- | --------- | --------- static/js/theme.js | 484.67 kB → 30.68 kB (-453.99 kB) | -93.67% **Unchanged** Asset | File Size | % Changed ----- | --------- | --------- static/js/BackgroundImage.js | 119.98 kB | 0% static/js/ReportRouter.js | 1021.25 kB | 0% static/js/TransactionList.js | 81.29 kB | 0% static/js/ca.js | 185.57 kB | 0% static/js/da.js | 104.66 kB | 0% static/js/de.js | 177.58 kB | 0% static/js/en-GB.js | 7.16 kB | 0% static/js/en.js | 170.68 kB | 0% static/js/es.js | 172.13 kB | 0% static/js/fr.js | 177.57 kB | 0% static/js/indexeddb-main-thread-worker-e59fee74.js | 13.46 kB | 0% static/js/it.js | 168.97 kB | 0% static/js/narrow.js | 354.12 kB | 0% static/js/nb-NO.js | 154.72 kB | 0% static/js/nl.js | 111.58 kB | 0% static/js/pl.js | 88.34 kB | 0% static/js/pt-BR.js | 180.5 kB | 0% static/js/resize-observer.js | 18.03 kB | 0% static/js/sv.js | 80.58 kB | 0% static/js/th.js | 179.94 kB | 0% static/js/uk.js | 213.14 kB | 0% static/js/wide.js | 418 B | 0% static/js/workbox-window.prod.es5.js | 7.28 kB | 0% </div> </details> --- #### loot-core **Total** Files count | Total bundle size | % Changed ----------- | ----------------- | --------- 1 | 4.83 MB → 4.83 MB (+302 B) | +0.01% <details> <summary>Changeset</summary> File | Δ | Size ---- | - | ---- `node_modules/i18next/dist/esm/i18next.js` | 📈 +302 B (+0.40%) | 74.53 kB → 74.83 kB </details> <details> <summary>View detailed bundle breakdown</summary> <div> **Added** Asset | File Size | % Changed ----- | --------- | --------- kcab.worker.By_0WaVr.js | 0 B → 4.83 MB (+4.83 MB) | - **Removed** Asset | File Size | % Changed ----- | --------- | --------- kcab.worker.Dmj0rSrb.js | 4.83 MB → 0 B (-4.83 MB) | -100% **Bigger** No assets were bigger **Smaller** No assets were smaller **Unchanged** No assets were unchanged </div> </details> --- #### api **Total** Files count | Total bundle size | % Changed ----------- | ----------------- | --------- 4 | 4.06 MB → 4.06 MB (+304 B) | +0.01% <details> <summary>Changeset</summary> File | Δ | Size ---- | - | ---- `node_modules/i18next/dist/esm/i18next.js` | 📈 +304 B (+0.41%) | 72.62 kB → 72.92 kB </details> <details> <summary>View detailed bundle breakdown</summary> <div> **Added** No assets were added **Removed** No assets were removed **Bigger** Asset | File Size | % Changed ----- | --------- | --------- index.js | 3.84 MB → 3.84 MB (+304 B) | +0.01% **Smaller** No assets were smaller **Unchanged** Asset | File Size | % Changed ----- | --------- | --------- from-Bl-Hslp4.js | 167.73 kB | 0% multipart-parser-BnDysoMr.js | 8.1 kB | 0% src-iMkUmuwR.js | 43.64 kB | 0% </div> </details> --- #### cli **Total** Files count | Total bundle size | % Changed ----------- | ----------------- | --------- 1 | 7.88 MB | 0% <details> <summary>View detailed bundle breakdown</summary> <div> **Added** No assets were added **Removed** No assets were removed **Bigger** No assets were bigger **Smaller** No assets were smaller **Unchanged** Asset | File Size | % Changed ----- | --------- | --------- cli.js | 7.88 MB | 0% </div> </details> </details> <!--- bundlestats-action-comment key:combined end ---> --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-04-10 22:13:10 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/actual#14100