Skip to content

Conversation

@gcgoncalves
Copy link
Collaborator

@gcgoncalves gcgoncalves commented Jan 16, 2026

🐛 Bug-fix PR

📌 Summary

This PR fixes issue 2121, where the initializeSearchInputs() function was being called repeatedly every few seconds on table view tabs in the Admin UI, causing performance degradation and console log spam. The fix implements a generic memoization pattern with debouncing to prevent duplicate initialisation while maintaining proper search functionality across all tabs.

Closes #2121

🔁 Reproduction Steps

  1. Access the Admin UI
  2. Click on any tab that displays a table (MCP Servers, Virtual Servers, Tools, Resources, Prompts, A2A Agents)
  3. Open browser console and monitor the logs
  4. Observe the initialisation sequence repeating continuously every few seconds:
    🔍 Initializing search inputs...
    ✅ Virtual Servers search initialized
    ✅ Found MCP Servers search input
    ✅ MCP Servers search events attached
    ✅ Tools search initialized
    ✅ Resources search initialized
    ✅ Prompts search initialized
    ✅ A2A Agents search initialized
    

🐞 Root Cause

The root cause was in the DOMContentLoaded event handler in mcpgateway/static/admin.js:

  1. Multiple HTMX event listeners (htmx:afterSwap, htmx:load) fired on every content update, not just tab switches
  2. No debouncing mechanism to prevent rapid successive calls when events fired in quick succession
  3. Event listener accumulation - the listeners that called initializeSearchInputs() were never removed and kept accumulating
  4. Cascading effect - Each HTMX content update triggered both events, causing multiple delayed calls that created a feedback loop

The initializeSearchInputs() function itself tried to prevent duplicate event listeners by cloning input elements, but this didn't address the root cause of the function being called repeatedly.

💡 Fix Description

Implemented a generic memoization pattern with the following key features:

1. Created createMemoizedInit() Utility Function

  • Generic, reusable pattern for any initialisation function
  • Uses closure-based state management (no global variables)
  • Returns object with { init, debouncedInit, reset } functions
  • Built-in guards prevent concurrent and duplicate initialisation
  • Integrated debouncing support

2. Applied Memoization to initializeSearchInputs()

  • Created memoized versions: initializeSearchInputsMemoized, initializeSearchInputsDebounced
  • Added resetSearchInputsState() for explicit state cleanup
  • Exported reset function to window object for manual cleanup scenarios

3. Updated Event Listeners

  • Use initializeSearchInputsMemoized() for immediate initialisation on page load
  • Use initializeSearchInputsDebounced() for HTMX and tab switch events
  • Added selective HTMX event handling (only triggers on relevant panel swaps)
  • Removed redundant htmx:load listener
  • Added resetSearchInputsState() calls before re-initialisation

4. Simplified initializeSearchInputs()

  • Removed input cloning logic (no longer needed with memoization)
  • Kept core initialisation logic intact

Key Design Points:

  • No global variables - all state encapsulated in closures
  • Reusable pattern - can be applied to other initialisation functions (metrics, modals, etc.)
  • Explicit reset mechanism - proper cleanup when elements are destroyed
  • Performance improvement - prevents unnecessary event listener creation/destruction

🧪 Verification

Check Command Status
Lint suite make lint
Unit tests make test
Coverage ≥ 90 % make coverage
Manual regression no longer fails See below

📝 Additional Notes

Future Reusability

This memoization pattern can be applied to other initialisation functions:

// Example: Memoize metrics loading
const {
    init: loadMetricsMemoized,
    debouncedInit: loadMetricsDebounced,
    reset: resetMetricsState
} = createMemoizedInit(loadAggregatedMetrics, 500, 'Metrics');

// Example: Memoize modal initialisation
const {
    init: initModalsMemoized,
    reset: resetModalsState
} = createMemoizedInit(initializeModals, 200, 'Modals');

Performance Impact

  • Eliminates unnecessary event listener creation/destruction
  • Reduces console log spam
  • Improves overall UI responsiveness
  • No memory leaks from accumulated event listeners

