navidocs/AGENT_2_SEARCH_DEBOUNCE_IMPLEMENTATION.md

5.7 KiB

Agent 2: Search-as-You-Type Implementation

Task

Implement Apple Preview-style search with debouncing in DocumentView.vue

File Modified

/home/setup/navidocs/client/src/views/DocumentView.vue

Changes Implemented

1. Added Reactive State (Line 353)

const isSearching = ref(false)
  • Tracks loading state during search operations
  • Used to show/hide loading spinner

2. Added Control Variables (Lines 418-419)

let searchDebounceTimer = null
let searchAbortController = null
  • searchDebounceTimer: Manages 300ms debounce delay
  • searchAbortController: Allows cancellation of in-flight searches

3. Enhanced performSearch() Function (Lines 707-767)

Key improvements:

  • Made async to support cancellable operations
  • Added minimum query length check (2 characters)
  • Implements AbortController for cancellation
  • Sets isSearching state for loading indicator
  • Validates search hasn't been aborted before proceeding
  • Wraps search in try-catch for error handling
  • Cleans up abort controller in finally block

Flow:

  1. Validate query (not empty, min 2 chars)
  2. Cancel any previous search
  3. Create new AbortController
  4. Set loading state
  5. Highlight current page immediately
  6. Start background search across all pages
  7. Clean up and reset loading state

4. Enhanced clearSearch() Function (Lines 769-799)

Key improvements:

  • Clears pending debounce timer
  • Aborts any ongoing search operation
  • Resets isSearching state
  • Cleans up all search-related state

Added cleanup for:

  • searchDebounceTimer
  • searchAbortController
  • isSearching state

5. Implemented handleSearchInput() Function (Lines 801-840)

Core debouncing logic:

function handleSearchInput() {
  // Clear previous debounce timer
  if (searchDebounceTimer) {
    clearTimeout(searchDebounceTimer)
  }

  // Cancel previous search if new input arrives
  if (searchAbortController) {
    searchAbortController.abort()
    searchAbortController = null
  }

  const query = searchInput.value.trim()

  // Clear results immediately if search input is cleared
  if (!query) {
    clearSearch()
    return
  }

  // Don't search if query is less than 2 characters
  if (query.length < 2) {
    // Clear previous results but don't perform new search
    searchQuery.value = ''
    totalHits.value = 0
    hitList.value = []
    allPagesHitList.value = []
    currentHitIndex.value = 0
    return
  }

  // Set searching state to show loading indicator
  isSearching.value = true

  // Debounce search - wait 300ms after user stops typing
  searchDebounceTimer = setTimeout(() => {
    performSearch()
    searchDebounceTimer = null
  }, 300)
}

Features:

  • Clears previous timer on each keystroke
  • Cancels in-flight searches when new input arrives
  • Immediately clears results when input is cleared
  • Doesn't search for queries < 2 characters
  • Shows loading state immediately
  • Waits 300ms after typing stops before searching

6. Added Loading Indicator UI (Lines 69-99)

Before:

<button @click="performSearch">
  <svg><!-- Search icon --></svg>
</button>

After:

<button
  @click="performSearch"
  :class="{ 'pointer-events-none': isSearching }"
  :title="isSearching ? 'Searching...' : 'Search'"
>
  <!-- Loading spinner when searching -->
  <svg v-if="isSearching" class="animate-spin">
    <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
    <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
  </svg>
  <!-- Search icon when not searching -->
  <svg v-else>
    <path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
  </svg>
</button>

Features:

  • Shows spinning loader during search
  • Disables button clicks while searching
  • Updates tooltip text dynamically

Requirements Met

Debounced search (300ms delay): Implemented with setTimeout in handleSearchInput()

Loading indicator: Spinning icon shown via isSearching reactive state

Cancel previous search: Uses AbortController to cancel in-flight operations

Real-time results: Triggers search automatically as user types

Clear results: Immediately clears when input is cleared

Minimum query length: Won't search if query < 2 characters

User Experience

Typing Flow

  1. User types "eng" (3 characters)
  2. Timer starts counting 300ms
  3. User continues typing "ine"
  4. Timer resets, counts another 300ms
  5. User stops typing
  6. After 300ms, search executes
  7. Loading spinner shows during search
  8. Results appear when complete

Backspace Flow

  1. User backspaces to 1 character
  2. Previous results clear immediately
  3. No search is performed (< 2 chars)

Clear Flow

  1. User clicks clear button or empties input
  2. All results clear immediately
  3. Any pending search is cancelled

Technical Implementation

Custom Debounce

  • No external libraries required (VueUse not installed)
  • Simple setTimeout pattern
  • Properly cleans up timers

Cancellation Pattern

// Create controller
searchAbortController = new AbortController()

// Check if aborted
if (searchAbortController.signal.aborted) {
  return
}

// Clean up
searchAbortController = null

State Management

  • isSearching: Boolean for loading state
  • searchDebounceTimer: Timer ID for cleanup
  • searchAbortController: AbortController instance

Build Status

Build successful with no errors All syntax validated No TypeScript/linting issues

Integration Notes

  • Works seamlessly with existing search functionality
  • Preserves cross-page search capability
  • Maintains highlight and navigation features
  • No breaking changes to public API