Search Scoping and Global Toggle Implementation

Fixed search to default to current project scope with optional global search toggle

๐Ÿ” Search Scoping and Global Toggle Implementation

Date: October 29, 2025
Time: Morning
Status: โœ… COMPLETE
Purpose: Fix search behavior to properly scope to current project by default, with an optional global search toggle


๐Ÿ“‹ Problem Statement

The search was showing results from ALL projects regardless of which project the user was currently viewing. This caused confusion and poor UX because:

  1. No Project Scoping: When in "GoGrab-Pulse" project, search showed results from Backend-Api, GoGrab-Space, GoGrab-Space-Deck-md, and public
  2. Missing Default Behavior: There was no clear default - should it search locally or globally?
  3. No User Control: Users couldn't toggle between local (current project) and global (all projects) search
  4. Project Filter Issues: On /select-project route, the search modal should act as a simple project filter

โœ… Solution Overview

Implemented a two-tier search system:

  1. Default Behavior: Search ONLY within the current project (local search)
  2. Optional Global Search: User can toggle to search across all accessible projects
  3. Visual Toggle: Clear UI indicator showing current search mode (Local vs Global)
  4. Smart Filtering: Different search behavior on /select-project route (project filter mode)

๐Ÿ”ง Implementation Details

Feature 1: Default Project Scoping

File: apps/docs-angular/src/app/services/search.service.ts

Added Global Search Signal

// ๐ŸŽฏ NEW: Global search toggle (user can enable to search all projects)
readonly globalSearchEnabled = signal<boolean>(false);

Default: false - searches only current project

Updated Content Search Logic

private performContentSearch(term: string): SearchResult[] {
  const currentProject = this.currentProject();
  const isGlobalSearchEnabled = this.globalSearchEnabled();
  
  // ๐ŸŽฏ CRITICAL RULE: Default behavior - ALWAYS scope to current project unless global search is enabled
  let filteredIndex = this.searchIndex.filter(i => {
    // Filter by language
    if (i.language !== activeLanguage) return false;
    
    const resultProjectName = i.projectName || 'public';
    
    // ๐ŸŽฏ DEFAULT: Scope to current project only
    if (!isGlobalSearchEnabled) {
      if (resultProjectName !== currentProject) {
        console.log(`๐Ÿ”’ Blocked ${resultProjectName} - global search disabled, only showing ${currentProject}`);
        return false;
      }
      return true; // Show current project results
    }
    
    // ๐ŸŒ GLOBAL SEARCH ENABLED - Apply additional rules
    // ... (access control, private project isolation, etc.)
  });
  
  // ... rest of search logic
}

Key Rules (in order of priority):

  1. Always filter by language first
  2. Default scope to current project unless global search is enabled
  3. When global search is enabled:
    • Check access permissions via AccessService
    • Private projects can ONLY be searched from within themselves
    • Respect project-specific crossProjectSearch setting

Feature 2: Global Search Toggle Method

File: apps/docs-angular/src/app/services/search.service.ts

// ๐ŸŽฏ NEW: Global search toggle
toggleGlobalSearch(): void {
  const currentValue = this.globalSearchEnabled();
  this.globalSearchEnabled.set(!currentValue);
  console.log(`๐ŸŒ Global search ${!currentValue ? 'enabled' : 'disabled'}`);
  
  // Re-run search with new scope
  this.refreshSearchResults();
}

Benefits:


Feature 3: UI Toggle Button

File: apps/docs-angular/src/app/shared/global-search/global-search.component.ts

HTML Template

<!-- ๐ŸŒ GLOBAL SEARCH TOGGLE - Only show in content-search mode -->
@if (searchService.searchMode() === 'content-search') {
  <button 
    class="global-search-toggle"
    [class.active]="searchService.globalSearchEnabled()"
    (click)="searchService.toggleGlobalSearch()"
    title="{{ searchService.globalSearchEnabled() ? 'Search all projects' : 'Search current project only' }}"
  >
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="toggle-icon">
      <circle cx="12" cy="12" r="10"/>
      <path d="M2 12h20"/>
      <path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>
    </svg>
    <span class="toggle-label">{{ searchService.globalSearchEnabled() ? 'Global' : 'Local' }}</span>
  </button>
}

Key Features:


Feature 4: Toggle Button Styling

File: apps/docs-angular/src/app/shared/global-search/global-search.component.scss

Light Mode Styles

