Skip to content

Draw figures from their cells, paint them with theme tokens, and check figure content - #19

Open
adewale wants to merge 4 commits into
mainfrom
claude/diagram-upgrade
Open

adewale wants to merge 4 commits into
mainfrom
claude/diagram-upgrade

Conversation

@adewale

@adewale adewale commented Sep 27, 2026 •

Copy link
Copy Markdown
Owner

What

Every figure paints with the page's colour tokens and fonts, ten figures are redrawn as mechanisms built from their own cell's values, and three new contracts check figure content, arrow length and theme tokens.

Why

Fixes #18. The bytes-and-bytearray figure rendered b'\\x63\\x61\\x66' with doubled backslashes, and those bytes never appear in the example. It shipped because every figure contract measures geometry and none reads a label. A review of the whole library (docs/diagram-upgrade-plan.md) found the same pattern elsewhere: the regular-expressions figure drew a pattern the example never uses, recursion drew factorial(3) on a page that sums a tree, dicts drew keys a to d and a collision on a page whose dict holds name and language, and sentinel-iteration drew placeholder chunks. Beyond content: figures hard-coded the light palette and sat on cream chips in dark mode; the SVG asked for JetBrains Mono and Source Sans while the page loads no web fonts; nine arrows were mostly arrowhead; and five selector figures put the accent on whichever row a loop reached last.

How

Grammar. INK, INK_SOFT, EMPHASIS and SOFT_FILL are var(--fig-*) references, and public/site.css defines the four tokens for light and dark, so one paint function renders in both schemes and the dark-mode chip goes away. Mono and sans faces follow the page's stacks, and each mono run carries textLength so divider maths holds on any platform. ARROW_MIN is 20 units. A new env phrase draws an environment frame with bindings.

Figures, each from its cell's own values. Closures: an environment diagram, the returned frame ghosted, one live __closure__ reference. Generators: the cell's two next() calls on countdown(3), one ribbon each, the surviving n beside each. Format spec: a railroad tracing the cell's 05.1f, skipped stations ghosted under bypass rails. Bytes: the example's two bytes as integer slots with packet[0] = ord("P"), anchored to the cell that performs it. Regex: the cell's two capture groups dropped onto Ada: 10. Values: text bound to a str object and the type() hop to where upper() lives. Logging: the cell's three records crossing the handler's INFO gate. Recursion: the example's tree at the deepest frame of total(tree), with each frame's return value. Dicts: the cell's keys hashed to slots, the insert of "year" accented. Sentinel iteration: read_chunk yielding "py" and "thon" and stopping on "". The binding figure is a factory, so variables and constants show their own names. Nine arrows were lengthened and five arbitrary accents removed.

Alternatives considered. An earlier commit removed the four table-shaped figures (values, literals, logging, collections-module) and recorded a rationale for each. Review of the branch showed that a page with a paragraph about a better picture has less than a page with the old picture, so values and logging were redrawn and literals and collections-module keep their tables at 8.5. The railroad was first drawn with a bypass rail over all seven stations (53 elements); it now traces one path with bypass rails only over the three stations 05.1f skips. A library-wide check found 54 of 115 attachments carrying a code-like label absent from their example; most are formatting drift or placeholders the rubric allows, and the three that drew values the example never computes are redrawn here. The rest are queued.

Contracts. 13: no rendered text carries a doubled backslash, and an escape sequence in an attached figure must appear in that example's source. 14: every arrow is at least ARROW_MIN. 15: site.css defines all four tokens in both schemes with the grammar's values, and every grammar colour is a token reference.

Docs. Both rubrics say a weak figure is redrawn rather than removed, a figure whose caption names no single element carries no accent, and how cell-0 anchors are scored. docs/rubric-audit-2026-09-27.md scores every changed figure criterion by criterion.

Testing

  • Added FigureContentContract, FigureArrowLengthContract and FigureThemeTokenContract in tests/test_marginalia_geometry.py.
  • Verified they fail against main: with main's src/marginalia.py and public/site.css swapped in, Contract 13 reports the two doubled-backslash labels (its escape-sequence check errors instead, because the branch registry names figures main does not define), Contract 14 reports the short arrows, and Contract 15 reports the missing tokens. With the branch files restored all three pass.
  • Full suite: 232 tests pass. make lint, make quality-checks and make check-generated pass; 120 social cards regenerated with provenance checked.
  • Manual: every changed page was rendered through src.app.route on a local HTTP wrapper and captured with scripts/capture_browser_screenshot.mjs in dark mode; one social card was inspected for token colours. make dev needs Node 22, which the authoring machine lacks, so the Worker runtime and the POST runner were not exercised; neither is touched by this change.
  • check-pr-readiness.py from the good-pr skill warns about diff size (most of it regenerated gestalt pages and the social-card manifest) and about print statements; the only new prints are progress lines in scripts/build_figure_contact_sheet.py.

