Select-Project Route Enhancements

Side navigation message and dual-mode search implementation

๐ŸŽฏ Select-Project Route Enhancements

Date: October 29, 2025
Time: Morning
Status: โœ… COMPLETE
Purpose: Enhance UX on /select-project route with smart navigation and search behavior


๐Ÿ“‹ Overview

Implemented intelligent behavior for the /select-project route to improve user experience:

  1. Side Navigation: Show helpful message instead of empty/irrelevant navigation
  2. Smart Search: Dual-mode search that filters projects on selection page, searches content elsewhere
  3. Proper Scoping: Respect project settings for cross-project search and private project isolation

โœ… Feature 1: Side Navigation Message

What Changed

When users are on /select-project route, the side navigation now displays a helpful message instead of showing navigation structure (which doesn't exist yet since no project is selected).

Implementation

File: apps/docs-angular/src/app/shared/docs-side-nav/docs-side-nav.component.ts

// ๐ŸŽฏ PROJECT-SCOPED: Detect if we're on the project selection route
readonly isSelectProjectRoute = computed(() => {
  const currentUrl = this.router.url;
  return currentUrl === '/select-project' || currentUrl.startsWith('/select-project');
});

File: apps/docs-angular/src/app/shared/docs-side-nav/docs-side-nav.component.html

<!-- ๐ŸŽฏ PROJECT SELECTION MESSAGE - Show helpful message when on selection route -->
@if (isSelectProjectRoute()) {
  <div class="project-selection-message">
    <div class="message-icon">๐Ÿ“š</div>
    <h3 class="message-title">Select a Project</h3>
    <p class="message-text">Please select a project to view the navigation structure.</p>
    <div class="message-hint">
      <svg>...</svg>
      <span>Choose from the available projects above</span>
    </div>
  </div>
} @else {
  <!-- Regular navigation structure -->
}

File: apps/docs-angular/src/app/shared/docs-side-nav/docs-side-nav.component.scss

.project-selection-message {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 3rem 2rem;
  text-align: center;
  gap: 1.5rem;
  animation: fadeIn 0.4s cubic-bezier(0.23, 1, 0.32, 1);
  
  .message-icon {
    font-size: 4rem;
    opacity: 0.9;
    animation: floatIcon 3s ease-in-out infinite;
  }
  
  .message-title {
    font-size: 1.5rem;
    font-weight: 700;
    color: var(--text-primary, #1f2937);
  }
  
  .message-text {
    font-size: 1rem;
    color: var(--text-secondary, #6b7280);
    max-width: 220px;
    line-height: 1.6;
  }
  
  .message-hint {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.75rem 1rem;
    background: linear-gradient(135deg, rgba(59, 130, 246, 0.08) 0%, rgba(139, 92, 246, 0.06) 100%);
    border: 1px solid rgba(59, 130, 246, 0.15);
    border-radius: 12px;
    font-size: 0.875rem;
    color: var(--text-secondary, #6b7280);
  }
}

Benefits


โœ… Feature 2: Dual-Mode Search

What Changed

Search now operates in two intelligent modes:

  1. Project Filter Mode (on /select-project): Filters available projects by name, description, tags
  2. Content Search Mode (everywhere else): Searches documentation content with proper scoping

Implementation

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

// ๐ŸŽฏ SEARCH MODE: Detect if we're on project selection page
readonly searchMode = computed(() => {
  const currentUrl = this.router.url;
  const isSelectProject = currentUrl === '/select-project' || currentUrl.startsWith('/select-project');
  return isSelectProject ? 'project-filter' : 'content-search';
});

private performSearch(term: string): SearchResult[] {
  const mode = this.searchMode();
  
  if (mode === 'project-filter') {
    return this.performProjectFilter(term);
  } else {
    return this.performContentSearch(term);
  }
}

Project Filter Logic:

private performProjectFilter(term: string): SearchResult[] {
  const searchTerms = term.toLowerCase().split(/\s+/);
  const activeLanguage = this.documentationService.currentLanguage();
  const rootProjects = this.accessService.getRootProjectsFor(languageKey);
  const results: SearchResult[] = [];
  
  for (const project of rootProjects) {
    let score = 0;
    
    // Score based on matches
    for (const term of searchTerms) {
      if (projectNameLower.includes(term)) score += 20;
      if (displayNameLower.includes(term)) score += 18;
      if (descriptionLower.includes(term)) score += 10;
      if (tagsLower.some(tag => tag.includes(term))) score += 15;
    }
    
    if (score > 0) {
      results.push({
        title: project.displayName || project.name,
        path: '/select-project',
        excerpt: project.description,
        section: 'Projects',
        score,
        type: 'page',
        projectName: project.name
      });
    }
  }
  
  return results.sort((a, b) => b.score - a.score);
}

Content Search Logic (enhanced):

private performContentSearch(term: string): SearchResult[] {
  const effectiveConfig = this.getEffectiveSearchConfig(currentProject);
  
  let filteredIndex = this.searchIndex.filter(i => {
    // RULE 1: Language filter
    if (i.language !== activeLanguage) return false;
    
    // RULE 2: Scope to current project if configured
    if (effectiveConfig.scopeToCurrentProject && !effectiveConfig.crossProjectSearch) {
      if (resultProjectName !== currentProject) return false;
    }
    
    // RULE 3: Check access permissions
    const canAccess = this.accessService.canAccessProject(resultProjectId);
    if (!canAccess) return false;
    
    // RULE 4: Private projects only searchable from within themselves
    if (i.visibility === 'private' && resultProjectName !== currentProject) {
      return false;
    }
    
    // RULE 5: Respect crossProjectSearch setting
    if (!effectiveConfig.crossProjectSearch && resultProjectName !== currentProject) {
      return false;
    }
    
    return true;
  });
  
  // ... rest of search logic
}

UI Updates:

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

// Dynamic placeholder
[placeholder]="searchService.searchMode() === 'project-filter' ? 'Filter projects...' : 'Search documentation...'"

// Different search tips based on mode
@if (searchService.searchMode() === 'project-filter') {
  <div class="search-tips">
    <h3>Project Filter</h3>
    <p class="search-mode-description">Search to filter available projects by name, description, or tags.</p>
    <ul>
      <li>Try: "GoGrab Space", "Backend", "Architecture"</li>
      <li>Filter by type: "public", "private", "documentation"</li>
      <li>Use keyboard shortcuts: <kbd>โŒ˜K</kbd> or <kbd>Ctrl+K</kbd></li>
    </ul>
  </div>
} @else {
  <div class="search-tips">
    <h3>Search Tips</h3>
    <ul>
      <li>Search for specific features: "components", "services", "widgets"</li>
      <li>Find guides: "getting started", "build guide", "architecture"</li>
      <li>Look for libraries: "core-models", "theme", "ux-menu"</li>
      <li>Use keyboard shortcuts: <kbd>โŒ˜K</kbd> or <kbd>Ctrl+K</kbd> to search</li>
    </ul>
  </div>
}

// Hide project selector in project-filter mode
@if (searchService.allAvailableProjects().length > 1 && searchService.searchMode() === 'content-search') {
  <div class="project-selector">
    <!-- Project selection chips -->
  </div>
}

Benefits


โœ… Feature 3: Search Scoping Rules

Implementation

The search now properly respects these rules:

Rule 1: Current Project Scope

if (effectiveConfig.scopeToCurrentProject && !effectiveConfig.crossProjectSearch) {
  if (resultProjectName !== currentProject) return false;
}

When scopeToCurrentProject: true, only show content from the current project.

Rule 2: Access Control

const canAccess = this.accessService.canAccessProject(resultProjectId);
if (!canAccess) return false;

Use the existing AccessService to check permissions before showing results.

Rule 3: Private Project Isolation

if (i.visibility === 'private' && resultProjectName !== currentProject) {
  return false;
}

Critical: Private projects can ONLY be searched from within themselves. This protects sensitive documentation.

Rule 4: Cross-Project Search Setting

if (!effectiveConfig.crossProjectSearch && resultProjectName !== currentProject) {
  return false;
}

Respect the crossProjectSearch configuration flag per project.

Configuration Example

File: apps/docs-angular/src/assets/docs-folders-config.json

{
  "globalConfig": {
    "search": {
      "scopeToCurrentProject": true,
      "crossProjectSearch": false,
      "includePrivateInGlobalSearch": false,
      "maxResultsPerProject": 10,
      "hasAccessToPublic": true
    }
  },
  "projects": {
    "private": {
      "visibility": "private",
      "password": "test123"
    }
  }
}

Per-Project Config (if needed):

{
  "name": "some-project",
  "config": {
    "search": {
      "crossProjectSearch": true,
      "hasAccessToPublic": true
    }
  }
}

๐Ÿ”„ User Flows

Flow 1: Project Selection with Search

1. User navigates to /select-project
   โ†“
2. Side navigation shows: "Please select a project to view the navigation."
   โ†“
3. User presses โŒ˜K to search
   โ†“
4. Search modal opens with placeholder: "Filter projects..."
   โ†“
5. User types: "gograb"
   โ†“
6. Search filters projects:
   - "GoGrab Space" (score: 40)
   - "GoGrab Pulse" (score: 38)
   - "GoGrab Space Deck" (score: 35)
   โ†“
7. User clicks on "GoGrab Space"
   โ†“
8. Redirects back to /select-project with that project highlighted

Flow 2: Content Search Within Project

1. User is in "GoGrab Space" project at /
   โ†“
2. User presses โŒ˜K to search
   โ†“
3. Search modal opens with placeholder: "Search documentation..."
   โ†“
4. User types: "widgets"
   โ†“
5. Search returns results ONLY from GoGrab Space:
   - "Widgets Library" (page)
   - "Widget Components" (heading)
   - "Widget System Architecture" (page)
   โ†“
6. NO results from other projects shown (scopeToCurrentProject: true)

Flow 3: Global Search (if enabled)

1. User in "Public Docs" project
   โ†“
2. Public Docs has: crossProjectSearch: true, hasAccessToPublic: true
   โ†“
3. User searches: "authentication"
   โ†“
4. Results shown from:
   โœ… Public Docs (own project)
   โœ… Backend API (public project)
   โœ… Architecture (public project)
   โŒ Private Docs (blocked - private)
   โ†“
5. User clicks result from Backend API
   โ†“
6. Navigates to Backend API project at /authentication

Flow 4: Private Project Search

1. User in "Private" project (private documentation)
   โ†“
2. User searches: "sensitive data"
   โ†“
3. Results shown ONLY from:
   โœ… Private project (own project)
   โŒ All other projects (blocked - can't search private from outside)
   โ†“
4. Privacy protected!

๐Ÿ“Š Benefits Summary

User Experience

Security

Performance


๐Ÿงช Testing Checklist

Test 1: Side Navigation Message

Test 2: Project Filter Mode

Test 3: Content Search Mode

Test 4: Private Project Isolation

Test 5: Cross-Project Search


๐Ÿ“ Configuration Reference

Global Search Config

interface GlobalSearchConfig {
  scopeToCurrentProject: boolean;        // Default: true - Search only current project
  crossProjectSearch: boolean;           // Default: false - Allow cross-project results
  includePrivateInGlobalSearch: boolean; // Default: false - Include private in global search
  maxResultsPerProject: number;          // Default: 10 - Max results per project
  enableProjectSwitchingFromSearch: boolean; // Default: true - Allow project switch from search
}

Per-Project Config

{
  "name": "project-name",
  "config": {
    "search": {
      "scopeToCurrentProject": false,  // Override global
      "crossProjectSearch": true,      // Allow searching other projects
      "hasAccessToPublic": true        // Can access public projects
    }
  }
}

๐ŸŽฏ Summary

What Was Implemented:

  1. โœ… Side navigation message on /select-project route
  2. โœ… Dual-mode search (project filter vs content search)
  3. โœ… Proper search scoping with 5 rules
  4. โœ… Private project isolation
  5. โœ… Configuration-driven behavior
  6. โœ… Beautiful UI with contextual feedback

Files Changed:

Status: โœ… COMPLETE - READY FOR TESTING


Last Updated: October 29, 2025
Impact: High - Significantly improves UX on project selection and search behavior