.global-search-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  margin-right: 12px;
  border: 1.5px solid rgba(59, 130, 246, 0.2);
  background: rgba(255, 255, 255, 0.7);
  backdrop-filter: blur(10px);
  border-radius: 10px;
  cursor: pointer;
  transition: all 0.25s cubic-bezier(0.23, 1, 0.32, 1);
  font-size: 12px;
  font-weight: 600;
  color: var(--text-secondary, #6b7280);
  z-index: 2;
  position: relative;
  flex-shrink: 0;
  
  &:hover {
    border-color: rgba(59, 130, 246, 0.4);
    background: rgba(255, 255, 255, 0.9);
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
  }
  
  &.active {
    border-color: rgba(59, 130, 246, 0.5);
    background: linear-gradient(135deg, rgba(59, 130, 246, 0.12) 0%, rgba(139, 92, 246, 0.08) 100%);
    color: var(--accent-primary, #3b82f6);
    
    .toggle-icon {
      color: var(--accent-primary, #3b82f6);
      transform: rotate(180deg); // Visual feedback
    }
    
    &:hover {
      background: linear-gradient(135deg, rgba(59, 130, 246, 0.18) 0%, rgba(139, 92, 246, 0.12) 100%);
      box-shadow: 0 6px 16px rgba(59, 130, 246, 0.2);
    }
  }
  
  .toggle-icon {
    width: 16px;
    height: 16px;
    transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
    flex-shrink: 0;
  }
  
  .toggle-label {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    transition: all 0.25s ease;
  }
  
  @media (max-width: 768px) {
    padding: 6px 10px;
    
    .toggle-label {
      display: none; // Icon only on mobile
    }
  }
}

Dark Mode Styles

.global-search-toggle {
  border-color: rgba(96, 165, 250, 0.3);
  background: rgba(15, 23, 42, 0.7);
  color: var(--text-secondary-dark, #9ca3af);
  
  &:hover {
    border-color: rgba(96, 165, 250, 0.5);
    background: rgba(15, 23, 42, 0.95);
    box-shadow: 0 4px 12px rgba(96, 165, 250, 0.2);
  }
  
  &.active {
    border-color: rgba(96, 165, 250, 0.6);
    background: linear-gradient(135deg, rgba(96, 165, 250, 0.15) 0%, rgba(168, 85, 247, 0.1) 100%);
    color: var(--accent-primary, #60a5fa);
    
    .toggle-icon {
      color: var(--accent-primary, #60a5fa);
    }
    
    &:hover {
      background: linear-gradient(135deg, rgba(96, 165, 250, 0.2) 0%, rgba(168, 85, 247, 0.15) 100%);
      box-shadow: 0 6px 16px rgba(96, 165, 250, 0.3);
    }
  }
}

Design Features:


๐Ÿ”„ Search Behavior Matrix

Context Global Toggle Behavior Example
Inside Project OFF (default) Search ONLY current project In "GoGrab-Pulse" โ†’ only GoGrab-Pulse results
Inside Project ON Search all accessible projects In "GoGrab-Pulse" โ†’ results from all public projects
Private Project OFF Search ONLY private project In "Private Docs" โ†’ only Private Docs results
Private Project ON Search current + public projects In "Private Docs" โ†’ Private + public results (private isolated)
/select-project N/A Filter projects by name/tags Search "gograb" โ†’ filters to GoGrab projects

๐ŸŽฏ Search Scoping Rules

Rule 1: Default Local Scope (CRITICAL)

if (!isGlobalSearchEnabled) {
  if (resultProjectName !== currentProject) {
    return false; // Block all other projects
  }
  return true; // Show current project only
}

Always enforce unless user explicitly enables global search.

Rule 2: Language Filtering (ALWAYS)

if (i.language !== activeLanguage) {
  return false; // Never show wrong language
}

Never show results from different language regardless of other settings.

Rule 3: Access Control (Global Search Only)

const canAccess = this.accessService.canAccessProject(resultProjectId);
if (!canAccess) {
  return false; // Block inaccessible projects
}

When global search is enabled, check permissions before showing results.

Rule 4: Private Project Isolation (ALWAYS)

if (i.visibility === 'private' && resultProjectName !== currentProject) {
  return false; // Private projects only searchable from within
}

Critical security rule: Private projects are NEVER searchable from outside, even with global search enabled.

Rule 5: Project-Specific Settings (Global Search Only)

if (!effectiveConfig.crossProjectSearch && resultProjectName !== currentProject) {
  return false; // Respect project's crossProjectSearch setting
}

Each project can opt-out of being included in global search results.


๐Ÿ“Š User Flows

Flow 1: Default Local Search (Most Common)

1. User in "GoGrab-Space" project
   โ†“
2. User presses โŒ˜K
   โ†“
3. Search modal opens with toggle showing "Local" (inactive)
   โ†“
4. User types "widgets"
   โ†“
5. Results shown ONLY from GoGrab-Space:
   - "Widgets Library" (GoGrab-Space)
   - "Widget Components" (GoGrab-Space)
   - "Widget System" (GoGrab-Space)
   โ†“
6. NO results from other projects (even if they match)

Flow 2: Global Search (Opt-In)

1. User in "GoGrab-Space" project
   โ†“
2. User presses โŒ˜K
   โ†“
3. Search modal opens with toggle showing "Local"
   โ†“
4. User clicks toggle โ†’ changes to "Global" (active, blue gradient)
   โ†“
5. User types "api"
   โ†“
6. Results shown from multiple accessible projects:
   โœ… "API Documentation" (GoGrab-Space)
   โœ… "Backend API Guide" (Backend-API)
   โœ… "API Reference" (Public Docs)
   โŒ "Internal API" (Private Docs) - blocked
   โ†“
7. User can see results from all public/accessible projects

Flow 3: Private Project Search

1. User in "Private" project
   โ†“
2. User presses โŒ˜K
   โ†“
3. Search modal opens with toggle showing "Local"
   โ†“
4. User types "confidential"
   โ†“
5. Results shown ONLY from Private project:
   โœ… "Confidential Docs" (Private)
   โŒ All other projects blocked
   โ†“
6. User clicks toggle to "Global"
   โ†“
7. Search again
   โ†“
8. Results from Private + Public projects:
   โœ… "Confidential Docs" (Private)
   โœ… "Public Guidelines" (Public)
   โŒ Other private projects still blocked

Key Point: Private projects are isolated even in global search.

Flow 4: Project Filter Mode

1. User navigates to /select-project
   โ†“
2. User presses โŒ˜K
   โ†“
3. Search modal opens in "project-filter" mode
   โ†“
4. No toggle button shown (not applicable)
   โ†“
5. Placeholder says "Filter projects..."
   โ†“
6. User types "gograb"
   โ†“
7. Projects filtered:
   โœ… "GoGrab-Space" (matches name)
   โœ… "GoGrab-Pulse" (matches name)
   โœ… "GoGrab-Space-Deck-md" (matches name)
   โŒ "Backend-API" (no match)
   โŒ "Public Docs" (no match)

๐ŸŽจ Visual Design

Toggle Button States

Inactive (Local Search) - Default

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  ๐ŸŒ  LOCAL          โ”‚  โ† Gray, subtle border
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Active (Global Search)

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  ๐ŸŒ  GLOBAL         โ”‚  โ† Blue gradient, rotated globe
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Mobile View

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  ๐ŸŒ  โ”‚  โ† Icon only, no label
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿงช Testing Checklist

Test 1: Default Local Search

Test 2: Global Search Toggle

Test 3: Private Project Isolation

Test 4: Toggle Persistence

Test 5: Project Filter Mode

Test 6: Dark Mode


๐Ÿ“ Configuration Reference

Global Search Config (Default Values)

private globalConfig: GlobalSearchConfig = {
  scopeToCurrentProject: true,     // Default: search only current project
  crossProjectSearch: false,        // Default: don't search across projects
  includePrivateInGlobalSearch: false,
  maxResultsPerProject: 10,
  enableProjectSwitchingFromSearch: true
};

User Toggle State

readonly globalSearchEnabled = signal<boolean>(false); // Default: OFF (local search)

๐ŸŽฏ Summary

What Was Fixed:

  1. โœ… Search now defaults to current project scope (local search)
  2. โœ… User can optionally enable global search via toggle
  3. โœ… Clear visual indicator (Local vs Global)
  4. โœ… Private projects properly isolated
  5. โœ… Project filter mode works correctly on /select-project
  6. โœ… Beautiful, accessible toggle button with animations

Benefits:

Files Changed:

Status: โœ… COMPLETE - READY FOR TESTING


Last Updated: October 29, 2025
Impact: Critical - Fixes fundamental search behavior