Repository navigation
Fix select2 change recursion from double-wired Criticality device filter - #270
Merged
Merged
Conversation
Gate the client-side Criticality filter injection on whether core already rendered the field (device_filters/CactiTableFilter) instead of a CACTI_VERSION string. When core renders it natively the handler now bails, avoiding the second widget/handler that drove the select2 change -> selection:update recursion. Legacy releases still inject once, matching core's widget engine and binding a single namespaced change handler.
TheWitness
requested review from
bmfmancini and
xmacan
and
a balanced review from Copilot
October 8, 2026 18:35
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Automated regression coverage is needed for the native-filter and legacy-injection paths.
1 open finding
What changed in this PR
Prevents recursive Select2 events by avoiding duplicate Criticality filter wiring.
Changes:
- Skips legacy injection when the filter already exists.
- Uses one available widget engine.
- Deduplicates the namespaced change handler.
| File | Description |
|---|---|
setup.php |
Makes Criticality filter injection and wiring conditional and idempotent. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Exercise the device_table_bottom hook that injects the legacy Criticality filter, locking in the select2 change-recursion fix: the bail guard when core already rendered the field, a single namespaced change.monitor handler, a single widget engine (never both), theme gating, and the selected-option build. Covers the previously untested changed lines (setup.php patch-coverage gate).
xmacan
approved these changes
Oct 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Problem
On the Device list page, interacting with a select2-widgeted filter dropdown
could send the browser into an infinite
select2loop(
_registerDomEvents→change.select2→selection:update→update→ …),spiking memory until the tab died. Reproduced on
developwith the Deepnesstheme via the Firefox profiler;
changeHostFormitself was only entered once(max depth 1), so the runaway was inside select2, not the host form.
Root cause
monitor_device_table_bottom()gates its client-side filter injection onversion_compare(CACTI_VERSION, '1.3.0', '<'). On currentdevelopbuilds theversion still compares
< 1.3.0, so the block runs even though core alreadyrenders the Criticality filter natively from the
device_filtershook(
monitor_device_filters()) via theCactiTableFilterclass, which alsowidgetizes the control (select2) and wires its own change handler.
The injected JS then, on top of core's already-wired field:
#criticalitycontrol,selectmenu(or a rawchangehandler) on a control corehad already made select2, and
applyFilter.That double widget/handler on one control is what re-enters select2's
change.select2→selection:update→updatecycle.Fix
Let the filter class own the field. The injection path is now gated on the
actual rendered outcome instead of a version string:
#criticalityalready exists (core rendered it fromdevice_filters),the handler bails immediately — no second control, no extra handler, core's
applyFilterleft intact.plugin-filter support) does it inject, widgetize with the same engine core
uses (select2 when present, else selectmenu — never both), bind a single
namespaced, de-duplicated
change.monitorhandler, and defineapplyFilter.This is behavior-based, so it is correct on 1.2.x (legacy inject), on
develop(native filter), and on the in-between case of a
developbuild stillreporting a
< 1.3.0version.Testing
php -l setup.php— no syntax errors.#criticalityis already present and stillinjects/wires once when it is not.