Skip to content

Fix select2 change recursion from double-wired Criticality device filter - #270

Merged
TheWitness merged 2 commits into
developfrom
fix/monitor-device-filter-select2-recursion
Oct 8, 2026
Merged

TheWitness merged 2 commits into
developfrom
fix/monitor-device-filter-select2-recursion

Conversation

@TheWitness

Copy link
Copy Markdown
Member

Problem

On the Device list page, interacting with a select2-widgeted filter dropdown
could send the browser into an infinite select2 loop
(_registerDomEvents → change.select2 → selection:update → update → …),
spiking memory until the tab died. Reproduced on develop with the Deepness
theme via the Firefox profiler; changeHostForm itself 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 on
version_compare(CACTI_VERSION, '1.3.0', '<'). On current develop builds the
version still compares < 1.3.0, so the block runs even though core already
renders the Criticality filter natively from the device_filters hook
(monitor_device_filters()) via the CactiTableFilter class, which also
widgetizes the control (select2) and wires its own change handler.

The injected JS then, on top of core's already-wired field:

  • injected a second #criticality control,
  • layered a jQuery-UI selectmenu (or a raw change handler) on a control core
    had already made select2, and
  • reassigned the global applyFilter.

That double widget/handler on one control is what re-enters select2's
change.select2 → selection:update → update cycle.

Fix

Let the filter class own the field. The injection path is now gated on the
actual rendered outcome instead of a version string:

  • If #criticality already exists (core rendered it from device_filters),
    the handler bails immediately — no second control, no extra handler, core's
    applyFilter left intact.
  • Only when the field is absent (true legacy releases without native
    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.monitor handler, and define applyFilter.

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 develop build still
reporting a < 1.3.0 version.

Testing

  • php -l setup.php — no syntax errors.
  • Verified the injection no-ops when #criticality is already present and still
    injects/wires once when it is not.

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
TheWitness requested review from bmfmancini and xmacan and a balanced review from Copilot October 8, 2026 18:35

Copilot AI 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.

🟡 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.

Comment thread setup.php
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).
@TheWitness
TheWitness merged commit 0409ecb into develop Oct 8, 2026
5 checks passed
@TheWitness
TheWitness deleted the fix/monitor-device-filter-select2-recursion branch October 8, 2026 18:58
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.

3 participants