Search Manifest Format Fix

Fixed search index building to work with new manifest structure format

๐Ÿ”ง Search Manifest Format Fix

Date: October 29, 2025
Time: Afternoon
Status: โœ… COMPLETE
Purpose: Fix search index building to work with actual manifest structure


๐Ÿ“‹ Problem

The search index was empty (๐Ÿ” Search index length: 0) because:

  1. setupSearchStream() not running on init: The effect only triggered on language changes, not initial load
  2. Wrong manifest format: Code expected manifest.public and manifest.projects, but actual manifest has manifest.structure array
  3. Hardcoded language: loadFileContent was hardcoded to 'en' instead of using actual language

โœ… What Was Fixed

1. Initialize Search Index on Service Start

Before:

private setupSearchStream(): void {
  // Only ran when language changed via effect()
  effect(() => {
    const currentLang = this.documentationService.currentLanguage();
    // ... build index
  });
}

After:

private setupSearchStream(): void {
  // ๐ŸŽฏ IMMEDIATE: Build index on service initialization
  const initialLang = this.documentationService.currentLanguage();
  void (async () => {
    const manifest = await this.structureSyncService.loadManifest(false, initialLang);
    if (manifest) {
      await this.buildSearchIndexFromManifest(manifest, initialLang);
      console.log(`Index built with ${this.searchIndex.length} items`);
    }
  })();

  // ๐Ÿ”„ REACTIVE: Also rebuild when language changes
  effect(() => {
    const currentLang = this.documentationService.currentLanguage();
    // ... rebuild index
  });
}

2. Handle New Manifest Format

Actual Manifest Structure:

{
  "version": "1.0.0",
  "generated": "2025-10-29T15:41:17.768Z",
  "structure": [
    {
      "type": "directory",
      "name": "english",
      "language": "en",
      "isLanguageRoot": true,
      "items": [
        {
          "type": "directory",
          "name": "GoGrab-Space",
          "items": [
            {
              "type": "file",
              "name": "index.md",
              "path": "english/GoGrab-Space/index.md",
              "title": "GoGrab Space",
              "hasContent": true
            }
          ]
        }
      ]
    }
  ]
}

Added New Methods:

private async buildIndexFromStructureArray(structure: any[], language: string): Promise<void> {
  for (const item of structure) {
    if (item.type === 'directory' && item.isLanguageRoot && item.language === language) {
      // Process all items in this language directory
      if (item.items && Array.isArray(item.items)) {
        await this.indexStructureItems(item.items, language);
      }
    }
  }
}

private async indexStructureItems(items: any[], language: string, maxFiles: number = 100): Promise<void> {
  for (const item of items) {
    if (item.type === 'directory' && item.items) {
      // Recursively index directory items
      await this.indexStructureItems(item.items, language, maxFiles - filesIndexed);
    } else if (item.type === 'file' && item.hasContent) {
      // Index this file
      const content = await this.loadFileContent(item.path, language);
      if (content) {
        const projectName = this.extractProjectNameFromPath(item.path);
        this.searchIndex.push({
          path: `/${item.path.replace(/\.(md|mdc)$/, '')}`,
          title: item.title || item.cleanName || item.name,
          content: this.stripMarkdown(content),
          headings: this.extractHeadings(content),
          section: projectName,
          language: item.language || language,
          visibility: item.path.includes('/private/') ? 'private' : 'public',
          projectName,
          tags: item.frontMatter?.tags || [],
          searchablePhrases: item.frontMatter?.searchablePhrases || []
        });
      }
    }
  }
}

3. Extract Project Name from Path

private extractProjectNameFromPath(path: string): string {
  // Remove language prefix (english/ or spanish/)
  const withoutLang = path.replace(/^(english|spanish)\//, '');
  
  // Get first segment as project name
  const segments = withoutLang.split('/');
  return segments[0] || 'public';
}

Examples:

4. Fixed loadFileContent Language Parameter

Before:

private async loadFileContent(manifestPath: string): Promise<string> {
  const content = await this.markdownService.loadContent(manifestPath, 'en').toPromise(); // Hardcoded!
}

After:

private async loadFileContent(manifestPath: string, language: string = 'en'): Promise<string> {
  const content = await this.markdownService.loadContent(manifestPath, language).toPromise();
}

๐Ÿ” Console Output (Expected)

When you refresh the app, you should now see:

๐Ÿ” SearchService: Initializing...
๐Ÿ” setupSearchStream: Setting up search index building...
๐Ÿ” setupSearchStream: Building initial index for language: en
๐Ÿ” buildSearchIndexFromManifest: Starting...
๐Ÿ” buildSearchIndexFromManifest: Manifest structure: {hasStructure: true, hasPublic: false, hasProjects: false, manifestKeys: ['version', 'generated', 'basePath', 'structure']}
๐Ÿ” buildSearchIndexFromManifest: Using new structure format
๐Ÿ” buildIndexFromStructureArray: Processing 2 top-level items
๐Ÿ” buildIndexFromStructureArray: Found language root: english (en)
๐Ÿ” indexStructureItems: Indexed 10 files so far...
๐Ÿ” indexStructureItems: Indexed 20 files so far...
...
๐Ÿ” buildIndexFromStructureArray: Indexed 100 total items
๐Ÿ” buildSearchIndexFromManifest: Complete! Index now has 100 items
๐Ÿ” setupSearchStream: Initial index built with 100 items

Then when you search:

๐Ÿ” CONTENT SEARCH STARTING
๐Ÿ” Search index length: 100  โ† NOT ZERO!
๐Ÿ” Search index items: [{title: "GoGrab Space", project: "GoGrab-Space", ...}, ...]

๐Ÿงช Testing

Test 1: Search Index Builds on Load

  1. Open browser console
  2. Refresh the page
  3. Look for "setupSearchStream: Initial index built with X items"
  4. X should be > 0 (probably 100, the limit)

Test 2: Search Works

  1. Press โŒ˜K to open search
  2. Type "space" or any term
  3. Should see actual results from indexed files

Test 3: Project Scoping Works

  1. Navigate to GoGrab-Space project
  2. Search for "widgets"
  3. Should see results from GoGrab-Space (and public if configured)
  4. Should NOT see results from GoGrab-Pulse

Test 4: Global Search Toggle Works

  1. Click the "Local/Global" toggle
  2. Search again
  3. Should now see results from multiple projects

๐Ÿ“Š Summary

What Changed:

  1. โœ… setupSearchStream() now builds index immediately on init
  2. โœ… Added support for new manifest structure array format
  3. โœ… Added buildIndexFromStructureArray() method
  4. โœ… Added indexStructureItems() recursive method
  5. โœ… Added extractProjectNameFromPath() helper
  6. โœ… Fixed loadFileContent to use actual language parameter
  7. โœ… Comprehensive console logging for debugging

Files Changed:

Result:


Last Updated: October 29, 2025
Status: โœ… COMPLETE - TEST NOW


๐ŸŽฏ Next Steps

  1. Test the search - Refresh and try searching
  2. Check console - Verify index builds successfully
  3. Adjust maxFiles - If needed, increase from 100 to index more files
  4. Monitor performance - Watch for slow indexing

The search should now work exactly as configured! ๐Ÿš€