Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(datagrid): save the whole filter set when switching away from a table tab - #2827

Merged
datlechin merged 3 commits into
mainfrom
fix/datagrid-filter-save-on-tab-switch
Sep 13, 2026
Merged

fix(datagrid): save the whole filter set when switching away from a table tab#2827
datlechin merged 3 commits into
mainfrom
fix/datagrid-filter-save-on-tab-switch

Conversation

@datlechin

Copy link
Copy Markdown
Member

Problem

Switching away from a table tab rewrote that table's saved filters with only the rows the current query used.

  • A row left unchecked was dropped. Reopening the table brought back only the checked rows.
  • After soloing a row, the file held only that row, forced on, and every other row was gone.

docs/features/filtering.mdx says filters survive a relaunch "unchecked rows included".

Root cause

No single place owned the saved shape. FilterCoordinator had two copied writers that saved filters.filter(\.isValid). handleTabChange skipped both and called FilterSettingsStorage.saveLastFilters directly with appliedFilters, which is the query's reading of the rows: .all drops unchecked rows and .solo returns one row forced on. The tab switch has saved appliedFilters since #968 (v0.38.0). #1561 (v0.49.0) made unchecked rows part of the saved set by moving the two coordinator writers to the working set, and missed this third writer, so the loss has shipped since v0.49.0.

Fix

One owner and one writer for the saved shape.

  • TabFilterState.persistedState sits next to appliedFilters in TableFilter.swift. With a commit it returns the valid working set, each row keeping its enabled flag, plus filterLogicMode. With nothing committed it returns no rows.
  • FilterCoordinator.saveLastFilters(of: QueryTab) replaces saveLastFilters(for: String). It guards the tab's table name and writes persistedState. It takes the tab instead of reading selectedTab, because during a switch the selection has already moved to the incoming tab.
  • saveLastFiltersForActiveTable delegates to it, and commitFilters passes tabs[tabIndex].
  • handleTabChange calls filterCoordinator.saveLastFilters(of: tabManager.tabs[oldIndex]).
  • The MainContentCoordinator+FilterState forwarder, both Navigation.swift callers and the discard path move to the new signature in the same commit.

Why nothing committed saves nothing: the Clear button runs clearAppliedFilters() and then clearFiltersAndReload(), which deletes the table's saved filters on purpose, and restore commits whatever was saved as .all. Saving the rows while nothing is committed would write the cleared filters back and apply them on the next open. The investigation behind this PR assumed Clear kept the saved rows; the code says otherwise, and review round 1 caught it. With nothing committed, the tab switch now behaves as it did on main.

Knock-on for the other writers, which already went through the working set: in-place navigation, reusing the active tab and discard also save nothing while nothing is committed. Before, a Clear followed by opening another table in the same tab wrote the cleared rows back the same way. The other side of that: rows typed into a table that had nothing committed are no longer saved when you open another table in the same tab or discard. Before, they were saved and then applied on the next open, which is the unapplied-rows problem listed at the end.

Unchanged: two tabs on the same table still overwrite each other's saved filters, which docs/features/tabs.mdx already describes.

Tests

MainContentCoordinatorTabSwitchTests, each on a users table tab followed by handleTabChange to a query tab:

  • tabSwitchSavesUncheckedFilterRows: rows [A on, B off] with commit .all. Saves both, B still off. Main saved only A.
  • tabSwitchSavesSoloedFilterWorkingSet: rows [A, B off] with commit .solo(B). Saves [A, B], B still off. Main saved only B, forced on.
  • tabSwitchAfterClearKeepsSavedFiltersCleared: Apply, then the Clear button's two steps (clearAppliedFilters and the saved-filter delete its reload runs), then switch. Nothing is saved and the panel keeps both rows. This one passes on main too; it guards the regression review round 1 found in the first version of this change.

The first two set the commit on the tab directly rather than through Apply, so the switch is the only write to storage. That also means a writer reading selectedTab (the query tab by then) would save nothing and fail them. Each test clears the saved filters for its own connection id.

TabFilterStateTests:

  • persistedStateKeepsWorkingSetForEveryCommit: keeps unchecked rows and drops invalid ones for .all and for .solo on either row.
  • persistedStateIsEmptyWithNothingCommitted.
  • persistedStateCarriesLogicMode.

No UI automation: the change is which rows a storage write receives, and the coordinator tests drive the same handleTabChange path the tab strip does.

Verification

All through .claude/skills/fix-issue/scripts/verify.sh on the committed tree.

  • generate: PASS
  • build TablePro: PASS
  • lint on the 8 changed Swift files: PASS, 0 violations
  • test MainContentCoordinatorTabSwitchTests TabFilterStateTests FilterMoveTests FilterSettingsStorageTests TableScopedSettingsRegistryTests FilterRestoreTests: PASS, 99 executed, 99 passed, 0 failed. All six new cases ran by name.

The spec also named a TabSwitchTests suite; no suite by that name exists, and MainContentCoordinatorTabSwitchTests is the only one matching.

Reviewed with the code-review skill (Codex was unavailable), two rounds. Round 1 found two issues: the Clear regression, fixed in the second commit, and the unapplied-rows problem listed below. Round 2 found no bugs. It checked every caller of saveLastFilters(of:) and every state that leaves commit nil (Clear, Remove All Filters, removing a soloed row, the always-hide panel setting, restored background tabs, find-bar search). In each of them the old tab-switch save of appliedFilters was already empty.

Found while investigating #2820

This came out of the collateral sweep during the #2820 investigation (holding the session driver while a database switch reconnects). It is unrelated to that fix and ships on its own.

Found in review, not fixed here

  • Rows typed but never applied are saved and then applied on the next open. Add an enabled row to a table whose filters are already committed, do not press Apply, and switch tabs or open another table in the same tab. The row is saved with the rest of the working set, and resolvedRestoredState restores every saved row under .all, so it filters the grid on the next open. main already did this through in-place navigation, reusing the active tab and discard, which save the working set by the documented contract. Fixing it means saving which rows were applied and restoring that instead of .all. That changes the stored format and the restore rules, so it belongs in its own change.

https://claude.ai/code/session_01SNC28GzXZvLwbgAB8G583A

@datlechin
datlechin merged commit c6265c1 into main Sep 13, 2026
4 checks passed
@datlechin
datlechin deleted the fix/datagrid-filter-save-on-tab-switch branch September 13, 2026 23:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant