feat(library): offline search via Pagefind #87

Merged
manlycucumber merged 1 commit from feat/library-search into develop 2026-07-11 00:42:33 +00:00
manlycucumber commented 2026-07-11 00:41:26 +00:00 (Migrated from github.com)

The Library — offline search (v0.3.0 PR3)

Static, offline, in-browser search over the prerendered Library, powered by Pagefind. No server, no
runtime network — the build indexes the HTML and all querying happens client-side over that index.

The gate came first

Before any UI, the PR's real risk was proven empirically: Pagefind's binary is vendored, not
postinstall-fetched
— it ships as ordinary platform packages (@pagefind/<platform>, an
optionalDependencies set with an empty scripts block), so pnpm install --frozen-lockfile resolves
it from the lockfile with zero network, and the indexer runs fully offline. The lockfile records every
platform, so CI's Linux runner gets @pagefind/linux-x64.

What shipped

  • Indexing folded into build (vite build && pnpm run index:search), so the guards and the offline
    smoke all see build/pagefind/**. scripts/index-search.mjs drives Pagefind's Node API, then:
    • asserts pagefind-entry.json reports ≥ 1 record (a zero-record index means the
      data-pagefind-body wrapper regressed → search would be silently empty), and
    • prunes Pagefind's default UI (pagefind-ui.*, -modular-ui.*, -component-ui.*, the highlight
      helper): we ship our own UI, and one of those CSS files embeds bug-tracker URLs in a comment that
      would (correctly) trip guard:external. Pruning keeps the offline invariant intact and trims the
      bundle.
  • One record per topic, by construction — only the default tier's prose is in the prerendered DOM and
    only the article body carries data-pagefind-body, so home/category/error pages are ignored with no
    data-pagefind-ignore choreography.
  • Our own tokenised UISearchPanel.svelte (on LibraryHome, so it appears both standalone and
    in-window). It loads the runtime with a runtime dynamic import(/* @vite-ignore */ .../pagefind.js)
    browser-only, absent under vite dev, and degrades to "unavailable" if the load fails. Results are real
    /library/** links, so the desktop's delegated intercept opens them in-window while standalone / no-JS
    follow the route. pagefind.ts holds the cached loader + a pure, unit-tested toHit() that re-applies
    base to Pagefind's root-relative URLs.

Verification

  • GATE: proven that pnpm install --frozen-lockfile yields a working indexer offline (no install
    script; binary from platform tarballs; runs and emits the bundle).
  • Browser-confirmed: query → live offline results, real /library links, <mark> highlight resolving to
    the gold token (opaque), empty-state, zero console errors; in-window intercept preserves other windows.
  • Full gauntlet green: build + all four guards (incl. new offline-smoke search assertions), svelte-check
    0/0/0, 62 unit (+3 toHit), 16 e2e (+4: standalone search, in-window intercept, no-results, a11y).
  • Adversarial multi-agent review (5 lenses → per-finding verify).

Architecture recorded in docs/architecture/library.md. Closes the PR3 item on milestone v0.3.0.

## The Library — offline search (v0.3.0 PR3) Static, offline, in-browser search over the prerendered Library, powered by Pagefind. No server, no runtime network — the build indexes the HTML and all querying happens client-side over that index. ### The gate came first Before any UI, the PR's real risk was proven empirically: Pagefind's binary is **vendored, not postinstall-fetched** — it ships as ordinary platform packages (`@pagefind/<platform>`, an `optionalDependencies` set with an empty `scripts` block), so `pnpm install --frozen-lockfile` resolves it from the lockfile with zero network, and the indexer runs fully offline. The lockfile records every platform, so CI's Linux runner gets `@pagefind/linux-x64`. ### What shipped - **Indexing folded into `build`** (`vite build && pnpm run index:search`), so the guards and the offline smoke all see `build/pagefind/**`. `scripts/index-search.mjs` drives Pagefind's Node API, then: - asserts `pagefind-entry.json` reports **≥ 1 record** (a zero-record index means the `data-pagefind-body` wrapper regressed → search would be silently empty), and - **prunes Pagefind's default UI** (`pagefind-ui.*`, `-modular-ui.*`, `-component-ui.*`, the highlight helper): we ship our own UI, and one of those CSS files embeds bug-tracker URLs in a comment that would (correctly) trip `guard:external`. Pruning keeps the offline invariant intact and trims the bundle. - **One record per topic, by construction** — only the default tier's prose is in the prerendered DOM and only the article body carries `data-pagefind-body`, so home/category/error pages are ignored with no `data-pagefind-ignore` choreography. - **Our own tokenised UI** — `SearchPanel.svelte` (on `LibraryHome`, so it appears both standalone and in-window). It loads the runtime with a runtime dynamic `import(/* @vite-ignore */ .../pagefind.js)` — browser-only, absent under `vite dev`, and degrades to "unavailable" if the load fails. Results are real `/library/**` links, so the desktop's delegated intercept opens them in-window while standalone / no-JS follow the route. `pagefind.ts` holds the cached loader + a pure, unit-tested `toHit()` that re-applies `base` to Pagefind's root-relative URLs. ### Verification - **GATE**: proven that `pnpm install --frozen-lockfile` yields a working indexer offline (no install script; binary from platform tarballs; runs and emits the bundle). - Browser-confirmed: query → live offline results, real `/library` links, `<mark>` highlight resolving to the gold token (opaque), empty-state, zero console errors; in-window intercept preserves other windows. - Full gauntlet green: build + all four guards (incl. new offline-smoke search assertions), svelte-check 0/0/0, 62 unit (+3 `toHit`), 16 e2e (+4: standalone search, in-window intercept, no-results, a11y). - Adversarial multi-agent review (5 lenses → per-finding verify). Architecture recorded in `docs/architecture/library.md`. Closes the PR3 item on milestone v0.3.0.
Sign in to join this conversation.
No reviewers
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Bosco/bosco!87
No description provided.