Fosterman by Sirrele
build log

Published is not reachable

Sixteen changes shipped. A phone visitor had three reachable links on an interior page. Almost everything was already there and could not be got to.

What happened

Sixteen changes went to production today, in two blocks: six between four and half past five in the morning, then ten between half past seven and eleven at night. Nothing in between.

This is the second log dated 2026-08-15. The first covered the five changes that merged before three, and two of today’s sixteen are that entry — drafting it, then publishing it at 05:28. Everything else here landed after it went up.

Two corrections. CLAUDE.md still carried a “Known issues” section describing the second-WebGL-context race as live, saying a separate task was fixing it. That task had merged the day before. The section is now “Bugs already paid for”, rewritten rather than deleted, with the general form of the lesson kept: when one script’s deadline can land on either side of another’s, ask the document, not the clock. And the Conduit project page, published 2026-08-06, still described an active product thesis, still posed a question a shipped engagement had already answered, and never named the product at all. The product went publicly live 2026-08-05 — the day before that page went up. A reader concluded idea stage. Corrected, with Threadout published beside it and a decision note on why the product shell stores no evidence.

An audit of the live site found five defects. They are all the same defect.

Below 980px, nav { display: none } removed the topbar’s links and nothing replaced them. Measured, an interior page offered a phone visitor three reachable links: skip-to-content, the brand, and the section back-link. Desktop offered ten destinations. Three of the four audiences arrive on a phone. It now ships two independent routes — a <details> disclosure that needs no script to open or navigate, and plain links in the footer at the same widths, because a disclosure is still a thing you have to find.

The entry curtain’s Skip button rendered 122 to 290px below the fold on every viewport tested. It borrowed the keyframes written for the wordmark, which is grid-centred and has to sit low, so they end at translateY(30vh) and both holds that forever. A click on the button timed out. It also took a Tab stop inside an aria-hidden subtree.

The Preparation section was 339px of nothing without JavaScript — 626 of the site’s 627-character no-JS text delta came from that one panel — while its select and two sliders still rendered and read as working controls. The arithmetic that builds the panel now lives in a module both renders import, and the controls are not shown to a visitor who cannot use them.

A device with no WebGL downloaded and parsed three.js to construct a renderer that was always going to throw. The inline probe already knew the answer and kept it in a local variable where nothing could read it.

And with the JS chunks blocked, the curtain went up from the inline script, intro-active locked scroll, and every escape gesture was registered in the module that never arrived. The documented promise — any click, key, scroll or touch lifts it immediately — was simply false, and the only way out was the seven-second failsafe.

The scene. It moved out of the hero band into one canvas fixed over the viewport, behind everything, mounted once, and asked per frame for a rect to scissor its draw to. In the hero chapter that rect is the hero band, so the hero costs what it cost. Then a decision written down rather than built: bloom on named nodes is wanted, EffectComposer is refused, and the reason is architectural rather than aesthetic. Then six named stations — SURVIVAL, SKYLAR, CODE, MENTORSHIP, SYSTEMS, ACCOUNTABILITY — real DOM text tracking projected positions, lit with the same halo that has always drawn the wolf’s eyes.

The map. The six trajectory windows became a real collection and the seven origin chapters got a visual block: 3 nodes to 16. A hypothesis field now separates a future window from a receipt, and the schema refuses to let a hypothesis claim a lesson it has not learned. Then the manuscript — 18 pieces imported as drafts, each classified: 7 public, 8 excerpt, 3 private. Seven published. Eleven held. Then the origin chapters and the seven rules became doors, which link on the day their writing is published and are ordinary cards until then. Six are open tonight.

What I noticed

Three reachable links. Not three broken links — three that worked. The same page at 1280 wide offered seven, and the homepage offered twenty-five. Nothing was down. The width was the whole difference.

Every defect in the audit is that. The Skip button was rendered, styled, and in the DOM. The preparation plan was computed correctly and shipped to the browser as JSON. The escape gestures were written, tested, and documented. The three.js chunk arrived intact on a device that could not use a single byte of it. Nothing was missing. Everything was out of reach.

The map says it more plainly than the audit did. Importing eighteen manuscript pieces changed the graph by zero nodes and zero edges — the before and after reports are byte-identical, and that is the correct result, because a draft is not a node. Forty-six connection references went in and every one of them is latent. Tonight, with seven of them published, the graph reports eight dropped edges: declared, resolved, and not drawn, because the thing at the other end is still held.

The two corrections are the same failure with the clock taken out. CLAUDE.md was reachable and said a fixed bug was open. The Conduit page was reachable and described a shipped product as a thesis it might one day test. Both were up, indexed, and read.

What changed in my thinking

Two entries ago I wrote about gates reading a convenient proxy for the state they were named for. This morning, about the same substitution one step earlier, inside the estimate. This is the third place it lives: in the definition of done. Shipped was standing in for someone can get to it, and like every other proxy it was true on the day it was written. It stopped being true quietly, one width and one disabled script at a time.

