Skip to content

refactor(filter): encapsulate FilterableTable behind an item-oriented API - #692

Open
jvanbuel wants to merge 1 commit into
mainfrom
refactor/encapsulate-filterable-table
Open

refactor(filter): encapsulate FilterableTable behind an item-oriented API#692
jvanbuel wants to merge 1 commit into
mainfrom
refactor/encapsulate-filterable-table

Conversation

@jvanbuel

@jvanbuel jvanbuel commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Summary

FilterableTable<T> exposed pub all: Vec<T> and pub filtered: StatefulTable<T>, so callers reached past the abstraction — and filtered held cloned copies of every matched item, re-cloned on every keystroke and every 2s refresh (on top of sync_panel cloning the whole Vec<T> out of environment_state).

This encapsulates the table so the representation is a private detail, and eliminates the per-keystroke/per-refresh clone of the matched items.

Design

  • Private fields. all (canonical items), view (filtered indices + selection state), filter, visual_anchor are all private. Callers go through an item-oriented API and never see the index representation:
    items(), current()/current_mut(), item_at(), len()/is_empty(), selected_ids(), sort_by()/update_all(), set_items()/clear(), filter()/filter_mut(), state_mut(), clear_visual_mode().
  • No clones. filter_items now returns Vec<usize> (indices into all); the matched items are referenced, not copied. items() resolves indices to &T.
  • Sorting & optimistic updates go through sort_by/update_all, which mutate the canonical all and re-derive the view — so the view is always in all's order, and mark_* writes to the source of truth.
  • Render sites use items() + state_mut(); the row builders now own their display strings (small, per-frame, and inherent to rendering) so the Table no longer borrows the table and the selection state can be updated in the same pass.

Because the index representation is now hidden, the follow-up win (sharing all as Arc<[T]> to also drop the environment_state → panel clone) becomes a private one-line change touching zero call sites.

Verification

  • cargo clippy --workspace --all-targets --all-features -- -D warnings — clean.
  • cargo test --workspace --lib --bins — 223 pass (the existing FilterableTable navigation / visual-mode / selected_ids and DAG-run sort/filter tests are the safety net; adapted to the public API).
  • cargo fmt --all --check — clean.

Context: this supersedes the leaky index-patch approach; the goal was a real abstraction rather than "we made filtered an index list."

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved table filtering, sorting, selection, and navigation across configurations, DAGs, DAG runs, and task instances.
    • Ensured updates remain accurate when filtered views are active.
    • Improved table state clearing when switching or resetting panels.
    • Corrected filter cursor positioning and selection handling.
  • Refactor
    • Improved consistency and reliability of table interactions and rendered data.

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR refactors FilterableTable to maintain canonical items with an indexed filtered view, then updates filtering, selection, mutation, synchronization, rendering, UI, and tests to use the new accessors and state APIs.

Changes

Filterable table migration

Layer / File(s) Summary
Indexed table view and filtering
src/app/model/filter/matching.rs, src/app/model/filterable_table/*
Filtering returns matching indices, while FilterableTable exposes canonical items, filtered accessors, selection state, navigation, and visual-mode helpers.
Model and application-state integration
src/app/model/config/*, src/app/model/dagruns/*, src/app/model/taskinstances/*, src/app/state/*, src/app/worker/config.rs
Panel operations, synchronization, resets, breadcrumbs, and config selection use set_items, update_all, item_at, clear, and filter accessors.
Rendering and filter cursor handling
src/app/model/*/render.rs, src/ui.rs
Table rows and rendering state are sourced through items() and state_mut(), and filter cursor handling uses filter().

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • jvanbuel/flowrs#466: Introduced the visual selection and popup marking behavior updated by this migration.
  • jvanbuel/flowrs#497: Introduced the filtering and matching APIs that now return filtered indices.
  • jvanbuel/flowrs#511: Introduced the FilterableTable APIs used throughout the updated models and UI.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: refactoring FilterableTable behind a private, item-oriented API.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/encapsulate-filterable-table

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@jvanbuel

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/model/filterable_table/mod.rs`:
- Around line 55-58: Update FilterableTable’s apply_filter flow to capture the
currently selected item’s identity before rebuilding view.items, then reselect
that item’s new index afterward. When the selected item no longer exists, clamp
the selection to a valid index (or the empty-view state), preserving selection
across set_items, update_all, and sort_by.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1aa292e2-e0b0-4928-83d7-0e53759382de

📥 Commits

Reviewing files that changed from the base of the PR and between bad883f and 3597e16.

📒 Files selected for processing (14)
  • src/app/model/config/mod.rs
  • src/app/model/dagruns/mod.rs
  • src/app/model/dagruns/render.rs
  • src/app/model/dags/render.rs
  • src/app/model/filter/matching.rs
  • src/app/model/filterable_table/mod.rs
  • src/app/model/filterable_table/render.rs
  • src/app/model/taskinstances/mod.rs
  • src/app/model/taskinstances/render.rs
  • src/app/state/breadcrumb.rs
  • src/app/state/mod.rs
  • src/app/state/sync.rs
  • src/app/worker/config.rs
  • src/ui.rs

Comment on lines 55 to 58
pub fn set_items(&mut self, items: Vec<T>) {
self.all = items;
self.apply_filter();
}

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how often sort_dag_runs / sort_task_instances / set_items run relative to polling/user input
rg -n "sort_dag_runs|sort_task_instances|\.set_items\(" src/app/state/sync.rs src/app/worker -A3 -B3

Repository: jvanbuel/flowrs

Length of output: 2893


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the filterable table implementation and the refresh callers.
sed -n '1,220p' src/app/model/filterable_table/mod.rs
printf '\n--- sync.rs ---\n'
sed -n '1,120p' src/app/state/sync.rs
printf '\n--- tasks.rs ---\n'
sed -n '1,80p' src/app/worker/tasks.rs

# Find any code that reselects/clamps after filtering.
printf '\n--- selection-related search ---\n'
rg -n "selected\(|select\(|state\.select|clamp|reselect|selected_position|current\(\)" src/app/model/filterable_table src/app/state src/app/worker -A2 -B2

Repository: jvanbuel/flowrs

Length of output: 21638


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find where the current/selected item is used for actions.
rg -n "selected_ids\(|current\(\)|current_mut\(|selected_position\(" src/app -A3 -B3

printf '\n--- dagruns selection actions ---\n'
sed -n '1,260p' src/app/model/dagruns/mod.rs

printf '\n--- taskinstances selection actions ---\n'
sed -n '1,280p' src/app/model/taskinstances/mod.rs

Repository: jvanbuel/flowrs

Length of output: 33736


Re-anchor selection when the filtered view changes In src/app/model/filterable_table/mod.rs:55-58, apply_filter() only rebuilds view.items; it leaves view.state.selected() untouched. After set_items, update_all, or sort_by, the highlighted row can silently move to a different item, so current()/selected_ids() will drive m, c, Enter, and o against the wrong row. Track the selected item’s identity and reselect it after filtering, falling back to clamping if it no longer exists.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/model/filterable_table/mod.rs` around lines 55 - 58, Update
FilterableTable’s apply_filter flow to capture the currently selected item’s
identity before rebuilding view.items, then reselect that item’s new index
afterward. When the selected item no longer exists, clamp the selection to a
valid index (or the empty-view state), preserving selection across set_items,
update_all, and sort_by.

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