[PR #6855] Fix Find & Replace duplicate updates for split transactions #6776

Closed
opened 2026-02-28 21:32:32 -06:00 by GiteaMirror · 0 comments
Owner

Original Pull Request: https://github.com/actualbudget/actual/pull/6855

State: closed
Merged: No


Problem

Find & Replace operations on split transactions cause duplicates and data corruption when multiple members of the same split are selected. Each selected member triggers updateTransaction() which returns a diff for the entire split, leading to:

  • Duplicate entries in the changes array (same transaction updated N times)
  • Non-idempotent operations applied multiple times ("test""testtest""testtesttest")

Solution

Track processed splits to ensure each split is only updated once, regardless of how many members are selected:

const processedSplits = new Set<string>();

const findSplitParentId = (trans: TransactionEntity): string => {
  if (trans.is_parent) return trans.id;
  if (trans.is_child && trans.parent_id) return trans.parent_id;
  return trans.id;
};

// In the forEach loop:
if (trans.is_parent || trans.is_child) {
  const splitParentId = findSplitParentId(trans);
  if (processedSplits.has(splitParentId)) return;
  processedSplits.add(splitParentId);
}

Changes

  • useTransactionBatchActions.ts: Add split deduplication logic
  • util.test.ts: Add test coverage for applyFindReplace (basic replacement, regex, null handling, edge cases)
Original prompt

This section details on the original issue you should resolve

<issue_title>[Bug]: Find & Replace not working correctly all the time</issue_title>
<issue_description>### Verified issue does not already exist?

  • I have searched and found no existing issue

What happened?

After a few large find/replace operations, I found a few duplicated transactions and some missing transactions. Previously reconciled accounts with matching running balances no longer matched.

One transaction, in a closed account, was duplicated twice.

I had to revert to a backup.

How can we reproduce the issue?

How can we reproduce the issue? I don't believe I can reproduce it exactly, nor do I want to mess with my data again. Also, the find/replace works strangely and has no "Apply", you have to press "Enter". Not intuitive at all.

Where are you hosting Actual?

None

What browsers are you seeing the problem on?

No response

Operating System

None</issue_description>

Comments on the Issue (you are @copilot in this section)

@youngcw I wonder if rules are getting run after and causing the problems. @youngcw I looked at the code for the find an replace again. Nothing in there seems like it would change amounts or create transactions. I would check your rules to see if anything jumps out. @youngcw If you could do it again and try to figure out edit caused the issue that would be useful. You should be able to CTRL-Z the changes once you find the one that is breaking things @youngcw I wonder if it isn't because of merged transactions, but transfers in general. I wonder if new transfers are getting made @MatissJanis Which PR introduced find&replace? We (or I) can ask an AI agent to do a preliminary analysis there to see if it finds any problems. @MatissJanis @copilot please investigate what could cause the problem reported in this issue. It was possibly introduced in actualbudget/actual#6282

Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.


Bundle Stats

Bundle Files count Total bundle size % Changed
desktop-client 28 14.47 MB → 14.47 MB (+3.97 kB) +0.03%
loot-core 1 5.84 MB 0%
api 1 4.38 MB 0%
View detailed bundle stats

desktop-client

Total

Files count Total bundle size % Changed
28 14.47 MB → 14.47 MB (+3.97 kB) +0.03%
Changeset
File Δ Size
src/hooks/useTransactionBatchActions.ts 📈 +559 B (+4.24%) 12.87 kB → 13.42 kB
locale/es.json 📈 +2.71 kB (+1.58%) 171.21 kB → 173.92 kB
locale/de.json 📈 +734 B (+0.40%) 177.78 kB → 178.5 kB
View detailed bundle breakdown

Added
No assets were added

Removed
No assets were removed

Bigger

Asset File Size % Changed
static/js/es.js 171.21 kB → 173.92 kB (+2.71 kB) +1.58%
static/js/de.js 177.78 kB → 178.5 kB (+734 B) +0.40%
static/js/useTransactionBatchActions.js 13.23 kB → 13.77 kB (+559 B) +4.13%

Smaller
No assets were smaller

Unchanged

Asset File Size % Changed
static/js/index.js 9.22 MB 0%
static/js/indexeddb-main-thread-worker-e59fee74.js 12.94 kB 0%
static/js/workbox-window.prod.es5.js 5.64 kB 0%
static/js/da.js 106.62 kB 0%
static/js/en-GB.js 7.18 kB 0%
static/js/en.js 162.91 kB 0%
static/js/fr.js 179.72 kB 0%
static/js/it.js 171.54 kB 0%
static/js/nb-NO.js 157.23 kB 0%
static/js/nl.js 106.65 kB 0%
static/js/pl.js 88.64 kB 0%
static/js/pt-BR.js 146.62 kB 0%
static/js/ru.js 106.97 kB 0%
static/js/sv.js 78.2 kB 0%
static/js/th.js 182.35 kB 0%
static/js/uk.js 215.11 kB 0%
static/js/resize-observer.js 18.37 kB 0%
static/js/BackgroundImage.js 120.54 kB 0%
static/js/ReportRouter.js 1.11 MB 0%
static/js/narrow.js 641.19 kB 0%
static/js/TransactionList.js 105.97 kB 0%
static/js/wide.js 160.07 kB 0%
static/js/AppliedFilters.js 9.71 kB 0%
static/js/usePayeeRuleCounts.js 11.79 kB 0%
static/js/FormulaEditor.js 1.04 MB 0%

loot-core

Total

Files count Total bundle size % Changed
1 5.84 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
kcab.worker.DE5uAdQe.js 5.84 MB 0%

api

Total

Files count Total bundle size % Changed
1 4.38 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
bundle.api.js 4.38 MB 0%
**Original Pull Request:** https://github.com/actualbudget/actual/pull/6855 **State:** closed **Merged:** No --- ## Problem Find & Replace operations on split transactions cause duplicates and data corruption when multiple members of the same split are selected. Each selected member triggers `updateTransaction()` which returns a diff for the entire split, leading to: - Duplicate entries in the changes array (same transaction updated N times) - Non-idempotent operations applied multiple times (`"test"` → `"testtest"` → `"testtesttest"`) ## Solution Track processed splits to ensure each split is only updated once, regardless of how many members are selected: ```typescript const processedSplits = new Set<string>(); const findSplitParentId = (trans: TransactionEntity): string => { if (trans.is_parent) return trans.id; if (trans.is_child && trans.parent_id) return trans.parent_id; return trans.id; }; // In the forEach loop: if (trans.is_parent || trans.is_child) { const splitParentId = findSplitParentId(trans); if (processedSplits.has(splitParentId)) return; processedSplits.add(splitParentId); } ``` ## Changes - **`useTransactionBatchActions.ts`**: Add split deduplication logic - **`util.test.ts`**: Add test coverage for `applyFindReplace` (basic replacement, regex, null handling, edge cases) <!-- START COPILOT ORIGINAL PROMPT --> <details> <summary>Original prompt</summary> > > ---- > > *This section details on the original issue you should resolve* > > <issue_title>[Bug]: Find & Replace not working correctly all the time</issue_title> > <issue_description>### Verified issue does not already exist? > > - [x] I have searched and found no existing issue > > ### What happened? > > After a few large find/replace operations, I found a few duplicated transactions and some missing transactions. Previously reconciled accounts with matching running balances no longer matched. > > One transaction, in a closed account, was duplicated twice. > > I had to revert to a backup. > > ### How can we reproduce the issue? > > How can we reproduce the issue? I don't believe I can reproduce it exactly, nor do I want to mess with my data again. Also, the find/replace works strangely and has no "Apply", you have to press "Enter". Not intuitive at all. > > ### Where are you hosting Actual? > > None > > ### What browsers are you seeing the problem on? > > _No response_ > > ### Operating System > > None</issue_description> > > ## Comments on the Issue (you are @copilot in this section) > > <comments> > <comment_new><author>@youngcw</author><body> > I wonder if rules are getting run after and causing the problems. </body></comment_new> > <comment_new><author>@youngcw</author><body> > I looked at the code for the find an replace again. Nothing in there seems like it would change amounts or create transactions. I would check your rules to see if anything jumps out. </body></comment_new> > <comment_new><author>@youngcw</author><body> > If you could do it again and try to figure out edit caused the issue that would be useful. You should be able to CTRL-Z the changes once you find the one that is breaking things</body></comment_new> > <comment_new><author>@youngcw</author><body> > I wonder if it isn't because of merged transactions, but transfers in general. I wonder if new transfers are getting made</body></comment_new> > <comment_new><author>@MatissJanis</author><body> > Which PR introduced find&replace? We (or I) can ask an AI agent to do a preliminary analysis there to see if it finds any problems. </body></comment_new> > <comment_new><author>@MatissJanis</author><body> > @copilot please investigate what could cause the problem reported in this issue. It was possibly introduced in actualbudget/actual#6282 > > </body></comment_new> > </comments> > </details> <!-- START COPILOT CODING AGENT SUFFIX --> - Fixes actualbudget/actual#6847 <!-- START COPILOT CODING AGENT TIPS --> --- ✨ Let Copilot coding agent [set things up for you](https://github.com/actualbudget/actual/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo. <!--- actual-bot-sections ---> <hr /> <!--- bundlestats-action-comment key:combined start ---> ### Bundle Stats Bundle | Files count | Total bundle size | % Changed ------ | ----------- | ----------------- | --------- desktop-client | 28 | 14.47 MB → 14.47 MB (+3.97 kB) | +0.03% loot-core | 1 | 5.84 MB | 0% api | 1 | 4.38 MB | 0% <details> <summary>View detailed bundle stats</summary> #### desktop-client **Total** Files count | Total bundle size | % Changed ----------- | ----------------- | --------- 28 | 14.47 MB → 14.47 MB (+3.97 kB) | +0.03% <details> <summary>Changeset</summary> File | Δ | Size ---- | - | ---- `src/hooks/useTransactionBatchActions.ts` | 📈 +559 B (+4.24%) | 12.87 kB → 13.42 kB `locale/es.json` | 📈 +2.71 kB (+1.58%) | 171.21 kB → 173.92 kB `locale/de.json` | 📈 +734 B (+0.40%) | 177.78 kB → 178.5 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/es.js | 171.21 kB → 173.92 kB (+2.71 kB) | +1.58% static/js/de.js | 177.78 kB → 178.5 kB (+734 B) | +0.40% static/js/useTransactionBatchActions.js | 13.23 kB → 13.77 kB (+559 B) | +4.13% **Smaller** No assets were smaller **Unchanged** Asset | File Size | % Changed ----- | --------- | --------- static/js/index.js | 9.22 MB | 0% static/js/indexeddb-main-thread-worker-e59fee74.js | 12.94 kB | 0% static/js/workbox-window.prod.es5.js | 5.64 kB | 0% static/js/da.js | 106.62 kB | 0% static/js/en-GB.js | 7.18 kB | 0% static/js/en.js | 162.91 kB | 0% static/js/fr.js | 179.72 kB | 0% static/js/it.js | 171.54 kB | 0% static/js/nb-NO.js | 157.23 kB | 0% static/js/nl.js | 106.65 kB | 0% static/js/pl.js | 88.64 kB | 0% static/js/pt-BR.js | 146.62 kB | 0% static/js/ru.js | 106.97 kB | 0% static/js/sv.js | 78.2 kB | 0% static/js/th.js | 182.35 kB | 0% static/js/uk.js | 215.11 kB | 0% static/js/resize-observer.js | 18.37 kB | 0% static/js/BackgroundImage.js | 120.54 kB | 0% static/js/ReportRouter.js | 1.11 MB | 0% static/js/narrow.js | 641.19 kB | 0% static/js/TransactionList.js | 105.97 kB | 0% static/js/wide.js | 160.07 kB | 0% static/js/AppliedFilters.js | 9.71 kB | 0% static/js/usePayeeRuleCounts.js | 11.79 kB | 0% static/js/FormulaEditor.js | 1.04 MB | 0% </div> </details> --- #### loot-core **Total** Files count | Total bundle size | % Changed ----------- | ----------------- | --------- 1 | 5.84 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 ----- | --------- | --------- kcab.worker.DE5uAdQe.js | 5.84 MB | 0% </div> </details> --- #### api **Total** Files count | Total bundle size | % Changed ----------- | ----------------- | --------- 1 | 4.38 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 ----- | --------- | --------- bundle.api.js | 4.38 MB | 0% </div> </details> </details> <!--- bundlestats-action-comment key:combined end --->
GiteaMirror added the pull-request label 2026-02-28 21:32:32 -06:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/actual#6776