Search Frontend Module
Search Frontend Module
Svelte 5 frontend module providing the search bar, results panel, reactive store, API client, and ProseMirror search highlight plugin for unified full-text search across content and glossary entries.
Responsibilities
- Search input bar in the header toolbar with debounced queries, keyboard shortcut, and loading state
- Results panel registered in PanelContainer with grouped display (content, glossary), keyboard navigation, and pagination
- Reactive state management via searchStore bridging SearchBar and SearchResults
- REST API client for the unified
/api/projects/{slug}/searchendpoint - ProseMirror searchHighlight plugin for in-editor match highlighting
- Auto-open of the search results panel when a search is initiated
Public API Surface
| Export | Kind | Description |
|---|---|---|
| SearchBar | component | Header search input with debounce, shortcut, and loading spinner |
| SearchResults | component | Panel displaying grouped results with keyboard navigation |
| SearchResultItem | component | Presentational row for a single search result |
| searchStore | store | Reactive state and API orchestration |
| searchApi | module | HTTP client for the search endpoint |
| searchHighlight plugin | plugin | ProseMirror visual-only highlight decorations |
Internal Structure -- Component Hierarchy
Header toolbar
+-- SearchBar.svelte (input, debounce, Ctrl/Cmd+K shortcut)
PanelContainer (right panel, id: 'search-results')
+-- SearchResults.svelte (panel component, reads searchStore)
+-- SearchResultItem.svelte (per result row, content or glossary)
Stores / API
+-- search.ts (searchStore — writable store + methods)
+-- search.ts (api) (searchApi — REST client)
ProseMirror plugin
+-- searchHighlight.ts (createSearchHighlightPlugin factory)
The SearchBar lives in the header and writes to the searchStore. The SearchResults panel reads from the searchStore. The searchHighlight plugin lives inside ProseMirrorEditor and reacts to $searchStore.query changes via a $effect in TextEditor.svelte.
Panel Registration
register({
id: 'search-results',
name: 'Search',
icon: 'Search',
tier: 'core',
alwaysEnabled: false,
defaultPosition: 'right',
allowedPositions: ['left', 'right', 'bottom', 'floating'],
defaultEnabled: true,
order: 10,
source: 'builtin',
component: () => import('$lib/components/search/SearchResults.svelte')
});
The search results panel is a Core-tier built-in panel. Unlike the glossary panel, alwaysEnabled is false -- the panel can be disabled by users. Default position is the right sidebar. Load order 10 places it after the glossary panel (order 5). The ensureSearchPanelVisible() helper in the search store programmatically opens and activates this panel when a search is triggered.
Dependencies
| Dependency | Type | Purpose |
|---|---|---|
| searchStore | internal | Reactive state management |
| searchApi | internal | REST API client |
| searchHighlightKey | internal | ProseMirror plugin key |
| moduleEventBus | internal | content:selected event dispatch |
| glossaryStore | internal | glossaryStore.navigateToEntry() for glossary result clicks |
| notificationStore | internal | Error notifications |
| userPreferences | internal | Panel layout management for auto-open |
| contentApi | internal | Fetch full Content object by slug path |
| ProseMirror | external | Editor framework (Plugin, Decoration, PluginKey) |
| Lucide icons | external | Search, FileText, BookOpen, Loader2, X, AlertCircle, Info |
| svelte/store | external | writable, get |
Security
- Search snippets containing
<mark>tags are generated by the backend FTS5snippet()function, not user input. The{@html}rendering in SearchResultItem is safe within this trust boundary. - The API client passes query strings through
URLSearchParamsto prevent injection. - No authentication layer exists yet (Core license, local-only). The
searchProject()function is a pass-through to the backend REST endpoint.
Performance
- Debounce: SearchBar applies a 300ms debounce to avoid excessive API calls during typing. Enter bypasses the debounce for instant results.
- Minimum query length: Searches require at least 2 characters to avoid overly broad queries.
- Pagination: Results are loaded in pages of 20 (DEFAULT_LIMIT). The "Load more" button appends the next page without re-fetching existing results.
- Decoration mapping: The searchHighlight plugin maps decorations through non-document-changing transactions (cursor movement, selection changes) instead of rebuilding the entire decoration set, avoiding per-keystroke full scans.
- Document change re-scan: When a document changes (content swap via search result click), the plugin re-scans the new document with the existing query string.