Skip to content

fix: filtered/sorted database views don't update on synced row changes (#8806)#8871

Open
hkarmoush wants to merge 1 commit into
AppFlowy-IO:mainfrom
hkarmoush:fix/filter-sync-row-change-8806
Open

fix: filtered/sorted database views don't update on synced row changes (#8806)#8871
hkarmoush wants to merge 1 commit into
AppFlowy-IO:mainfrom
hkarmoush:fix/filter-sync-row-change-8806

Conversation

@hkarmoush

@hkarmoush hkarmoush commented Jul 18, 2026

Copy link
Copy Markdown

What's going on

On Android (or any client), if you have a filter or sort on a database view and someone else edits a row's cell on another device, the change syncs in but the filter/sort never re-runs on it. You'll see the cell's new value in place, but a row that should now be hidden by the filter stays visible (or vice versa).

Traced it down to observe_rows_change in flowy-database2 — this is the handler for cell changes that arrive via sync (as opposed to a local edit). It only re-notifies the cell and the row itself; it never tells the filter/sort/calculation controllers that a row changed. Local edits don't have this problem because update_cell explicitly calls did_update_row afterwards, which does trigger that re-evaluation — the sync path just never had the equivalent call.

The fix

Nothing new architecturally, just wiring up what's already there:

  • Added DatabaseViewEditor::v_did_update_row_from_sync, a thin wrapper over the existing (private) gen_did_update_row_view_tasks, which re-runs filters, sorts, and calculations for a row.
  • observe_rows_change now has access to the full DatabaseEditor (previously it only had a weak handle to the raw collab Database, so it couldn't reach the view/filter controllers at all — that's why this was never wired up). On a synced cell change, it now calls the new method for every view.
  • Moved where observe_rows_change gets set up in DatabaseEditor::new so it has that access — it now starts after the editor is fully constructed instead of before, alongside the other view-level observers.

One thing worth calling out: local edits already go through the collab write, which independently triggers this same observer. So after this change, a local edit's filter check runs twice (once via did_update_row, once via the observer). This turned out to be harmless — checked the Dart side, and RowList.insert/remove are idempotent, so a duplicate visibility notification is a no-op on the client. Existing filter tests confirm nothing regresses there.

Testing

Added a regression test that simulates a synced cell change by writing directly to the collab row (via DatabaseEditor::update_row, bypassing update_cell/did_update_row — the same way an incoming sync applies a change) and asserts that the filter's FilterNotification actually fires.

Deliberately did not rely on assert_number_of_visible_rows alone for this — that helper re-opens the view and re-filters from scratch, so it would pass even with the bug still present. The test checks the incremental notification directly instead.

Note: I didn't have a local Rust toolchain to run cargo test on this machine, so I'm relying on CI here — flagging that in case the checks turn something up that a local run would've caught first.

Test plan

  • CI passes (cargo test -p flowy-database2)
  • New regression test grid_filter_single_select_is_synced_row_change_test passes
  • Existing filter/sort tests still pass (confirms local-edit double-trigger is harmless)
  • Manual: two devices on the same account, filter/sort a database view, edit the filtered/sorted field on the other device, confirm the view updates once the change syncs in

Summary by Sourcery

Ensure filtered and sorted database views are re-evaluated when rows change via synced (remote) updates, and add coverage to prevent regressions.

Bug Fixes:

  • Trigger view-level row update handling for remotely synced cell changes so filters, sorts, and calculations stay consistent across devices.

Enhancements:

  • Wire row-change observation through the full DatabaseEditor so synced updates can notify all relevant views.
  • Expose a dedicated view editor method for handling row updates originating from sync events.

Tests:

  • Add regression test utilities to simulate remote single-select cell changes via direct collab row updates.
  • Add a regression test verifying that a filtered single-select view updates correctly when a row changes through a synced update.

Filtered/sorted database views weren't updating when a row's cell
changed via sync from another device. Turns out the sync path
(observe_rows_change) only repainted the cell and refreshed the row's
fields — it never told the filter/sort/calculation controllers a row
had changed, so a Select filter on Android would keep showing a row
that should've been hidden after a Desktop edit synced in.

The local edit path already handles this correctly via
did_update_row -> gen_did_update_row_view_tasks, it's just that
nothing calls the equivalent on the sync path. This wires up the
observer to do the same re-evaluation, reusing the existing
filter/sort/calculation controllers instead of adding anything new.

Added a regression test that simulates a synced cell change (writing
straight to the collab row, the same way a remote sync would) and
asserts the incremental FilterNotification actually fires.
@sourcery-ai