@gcgoncalves gcgoncalves force-pushed the 2121-initializeSearchInputs-recurrency branch from 5ddc5d5 to 1114be2 Compare January 16, 2026 12:13
@gcgoncalves gcgoncalves changed the base branch from main to 2111-show-inactive-fix January 16, 2026 13:53
@gcgoncalves gcgoncalves changed the base branch from 2111-show-inactive-fix to main January 16, 2026 13:53
@crivetimihai crivetimihai self-assigned this Jan 19, 2026
@gcgoncalves gcgoncalves marked this pull request as ready for review January 19, 2026 09:35
@gcgoncalves gcgoncalves changed the base branch from main to oauth-fix January 19, 2026 09:35
@gcgoncalves gcgoncalves changed the base branch from oauth-fix to main January 19, 2026 09:35
@gcgoncalves gcgoncalves force-pushed the 2121-initializeSearchInputs-recurrency branch from 1114be2 to 10ee939 Compare January 19, 2026 09:36
gcgoncalves and others added 3 commits January 20, 2026 02:08
The initializeSearchInputs() function was being called repeatedly every
few seconds on table view tabs, causing performance issues and console
log spam. This occurred due to multiple HTMX event listeners firing
frequently without proper debouncing or initialization guards.

Changes:
- Added createMemoizedInit() utility function for generic initialization
  memoization with closure-based state management (no global variables)
- Created memoized versions of initializeSearchInputs with debouncing
- Updated DOMContentLoaded event listeners to use memoized versions
- Added selective HTMX event handling (only relevant panel swaps)
- Removed redundant htmx:load listener
- Removed input cloning logic (no longer needed with memoization)
- Added resetSearchInputsState() for explicit state cleanup

The memoization pattern is reusable for other initialization functions
and prevents duplicate initialization while providing explicit reset
capability for cleanup scenarios.

Signed-off-by: Gabriel Costa <[email protected]>
The memoization pattern allows re-initialization via reset calls on tab
switches and HTMX swaps. Since search inputs persist (they're outside
HTMX swap targets), this caused duplicate event listeners. Restore the
input cloning logic to remove existing listeners before re-adding.

Signed-off-by: Mihai Criveti <[email protected]>
…panel)

The htmx:afterSwap handler was checking for 'servers-panel' but the
actual panel ID in admin.html is 'catalog-panel'. This prevented search
re-initialization after pagination or toggle changes in Virtual Servers.

Signed-off-by: Mihai Criveti <[email protected]>
@crivetimihai crivetimihai force-pushed the 2121-initializeSearchInputs-recurrency branch from 10ee939 to 21f576f Compare January 20, 2026 02:42
@crivetimihai
Copy link
Member

Review Feedback & Fixes

Thanks for the PR! The memoization pattern is well-designed and reusable. During review, I identified two issues that I've fixed in additional commits:

1. Duplicate Event Listeners (Fixed in 6ac89d0b)

The PR removed the input cloning logic with the assumption that memoization prevents duplicate initialization. However, since resetSearchInputsState() is called on tab switches and HTMX swaps, the function can run multiple times. The search inputs persist (they're outside HTMX swap targets like servers-table), so each re-initialization added duplicate event listeners.

Fix: Restored the input cloning logic in initializeSearchInputs() to remove existing listeners before re-adding them.

2. Wrong Panel ID for Virtual Servers (Fixed in 21f576fe)

The relevantPanels array used servers-panel, but the actual panel ID in admin.html is catalog-panel. This prevented search re-initialization after pagination or "include inactive" toggle changes in the Virtual Servers tab.

Fix: Changed servers-panelcatalog-panel.

Commits Added

21f576fe fix: correct panel ID for Virtual Servers (catalog-panel not servers-panel)
6ac89d0b fix: restore input cloning to prevent duplicate event listeners

Both fixes pass make lint-web and syntax checks.

@crivetimihai crivetimihai merged commit 8d31bb5 into main Jan 20, 2026
50 checks passed
@crivetimihai crivetimihai deleted the 2121-initializeSearchInputs-recurrency branch January 20, 2026 02:53
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.

[Bug]: On table views, initializeSearchInputs() is called recurrently

3 participants