perf(unified-search): skip redundant loading-spinner DOM rebuild on every keystroke

Author: LearningCircuitCreated Aug 15, 2026Updated Sep 18, 2026
Labelsjavascriptperformanceseverity:suggestedfrom-review-extractionfollow-up-work-in-progress

Summary

The unified-search input handler calls showUnifiedSearchLoading('Searching...') on every input event, and that function unconditionally rebuilds the spinner DOM (replaceChildren + 3 createElement + 2 style writes) even when the spinner is already displayed.

Field Value
Severity suggested
Origin Review of #5440 (post-merge extraction)
Review section Suggested
Classification PR-introduced (per-keystroke call added in #5440; rebuild itself pre-existing)
Verified at main@99c07fe97fca
Dedup distinct · 0 hits reviewed

Trigger

Type multiple characters into the library search bar: each keystroke enters the input handler, which (for queries at/above MIN_QUERY_LENGTH) calls showUnifiedSearchLoading before arming the debounce timer. Every call discards and recreates the spinner nodes even though the spinner from the previous keystroke is still on screen.

Impact

Wasteful per-keystroke DOM churn during typing (write-only, no layout thrash today) and results→spinner→spinner transitions mid-typing. Degrades on slow devices and long fast-typing bursts; no correctness break.

Pinned evidence

  • 99c07fe97fca:src/local_deep_research/web/static/js/pages/unified_search.js:L149 — unconditional per-keystroke showUnifiedSearchLoading('Searching...') in the input handler
  • 99c07fe97fca:src/local_deep_research/web/static/js/pages/unified_search.js:L226-L240showUnifiedSearchLoading always does replaceChildren() + node creation + style writes, with no already-loading short-circuit

Fix direction

Track a lightweight UI state ('idle' | 'loading' | 'results', e.g. alongside unifiedSearchRunId) and short-circuit showUnifiedSearchLoading when the spinner is already displayed; reset the state on idle/results/error renders.

Targeted tests

Add a vitest case: dispatch two consecutive input events (query ≥ MIN_QUERY_LENGTH), capture the spinner node after the first keystroke, and assert the same node instance survives the second (node identity — childElementCount cannot detect replacement with an equivalent spinner); alternatively spy on document.createElement/replaceChildren and assert a single rebuild. Then assert results still render after debounce.

Provenance

Extracted from review of PR #5440 (post-merge extraction).

Source: LearningCircuit/local-deep-research