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:
- No Project Scoping: When in "GoGrab-Pulse" project, search showed results from Backend-Api, GoGrab-Space, GoGrab-Space-Deck-md, and public
- Missing Default Behavior: There was no clear default - should it search locally or globally?
- No User Control: Users couldn't toggle between local (current project) and global (all projects) search
- Project Filter Issues: On
/select-projectroute, the search modal should act as a simple project filter
โ Solution Overview
Implemented a two-tier search system:
- Default Behavior: Search ONLY within the current project (local search)
- Optional Global Search: User can toggle to search across all accessible projects
- Visual Toggle: Clear UI indicator showing current search mode (Local vs Global)
- Smart Filtering: Different search behavior on
/select-projectroute (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):
- Always filter by language first
- Default scope to current project unless global search is enabled
- When global search is enabled:
- Check access permissions via
AccessService - Private projects can ONLY be searched from within themselves
- Respect project-specific
crossProjectSearchsetting
- Check access permissions via
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:
- โ Instant toggle between local and global search
- โ Automatically re-runs search with new scope
- โ Maintains current search term
- โ Clear console logging for debugging
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:
- Only shown in
content-searchmode (not on/select-project) - Globe icon for visual clarity
- Dynamic label: "Local" or "Global"
- Tooltip for additional context
- Reactive state binding
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:
- โ Glassmorphism with backdrop blur
- โ Smooth transitions and hover effects
- โ Rotate globe icon when active (180deg)
- โ Gradient background when active
- โ Responsive (label hidden on mobile)
- โ Full dark mode support
๐ 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
- Navigate to "GoGrab-Space" project
- Open search (โK)
- Verify toggle shows "Local" (inactive)
- Search for "widgets"
- Verify ONLY GoGrab-Space results shown
- Verify no results from other projects
Test 2: Global Search Toggle
- In "GoGrab-Space", open search
- Click toggle button
- Verify label changes to "Global" (blue)
- Verify globe icon rotates 180deg
- Search for "api"
- Verify results from multiple projects shown
Test 3: Private Project Isolation
- Navigate to "Private" project
- Search with local mode
- Verify only private project results
- Enable global search
- Verify private + public results
- Verify OTHER private projects still blocked
Test 4: Toggle Persistence
- Enable global search
- Close search modal
- Reopen search
- Verify toggle is still in "Global" state
- Navigate to different page
- Verify toggle state persists
Test 5: Project Filter Mode
- Navigate to /select-project
- Open search
- Verify no toggle button shown
- Verify placeholder says "Filter projects..."
- Type "gograb"
- Verify projects are filtered correctly
Test 6: Dark Mode
- Toggle dark mode
- Open search with local mode
- Verify button has dark styling
- Click toggle to global
- Verify active state with dark gradient
๐ 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:
- โ Search now defaults to current project scope (local search)
- โ User can optionally enable global search via toggle
- โ Clear visual indicator (Local vs Global)
- โ Private projects properly isolated
- โ Project filter mode works correctly on /select-project
- โ Beautiful, accessible toggle button with animations
Benefits:
- Better UX: Users see relevant results from their current context
- User Control: Clear toggle for switching between local/global
- Security: Private projects remain isolated
- Performance: Fewer results to process in default mode
- Clarity: Visual feedback about current search scope
Files Changed:
search.service.ts- Added scoping logic and toggle methodglobal-search.component.ts- Added toggle button UIglobal-search.component.scss- Added toggle button styles
Status: โ COMPLETE - READY FOR TESTING
Last Updated: October 29, 2025
Impact: Critical - Fixes fundamental search behavior