Skip to content

Implement plugin filters#490

Merged
DropSnorz merged 5 commits into
v2from
feat/plugin-filter
Jul 1, 2026
Merged

Implement plugin filters#490
DropSnorz merged 5 commits into
v2from
feat/plugin-filter

Conversation

@DropSnorz

Copy link
Copy Markdown
Owner

Resolve #254

@DropSnorz DropSnorz self-assigned this Jun 29, 2026
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR adds a filterable sidebar to the Plugins tab with checkboxes for format, type, manufacturer, and category, backed by a new reactive PluginFilterState and PluginFilterController. It introduces PluginComponent.active/pluginFormat fields, a PluginComponentRepository, a PluginKindBadgeView, and migrates component icon rendering from ImageView to FontIcon.

Changes

Plugin Filter Sidebar

Layer / File(s) Summary
Reactive filter state
owlplug-client/src/main/java/com/owlplug/plugin/components/PluginFilterState.java
New component exposes ObservableSets for format/type/manufacturer/category selections and an auto-rebuilding predicate property matching IPlugin/PluginComponent items.
Component model and count repository
owlplug-client/.../model/PluginComponent.java, .../repositories/StringCountEntry.java, .../repositories/PluginComponentRepository.java, .../repositories/PluginRepository.java, .../tasks/PluginScanTask.java, .../core/utils/StringUtils.java
PluginComponent gains pluginFormat/active fields; PluginComponentRepository adds grouped-count queries; PluginScanTask sets new fields during scanning; StringUtils.capitalize helper added.
Filter sidebar controller
.../controllers/PluginFilterController.java
New controller builds checkbox sections, syncs with PluginFilterState, and refreshes counts/expandable sections from the repository.
Plugins screen wiring
.../controllers/PluginsController.java
Injects filter controller/state, inserts sidebar view, binds state to sub-controllers, refreshes on load, and adds toggleFilter().
Table and tree filter integration
.../controllers/PluginTableController.java, .../controllers/PluginTreeViewController.java
Both gain filterPredicate/bindFilterState(), combine search with external predicate, and table renders Format column via PluginKindBadgeView.
Icon migration to FontIcon/PluginKindBadgeView
.../core/components/ApplicationDefaults.java, .../ui/PluginKindBadgeView.java, .../ui/PluginComponentCellFactory.java, .../ui/PluginTreeCell.java, .../controllers/ComponentInfoController.java, .../controllers/PluginInfoController.java
New PluginKindBadgeView; defaultFormat replaces pluginComponentImage; component cells/badges render via FontIcon reflecting active state.
FXML/CSS for sidebar
owlplug-client/src/main/resources/fxml/plugins/PluginsView.fxml, owlplug-client/src/main/resources/owlplug.css
Adds filter toggle button, filterContainer wrapper, and sidebar styling.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant PluginFilterController
  participant PluginFilterState
  participant PluginTableController
  participant PluginTreeViewController

  User->>PluginFilterController: toggles checkbox
  PluginFilterController->>PluginFilterState: add/remove selection
  PluginFilterState->>PluginFilterState: recompute predicateProperty
  PluginFilterState-->>PluginTableController: filterPredicate binding fires
  PluginFilterState-->>PluginTreeViewController: filterPredicate binding fires
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • DropSnorz/OwlPlug#359: Both PRs relate to PluginScanTask populating PluginComponent.pluginFormat/active consumed by badge/cell rendering.
  • DropSnorz/OwlPlug#480: Both PRs modify ApplicationDefaults.getPluginFormatIcon(...) and associated icon/badge rendering logic.
  • DropSnorz/OwlPlug#484: Both PRs modify ComponentInfoController's format badge rendering in overlapping ways.

Suggested labels

Status: In Progress

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The CONTRIBUTORS entry is unrelated to plugin filtering and appears outside the requested scope. Move unrelated contributor changes to a separate PR or remove them from this patch.
Docstring Coverage ⚠️ Warning Docstring coverage is 9.21% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main change: adding plugin filters.
Description check ✅ Passed The description references issue #254, which matches the filter work.
Linked Issues check ✅ Passed The PR adds format, category, manufacturer, and type filtering to the Plugins tab TreeView.
✨ 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 feat/plugin-filter

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.

@DropSnorz DropSnorz added this to the 2.0.0 milestone Jun 29, 2026
@DropSnorz

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
✅ 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

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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
`@owlplug-client/src/main/java/com/owlplug/plugin/components/PluginFilterModel.java`:
- Around line 36-76: The shared filter model currently ignores the
Instrument/Effect type dimension, so `PluginFilterModel` only filters by format,
manufacturer, and category. Add type selection state to the same model alongside
`selectedFormats`, `selectedManufacturers`, and `selectedCategories`, then
update `buildPredicate()` and the relevant helper logic so the returned
predicate also enforces the selected type(s) for both `PluginComponent` and
`Plugin` cases. Make sure the new type filter is wired through the existing
`predicate` binding so table and tree views stay in sync.

