8.6 KiB
		
	
	
	
	
	
	
	
			
		
		
	
	
			8.6 KiB
		
	
	
	
	
	
	
	
Search Migration Guide
Overview
This guide helps you migrate from the old SearchEvaluatorService to the new SearchOrchestratorService.
Why Migrate?
The new orchestrator provides:
- ✅ Correct filtering: All field operators actually work
- ✅ Better highlighting: Precise ranges instead of text matching
- ✅ More features: Context lines, max results, match ranges
- ✅ Better performance: Pre-calculated ranges, no rescanning
Quick Migration
Before (Old)
import { SearchEvaluatorService } from './core/search/search-evaluator.service';
constructor(private evaluator: SearchEvaluatorService) {}
search(query: string) {
  const results = this.evaluator.search(query, {
    caseSensitive: false
  });
  
  // Results don't include ranges
  results.forEach(result => {
    result.matches.forEach(match => {
      // match.startOffset and match.endOffset are basic
    });
  });
}
After (New)
import { SearchOrchestratorService } from './core/search/search-orchestrator.service';
constructor(private orchestrator: SearchOrchestratorService) {}
search(query: string) {
  const results = this.orchestrator.execute(query, {
    caseSensitive: false,
    contextLines: 5,      // NEW: Adjustable context
    maxResults: 100       // NEW: Limit results
  });
  
  // Results include precise ranges
  results.forEach(result => {
    result.matches.forEach(match => {
      // match.ranges: MatchRange[] with start/end/line/context
    });
  });
}
Step-by-Step Migration
1. Update Imports
// OLD
import { SearchEvaluatorService, SearchResult } from './core/search/search-evaluator.service';
// NEW
import { SearchOrchestratorService, SearchResult } from './core/search/search-orchestrator.service';
2. Update Injection
// OLD
constructor(private evaluator: SearchEvaluatorService) {}
// NEW
constructor(private orchestrator: SearchOrchestratorService) {}
3. Update Method Calls
// OLD
const results = this.evaluator.search(query, options);
// NEW
const results = this.orchestrator.execute(query, options);
4. Update Result Handling
// OLD
results.forEach(result => {
  const { noteId, matches, score } = result;
  // matches[].startOffset, matches[].endOffset
});
// NEW
results.forEach(result => {
  const { noteId, matches, score, allRanges } = result;
  // matches[].ranges: MatchRange[]
  // allRanges: MatchRange[] (all ranges in note)
});
Highlighting Migration
Before (Manual)
highlightMatch(context: string, matchText: string): string {
  const regex = new RegExp(`(${matchText})`, 'gi');
  return context.replace(regex, '<mark>$1</mark>');
}
After (Service)
import { SearchHighlighterService } from './core/search/search-highlighter.service';
constructor(private highlighter: SearchHighlighterService) {}
highlightMatch(match: SearchMatch): string {
  // Use ranges for precise highlighting
  if (match.ranges && match.ranges.length > 0) {
    return this.highlighter.highlightWithRanges(match.context, match.ranges);
  }
  
  // Fallback to text-based
  return this.highlighter.highlightMatches(match.context, [match.text], false);
}
Preferences Migration
Before (Manual State)
export class MyComponent {
  collapseResults = false;
  showMoreContext = false;
  
  // Manual localStorage
  ngOnInit() {
    const saved = localStorage.getItem('my-prefs');
    if (saved) {
      const prefs = JSON.parse(saved);
      this.collapseResults = prefs.collapse;
    }
  }
  
  savePrefs() {
    localStorage.setItem('my-prefs', JSON.stringify({
      collapse: this.collapseResults
    }));
  }
}
After (Service)
import { SearchPreferencesService } from './core/search/search-preferences.service';
export class MyComponent {
  constructor(private preferences: SearchPreferencesService) {}
  
  collapseResults = false;
  showMoreContext = false;
  
  ngOnInit() {
    // Auto-load preferences
    const prefs = this.preferences.getPreferences('my-context');
    this.collapseResults = prefs.collapseResults;
    this.showMoreContext = prefs.showMoreContext;
  }
  
