Search consolidation and Alpine cleanup
PublishedOne search, every collection
Consolidated the site search into a single component that indexes every public collection: blog, projects, gallery, grimoire, help, and changelog. Previously the global search FAB only found blog posts, even though it rendered on every page; a separate blog-page-only modal duplicated the work and depended on Alpine.js, which was removed from the dependency list during the March 2026 site audit.
What changed
src/components/global/Search.astronow collects entries from all six public collections at build time, normalizes them into a single shape (type,title,description,href, optionaltags,category,date), and runs a Fuse.js index across them. Draft entries are filtered per each collection’s draft convention (draft: truefor blog and grimoire,status: "draft"for gallery; projects, help, and changelog have no draft flag and always include all entries).src/components/blog/BlogSearch.astrodeleted. It had been unimported and its Alpine directives were inert since Alpine was removed from the dependency list.
Security and accessibility fixes
While the file was open, two issues from the site audit were closed:
- The result list previously rendered via
searchResults.innerHTML = …with interpolated authored content. That created an XSS surface as soon as the index expanded beyondd00d-authored markdown. Rendering now usesdocument.createElement+textContentfor every visible string — authored content cannot become markup. - The search-input label had
for="email", which never matched the input’sid="searchInput". The association is now correct and screen readers will announce the input when the label is focused.
What this closes
This completes the Alpine.js cleanup loop opened by the March 2026 site audit (Alpine package removed, but BlogSearch.astro was missed) and resolves the highest-leverage item in the May 2026 site audit’s Phase 3a.