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

claude and others added 16 commits May 9, 2026 00:26
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
Cloudflare Workers static assets serve files from public/, so the
gestalt review page belongs there to be reachable in a deployed
preview.

https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
A diagram set needs a grammar, not a collection of one-off layouts. The
hand-drawn cards drifted in stroke weight, cell size, type-tag placement,
and arrow style because each was a bespoke sketch. This change makes
drift impossible.

- scripts/marginalia_grammar.py defines TOKENS (private marks),
  WORDS (name_box, object_box, cell, register, node, frame, gate,
  ribbon, lane), and PHRASES (bind, dispatch, lanes). Palette,
  stroke weights, geometry, and typography are locked at module
  level. Cards may not emit raw SVG.
- scripts/build_marginalia.py declares every journey and example as
  a small function that composes Canvas methods. Drops the
  hand-written 1668-line HTML to a 401-line generated file.
- public/marginalia-gestalt.html is now build output.

Run with: python3 scripts/build_marginalia.py

https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
- docs/visual-explainer-spec.md captures the design: figures live in the
  page's implicit outer margin via position: absolute, leaving today's
  centered layout bit-for-bit unchanged. Below 1440px viewports they
  collapse into a .margin-collected section after the playground.
  Contributors keep authoring example markdown unchanged; figures are
  curated separately by the project owner in src/marginalia.py.
- src/marginalia_grammar.py moves from scripts/ so the Worker can import
  it at request time. scripts/build_marginalia.py keeps generating the
  gestalt review page from the same grammar.
- src/marginalia.py defines the figure registry (FIGURES) and the
  attachment map (ATTACHMENTS: slug -> [(anchor, figure_name, caption)]).
  A first figure, aliasing-mutation, attaches to the mutability slug's
  cell-0 as an end-to-end smoke test.
- src/app.py renders each cell's anchored figure inline (aria-hidden so
  screen readers see only the canonical copy) and appends the collected
  section after the playground using a new __MARGIN_COLLECTED__ template
  slot in src/templates/example.html.
- public/site.css adds .margin-anchor + .margin-collected rules with the
  1440/1600/1800px breakpoints and a print fallback.

https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
A polish pass after applying the impeccable/polish protocol. Key issue:
edges between nodes were hand-coordinated, so the operators tree's lines
visibly missed the circles. The fix lifts the geometry into the grammar.

- src/marginalia_grammar.py adds Canvas.connect(ax, ay, ar, bx, by, br),
  which computes line endpoints from the line of centers and terminates
  exactly at each circle's boundary plus an optional offset. Misalignment
  becomes structurally impossible: cards no longer hand-pick line
  coordinates.
- scripts/build_marginalia.py replaces hand-coded strokes in e_operators
  (tree edges) and e_iterators (state-machine arrows with offset=2) with
  connect() calls.
- e_inheritance ghost diamond endpoints corrected to A's bottom-center
  (160, 28) instead of (150, 28) and (170, 28).
- e_values third box repositioned to honour the 16px gap (192 not 196).
- e_dataclasses, e_recursion, e_unpacking: stacked-cell row strides now
  match cell heights (no more random 2px gaps between adjacent cells).
- e_break_continue: continue arrow changed from a diagonal off-frame line
  to a clean vertical "back to top" arrow; break arrow caption tightened.

https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
Side-by-side view at /operators-polish-comparison.html showing the same
five-node tree with hand-coded line endpoints (red markers) versus
endpoints computed by Canvas.connect() (green markers, tangent by
construction). Lets reviewers verify the alignment fix visually.

https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
Editing src/marginalia.py (figure registry, attachments) or
src/marginalia_grammar.py (Canvas grammar) changes runtime HTML output,
but they were absent from html_version()'s content_paths. The Worker's
in-memory HTML cache would serve stale figures after a deploy. Add both
files to the digest so the cache version moves whenever either changes.

Verified: a probe edit to src/marginalia.py now flips HTML_CACHE_VERSION;
removing the probe restores it.

