Fosterman by Sirrele
build log

Measuring the wrong thing

Seven changes shipped today, most of them gates that had been checking a convenient proxy instead of the state they were named for.

What happened

Seven changes went to production today. Five of them touch the front door.

The entry curtain was gated on sessionStorage. It now reads a 24-hour recency stamp from localStorage, so a refresh and a fresh tab behave the same way, and someone returning next week sees the intro again. On the path where no curtain plays, the hero scene mounts immediately instead of waiting on requestIdleCallback — returning visitors were arriving after the wolf had already finished assembling.

The curtain’s wordmark was drawn at random from four lines. It now cycles in order, starting at the headline.

The hero wolf runs full-bleed above the headline at every width, not in a column on desktop. The band was still too short and the caption was laid over the animal, so the band grew, the caption moved into normal flow below it, and the wolf went back to centre.

I removed the section that explained the site’s own DNS and 301-redirect strategy to readers. The redirect still works. It is no longer content.

I added a CLAUDE.md at the repo root: the four degradation guarantees, the rule that anything built from content goes through the published*() helpers, and seven things not to do to the scene, each with the reason it exists.

And I closed a race where a slow network could open a second WebGL context on a canvas that had already been removed from the page.

What I noticed

Almost none of this was new. It was correction.

sessionStorage does not measure whether someone has seen the intro. It measures how long a tab has been open. A random draw from four lines does not measure coverage — with the curtain playing at most once a day, four lines sampled at random are, from any one person’s side, roughly one line. The curtain module’s five-second failsafe does not measure whether the curtain is still on the page; its clock only starts when the module runs, so a cold cache put it on the far side of the inline seven-second one, and it went on to build a scene on a canvas that had already left the document. And the wolf’s scale was a fixed number tuned against a square column that no longer exists.

Four gates. Each one named for a quantity, each one reading a different quantity.

What changed in my thinking

A gate is a claim about state, and every one of these read a proxy for that state instead — a session, a clock, a draw, a constant. Proxies get chosen because they are cheap to read and because they are true on the day they are written. They stop being true quietly. Nothing throws. The gate keeps returning an answer, and the answer keeps looking reasonable.

In every case the fix was to ask the real question directly, and in every case asking directly cost nothing extra. When did this person last see the intro. Which line comes next. How tall is the visible frame right now. Is my canvas still in the document. The failsafe is the clearest of the four: a timing tweak would only have narrowed the window, so the module now asks the document rather than the clock.

Pulling the DNS section is the same mistake one level up. I was publishing what I found interesting about the site instead of what a reader came for. Nobody arrives here to learn which domain redirects to which.

The receipt

Seven pull requests, all merged to main today.

  • #1 e5bb24a — tier the entry curtain by visit recency, not by session
  • #2 c272cba — cycle the curtain wordmark instead of drawing it at random
  • #3 085fe44 — run the hero wolf full-bleed above the headline at every width
  • #4 4d15a6f — write the WebGL/narrative constraints into a repo CLAUDE.md
  • #5 87e4dc8 — give the hero wolf room to read as the whole animal
  • #6 1430cb8 — stop publishing the DNS strategy to readers
  • #7 b39947e — stop the curtain building a scene after the pre-paint failsafe handed off

#7 carries the hardest evidence. Forced onto a slow enough network, three runs before the fix each opened a WebGL context on a canvas that had left the document roughly eight seconds earlier — leaked for the rest of the visit, alongside the hero’s — and each dispatched the intro’s completion event twice, stamping the recency timestamp and burning a wordmark for a play nobody watched. Three runs after the fix opened no curtain context at all, dispatched once, and wrote nothing.

What comes next

Two things are open, and one of them is mine.

Mounting the hero promptly, from #1, broke same-day refresh: an empty box where the scene should be, and a canvas sized wrong when it does appear. It is being fixed now.

Then the larger one. The scene is still decoration sitting above the copy. The plan is for it to become the spine — the thing the page is read through, rather than the thing above the headline. CLAUDE.md exists because that rebuild is where the degradation guarantees are most likely to get traded for spectacle. Whether writing them down is enough is the open question. Nothing has tried to break them yet.

← All notes