Lossless Changelog
product

One Owner for the URL — and Chips That Actually Do Something

Train-Case is the storage form and Train Case is the display form, and for a few hours the toolkit confused the two badly enough to route people at /tags/Video%20Generators/. Fixing it meant giving the URL a single owner, teaching Pagefind to index the stored value rather than the rendered label, and lifting a decorative span out of a link so it could finally be clicked.

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

Changelog — 2026-08-23 (#3)

One Owner for the URL — and Chips That Actually Do Something

Why Care?

Selecting a single tag now puts you on /tags/data-utilities/ — the real, prerendered page, with its own title and share card — instead of a query string that unfurls generically. Select two and it falls back to parameters, because 255 tags have 32,385 pairs and none of those pages can exist.

Along the way both kinds of tag chip became clickable. One of them never had been.

What's New?

  • The URL has exactly one owner. canonicalPath() lives in the shared atom, and Copy shareable link reads the same function the address bar does, so they cannot disagree.

  • Both chip designs work. The facet row and the card tags are allowed to look different; they now behave the same.

  • Pagefind indexes the stored value, not the rendered label.

  • tests/chip-drive.mjs pins all of it.

Train-Case is storage, Train Case is display

Tags are stored as Video-Generators and shown as Video Generators. Pagefind takes a filter value from an element's text by default — which is the display form. So the index filled with labels that nothing else in the system could resolve, and picking one produced:

/tags/Video%20Generators/     ← a page that does not exist

The [attribute] syntax indexes a stored value instead:

ASTRO
<a href={`/tags/${tagSlug(t)}/`} data-tag={t} data-pagefind-filter="tag[data-tag]">
  {tagLabel(t)}
</a>

Display stays a display concern. Every other layer keeps speaking Train-Case.

Two writers, one address bar

The atom wrote /tools/?tags=…; the explorer wrote /tags/<slug>/. Both fired on every change and the last one won, so the canonical path kept getting clobbered and the copied link disagreed with the address bar. The explorer's logic was the better one, so it moved into the atom and the duplicate was deleted.

A span cannot be clicked

The tag row on each card was <span class="chip"> nested inside the card's <a>. It looked identical to the working chips and did nothing, because an interactive element inside a link is invalid markup. It now sits outside the anchor as a real TagChip.

What the browser caught, again

Consolidating the URL writers introduced an infinite loop: an effect that called registerSlugs() and writeUrl(), where the second reads the state the first writes. Svelte spun to effect_update_depth_exceeded and took the whole component render down — which is why the card chips vanished entirely for one build.

Writing state that an effect also reads is a loop, not a sync. The build was green throughout.