Lossless Changelog
product

One Atom, Two Islands — Search That Spills Out of the Bar

Pagefind lands in the toolkit spike, and with it the fix for a subtler problem: two Svelte islands that looked like one feature and behaved like two. A shared reactive atom makes the header bar and the catalogue explorer genuinely the same state, tags become clickable objects that fold the query into themselves, and a Playwright drive finally catches the class of bug that a passing build never will — starting with a one-argument mistake that silently killed hydration.

Michael StatonMusic Editoraugmented with Claude Code on Claude Opus 5 (1M context)

Changelog — 2026-08-23 (#2)

One Atom, Two Islands — Search That Spills Out of the Bar

Why Care?

A catalogue of ~1,900 tools is only as good as the path to the one you meant. This variant now has real full-text search, and — more usefully — tags behave like objects rather than strings. Type AI Toolkit, and the AI Toolkit tag appears with its count. Click it and the text you typed folds into the token, because it has done its job.

The less visible fix matters more. The header search bar and the catalogue explorer were two separate Svelte islands, which meant two separate pieces of state that merely happened to read the same query parameters. They looked like one feature and behaved like two.

What's New?

  • Pagefind, indexing 443 pages, fetched in fragments so a query costs a few KB rather than the whole corpus.

  • A spill-out panel anchored to the bar — results and tags overlay the page instead of rearranging it. Command palette, not a filtered index page.

  • TagChip.svelte, the single way a tag is drawn anywhere. Prop API deliberately mirrors site/src/components/toolkit/TagChip.svelte so the two are portable onto each other.

  • A shared reactive atom — src/lib/toolkit-state.svelte.js.

  • Two bars, two jobs, two labels. The header searches the whole corpus; the explorer bar filters what is already on the page, and now says Filter with a funnel icon rather than a second magnifier.

  • tests/browser-drive.mjs — eight assertions against a real browser.

The atom

Every client: directive mounts a separate Svelte root with its own state. That is what an island is. So:

   header bar  ──▶  its own query, its own tags
   explorer    ──▶  its own query, its own tags
                    …coordinating only through the URL

A module-level $state object is a singleton across every island that imports it, so:

   header bar  ──┐
                 ├──▶  toolkit  { query, tags, category, sort }
   explorer    ──┘

Picking a tag in the header narrows the grid underneath in the same tick, with no navigation. The URL becomes the serialisation of that atom rather than its source of truth.

What the browser caught that the build could not

Every defect in this surface passed pnpm build and passed curl:

SymptomCause
"No search box at all"The page existed; nothing linked to it
"Can't type"requestIdleCallback given 1 as its second argument — it wants an options object. Threw during init, killed hydration
"Only works with ?q="hydrateFromUrl() inside a reactive effect, re-reading the URL on every keystroke and overwriting what was typed
Chips did nothingA stray selected left by a careless find-and-replace
Bright yellow highlightPagefind's <mark> arriving through {@html}, which scoped styles cannot reach

The first Playwright run found the requestIdleCallback throw immediately. None of these fail a build, and all of them are one page load from obvious.

NixOS note: Playwright's bundled Chromium will not run — point executablePath at the Nix-provided Brave.

What is still open

The URL has two writers: the atom's writeUrl() and the explorer's older currentPath(), which is the smarter of the two — it collapses to the canonical /tags/<tag>/ route when exactly one tag is selected. They race, so the canonical path gets clobbered and Copy shareable link copies a stale URL. One owner is the fix, and the explorer's logic is the one to keep.