Skip to content

Loose ends: a social card that can be rebuilt, validate-before-write, and a current mutation record - #175

Merged
caviri merged 3 commits into
developfrom
chore/loose-ends
Aug 16, 2026
Merged

Loose ends: a social card that can be rebuilt, validate-before-write, and a current mutation record#175
caviri merged 3 commits into
developfrom
chore/loose-ends

Conversation

@caviri

@caviri caviri commented Aug 16, 2026

Copy link
Copy Markdown
Member

Three items left deliberately from #174, each recorded at the time rather than forgotten.

The social card, and a script that can rebuild it

The premise needed correcting first: og-default.jpg was already a branded typographic card, not the stock photo the task assumed. But dbbc8c81's own message says it was "rendered once with satori + resvg-js in a throwaway script, not a project dependency" — so the real gap was reproducibility, not design.

The template and renderer now live in components/frontend/design/og/. The design language is unchanged (the static/social/ set carries it at three other sizes and would otherwise desync); what changed is the mark no longer carrying a lockup the headline repeated, and a feature list becoming an invitation. Event pages pass no image to <Seo>, so this card is literally what appears when any hackathon link is shared.

Two bugs found while building it:

  • the headline check measured 1056px — exactly 1200 minus 144 of padding. #headline is a block, so it asserted on the padding and would have passed in any font. It measures the text through a Range now.
  • oklch() colour stops render as nothing inside repeating-linear-gradient in Playwright's Firefox — no error, no console message, and getComputedStyle reports the fully-resolved gradient. The grid was invisible in two renders, caught only by sampling pixels out of the PNG. The renderer now asserts the grid's 60px period in the finished JPEG, because a wash can render and still be destroyed by the downscale.

1200×630, opaque, 56.8 KiB, identical md5 across runs, og:image absolute at runtime, LFS pointer matching the file's sha256.

Validate before writing

embed-run-report.mjs had the bug build-quality-report.mjs was fixed for in 226d2016 — it wrote the player and then checked it, so a bad splice corrupted the file and the validator reported it afterwards. Same treatment: assemble in memory, check the string, write via temp/fsync/rename only on a clean pass. The invariant differs per file (three close-script tags for the player, one for the report) and both are kept correct.

A current mutation record

verify.json held 23 EXACT / 11 GAP from two days earlier, against a manifest declaring 37 EXACT / 1 GAP — so the quality report was printing a derived callout about its own stale input.

Re-run: 38 run, 36 exact, 1 known gap, 0 untested properties, 1 other failure. That confirms from a fresh run what the manifest had only claimed — the eleven gaps closed by the 28 backend specs are genuinely closed. The remaining failure is the known shape: capacity.oversell-by-one's witness is a declared-flaky concurrency spec, so it can come back MISMATCH when the flake fires rather than when anything is wrong. Left listed as flaky-witnessed rather than quietly excused.

Also: a lesson worth more than the fixes

.claude/CLAUDE.md gains "Ways a HANDOVER was confidently wrong", next to the existing section on tests that passed while proving nothing — because it is the same disease in a different host.

Five times in one week a brief asserted something false with enough confidence to be taken on trust: a 44-second Nix floor that did not exist (and had already propagated into five files as justification for design decisions), git-lfs framed as its fix, a vacuity guard removed on a wrong theory that made two neighbouring assertions pass instantly against a page that never rendered, "the devcontainer can drive k3d" when it mounts no Docker socket, and a liveness-probe consequence that stopped being true when the landing page learned to catch its own failures.

Each was caught because the brief said verify this rather than transcribe it. The failure mode is not a lie — it is a true-once observation restated after its context moved, which is exactly the shape nobody re-checks.

Verification

Generators both report "checked first, then written"; just check::format clean at 0 changed; mutation journal empty and components/ clean after the sweep; player at 477 lines / 465 actions.

Note the running :8081 server still serves the old card — build/ snapshots static/ at build time, and it was left alone because two servers were running off it and the mutation sweep depended on them. A rebuild and restart picks it up.

caviri added 3 commits August 15, 2026 16:07
The repo already documents ways a TEST reported green while proving nothing.
This week produced a different and more expensive shape: briefs that told an
agent something false with enough confidence to be taken on trust. Five in one
week, all mine.

The Nix floor is the clearest. A 44-second figure was handed over as fact and had
already propagated into five files as the justification for design decisions;
measured, it is 4.6-5.0 s and clean-vs-dirty is not the variable. It had been
real once — during a crash loop fixed hours earlier. A wrong number stated once
became load-bearing in four other files within a day.

The others: git-lfs framed as that floor's fix (it makes `git status` truthful
and changes no timing); a vacuity guard removed on the theory that a no-op
preview lists nothing, which made the two assertions after it pass instantly
against a page that never rendered; "the devcontainer can drive k3d", which
mounts no Docker socket; and a liveness-probe consequence that stopped being true
when the landing page learned to catch its own gRPC failures.