  onToggleCollapse() {
    // Auto-save preferences
    this.preferences.updatePreferences('my-context', {
      collapseResults: this.collapseResults
    });
  }
}
Component Migration
SearchResultsComponent
Before
<app-search-results
  [results]="results()"
  (noteOpen)="onNoteOpen($event)"
/>
After
<app-search-results
  [results]="results()"
  [collapseAll]="collapseResults"
  [showMoreContext]="showMoreContext"
  [contextLines]="contextLines()"
  (noteOpen)="onNoteOpen($event)"
/>
SearchPanelComponent
No changes needed! The component now includes toggles automatically.
<app-search-panel
  placeholder="Search in vault..."
  context="vault"
  (noteOpen)="openNote($event)"
/>
Common Patterns
Pattern 1: Search with Context
// OLD: Fixed context
const results = this.evaluator.search(query);
// NEW: Adjustable context
const results = this.orchestrator.execute(query, {
  contextLines: this.showMoreContext ? 5 : 2
});
Pattern 2: Limit Results
// OLD: Manual slicing
const results = this.evaluator.search(query).slice(0, 100);
// NEW: Built-in limit
const results = this.orchestrator.execute(query, {
  maxResults: 100
});
Pattern 3: Highlighting
// OLD: Manual regex
const highlighted = text.replace(
  new RegExp(`(${term})`, 'gi'),
  '<mark>$1</mark>'
);
// NEW: Service with XSS protection
const highlighted = this.highlighter.highlightMatches(
  text,
  [term],
  caseSensitive
);
Testing Migration
Before
it('should search', () => {
  const results = evaluator.search('test');
  expect(results.length).toBeGreaterThan(0);
});
After
it('should search with orchestrator', () => {
  const results = orchestrator.execute('test');
  expect(results.length).toBeGreaterThan(0);
  expect(results[0].allRanges).toBeDefined();
  expect(results[0].matches[0].ranges).toBeDefined();
});
Backward Compatibility
The old SearchEvaluatorService still works as a wrapper:
// This still works (delegates to orchestrator)
const results = this.evaluator.search(query, options);
// But you won't get the new features:
// - No contextLines option
// - No maxResults option
// - No allRanges in results
// - matches[].ranges are converted from allRanges[0]
Breaking Changes
None! All existing code continues to work.
Deprecation Timeline
- Now: SearchEvaluatorServicemarked as@deprecated
- v2.0: SearchEvaluatorServicewill be removed
- Migration window: ~6 months
Checklist
- Update imports to SearchOrchestratorService
- Update injection in constructors
- Replace .search()with.execute()
- Add SearchHighlighterServicefor highlighting
- Add SearchPreferencesServicefor preferences
- Update component inputs (collapseAll, showMoreContext, contextLines)
- Update tests to check for ranges
- Remove manual localStorage code
- Test all search scenarios
Need Help?
- Documentation: src/core/search/README.md
- Examples: src/components/search-panel/search-panel.component.ts
- Tests: src/core/search/*.spec.ts
- Issues: Create a GitHub issue with [search]prefix
FAQ
Q: Do I have to migrate immediately?
A: No, the old service still works. But you won't get the bug fixes and new features.
Q: Will my existing code break?
A: No, backward compatibility is maintained.
Q: What if I only want highlighting?
A: You can use SearchHighlighterService independently:
import { SearchHighlighterService } from './core/search/search-highlighter.service';
constructor(private highlighter: SearchHighlighterService) {}
highlight(text: string, terms: string[]) {
  return this.highlighter.highlightMatches(text, terms, false);
}
Q: What if I only want preferences?
A: You can use SearchPreferencesService independently:
import { SearchPreferencesService } from './core/search/search-preferences.service';
constructor(private preferences: SearchPreferencesService) {}
loadPrefs() {
  return this.preferences.getPreferences('my-context');
}
Q: Can I mix old and new?
A: Yes, but not recommended. Stick to one approach per component.
Examples
See complete examples in:
- src/components/search-panel/search-panel.component.ts
- src/components/search-results/search-results.component.ts
- src/core/search/*.spec.ts