https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
- public/_headers gains a /prototyping/* rule with
  Cache-Control: no-cache, must-revalidate so review pages always
  show the latest deploy. Generated by fingerprint_assets.py.
- public/marginalia-gestalt.html and operators-polish-comparison.html
  move into public/prototyping/.
- scripts/build_marginalia.py writes to the new location.
- src/marginalia.py adds two new figures, iterator-unroll and
  scope-rings, used by the journey spread prototype.
- scripts/build_prototypes.py is a new builder producing seven
  prototypes plus an index, all under /prototyping/:
    index.html                 listing of all prototypes
    layout-margin.html         floats figure into the implicit outer
                               margin (the planned approach)
    layout-inline-above.html   figure block above prose
    layout-inline-between.html figure between prose and code
    layout-third-column.html   figure as a third lp-cell column
                               (squeezes prose+code)
    layout-summary-bottom.html figures collected after playground
                               (the spec's mobile fallback)
    journey-spread.html        Streams journey with per-section
                               figure-icons next to each heading
  Each prototype renders the real lesson content (mutability or the
  Streams journey) so the layout strategies can be compared on
  representative material.

https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
Empty commit to nudge the Preview viz workflow. The live preview at
viz-pythonbyexample.adewale-883.workers.dev still references the
pre-session site.css fingerprint (89fbe5e5f734), so the last five real
commits haven't been deployed.

https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
The pywrangler proxy auto-runs `sync` for `dev` and `deploy`, but not
for `preview`. So the Worker bundle was uploaded without its vendored
Python dependencies, which is the most likely cause of the silent
upload failures keeping the viz preview pinned to the pre-session
fingerprint (site.89fbe5e5f734.css).

Changes:
- New step: Verify Cloudflare auth (wrangler whoami) so token issues
  surface early instead of silently failing the upload.
- New step: Sync Python Workers vendor (pywrangler sync) before
  pywrangler preview, replicating what `deploy` does automatically.
- Upload step now uses `set -x` so the actual command is visible in
  the run log.
- New step: Dump wrangler logs on failure (.config/.wrangler/logs/*.log
  contains the detailed wrangler error trace; surfacing it removes the
  next round of guess-and-push).

https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
The marginalia grammar had drifted onto its own slate/coral palette
(#2b3441 / #c85a4a / #ececea), unrelated to the site's warm browns
and brand orange (#521000 / #FF4801 / #F5F1EB). Every prototype and
the gestalt page inherited the drift. Fixed by remapping the grammar's
four palette constants onto site tokens, and removing every stray
neutral rgba from the prototype scaffolds.

Mapping:
  INK        #2b3441 → #521000              (--text)
  INK_SOFT   #6e7888 → rgba(82, 16, 0, .7)  (--muted)
  EMPHASIS   #c85a4a → #FF4801              (--accent)
  SOFT_FILL  rgba(0,0,0,.06) → rgba(255, 72, 1, .08)  (--accent-soft)

Other touches:
- gestalt page CSS now mirrors site palette (--paper #F5F1EB, --rule
  #EBD5C1) and uses var(--rule) for section dividers instead of a
  hardcoded grey
- prototype banner background swapped from rgba(0,0,0,.04) to
  var(--accent-soft) so it pulls from the same theme
- operators-polish-comparison drops the bespoke --bad/--good colours
  in favour of a single accent paint, distinguishing before from
  after by hollow ring vs filled dot rather than by hue. Caption text
  ("red dots" / "green dots") updated accordingly

Audit: grep over public/prototyping/**/*.html now finds only
#521000, #EBD5C1, #F5F1EB, #FF4801, rgba(255, 72, 1, 0.08), and
rgba(82, 16, 0, 0.7). Six values, all site tokens.

https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
User feedback: the implicit-outer-margin approach was unnecessary, and
its "extra columns" displaced the code on viewports where the figure
forced a multi-row grid. The inline-between layout demonstrated by the
prototype works at every width without breaking anything.

Production switch:
- src/app.py: new _render_walkthrough_cell helper. When a figure is
  attached, the cell gets the has-figure class and the figure renders
  between .lp-prose and .cell-code-stack
- public/site.css: replace the margin-anchor / margin-collected /
  @media (min-width: 1440px) rules with two simple ones —
  .lp-cell.has-figure { grid-template-columns: 1fr; } and .cell-figure
  styling. Cells without figures keep today's prose|code grid bit-for-bit
- src/marginalia.py: render_for_anchor emits .cell-figure with figcaption
  visible (no aria-hidden); render_collected removed
- src/templates/example.html: drop __MARGIN_COLLECTED__ slot
- docs/visual-explainer-spec.md: rewritten for the inline approach;
  no viewport-conditional behaviour, no JS, no overlay layer

Grammar tightening (the gestalt had become too vivid):
- src/marginalia_grammar.py: closed_arrow now defaults to emphasis=False
  so figures must opt in to accent strokes for the single live element.
  SOFT_FILL changes from rgba(255,72,1,0.08) (orange-tinted, made every
  object box look highlighted) to rgba(82,16,0,0.05) (neutral warm tint
  that reads as a quiet container). Both changes restore the
  "emphasis is scarce" rule against the brighter --accent.

Five new figures, designed to land where they teach:
- closure-cell    — outer scope holds a cell; inner function references it
- slice-ruler     — indices between cells; [:3] and [3:] partition at 3
- branch-fork     — value flows through predicate to one of several branches
- loop-repetition — walk the sequence, run the body, loop back
- iter-protocol   — iterable → iter() → next() … values

Prototype refresh:
- Drop layout-margin, layout-inline-above, layout-inline-between,
  layout-third-column, layout-summary-bottom, journey-spread (all
  superseded or rolled-back layouts)
- Add four real example renderings, one per slug, each demonstrating
  the canonical inline-between layout on representative content:
    /prototyping/example-mutability.html  (aliasing-mutation, cell 0)
    /prototyping/example-closures.html    (closure-cell, cell 0)
    /prototyping/example-for-loops.html   (iterator-unroll, cell 1)
    /prototyping/example-slices.html      (slice-ruler, cell 0)