Visual evidence

Claim: every changed figure is a drawing of the mechanism the cell describes, using the cell's own values, and renders in both colour schemes without a paper chip.

Input/fixture: every [[figure_attachments]] and [[journey_section_figures]] entry in docs/quality-registries.toml, rendered by src/marginalia.py at both revisions; rows whose drawing is unchanged once colour, font and textLength attributes are ignored are omitted.

Baseline SHA: 3f300af0562bc6c0b0a0f41ead7ed02a0b147e7b

Current SHA: fb7c0b390d7b5c0804cf2182be3795aededcf3b7

Regenerate: uv run --python 3.13 scripts/build_figure_contact_sheet.py --base main --output docs/pr-evidence/diagram-upgrade-contact-sheet --notes docs/pr-evidence/diagram-upgrade-contact-sheet-notes.json

Before / After Why What to inspect
Light contact sheet: 21 changed figures rendered from main on the left and from this branch on the right, grouped into twelve redrawn and nine adjusted rows, with captions, element counts and a note per row The twelve redrawn rows replace labelled boxes or invented values with the mechanism the cell describes; the nine adjusted rows lengthen an arrow or remove an accent. In the redrawn group, whether each right-hand figure can be read in two seconds and whether its labels match the cell's code. In the adjusted group, only that the arrow now has a shaft and that no orange remains on an arbitrary row.
Dark contact sheet: the same 21 rows under prefers-color-scheme dark, with the main version on the light paper chip the site used to draw and the branch version recoloured through the figure tokens The left column shows the chip that dark mode used to draw behind every figure; the right column is the page's own dark ink and accent. That every stroke, fill and label on the right is legible against the dark page, and that the soft fills still read as containers rather than highlights.

Independent check: tests/test_marginalia_geometry.py asserts no clipping, no collisions, palette and font discipline, at most one accent, content fidelity, arrow length and token presence for every figure at the head SHA. Hashes for both sheets are in docs/pr-evidence/README.md.

Limitation: the sheets are rendered at one width with macOS fonts and do not show phone-width banners, the subgrid caption alignment on a paired banner, or the About page token list. Whether a redrawn figure is clearer than the one it replaces is a judgement; the per-criterion scores below are mine and the reviewer should form their own.

Legibility review

Scored against docs/example-figure-rubric.md v2; full per-criterion rows in docs/rubric-audit-2026-09-27.md.

figure elements before → after score where it loses
closures 10 → 15 9.5 two frames and two arrows take a beat longer than two seconds
generators 11 → 15 9.5 the prose already says next() resumes to the next yield
string-formatting 14 → 41 9.0 densest figure in the set, at the banner ceiling; scored below the boxes it replaced on density
bytes-and-bytearray 9 → 14 9.5 the prose already states frozen versus mutable
regular-expressions 6 → 17 9.0 pattern row, text row and two drops read the move twice
values 12 → 12 9.0 the cell already prints type(text).__name__
logging 20 → 21 9.5 the output already shows debug missing
recursion 11 → 17 9.0 the output already gives 10; held at 9.0 until reviewed on the page
dicts 15 → 14 9.0 insert and lookup share one caption; the figure draws the insert
sentinel-iteration 13 → 11 9.0 the output already omits the sentinel; held at 9.0 until reviewed on the page

Each redraw is one paint function in src/marginalia.py plus one caption row in the registry, so any one of them reverts alone without touching the tokens, fonts or contracts.

Risk

  • The SVG output of all 115 figures changes (colour attributes, font stacks, textLength), so every social card was regenerated. An HTML shell that embeds a figure without defining the tokens would render it in the browser's default black; Contract 15 covers site.css, and the gestalt and social-card shells embed the tokens through figure_token_css().
  • grid-template-rows: subgrid on .cell-banner figure needs Chrome 117, Safari 16 or Firefox 71. Older browsers ignore it and render the banner as before, without caption alignment.
  • textLength with lengthAdjust="spacing" adds up to 0.05em of letter-spacing on monospace faces narrower than 0.6em, such as Consolas. Wider faces are compressed by the same mechanism; none in the stack is wider.

