The Toolkit Grows Client Portals, and Two Bugs That Only a Browser Could Find
A filter that ate every keystroke, icons that were 130 bytes of text, and a page nothing linked to — all invisible to a green build.
Changelog — 2026-09-15 (#1)
Why Care?
Every bug that mattered today passed every automated check we had.
The build was green. Eighteen tests passed, including the one that walks the whole
dist/ and fails if a client's name reaches a rendered byte. Every route returned
200. And the site had a filter box that ate every keystroke, eleven client logos
that were 130 bytes of ASCII, and a page with nothing linking to it.
None of that is a gap in the test suite so much as a statement about what a test suite is for. A status code tells you the server answered. It does not tell you the answer was an image, that the page is reachable, or that typing into the box does anything. Those need a browser, and today is the clearest argument yet for the house's own browser-drive rule — the one that says a drive is codified work, not a thing you improvise once and forget.
What's New?
Per-client portals on
lossless-toolkit-site, each filtering the catalogue by the engagement it belongs to, with the/toolkitExplorer scoped to that subset./repositories/gains the same filter, keeping the source links that are its entire point.Three bugs fixed that a green build could not see — details below.
lossless-slides-sitewired in as a submodule here.
Full engineering detail lives in the site's own log at [[lossless-toolkit-site/changelog/2026-09-15_01]]; this entry is the family-level view.
The three things a 200 did not tell us
An image that was not an image. Seven client app-icons were git-lfs pointer
files. A web server returns those with a 200 and Content-Type: image/png, because
the extension says so and nothing checks. Worse, the objects 404 on the LFS
endpoint — the pointers were pushed, the objects never were, so git lfs pull was
never going to end it. The bytes were recovered from lossless-site, where this
client list originates, and verified by SHA-256 against each pointer's own oid
before being written. Seven matches, zero mismatches.
The lesson that generalises past this repo: an LFS pointer is a checksum with a filename attached. When you have a candidate file and a pointer, you never have to guess whether they are the same file.
A page nothing linked to. /curious-clients/ shipped on the 13th and could only
be reached by typing the URL. The card CTA was deliberately unlinked — a comment
explained that the route did not exist and "a dead link is worse than none" — and
that comment quietly went stale the moment the route landed two days later. The
header had no entry for it either.
A filter box that ate every keystroke. bind:value and an explicit oninput
both fire on the same event, and the handler ran first, so it pushed a stale value
into the shared state atom; the bridge effect then saw local and shared disagree and
reset the input. The box looked dead. A browser drive found it reading ""
immediately after typing — not a rejected keystroke, an erased one.
That one had been live on /toolkit/ for some time. Putting the Explorer on two more
pages is simply what caused someone to drive it.
The measurement that changed a decision
Scoping the Explorer meant deciding whether a portal could reuse the global
/api/tools.json. Measuring settled it:
| endpoint | tools | payload |
| global | 1,765 | 1,019 KB |
| one client | 189 | 152 KB |
| repositories | 155 | 95 KB |
A portal listing fourteen tools has no business shipping a megabyte to show them, and facet counts computed against the corpus would describe a page the reader is not looking at. Each scope prerenders its own endpoint.
A second measurement killed a truncation nobody had questioned. Cards rendered
tags.slice(0, 3). The corpus has a median of 3 tags but a p90 of 6 and a max of 13 —
729 of 1,765 tools carry more than three, so 41% of the catalogue had tags that
were never rendered. Once the container caps its own height and scrolls, the
truncation protects nothing.
On the tree
lossless-slides-site is now a submodule here. Worth recording honestly: it was
already added on origin/main two days ago, and this branch is master, which
did not have it. Adding it here rather than promoting master is the expedient move
and it costs a future merge conflict in .gitmodules — master was a clean
fast-forward to origin/main before this commit and is not one after it. Logged so
that whoever hits the conflict knows it was chosen, not stumbled into.
What's next
astro.config.mjsnames a dead origin.SITEislossless-toolkit.vercel.app, which 404s; the deployment is atlossless-toolkit-site.vercel.app. Every canonical, sitemap entry and absoluteog:imageis built from a host that does not answer.Two tools name a client in a public summary. Not a
for_clientsleak — the parse-time strip holds — but body prose that the summary precedence chain picks up. Both pages are public, notnoindex, and in the sitemap. A content fix.Codify the drive. Every bug above was caught by a throwaway Playwright script. The house rule says the click-path belongs in the phase plan, not in a session transcript. It is not there yet.