sourcery-ai Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Wire row-change observation for synced updates through DatabaseEditor so that database views re-run filters/sorts/calculations on remotely edited cells, and add a regression test that simulates a synced single-select update to verify incremental filter notifications fire correctly.

Sequence diagram for synced row change triggering view re-evaluation

sequenceDiagram
    participant SyncCollab
    participant DatabaseEditor
    participant DatabaseViewEditor

    SyncCollab->>DatabaseEditor: subscribe_row_change
    loop for_each_row_change
        SyncCollab-->>DatabaseEditor: RowChange::DidUpdateCell
        DatabaseEditor->>DatabaseEditor: notify_cell
        DatabaseEditor->>DatabaseEditor: notify_row for_each_view
        loop for_each_view_editor
            DatabaseEditor->>DatabaseViewEditor: v_did_update_row_from_sync(row_id, field_id)
            DatabaseViewEditor->>DatabaseViewEditor: gen_did_update_row_view_tasks(row_id, Some(field_id))
            opt [filter/sort/calculation controllers re-evaluate row]
            end
        end
    end
Loading

File-Level Changes

Change Details Files
Ensure row-change observer can trigger view-level recomputation (filters/sorts/calculations) for synced cell updates.
  • Change row-change observer signature to take a DatabaseEditor reference and database UUID instead of a raw Database handle and string ID.
  • Use the DatabaseEditor to obtain view metadata for notifications and to iterate view editors when a RowChange::DidUpdateCell event occurs.
  • For each affected view editor, invoke a new method to re-evaluate the changed row after a synced update, in addition to existing cell/row notifications.
frontend/rust-lib/flowy-database2/src/services/database/database_observe.rs
Expose a sync-specific hook on DatabaseViewEditor to reuse existing row-update recomputation logic.
  • Add a public v_did_update_row_from_sync method that wraps the existing gen_did_update_row_view_tasks with an explicit field_id.
  • Keep gen_did_update_row_view_tasks private and unchanged as the internal implementation for row-level recomputation.
frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs
Initialize the row-change observer after DatabaseEditor construction so it has access to view editors.
  • Remove the old observe_rows_change call that used the raw Database during DatabaseEditor::new setup.
  • Add a new observe_rows_change call that passes &DatabaseEditor and notification_sender once the editor, block observers, and view observers are established.
frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs
Add a regression test harness helper to simulate a remote synced single-select cell change via collab row mutation.
  • Introduce update_single_select_cell_via_sync on DatabaseFilterTest that subscribes to view changes, builds a new single-select cell via apply_cell_changeset/SelectOptionCellChangeset, and updates the row through DatabaseEditor::update_row.
  • Wire in the necessary imports for apply_cell_changeset and SelectOptionCellChangeset to construct the new cell value.
frontend/rust-lib/flowy-database2/tests/database/filter_test/script.rs
Add a regression test verifying filtered views react to synced single-select cell changes via incremental notifications.
  • Create grid_filter_single_select_is_synced_row_change_test that sets up a SingleSelect "Is" filter, asserts initial visible-row count, then calls update_single_select_cell_via_sync and asserts both a FilterRowChanged notification and the updated visible-row count.
  • Ensure the test asserts against the incremental filter notification path (assert_future_changed) instead of only re-filtering via assert_number_of_visible_rows.
frontend/rust-lib/flowy-database2/tests/database/filter_test/select_option_filter_test.rs

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@hkarmoush
hkarmoush marked this pull request as ready for review July 20, 2026 15:21

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • In observe_rows_change, you now re-run filters/sorts/calculations for every view on every DidUpdateCell; if there are many views this could be expensive, so consider narrowing this to only views that reference the changed field (or are otherwise affected) if that information is available.
  • The new update_single_select_cell_via_sync test helper mirrors existing patterns, but it might be safer to assert that the applied apply_cell_changeset result actually differs from old_cell before calling update_row, to avoid exercising a no-op path if the test setup changes.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `observe_rows_change`, you now re-run filters/sorts/calculations for every view on every `DidUpdateCell`; if there are many views this could be expensive, so consider narrowing this to only views that reference the changed field (or are otherwise affected) if that information is available.
- The new `update_single_select_cell_via_sync` test helper mirrors existing patterns, but it might be safer to assert that the applied `apply_cell_changeset` result actually differs from `old_cell` before calling `update_row`, to avoid exercising a no-op path if the test setup changes.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

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