Skip to content

Add marginalia gestalt page#1

Open
adewale wants to merge 16 commits intomainfrom
claude/tuftean-marginalia-viz-TB0fw
Open

Add marginalia gestalt page#1
adewale wants to merge 16 commits intomainfrom
claude/tuftean-marginalia-viz-TB0fw

Conversation

@adewale
Copy link
Copy Markdown
Owner

@adewale adewale commented May 9, 2026

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

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
@adewale adewale force-pushed the claude/tuftean-marginalia-viz-TB0fw branch from f6e591d to 50ed727 Compare May 10, 2026 12:38
adewale and others added 15 commits May 10, 2026 14:00
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.
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.

2 participants