Reachable is a separate claim from built, and true is a separate claim from reachable. I had been counting the first and assuming the other two. A phone visitor and a JavaScript-off visitor are not edge cases here — they are two of the four people this site exists for, and neither of them can come back later to find the thing I meant to give them.

The exception is worth naming, because it is the one decision today that was not correction. Refusing EffectComposer on taste would have been cheap, and it would have been overturned by the next person who wanted glow. It holds because there is an argument: setRenderTarget copies scissor state from the render target, RenderTarget.scissorTest defaults to false, and a separable blur samples outside its source rect by construction — so the field and its glow would composite across the headline. And because the rule names the cheaper route to the thing the composer was reached for. The site has been drawing that glow on the wolf’s eyes since the beginning. Three hours later, six stations used it: one extra draw call, six vertices, no new passes. A rule that only says no gets argued with. A rule that says no and hands you the other way does not.

The receipt

Sixteen pull requests, all merged to main today.

  • #13 f609cde — draft the 2026-08-15 build log
  • #14 f568b5e — record the curtain context race as closed, not open
  • #15 3998cca — bring the curtain’s Skip button back on screen
  • #16 8bfc490 — give phone visitors navigation instead of hiding it
  • #17 88316c3 — move the scene into one persistent, scroll-driven canvas
  • #18 ebabde1 — publish “The constraint was somewhere else”
  • #19 42f0f87 — draw the post-processing line at the composer, not the effect
  • #20 14c0a22 — put the trajectory and the origin chapters on the map
  • #21 8240346 — render the preparation plan on the server, not only in the browser
  • #22 079133a — import the manuscript as drafts, on the map, and classified
  • #23 91517ed — turn the origin chapters into doors, and render the map from the journey
  • #24 c29eec4 — correct the Conduit page and publish Threadout beside it
  • #25 dd6785a — publish the seven public manuscript artifacts
  • #26 fb48fae — put the two guarantees in the only script guaranteed to run
  • #27 8b2274a — turn the seven rules into doors, four of which open today
  • #28 d77ce35 — give the field six named stations, with the words in the DOM

What the audit fixes measured. Reachable destinations are laid-out, non-hidden <a href> elements at 390px wide.

Measurebeforeafter
Reachable links, interior page at 390px311
Reachable links, homepage at 390px1529
Same counts with JavaScript disabled—identical
Skip button, distance below the fold (5 viewports)122–290pxon screen
Preparation panel text with JS off72 chars700 chars
Site-wide JS-on vs JS-off text delta627 chars−67 chars
_astro bytes transferred, no WebGL137,1308,218
_astro bytes parsed, no WebGL524,04122,838
Time to escape the curtain, JS chunks blocked — click6,696ms41ms
— keydown / wheel / touchstart6,628 / 6,648 / 6,393ms10 / 16 / 20ms

The untouched case is unchanged in that last block: with nothing touched, the seven-second inline failsafe still fires at 6,640ms. It was never the thing that was broken. It was the only thing that worked.

npm run graph, run against the content as it stands tonight:

Scene graph — 24 nodes, 28 edges from 21 published entries and 7 origin chapters

Hypotheses (6 of 24 nodes)
  future windows, not receipts — a renderer must not draw them as shipped work

Nodes per cluster
  origin           10
  engineering       3
  leadership        2
  conduit           3
  foster-care       4
  public-service    2

Published entries and chapters with no visual block (4)
Dropped edges (8)

This morning that was 3 nodes, 3 edges, and three empty clusters. The steps: 3 → 16 when the windows and chapters went on (#20), 16 → 17 when Threadout landed (#24), 17 → 24 when the seven public manuscript pieces were published (#25). The import itself, eighteen files and forty-six references, moved it by nothing.

Six doors are open: chapters 1 and 7, and rules 2, 3, 5 and 7. Each was proved shut before it was proved open — a private artifact was set to status: published locally and its card stayed inert, because publishedArtifacts() filters on two fields and both were tested.

What comes next

Eleven manuscript pieces are still held, and they are not nearly ready.

Three are private. Two name living people who did not choose to be written about. The third carries an editorial note that was never meant for a reader at all, and an account of a recent employer exit that anyone with my work history could place. Those are not an editing problem.

Eight are excerpt, and that is the honest one. excerpt means part of the piece must be withheld, and nothing has been withheld from any of them yet. Publishing one as it stands ships the exact material the classification exists to hold back. That is eight editing passes, done by me, on my own prose about other people — not a flag to flip. Eight of the graph’s dropped edges point straight at them.

The constellation is being built now. The six stations carry hardcoded words; wiring them to content is the next phase, and it goes through src/lib/collections.ts like every other read, for the reason that file exists.

And the sentence from this morning still holds, even though the number moved three times: the map only grows when I write. It went from 3 nodes to 24 today and not one of them came from a renderer. Ten are the origin chapters and their long form, six are windows that have not arrived, and four published notes still carry no visual block — including both of the ones I wrote today. The field will draw whatever it is given.

One loose thread to close: all six journey entries cite provenance: "The ten-year map, src/data/trajectory.json", and that file was deleted when the section started reading the collection. Git has it. The citation still points at a path that is not there.

← All notes