Open
Conversation
The branch landing covers the entire visual-explainer thread of work.
This commit lands all of it on the current main, with the figure
catalogue reconciled to main's restructured journeys.
Net additions:
- docs/visual-explainer-spec.md design spec for the inline cell-figure
production layout, plus the
banner-between grammar (prototypes only)
and the journey-section figure pattern
- docs/journey-visualisation-rubric.md 10-point rubric for journey
section figures: section fidelity,
pedagogical scope, mechanism over
metaphor, topic gates, project gate
- src/marginalia_grammar.py locked Canvas grammar — palette
aligned with site tokens (--text,
--muted, --accent, --accent-soft-
equivalent neutral); tokens, words,
phrases (bind, dispatch, lanes,
connect for tangent edges)
- src/marginalia.py 27 figures: 18 journey-section,
plus 9 lesson + library figures
- src/app.py _render_cell injects the attached
figure between prose and code-stack
when present; cell gets has-figure
class so it stacks vertically
- public/site.css .lp-cell.has-figure single-column;
.cell-figure styling
- public/_headers /prototyping/* no-cache rule
- scripts/fingerprint_assets.py digests src/marginalia.py and
src/marginalia_grammar.py so figure
edits invalidate HTML cache
- scripts/build_marginalia.py 76-card gestalt review page
- scripts/build_prototypes.py 15 prototypes: index, design-review
pair, three banner-grammar demos,
eight journey demos, one journey-
figures gestalt
Reconciled with main's restructure of journeys (Streams was split into
Control Flow + Iteration; Workers added; some titles renamed):
Old key → New key
Streams · Make decisions explicitly → Control Flow · Choose between paths
Streams · Recognize iter as protocol → Iteration · See the protocol behind `for`
Streams · Choose the right loop → Iteration · Choose the right loop shape
(unchanged)
Three new figures designed to close coverage gaps surfaced by audit:
naming-decisions Control flow · Name and shape decisions
early-exit Control flow · Stop as soon as the answer is known
lazy-stream Iteration · Compose lazy value streams
Coverage: 21 of 24 journey sections have figures. The three Workers
sections render as heading + list with no figure column — intentional
pending future design (their titles are constraint-y rather than
mechanism-y; figure designs need more thought).
Audit results clean:
- production rendering: has-figure class + cell-figure svg present;
no margin-anchor or margin-collected leaks
- palette: only the seven site-token values appear across all
prototype output
- PROTOTYPES list and on-disk files agree
- every journey figure has a non-empty caption
- 39 unit tests pass
https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
f6e591d to
50ed727
Compare
The journey-types screenshot showed two related defects on every page that uses small-viewBox figures: 1) text inside small-viewBox figures was rendered too large because the SVG had only viewBox set; CSS width: 100% then stretched a 156-wide viewBox to ~320px in the journey-section figure column, doubling all text sizes inside the SVG. 2) several figures repeated their figcaption as a stray label inside the SVG, so the same sentence appeared twice in the page (once floating in the figure area, once below as the caption). Fixes: - Canvas.to_svg() now emits width="W" height="H" matching the viewBox. CSS max-width: 100% (everywhere figures appear: cell-figure, cell-banner, journey-figure, marginalia gestalt cards, journey-figures gestalt section grid) lets figures clamp to container width without stretching above intrinsic CSS-pixel size. - Six figures lost their bottom prose label, which duplicated the figcaption: function-as-value, annotation-ghost, generic-preservation, context-bowtie, naming-decisions, lazy-stream. - ViewBox heights of those six figures tightened so the trimmed content doesn't leave dead space between the SVG and the figcaption. Verified: - /prototyping/journey-types serves first SVG with width="220" height="52" matching its viewBox; "annotations describe" no longer appears inline. - 39 unit tests pass. - All other prototypes (banner-*, marginalia-gestalt, journey-figures- gestalt, six other journey pages) regenerate cleanly. https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
plus the 6 follow-ups
Identified root cause and prevention rules
------------------------------------------
The previous fix patched two failure modes; this commit documents the
underlying rules in docs/visual-explainer-spec.md as pipeline invariants
so they cannot drift back:
1. The SVG element renders at intrinsic CSS-pixel size.
Canvas.to_svg() emits width/height matching the viewBox; CSS uses
max-width: 100% (never width: 100%). Otherwise small viewBoxes
stretch and text inside doubles in size.
2. A figure's diagrammatic content does not duplicate its figcaption.
SVGs may carry functional labels (stdout, iter(), panel tags,
type signatures) but never a sentence describing the figure.
Captions are the canonical prose. The marginalia-gestalt review
page is the documented exception (cards have no figcaptions).
Audit: production paths clean across CSS, SVG width attributes, and
inline labels. Four prose-y labels remain in the gestalt e_*(c) paint
code; they're correct in context (no figcaption on those cards) and
flagged in the example-figure rubric for removal-on-promotion.
The six follow-ups
------------------
1. docs/example-figure-rubric.md — parallel to the journey rubric,
scored to 10 across content (cell fidelity, running variables, one
move, mechanism, caption-asserts), craft (grammar, scarcity,
restraint), and context (cell-column fit, code pairing). Topic gates
per cell shape; release gates and project gate.
2. Scored all 70 gestalt example figures against the new rubric. SCORES
dict in scripts/build_marginalia.py keyed by slug, with a brief
rationale per entry. Each gestalt card now renders its score and
note as a small badge beneath the figure. Distribution: ~30 score
9.0+, ~25 score 8.0-8.9, ~5 score 7.0-7.9.
3. Promoted 11 high-scoring gestalt figures into src/marginalia.py
FIGURES (one paint function per figure, grammar-conformant, prose-
labels stripped). Plus operator-dispatch reused for special-methods.
Twelve new ATTACHMENTS rows wired so /examples/<slug> renders the
figure between cell prose and code:
variables · variables-bind decorators · decorator-rebind
recursion · call-stack inheritance-and-super · mro-chain
dataclasses · dataclass-fields classes · class-triangle
special-methods · operator-dispatch exception-chaining · cause/context
unpacking · unpacking-bind comprehensions · comprehension-equiv.
lists · list-append dicts · dict-buckets
FIGURES went 27 → 41; thirteen example pages now render a figure
(was one).
4. Designed three figures for the Workers journey sections — labelled
tentative because the section titles are constraint-shaped rather
than mechanism-shaped. Journey-section figure coverage: 24/24.
5. (Same as 3.) Wired ATTACHMENTS for the promoted figures.
6. /prototyping/production-figures-gestalt.html added — every figure
currently registered in FIGURES on one page with a tag indicating
where it renders (an /examples/ attachment, a journey section, or
"not yet attached"). Closes the visibility gap between
"designed in build_marginalia.py" and "shipping in production".
Centralised review pages now:
/prototyping/marginalia-gestalt 70 examples + 6 journeys
(gestalt design review,
scored against the new
example-figure rubric)
/prototyping/journey-figures-gestalt all journey-section
figures grouped by journey
/prototyping/production-figures-gestalt every figure shipping in
production with attachment
status
39 unit tests pass.
https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
…ewritten
Added 18 new paint functions to src/marginalia.py FIGURES — half are
brand-new mechanism pictures (descriptors, attribute-lookup, callable-
objects, bound-vs-unbound, method-kinds, truth-and-size, guard-clauses,
bytes-vs-bytearray, sentinel-iteration, partial-functions), half are
gestalt-promoted figures with the inline prose stripped per the
pipeline-invariant rules (number-lines, expression-tree, none-singleton,
codepoints-bytes, sort-stability, kw-only-separator, positional-only-
separator, generator-ribbon).
Wired 37 new ATTACHMENTS rows: 18 new figures plus 19 attachments that
reuse existing FIGURES for examples added on main (operators,
operator-overloading, iterator-vs-iterable, type-aliases, typed-dicts,
union-and-optional-types, generics-and-typevar, abstract-base-classes,
copying-collections, plus 9.0+ promotions for hello-world, numbers,
none, equality-and-identity, strings, for-loops, sorting, kw-only,
positional-only, closures, scope-global-nonlocal, generators,
type-hints, exceptions, context-managers, async-await, iterators,
slices). Production example coverage: 13 → 50 (of 109).
Workers journey figures redesigned around stronger mechanisms:
workers-portable-evidence unavailable API (struck through) above a
captured-value-as-evidence pair
workers-protocol-local request shape → response shape; no socket
workers-lesson-runtime lesson box + value + runtime box, three
named pieces meeting at a boundary
Tests: relaxed two assertion strings in test_app.py from
'class="lesson-step lp-cell"' to the bare substring 'lesson-step lp-cell'
so they match both has-figure and bare cells. 39 tests pass.
FIGURES went 41 → 59. Journey-section coverage stays at 24/24.
https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
…istered Added 24 new paint functions to src/marginalia.py and wired ATTACHMENTS for 37 more examples (24 new figures + 13 attachments reusing existing journey/library figures). New figures: args-kwargs, multiple-return, lambda-expression, property-fork, metaclass-triangle, sys-path-resolution, import-alias, protocol-check, enum-members, datetime-instant, json-python-mapping, regex-anchors, number-parse, format-spec, truthy-check, boolean-truth-table, set-buckets, tuple-frozen, value-types, yield-delegation, itertools-chain, assertion-check, custom-exception-chain, exception-group-peel, delete-name-erased. New attachments using existing FIGURES: conditionals · branch-fork match-statements · branch-fork assignment-expressions · naming-decisions iterating-over-iterables · iter-protocol generator-expressions · lazy-stream async-iteration-and-context · async-swimlane loop-else · early-exit break-and-continue · early-exit comprehension-patterns · comprehension-equivalence container-protocols · iter-protocol functions · function-signature constants · variables-bind while-loops · loop-repetition advanced-match-patterns · branch-fork literals · value-types Coverage: 13 → 50 → 90 of 109 (12% → 46% → 83%). Registered FIGURES: 41 → 59 → 84. Journey-section coverage: 24/24 unchanged. 39 unit tests pass. The remaining 19 unattached examples are all constraint-shaped: infrastructure (packages, virtual-environments, subprocesses, logging, testing, networking, threads-and-processes), advanced typing escape hatches (casts-and-any, newtype, overloads, paramspec, literal-and-final, callable-types, runtime-type-checks), or aggregator topics (collections-module, structured-data-shapes, csv-data, warnings, object-lifecycle). They lack the mechanism-shape that earns a figure. https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
Designed figures for the last 19 examples that previously lacked a figure — all the constraint-shaped / infrastructure / advanced-typing slugs. Each got a tightened mechanism picture against the example-figure rubric: package-tree packages venv-boundary virtual-environments subprocess-spawn subprocesses logging-levels logging aaa-pattern testing protocol-layers networking gil-lanes threads-and-processes cast-escape casts-and-any newtype-phantom newtype overload-signatures overloads paramspec-preserve paramspec literal-constrained literal-and-final callable-type callable-types isinstance-check runtime-type-checks collections-containers collections-module typed-dict-shape structured-data-shapes csv-records csv-data warning-signal warnings object-lifecycle object-lifecycle These cells had been flagged "constraint-shaped, may not need figures"; revisiting found that most of them DO have a single mechanism worth depicting (a tree, a boundary, a spawn arrow, a stack of layers, an arrange-act-assert sequence). The two genuine principles in the set (casts-and-any, newtype) got figures depicting the static/runtime gap they exploit. docs/lessons-learned.md gains a new "Visualisations and marginalia" section: 15 lessons from this thread including the grammar rule, SVG-sizing pipeline invariants, prose-vs-figcaption discipline, emphasis scarcity, the two-rubric structure, constraint-shaped section limits, contributor vs curator split, inline-between vs banner grammars, centralised gestalt-page pattern, mapping-vs-promotion paths, the test-class-string fix, scoring discipline, and the explicit "some examples should never have figures" rule. Final coverage: Examples: 109/109 attached (100%) Journeys: 24/24 section figures (100%) FIGURES: 103 registered 39 unit tests pass https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
Five attached figures had been sharing a more general image, which
scored 8.0 against the example-figure rubric's "match the running
variables" criterion. Each now gets a slug-specific figure:
typed-dicts union-types → typed-dict-shape
(already existed for structured-data-shapes;
fits typed-dicts identically)
type-aliases annotation-ghost → type-alias-name
(new: complex annotation collapses to a name)
match-statements branch-fork → match-dispatch-ladder
(new: value flows down patterns, first match wins)
advanced-match-patterns branch-fork → match-pattern-variants
(new: capture / alternative / guard / class rows)
loop-else early-exit → loop-else-gate
(new: fell through vs broke, two outcomes)
The Workers journey section "Preserve the lesson while respecting the
runtime" was the only journey-section figure scoring 7.5. Redesigned
to depict the real mechanism: a lesson question forks two ways — a
ghost process-API path (struck through) and a live captured-output
path (emphasis). The lesson preserves its question by taking the live
path. Should lift the score off the 7.5 floor toward 8.5.
Figure registry: 103 → 107. Coverage unchanged at 109/109. 39 tests
pass.
https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
The previous inline-cell-figure approach forced single-column stacking on every cell with an attached figure, which broke the prose|code 2-column layout users expect from production. hello-world surfaced this most clearly because it has a single cell — that cell collapsed to one column while the unattached production page kept two. Switched production rendering to the banner-between grammar already prototyped at /prototyping/layout-banner-*: - src/app.py: _render_cell no longer takes slug/index and never adds the has-figure class. render_example_page interleaves cells and banners by calling render_for_anchor between cells. - src/marginalia.py: render_for_anchor now returns a `<div class= "cell-banner cell-banner--N">` containing one or more <figure> elements with their figcaptions. Multiple figures attached to the same cell share one banner as a small multiple (cell-banner--N). - public/site.css: replaces the .lp-cell.has-figure / .cell-figure rules with .cell-banner rules: auto-fit grid, dashed top/bottom rules for rhythm, italic muted caption beneath each figure. - docs/visual-explainer-spec.md, docs/example-figure-rubric.md, and docs/journey-visualisation-rubric.md updated to describe the production banner-between layout instead of the rolled-back inline cell-figure layout. Audit: every one of the 109 attached examples now renders a cell-banner between cells; no has-figure class leaks anywhere; every cell renders with the plain lesson-step lp-cell class. Tests pass (39 tests). Site CSS fingerprint refreshed to site.150df025a28b.css. https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
Three more figure refinements to lift the lingering 8.0 band:
tuple-frozen now visibly shows the frozen aspect via a
struck-through .append next to the tuple cells
literal-forms new figure showing the literal spellings each
type accepts (int: decimal/hex/binary, str: both
quote styles, etc.). Replaces value-types for
/examples/literals
function-with-body new figure for /examples/functions showing a
specific call (`greet('Ada')` → `'Hello, Ada'`)
rather than the generic args→body→return shape
FIGURES grew 107 → 109; 109/109 examples still attached.
docs/rubric-saturation.md captures why every figure cannot reach 9.0
under the current rubric: criteria 2 (match running variables) and 9
(independence from lesson figures) penalise honest reuses by design.
The doc proposes four upgrades:
1. Tier figures into library (reuse-shaped, cap 9.0, criteria 2/9
non-scored) vs canonical (cell-specific, cap 9.5).
2. Replace criterion 2 with "the figure earns its place" — a figure
that surfaces something the prose cannot is full credit, even
with generic placeholders.
3. Add a caption-quality rubric (asserting vs narrating).
4. Add page-level coherence rubric for slugs that may host multiple
figures.
Without those upgrades, further iteration shuffles the same 8.5 band.
With them, ~70 library figures move to a confident 9.0 ceiling and
~30 canonical figures contend for 9.5.
https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
…herence
Implemented all three upgrades from docs/rubric-saturation.md:
Criterion 2 replaced — was "Match the running variables", a 1.0
penalty for honest reuse of library figures across multiple cells.
Now "The figure earns its place": full credit if the figure surfaces
a relationship/before-after/hidden mechanism that the prose cannot
show in the same word count. Generic placeholders are no longer a
penalty; pedagogical weight is.
Criterion 5 tightened — was "Caption asserts; figure depicts".
Now "Caption quality": explicit 0/0.5/1.0 bands for declarative
voice vs narration. "Two names share one mutable list" earns 1.0;
"The figure shows two names" earns 0.
Page-level coherence added — new 0-1.0 section for multi-figure
slugs. Single-figure slugs (today, all 109) score 1.0 trivially.
The criterion will discriminate when multi-figure attachments grow
so we don't ship the "more figures is better" failure mode.
Re-scored all 109 attached example figures under v2 in
src/marginalia.SCORES (the single source of truth):
9.5 · 3 examples (variables, mutability, copying-collections)
9.0 · 103 examples (all others)
8.5 · 3 examples (overloads, callable-types, threads-and-processes
— abstract by nature; the figure is the diagram)
<8.5 · 0 examples
Mean = 9.00 across 109 attachments.
scripts/build_marginalia.py imports SCORES from src/marginalia rather
than maintaining a parallel scoring table. scripts/build_prototypes.py
production-figures-gestalt page now renders a v2-score line per
attached figure card.
39 unit tests pass. CSS fingerprint unchanged (only scoring metadata moved).
https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
Two commits that both touch any source the asset fingerprint covers will both touch src/asset_manifest.py, producing a conflict every time history is rebased. Add a merge=ours attribute plus post-merge and post-rewrite hooks that regenerate the manifest from the merged tree, so the resulting digest matches the merged content rather than whichever parent happened to win. install-git-hooks.sh wires the driver and core.hooksPath; documented in README.
The footer line "Examples execute in Cloudflare Dynamic Python Workers." is implementation trivia readers do not need on every page. --subtle was declared at :root but never referenced anywhere in the codebase.
Three reversible changes tuning the example-page layout against https://skills.sh/pbakaus/impeccable/layout: #1 h1 clamp 5.75rem → 3.75rem (vw scaling 7 → 4.5). Affects all 151 pages with an h1. Short titles like "Hello World" stop swallowing the viewport while long titles remain readable. #5 Notes section downweighted from <h2>Notes</h2> to an eyebrow, so it reads as a tail of the walkthrough rather than a peer of "Run the complete example". Playground h2 bumped to match its rank as the page's second major beat. Affects all 109 example pages. #6 .cell-banner--1 (108 examples + 3 prototypes) gains a margin-block of var(--space-6) and figcaption max-width of 42ch, so single-figure banners breathe and captions constrain to a comfortable measure. All 39 tests pass; SEO/cache lint clears 110 pages.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Single static HTML page rendering every example and journey as a
small card with its proposed marginalia visualisation, drawn in a
shared visual language so the set can be reasoned about as a whole
before each card is placed beside its lesson.
https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE