feat: miller column browser, offline cache, search, breadcrumbs

- Dynamic miller column sizing with flexbox layout
- Root column (160px), middle slices (48px), active column (flex)
- Auto-scroll browser to rightmost column on navigation
- Offline indicator UI and IndexedDB cache integration
- /api/documents endpoint for client-side document caching
- Breadcrumb navigation in document content area
- FTS5 search with SQLite virtual table
- Client-side Mermaid and KaTeX rendering via CDN
- Deep sample content (advanced-topics/raft-deep-dive)
- Border removal (only search button keeps radius)
- Full viewport layout with proper borders between sidebar/content
This commit is contained in:
2026-04-30 11:55:03 -04:00
parent e45eeeb600
commit 780ff3a02c
40 changed files with 2986 additions and 190 deletions

View File

@@ -271,6 +271,47 @@ func (r *Repository) GetAttachment(ctx context.Context, hash string) (*Attachmen
return &record, nil
}
type SearchResult struct {
Path string `json:"path"`
Title string `json:"title"`
}
func (r *Repository) SearchDocuments(ctx context.Context, query string) ([]SearchResult, error) {
rows, err := r.db.QueryContext(ctx, `
SELECT d.path, d.title
FROM document_search ds
JOIN documents d ON d.rowid = ds.rowid
WHERE document_search MATCH ?
ORDER BY rank
LIMIT 50
`, query)
if err != nil {
return nil, fmt.Errorf("search documents: %w", err)
}
defer rows.Close()
var results []SearchResult
for rows.Next() {
var result SearchResult
if err := rows.Scan(&result.Path, &result.Title); err != nil {
return nil, fmt.Errorf("scan search result: %w", err)
}
results = append(results, result)
}
return results, rows.Err()
}
func (r *Repository) UpdateSearchContent(ctx context.Context, path string, content string) error {
if _, err := r.db.ExecContext(ctx, `
UPDATE document_search SET content = ?
WHERE rowid = (SELECT rowid FROM documents WHERE path = ?)
`, content, path); err != nil {
return fmt.Errorf("update search content: %w", err)
}
return nil
}
func (r *Repository) Ping(ctx context.Context) error {
return r.db.PingContext(ctx)
}