In
`@owlplug-client/src/main/java/com/owlplug/plugin/controllers/ComponentInfoController.java`:
- Around line 92-94: The new label in ComponentInfoController.refresh()
dereferences component.asPlugin().getFormat().getName() even when the plugin
format may be missing, which can break the details pane for unknown formats.
Update the label construction to handle a null format safely, using the same
fallback behavior already supported by PluginKindBadgeView and
PluginFormatBadgeView, and keep the refresh() path from throwing when format
information is unavailable.

In
`@owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginFilterController.java`:
- Around line 141-157: The filter counts in PluginFilterController.refresh()
only use Plugin::getManufacturerName and Plugin::getCategory, so sidebar options
miss values that exist only on PluginComponent entries. Update the manufacturer
and category aggregation logic to include metadata from both Plugin and its
components, then pass those combined counts into buildExpandableSection so every
value rendered by PluginTableController and PluginTreeViewController can be
toggled from the sidebar.
- Around line 212-223: The createCheckBox() helper is registering a new
selectedSet listener for every checkbox, but refresh() later replaces the UI
without removing those listeners, so detached CheckBox instances stay
referenced. Add listener lifecycle management so each listener is unregistered
when the checkbox is discarded, or reuse a shared listener pattern owned by
PluginFilterController instead of attaching one per checkbox. Update the
checkbox wiring in createCheckBox() and the rebuild path in refresh() to ensure
old listeners are cleaned up when the section is rebuilt.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 41b4a971-68db-466e-8370-ec49b8d866fb

📥 Commits

Reviewing files that changed from the base of the PR and between 84a0217 and 07400ea.

⛔ Files ignored due to path filters (1)
  • owlplug-client/src/main/resources/icons/cube-white-16.png is excluded by !**/*.png, !**/*.png
📒 Files selected for processing (13)
  • owlplug-client/src/main/java/com/owlplug/core/components/ApplicationDefaults.java
  • owlplug-client/src/main/java/com/owlplug/plugin/components/PluginFilterModel.java
  • owlplug-client/src/main/java/com/owlplug/plugin/controllers/ComponentInfoController.java
  • owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginFilterController.java
  • owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginInfoController.java
  • owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginTableController.java
  • owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginTreeViewController.java
  • owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginsController.java
  • owlplug-client/src/main/java/com/owlplug/plugin/ui/PluginComponentCellFactory.java
  • owlplug-client/src/main/java/com/owlplug/plugin/ui/PluginKindBadgeView.java
  • owlplug-client/src/main/java/com/owlplug/plugin/ui/PluginTreeCell.java
  • owlplug-client/src/main/resources/fxml/plugins/PluginsView.fxml
  • owlplug-client/src/main/resources/owlplug.css

@DropSnorz

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
✅ 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

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginsController.java (1)

227-234: 🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

filterController.refresh() runs blocking repository queries on the FX Application Thread.

displayPlugins() correctly fetches plugins asynchronously via Async.supply, but filterController.refresh() is invoked inside FX.run(...) at line 232, where it performs four separate JPA count queries (see PluginFilterController.refresh()). This blocks UI rendering for the duration of those queries.

Move the count-fetching into the async stage and only apply the resulting UI updates inside FX.run(...).

🤖 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
`@owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginsController.java`
around lines 227 - 234, `displayPlugins()` is still triggering blocking database
work on the JavaFX Application Thread because `filterController.refresh()` is
called inside `FX.run(...)`. Move the plugin count queries out of the UI
callback by changing the async chain in `PluginsController.displayPlugins()` so
the repository work happens in the `Async.supply`/subsequent background stage,
then pass the computed filter data into `FX.run(...)` only for UI updates. Use
the existing `PluginFilterController.refresh()` logic as the reference for what
needs to be split so the FX thread only updates `treeViewController`,
`tableController`, and the refreshed filter state.
🧹 Nitpick comments (4)
owlplug-client/src/main/java/com/owlplug/plugin/components/PluginFilterState.java (1)

53-58: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

matchesFormat bypasses the new pluginFormat field on PluginComponent.

component.asPlugin().getFormat() reads the format through the lazily-associated parent Plugin entity, while PluginComponent now carries its own denormalized pluginFormat field (set during scan specifically "based on parent plugin"). Using component.getPluginFormat() here would be consistent with the field's stated purpose and avoid relying on the plugin association being populated/consistent at filter-evaluation time.

