From c0809f617a5e78a767dcf02a65e652e0fb6a78e4 Mon Sep 17 00:00:00 2001 From: Zach Dunn Date: Thu, 15 Oct 2026 22:22:47 -0400 Subject: [PATCH 1/3] feat(anatomy): margin notes and a tonal flat view instead of numbered keys The flat and screenshot views put each key's text in the margin, joined to its part by a bracket ("span": true) or a dot. Below about 560px they fall back to small numbers and the legend. "notes": "keys" keeps the old hotspots; iso always uses them. The flat view takes the iso tones with an offset shadow, and the site's digest card is drawn flat instead of iso. --- CHANGELOG.md | 2 + dev/gallery.mjs | 8 +- dev/test/anatomy.test.mjs | 65 ++++++-- dev/test/site.test.mjs | 5 +- docs/components.md | 2 +- .../releases-weekly-digests/viz/card.json | 8 +- site/src/index.html | 6 +- .../buildnotes/assets/specs/anatomy-card.json | 4 +- skills/buildnotes/assets/template.html | 36 ++++- skills/buildnotes/references/visuals.md | 14 +- skills/buildnotes/scripts/inline-assets.mjs | 3 +- skills/buildnotes/scripts/lint.mjs | 8 +- skills/buildnotes/scripts/viz/anatomy.mjs | 140 ++++++++++++++++-- 13 files changed, 248 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7900951..5dd18cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,8 @@ Until the first release, changes are recorded under [Unreleased]. ### Changed +- `anatomy`: the flat and screenshot views put each key's text in the margin, joined to its part by a hairline (a bracket for a key with `"span": true`, a dot otherwise), instead of numbered discs and a legend. Below about 560px they fall back to small numbers and the legend; `"notes": "keys"` keeps the old hotspots, and iso always uses them. The flat view takes the iso view's tones with an offset shadow, and its list rows are drawn as raised slabs. The weekly-digests card on the site is drawn flat instead of iso. `lint.mjs` counts margin notes as hotspots, and `inline-assets.mjs` and the gallery handle an SVG ``. + - Length guidance sizes a post by the scope of the change, with bands measured against published Cloudflare and Astro posts (`references/story.md`). The deep dive gets a soft ceiling of about 3,000 words: `lint.mjs` warns past it and the author can let it stand. - Lessons from comparing two outside deep dives with the projects' own announcements: stage a cause a reader would hit in a demo run and show the fix (`references/evidence.md`), read a source's claim in its context before calling it wrong (`references/evidence.md`), walk a report from the top down to a cause even in a deep dive, and let the try-it prove the feature's promise (`references/story.md`), and read the release a feature shipped in for where it fits (`references/brief.md`). - The skill's argument hint and description name the deck (`[post|video|deck] …`, with a release story and a deep dive as examples), and the site's Deck card shows a `/buildnotes deck …` request like the other outputs. The home page's hero shows the request's shape under the install command: `/buildnotes [post | video | deck] what to write about`. diff --git a/dev/gallery.mjs b/dev/gallery.mjs index a680b7f..0bb2b01 100644 --- a/dev/gallery.mjs +++ b/dev/gallery.mjs @@ -36,7 +36,7 @@ const ABOUT = { code: "Real code from git at a given commit, cut to the lines that make the point. Renders the whole figure.", term: "One command and the output lines that matter. Renders the whole figure.", strip: "Before/after, or a progression of up to four rounds: images, code or terminal output.", - anatomy: "The parts of one thing, named: a keyed figure drawn from a parts list measured on a capture. Flat shows it straight on with its own labels, iso at an angle as structure only (it comes apart on request), screenshot puts the keys on the capture itself. Pointing at a part lifts it; pointing at a key fades the rest.", + anatomy: "The parts of one thing, named: a keyed figure drawn from a parts list measured on a capture. Flat redraws it straight on in grey and accent tones, and screenshot uses the capture itself; both put each key's text in the margin, joined to its part by a bracket or a dot. Iso draws it at an angle with numbered keys, and comes apart on request. Pointing at a note fades the other parts.", shader: "A WebGL figure drawn by a fragment shader, for what boxes and lines can't show: many things moving at once, a field or gradient, or pixels that are themselves the subject. Follows the theme, pauses off screen, holds a still frame for reduced motion.", }; const ORDER = Object.keys(ABOUT); @@ -61,8 +61,8 @@ const EXAMPLES = { "strip-rounds.json": "Three rounds of a design, as images.", "strip-filter.json": "Before and after, as terminal output.", "anatomy-card.json": "At an angle: structure only, with the object's title on its base. Assembled / Apart lifts the parts off, and the keys go with them.", - "anatomy-card-flat.json": "Straight on, with the object's own labels. The same spec, through \"from\".", - "anatomy-card-shot.json": "Keys over the capture itself. Expand opens it full size.", + "anatomy-card-flat.json": "Straight on, with the object's own labels and margin notes. The same spec, through \"from\".", + "anatomy-card-shot.json": "The capture itself, with margin notes. Narrow the page to see numbers and a legend instead.", "shader-jitter.json": "Many things at once: 80 clients retrying, and the load they put on the server. Loops every 5 s on u_phase; labels sit over the canvas.", "shader-rehash.json": "The mechanism behind a number: which keys move when a server joins, and why the ring moves so few. Loops every 5 s.", "shader-banding.json": "When the pixels are the subject: a rendering fix, shown on the reader's own screen. A still shader, with the level count as an extra uniform.", @@ -85,7 +85,7 @@ for (const f of specs) { const kind = f.split("-")[0]; const id = f.replace(/\.\w+$/, ""); const text = readFileSync(join(specDir, f), "utf8"); - const frag = renderKind(kind, text, { id, base: specDir, style }).replace(/src="img\//g, `src="${imgBase}/img/`); + const frag = renderKind(kind, text, { id, base: specDir, style }).replace(/(src|href)="img\//g, (_, a) => `${a}="${imgBase}/img/`); for (const w of takeWarnings()) { warned++; console.error(`! ${f}: ${w}`); } const name = id.slice(kind.length + 1); const whole = /^(code|term)$/.test(kind); diff --git a/dev/test/anatomy.test.mjs b/dev/test/anatomy.test.mjs index a730fc1..60dcd46 100644 --- a/dev/test/anatomy.test.mjs +++ b/dev/test/anatomy.test.mjs @@ -1,4 +1,4 @@ -// Checks for the anatomy renderer: one parts list, three views, each a keyed figure. Run: node --test dev/test/*.test.mjs +// Checks for the anatomy renderer: one parts list, three views, margin notes or numbered keys. Run: node --test dev/test/*.test.mjs import { test } from "node:test"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; @@ -15,29 +15,64 @@ const card = spec("anatomy-card.json"); const hotspots = (html) => [...html.matchAll(/