- Replace journey-spread with journey-streams using three figures that
  actually map to the section concepts: branch-fork for "Make decisions
  explicitly", loop-repetition for "Choose the right loop shape",
  iter-protocol for "Recognize iteration as a protocol"

https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
Following the impeccable/layout protocol — distinct structure, distinct
hierarchy, distinct rhythm per variant, not just "same layout, figure
moved". Squint test: each variant should be distinguishable by overall
shape alone.

All five render the same mutability lesson with the aliasing-mutation
figure on cell 0, so only the layout differs:

  layout-above           figure leads the cell, then prose, then code.
                         Single-column stacking. Eye flow: picture →
                         intuition → code.

  layout-after-output    figure ends the cell as a visual summary after
                         the output. Single-column stacking. Eye flow:
                         everything → tying-it-together picture.

  layout-banner-top      figure spans both columns as a banner above;
                         prose and code keep their 2-column grid below.
                         Eye flow: illustrated header → 2-up reading.

  layout-banner-bottom   2-column prose|code above, figure spans both
                         columns as a footer. Eye flow: 2-up reading →
                         illustrated summary.

  layout-prose-aside     figure floats right inside the prose column
                         with paragraphs wrapping around it; code-stack
                         continues beside in its own column. Eye flow:
                         prose with embedded illustration, code beside.

The render_cell helper takes a figure_position parameter and emits the
right DOM order plus a variant class on the .lp-cell. Per-variant CSS
overrides live in VARIANT_CSS and are inlined into each prototype's
<style>. Production rendering is unchanged: example pages still use the
"between" layout via the existing has-figure class.

Index page updated to list the five new variants alongside the
canonical example-mutability prototype for direct comparison.

https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
… pages

Each of the six journeys now has its own prototype page with three
section figures that actually depict the section's conceptual shift —
not recycled lesson figures.

Fifteen new figures added to src/marginalia.py:

  Runtime
    program-output         source produces visible output
    identity-and-equality  same object vs equal objects
    operator-dispatch      a + b becomes a.__add__(b)

  Shapes
    container-questions    list/tuple/dict/set, each answers a different question
    reshape-pipeline       input → transform → result
    text-data-boundary     text in, structured value out

  Interfaces
    function-signature     args → body → return
    function-as-value      a second name binds to the same function object
    class-with-state       state and methods bundled behind one interface

  Types
    annotation-ghost       annotations describe; runtime accepts any object
    union-types            int | str | None — slot accepts one of several
    generic-preservation   the same T flows in and out of fn[T]

  Reliability
    exception-lanes        try/except/else/finally as parallel lanes
    context-bowtie         enter → body → exit; raise still routes through exit
    async-swimlane         loop and coroutine swap on await

Streams keeps the three figures shipped earlier (branch-fork,
loop-repetition, iter-protocol).

build_prototypes.py refactored: a single JOURNEY_SECTION_FIGURES map
keys section title → (figure_name, caption); build_journey(slug)
renders one prototype page per journey. Six pages added under
/prototyping/journey-{runtime,streams,shapes,interfaces,types,reliability}.html
plus index entries.

https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
Single rule: the cell stays a 2-column unit always; figures live BETWEEN
cells in banner rows that span both columns. A banner row holds one OR
many figures via an auto-fit grid. No more switching between 1, 2, and
3 column layouts within a page.

This is the intended union the user asked for:
- Knuth   — cells preserve the literate prose|code rhythm uninterrupted
- Tufte   — banner accepts a small-multiple of related figures
- Algebrica — quiet italic caption beneath each figure, generous
            whitespace above and below as a teaching pause

Three new prototypes on the mutability lesson, all with the same cell
content; only the banner configuration varies:

  /prototyping/layout-banner-single  one banner with one figure
                                      between cells 0 and 1
  /prototyping/layout-banner-pair    one banner with two figures —
                                      list mutates, tuple does not —
                                      a Tufte small-multiple
  /prototyping/layout-banner-trio    multiple banners across the
                                      walkthrough (lead-in, mid, summary)
                                      proving cells never reflow

Implementation:
- Added tuple-no-mutation figure to src/marginalia.py to pair with
  aliasing-mutation for the small-multiples demo
- scripts/build_prototypes.py: new banner() helper for 1+ figure rows;
  render_article gains a banners= parameter keyed by position
  (before, after-cell-N, after-walkthrough); BANNER_CSS supplies the
  auto-fit grid + dashed top/bottom border for visual rhythm
- docs/visual-explainer-spec.md rewritten: two-pattern grammar
  (banners between cells for example pages, figure beside heading
  for journey pages); the earlier inline-* / margin-overlay variants
  catalogued as the perils of switching column models

Production rendering is untouched in this commit — banner-between
lives in prototypes for review before migration.

https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
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