# Search Configuration System Specification ## Overview The search configuration system for v1.1.1 provides operators with control over search functionality, including the ability to disable it entirely for sites that don't need it, configure title extraction parameters, and enhance result presentation. ## Requirements ### Functional Requirements 1. **Search Toggle** - Ability to completely disable search functionality - When disabled, search UI elements should be hidden - Search endpoints should return appropriate messages - Database FTS5 tables can be skipped if search disabled from start 2. **Title Length Configuration** - Configure maximum title extraction length (currently hardcoded at 100) - Apply to both new and existing notes during search - Ensure truncation doesn't break words mid-character - Add ellipsis (...) for truncated titles 3. **Search Result Enhancement** - Highlight search terms in results - Show relevance score for each result - Configurable highlight CSS class - Preserve HTML safety (no XSS via highlights) 4. **Graceful FTS5 Degradation** - Detect FTS5 availability at startup - Fall back to LIKE queries if unavailable - Show appropriate warnings to operators - Document SQLite compilation requirements ### Non-Functional Requirements 1. **Performance** - Configuration checks must not impact request latency (<1ms) - Search highlighting must not slow results >10% - Graceful degradation should work within 2x time of FTS5 2. **Compatibility** - All existing deployments continue working without configuration - Default values match current behavior exactly - No database migrations required 3. **Security** - Search term highlighting must be XSS-safe - Configuration values must be validated - No sensitive data in configuration ## Design ### Configuration Schema ```python # Environment variables with defaults STARPUNK_SEARCH_ENABLED = True STARPUNK_SEARCH_TITLE_LENGTH = 100 STARPUNK_SEARCH_HIGHLIGHT_CLASS = "highlight" STARPUNK_SEARCH_MIN_SCORE = 0.0 STARPUNK_SEARCH_HIGHLIGHT_ENABLED = True STARPUNK_SEARCH_SCORE_DISPLAY = True ``` ### Component Architecture ``` ┌─────────────────────────────────────┐ │ Configuration Layer │ ├─────────────────────────────────────┤ │ Search Controller │ │ ┌─────────────┬─────────────┐ │ │ │ FTS5 Engine │ LIKE Engine │ │ │ └─────────────┴─────────────┘ │ ├─────────────────────────────────────┤ │ Result Processor │ │ • Highlighting │ │ • Scoring │ │ • Title Extraction │ └─────────────────────────────────────┘ ``` ### Search Disabling Flow ```python # In search module def search_notes(query: str) -> List[Note]: if not config.SEARCH_ENABLED: return SearchResults( results=[], message="Search is disabled on this instance", enabled=False ) # Normal search flow return perform_search(query) # In templates {% if config.SEARCH_ENABLED %}
{% endif %} ``` ### Title Extraction Logic ```python def extract_title(content: str, max_length: int = None) -> str: """Extract title from note content""" max_length = max_length or config.SEARCH_TITLE_LENGTH # Try to extract first line first_line = content.split('\n')[0].strip() # Remove markdown formatting title = strip_markdown(first_line) # Truncate if needed if len(title) > max_length: # Find last word boundary before limit truncated = title[:max_length].rsplit(' ', 1)[0] return truncated + '...' return title ``` ### Search Highlighting Implementation ```python import html from markupsafe import Markup def highlight_terms(text: str, terms: List[str]) -> Markup: """Highlight search terms in text safely""" if not config.SEARCH_HIGHLIGHT_ENABLED: return Markup(html.escape(text)) # Escape HTML first safe_text = html.escape(text) # Highlight each term (case-insensitive) for term in terms: pattern = re.compile( re.escape(html.escape(term)), re.IGNORECASE ) replacement = f'\g<0>' safe_text = pattern.sub(replacement, safe_text) return Markup(safe_text) ``` ### FTS5 Detection and Fallback ```python def check_fts5_support() -> bool: """Check if SQLite has FTS5 support""" try: conn = get_db_connection() conn.execute("CREATE VIRTUAL TABLE test_fts USING fts5(content)") conn.execute("DROP TABLE test_fts") return True except sqlite3.OperationalError: return False class SearchEngine: def __init__(self): self.has_fts5 = check_fts5_support() if not self.has_fts5: logger.warning( "FTS5 not available, using fallback search. " "For better performance, compile SQLite with FTS5 support." ) def search(self, query: str) -> List[Result]: if self.has_fts5: return self._search_fts5(query) else: return self._search_fallback(query) def _search_fallback(self, query: str) -> List[Result]: """LIKE-based search fallback""" # Note: No relevance scoring available sql = """ SELECT id, content, created_at FROM notes WHERE content LIKE ? ORDER BY created_at DESC LIMIT 50 """ return db.execute(sql, [f'%{query}%']) ``` ### Relevance Score Display ```python @dataclass class SearchResult: note_id: int content: str title: str score: float # Relevance score from FTS5 highlights: str # Snippet with highlights def format_score(score: float) -> str: """Format relevance score for display""" if not config.SEARCH_SCORE_DISPLAY: return "" # Normalize to 0-100 scale normalized = min(100, max(0, abs(score) * 10)) return f"{normalized:.0f}% match" ``` ## Testing Strategy ### Unit Tests 1. Configuration loading with various values 2. Title extraction with edge cases 3. Search term highlighting with XSS attempts 4. FTS5 detection logic 5. Fallback search functionality ### Integration Tests 1. Search with configuration disabled 2. End-to-end search with highlighting 3. Performance comparison FTS5 vs fallback 4. UI elements hidden when search disabled ### Configuration Test Matrix | SEARCH_ENABLED | FTS5 Available | Expected Behavior | |----------------|----------------|-------------------| | true | true | Full search with FTS5 | | true | false | Fallback LIKE search | | false | true | Search disabled | | false | false | Search disabled | ## User Interface Changes ### Search Results Template ```html