Skip to content

Commit 3df24f3

Browse files
committed
fix: Preserve cursor position after bulk edits in aggregate views
Add cursor position preservation to all bulk edit operations in aggregate views, matching the UX of detail view edits. Changes: - Save cursor position before refresh in all bulk edit methods - Restore cursor after refresh (bounded by new row count) - Applies to: * Single group merchant edit * Multi-select group merchant edit * Single group category edit * Multi-select group category edit Behavior: - Cursor stays at same row number after edit - If row count changes (groups consolidate), cursor bounded to valid range - Minimizes user navigation disruption - Consistent with detail view behavior Note: Scroll position is automatically maintained by move_cursor() which scrolls to keep cursor visible. Fine-grained scroll_y offset preservation can be added later if needed. All 744 tests passing.
1 parent c5c58ff commit 3df24f3

File tree

1 file changed

+36
-0
lines changed

1 file changed

+36
-0
lines changed

moneyflow/app.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,10 @@ async def _bulk_edit_merchant_from_aggregate(self) -> None:
10321032
)
10331033

10341034
if new_merchant:
1035+
# Save cursor position before refresh
1036+
table = self.query_one("#data-table", DataTable)
1037+
saved_cursor_row = table.cursor_row
1038+
10351039
# Get all transactions for this merchant
10361040
filtered_df = self.state.get_filtered_df()
10371041
merchant_txns = self.data_manager.filter_by_merchant(filtered_df, merchant_name)
@@ -1043,6 +1047,11 @@ async def _bulk_edit_merchant_from_aggregate(self) -> None:
10431047

10441048
self._notify(NotificationHelper.edit_queued(count))
10451049
self.refresh_view()
1050+
1051+
# Try to restore cursor position
1052+
table = self.query_one("#data-table", DataTable)
1053+
if saved_cursor_row < table.row_count:
1054+
table.move_cursor(row=saved_cursor_row)
10461055
else:
10471056
self.notify("Edit merchant only works from Merchant view", timeout=2)
10481057

@@ -1085,13 +1094,22 @@ async def _bulk_edit_merchant_from_selected_groups(self) -> None:
10851094
)
10861095

10871096
if new_merchant:
1097+
# Save cursor position before refresh
1098+
table = self.query_one("#data-table", DataTable)
1099+
saved_cursor_row = table.cursor_row
1100+
10881101
# Queue edits for all transactions
10891102
count = self.controller.queue_merchant_edits(all_txns, "multiple", new_merchant)
10901103

10911104
self.state.clear_selection()
10921105
self._notify(NotificationHelper.edit_queued(count))
10931106
self.refresh_view()
10941107

1108+
# Try to restore cursor position
1109+
table = self.query_one("#data-table", DataTable)
1110+
if saved_cursor_row < table.row_count:
1111+
table.move_cursor(row=saved_cursor_row)
1112+
10951113
async def _edit_merchant_detail(self) -> None:
10961114
"""Edit merchant in detail view."""
10971115
if self.state.current_data is None:
@@ -1235,6 +1253,10 @@ async def _bulk_edit_category_from_aggregate(self) -> None:
12351253
if not new_category_id or (current_category_id and new_category_id == current_category_id):
12361254
return
12371255

1256+
# Save cursor position before refresh
1257+
table = self.query_one("#data-table", DataTable)
1258+
saved_cursor_row = table.cursor_row
1259+
12381260
# Get all transactions for this merchant/category/group
12391261
filtered_df = self.state.get_filtered_df()
12401262
matching_txns = filter_func(filtered_df, field_name)
@@ -1250,6 +1272,11 @@ async def _bulk_edit_category_from_aggregate(self) -> None:
12501272
)
12511273
self.refresh_view()
12521274

1275+
# Try to restore cursor position
1276+
table = self.query_one("#data-table", DataTable)
1277+
if saved_cursor_row < table.row_count:
1278+
table.move_cursor(row=saved_cursor_row)
1279+
12531280
async def _bulk_edit_category_from_selected_groups(self) -> None:
12541281
"""Edit category for all transactions in all selected groups."""
12551282
# Determine which field we're grouping by
@@ -1288,6 +1315,10 @@ async def _bulk_edit_category_from_selected_groups(self) -> None:
12881315
if not new_category_id:
12891316
return
12901317

1318+
# Save cursor position before refresh
1319+
table = self.query_one("#data-table", DataTable)
1320+
saved_cursor_row = table.cursor_row
1321+
12911322
# Queue edits for all transactions
12921323
count = self.controller.queue_category_edits(all_txns, new_category_id)
12931324

@@ -1299,6 +1330,11 @@ async def _bulk_edit_category_from_selected_groups(self) -> None:
12991330
)
13001331
self.refresh_view()
13011332

1333+
# Try to restore cursor position
1334+
table = self.query_one("#data-table", DataTable)
1335+
if saved_cursor_row < table.row_count:
1336+
table.move_cursor(row=saved_cursor_row)
1337+
13021338
async def _edit_category(self) -> None:
13031339
"""Show category selection and apply (for detail view)."""
13041340
if self.state.current_data is None:

0 commit comments

Comments
 (0)