[PR #6653] Add drag&drop reordering for transactions within the same day #6651

Open
opened 2026-02-28 21:31:05 -06:00 by GiteaMirror · 0 comments
Owner

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

State: open
Merged: No


Summary

Adds drag-and-drop reordering for transactions within the same date, allowing users to manually control transaction order. This provides a more intuitive way to arrange transactions when multiple occur on the same day.

Motivation

When multiple transactions happen on the same date, users may want to control the display order to reflect the actual sequence of events or for personal organization. Previously, the only way to affect ordering was through the sort_order field which was not directly user-controllable.

Fixes https://github.com/actualbudget/actual/issues/1165
Fixes https://github.com/actualbudget/actual/issues/6588

Demo

ezgif-80ce81128565e25a

Trying to drag a transaction outside of its day is prohibited, and visually noted (no invalid drop zones).

Changes

Core Logic (loot-core)

  • New transaction-move API: Added a new undoable server method to reorder transactions within a date group using a midpoint/shove algorithm (similar to category reordering)
  • New moveTransaction database function: Implementation in packages/loot-core/src/server/db/index.ts that handles the actual reordering logic, including child/split transaction handling
  • TRANSACTION_SORT_INCREMENT constant: Introduced a smaller increment (1024 vs 16384) for transaction sort orders, allowing more reordering operations before needing to resequence
  • New shoveSortOrdersDescending algorithm: Added to sort.ts for handling sort order calculations in descending-sorted lists (transactions are displayed newest first by default)
  • Child transaction handling: Child/split transactions inherit their parent's sort_order and move together with the parent; child transactions can also be reordered within their siblings
  • Import sort order spacing: Updated sync.ts to use TRANSACTION_SORT_INCREMENT for better spacing during imports
  • Stable query ordering: Added sort_order as a final tiebreaker in transaction queries

Desktop Client - New React-Aria Drag-Drop Hook

Added a new useAriaDragDrop.ts hook (useDrag/useDrop) using react-aria, specifically for transaction reordering. This coexists with the existing react-dnd system used elsewhere in the app.

Key features:

  • Custom drag preview showing transaction date, payee, category/split info, and amount
  • Visual drop indicator (highlight line) showing where the transaction will land
  • Reduced opacity on the dragged row to indicate it's being moved
  • Cursor position tracking for accurate drop position detection

Desktop Client - Transaction Table Updates

Changes in TransactionsTable.tsx:

  • Each transaction row now supports drag-and-drop with the new hooks
  • Added props: canDrag, draggedDate, draggedId, draggedParentId, prevRowDate, nextRowDate, sortField, ascDesc, onDragChange, onDrop
  • Boundary drop validation using shared helper to ensure drops only occur within valid date groups

Changes in TransactionList.tsx:

  • Added onReorder callback that validates drops and calls the new transaction-move API
  • Added isFiltered and allowReorder props to control when reordering is enabled

New file dragDropValidation.ts:

  • Shared helper isValidBoundaryDrop used by both TransactionList and TransactionsTable

Desktop Client - Drop Highlight Updates

Updated sort.tsx to support both legacy (top/bottom) and react-aria (before/after) position terminology for the DropHighlight component.

Reorder Constraints

  • Single account only: Disabled on "All Accounts", "On Budget", "Off Budget", "Uncategorized" views, and Calendar report
  • Date sort only: Disabled when any sort column other than date is active
  • No filters: Disabled when filters are applied
  • Same date group: Only allows reordering within the same date group (or at date boundaries where the neighbor has the same date)
  • Parent transactions only: Preview/scheduled transactions cannot be reordered
  • Child transactions: Can only be reordered within their siblings (same parent)
  • Split parent protection: Dropping immediately after a split parent is prevented to avoid inserting between parent and children

Testing

  • Updated TransactionsTable.test.tsx to use userEvent.keyboard instead of userEvent.type for Enter key
  • Updated test snapshots in parse-file.test.ts.snap for new TRANSACTION_SORT_INCREMENT spacing
  • Manual testing of drag-drop behavior across various scenarios

Checklist

  • yarn typecheck passes
  • yarn lint:fix has been run
  • Relevant tests pass
  • Code follows repository conventions (AGENTS.md)
  • User-facing strings are translated

AI generated

This code was completely vibe-coded by Claude, based on an analysis of the original PR.


Bundle Stats

Bundle Files count Total bundle size % Changed
desktop-client 27 14.85 MB → 14.87 MB (+19.28 kB) +0.13%
loot-core 1 5.82 MB → 5.83 MB (+5.63 kB) +0.09%
api 1 4.43 MB → 4.44 MB (+5.1 kB) +0.11%
View detailed bundle stats

desktop-client

Total

Files count Total bundle size % Changed
27 14.85 MB → 14.87 MB (+19.28 kB) +0.13%
Changeset
File Δ Size
src/hooks/useDragDrop.tsx 🆕 +8.26 kB 0 B → 8.26 kB
src/components/transactions/TransactionList.tsx 📈 +3.34 kB (+20.45%) 16.34 kB → 19.69 kB
src/components/transactions/TransactionsTable.tsx 📈 +7.31 kB (+8.48%) 86.2 kB → 93.5 kB
src/components/sort.tsx 📈 +55 B (+1.07%) 5.03 kB → 5.08 kB
home/runner/work/actual/actual/packages/loot-core/src/shared/transactions.ts 📈 +84 B (+0.91%) 9.01 kB → 9.09 kB
src/components/accounts/Account.tsx 📈 +218 B (+0.42%) 50.67 kB → 50.88 kB
node_modules/react-dnd/dist/hooks/useDrop/useDrop.js 📈 +2 B (+0.36%) 557 B → 559 B
node_modules/@use-gesture/react/dist/use-gesture-react.esm.js 📈 +2 B (+0.34%) 580 B → 582 B
src/components/reports/reports/Calendar.tsx 📈 +21 B (+0.07%) 29.45 kB → 29.47 kB
src/components/mobile/MobileNavTabs.tsx 📈 +2 B (+0.02%) 11.53 kB → 11.53 kB
View detailed bundle breakdown

Added
No assets were added

Removed
No assets were removed

Bigger

Asset File Size % Changed
static/js/index.js 9.54 MB → 9.77 MB (+234.05 kB) +2.40%
static/js/wide.js 164.15 kB → 164.36 kB (+218 B) +0.13%
static/js/ReportRouter.js 1.16 MB → 1.16 MB (+21 B) +0.00%

Smaller

Asset File Size % Changed
static/js/narrow.js 637.68 kB → 422.69 kB (-215 kB) -33.72%

Unchanged

Asset File Size % Changed
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/ca.js 188.15 kB 0%
static/js/da.js 106.35 kB 0%
static/js/de.js 180.07 kB 0%
static/js/en-GB.js 7.18 kB 0%
static/js/en.js 170.37 kB 0%
static/js/es.js 174.55 kB 0%
static/js/fr.js 179.6 kB 0%
static/js/it.js 171.16 kB 0%
static/js/nb-NO.js 156.96 kB 0%
static/js/nl.js 106.37 kB 0%
static/js/pl.js 88.37 kB 0%
static/js/pt-BR.js 154.22 kB 0%
static/js/th.js 181.87 kB 0%
static/js/uk.js 214.74 kB 0%
static/js/resize-observer.js 18.37 kB 0%
static/js/BackgroundImage.js 120.54 kB 0%
static/js/TransactionList.js 106.22 kB 0%
static/js/AppliedFilters.js 9.71 kB 0%
static/js/usePayeeRuleCounts.js 10.04 kB 0%
static/js/useTransactionBatchActions.js 13.23 kB 0%
static/js/FormulaEditor.js 1.04 MB 0%

loot-core

Total

Files count Total bundle size % Changed
1 5.82 MB → 5.83 MB (+5.63 kB) +0.09%
Changeset
File Δ Size
home/runner/work/actual/actual/packages/loot-core/src/server/db/sort.ts 📈 +2.15 kB (+160.67%) 1.34 kB → 3.5 kB
home/runner/work/actual/actual/packages/loot-core/src/server/transactions/app.ts 📈 +575 B (+23.33%) 2.41 kB → 2.97 kB
home/runner/work/actual/actual/packages/loot-core/src/server/db/index.ts 📈 +2.66 kB (+14.79%) 17.97 kB → 20.63 kB
home/runner/work/actual/actual/packages/loot-core/src/shared/transactions.ts 📈 +88 B (+1.21%) 7.12 kB → 7.21 kB
home/runner/work/actual/actual/packages/loot-core/src/server/accounts/sync.ts 📈 +172 B (+0.65%) 25.88 kB → 26.05 kB
View detailed bundle breakdown

Added

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

Removed

Asset File Size % Changed
kcab.worker.BwrdDDMW.js 5.82 MB → 0 B (-5.82 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
1 4.43 MB → 4.44 MB (+5.1 kB) +0.11%
Changeset
File Δ Size
src/server/db/sort.ts 📈 +1.94 kB (+164.60%) 1.18 kB → 3.12 kB
src/server/transactions/app.ts 📈 +503 B (+23.15%) 2.12 kB → 2.61 kB
src/server/db/index.ts 📈 +2.44 kB (+15.19%) 16.04 kB → 18.47 kB
src/shared/transactions.ts 📈 +84 B (+1.31%) 6.28 kB → 6.36 kB
src/server/accounts/sync.ts 📈 +156 B (+0.66%) 23.1 kB → 23.25 kB
View detailed bundle breakdown

Added
No assets were added

Removed
No assets were removed

Bigger

Asset File Size % Changed
bundle.api.js 4.43 MB → 4.44 MB (+5.1 kB) +0.11%

Smaller
No assets were smaller

Unchanged
No assets were unchanged

**Original Pull Request:** https://github.com/actualbudget/actual/pull/6653 **State:** open **Merged:** No --- ## Summary Adds drag-and-drop reordering for transactions within the same date, allowing users to manually control transaction order. This provides a more intuitive way to arrange transactions when multiple occur on the same day. ## Motivation When multiple transactions happen on the same date, users may want to control the display order to reflect the actual sequence of events or for personal organization. Previously, the only way to affect ordering was through the `sort_order` field which was not directly user-controllable. Fixes https://github.com/actualbudget/actual/issues/1165 Fixes https://github.com/actualbudget/actual/issues/6588 ## Demo ![ezgif-80ce81128565e25a](https://github.com/user-attachments/assets/bdfdcef1-5b32-4d1c-be9f-b43da62eeb08) Trying to drag a transaction outside of its day is prohibited, and visually noted (no invalid drop zones). ## Changes ### Core Logic (`loot-core`) - **New `transaction-move` API**: Added a new undoable server method to reorder transactions within a date group using a midpoint/shove algorithm (similar to category reordering) - **New `moveTransaction` database function**: Implementation in `packages/loot-core/src/server/db/index.ts` that handles the actual reordering logic, including child/split transaction handling - **`TRANSACTION_SORT_INCREMENT` constant**: Introduced a smaller increment (1024 vs 16384) for transaction sort orders, allowing more reordering operations before needing to resequence - **New `shoveSortOrdersDescending` algorithm**: Added to `sort.ts` for handling sort order calculations in descending-sorted lists (transactions are displayed newest first by default) - **Child transaction handling**: Child/split transactions inherit their parent's `sort_order` and move together with the parent; child transactions can also be reordered within their siblings - **Import sort order spacing**: Updated sync.ts to use `TRANSACTION_SORT_INCREMENT` for better spacing during imports - **Stable query ordering**: Added `sort_order` as a final tiebreaker in transaction queries ### Desktop Client - New React-Aria Drag-Drop Hook Added a new `useAriaDragDrop.ts` hook (`useDrag`/`useDrop`) using react-aria, specifically for transaction reordering. This coexists with the existing react-dnd system used elsewhere in the app. Key features: - Custom drag preview showing transaction date, payee, category/split info, and amount - Visual drop indicator (highlight line) showing where the transaction will land - Reduced opacity on the dragged row to indicate it's being moved - Cursor position tracking for accurate drop position detection ### Desktop Client - Transaction Table Updates Changes in `TransactionsTable.tsx`: - Each transaction row now supports drag-and-drop with the new hooks - Added props: `canDrag`, `draggedDate`, `draggedId`, `draggedParentId`, `prevRowDate`, `nextRowDate`, `sortField`, `ascDesc`, `onDragChange`, `onDrop` - Boundary drop validation using shared helper to ensure drops only occur within valid date groups Changes in `TransactionList.tsx`: - Added `onReorder` callback that validates drops and calls the new `transaction-move` API - Added `isFiltered` and `allowReorder` props to control when reordering is enabled New file `dragDropValidation.ts`: - Shared helper `isValidBoundaryDrop` used by both TransactionList and TransactionsTable ### Desktop Client - Drop Highlight Updates Updated `sort.tsx` to support both legacy (`top`/`bottom`) and react-aria (`before`/`after`) position terminology for the `DropHighlight` component. ### Reorder Constraints - **Single account only**: Disabled on "All Accounts", "On Budget", "Off Budget", "Uncategorized" views, and Calendar report - **Date sort only**: Disabled when any sort column other than date is active - **No filters**: Disabled when filters are applied - **Same date group**: Only allows reordering within the same date group (or at date boundaries where the neighbor has the same date) - **Parent transactions only**: Preview/scheduled transactions cannot be reordered - **Child transactions**: Can only be reordered within their siblings (same parent) - **Split parent protection**: Dropping immediately after a split parent is prevented to avoid inserting between parent and children ## Testing - Updated `TransactionsTable.test.tsx` to use `userEvent.keyboard` instead of `userEvent.type` for Enter key - Updated test snapshots in `parse-file.test.ts.snap` for new `TRANSACTION_SORT_INCREMENT` spacing - Manual testing of drag-drop behavior across various scenarios ## Checklist - [x] `yarn typecheck` passes - [x] `yarn lint:fix` has been run - [x] Relevant tests pass - [x] Code follows repository conventions (AGENTS.md) - [x] User-facing strings are translated ## Related Issues - Resolves https://github.com/actualbudget/actual/issues/1165 - Obsoletes https://github.com/actualbudget/actual/pull/6632 - Based on #1603 - original implementation of transaction drag-drop reordering ## AI generated This code was completely vibe-coded by Claude, based on an analysis of the original PR. <!--- actual-bot-sections ---> <hr /> <!--- bundlestats-action-comment key:combined start ---> ### Bundle Stats Bundle | Files count | Total bundle size | % Changed ------ | ----------- | ----------------- | --------- desktop-client | 27 | 14.85 MB → 14.87 MB (+19.28 kB) | +0.13% loot-core | 1 | 5.82 MB → 5.83 MB (+5.63 kB) | +0.09% api | 1 | 4.43 MB → 4.44 MB (+5.1 kB) | +0.11% <details> <summary>View detailed bundle stats</summary> #### desktop-client **Total** Files count | Total bundle size | % Changed ----------- | ----------------- | --------- 27 | 14.85 MB → 14.87 MB (+19.28 kB) | +0.13% <details> <summary>Changeset</summary> File | Δ | Size ---- | - | ---- `src/hooks/useDragDrop.tsx` | 🆕 +8.26 kB | 0 B → 8.26 kB `src/components/transactions/TransactionList.tsx` | 📈 +3.34 kB (+20.45%) | 16.34 kB → 19.69 kB `src/components/transactions/TransactionsTable.tsx` | 📈 +7.31 kB (+8.48%) | 86.2 kB → 93.5 kB `src/components/sort.tsx` | 📈 +55 B (+1.07%) | 5.03 kB → 5.08 kB `home/runner/work/actual/actual/packages/loot-core/src/shared/transactions.ts` | 📈 +84 B (+0.91%) | 9.01 kB → 9.09 kB `src/components/accounts/Account.tsx` | 📈 +218 B (+0.42%) | 50.67 kB → 50.88 kB `node_modules/react-dnd/dist/hooks/useDrop/useDrop.js` | 📈 +2 B (+0.36%) | 557 B → 559 B `node_modules/@use-gesture/react/dist/use-gesture-react.esm.js` | 📈 +2 B (+0.34%) | 580 B → 582 B `src/components/reports/reports/Calendar.tsx` | 📈 +21 B (+0.07%) | 29.45 kB → 29.47 kB `src/components/mobile/MobileNavTabs.tsx` | 📈 +2 B (+0.02%) | 11.53 kB → 11.53 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/index.js | 9.54 MB → 9.77 MB (+234.05 kB) | +2.40% static/js/wide.js | 164.15 kB → 164.36 kB (+218 B) | +0.13% static/js/ReportRouter.js | 1.16 MB → 1.16 MB (+21 B) | +0.00% **Smaller** Asset | File Size | % Changed ----- | --------- | --------- static/js/narrow.js | 637.68 kB → 422.69 kB (-215 kB) | -33.72% **Unchanged** Asset | File Size | % Changed ----- | --------- | --------- 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/ca.js | 188.15 kB | 0% static/js/da.js | 106.35 kB | 0% static/js/de.js | 180.07 kB | 0% static/js/en-GB.js | 7.18 kB | 0% static/js/en.js | 170.37 kB | 0% static/js/es.js | 174.55 kB | 0% static/js/fr.js | 179.6 kB | 0% static/js/it.js | 171.16 kB | 0% static/js/nb-NO.js | 156.96 kB | 0% static/js/nl.js | 106.37 kB | 0% static/js/pl.js | 88.37 kB | 0% static/js/pt-BR.js | 154.22 kB | 0% static/js/th.js | 181.87 kB | 0% static/js/uk.js | 214.74 kB | 0% static/js/resize-observer.js | 18.37 kB | 0% static/js/BackgroundImage.js | 120.54 kB | 0% static/js/TransactionList.js | 106.22 kB | 0% static/js/AppliedFilters.js | 9.71 kB | 0% static/js/usePayeeRuleCounts.js | 10.04 kB | 0% static/js/useTransactionBatchActions.js | 13.23 kB | 0% static/js/FormulaEditor.js | 1.04 MB | 0% </div> </details> --- #### loot-core **Total** Files count | Total bundle size | % Changed ----------- | ----------------- | --------- 1 | 5.82 MB → 5.83 MB (+5.63 kB) | +0.09% <details> <summary>Changeset</summary> File | Δ | Size ---- | - | ---- `home/runner/work/actual/actual/packages/loot-core/src/server/db/sort.ts` | 📈 +2.15 kB (+160.67%) | 1.34 kB → 3.5 kB `home/runner/work/actual/actual/packages/loot-core/src/server/transactions/app.ts` | 📈 +575 B (+23.33%) | 2.41 kB → 2.97 kB `home/runner/work/actual/actual/packages/loot-core/src/server/db/index.ts` | 📈 +2.66 kB (+14.79%) | 17.97 kB → 20.63 kB `home/runner/work/actual/actual/packages/loot-core/src/shared/transactions.ts` | 📈 +88 B (+1.21%) | 7.12 kB → 7.21 kB `home/runner/work/actual/actual/packages/loot-core/src/server/accounts/sync.ts` | 📈 +172 B (+0.65%) | 25.88 kB → 26.05 kB </details> <details> <summary>View detailed bundle breakdown</summary> <div> **Added** Asset | File Size | % Changed ----- | --------- | --------- kcab.worker.BfTWq4Aq.js | 0 B → 5.83 MB (+5.83 MB) | - **Removed** Asset | File Size | % Changed ----- | --------- | --------- kcab.worker.BwrdDDMW.js | 5.82 MB → 0 B (-5.82 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 ----------- | ----------------- | --------- 1 | 4.43 MB → 4.44 MB (+5.1 kB) | +0.11% <details> <summary>Changeset</summary> File | Δ | Size ---- | - | ---- `src/server/db/sort.ts` | 📈 +1.94 kB (+164.60%) | 1.18 kB → 3.12 kB `src/server/transactions/app.ts` | 📈 +503 B (+23.15%) | 2.12 kB → 2.61 kB `src/server/db/index.ts` | 📈 +2.44 kB (+15.19%) | 16.04 kB → 18.47 kB `src/shared/transactions.ts` | 📈 +84 B (+1.31%) | 6.28 kB → 6.36 kB `src/server/accounts/sync.ts` | 📈 +156 B (+0.66%) | 23.1 kB → 23.25 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 ----- | --------- | --------- bundle.api.js | 4.43 MB → 4.44 MB (+5.1 kB) | +0.11% **Smaller** No assets were smaller **Unchanged** No assets were unchanged </div> </details> </details> <!--- bundlestats-action-comment key:combined end --->
GiteaMirror added the pull-request label 2026-02-28 21:31:05 -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#6651