Not in this PR

The queue in docs/diagram-upgrade-plan.md: state-over-time drawings for context managers and decorators, env for scope and recursion, mechanisms for the two remaining table figures, parametrising the other reused figures, moving anchors to the cell where the move happens, the wider content check across the 51 remaining attachments with labels absent from their example, and a pairwise ranking to replace the saturated score.

🤖 Generated with Claude Code

adewale and others added 4 commits September 27, 2026 08:47
…contracts

Figures paint with --fig-* CSS tokens instead of literal colours, so one
paint function renders in light mode, dark mode, the gestalt pages and the
social cards; the dark-mode paper chip is gone. Figure fonts follow the
page's own stacks and mono runs are pinned to MONO_ADVANCE with textLength.
Paired figures share a baseline through a two-row subgrid.

Redraw five figures as mechanisms carrying the lesson's own values:
closures as an environment diagram, generators as three next() ribbons on
countdown(3), the format spec as a real railroad, bytes as integer slots
with the in-place write, and the regex figure as capture groups landing on
"Ada: 10". Binding figures come from one parametrised factory so variables
and constants show their own names.

Fix nine arrows shorter than the new 20-unit minimum, drop the accent from
five selector figures where it marked an arbitrary row, and demote four
table-shaped figures (values, literals, logging, collections-module) with
recorded rationales.

Three new contracts: figure text must match the example source (no doubled
backslashes; escape sequences must appear in the source), every arrow is at
least ARROW_MIN, and site.css defines every figure token in both schemes.
The bytes figure had shipped doubled backslashes and bytes the example
never used, and the regex figure a pattern it never used; no geometry
contract could see either.

Rubrics gain a table gate, a zero-accent rule and a placement-honesty
note; the synthesis and queue live in docs/diagram-upgrade-plan.md.

Fixes #18

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
scripts/build_figure_contact_sheet.py renders every attached figure from a
base revision and the working tree, keeps the rows whose drawing changed
once colour, font and text-length attributes are ignored, and captures the
sheet in both colour schemes through the existing headless-Chrome script.
docs/pr-evidence gains the light and dark sheets for the diagram upgrade.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…re the redraws

Restore figures on the four pages the previous commit left with a
rationale instead of a drawing: values and logging are redrawn as
mechanisms (the cell's text → str binding with the type() hop; the cell's
three records crossing the handler's INFO gate), and literals and
collections-module keep their tables at 8.5 until a mechanism replaces
them. The no_figure_rationales registry is empty again and the rubric
now says a weak figure is redrawn, not removed.

Lighten the format-spec railroad from 53 elements to 35 by drawing bypass
rails only over the stations the cell's 05.1f skips. Drop the "text" tag
from the regex figure. Anchor the bytes figure to the cell that mutates
packet.

Score every changed figure criterion by criterion against the v2 rubric,
record the totals and the losing criteria in the registry, and add a
fresh rubric audit snapshot with the per-criterion table.

Group the contact sheet into redrawn and adjusted rows with a note under
each, so the eight drawings that need judgement are separated from the
eleven that need a glance.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…estly

Values lists only upper(), the method the example calls; generators
shows the two next() calls the cell makes, not three; the railroad names
its station precision, as the format-spec mini-language does.

Three more figures drew values their example never computes and are
redrawn from the cell: recursion shows the example's tree at the deepest
frame of total(tree) with each frame's return value (it drew
factorial(3)); dicts shows the cell's keys hashed to slots with the
insert of "year" (it drew keys a to d and a collision); sentinel
iteration shows read_chunk yielding "py" and "thon" and stopping on ""
(it drew iter(read, '')).

Format-spec and regex move from 9.5 to 9.0: the review called them the
densest redraws, and the scores now say the same. The audit snapshot's
per-criterion table covers all fourteen changed figures.

The contact sheet classes a row as redrawn when its labels change, not
only when its element count does; the spec's CSS sample uses max-width
as its own invariant requires; the prototypes docstring describes the
banner layout that ships.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@adewale adewale changed the title Upgrade the figure grammar: theme tokens, mechanism redraws, content contracts Draw figures from their cells, paint them with theme tokens, and check figure content Sep 29, 2026
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.

bytes-and-bytearray figure renders doubled backslashes

1 participant