♻️ Proposed fix
       if (plugin instanceof PluginComponent component) {
-        return matchesFormat(component.asPlugin().getFormat())
+        return matchesFormat(component.getPluginFormat())
             && matchesType(component.getType())
             && matchesManufacturer(component.getManufacturerName())
             && matchesCategory(component.getCategory());
🤖 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
`@owlplug-client/src/main/java/com/owlplug/plugin/components/PluginFilterState.java`
around lines 53 - 58, `PluginFilterState` is still reading the format from the
parent `Plugin` via `component.asPlugin().getFormat()` instead of the
denormalized `pluginFormat` stored on `PluginComponent`. Update the filter logic
in the `matchesFormat` check to use `component.getPluginFormat()` so
`matchesFormat` relies on the component’s own scanned data and does not depend
on the association being loaded or consistent.
owlplug-client/src/main/java/com/owlplug/plugin/model/PluginComponent.java (1)

56-67: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Remove the unused PluginComponent.pluginFormat field It only gets set during scan; the existing read paths use plugin.getFormat(), so this copy adds a second source of truth with no current consumer.

🤖 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 `@owlplug-client/src/main/java/com/owlplug/plugin/model/PluginComponent.java`
around lines 56 - 67, Remove the unused PluginComponent.pluginFormat field from
PluginComponent, since the scan-time copy is not consumed anywhere and creates a
duplicate source of truth. Update the model by deleting the field and its
related JPA annotation, and then verify any scan or mapping code that references
PluginComponent.pluginFormat is switched to use the parent Plugin’s format via
plugin.getFormat() instead.
owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginTableController.java (1)

98-112: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Search/filter predicate logic duplicated with PluginTreeViewController.buildTreePredicate.

The combined search-match + external-predicate test (name/category substring matching, then fp.test(plugin)) is re-implemented almost verbatim in PluginTreeViewController.buildTreePredicate (lines 93-109 there). Consider extracting a shared Predicate<IPlugin> builder (e.g., a static helper or a method on PluginFilterState) that both controllers call, to avoid the two copies drifting apart.

🤖 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
`@owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginTableController.java`
around lines 98 - 112, The search/filter predicate logic in
PluginTableController is duplicated from
PluginTreeViewController.buildTreePredicate, so refactor the shared
name/category search plus external predicate combination into one reusable
Predicate<IPlugin> builder. Add a common helper (for example on
PluginFilterState or a static utility) that takes the search value and optional
fp predicate, then have both filteredPluginList.predicateProperty() and
buildTreePredicate call it so the matching behavior stays in sync.
owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginFilterController.java (1)

227-287: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

"View more" expansion state is lost on every rebuild.

buildExpandableSection always starts fullBox collapsed (setVisible(false)/setManaged(false)). Since this method runs on every refresh() (i.e., after every scan/reload), a user's previously expanded manufacturer/category list collapses back each time, which is a minor but recurring UX regression.

Consider tracking the expanded state per section (e.g., a boolean field) and restoring it after rebuild.

💡 Sketch
+  private boolean manufacturerExpanded = false;
+  private boolean categoryExpanded = false;
+
   private void buildExpandableSection(VBox container, Map<String, CheckBox> checkBoxMap,
-      Map<String, Long> countMap, Consumer<String> onToggle, ObservableSet<String> selectedSet) {
+      Map<String, Long> countMap, Consumer<String> onToggle, ObservableSet<String> selectedSet,
+      boolean expanded, Consumer<Boolean> onExpandToggle) {
     ...
-      fullBox.setVisible(false);
-      fullBox.setManaged(false);
+      fullBox.setVisible(expanded);
+      fullBox.setManaged(expanded);
       Hyperlink viewMore = new Hyperlink(...);
       viewMore.setOnAction(e -> {
         boolean newState = !fullBox.isVisible();
         fullBox.setVisible(newState);
         fullBox.setManaged(newState);
+        onExpandToggle.accept(newState);
         ...
       });
🤖 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
`@owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginFilterController.java`
around lines 227 - 287, The “View more” toggle state is reset on every rebuild
because buildExpandableSection always initializes the overflow VBox collapsed.
Update PluginFilterController so each expandable section remembers whether it
was expanded, then have buildExpandableSection restore that state after
recreating the preview/full boxes; use the existing Hyperlink toggle logic and
the section-specific data structures around checkBoxMap to locate the right
place to persist and reapply the expanded flag.
🤖 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
`@owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginFilterController.java`:
- Around line 188-217: `PluginFilterController.refresh()` is doing blocking
repository work on the JavaFX thread, so split the DB queries from the UI
updates. Move the calls to
`pluginComponentRepository.countFormatsFromComponents`, `countByType`,
`countManufacturerNamesFromComponents`, and `countCategoriesFromComponents`
off-thread (or into a background task), then apply only the checkbox text
updates and `buildExpandableSection(...)` calls back on the FX thread via
`FX.run(...)`. Keep `refresh()`/its call site in
`PluginsController.displayPlugins()` responsive by ensuring no repository I/O
runs synchronously inside the FX callback.

---

Outside diff comments:
In
`@owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginsController.java`:
- Around line 227-234: `displayPlugins()` is still triggering blocking database
work on the JavaFX Application Thread because `filterController.refresh()` is
called inside `FX.run(...)`. Move the plugin count queries out of the UI
callback by changing the async chain in `PluginsController.displayPlugins()` so
the repository work happens in the `Async.supply`/subsequent background stage,
then pass the computed filter data into `FX.run(...)` only for UI updates. Use
the existing `PluginFilterController.refresh()` logic as the reference for what
needs to be split so the FX thread only updates `treeViewController`,
`tableController`, and the refreshed filter state.

---

Nitpick comments:
In
`@owlplug-client/src/main/java/com/owlplug/plugin/components/PluginFilterState.java`:
- Around line 53-58: `PluginFilterState` is still reading the format from the
parent `Plugin` via `component.asPlugin().getFormat()` instead of the
denormalized `pluginFormat` stored on `PluginComponent`. Update the filter logic
in the `matchesFormat` check to use `component.getPluginFormat()` so
`matchesFormat` relies on the component’s own scanned data and does not depend
on the association being loaded or consistent.

In
`@owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginFilterController.java`:
- Around line 227-287: The “View more” toggle state is reset on every rebuild
because buildExpandableSection always initializes the overflow VBox collapsed.
Update PluginFilterController so each expandable section remembers whether it
was expanded, then have buildExpandableSection restore that state after
recreating the preview/full boxes; use the existing Hyperlink toggle logic and
the section-specific data structures around checkBoxMap to locate the right
place to persist and reapply the expanded flag.

In
`@owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginTableController.java`:
- Around line 98-112: The search/filter predicate logic in PluginTableController
is duplicated from PluginTreeViewController.buildTreePredicate, so refactor the
shared name/category search plus external predicate combination into one
reusable Predicate<IPlugin> builder. Add a common helper (for example on
PluginFilterState or a static utility) that takes the search value and optional
fp predicate, then have both filteredPluginList.predicateProperty() and
buildTreePredicate call it so the matching behavior stays in sync.

In `@owlplug-client/src/main/java/com/owlplug/plugin/model/PluginComponent.java`:
- Around line 56-67: Remove the unused PluginComponent.pluginFormat field from
PluginComponent, since the scan-time copy is not consumed anywhere and creates a
duplicate source of truth. Update the model by deleting the field and its
related JPA annotation, and then verify any scan or mapping code that references
PluginComponent.pluginFormat is switched to use the parent Plugin’s format via
plugin.getFormat() instead.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 22cd1aa7-46dc-43d6-a9ad-d33a2a626124

📥 Commits

Reviewing files that changed from the base of the PR and between 07400ea and 27cae7e.

📒 Files selected for processing (13)
  • CONTRIBUTORS
  • owlplug-client/src/main/java/com/owlplug/core/utils/StringUtils.java
  • owlplug-client/src/main/java/com/owlplug/plugin/components/PluginFilterState.java
  • owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginFilterController.java
  • owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginTableController.java
  • owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginTreeViewController.java
  • owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginsController.java
  • owlplug-client/src/main/java/com/owlplug/plugin/model/PluginComponent.java
  • owlplug-client/src/main/java/com/owlplug/plugin/repositories/PluginComponentRepository.java
  • owlplug-client/src/main/java/com/owlplug/plugin/repositories/PluginRepository.java
  • owlplug-client/src/main/java/com/owlplug/plugin/repositories/StringCountEntry.java
  • owlplug-client/src/main/java/com/owlplug/plugin/tasks/PluginScanTask.java
  • owlplug-client/src/main/java/com/owlplug/plugin/ui/PluginComponentCellFactory.java
✅ Files skipped from review due to trivial changes (2)
  • CONTRIBUTORS
  • owlplug-client/src/main/java/com/owlplug/plugin/repositories/PluginRepository.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • owlplug-client/src/main/java/com/owlplug/plugin/ui/PluginComponentCellFactory.java

@DropSnorz DropSnorz merged commit 7bc9024 into v2 Jul 1, 2026
3 of 4 checks passed
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