Migrate website to Starlight with full-text resource search (#883)

* Add search functionality to Learning Hub index page

Add a client-side search bar that filters articles by title, description,
and tags. Sections with no matching results are hidden automatically.
Uses the existing .search-bar CSS pattern from the cookbook page.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: remove deprecated layouts, theme script, and learning-hub config

Phase 5 cleanup of Starlight migration:
- Delete BaseLayout.astro (replaced by StarlightPage)
- Delete ArticleLayout.astro (replaced by Starlight docs rendering)
- Delete theme.ts (Starlight has built-in theme toggle)
- Delete src/config/learning-hub.ts (sidebar order now in astro.config.mjs)
- Replace learning-hub glob collection with Starlight docs collection in content.config.ts
- Keep search.ts (still used by homepage and all resource page scripts)

Build verified: 23 pages, no errors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Migrate website to Starlight with full-text resource search

- Replace bespoke Astro layouts with Starlight integration
  - Homepage and resource pages use StarlightPage wrapper
  - Learning Hub articles rendered via Starlight docs collection
  - Starlight provides search, theme toggle, sidebar, ToC, a11y

- Add custom Pagefind integration for resource search
  - All 614 agents/skills/instructions/hooks/workflows/plugins
    indexed as custom records with deep-link URLs
  - Type filter pills (horizontal pill toggles) above results
  - Search results link directly to resource modals via #file= hash

- Move global.css to src/styles/ for Vite processing
  - Scope CSS reset to #main-content to avoid Starlight conflicts
  - Full-width page gradient via body:has(#main-content)
  - Light/dark theme support with Starlight gray scale inversion

- Delete old layouts (BaseLayout, ArticleLayout), theme.ts, config

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address PR review feedback

- Fix pagefind-resources.ts header comment (pagefind:true not false)
- Remove unused base variable in cookbook/index.astro
- Replace hardcoded /awesome-copilot/ paths with relative links in index.md
- Delete stale public/styles/global.css (source of truth is src/styles/)
- Replace fragile getBasePath() with Astro config base in pagefind integration
- Document pagefind:true reasoning in astro.config.mjs
- Use proper visually-hidden pattern + :focus-visible ring for filter pills
- Remove dead header/nav/theme CSS from global.css (~160 lines)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Aaron Powell
2026-03-05 21:50:44 +11:00
committed by GitHub
parent 8fedf95507
commit 40fd1a6c72
50 changed files with 1891 additions and 888 deletions

View File

@@ -0,0 +1,142 @@
/**
* Custom Pagefind integration that extends Starlight's search index
* with resource records (agents, skills, instructions, hooks, workflows, plugins).
*
* Starlight's pagefind is enabled (pagefind: true) so the search UI renders,
* but this integration runs AFTER Starlight's and overwrites the index with
* HTML pages + custom resource records combined.
*/
import type { AstroIntegration } from "astro";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import * as pagefind from "pagefind";
interface SearchRecord {
type: string;
id: string;
title: string;
description: string;
path: string;
tags?: string[];
searchText: string;
}
const TYPE_LABELS: Record<string, string> = {
agent: "Agent",
instruction: "Instruction",
skill: "Skill",
hook: "Hook",
workflow: "Workflow",
plugin: "Plugin",
tool: "Tool",
};
const TYPE_PAGES: Record<string, string> = {
agent: "/agents/",
instruction: "/instructions/",
skill: "/skills/",
hook: "/hooks/",
workflow: "/workflows/",
plugin: "/plugins/",
tool: "/tools/",
};
export default function pagefindResources(): AstroIntegration {
let siteBase = "/";
return {
name: "pagefind-resources",
hooks: {
"astro:config:done": ({ config }) => {
siteBase = config.base;
},
"astro:build:done": async ({ dir, logger }) => {
const log = logger.fork("pagefind-resources");
const now = performance.now();
try {
log.info("Building search index with Pagefind + resource records...");
const response = await pagefind.createIndex();
if (response.errors.length > 0) {
for (const err of response.errors) log.error(err);
throw new Error("Failed to create Pagefind index");
}
const { index } = response;
// Index all built HTML pages (same as Starlight's default)
const indexResult = await index.addDirectory({
path: fileURLToPath(dir),
});
if (indexResult.errors.length > 0) {
for (const err of indexResult.errors) log.error(err);
throw new Error("Failed to index HTML directory");
}
log.info(`Indexed ${indexResult.page_count} HTML pages.`);
// Read and index resource records from search-index.json
const searchIndexPath = fileURLToPath(
new URL("./data/search-index.json", dir)
);
let records: SearchRecord[];
try {
records = JSON.parse(readFileSync(searchIndexPath, "utf-8"));
} catch {
log.warn("Could not read search-index.json, skipping resource indexing.");
records = [];
}
// Use the base path from Astro config (e.g. "/awesome-copilot/")
const base = siteBase.endsWith("/") ? siteBase : `${siteBase}/`;
let added = 0;
for (const record of records) {
const typePage = TYPE_PAGES[record.type];
if (!typePage) continue;
const url = `${base}${typePage.slice(1)}#file=${encodeURIComponent(record.path)}`;
const typeLabel = TYPE_LABELS[record.type] || record.type;
const addResult = await index.addCustomRecord({
url,
content: record.searchText || `${record.title} ${record.description}`,
language: "en",
meta: {
title: `${record.title}${typeLabel}`,
},
filters: {
type: [record.type],
},
});
if (addResult.errors.length > 0) {
for (const err of addResult.errors) log.warn(`Record ${record.id}: ${err}`);
} else {
added++;
}
}
log.info(`Added ${added} resource records.`);
// Write the combined index
const writeResult = await index.writeFiles({
outputPath: fileURLToPath(new URL("./pagefind/", dir)),
});
if (writeResult.errors.length > 0) {
for (const err of writeResult.errors) log.error(err);
throw new Error("Failed to write Pagefind files");
}
const elapsed = performance.now() - now;
log.info(
`Search index built in ${elapsed < 750 ? `${Math.round(elapsed)}ms` : `${(elapsed / 1000).toFixed(2)}s`}.`
);
} catch (cause) {
throw new Error("Failed to build Pagefind search index.", { cause });
} finally {
await pagefind.close();
}
},
},
};
}