Every one was caught because the brief said VERIFY THIS rather than transcribe
it, and named what would count as disproof. The failure mode is not a lie — it
is a true-once observation restated after its context moved, which is exactly the
shape nobody re-checks. Hence the two habits recorded with it: attribute a number
to the run that produced it, and when handing over a diagnosis, hand over the
measurement that would falsify it.
…d it

The premise needed correcting first: og-default.jpg was already a branded
typographic card, not the stock photo I assumed. But dbbc8c8's own message says
it was "rendered once with satori + resvg-js in a throwaway script, not a project
dependency" — so the real gap was reproducibility, not design. The template and
build script now live in the repo and print dimensions, size, alpha, headline
fill and grid contrast on every run.

Kept the established design language rather than restyling: static/social/
{banner,square,story}.png are the same design at three other sizes, and changing
only the OG card would desync the set. The SDSC mark is placed and scaled, never
restyled — it is a real organisation's identity.

What changed is the words and one repetition. The mark carried a "Hackathons"
lockup that the headline then repeated, so it is the bare mark now, as the app's
own footer uses it. The subline was a feature list — "Propose projects, form
teams, and build solutions together" — and is now an invitation: "Bring an idea,
or join one. Then build it together." Set in sans, per the theme's rule that mono
carries labels and sans carries sentences; an invitation in a typewriter face
reads as a terminal banner. Event pages pass NO image to <Seo>, so this card is
literally what appears when a hackathon link is shared — the title carries the
event, so the image carries the platform.

## Two bugs found while building it

My own headline check was VACUOUS: it measured 1056px, which is exactly
1200 − 144 of padding, because #headline is a block. It was asserting on the
padding and would have passed in any font. It measures the text itself through a
Range over the element's contents now (882px, 83% of the content box).

And `oklch()` colour stops render as NOTHING inside repeating-linear-gradient in
Playwright's Firefox — no error, no console message, and getComputedStyle
happily reports the fully-resolved gradient. The grid was invisible in the first
two renders and was found only by sampling pixels out of the PNG. The template
uses each token's measured sRGB with the oklch source in a comment, and the
renderer asserts the grid's 60px period IN THE FINISHED JPEG, because a wash can
render and still be destroyed by the downscale or the quantiser.

1200×630 exactly, opaque, 56.8 KiB, identical md5 across runs. The lfs clean
filter's pointer oid and size match the file's sha256, and the file starts ff d8
— a real content change, not a pointer mismatch. og:image resolves absolute at
runtime, which matters because most clients ignore a relative one.

Playwright and sharp are borrowed from the e2e skill's node_modules at render
time rather than added to the frontend's package.json — no dependency or
lockfile change for a build-time-only tool.

Note the running :8081 server still serves the old card: build/ snapshots static/
at build time, and it was left alone deliberately because two servers have been
running off it for hours and a concurrent agent depends on them. It needs a
rebuild and restart to serve this.
…tion record

`embed-run-report.mjs` had the same bug `build-quality-report.mjs` was fixed for
in 226d201: it wrote `recipe-player.html` and only then ran its read-back,
spec-count and close-tag checks. A bad splice corrupted the player and the
validator reported it afterwards — the corrupt file existed either way.

Same treatment, reusing that file's approach rather than inventing a second one:
assemble in memory, run every check against the string, and only on a clean pass
write a same-directory temp, fsync, rename (retrying EPERM — 9p, container trap
5), then re-read the destination and throw unless it matches. Note the invariant
differs by file: the player asserts THREE literal close-script tags, the quality
report asserts one. Both now say "checked first, then written" on success.

## The mutation record is current again

`verify.json` held 23 EXACT / 11 GAP from 2026-08-13, two days behind a manifest
that declares 37 EXACT / 1 GAP — so the quality report was printing a derived
callout about its own stale input. Re-run: **38 run, 36 exact, 1 known gap,
0 untested properties, 1 other failure.**

That confirms from a fresh run what the manifest had only claimed: the eleven
gaps closed by the 28 backend specs are genuinely closed, and no property is
untested. `EXIT=1` is the runner working — any non-EXACT verdict fails the run,
which is what stops a quiet regression sliding past.

The single other failure is the known shape rather than a new one:
`capacity.oversell-by-one`'s witness is a declared-flaky concurrency spec, so it
can come back MISMATCH when the flake fires rather than when anything is wrong.
A second run was started to separate those two; the record here is the first
run's, and the entry stays listed as flaky-witnessed rather than quietly excused.

Journal empty and `components/` clean afterwards, verified — a mutation left
applied and then committed is the worst thing this tooling can do, and it is why
nothing was staged while the sweep was running.
@caviri
caviri merged commit d69f515 into develop Aug 16, 2026
1 check passed
@caviri
caviri deleted the chore/loose-ends branch August 16, 2026 08:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant