diff --git a/CHANGELOG.md b/CHANGELOG.md
index f6e6f58..167bef0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,8 +13,15 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/en/1.1.0
- A Run whose Turnstile token the server rejects now ends with the server's message instead of re-challenging. The runner used to solve again every time the response carried the challenge marker, so a persistently failing Siteverify (wrong secret, hostname, or action) turned one click into an unbounded loop of solves, Worker POSTs, and Siteverify subrequests. Each Run now earns at most one challenge, and the failure message says to press Run again.
- Siteverify calls time out after 10 seconds, tokens longer than Cloudflare's documented 2048 characters are rejected without a subrequest, and a `success: true` response with a missing or non-string `hostname` fails closed instead of raising a 500. The gaps surfaced by comparing `_verify_turnstile` with the canonical handler in Cloudflare's Turnstile Spin skill.
- Production Python packages are locked. Pywrangler 1.17.4 vendors a committed, hash-pinned `pylock.toml` instead of resolving an unpinned `fastapi` at deploy time, and `uv.lock` pins the same versions (FastAPI 0.141.1, Starlette 1.7.0, Pydantic 2.10.6) so the test suite runs against exactly what ships. `tests/test_dependency_locks.py` fails on drift, and CI and `make deploy` fail if a sync would change `pylock.toml`. Starlette 1.7.0 also clears the five advisories against the previously tested 1.0.0.
+- The bytes-and-bytearray figure rendered doubled backslashes (`b'\\x63\\x61\\x66'`) and drew bytes the example never uses (#18). Redrawn from the example's real values, and three new figure contracts close the class of bug: no rendered text may carry a doubled backslash and escape sequences must appear in the example source (13), every arrow is at least 20 units (14), and `site.css` must define every figure token in both schemes (15).
- Example-page runner wiring (Run interception, Reset, the share button, and keyboard navigation) no longer waits for the CDN-backed highlighter and editor modules: `runner.js` loads `async`, so a slow or unreachable esm.sh cannot stall it — ordered module scripts otherwise execute strictly after every preceding module settles, including their top-level awaits.
+### Changed
+
+- Figures now paint with `--fig-ink`, `--fig-ink-soft`, `--fig-accent` and `--fig-soft` tokens instead of literal colours, and `site.css` defines the tokens for both colour schemes. Dark mode recolours every figure with the page; the light "paper chip" behind figures is gone. Figure mono and sans faces follow the page's own font stacks, and mono runs are pinned to the grammar's character advance with `textLength` so divider positions hold on every platform. Paired figures in a banner share a baseline (a two-row subgrid). Five figures lost an accent that marked an arbitrary row (`naming-decisions`, `iteration-loop-selector`, `type-shape-catalog`, `reliability-signal-map`, `container-methods`); nine arrows that were mostly arrowhead were lengthened to the new 20-unit minimum.
+- Ten figures redrawn as mechanisms carrying the lesson's own values: closures is an environment diagram with the returned frame ghosted and one live `__closure__` reference; generators shows the cell's two `next()` calls on `countdown(3)` with the surviving local beside each; recursion shows the example's tree at the deepest frame of `total(tree)` with the values each frame returns; dicts shows the cell's keys hashed to slots with the insert of `"year"`; sentinel iteration shows `read_chunk` yielding `"py"` and `"thon"` and stopping on the empty string; string formatting's format spec is a railroad tracing the cell's `05.1f`, skipped stations ghosted under a bypass rail; bytes-and-bytearray draws the example's bytes as integer slots with the in-place write and moves to the cell that performs it; regular expressions shows the cell's two capture groups landing on `Ada: 10`; values shows `text` bound to a `str` object and the `type()` hop to where `upper()` lives; logging shows the cell's three records crossing the handler's INFO gate. The binding figure is a parametrised factory, so `variables` shows `message → "hi"` and `constants` shows `MAX_RETRIES → 3`.
+- The rubric states that table-shaped figures are redrawn rather than removed, that a figure whose caption names no single element carries no accent, and how cell-0 anchors are scored. The review, principles and queue are in `docs/diagram-upgrade-plan.md`.
+
### Added
- `/about` page describing how the site is made — the verified-output pipeline, the runner sandbox, the figure grammar, and the quality gates — with a design-language section that renders the live CSS tokens (color swatches, spacing scale, type specimens, and a real walkthrough cell as a specimen). Linked from the nav on every page and listed in the sitemap.
diff --git a/docs/diagram-upgrade-plan.md b/docs/diagram-upgrade-plan.md
new file mode 100644
index 0000000..2678303
--- /dev/null
+++ b/docs/diagram-upgrade-plan.md
@@ -0,0 +1,179 @@
+# Diagram upgrade plan
+
+Written 2026-09-27 from a review of every attached figure on the live
+site, the gestalt review pages, the grammar, both rubrics and the
+registry. The rubrics remain the scoring instruments. This document
+records what the review found, the principles that follow from it,
+what the first pull request changed, and the queue after that.
+
+## What the review found
+
+The library's strengths are enforced rather than hoped for: the palette,
+stroke weights and typography are locked by contracts, captions assert
+what is true instead of narrating the picture, and the gestalt review
+page paints from the production paint functions. The figures that teach
+best show a mechanism changing state, such as aliasing before and after
+an append, the iterator caret advancing one row at a time, and the MRO
+diamond flattening into a chain.
+
+The weaknesses are measurable.
+
+| finding | measure |
+|---|---:|
+| paint functions using only cell, arrow, dashed line and text | 66 of 124 |
+| `cell` calls / `closed_arrow` calls / `lanes` calls | 236 / 129 / 1 |
+| attachments anchored after cell 0 | 106 of 115 |
+| example figures scoring exactly 9.0 (the rest 9.5, none lower) | 106 of 109 |
+| journey figures scoring exactly 9.0 | 21 of 21 |
+| arrows shorter than 20 units | 9 |
+
+Behind the numbers: figures hard-coded the light palette, so in dark
+mode each one sat on a cream chip; a paired banner had no shared
+baseline; the SVG asked for JetBrains Mono and Source Sans while the
+page loaded no web fonts, so the visitor's operating system chose the
+faces; several selector figures put the accent on whichever row the
+loop reached last; and because no contract read figure text, the
+bytes-and-bytearray figure rendered doubled backslashes for months and
+the regular-expressions figure drew a pattern the example never uses.
+
+## Principles
+
+1. **Show state over time.** One frame is a label. The same frame after
+ each step is a mechanism. Repeating one drawing is cheap in this
+ grammar; `iterator-unroll` and `generator-resume` do it.
+2. **Draw the heap.** Python's picture is frames on the left and objects
+ on the right with arrows crossing between them. The grammar now has
+ an `env` phrase for the frame, and closures uses it.
+3. **Redraw a weak figure; never replace it with a note.** A drawing
+ that would be no worse as a two-column table needs a mechanism
+ drawn in its place. Removing it and recording a rationale leaves the
+ page with less than it had, which the first draft of this plan got
+ wrong for four pages.
+4. **The figure carries the lesson's own names.** Reuse the mechanism
+ by parametrising the paint function, so `variables` shows `message`
+ and `constants` shows `MAX_RETRIES`.
+5. **Accent only what the caption names.** When the caption names no
+ single element, the figure has no accent.
+6. **Check figure content against the example.** Geometry contracts
+ cannot see a wrong label. A content contract can.
+7. **Paint with tokens.** The grammar emits `var(--fig-*)`; the page's
+ stylesheet decides the ink in each colour scheme.
+8. **Typography follows the page.** Mono and sans use the page's own
+ stacks, and mono runs carry `textLength` so divider maths holds on
+ every platform. The italic serif stays for identifiers.
+9. **Rank before scoring.** With 106 of 109 figures at 9.0, the score
+ is a ship gate. Choosing what to redesign next needs a ranking.
+
+## What the first pull request changed
+
+Grammar (`src/marginalia_grammar.py`):
+
+- `INK`, `INK_SOFT`, `EMPHASIS` and `SOFT_FILL` are `var(--fig-*)`
+ references. `FIGURE_TOKENS_LIGHT` and `FIGURE_TOKENS_DARK` hold the
+ values; `figure_token_css()` emits them for the gestalt pages and the
+ social-card shell.
+- `FONT_MONO` and `FONT_SANS` match `public/site.css`; every mono run is
+ pinned to `MONO_ADVANCE` with `textLength`.
+- `ARROW_MIN = 20`; `Canvas` records every arrow's length.
+- `name_box` and `bind` take a width so long names fit.
+- The `env` phrase draws a frame with bindings, optionally ghosted, and
+ returns anchors for heap arrows.
+
+Figures (`src/marginalia.py`):
+
+- `closure-cell` is an environment diagram: the returned frame ghosted,
+ `double` naming a heap function object, one accented `__closure__`
+ reference back into the frame.
+- `generator-resume` replaces `generator-ribbon`: three ribbons for
+ three `next()` calls on the cell's own `countdown(3)`, the surviving
+ local beside each.
+- `format-spec` is a railroad tracing the cell's own `05.1f`: visited
+ stations shaded, skipped stations ghosted under a bypass rail.
+- `bytes-vs-bytearray` draws the example's bytes as integer slots and
+ the in-place write `packet[0] = ord("P")`, anchored to the cell that
+ performs it.
+- `regex-groups` replaces `regex-anchors`: the cell's two capture
+ groups dropped onto the record `Ada: 10`.
+- `value-type-lookup` replaces `value-types`: `text` bound to a `str`
+ object, then the `type()` hop to the class where `upper()` lives.
+- `logging-threshold` replaces `logging-levels`: the cell's three
+ records crossing the handler's INFO gate, `debug` dropped.
+- `variables-bind` and `constants-bind` come from one parametrised
+ factory.
+- Nine arrows lengthened to the minimum; five figures lost an accent
+ that marked an arbitrary row.
+- `literal-forms` and `collections-containers` stay as they were, scored
+ 8.5, until a mechanism replaces them.
+
+Page (`public/site.css`, `src/templates/about.html`):
+
+- `--fig-ink`, `--fig-ink-soft`, `--fig-accent` and `--fig-soft` defined
+ for light and dark; the dark-mode paper chip removed.
+- `.cell-banner figure` is a two-row subgrid, so paired figures
+ bottom-align their drawings and start both captions on one line.
+
+Contracts (`tests/test_marginalia_geometry.py`):
+
+- 13, content: no doubled backslash anywhere; an escape sequence in an
+ attached figure must appear in that example's source.
+- 14, arrow length: every arrow at least `ARROW_MIN`.
+- 15, theme tokens: `site.css` defines all four tokens in both schemes
+ with the grammar's values, and every grammar colour is a token.
+
+Rubrics: the redraw rule, the zero-accent rule, the placement note and
+the three contracts are in `docs/example-figure-rubric.md` and
+`docs/journey-visualisation-rubric.md`; the token and typography
+invariants are in `docs/visual-explainer-spec.md`.
+
+## Queue
+
+Ordered by payoff. Each item is one pull request.
+
+1. **State over time** for `context-managers` (enter, body, exit, with
+ the exception path as a second strip), `decorators` (two calls, two
+ wrappers) and `object-lifecycle`.
+2. **Heap drawings** with `env` for `scope-global-nonlocal`, `recursion`
+ (frames with `n` bindings instead of labelled cells) and `unpacking`.
+3. **Mechanisms for the two table figures.** `literals`: the same value
+ spelled three ways converging on one `int` object. `collections-module`:
+ one container's operation, or split the page.
+4. **Parametrise the remaining reused figures**: `iterator-unroll`,
+ `iter-protocol`, `class-triangle`, `operator-dispatch`,
+ `comprehension-equivalence`.
+5. **Move anchors to the cell where the move happens.** For each cell-0
+ attachment, ask whether a later cell holds the mutation, dispatch or
+ resume the figure depicts. Rewrite criterion 10 once the distribution
+ is honest.
+6. **Rank the gestalt.** A pairwise ranking table in the registry, and
+ redesign from the bottom ten. The 8.5 score stays as the gate.
+7. **Widen the content contract** from escape sequences to every mono
+ literal that looks like Python source: it must appear in the
+ example's code or output.
+8. **Buffers and hashes.** `dict-buckets` and `set-buckets` with hash
+ values from the example; `list-append` and `slice-ruler` as header
+ plus buffer.
+
+## What to study
+
+- **Composing Programs** (composingprograms.com) and **Python Tutor**
+ (pythontutor.com) for the environment diagram: frames left, objects
+ right, and how a closure and a recursive call are drawn.
+- **Ned Batchelder, "Facts and myths about Python names and values"**
+ for name-to-value drawings done the same way every time.
+- **Crafting Interpreters** (craftinginterpreters.com) for warm ink on
+ cream, one idea per figure, held consistent across a whole book.
+- **Sam Rose, samwho.dev** ("Hashing", "Memory Allocation") for
+ programmer-facing figures with a locked grammar; the model for the
+ bucket figures.
+- **Lydia Hallie, "JavaScript Visualized"** for the frame designs of
+ scope chain and closures.
+- **Bartosz Ciechanowski** (ciechanow.ski) for figures where exactly
+ one variable changes between frames.
+- **SQLite syntax diagrams** for what a railroad is; `format-spec`
+ follows them.
+- **The Rust Book, chapter 4** and **"Go Slices: usage and internals"**
+ for header-plus-buffer drawings of list, bytearray and slice.
+- **Tufte, Visual Explanations, "the smallest effective difference"**
+ for how little contrast emphasis needs.
+- **Maggie Appleton** and **Julia Evans** for mechanism drawings that stay playful without adding marks.
+- **Distill.pub** for captions that carry the claim.
diff --git a/docs/example-figure-rubric.md b/docs/example-figure-rubric.md
index 1eac551..736d218 100644
--- a/docs/example-figure-rubric.md
+++ b/docs/example-figure-rubric.md
@@ -60,6 +60,9 @@ per-figure scoring.
figure. The accent goes on the single element the cell prose
names (the live mutation, the captured cell, the dispatch arrow).
Three accent marks competing for attention is no emphasis at all.
+ When the caption names no single element, as in a selector where
+ every branch is equal, the figure has zero accents, because an
+ accent on the row a loop happened to reach last marks nothing.
8. **Restraint (0-1.0)** — no decoration that does not carry
information. No drop shadows, gradients, ornamental rules,
non-orthogonal tilts, or marks placed for "balance".
@@ -77,6 +80,14 @@ per-figure scoring.
cell just made, not stand alone as a generic illustration of the
example title.
+ As of September 2026, 106 of 115 attachments anchor after cell 0,
+ so this criterion is usually scored against the first cell. An
+ opening figure is a legitimate design, and it earns the 0.5 by
+ summarising the first cell's move rather than the title. When a
+ later cell holds the mutation, dispatch or resume the figure
+ depicts, move the anchor there; `bytes-and-bytearray` moved to
+ cell 3 for that reason.
+
## Topic gates (cell-shape specific)
- **Binding cells** (assignments, `=`) — show the name-arrow with the
@@ -107,6 +118,34 @@ the figure can merge.
- **One figure per cell, at most.** Two figures on one cell signal
the cell is doing two things; split the cell instead.
+- **Table figures are redrawn, never removed.** Rows of `name | role`,
+ `type | spellings` or `level | number` are reference material; they
+ score 8.5 at most under criteria 2 and 4 because a table in the prose
+ would do the same work. The remedy is a mechanism in the same slot:
+ `values` became the `text → str` binding with the `type()` hop to
+ where `upper()` lives, and `logging` became three records crossing the
+ handler's level gate. Removing a shipped figure and recording a
+ rationale leaves the page with less than it had; the
+ `no_figure_rationales` registry is for pages whose lesson has no
+ drawable mechanism, and a page that once carried a figure does not
+ qualify.
+- **The figure carries the lesson's own names.** Where one mechanism
+ serves several lessons, parametrise the paint function
+ (`_bind_figure("MAX_RETRIES", "int", "3")`) instead of choosing
+ between placeholder names and a bespoke copy.
+- **Content fidelity, enforced.** No rendered text may contain a
+ doubled backslash, and a mono label containing an escape sequence
+ must appear verbatim in the example's Markdown source. The
+ `bytes-vs-bytearray` figure shipped `b'\\x63\\x61\\x66'` for
+ months because every contract measured geometry and none read the
+ label. *Contract 13 — FigureContentContract.*
+- **Minimum arrow length, enforced.** Every closed arrow is at least
+ `ARROW_MIN` (20) units long; below that it is mostly head. Widen
+ the gap between the boxes, never shrink the head. *Contract 14.*
+- **Both colour schemes, enforced.** Figures paint with the
+ `var(--fig-*)` tokens; `public/site.css` defines all four in the
+ light root and the dark media block with the grammar's values, so
+ no figure needs a light chip in dark mode. *Contract 15.*
- **figcaption present and declarative.** Captions in the form
"Two names share one mutable list — appending through one name
changes the object visible through both." Not "this shows X" or
@@ -231,4 +270,8 @@ example reads as a coherent set rather than independently authored
diagrams.
The score is a guide, not a substitute for reading the cell beside
-its surrounding prose.
+its surrounding prose. Because 106 of 109 attached figures score 9.0
+and the rest 9.5, the score no longer says which figure to redesign
+next. Use the gestalt page to rank the library pairwise and redesign
+from the bottom, and keep the score as the ship gate
+(`docs/diagram-upgrade-plan.md`).
diff --git a/docs/journey-visualisation-rubric.md b/docs/journey-visualisation-rubric.md
index 18f8258..83eb91e 100644
--- a/docs/journey-visualisation-rubric.md
+++ b/docs/journey-visualisation-rubric.md
@@ -43,7 +43,10 @@ Score each section figure on a 10-point scale.
7. **Emphasis scarcity (0-1.0)** — at most one accent mark per
figure. The accent goes on the single element the section names
(the live yield, the dispatch arrow, the captured cell). If three
- things are orange the figure has no emphasis at all.
+ things are orange the figure has no emphasis at all. When the
+ caption names no single element, as in a selector where every branch
+ is equal, the figure has zero accents, because an accent on the row a
+ loop happened to reach last marks nothing.
8. **Restraint (0-1.0)** — no decoration that does not carry
information. No drop shadows, gradients, ornamental rules,
non-orthogonal tilts, or marks placed for "balance".
@@ -95,7 +98,9 @@ Score each section figure on a 10-point scale.
width the layout gives it (`clamp(280px, 70vw, 640px)`); design at a
viewBox whose rendered width fits the shared 640px ceiling.
- **Uses only the four palette constants.** `INK`, `INK_SOFT`,
- `EMPHASIS`, `SOFT_FILL`. Anything else is grounds for redesign.
+ `EMPHASIS`, `SOFT_FILL`, which resolve to the page's `--fig-*`
+ tokens so a section figure renders in both colour schemes without a
+ chip. Anything else is grounds for redesign.
## Quality bands
diff --git a/docs/lessons-learned.md b/docs/lessons-learned.md
index 6e9a944..91a0a50 100644
--- a/docs/lessons-learned.md
+++ b/docs/lessons-learned.md
@@ -135,6 +135,14 @@ git diff --check
- **A bypassed check needs its own signal.** Deployment smoke uses `PBE_SMOKE_BYPASS_SECRET`, so it never proves the production `TURNSTILE_SECRET_KEY` works, and the wide event recorded both "challenge issued" and "Siteverify rejected" as `fail`. A wrong or rotated secret would have passed smoke while every new session's first run failed. Turnstile Spin's "Fix with Spin" banner is Cloudflare noticing widgets that serve traffic without working server-side validation; the self-hosted equivalent is `challenged` versus `fail` outcomes plus Siteverify `error_codes`, with `invalid-input-secret` reported by `scripts/learner_report.py` as a configuration alert rather than a bot failure. Verify the signal against the live service, not a stub: real Siteverify returns `invalid-input-secret` and `missing-input-secret` with HTTP 400, so the common `if (!response.ok) throw` pattern (Spin's reference handler included) discards the one code that names the misconfiguration. Unit stubs had assumed a 200. The deploy-time half of the fix is a smoke-gated probe (`POST /__smoke/turnstile`) that sends the production secret with Cloudflare's dummy token. Classify on more than the error code: Cloudflare's always-fail test secret answers the dummy token with the same `invalid-input-response` as a working production secret, and only `metadata.result_with_testing_key` tells them apart.
- **Check hand-rolled security code against the vendor's reference contract.** Reading the Turnstile Spin skill's canonical Siteverify handler beside `_verify_turnstile` found three gaps no test had asked about: no timeout, no 2048-character token bound, and a `null` hostname raising `AttributeError` (a 500) instead of failing closed. Use the reference as a checklist, and keep deliberate divergences written down: Invisible mode, session clearance instead of per-request verification, and the serving hostname instead of a configured allowlist (safe only while `workers.dev` and preview URLs stay disabled).
- **Credential-bearing commands run code from the dependency tree.** Turnstile Spin refuses to run `secret put` or other credential-bearing commands through `npx`, package scripts, or a project-local binary. `make deploy` runs the repository-pinned Wrangler with the account's credentials, and Dependabot now proposes npm updates weekly. Exact pins, the three-day cooldown, and `npm ci --ignore-scripts` limit the exposure; review Wrangler and Miniflare bumps as credential-bearing changes, not routine ones.
+- Geometry contracts cannot see a wrong label. The bytes-and-bytearray figure rendered doubled backslashes, and bytes the example never used, for months while every clipping, collision, palette and accent contract passed; the regular-expressions figure drew a pattern the example never uses. Contract 13 now requires that escape sequences in a figure appear in the example source and that no rendered text carries a doubled backslash. A wider check (every code-like mono label must appear in the example) flags 54 of 115 attachments; three of those drew values the example never computes (`factorial(3)` on a page that sums a tree, keys `a` to `d` on a page whose dict holds `name` and `language`) and were redrawn from the cell. Placeholders such as `[a,b,c]` are allowed by the rubric and were left.
+- A score that every figure passes by the same margin cannot say what to redesign next. The v1 figure rubric saturated at 8.5, was rewritten, and v2 saturated at 9.0 (106 of 109). Rank the gestalt pairwise to choose the next redesign, and keep the score as the minimum.
+- A weak figure is redrawn, not removed. Four shipped figures (`values`, `literals`, `logging`, `collections-module`) were rows of `name | role`, and the first fix deleted them and recorded a rationale for each. A page that had a picture and now has a paragraph about a better picture has lost something, and the owner said so. `values` and `logging` were redrawn as mechanisms in the same pull request; `literals` and `collections-module` keep their tables at 8.5 until a mechanism replaces them. The `no_figure_rationales` registry is for pages whose lesson has no drawable mechanism.
+- Inline SVG inherits CSS custom properties, so figures should paint with `var(--fig-*)` tokens rather than literal colours. The dark-mode "paper chip" was a workaround for a palette baked into the grammar; once the tokens moved to `site.css` the chip, the second palette and the seam between figure and page all disappeared. Any shell that embeds a figure outside the site stylesheet (gestalt pages, social cards) must define the tokens itself.
+- Do not ask for fonts the page never loads. The grammar requested JetBrains Mono and Source Sans while the site shipped no web fonts, so which faces a visitor saw was decided by their OS, and the italic serif identifiers were the only serif on the page. Figure faces now follow the page's own stacks, and mono runs are pinned with `textLength` so divider math computed from `MONO_ADVANCE` holds on every platform.
+- Several selector figures accented the third branch because a loop variable happened to be true there. When the caption names no single element the figure gets zero accents; the one-accent contract already allowed zero and the rubric now says so.
+- An arrow shorter than about three times its head reads as a wedge between two boxes. `ARROW_MIN` (20 units) and Contract 14 keep the census in the grammar because the emitted shaft is already shortened by the head.
+- A paint function that takes the lesson's names removes the choice between reuse and fidelity. `_bind_figure("MAX_RETRIES", "int", "3")` and `_bind_figure("message", "str", '"hi"')` share one mechanism and each match the cell they sit under.
## Discoverability, theming, and learner analytics
diff --git a/docs/pr-evidence/README.md b/docs/pr-evidence/README.md
index bbbee45..72e8607 100644
--- a/docs/pr-evidence/README.md
+++ b/docs/pr-evidence/README.md
@@ -1,5 +1,33 @@
# PR visual evidence
+## Diagram upgrade (2026-09-27)
+
+Before/after contact sheet of every figure whose drawing changed on the
+`claude/diagram-upgrade` branch relative to `main`, in both colour schemes.
+Rows are grouped: twelve redrawn figures to judge, nine adjusted figures (an
+arrow lengthened or an accent removed) to glance at. Each row carries a note
+from `diagram-upgrade-contact-sheet-notes.json` saying what changed and why.
+The base column in the dark sheet sits on the light paper chip the site used
+to draw; the head column is recoloured through the `--fig-*` tokens.
+
+| Evidence | Review point | Base | SHA-256 |
+| --- | --- | --- | --- |
+| [Light contact sheet](diagram-upgrade-contact-sheet-light.png) | 21 changed figure rows with element counts, shipped captions and per-row notes. | `main` (`3f300af`) | `55fc7a56bc03ef2841dc0bcf099b98911f667e56b3de7799414c90d1075b8abf` |
+| [Dark contact sheet](diagram-upgrade-contact-sheet-dark.png) | The same rows under `prefers-color-scheme: dark`: paper chip before, token recolouring after. | `main` (`3f300af`) | `d407aa9aaee7e943cc48887ecf1cd25adb4463eee9eeb3e0715482a8233cd611` |
+
+Regenerate both from the branch with:
+
+```bash
+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
+```
+
+The script extracts the base revision with `git archive`, renders every attached
+figure from both trees, ignores colour, font and text-length attributes when
+deciding what changed, and captures the sheet through
+`scripts/capture_browser_screenshot.mjs`. The per-criterion scores for the
+redrawn figures are in `docs/rubric-audit-2026-09-27.md`.
## Turnstile rejection loop (2026-09-26)
These captures show `/examples/values` six seconds after one Run click, with Siteverify rejecting every token. Both Workers used Cloudflare's invisible always-pass test site key (`1x00000000000000000000BB`), so the real Turnstile widget loaded and solved in the browser, and the always-fail test secret (`2x0000000000000000000000000000000AA`), so Siteverify rejected every solved token.
diff --git a/docs/pr-evidence/diagram-upgrade-contact-sheet-dark.png b/docs/pr-evidence/diagram-upgrade-contact-sheet-dark.png
new file mode 100644
index 0000000..1f1f4d9
Binary files /dev/null and b/docs/pr-evidence/diagram-upgrade-contact-sheet-dark.png differ
diff --git a/docs/pr-evidence/diagram-upgrade-contact-sheet-light.png b/docs/pr-evidence/diagram-upgrade-contact-sheet-light.png
new file mode 100644
index 0000000..6b62c80
Binary files /dev/null and b/docs/pr-evidence/diagram-upgrade-contact-sheet-light.png differ
diff --git a/docs/pr-evidence/diagram-upgrade-contact-sheet-notes.json b/docs/pr-evidence/diagram-upgrade-contact-sheet-notes.json
new file mode 100644
index 0000000..4b52f93
--- /dev/null
+++ b/docs/pr-evidence/diagram-upgrade-contact-sheet-notes.json
@@ -0,0 +1,23 @@
+{
+ "variables": "Same mechanism, the cell's own name and value: message → \"hi\" instead of x → 42.",
+ "constants": "Same mechanism, the cell's own name and value: MAX_RETRIES → 3; the name box widens to fit.",
+ "closures": "Environment diagram. The returned frame is ghosted; double names a function object whose __closure__ points back into it.",
+ "generators": "State over time: one ribbon per next() call the cell makes on countdown(3), with the local n beside each row.",
+ "string-formatting": "Railroad tracing the cell's 05.1f. Visited stations shaded; skipped stations ghosted under a bypass rail.",
+ "bytes-and-bytearray": "The example's real bytes as integer slots, the in-place write shown. The old figure drew bytes the example never uses, with doubled backslashes. Anchor moved to the cell that mutates packet.",
+ "regular-expressions": "The cell's own pattern; capture groups dropped onto the record they extract. The old figure drew a pattern the example never uses.",
+ "values": "The cell's binding text → str plus the type() hop to the class where upper() lives, replacing four stacked typed boxes.",
+ "logging": "The cell's three records crossing the handler's INFO gate; debug stops at the gate. Replaces the five-level lookup table.",
+ "decorators": "Arrow in the 'before' row lengthened from 16 to 22 units.",
+ "recursion": "The example's own tree at the deepest frame of total(tree), with the values each frame returns. The old figure drew factorial(3), which the example never computes.",
+ "type-aliases": "Arrow lengthened from 16 to 22 units; the Index box moves down 6.",
+ "multiple-return-values": "Both arrows lengthened from 18 to 22 units.",
+ "match-statements": "First-match arrow lengthened from 14 to 20 units.",
+ "assignment-expressions": "Case boxes spread so the arrows between them are 22 units; the accent on the middle case removed.",
+ "container-protocols": "Accent and soft fill removed from the middle row; the caption names all three routes equally.",
+ "journey section · Choose the right loop shape.": "Accent and soft fill removed from the sentinel row; the caption names no single loop.",
+ "journey section · Describe realistic data shapes.": "Accent and soft fill removed from the Union row.",
+ "journey section · Make failure explicit.": "Accent and soft fill removed from the except row.",
+ "dicts": "The cell's own keys hashed to slots, with the insert of \"year\" accented. The old figure drew keys a to d and a collision the example never shows.",
+ "sentinel-iteration": "The cell's read_chunk and its chunks \"py\", \"thon\", \"\". The old figure drew iter(read, '') with placeholder values."
+}
diff --git a/docs/quality-registries.toml b/docs/quality-registries.toml
index 492aa7f..1ba768b 100644
--- a/docs/quality-registries.toml
+++ b/docs/quality-registries.toml
@@ -199,8 +199,9 @@ expires = "2026-12-01"
[no_figure_rationales]
# Slugs may be added here only when the page is constraint-shaped,
# infrastructure-shaped, or aggregator-shaped and a single mechanism
-# figure would distort the lesson. Current production attaches figures
-# to every example, so this registry is intentionally empty.
+# figure would distort the lesson. A weak figure is redrawn, not removed:
+# a page that had a figure keeps one. Current production attaches
+# figures to every example, so this registry is intentionally empty.
[journey_outcomes]
@@ -1381,7 +1382,7 @@ caption = "Lists are mutable sequences. `.append` extends the same list object
slug = "dicts"
anchor = "cell-0"
figure = "dict-buckets"
-caption = "Each key is hashed to a bucket; collisions chain into the next slot. Lookup is constant-time on average."
+caption = "hash(key) picks the slot: \"year\" lands in an empty one, and profile[\"name\"] goes straight to its slot without scanning the other entries."
[[figure_attachments]]
slug = "unpacking"
@@ -1429,7 +1430,7 @@ caption = "@dec rebinds the name to wrapper(f₀); the original function survive
slug = "recursion"
anchor = "cell-1"
figure = "call-stack"
-caption = "Each call pushes a new frame with the same name and a smaller argument; the base case unwinds back up the stack."
+caption = "At the leaf with value 4, three frames of total are stacked, each waiting on the one below; unwinding returns 4, then 3 + 4 = 7, then 1 + 2 + 7 = 10."
[[figure_attachments]]
slug = "exception-chaining"
@@ -1507,7 +1508,7 @@ caption = "In the same signature, the bare `*` works the other way: parameters a
slug = "closures"
anchor = "cell-0"
figure = "closure-cell"
-caption = "The inner function keeps a reference into the outer scope's cell, so the captured factor survives the outer return."
+caption = "make_multiplier(2) has returned, but the function object double names still points into that frame's cell, so the captured factor outlives the call."
[[figure_attachments]]
slug = "scope-global-nonlocal"
@@ -1518,8 +1519,8 @@ caption = "Name lookup walks LEGB — local, enclosing, global, built-in — out
[[figure_attachments]]
slug = "generators"
anchor = "cell-0"
-figure = "generator-ribbon"
-caption = "A generator's body is a timeline cut by yield gates: each next() advances to the next gate; locals survive the pause."
+figure = "generator-resume"
+caption = "The cell's two next() calls on countdown(3), one timeline each: every call resumes the body to the next yield gate, and the local n survives the pause."
[[figure_attachments]]
slug = "type-hints"
@@ -1655,15 +1656,15 @@ caption = "Early returns handle the exceptional cases first so the main work is
[[figure_attachments]]
slug = "bytes-and-bytearray"
-anchor = "cell-0"
+anchor = "cell-3"
figure = "bytes-vs-bytearray"
-caption = "bytes is a frozen sequence of integers; bytearray is the mutable counterpart with append/extend/etc."
+caption = "Both objects hold the same two bytes as integers; b\"py\" cannot change, while packet[0] = ord(\"P\") rewrites the first slot of the bytearray in place."
[[figure_attachments]]
slug = "sentinel-iteration"
anchor = "cell-0"
figure = "sentinel-iteration"
-caption = "`iter(callable, sentinel)` calls the callable repeatedly, stopping when it returns the sentinel."
+caption = "iter(read_chunk, \"\") keeps calling read_chunk and yields \"py\" and \"thon\"; the empty string is the sentinel, so it stops the loop and is never yielded."
[[figure_attachments]]
slug = "partial-functions"
@@ -1746,8 +1747,8 @@ caption = "Six type pairs bridge the JSON text boundary; each json value maps to
[[figure_attachments]]
slug = "regular-expressions"
anchor = "cell-0"
-figure = "regex-anchors"
-caption = "^ and $ anchor the pattern; quantifiers like {2} bound how many times a token repeats."
+figure = "regex-groups"
+caption = "findall returns only what the parentheses capture: `([A-Za-z]+)` takes the name and `(\\d+)` the score, one (name, score) pair per record."
[[figure_attachments]]
slug = "number-parsing"
@@ -1759,7 +1760,7 @@ caption = "int() turns text into a typed number; malformed input raises ValueErr
slug = "string-formatting"
anchor = "cell-1"
figure = "format-spec"
-caption = "The format spec is a railroad of named optional fields: alignment, sign, width, precision, type."
+caption = "The format spec is a railroad of optional stations in a fixed order; this cell's 05.1f visits zero-pad, width, precision and type and bypasses the rest."
[[figure_attachments]]
slug = "truthiness"
@@ -1791,12 +1792,6 @@ anchor = "cell-3"
figure = "list-append"
caption = "A list is the other intent: a variable number of similar items, growing in place with `.append`."
-[[figure_attachments]]
-slug = "values"
-anchor = "cell-0"
-figure = "value-types"
-caption = "Every literal is an object with a type; the type carries the behaviour, not the variable name."
-
[[figure_attachments]]
slug = "yield-from"
anchor = "cell-0"
@@ -1902,9 +1897,33 @@ caption = "A function takes inputs, evaluates a body, and returns a value: `gree
[[figure_attachments]]
slug = "constants"
anchor = "cell-0"
-figure = "variables-bind"
+figure = "constants-bind"
caption = "UPPER_CASE is a naming convention, not a language constraint; the binding behaves like any other variable."
+[[figure_attachments]]
+slug = "values"
+anchor = "cell-0"
+figure = "value-type-lookup"
+caption = "text names a str object, and type(text) is str: the type carries upper() and the rest of the behaviour, not the name."
+
+[[figure_attachments]]
+slug = "literals"
+anchor = "cell-0"
+figure = "literal-forms"
+caption = "Each Python type has its own literal spellings; ints accept decimal, hex, and binary; strings accept either quote."
+
+[[figure_attachments]]
+slug = "logging"
+anchor = "cell-0"
+figure = "logging-threshold"
+caption = "The handler's level is a gate: debug is below INFO and never reaches stdout, while info and warning pass through unchanged."
+
+[[figure_attachments]]
+slug = "collections-module"
+anchor = "cell-0"
+figure = "collections-containers"
+caption = "Four specialised containers for shapes the built-in types don't cover well: deque, Counter, defaultdict, namedtuple."
+
[[figure_attachments]]
slug = "while-loops"
anchor = "cell-0"
@@ -1923,12 +1942,6 @@ anchor = "cell-0"
figure = "match-pattern-variants"
caption = "Capture, alternative, guard, and class patterns each name a different way a value can match a case."
-[[figure_attachments]]
-slug = "literals"
-anchor = "cell-0"
-figure = "literal-forms"
-caption = "Each Python type has its own literal spellings; ints accept decimal, hex, and binary; strings accept either quote."
-
[[figure_attachments]]
slug = "packages"
anchor = "cell-0"
@@ -1947,12 +1960,6 @@ anchor = "cell-0"
figure = "subprocess-spawn"
caption = "subprocess.run spawns a child process and captures its stdout, stderr, and exit code as portable evidence."
-[[figure_attachments]]
-slug = "logging"
-anchor = "cell-0"
-figure = "logging-levels"
-caption = "Five severity levels; the logger's configured threshold drops everything below it."
-
[[figure_attachments]]
slug = "testing"
anchor = "cell-0"
@@ -2013,12 +2020,6 @@ anchor = "cell-0"
figure = "isinstance-check"
caption = "isinstance and issubclass ask the runtime; the answer is a bool, not a static type refinement."
-[[figure_attachments]]
-slug = "collections-module"
-anchor = "cell-0"
-figure = "collections-containers"
-caption = "Four specialised containers for shapes the built-in types don't cover well: deque, Counter, defaultdict, namedtuple."
-
[[figure_attachments]]
slug = "structured-data-shapes"
anchor = "cell-0"
@@ -2217,8 +2218,8 @@ comment = "signature with explicit `/` separator"
[[example_figure_scores]]
slug = "closures"
-score = 9.0
-comment = "captured cell reference"
+score = 9.5
+comment = "environment diagram: ghosted returned frame, heap function object, one live __closure__ reference; 1.5/1/0.75/1/1 · 1/1/1 · 1/0.5"
[[example_figure_scores]]
slug = "scope-global-nonlocal"
@@ -2228,7 +2229,7 @@ comment = "LEGB nested rings"
[[example_figure_scores]]
slug = "recursion"
score = 9.0
-comment = "stacked frames with same name, different argument"
+comment = "the example's own tree at its deepest frame, return values unwinding; earns-place 0.75, the cell's output already gives 10; 1.5/0.75/1/1/1 · 1/1/1 · 1/0.5"
[[example_figure_scores]]
slug = "lists"
@@ -2238,7 +2239,7 @@ comment = "cells with append mechanism"
[[example_figure_scores]]
slug = "dicts"
score = 9.0
-comment = "hash buckets with collision chain"
+comment = "the cell's own keys hashed to slots, the insert of \"year\" accented; earns-place 0.75, one-move 0.75 since insert and lookup share the caption; 1.5/0.75/0.75/1/1 · 1/1/1 · 1/0.5"
[[example_figure_scores]]
slug = "slices"
@@ -2257,8 +2258,8 @@ comment = "ghost annotations over runtime values"
[[example_figure_scores]]
slug = "generators"
-score = 9.0
-comment = "ribbon cut by yield gates"
+score = 9.5
+comment = "the cell's two next() ribbons on countdown(3), local n shown surviving; earns-place 0.75, the prose says the same; 1.5/0.75/1/1/1 · 1/1/1 · 1/0.5"
[[example_figure_scores]]
slug = "exceptions"
@@ -2393,12 +2394,12 @@ comment = "function literal: params / expression"
[[example_figure_scores]]
slug = "string-formatting"
score = 9.0
-comment = "format-spec railroad"
+comment = "railroad tracing the cell's 05.1f; one-move 0.75, restraint 0.75 and fit 0.75: still the densest figure in the set at the banner ceiling; 1.5/1/0.75/1/1 · 1/1/0.75 · 0.75/0.5"
[[example_figure_scores]]
slug = "regular-expressions"
score = 9.0
-comment = "pattern ruler with anchors"
+comment = "the cell's pattern with both capture groups dropped onto Ada: 10; one-move 0.75 and restraint 0.75 for two rows plus two drops; 1.5/1/0.75/1/1 · 1/1/0.75 · 1/0.5"
[[example_figure_scores]]
slug = "json"
@@ -2415,16 +2416,6 @@ slug = "datetime"
score = 9.0
comment = "one instant, two clock offsets"
-[[example_figure_scores]]
-slug = "values"
-score = 9.0
-comment = "every literal is a typed object"
-
-[[example_figure_scores]]
-slug = "literals"
-score = 9.0
-comment = "literal spellings per type"
-
[[example_figure_scores]]
slug = "booleans"
score = 9.0
@@ -2498,7 +2489,27 @@ comment = "specific call: greet('Ada') → 'Hello, Ada'"
[[example_figure_scores]]
slug = "constants"
score = 9.0
-comment = "name binding; UPPER_CASE is convention"
+comment = "name binding with the cell's own MAX_RETRIES → 3; earns-place 0.5, the prose already says it is an ordinary binding; 1.5/0.5/1/1/1 · 1/1/1 · 1/0.5"
+
+[[example_figure_scores]]
+slug = "values"
+score = 9.0
+comment = "text → str binding plus the type() hop to where upper() lives; earns-place 0.75 since the cell prints type(text), one-move 0.75; 1.5/0.75/0.75/1/1 · 1/1/1 · 1/0.5"
+
+[[example_figure_scores]]
+slug = "literals"
+score = 8.5
+comment = "literal spellings per type; a reference table, kept until a mechanism figure replaces it"
+
+[[example_figure_scores]]
+slug = "logging"
+score = 9.5
+comment = "the cell's three records crossing the handler's INFO gate, debug dropped; earns-place 0.75 since the output already shows the drop; 1.5/0.75/1/1/1 · 1/1/1 · 1/0.5"
+
+[[example_figure_scores]]
+slug = "collections-module"
+score = 8.5
+comment = "deque / Counter / defaultdict / namedtuple; a catalogue, kept until the page is split"
[[example_figure_scores]]
slug = "import-aliases"
@@ -2567,13 +2578,13 @@ comment = "__bool__ → __len__ → True fallback chain"
[[example_figure_scores]]
slug = "bytes-and-bytearray"
-score = 9.0
-comment = "frozen vs mutable contrast"
+score = 9.5
+comment = "the example's two bytes as integer slots, packet[0] rewritten in place, anchored to the mutating cell; 1.5/0.75/1/1/1 · 1/1/1 · 1/0.5"
[[example_figure_scores]]
slug = "sentinel-iteration"
score = 9.0
-comment = "iter(callable, sentinel) stop condition"
+comment = "the cell's read_chunk and chunks; the empty string ghosted as the sentinel; earns-place 0.75, the output already omits it; 1.5/0.75/1/1/1 · 1/1/1 · 1/0.5"
[[example_figure_scores]]
slug = "partial-functions"
@@ -2600,11 +2611,6 @@ slug = "subprocesses"
score = 9.0
comment = "spawn → child → captured output"
-[[example_figure_scores]]
-slug = "logging"
-score = 9.0
-comment = "five thresholded levels"
-
[[example_figure_scores]]
slug = "testing"
score = 9.0
@@ -2640,11 +2646,6 @@ slug = "runtime-type-checks"
score = 9.0
comment = "isinstance returns bool"
-[[example_figure_scores]]
-slug = "collections-module"
-score = 9.0
-comment = "deque / Counter / defaultdict / namedtuple"
-
[[example_figure_scores]]
slug = "structured-data-shapes"
score = 9.0
diff --git a/docs/rubric-audit-2026-09-27.md b/docs/rubric-audit-2026-09-27.md
new file mode 100644
index 0000000..1b7c49d
--- /dev/null
+++ b/docs/rubric-audit-2026-09-27.md
@@ -0,0 +1,234 @@
+# Rubric audit snapshot — 2026-09-29
+
+This snapshot puts the catalog's quality ledgers beside each other for an editorial pass. Every number is computed from the live registries at generation time; rows marked CURATED are editorial judgements that the audit pass must re-affirm by review — this report does not validate them.
+
+## Scoreboard
+
+- Examples: count=109, min=7.1, avg=8.98, median=9, below9=1, distribution=7.1 × 1, 9 × 108
+- Example diagrams: count=109, min=8.5, avg=9.02, median=9, below9=2, distribution=8.5 × 2, 9 × 100, 9.5 × 7
+- Journey diagrams: count=21, min=9, avg=9.02, median=9, below9=0, distribution=9 × 20, 9.5 × 1
+- Accepted waiver: `hello-world` at 7.1 (floor 7.0, expires 2026-12-01).
+- Graph health: 109 linked sources, 361 edges, 0 orphaned examples.
+
+## Example rubric dimensions
+
+| Dimension | Evidence | Result |
+| --- | --- | --- |
+| Conceptual payoff | Curated score registry + criterion search | CURATED (re-affirm by review) |
+| Rationale | Criterion search + notes-supported gate | GATED by check_notes_supported |
+| Alternatives and boundaries | Confusable pairs, footguns, broad tours, graph edges | GATED by quality-checks |
+| Executable determinism | `verify_examples.py` over every program and cell | GATED by verify-examples |
+| Python idiom and accuracy | Python 3.13 verification, docs links, Ruff | GATED by verify-examples + lint |
+| Literate fit | Markdown parser requires prose beside every cell | GATED by the loader |
+| Source/result pairing | Every runnable cell carries expected output | GATED by verify-examples |
+| Concept decomposition | Criterion search + curated score floor | CURATED (re-affirm by review) |
+| Progressive walkthrough | Cell sequence review + criterion search | CURATED (re-affirm by review) |
+| Representative coverage | Broad-surface and syntax-surface gates | GATED by check_broad_surface_tours |
+| Practical usefulness | Criterion search + editorial score comments | CURATED (re-affirm by review) |
+| Editorial progression | Quality registry + graph audit | GATED by audit_example_graph |
+
+## Example-figure rubric dimensions
+
+| Dimension | Evidence | Result |
+| --- | --- | --- |
+| Cell fidelity | Attachment anchor resolves to a real rendered cell | GATED by FigureAnchorContract |
+| Earns its place | Curated figure scores | CURATED (re-affirm by review) |
+| One conceptual move | Figure score rationale + emphasis-scarcity contract | CURATED + accent contract |
+| Mechanism over metaphor | Canvas grammar uses bindings, arrows, cells, lanes, boundaries | CURATED (re-affirm by review) |
+| Caption quality | Unique, complete-sentence figcaptions | GATED by FigureCaptionContract |
+| Grammar conformance | Palette, font, and stroke contracts | GATED by geometry contracts |
+| Emphasis scarcity | At most one accent mark per figure | GATED by FigureEmphasisScarcityContract |
+| Restraint | Locked primitive grammar; no bespoke SVG | GATED by the grammar's surface |
+| Banner-row fit | Intrinsic-width contract | GATED by FigureSizeContract |
+| Pairs with surrounding cell | Caption/figure agreement on the marginalia gestalt | CURATED (re-affirm by review) |
+
+## Journey-visualisation rubric dimensions
+
+| Dimension | Evidence | Result |
+| --- | --- | --- |
+| Section fidelity | Section figure keyed by journey section title | GATED by SectionFigureContract |
+| Pedagogical scope | Section-level captions and score rationales | CURATED (re-affirm by review) |
+| One conceptual move | One figure per section and one emphasis mark | GATED + CURATED |
+| Mechanism over metaphor | Canvas primitives show protocols, boundaries, lanes, or flows | CURATED (re-affirm by review) |
+| Caption alignment | Section figure captions unique and declarative | GATED by FigureCaptionContract |
+| Grammar conformance | Shared geometry contracts | GATED by geometry contracts |
+| Independence from lesson figures | Section figures compared with example attachments | computed: no reuse |
+| Layout fit | Journey figure dimensions within production column | GATED by FigureSizeContract |
+| Outcome support | `check_journey_outcomes.py` over all journey sections | GATED by check_journey_outcomes |
+| Prerequisite order | Journey order reviewed against lesson dependencies | CURATED (re-affirm by review) |
+
+## Example and diagram inventory
+
+| Slug | Quality | Heuristic | Cells | Notes | Edges | Figure | Anchor | Fig score | Weakest heuristic axes |
+| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
+| hello-world | 7.1 | 8.3 | 1 | 2 | 2 | program-output | cell-0 | 9.0 | alternatives_and_boundaries=0.25, concept_decomposition=0.58, representative_coverage=0.65 |
+| values | 9.0 | 9.3 | 3 | 3 | 4 | value-type-lookup | cell-0 | 9.0 | alternatives_and_boundaries=0.25, practical_usefulness=0.80, conceptual_payoff=1.00 |
+| literals | 9.0 | 9.6 | 6 | 4 | 4 | literal-forms | cell-0 | 8.5 | alternatives_and_boundaries=0.50, conceptual_payoff=0.97, rationale=1.00 |
+| numbers | 9.0 | 9.4 | 4 | 4 | 2 | number-lines | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=0.90 |
+| booleans | 9.0 | 9.8 | 3 | 4 | 3 | boolean-truth-table | cell-0 | 9.0 | alternatives_and_boundaries=0.70, conceptual_payoff=1.00, rationale=1.00 |
+| operators | 9.0 | 9.5 | 6 | 4 | 4 | expression-tree | cell-0 | 9.0 | alternatives_and_boundaries=0.50, conceptual_payoff=0.90, rationale=1.00 |
+| none | 9.0 | 9.6 | 3 | 3 | 4 | none-singleton | cell-0 | 9.0 | alternatives_and_boundaries=0.50, conceptual_payoff=0.97, rationale=1.00 |
+| variables | 9.0 | 9.3 | 3 | 3 | 4 | variables-bind | cell-0 | 9.5 | alternatives_and_boundaries=0.50, conceptual_payoff=0.80, practical_usefulness=0.80 |
+| constants | 9.0 | 9.4 | 3 | 3 | 3 | constants-bind | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=0.97 |
+| truthiness | 9.0 | 9.5 | 3 | 2 | 4 | truthy-check | cell-0 | 9.0 | alternatives_and_boundaries=0.50, conceptual_payoff=0.87, rationale=1.00 |
+| equality-and-identity | 9.0 | 9.4 | 4 | 4 | 4 | identity-and-equality | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=0.90 |
+| mutability | 9.0 | 9.4 | 3 | 3 | 4 | 2 figures | — | — | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=0.90 |
+| object-lifecycle | 9.0 | 8.7 | 2 | 3 | 3 | object-lifecycle | cell-0 | 9.0 | alternatives_and_boundaries=0.25, rationale=0.65, concept_decomposition=0.80 |
+| strings | 9.0 | 9.5 | 3 | 5 | 4 | codepoints-bytes | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=1.00 |
+| bytes-and-bytearray | 9.0 | 9.5 | 4 | 4 | 3 | bytes-vs-bytearray | cell-3 | 9.5 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=1.00 |
+| string-formatting | 9.0 | 9.2 | 3 | 3 | 4 | format-spec | cell-1 | 9.0 | alternatives_and_boundaries=0.25, conceptual_payoff=0.80, rationale=1.00 |
+| conditionals | 9.0 | 9.3 | 3 | 3 | 4 | branch-fork | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=0.87 |
+| guard-clauses | 9.0 | 9.2 | 2 | 3 | 3 | guard-clauses | cell-0 | 9.0 | alternatives_and_boundaries=0.50, concept_decomposition=0.80, representative_coverage=0.80 |
+| assignment-expressions | 9.0 | 9.2 | 2 | 3 | 3 | naming-decisions | cell-0 | 9.0 | alternatives_and_boundaries=0.70, concept_decomposition=0.80, representative_coverage=0.80 |
+| for-loops | 9.0 | 9.5 | 3 | 3 | 3 | iterator-unroll | cell-1 | 9.0 | alternatives_and_boundaries=0.50, conceptual_payoff=0.90, rationale=1.00 |
+| break-and-continue | 9.0 | 9.4 | 2 | 3 | 3 | early-exit | cell-0 | 9.0 | alternatives_and_boundaries=0.70, concept_decomposition=0.80, representative_coverage=0.80 |
+| loop-else | 9.0 | 9.3 | 2 | 3 | 3 | loop-else-gate | cell-0 | 9.0 | alternatives_and_boundaries=0.50, representative_coverage=0.75, concept_decomposition=0.80 |
+| iterating-over-iterables | 9.0 | 9.7 | 3 | 3 | 3 | iter-protocol | cell-0 | 9.0 | alternatives_and_boundaries=0.70, conceptual_payoff=0.93, rationale=1.00 |
+| iterators | 9.0 | 9.6 | 3 | 3 | 3 | iter-protocol | cell-0 | 9.0 | alternatives_and_boundaries=0.50, conceptual_payoff=0.93, rationale=1.00 |
+| iterator-vs-iterable | 9.0 | 9.6 | 4 | 3 | 3 | 2 figures | — | — | alternatives_and_boundaries=0.50, conceptual_payoff=0.97, rationale=1.00 |
+| sentinel-iteration | 9.0 | 9.1 | 2 | 3 | 3 | sentinel-iteration | cell-0 | 9.0 | alternatives_and_boundaries=0.50, representative_coverage=0.75, concept_decomposition=0.80 |
+| match-statements | 9.0 | 9.4 | 3 | 4 | 4 | match-dispatch-ladder | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=0.93 |
+| advanced-match-patterns | 9.0 | 9.4 | 3 | 3 | 3 | match-pattern-variants | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=0.90 |
+| while-loops | 9.0 | 9.2 | 2 | 3 | 3 | 2 figures | — | — | alternatives_and_boundaries=0.70, concept_decomposition=0.80, representative_coverage=0.80 |
+| lists | 9.0 | 9.3 | 3 | 3 | 4 | list-append | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=0.87 |
+| tuples | 9.0 | 9.5 | 4 | 4 | 3 | 2 figures | — | — | alternatives_and_boundaries=0.50, conceptual_payoff=0.87, rationale=1.00 |
+| unpacking | 9.0 | 9.7 | 3 | 3 | 4 | unpacking-bind | cell-0 | 9.0 | alternatives_and_boundaries=0.70, conceptual_payoff=0.90, rationale=1.00 |
+| dicts | 9.0 | 9.6 | 4 | 4 | 4 | dict-buckets | cell-0 | 9.0 | alternatives_and_boundaries=0.50, conceptual_payoff=1.00, rationale=1.00 |
+| sets | 9.0 | 9.5 | 3 | 3 | 3 | set-buckets | cell-0 | 9.0 | alternatives_and_boundaries=0.70, practical_usefulness=0.80, conceptual_payoff=0.87 |
+| slices | 9.0 | 9.0 | 2 | 3 | 3 | slice-ruler | cell-0 | 9.0 | alternatives_and_boundaries=0.50, conceptual_payoff=0.80, concept_decomposition=0.80 |
+| comprehensions | 9.0 | 9.5 | 3 | 4 | 4 | comprehension-equivalence | cell-0 | 9.0 | alternatives_and_boundaries=0.50, conceptual_payoff=0.90, rationale=1.00 |
+| comprehension-patterns | 9.0 | 9.1 | 2 | 3 | 3 | comprehension-equivalence | cell-0 | 9.0 | alternatives_and_boundaries=0.70, representative_coverage=0.75, concept_decomposition=0.80 |
+| sorting | 9.0 | 9.6 | 3 | 3 | 3 | sort-stability | cell-1 | 9.0 | alternatives_and_boundaries=0.50, conceptual_payoff=1.00, rationale=1.00 |
+| collections-module | 9.0 | 9.8 | 3 | 3 | 4 | collections-containers | cell-0 | 8.5 | alternatives_and_boundaries=0.70, conceptual_payoff=1.00, rationale=1.00 |
+| copying-collections | 9.0 | 9.6 | 3 | 3 | 3 | aliasing-mutation | cell-0 | 9.5 | alternatives_and_boundaries=0.50, conceptual_payoff=0.93, rationale=1.00 |
+| functions | 9.0 | 9.4 | 4 | 4 | 4 | function-with-body | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=0.97 |
+| keyword-only-arguments | 9.0 | 9.2 | 4 | 3 | 4 | kw-only-separator | cell-0 | 9.0 | alternatives_and_boundaries=0.25, practical_usefulness=0.80, conceptual_payoff=0.87 |
+| positional-only-parameters | 9.0 | 9.3 | 3 | 3 | 3 | 2 figures | — | — | alternatives_and_boundaries=0.50, conceptual_payoff=0.80, practical_usefulness=0.80 |
+| args-and-kwargs | 9.0 | 9.6 | 3 | 3 | 4 | args-kwargs | cell-0 | 9.0 | alternatives_and_boundaries=0.70, conceptual_payoff=0.80, rationale=1.00 |
+| multiple-return-values | 9.0 | 9.0 | 2 | 3 | 3 | multiple-return | cell-0 | 9.0 | alternatives_and_boundaries=0.50, concept_decomposition=0.80, representative_coverage=0.80 |
+| closures | 9.0 | 9.2 | 3 | 4 | 4 | closure-cell | cell-0 | 9.5 | alternatives_and_boundaries=0.25, practical_usefulness=0.80, conceptual_payoff=0.93 |
+| partial-functions | 9.0 | 9.6 | 3 | 3 | 3 | partial-functions | cell-0 | 9.0 | alternatives_and_boundaries=0.50, conceptual_payoff=0.93, rationale=1.00 |
+| scope-global-nonlocal | 9.0 | 9.2 | 2 | 3 | 3 | scope-rings | cell-0 | 9.0 | alternatives_and_boundaries=0.70, concept_decomposition=0.80, representative_coverage=0.80 |
+| recursion | 9.0 | 9.0 | 2 | 3 | 3 | call-stack | cell-1 | 9.0 | alternatives_and_boundaries=0.50, representative_coverage=0.75, concept_decomposition=0.80 |
+| lambdas | 9.0 | 9.7 | 3 | 3 | 3 | lambda-expression | cell-0 | 9.0 | alternatives_and_boundaries=0.70, conceptual_payoff=0.90, rationale=1.00 |
+| generators | 9.0 | 9.6 | 4 | 4 | 3 | generator-resume | cell-0 | 9.5 | alternatives_and_boundaries=0.70, practical_usefulness=0.80, conceptual_payoff=1.00 |
+| yield-from | 9.0 | 9.0 | 2 | 3 | 3 | yield-delegation | cell-0 | 9.0 | alternatives_and_boundaries=0.50, representative_coverage=0.75, concept_decomposition=0.80 |
+| generator-expressions | 9.0 | 9.4 | 3 | 3 | 4 | lazy-stream | cell-1 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=0.90 |
+| itertools | 9.0 | 9.6 | 3 | 4 | 4 | itertools-chain | cell-0 | 9.0 | alternatives_and_boundaries=0.50, conceptual_payoff=0.93, rationale=1.00 |
+| decorators | 9.0 | 9.2 | 3 | 3 | 4 | decorator-rebind | cell-0 | 9.0 | alternatives_and_boundaries=0.25, practical_usefulness=0.80, conceptual_payoff=0.93 |
+| classes | 9.0 | 9.4 | 4 | 5 | 4 | class-triangle | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=0.90 |
+| inheritance-and-super | 9.0 | 9.2 | 2 | 3 | 4 | mro-chain | cell-0 | 9.0 | alternatives_and_boundaries=0.70, concept_decomposition=0.80, representative_coverage=0.80 |
+| classmethods-and-staticmethods | 9.0 | 9.5 | 4 | 4 | 3 | method-kinds | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=1.00 |
+| dataclasses | 9.0 | 9.4 | 3 | 3 | 3 | dataclass-fields | cell-0 | 9.0 | alternatives_and_boundaries=0.25, conceptual_payoff=0.93, rationale=1.00 |
+| properties | 9.0 | 9.5 | 3 | 3 | 4 | property-fork | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=1.00 |
+| special-methods | 9.0 | 9.5 | 8 | 6 | 4 | operator-dispatch | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=1.00 |
+| truth-and-size | 9.0 | 9.6 | 3 | 3 | 3 | truth-and-size | cell-0 | 9.0 | alternatives_and_boundaries=0.70, practical_usefulness=0.80, conceptual_payoff=1.00 |
+| container-protocols | 9.0 | 9.7 | 3 | 3 | 3 | container-methods | cell-0 | 9.0 | alternatives_and_boundaries=0.70, conceptual_payoff=0.93, rationale=1.00 |
+| callable-objects | 9.0 | 9.5 | 3 | 3 | 4 | callable-objects | cell-0 | 9.0 | alternatives_and_boundaries=0.70, practical_usefulness=0.80, conceptual_payoff=0.87 |
+| operator-overloading | 9.0 | 9.4 | 3 | 3 | 3 | operator-dispatch | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=0.97 |
+| attribute-access | 9.0 | 9.4 | 3 | 3 | 4 | attribute-lookup | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=0.93 |
+| bound-and-unbound-methods | 9.0 | 9.4 | 4 | 5 | 4 | bound-unbound | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=0.97 |
+| descriptors | 9.0 | 9.3 | 2 | 3 | 3 | descriptor-protocol | cell-0 | 9.0 | alternatives_and_boundaries=0.50, concept_decomposition=0.80, representative_coverage=0.80 |
+| metaclasses | 9.0 | 9.2 | 2 | 3 | 3 | 2 figures | — | — | alternatives_and_boundaries=0.70, concept_decomposition=0.80, representative_coverage=0.80 |
+| context-managers | 9.0 | 9.5 | 3 | 4 | 3 | context-bowtie | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=1.00 |
+| delete-statements | 9.0 | 9.6 | 3 | 3 | 3 | delete-name-erased | cell-0 | 9.0 | alternatives_and_boundaries=0.50, conceptual_payoff=0.93, rationale=1.00 |
+| exceptions | 9.0 | 9.5 | 3 | 4 | 4 | exception-lanes | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=1.00 |
+| assertions | 9.0 | 9.2 | 2 | 3 | 3 | assertion-check | cell-0 | 9.0 | alternatives_and_boundaries=0.50, representative_coverage=0.75, concept_decomposition=0.80 |
+| exception-chaining | 9.0 | 9.1 | 2 | 3 | 3 | exception-cause-context | cell-0 | 9.0 | alternatives_and_boundaries=0.50, concept_decomposition=0.80, representative_coverage=0.80 |
+| exception-groups | 9.0 | 9.1 | 2 | 3 | 3 | exception-group-peel | cell-0 | 9.0 | alternatives_and_boundaries=0.50, concept_decomposition=0.80, representative_coverage=0.80 |
+| warnings | 9.0 | 9.1 | 2 | 3 | 3 | warning-signal | cell-0 | 9.0 | alternatives_and_boundaries=0.50, concept_decomposition=0.80, representative_coverage=0.80 |
+| modules | 9.0 | 9.8 | 4 | 4 | 2 | sys-path-resolution | cell-0 | 9.0 | alternatives_and_boundaries=0.70, conceptual_payoff=1.00, rationale=1.00 |
+| import-aliases | 9.0 | 9.3 | 2 | 4 | 2 | import-alias | cell-0 | 9.0 | alternatives_and_boundaries=0.70, concept_decomposition=0.80, representative_coverage=0.80 |
+| packages | 9.0 | 9.8 | 4 | 4 | 3 | package-tree | cell-0 | 9.0 | alternatives_and_boundaries=0.70, conceptual_payoff=1.00, rationale=1.00 |
+| virtual-environments | 9.0 | 8.8 | 1 | 4 | 3 | venv-boundary | cell-0 | 9.0 | alternatives_and_boundaries=0.45, concept_decomposition=0.58, representative_coverage=0.75 |
+| type-hints | 9.0 | 9.6 | 5 | 5 | 4 | annotation-ghost | cell-0 | 9.0 | alternatives_and_boundaries=0.50, conceptual_payoff=0.97, rationale=1.00 |
+| runtime-type-checks | 9.0 | 9.6 | 3 | 3 | 4 | isinstance-check | cell-0 | 9.0 | alternatives_and_boundaries=0.70, practical_usefulness=0.80, conceptual_payoff=0.93 |
+| union-and-optional-types | 9.0 | 9.6 | 3 | 3 | 3 | union-types | cell-0 | 9.0 | alternatives_and_boundaries=0.50, conceptual_payoff=0.93, rationale=1.00 |
+| type-aliases | 9.0 | 9.5 | 3 | 3 | 3 | type-alias-name | cell-0 | 9.0 | alternatives_and_boundaries=0.50, conceptual_payoff=0.83, rationale=1.00 |
+| typed-dicts | 9.0 | 9.6 | 3 | 3 | 4 | typed-dict-shape | cell-0 | 9.0 | alternatives_and_boundaries=0.50, conceptual_payoff=1.00, rationale=1.00 |
+| structured-data-shapes | 9.0 | 9.8 | 4 | 4 | 4 | structured-shapes | cell-0 | 9.0 | alternatives_and_boundaries=0.70, conceptual_payoff=1.00, rationale=1.00 |
+| literal-and-final | 9.0 | 9.4 | 3 | 3 | 4 | literal-constrained | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=0.93 |
+| callable-types | 9.0 | 9.4 | 3 | 3 | 3 | callable-type | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=0.90 |
+| generics-and-typevar | 9.0 | 9.6 | 3 | 4 | 3 | generic-preservation | cell-0 | 9.0 | alternatives_and_boundaries=0.50, conceptual_payoff=0.97, rationale=1.00 |
+| paramspec | 9.0 | 9.5 | 3 | 4 | 3 | paramspec-preserve | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=1.00 |
+| overloads | 9.0 | 9.5 | 3 | 3 | 3 | overload-signatures | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=1.00 |
+| casts-and-any | 9.0 | 9.7 | 3 | 3 | 3 | cast-escape | cell-0 | 9.0 | alternatives_and_boundaries=0.70, conceptual_payoff=0.97, rationale=1.00 |
+| newtype | 9.0 | 9.4 | 3 | 3 | 3 | newtype-phantom | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=0.97 |
+| protocols | 9.0 | 9.7 | 3 | 3 | 4 | protocol-check | cell-0 | 9.0 | alternatives_and_boundaries=0.70, conceptual_payoff=0.93, rationale=1.00 |
+| abstract-base-classes | 9.0 | 9.6 | 4 | 4 | 3 | class-triangle | cell-0 | 9.0 | alternatives_and_boundaries=0.70, practical_usefulness=0.80, conceptual_payoff=1.00 |
+| enums | 9.0 | 9.2 | 2 | 4 | 3 | enum-members | cell-0 | 9.0 | alternatives_and_boundaries=0.70, concept_decomposition=0.80, representative_coverage=0.80 |
+| regular-expressions | 9.0 | 9.6 | 6 | 6 | 2 | regex-groups | cell-0 | 9.0 | alternatives_and_boundaries=0.50, conceptual_payoff=1.00, rationale=1.00 |
+| number-parsing | 9.0 | 9.4 | 3 | 3 | 3 | number-parse | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=0.97 |
+| custom-exceptions | 9.0 | 9.1 | 3 | 3 | 4 | custom-exception-chain | cell-0 | 9.0 | alternatives_and_boundaries=0.50, rationale=0.65, practical_usefulness=0.80 |
+| json | 9.0 | 9.5 | 4 | 4 | 3 | json-python-mapping | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=1.00 |
+| logging | 9.0 | 9.0 | 2 | 3 | 3 | logging-threshold | cell-0 | 9.5 | alternatives_and_boundaries=0.25, concept_decomposition=0.80, representative_coverage=0.80 |
+| testing | 9.0 | 9.4 | 3 | 3 | 3 | aaa-pattern | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=0.97 |
+| subprocesses | 9.0 | 8.8 | 1 | 4 | 3 | subprocess-spawn | cell-0 | 9.0 | alternatives_and_boundaries=0.50, concept_decomposition=0.58, representative_coverage=0.75 |
+| threads-and-processes | 9.0 | 9.2 | 2 | 4 | 3 | gil-lanes | cell-0 | 9.0 | alternatives_and_boundaries=0.70, representative_coverage=0.75, concept_decomposition=0.80 |
+| networking | 9.0 | 8.7 | 1 | 4 | 3 | socket-byte-boundary | cell-0 | 9.0 | alternatives_and_boundaries=0.50, concept_decomposition=0.58, representative_coverage=0.75 |
+| datetime | 9.0 | 9.6 | 3 | 3 | 3 | datetime-instant | cell-0 | 9.0 | alternatives_and_boundaries=0.70, practical_usefulness=0.80, conceptual_payoff=1.00 |
+| csv-data | 9.0 | 9.6 | 3 | 3 | 3 | csv-records | cell-0 | 9.0 | alternatives_and_boundaries=0.50, conceptual_payoff=1.00, rationale=1.00 |
+| async-await | 9.0 | 9.6 | 5 | 4 | 3 | async-swimlane | cell-1 | 9.0 | alternatives_and_boundaries=0.70, practical_usefulness=0.80, conceptual_payoff=1.00 |
+| async-iteration-and-context | 9.0 | 9.4 | 3 | 3 | 3 | async-swimlane | cell-0 | 9.0 | alternatives_and_boundaries=0.50, practical_usefulness=0.80, conceptual_payoff=0.93 |
+
+## Journey section inventory
+
+| Journey | Section | Support examples | Outcomes | Figure | Score | Caption |
+| --- | --- | --- | --- | --- | --- | --- |
+| runtime | Start with executable evidence. | 5 | 4 | runtime-evidence-loop | 9.0 | Examples are evidence loops: source, a run step, and visible output stay together. |
+| runtime | Separate value, identity, and absence. | 6 | 4 | runtime-object-axes | 9.0 | Runtime objects answer separate questions: equal value, same identity, or the singleton that marks absence. |
+| runtime | Read expressions as object operations. | 5 | 4 | runtime-expression-model | 9.0 | Expression syntax enters the data model; object methods produce the result. |
+| control-flow | Choose between paths. | 4 | 4 | control-decision-map | 9.0 | Facts flow into one decision point; exactly one branch owns the next step. |
+| control-flow | Name and shape decisions. | 3 | 3 | control-fact-shape | 9.0 | Name a fact when a condition needs it; match shape when the data structure is the decision. |
+| control-flow | Stop as soon as the answer is known. | 3 | 3 | control-stop-boundary | 9.0 | Early exits draw a boundary: once the answer is found, the tail stays unread. |
+| iteration | Choose the right loop shape. | 5 | 4 | iteration-loop-selector | 9.0 | Choose the loop from its stopping rule: exhaustion, condition, or sentinel marker. |
+| iteration | See the protocol behind `for`. | 4 | 3 | iteration-protocol-map | 9.5 | for is surface syntax; iter() creates an iterator and next() pulls values until StopIteration. |
+| iteration | Compose lazy value streams. | 3 | 3 | iteration-lazy-pull | 9.0 | Lazy pipelines run from the consumer's pull: next() requests one value through each stage. |
+| shapes | Pick the container that matches the question. | 5 | 4 | container-questions | 9.0 | Each container answers a different question: ordered, fixed, lookup, unique. |
+| shapes | Move between shapes deliberately. | 6 | 4 | reshape-pipeline | 9.0 | Most everyday code reshapes data: one input, one transform, one new value. |
+| shapes | Cross text and data boundaries. | 5 | 4 | text-data-boundary | 9.0 | Programs receive text and produce structured data; parsing makes the boundary explicit. |
+| interfaces | Start with functions as named behavior. | 5 | 4 | function-signature | 9.0 | A function is the first abstraction boundary: arguments in, body, return value out. |
+| interfaces | Use functions as values. | 6 | 4 | function-as-value | 9.0 | Functions are first-class values. A second name binds to the same function object. |
+| interfaces | Bundle behavior with state. | 14 | 4 | class-with-state | 9.0 | Classes group fields and methods so data and behavior move together behind one interface. |
+| types | Keep runtime and static analysis separate. | 5 | 4 | type-runtime-static-split | 9.0 | Runtime values run the program; static tools inspect separate annotations and report before execution. |
+| types | Describe realistic data shapes. | 6 | 4 | type-shape-catalog | 9.0 | Real data contracts combine fields, variants, and expected absence instead of one scalar type. |
+| types | Scale annotations for reusable libraries. | 5 | 4 | type-library-contract | 9.0 | Reusable APIs carry caller contracts through the library boundary with generics, parameters, and overloads. |
+| reliability | Make failure explicit. | 6 | 4 | reliability-signal-map | 9.0 | Different failure shapes need explicit signals: assertions, recovery, chained causes, or warnings. |
+| reliability | Control resource and module boundaries. | 6 | 4 | reliability-boundary-map | 9.0 | Reliable programs name their boundaries: resources clean up, modules import, environments constrain runtime. |
+| reliability | Handle operations that outlive one expression. | 7 | 4 | reliability-operation-boundary | 9.0 | Async, threaded, test, and logging work cross an operation boundary before evidence comes back. |
+
+## Journey figure independence watchlist
+
+No journey section figures reuse production example paint functions.
+
+## Computed findings
+
+- No gate-blocking conditions detected in the computed ledgers.
+- Weakest example diagram score: 8.5; weakest journey diagram score: 9.
+
+CURATED dimensions above are not validated by this report; the audit pass owns re-affirming them (the marginalia gestalt page shows every figure with its production caption for exactly that review).
+
+## Curated: figures changed by the diagram upgrade (2026-09-30)
+
+Scored by hand against `docs/example-figure-rubric.md` v2. Columns are the ten criteria in rubric order: cell fidelity (1.5), earns its place (1.0), one conceptual move (1.0), mechanism over metaphor (1.0), caption quality (1.0) · grammar (1.0), emphasis scarcity (1.0), restraint (1.0) · banner fit (1.0), pairs with cell (0.5). The registry stores the total rounded down to the nearest half point.
+
+| slug | figure | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | total | where it loses |
+|---|---|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|---|
+| closures | closure-cell | 1.5 | 1.0 | 0.75 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 0.5 | 9.75 → 9.5 | Two frames and two arrows take a beat longer than two seconds to resolve. |
+| generators | generator-resume | 1.5 | 0.75 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 0.5 | 9.75 → 9.5 | The prose already says next() resumes to the next yield; the rows add the surviving local. Two rows now, matching the cell's two calls. |
+| string-formatting | format-spec | 1.5 | 1.0 | 0.75 | 1.0 | 1.0 | 1.0 | 1.0 | 0.75 | 0.75 | 0.5 | 9.25 → 9.0 | Densest figure in the set at the banner ceiling; the traced path and the grammar are two things to read. Scored below the row of boxes it replaced on density, above it on fidelity. |
+| bytes-and-bytearray | bytes-vs-bytearray | 1.5 | 0.75 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 0.5 | 9.75 → 9.5 | The cell's prose already states frozen versus mutable; the slots add only that bytes are integers. |
+| regular-expressions | regex-groups | 1.5 | 1.0 | 0.75 | 1.0 | 1.0 | 1.0 | 1.0 | 0.75 | 1.0 | 0.5 | 9.5 → 9.0 | Pattern row, text row and two drops read the move twice. Rounded down for the second reading. |
+| values | value-type-lookup | 1.5 | 0.75 | 0.75 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 0.5 | 9.5 → 9.0 | The cell prints `type(text).__name__`, so the hop restates code; binding plus hop is two small moves. |
+| logging | logging-threshold | 1.5 | 0.75 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 0.5 | 9.75 → 9.5 | The cell's output already shows `debug` missing; the gate makes the reason visible. |
+| recursion | call-stack | 1.5 | 0.75 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 0.5 | 9.75 → 9.5 | The cell's output already gives 10; the stack shows where 7 and 4 come from. Held at 9.0 in the registry until reviewed on the page. |
+| dicts | dict-buckets | 1.5 | 0.75 | 0.75 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 0.5 | 9.5 → 9.0 | Insert and lookup share one caption; the figure draws the insert. |
+| sentinel-iteration | sentinel-iteration | 1.5 | 0.75 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 0.5 | 9.75 → 9.5 | The output already omits the sentinel. Held at 9.0 in the registry until reviewed on the page. |
+| variables | variables-bind | 1.5 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 0.5 | 10.0 → 9.5 | Held at the previous 9.5: the drawing is unchanged apart from the cell's own name and value. |
+| constants | constants-bind | 1.5 | 0.5 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 0.5 | 9.5 → 9.0 | The prose says the binding behaves like any other, which is all the figure shows. |
+| literals | literal-forms | 1.5 | 0.5 | 0.5 | 0.5 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 0.5 | 8.5 | A table of spellings; kept because a page with a table beats a page with a note, queued for a mechanism. |
+| collections-module | collections-containers | 1.5 | 0.5 | 0.5 | 0.5 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 0.5 | 8.5 | A catalogue of four containers; same reasoning as `literals`. |
+
+The adjusted figures (arrow lengthened or arbitrary accent removed) keep their previous scores; the drawings are otherwise unchanged, and criterion 7 now reads the zero-accent selectors as correct rather than as missing emphasis.
+
+The library-wide check behind the recursion, dicts and sentinel redraws: of 115 attachments, 54 carry a code-like mono label that does not appear verbatim in the example's source. Most are formatting drift (`["python","workers"]` against `['python', 'workers']`) or placeholders the rubric allows (`[a,b,c]`, `obj.x`). The three redrawn here were the ones drawing values the example never computes; the rest are queued in `docs/diagram-upgrade-plan.md`.
diff --git a/docs/visual-explainer-spec.md b/docs/visual-explainer-spec.md
index ecdc813..0695522 100644
--- a/docs/visual-explainer-spec.md
+++ b/docs/visual-explainer-spec.md
@@ -61,7 +61,10 @@ Banner positions:
| `after-walkthrough`| once, after the last cell |
Each position holds **one or more** figures via `cell-banner` markup.
-Captions are per-figure.
+Captions are per-figure. Each `figure` is a two-row subgrid (drawing,
+caption): in a pair of small multiples the drawings bottom-align in
+row one and both captions start on the same line in row two, even when
+one drawing is taller.
```html
@@ -91,7 +94,7 @@ Captions are per-figure.
justify-items: center;
}
.cell-banner figure { margin: 0; padding: 0; max-width: 360px; }
-.cell-banner svg { width: 100%; height: auto; display: block; }
+.cell-banner svg { max-width: 100%; height: auto; display: block; }
.cell-banner figcaption {
margin-top: var(--space-2);
color: var(--muted);
@@ -222,11 +225,30 @@ explicitly. Re-introducing either is a defect.
explanation. Figures destined for promotion to the production
registry must drop their inline prose first.
+3. **Figures paint with tokens, never literal colours.** `INK`,
+ `INK_SOFT`, `EMPHASIS` and `SOFT_FILL` are `var(--fig-*)`
+ references. Inline SVG inherits custom properties, so the page's
+ stylesheet decides the ink and one paint function is right in light
+ mode, dark mode, the gestalt pages and the social cards. There is
+ no paper chip. Any HTML shell that embeds a figure outside
+ `site.css` must define the tokens with
+ `figure_token_css(FIGURE_TOKENS_LIGHT)`; Contract 15 checks that
+ `site.css` carries both sets.
+
+4. **Typography follows the page and mono runs are pinned.** The
+ grammar's mono and sans stacks are the page's own (the site loads no
+ web fonts). `Canvas.mono` emits `textLength` with
+ `lengthAdjust="spacing"` so every mono run advances exactly
+ `MONO_ADVANCE` per character whatever face the platform resolves,
+ and a divider computed from the advance lands on the right glyph.
+
## Files
- `src/marginalia_grammar.py` — palette, tokens, words, phrases, metrics.
- Aligned with `public/site.css` design tokens; figures use the four
- palette constants and never pick colours directly.
+ The four palette constants are `var(--fig-*)` references and
+ `FIGURE_TOKENS_LIGHT` / `FIGURE_TOKENS_DARK` hold their values;
+ figures never pick colours directly. `env` draws an environment
+ frame; `ARROW_MIN` is the shortest arrow allowed.
- `docs/quality-registries.toml` — source of truth for attachments,
captions, journeys, and curated editorial scores.
- `src/editorial_registry.py` — loader for the registry (or its generated
@@ -267,6 +289,7 @@ explicitly. Re-introducing either is a defect.
- **No contributor surface.** Contributors do not author figures or
preview placement.
- **No chromatic decoration.** Figures use only the locked palette
- (`--text`, `--muted`, `--accent`, `--accent-soft`-equivalent neutral).
- Emphasis is scarce: at most one accent mark per figure, used only for
- the single element the prose names.
+ (`--fig-ink`, `--fig-ink-soft`, `--fig-accent`, `--fig-soft`, defined
+ in `public/site.css` for both colour schemes). Emphasis is scarce: at
+ most one accent mark per figure, used only for the single element the
+ prose names, and none when the prose names none.
diff --git a/public/og/abstract-base-classes.jpg b/public/og/abstract-base-classes.jpg
index d1b6a02..50ee0a5 100644
Binary files a/public/og/abstract-base-classes.jpg and b/public/og/abstract-base-classes.jpg differ
diff --git a/public/og/advanced-match-patterns.jpg b/public/og/advanced-match-patterns.jpg
index 2156282..08dbaf9 100644
Binary files a/public/og/advanced-match-patterns.jpg and b/public/og/advanced-match-patterns.jpg differ
diff --git a/public/og/args-and-kwargs.jpg b/public/og/args-and-kwargs.jpg
index daeef0c..ec55b38 100644
Binary files a/public/og/args-and-kwargs.jpg and b/public/og/args-and-kwargs.jpg differ
diff --git a/public/og/assertions.jpg b/public/og/assertions.jpg
index 3942f21..99b5c6a 100644
Binary files a/public/og/assertions.jpg and b/public/og/assertions.jpg differ
diff --git a/public/og/assignment-expressions.jpg b/public/og/assignment-expressions.jpg
index 42f90b4..bb65e95 100644
Binary files a/public/og/assignment-expressions.jpg and b/public/og/assignment-expressions.jpg differ
diff --git a/public/og/async-await.jpg b/public/og/async-await.jpg
index 6324655..ded45c8 100644
Binary files a/public/og/async-await.jpg and b/public/og/async-await.jpg differ
diff --git a/public/og/async-iteration-and-context.jpg b/public/og/async-iteration-and-context.jpg
index d0f022c..a09791a 100644
Binary files a/public/og/async-iteration-and-context.jpg and b/public/og/async-iteration-and-context.jpg differ
diff --git a/public/og/attribute-access.jpg b/public/og/attribute-access.jpg
index 59888d1..a981912 100644
Binary files a/public/og/attribute-access.jpg and b/public/og/attribute-access.jpg differ
diff --git a/public/og/booleans.jpg b/public/og/booleans.jpg
index cd3c3ed..7caa5b4 100644
Binary files a/public/og/booleans.jpg and b/public/og/booleans.jpg differ
diff --git a/public/og/bound-and-unbound-methods.jpg b/public/og/bound-and-unbound-methods.jpg
index 4eac39c..9e1c588 100644
Binary files a/public/og/bound-and-unbound-methods.jpg and b/public/og/bound-and-unbound-methods.jpg differ
diff --git a/public/og/break-and-continue.jpg b/public/og/break-and-continue.jpg
index c499fd0..beb50e2 100644
Binary files a/public/og/break-and-continue.jpg and b/public/og/break-and-continue.jpg differ
diff --git a/public/og/bytes-and-bytearray.jpg b/public/og/bytes-and-bytearray.jpg
index 68cbfdb..034a74b 100644
Binary files a/public/og/bytes-and-bytearray.jpg and b/public/og/bytes-and-bytearray.jpg differ
diff --git a/public/og/callable-objects.jpg b/public/og/callable-objects.jpg
index 43c52b1..21b2400 100644
Binary files a/public/og/callable-objects.jpg and b/public/og/callable-objects.jpg differ
diff --git a/public/og/callable-types.jpg b/public/og/callable-types.jpg
index 1038a04..05cdeb7 100644
Binary files a/public/og/callable-types.jpg and b/public/og/callable-types.jpg differ
diff --git a/public/og/casts-and-any.jpg b/public/og/casts-and-any.jpg
index f5a8da2..3f498f2 100644
Binary files a/public/og/casts-and-any.jpg and b/public/og/casts-and-any.jpg differ
diff --git a/public/og/classes.jpg b/public/og/classes.jpg
index ff6a34d..427e8f5 100644
Binary files a/public/og/classes.jpg and b/public/og/classes.jpg differ
diff --git a/public/og/classmethods-and-staticmethods.jpg b/public/og/classmethods-and-staticmethods.jpg
index 57564df..1fdd03c 100644
Binary files a/public/og/classmethods-and-staticmethods.jpg and b/public/og/classmethods-and-staticmethods.jpg differ
diff --git a/public/og/closures.jpg b/public/og/closures.jpg
index 3912418..1396524 100644
Binary files a/public/og/closures.jpg and b/public/og/closures.jpg differ
diff --git a/public/og/collections-module.jpg b/public/og/collections-module.jpg
index 4888411..e8eaa59 100644
Binary files a/public/og/collections-module.jpg and b/public/og/collections-module.jpg differ
diff --git a/public/og/comprehension-patterns.jpg b/public/og/comprehension-patterns.jpg
index fce50a9..d82334a 100644
Binary files a/public/og/comprehension-patterns.jpg and b/public/og/comprehension-patterns.jpg differ
diff --git a/public/og/comprehensions.jpg b/public/og/comprehensions.jpg
index a7f5326..74e1a9f 100644
Binary files a/public/og/comprehensions.jpg and b/public/og/comprehensions.jpg differ
diff --git a/public/og/conditionals.jpg b/public/og/conditionals.jpg
index c921cf0..f60c7ff 100644
Binary files a/public/og/conditionals.jpg and b/public/og/conditionals.jpg differ
diff --git a/public/og/constants.jpg b/public/og/constants.jpg
index 872a868..6a3553e 100644
Binary files a/public/og/constants.jpg and b/public/og/constants.jpg differ
diff --git a/public/og/container-protocols.jpg b/public/og/container-protocols.jpg
index 97f02d5..0a3bdb2 100644
Binary files a/public/og/container-protocols.jpg and b/public/og/container-protocols.jpg differ
diff --git a/public/og/copying-collections.jpg b/public/og/copying-collections.jpg
index b12c215..5a4ac65 100644
Binary files a/public/og/copying-collections.jpg and b/public/og/copying-collections.jpg differ
diff --git a/public/og/csv-data.jpg b/public/og/csv-data.jpg
index 3a67c4a..c50a29f 100644
Binary files a/public/og/csv-data.jpg and b/public/og/csv-data.jpg differ
diff --git a/public/og/custom-exceptions.jpg b/public/og/custom-exceptions.jpg
index f3a4c1b..4874f8a 100644
Binary files a/public/og/custom-exceptions.jpg and b/public/og/custom-exceptions.jpg differ
diff --git a/public/og/dataclasses.jpg b/public/og/dataclasses.jpg
index d007540..bdef393 100644
Binary files a/public/og/dataclasses.jpg and b/public/og/dataclasses.jpg differ
diff --git a/public/og/datetime.jpg b/public/og/datetime.jpg
index b8fbe45..294bbd5 100644
Binary files a/public/og/datetime.jpg and b/public/og/datetime.jpg differ
diff --git a/public/og/decorators.jpg b/public/og/decorators.jpg
index 5185167..e2619c7 100644
Binary files a/public/og/decorators.jpg and b/public/og/decorators.jpg differ
diff --git a/public/og/delete-statements.jpg b/public/og/delete-statements.jpg
index 4e31659..94cf750 100644
Binary files a/public/og/delete-statements.jpg and b/public/og/delete-statements.jpg differ
diff --git a/public/og/descriptors.jpg b/public/og/descriptors.jpg
index f7a3b5b..5ca9151 100644
Binary files a/public/og/descriptors.jpg and b/public/og/descriptors.jpg differ
diff --git a/public/og/dicts.jpg b/public/og/dicts.jpg
index c0fe2aa..bbd13e1 100644
Binary files a/public/og/dicts.jpg and b/public/og/dicts.jpg differ
diff --git a/public/og/enums.jpg b/public/og/enums.jpg
index 10f1891..fc39051 100644
Binary files a/public/og/enums.jpg and b/public/og/enums.jpg differ
diff --git a/public/og/equality-and-identity.jpg b/public/og/equality-and-identity.jpg
index ab06ad1..63d9ada 100644
Binary files a/public/og/equality-and-identity.jpg and b/public/og/equality-and-identity.jpg differ
diff --git a/public/og/exception-chaining.jpg b/public/og/exception-chaining.jpg
index c62efac..a7c6a3b 100644
Binary files a/public/og/exception-chaining.jpg and b/public/og/exception-chaining.jpg differ
diff --git a/public/og/exception-groups.jpg b/public/og/exception-groups.jpg
index 98bdaaa..23e9268 100644
Binary files a/public/og/exception-groups.jpg and b/public/og/exception-groups.jpg differ
diff --git a/public/og/exceptions.jpg b/public/og/exceptions.jpg
index 24ad674..856f55b 100644
Binary files a/public/og/exceptions.jpg and b/public/og/exceptions.jpg differ
diff --git a/public/og/for-loops.jpg b/public/og/for-loops.jpg
index a94806a..88054e3 100644
Binary files a/public/og/for-loops.jpg and b/public/og/for-loops.jpg differ
diff --git a/public/og/functions.jpg b/public/og/functions.jpg
index 1c1fabd..643f3f2 100644
Binary files a/public/og/functions.jpg and b/public/og/functions.jpg differ
diff --git a/public/og/generator-expressions.jpg b/public/og/generator-expressions.jpg
index f67912e..0518e94 100644
Binary files a/public/og/generator-expressions.jpg and b/public/og/generator-expressions.jpg differ
diff --git a/public/og/generators.jpg b/public/og/generators.jpg
index 1e81655..55ef670 100644
Binary files a/public/og/generators.jpg and b/public/og/generators.jpg differ
diff --git a/public/og/generics-and-typevar.jpg b/public/og/generics-and-typevar.jpg
index f47fc84..05787bb 100644
Binary files a/public/og/generics-and-typevar.jpg and b/public/og/generics-and-typevar.jpg differ
diff --git a/public/og/guard-clauses.jpg b/public/og/guard-clauses.jpg
index 8426ef2..daf0eff 100644
Binary files a/public/og/guard-clauses.jpg and b/public/og/guard-clauses.jpg differ
diff --git a/public/og/hello-world.jpg b/public/og/hello-world.jpg
index 9d6be12..c3d84ae 100644
Binary files a/public/og/hello-world.jpg and b/public/og/hello-world.jpg differ
diff --git a/public/og/import-aliases.jpg b/public/og/import-aliases.jpg
index 0c223c3..5c31425 100644
Binary files a/public/og/import-aliases.jpg and b/public/og/import-aliases.jpg differ
diff --git a/public/og/inheritance-and-super.jpg b/public/og/inheritance-and-super.jpg
index 9da9bc3..1fbd86d 100644
Binary files a/public/og/inheritance-and-super.jpg and b/public/og/inheritance-and-super.jpg differ
diff --git a/public/og/iterating-over-iterables.jpg b/public/og/iterating-over-iterables.jpg
index 113f4db..170cb34 100644
Binary files a/public/og/iterating-over-iterables.jpg and b/public/og/iterating-over-iterables.jpg differ
diff --git a/public/og/iterator-vs-iterable.jpg b/public/og/iterator-vs-iterable.jpg
index 47c431f..78d7cd6 100644
Binary files a/public/og/iterator-vs-iterable.jpg and b/public/og/iterator-vs-iterable.jpg differ
diff --git a/public/og/iterators.jpg b/public/og/iterators.jpg
index dddd06c..3e1ba7d 100644
Binary files a/public/og/iterators.jpg and b/public/og/iterators.jpg differ
diff --git a/public/og/itertools.jpg b/public/og/itertools.jpg
index 524ebc4..eb27963 100644
Binary files a/public/og/itertools.jpg and b/public/og/itertools.jpg differ
diff --git a/public/og/json.jpg b/public/og/json.jpg
index 70add17..bc348bc 100644
Binary files a/public/og/json.jpg and b/public/og/json.jpg differ
diff --git a/public/og/keyword-only-arguments.jpg b/public/og/keyword-only-arguments.jpg
index 8b418a9..6fa59a8 100644
Binary files a/public/og/keyword-only-arguments.jpg and b/public/og/keyword-only-arguments.jpg differ
diff --git a/public/og/lambdas.jpg b/public/og/lambdas.jpg
index c4fb65e..61a5717 100644
Binary files a/public/og/lambdas.jpg and b/public/og/lambdas.jpg differ
diff --git a/public/og/lists.jpg b/public/og/lists.jpg
index f147ff0..3275014 100644
Binary files a/public/og/lists.jpg and b/public/og/lists.jpg differ
diff --git a/public/og/literal-and-final.jpg b/public/og/literal-and-final.jpg
index 0fda9b2..50dcce5 100644
Binary files a/public/og/literal-and-final.jpg and b/public/og/literal-and-final.jpg differ
diff --git a/public/og/literals.jpg b/public/og/literals.jpg
index 1009474..b56b085 100644
Binary files a/public/og/literals.jpg and b/public/og/literals.jpg differ
diff --git a/public/og/logging.jpg b/public/og/logging.jpg
index 7c18e7d..397e358 100644
Binary files a/public/og/logging.jpg and b/public/og/logging.jpg differ
diff --git a/public/og/loop-else.jpg b/public/og/loop-else.jpg
index fca1a37..681752a 100644
Binary files a/public/og/loop-else.jpg and b/public/og/loop-else.jpg differ
diff --git a/public/og/manifest.json b/public/og/manifest.json
index 938564e..678dc17 100644
--- a/public/og/manifest.json
+++ b/public/og/manifest.json
@@ -1,125 +1,125 @@
{
"inputs": {
- "about.jpg": "fc631a56878ddf2984336d15058f4704dd7fe58030ca8bf82cf27ed559f9a044",
- "abstract-base-classes.jpg": "4eb9d5a3d66a2b155193e94b811323feea80f393735e574c2de4dfad78168848",
- "advanced-match-patterns.jpg": "007a8737ec0e21fdeac4e7ef62fc6e96d3b18ae063c0683bdd9efc2f40201a77",
- "args-and-kwargs.jpg": "81c412d318a63158b0b2719d31553c9fd1b1ffc44ccd9f42b3f7422f97853647",
- "assertions.jpg": "127114b857df5807ac5cbf480aca13856f74a476a279cf4bae69749f77df51b8",
- "assignment-expressions.jpg": "0c61471475551ca7f3b5486e7a3bac85cb724950b798584baaca2953f5580524",
- "async-await.jpg": "61a41331327edc4cee89d0002d51a095da249e8fda00ecaf5352e1184d42c9dd",
- "async-iteration-and-context.jpg": "2ae349b29c467cff1fbad22723470f6d8245971a89cd105aab863b0d67deb168",
- "attribute-access.jpg": "fde890ae1f69f23b310beb84355237cd00e864d3e8ce4989fd7d4f352d0ee804",
- "booleans.jpg": "2fda05aa2472fc59dad8c1aced133533d425a8a6c6c08a9da1a2257eb4f69b5f",
- "bound-and-unbound-methods.jpg": "93c01e2f5c078b600297f969ef6b7567f498bdcb939fb50b1f0b7effd4f57dbd",
- "break-and-continue.jpg": "b1a134611429e246c5803bf152505c25402ea6c1aad26cfc1b749594e234e7bf",
- "bytes-and-bytearray.jpg": "15544c190969d0c1b64f3337fe3225da330c146a798883c088d635dde04b8926",
- "callable-objects.jpg": "3fac082362050cee00507cb21ee57556a1754f8fea2d3ac935a26883d60598aa",
- "callable-types.jpg": "a53cca3b1bcb0e8c734aa2fcea49f96cd0aa0edfe498d947aefbaebf6a06a799",
- "casts-and-any.jpg": "c02f88d09fa2cd2c1898f1cd84a54ecd44be5a254bb6b173511f5da96258c848",
- "classes.jpg": "ded291ee2f4ecd147bd5f5bf49979ff51e953a1116050b22b6a7d801f16b2e99",
- "classmethods-and-staticmethods.jpg": "11751d8b28eed793ff76a151e699fdcc63b69ab46be16d4ad22ae4cd0a7ac0e5",
- "closures.jpg": "58a33e11dc387020b09096b9b8caba8ff4241e4f0c4da5f0bea810f05abadf9a",
- "collections-module.jpg": "e735b4a27f7c06ec16ca3d28ddec8c4b6258ae0e83f3834070dce284363fd3f3",
- "comprehension-patterns.jpg": "0bb23dad7a472b1dfcbf8fc88e9aa1d861c87fe2587a89755d63bd17082863dc",
- "comprehensions.jpg": "485ac7bade8c8bf292feed056d4ecdd96ddf7b8b95720881f1d291fb0e32b967",
- "conditionals.jpg": "740c2c6ec0297c84e0bf8f6250f14655365d8426a722557ef986183ae8eac782",
- "constants.jpg": "7074e9b55b423de60370b57269aba6d97054c946f5e6144e830159b2b2f2eead",
- "container-protocols.jpg": "d28f1ac24ab2eb13ea29d79c79743e47ae7129d84fad5ccc88c5094778de1759",
- "context-managers.jpg": "d1dca1804eb86bfadbdcdc6f12c60912e864598b1ca0dabb785ca1d7bd4dba20",
- "copying-collections.jpg": "69c608404148bc7e5ec020891da007ca256288941765ea456e15c5ecc3a4a91b",
- "csv-data.jpg": "85aae79d5b3a968e6796a9ade409fb1a21a9cb267e22edf5dcaff85ff681edff",
- "custom-exceptions.jpg": "72bbce088669c16192a2b79f901577602513e64640f14cf3c2b208d1d0e9e2fd",
- "dataclasses.jpg": "c5f4dd06c223584573470bb2487e5ee37b692f317921a4e303953e60abd91305",
- "datetime.jpg": "09b4945c5bce3e191f597b61f85928d67acc1fa78cc1203da13969b7491a2cff",
- "decorators.jpg": "239fb2f672cf23d08e4f9f2bd6bbd443006808cb44f5613e9a2d37250ce3fd53",
- "delete-statements.jpg": "f6d2669d22df3ab989194c63e3c7294bf2e6bebf5a0577fe6c368f64400cd662",
- "descriptors.jpg": "4d14b1e22113ea7a17d509cda6a0becdb92ed53c4cc4076cd9a7e6d31ac24991",
- "dicts.jpg": "e9947eb1388974886bf4199068ee294fed81cd2b902d6c0a7998530b3c4d5744",
- "enums.jpg": "122205f59a0698b83e8d93f6752e374469151f70e86cd46b27a1a27f4ec36b13",
- "equality-and-identity.jpg": "18b2a3fc5ac8cd8ef7fff930d3f49cdac977d3a3bdb297232a1395264df67db2",
- "exception-chaining.jpg": "89f1db2063f48a16b03411ac0ea6197448868c3358818374d2b508d9a1ba38b9",
- "exception-groups.jpg": "8bc2db003be0e6beb10b1a739396025a288c4634f5084e4c0a32af6b61342ca2",
- "exceptions.jpg": "cc611b2e1c5e4ff561876481932df5a127201ba73391b62b8d9f734bf32118cd",
- "for-loops.jpg": "698604d5ea875b2ade49b900207452976b2f1f2a27d9f425ea05b85ae2e1d9f3",
- "functions.jpg": "8b08a68853309f0afde7cd1eb71efbac56dc82d6328d60c88959b62ce93f43fd",
- "generator-expressions.jpg": "a7dbd229875a9d59dc9e628bfa78df698e7f8193731b6583270078d9f563ca26",
- "generators.jpg": "4d6076f85d31f1326fc18ad00071216edeb80f1728e6926cc8336f27f32f478d",
- "generics-and-typevar.jpg": "730b06bc13f0cb18e6f7afd5c83f852f7f7f99f99ec686ad1fc54e3f8389e2ac",
- "guard-clauses.jpg": "c87070175312a945a691ce7d0c2bee0b9576ed2576f46ff26ca5dcd6c816e107",
- "hello-world.jpg": "4ef32ce27d405bf38f3eaead06eddaa85b33a05f5caf4a9623272cdae8b3d7ee",
- "home.jpg": "2124a1fd35c2cde18b94ccdd3c8f99aab5ced63df69d2a493fed3756da450ac2",
- "import-aliases.jpg": "25d64d0cafb41033f5c544a8a8ad3e9db00f29dfc27338fc58572c0df1e8b11e",
- "inheritance-and-super.jpg": "f22dc835d0d40cb64cd17477f9f5627dc92c285fe947478412bf77939cfe6374",
- "iterating-over-iterables.jpg": "bb033e943a684cb3facb4d9abb3581d7910be5df53c8c528a1ea43a0f719a147",
- "iterator-vs-iterable.jpg": "1c9b99e2da6c51beda4abfec940f66406b3b65dc4ec72e1322b16ea857247c1a",
- "iterators.jpg": "2b94e017371b139088be09df2b74ccab0e8d7e824ddec00ae3013ef1e81c8c9a",
- "itertools.jpg": "d7d39083ecc1c0a4fb8d0cef48e131b42045435d5c22be9d1e0a974dcb324039",
- "journey-control-flow.jpg": "5122b50700f494507a64b334bf0e6570d6a56e8e9a1833280e6fa3b962db4d58",
- "journey-interfaces.jpg": "43c184b4131e0519936b617e98c8ed291d369171b882e169d4a88fc4fa9b1063",
- "journey-iteration.jpg": "c1cb46bea442cdc71d7fd8059bc9c041b73d55171650ab1eb12080226a4c5a2e",
- "journey-reliability.jpg": "de28052e7841e7570e884f2fb25a7138e6050ac8b052b61b95444604382a3669",
- "journey-runtime.jpg": "b715668f6a3c9c48da20ed7abaa789270c6872f08ecf07082f4069ad8bf8ce7f",
- "journey-shapes.jpg": "1830653ff50999f6fdd9758c3a0385dd21aab45958b3e329a91066e3ccbd9ccc",
- "journey-types.jpg": "9bb0fc59e9b10ff8215a0b87b2936aefb7a85ab7564500717d2841b5f7510b58",
- "journeys.jpg": "a65158f9f26307a7204c4a0d57895d17e4f1a08f807a5fcf11422804a175a0fe",
- "json.jpg": "8c245cbe351a9debb73ef21959b12d9b651237371e5dd53f4867776ab7bd7bda",
- "keyword-only-arguments.jpg": "d497855397457f163de5259a6417f783012030d7014d561f16f80c7c27b9e8d7",
- "lambdas.jpg": "3ed20aa5a8bc388b28b3e94a933ce3384aa08f6fb02d5c867f705d24ea57f5c8",
- "lists.jpg": "e8e2d268fce9da5ebd1d4316ad467ecb12e72d7c048e436a032d82640136ba5d",
- "literal-and-final.jpg": "b793717216bc791c400d0b1db37923576077ebc056704feab1886fdb5109c3fb",
- "literals.jpg": "693ff2336faabec85527cddae4677074cf68670928e703f1d3793de5c3524155",
- "logging.jpg": "df716fe4291cfef5384a47ade35efaf45b66f815bc7cdd48341405567f355153",
- "loop-else.jpg": "7562ec642fb87473abe071269bdcad8ddacb2fa9cb351f42cfd0a448ba34f222",
- "match-statements.jpg": "00a0584666704e38ff9af9d317d17ef1f6d72115ace376348e0f208cacb8d46f",
- "metaclasses.jpg": "e0e56982acfd2d8e7669c0a5084c5b69dcf835883a913891624f104121e14b12",
- "modules.jpg": "5ff3b3098047e4eb0071dc66d203c18d46759e26b587861ab8847daa4c5c71ac",
- "multiple-return-values.jpg": "745935d0d57f5b930f42586f86317c417d1a911c08ca46c348444a76f4024edc",
- "mutability.jpg": "c17ab7227b73d2e281424a2c54fdaf1c66818d6315ea66fb6d3ecba35e3ec9fb",
- "networking.jpg": "3f986210fa5e154f922d1510ede795a399f1039931bfa837d73ede70386ac5aa",
- "newtype.jpg": "edbbefb72cf1f9be63319a2b859667ef6c8a832da7645fc902a452178e1a4843",
- "none.jpg": "bc42a5c644a40b0afcd24038852719b400f602f11dd8fa4fd3599751d67d1fd0",
- "number-parsing.jpg": "43c4bbd69a67ad7f33244b09e2cee5b39312ba5f496ad95bf4a4d2e202cadb8b",
- "numbers.jpg": "b539c33471e70534cc7b1ab37888be2bf58442f77e634fb1be1810a184173355",
- "object-lifecycle.jpg": "4bfee8be77a7926c5f19665b3c85c041d9b1d6e90c3b381d7d76d3e1c8beeda2",
- "operator-overloading.jpg": "10ed2e6cf319c4e0fe84c22862a8abfaca9b50599f6e1e1c6c3f883dbb4328e9",
- "operators.jpg": "01cc9856474665bf8b293a4c70f28d981f22c9d38fc55e610ce41875f356f57e",
- "overloads.jpg": "741e318ba9c664c4b84f0f6aaf7c05d956d57143d4fe83540d92d57fcf183e1a",
- "packages.jpg": "f52933e5dfa8dbf79a0496082cc782bffe68d8ec5e5d91c649329df49e38f9cf",
- "paramspec.jpg": "bd96b51b333f30496c90b4bed08edf867e583258c208786dab8690acbd7ac727",
- "partial-functions.jpg": "fd8ae2052a7d6b1cb17418111fb5083887b0903c19ef37394777b5ab304ad0f0",
- "positional-only-parameters.jpg": "93d22d48edb838e725b7a1f7bf285e72dad09bb56d151f21eb91ecc3f36ddff8",
- "privacy.jpg": "3eb2a28f8ace3441f6b95ad43facefaa78dabd11c0061bbfc70ef7a5b15c4fa7",
- "properties.jpg": "f42d17549e38b8b4ee8b18797deee917495ed75807da28bdcfca05db27c66a9b",
- "protocols.jpg": "fee2838223a57c3352a848f4988ada030e48bd9b4bb36e496682f68e6556fa2c",
- "recursion.jpg": "d3287842bdefa7ad12e8634f98e394e47d4b1990f7efcd8aa60a9bc418997830",
- "regular-expressions.jpg": "63e91f550b56acacc2391c74e9e1efae6de7d800bb762f76bacb287e8d088c01",
- "runtime-type-checks.jpg": "406dcd87d538027641fecf5584f0547ebef84c37f8580a442afd7ec4d56d9151",
- "scope-global-nonlocal.jpg": "f8fab3dec00ab15ebc117961b01be1304f50795ea015ac0144553ba596945c90",
- "sentinel-iteration.jpg": "56c7213ffdd2a607fd3b662ac95f299efed0fc94d199e07cbe58c45822228033",
- "sets.jpg": "8139946e9c0307d55f436f962ff67a90d21a62881df072c2ef0781b475d81d3a",
- "slices.jpg": "0d5d2d1a226cae265603f76b522a34c9a5ae2b72a5e51bd6e2b7280f37e869ca",
- "sorting.jpg": "99ec4849544eece9ddf03f9e94932c3eca909b63b1394b85cb2d9df10dfac6d0",
- "special-methods.jpg": "3b27cea80b1aea350e7abe2ea54a7395514a8d73c4c712ef0d708afaa2522fcf",
- "string-formatting.jpg": "9ab912a0ad18e99a8d0156cb51c02aad5e9039c0629a0b51ed392050f79611f9",
- "strings.jpg": "deb4d52d16a39866a4cf0cf5a3b25d2c82646e33349441c151376f6ecf5ab4b8",
- "structured-data-shapes.jpg": "91ec3d749262877ca5540de6629adaeedc7593039822518fa5177d872f51758a",
- "subprocesses.jpg": "cc6150265004b2838056e3476fc6ec6d80c5ec681a148630bb01c0cfc621878c",
- "testing.jpg": "01229650874207e8a3c483f51bcf73c256ded7e368bc7a0a6688f79085d342ba",
- "threads-and-processes.jpg": "0b51d992cf0cc7a4218a6a684b3f0f4f29544580f2feec55eacdbfdf64e571c5",
- "truth-and-size.jpg": "8d97d02a1b3f2f97f38f20bdef5544edb46ed82fbb58ca35f354e19c15302a55",
- "truthiness.jpg": "e78f3aefaf69a44d4a33d31bf9aa14456cbd7cb6bcec79457d12abb183a06c29",
- "tuples.jpg": "7dee6259e2d45cd62b1d5056a9c6031ea9d8d959df2ffa5e54aeeae215dcf413",
- "type-aliases.jpg": "3f63a98319ca4cebc10f82aaf41faad23138d9e9f2cf55d5afe2cfd76179b957",
- "type-hints.jpg": "ab4f5733deef421d54da9ab6bd346edcf36726356ac1c64ca68213e3d497a81a",
- "typed-dicts.jpg": "9fa240bda06a3be73fb35b5a890622f2893774f505547410c603ba82c8963357",
- "union-and-optional-types.jpg": "1cb38f7b050243af7fa7b36c1bab7a6042f0350be37973fd12a6e2970f63451c",
- "unpacking.jpg": "c84a419c3b68d94c79e2b8cea03e028469b33580da6823e569012d46845ab283",
- "values.jpg": "1f7fa50f830546b66a48c205967c793e3be2181bd58c0186c1c5f5a25578b294",
- "variables.jpg": "2e4a5fb4e2969998fd3a6e03a50e5f25351cd4d08ca4bb2572ceff88525a54b3",
- "virtual-environments.jpg": "53801b234c21ecb97124cb2acb59d5c44e4830d69b2109230f3e78fad29a5aa7",
- "warnings.jpg": "3809067ed3721c6f1797e629e910c8881c787df78006f1e0eca8d68035883fea",
- "while-loops.jpg": "8b9a4647848fd5b3e847495262a48e4542b5156867c045c486c444c3b935dbf6",
- "yield-from.jpg": "19867ab0652dc62d14ebd5a61f9a9261f2a4967c19c2cd06366583921d5aa4ba"
+ "about.jpg": "95a379ab627fd3f5195fe86d60794c607e6928b9da79999a56d1c13b96420028",
+ "abstract-base-classes.jpg": "e3b2c28ac965a09d604cd82c863744c145f060c423a428e2cc5fb112ab38dba3",
+ "advanced-match-patterns.jpg": "b543d94bff57203ad3b38a291a9779cebe4ed6eaa5e5cdb1e85777edbbeaf17e",
+ "args-and-kwargs.jpg": "e930f2987327fff969a5491db7cc6a4782ad45487e415c03a1a5eab7c1fef123",
+ "assertions.jpg": "bcc9cfe3276a6e60536fe0c6621e2e74ae925b149e17ec6dc5da4240a5914dcd",
+ "assignment-expressions.jpg": "002797e13142aa4ae1a8534fcd25c9d05168c606dc6c7e7de138abbd04d9d5d8",
+ "async-await.jpg": "ff5b7d436c81a784061c8f397b64d0ee37dbca637b818e43466af53bbdd17d6b",
+ "async-iteration-and-context.jpg": "b5e48881ff959f112d1e73ca94c28dee31e618a74843d3161590698e005220c1",
+ "attribute-access.jpg": "78a2a591729de0c6c366c3532940d22a89184c7fca8de6e5d1b2bc0a51367ccb",
+ "booleans.jpg": "d211264a6c8b24e9b5b651520d2ca1329cc74eee06fb39d71e0861c7dc069e3b",
+ "bound-and-unbound-methods.jpg": "375266281cc733f1b7e182082e6d7076a95ff3bff166cbbb3776fa5a19f20d1c",
+ "break-and-continue.jpg": "d071b149980b5b8acc0b8029741ad7fe16789f3ee79b9ae205072272851c3756",
+ "bytes-and-bytearray.jpg": "33dca4a38dd9501ceed2202971edeb1d0af6aa198c227fa11cfbcc152f5fef08",
+ "callable-objects.jpg": "9666567e3456bae76c605c9b49988c4d643b4235a85d76fb8bf9fb376d466401",
+ "callable-types.jpg": "adca07983c1d822748932e5dec09e416f3fcdddba4b6749eb879502c04d897ff",
+ "casts-and-any.jpg": "125f3eced3c02e40949c50cab46a98cf324e2cf9a51988f55bb2424709847afd",
+ "classes.jpg": "e7e56c07a81f4c7282663f7c1e8ea25487a90e59fefe7d77319df4c43134352d",
+ "classmethods-and-staticmethods.jpg": "ad8c6c45ce3ed584e3c0de24271945301d84a3f6acb0bc172d05459968f3def8",
+ "closures.jpg": "3a78039bd063458bca2a46bf51f92839a342e7e4e6a191a6ebe59dd45744d654",
+ "collections-module.jpg": "d9b2dd27659ecc6085682d53990e5a7c5ceb23d797abb608f76549fb028153c9",
+ "comprehension-patterns.jpg": "823a860aee2c8e5e33ebc49c77f18ead8cbeacddca2df67a80b9b8470a8b83d3",
+ "comprehensions.jpg": "a4d586394db93dfa140f624d87bc4b13fde168f5599feb98372537f4b3cbce93",
+ "conditionals.jpg": "86284a3e11f9c64a32122f5c130f9157c2a190128914af40a3ae2abe49f56f72",
+ "constants.jpg": "8fe9653f6f286b8e13881c0368bc089cc05c320c7a1b37622f10f9f20e9ae841",
+ "container-protocols.jpg": "71bb1dd8a7d4e6d78bba3db798064cdcd90db8cee3a91f6981a63ee9ec78597d",
+ "context-managers.jpg": "9c0848423c137a79235206f3712077552340768af386206fde72cd93b8558331",
+ "copying-collections.jpg": "e716496d14652d3b518c5ebd47d3136e8bd3647ba47314cdd507195fa5e50392",
+ "csv-data.jpg": "44750d69e0fd9c650536ce8b5b997dd6b43be35479a8360237d2dad3ce55bb34",
+ "custom-exceptions.jpg": "383e39f00b95519e1c32e09c6b7073c66700a32780c3a0dffe9f0773023e316a",
+ "dataclasses.jpg": "e95eb12a497d5fc01c3d46f04b2db5bb52ab1f51019cd6eb39039403010a4e1b",
+ "datetime.jpg": "1199a178511fb0a8598d5253739d76368e7ab4da551c2d5604c273c2f239a77c",
+ "decorators.jpg": "b4a56cea2653bffb79b9f269d6f2cd549e4d5728056ded07df9d20ca0c50492b",
+ "delete-statements.jpg": "13a44093ffe9eaaa663168bd9e3db4f63b0dd9db45a0bbf5e74d0726b0cf70a0",
+ "descriptors.jpg": "5899b29ac99ed9c858ad6fef1b024f228b6fb9b5cefde0f89ff8cc418da57a47",
+ "dicts.jpg": "897523290ec0fe1daaeccbf39c45c6e4d489a1c648489d41c5487a5be08611a1",
+ "enums.jpg": "8ed23ecc94e30dcee017b738f3d33a626ca6576b9836e1e9b7a8d75b60183898",
+ "equality-and-identity.jpg": "1622f2f2d805fa76f627ef06d6060aa2974b222852c7f81861de1ad82d315658",
+ "exception-chaining.jpg": "38c928a27b70b527046763d58cce3c356177f976242a3cee92e3f9b5440a7cff",
+ "exception-groups.jpg": "85d625b68bb4bb10fbd9a1e0ccdaa3ccc87885d020a251417ca6b5cc561b6998",
+ "exceptions.jpg": "4bb40003b81562f4733e25b29fc37960bda53d13a0fb8976ed086c5c37cc9c4e",
+ "for-loops.jpg": "b6126bf8695b73b6c8c19052dd6aea9aec15e2559576f0fe766b70e54e298215",
+ "functions.jpg": "d1677c243591d3511e4cf5e47188736206c15ecafb7b6309af7631ef66bd4e78",
+ "generator-expressions.jpg": "25daaaf327e1faa6007a63e821a0f6f3027cb20a0526ea13044b2c0005e8abbe",
+ "generators.jpg": "e0a7fee94dd239fdf174583061a04e34ed1a530437a3710e31915159812b6810",
+ "generics-and-typevar.jpg": "d3d54dcafb5c31529898493866d3bf0f99397d84285a55292748cddd05fe3da2",
+ "guard-clauses.jpg": "5eac9618b5dfc78c76dd430b0387e16e522acb1845106bcf6f85f3e24efbcb48",
+ "hello-world.jpg": "23d49f2faf40acb57f24e276157523ad4775b443079be4717176158d85aab71a",
+ "home.jpg": "8bf820047ec9377519fb67594c46d7bd026e322af87caea28acc32d643438e60",
+ "import-aliases.jpg": "cc2216976ed3323031ff8e708c7a8c27b4e53b1daac35567f073e5f16cbfa0af",
+ "inheritance-and-super.jpg": "a4bb54a91bade5aa4aae2ffdd8dc4e318656dad673079566ac3e0438262a89a5",
+ "iterating-over-iterables.jpg": "e0483b2a6b44788b4d661d2cc9f1528497bbde62de8521462b427da417228485",
+ "iterator-vs-iterable.jpg": "b7b29cfe2f4e8dd6c5645d94c86cf2c181427103ca83b3577d8f8b6124b035a5",
+ "iterators.jpg": "d7a18b96a2aa29eb0caa8152e14fa06d92b214806174a988ccf967462042da19",
+ "itertools.jpg": "b5f37d0bfbb3e66713e92fd2382e02c5ee6665188f9061410f4a744be941d3b1",
+ "journey-control-flow.jpg": "3af49c5fe7d172295d6ee5646bf258d7d356600c08486696c023699630f0e733",
+ "journey-interfaces.jpg": "6bce39cc262dc18bbdd96eeb3a12abc76efe139fe0ffbeb8e8c9e60ddc4b38a3",
+ "journey-iteration.jpg": "e38968bd55f23cea42d8a41ca65b4966370355e6a6a45fe02853385c5ac51ba5",
+ "journey-reliability.jpg": "8d227252f8e36281e504be2131c4964759bcc3c10151f237100c87922258a480",
+ "journey-runtime.jpg": "31f979aa81c8586a51bbbc8b005a59ec7aa86af57d60976bad24694edf522e0f",
+ "journey-shapes.jpg": "68015e6b3396f30fba8fa2a190bc1334eab48aaf2243d56bedc0ca5c04293df1",
+ "journey-types.jpg": "3c94076f9634348ee59d9580dd3211dcd5bde9a5e8df6c565929fa9ce8ee4eac",
+ "journeys.jpg": "409ae1f4328026eb6cfc4a6812ed279d1281df81b971aeb3d6faae1f30834c96",
+ "json.jpg": "be7853e69b3f534fe6057952e8d31e819becca57b7d437d1400077d62ff70435",
+ "keyword-only-arguments.jpg": "159598af31c031986e4f07f356ff5ae214adbdfe127281f4df97cb94b752866f",
+ "lambdas.jpg": "09edf49e893b4a588636596c334ebd6632504521707c2d7b6d306e2df8dcd0fe",
+ "lists.jpg": "4b1c57d1028533252633b332ae16454c135fbdaf951ca293030e85f0cb458787",
+ "literal-and-final.jpg": "55816e31d2a3c45617375b416c16d83f974b63bce5be650a4b586a0fb89afe31",
+ "literals.jpg": "d6a17a596bc71dad3728ca6a2ed386a6e191622dac20228750e9427b801936d1",
+ "logging.jpg": "0fd1b7b3018242d2c47069c747769cd5d1d5ce3becccc7523419c336ab9a397f",
+ "loop-else.jpg": "a78291c7fe683c4cc0004ed837d6a46f1f4079c7b3bd80606827919793984ebe",
+ "match-statements.jpg": "824a9e1d280338353388bcc7f11c37864a7e7a99729b65c7d6285ba77572be19",
+ "metaclasses.jpg": "0409dbdfabfe75f92c9f42e9a5d7721fa5a83b58eaa2aebf59c0f8b32e00609b",
+ "modules.jpg": "6d2ef2bf58f64f7ce6eda5cd24f3f24c2ad38ba842153aab80167bbddb427a93",
+ "multiple-return-values.jpg": "a054e2bf590239f35ef1ed0815202ffa54ec76223dad96fa42668899afffccb1",
+ "mutability.jpg": "8af2851fbc1d507df037e538855e929e723462c94d02bc8a49b0afe398adff66",
+ "networking.jpg": "2af69c46890b595808b4317301da8302bd3b52d0dab0865e95efd8a5d3c65de2",
+ "newtype.jpg": "d0ccba888dcf57edc4611354bb3e1ca13d8d8eef5239a02223f75e6f8801cfb4",
+ "none.jpg": "96ebebde5daa6e80c4e8ea9d310ea4266b2079a9264f5a619fb2e9d1b3736d2c",
+ "number-parsing.jpg": "f678cfb478a23bd4e1e17b25e40a29f0d472ca8372ead0e412afc2eb2218e9cb",
+ "numbers.jpg": "b2d83311c23941a21cc07ed60ec1c1bd48d726c22ce1c429d141ba6ad9805f85",
+ "object-lifecycle.jpg": "94325fcaa13ccf630685bfb2b91f73264b2db5ded70681028961a4962a3c883f",
+ "operator-overloading.jpg": "a0c349063e162855c07085e7c24a41e71b86e57fa215f82120ca9825e1d3fd62",
+ "operators.jpg": "4adb02e1d55471df8f65676a02ff32c1243ce6eaaeae422bef16ccd3fb3fd7a5",
+ "overloads.jpg": "a530a32139fa033981b05cc467b40c0a4c073dab856df8f484bcfbf446cfa3c2",
+ "packages.jpg": "8f217e948744b8d5137911c5fe7b156dfc5f2b1e62de8c4aaa486c22ab955638",
+ "paramspec.jpg": "b3352f5b5fb56bccbb74da26b30a42d68ca6eecf56c7b4b1f74d3f9a060703f8",
+ "partial-functions.jpg": "18172b5e04a4ef759bb003e452980e3bb45c3ee59d70c6d96c8418587c9ed01f",
+ "positional-only-parameters.jpg": "fedc77af018f191a651fbd5966c90f7e3cbf488f5f8281011d0f826864ae143d",
+ "privacy.jpg": "71807ae014e95a733f3ff6548852d57fa2134ae12dc41c7dc1b7f9d0460f27a9",
+ "properties.jpg": "869c81bb73c66ae35def9ef6c23e3965740e15702b4f7d809b6c30454392b3fc",
+ "protocols.jpg": "d871f212cc114f957ec1eb602547311c40e9f69b3f21959d5063994abadad3ad",
+ "recursion.jpg": "5c1cdee9ac58624b1fab8924fd32c88d1f504f8b065627020fc8bcaf31175888",
+ "regular-expressions.jpg": "12cdd4688babd2fdb34efae509841ea3c4179e88d77e19af6fa79009f7625124",
+ "runtime-type-checks.jpg": "0bb5334ef5f3cfa742a59f36c0e620ae5942a411c1737a9d832cdfeb1a2a772b",
+ "scope-global-nonlocal.jpg": "2c290e9e6ce601d44f9d56d0c5189c7630c94b7b7d7c56cc288d69285f984ba9",
+ "sentinel-iteration.jpg": "f23aef76cf9e68d71c1991b4296ecd0d2bece46500cd8680661d027c3b3c671c",
+ "sets.jpg": "9dcbff083b66e5f26bb3b2c258ff56ce65d121314f8e91d497851da9171f5191",
+ "slices.jpg": "e6e83ab70f13a73a893b75ce5c5a9e514aaedc576ab1fca41bbd8b72bb697529",
+ "sorting.jpg": "831abc9432a976f0bf59c2aa603f3f1a0381a217b4561bf9b8ef8ea57a7df2cc",
+ "special-methods.jpg": "ab67329531d657986b0d082a282ae1c1cbe7d776946a257f0b9c7c74157f37f6",
+ "string-formatting.jpg": "e9762ac3c5b820863b671339fe4a9b2647dcf1ac82a825f2ef55d40957c19550",
+ "strings.jpg": "dd4e643795b541b090cabaf84db3174d6016a6e343b0846e7992fbed545ac166",
+ "structured-data-shapes.jpg": "c2a28bcaae4e06fa3f312c9b7275e6901cf7b82cc177cd3f5edbc1d16f4b4d7d",
+ "subprocesses.jpg": "12758063e1ed5f603796ef2cddc527df6368151ab0942051ce09f3ed52323a09",
+ "testing.jpg": "02e83e7ffd92d3bc53c0539e0d24735ca63d90ad7518e2fa7eb6ce31bd5d6194",
+ "threads-and-processes.jpg": "3f8e51b176efbb0c76648c48eb1f99331d35f68bc53c55db27107724cc3d1c45",
+ "truth-and-size.jpg": "816d8b8b3c44c7980c9bb2523d30b49391179155ce0689c5ec1feb757575197b",
+ "truthiness.jpg": "58805b51b4633eddf09e32c2b9b41505d2e806135cafaa167969e4e8f14df8f0",
+ "tuples.jpg": "77978ab66d56f16deaa723d2ea8e6003fbcd87aba4454f101ee271cddb2fd1a2",
+ "type-aliases.jpg": "8e60b0bbce1e8c9b1f987f6697e288bd22e1c364421808b52e89196dfa6cb8fb",
+ "type-hints.jpg": "d68619d9ff72589d3fd16c10d9456d8cc7950da3a911e46e58dfc34dc6b92afa",
+ "typed-dicts.jpg": "aa85e3ba4ef56d7780f517474e5e8e45116bcd936467c4572c5c2ec441277adc",
+ "union-and-optional-types.jpg": "42d5c4d027200a5eb66c6e6acf387e93ffca43174b9fb5aca0bac1bb68c6971f",
+ "unpacking.jpg": "37d1f9de084bbb30f31889051bc3ff483d0a641affa27c2c24aaf684702fbb51",
+ "values.jpg": "e18c5be1f8ce3250d23b2fcbeb936aff381a9856a687c6b11beb23859b2377ad",
+ "variables.jpg": "1c7c32d89fe9addf61e975c44ecdf9e78b7676a9542de58d8a78077448525191",
+ "virtual-environments.jpg": "7b2e6c774b91cf219e617f4166230088e80029ea95f99594c61b0ef93f71956f",
+ "warnings.jpg": "4b4dc83e5c1c123179ddffd4044d0aeb412a599a81092c04df4a2f5e150f5f2e",
+ "while-loops.jpg": "78862cbf71c79815b3f251fb684483668b3cc73402e782ed6db575452e9e67af",
+ "yield-from.jpg": "2f126f132e6df911d8e505826bfa43e85041fc19cee3c9192bf1807fe7a5f321"
},
"version": 1
}
diff --git a/public/og/match-statements.jpg b/public/og/match-statements.jpg
index 80c836e..a93d1f7 100644
Binary files a/public/og/match-statements.jpg and b/public/og/match-statements.jpg differ
diff --git a/public/og/metaclasses.jpg b/public/og/metaclasses.jpg
index ec1e653..bd052fa 100644
Binary files a/public/og/metaclasses.jpg and b/public/og/metaclasses.jpg differ
diff --git a/public/og/modules.jpg b/public/og/modules.jpg
index 3aa6adc..b757b90 100644
Binary files a/public/og/modules.jpg and b/public/og/modules.jpg differ
diff --git a/public/og/multiple-return-values.jpg b/public/og/multiple-return-values.jpg
index 57db9e7..d774d64 100644
Binary files a/public/og/multiple-return-values.jpg and b/public/og/multiple-return-values.jpg differ
diff --git a/public/og/mutability.jpg b/public/og/mutability.jpg
index f3f5f89..4b20d86 100644
Binary files a/public/og/mutability.jpg and b/public/og/mutability.jpg differ
diff --git a/public/og/networking.jpg b/public/og/networking.jpg
index ed980d5..462de33 100644
Binary files a/public/og/networking.jpg and b/public/og/networking.jpg differ
diff --git a/public/og/newtype.jpg b/public/og/newtype.jpg
index fde46b8..1c4a2fd 100644
Binary files a/public/og/newtype.jpg and b/public/og/newtype.jpg differ
diff --git a/public/og/none.jpg b/public/og/none.jpg
index 39ec53d..2524da4 100644
Binary files a/public/og/none.jpg and b/public/og/none.jpg differ
diff --git a/public/og/number-parsing.jpg b/public/og/number-parsing.jpg
index 7f9e654..eacbb36 100644
Binary files a/public/og/number-parsing.jpg and b/public/og/number-parsing.jpg differ
diff --git a/public/og/numbers.jpg b/public/og/numbers.jpg
index 60467a4..e5c6073 100644
Binary files a/public/og/numbers.jpg and b/public/og/numbers.jpg differ
diff --git a/public/og/object-lifecycle.jpg b/public/og/object-lifecycle.jpg
index 262e095..5172074 100644
Binary files a/public/og/object-lifecycle.jpg and b/public/og/object-lifecycle.jpg differ
diff --git a/public/og/operator-overloading.jpg b/public/og/operator-overloading.jpg
index 53daeba..050b258 100644
Binary files a/public/og/operator-overloading.jpg and b/public/og/operator-overloading.jpg differ
diff --git a/public/og/overloads.jpg b/public/og/overloads.jpg
index 699ebe0..037769f 100644
Binary files a/public/og/overloads.jpg and b/public/og/overloads.jpg differ
diff --git a/public/og/packages.jpg b/public/og/packages.jpg
index 33844bc..750e631 100644
Binary files a/public/og/packages.jpg and b/public/og/packages.jpg differ
diff --git a/public/og/paramspec.jpg b/public/og/paramspec.jpg
index feaa756..1d829b7 100644
Binary files a/public/og/paramspec.jpg and b/public/og/paramspec.jpg differ
diff --git a/public/og/partial-functions.jpg b/public/og/partial-functions.jpg
index 868eab6..575af4b 100644
Binary files a/public/og/partial-functions.jpg and b/public/og/partial-functions.jpg differ
diff --git a/public/og/positional-only-parameters.jpg b/public/og/positional-only-parameters.jpg
index b7d1552..3cc1090 100644
Binary files a/public/og/positional-only-parameters.jpg and b/public/og/positional-only-parameters.jpg differ
diff --git a/public/og/properties.jpg b/public/og/properties.jpg
index 77f251f..ef27ca7 100644
Binary files a/public/og/properties.jpg and b/public/og/properties.jpg differ
diff --git a/public/og/protocols.jpg b/public/og/protocols.jpg
index c40e708..e762ffd 100644
Binary files a/public/og/protocols.jpg and b/public/og/protocols.jpg differ
diff --git a/public/og/recursion.jpg b/public/og/recursion.jpg
index d3f993b..62a7899 100644
Binary files a/public/og/recursion.jpg and b/public/og/recursion.jpg differ
diff --git a/public/og/regular-expressions.jpg b/public/og/regular-expressions.jpg
index 348dec5..ce0bf05 100644
Binary files a/public/og/regular-expressions.jpg and b/public/og/regular-expressions.jpg differ
diff --git a/public/og/runtime-type-checks.jpg b/public/og/runtime-type-checks.jpg
index e1e36c3..d0630f2 100644
Binary files a/public/og/runtime-type-checks.jpg and b/public/og/runtime-type-checks.jpg differ
diff --git a/public/og/scope-global-nonlocal.jpg b/public/og/scope-global-nonlocal.jpg
index 70575bf..b0c334d 100644
Binary files a/public/og/scope-global-nonlocal.jpg and b/public/og/scope-global-nonlocal.jpg differ
diff --git a/public/og/sentinel-iteration.jpg b/public/og/sentinel-iteration.jpg
index 443d629..252b7da 100644
Binary files a/public/og/sentinel-iteration.jpg and b/public/og/sentinel-iteration.jpg differ
diff --git a/public/og/sets.jpg b/public/og/sets.jpg
index 05fcb79..e7c4eb3 100644
Binary files a/public/og/sets.jpg and b/public/og/sets.jpg differ
diff --git a/public/og/slices.jpg b/public/og/slices.jpg
index 89ec798..533a7d2 100644
Binary files a/public/og/slices.jpg and b/public/og/slices.jpg differ
diff --git a/public/og/sorting.jpg b/public/og/sorting.jpg
index 2ef3f67..fe6ebc3 100644
Binary files a/public/og/sorting.jpg and b/public/og/sorting.jpg differ
diff --git a/public/og/special-methods.jpg b/public/og/special-methods.jpg
index 2a8792f..574dfa7 100644
Binary files a/public/og/special-methods.jpg and b/public/og/special-methods.jpg differ
diff --git a/public/og/string-formatting.jpg b/public/og/string-formatting.jpg
index de2128d..31954e6 100644
Binary files a/public/og/string-formatting.jpg and b/public/og/string-formatting.jpg differ
diff --git a/public/og/strings.jpg b/public/og/strings.jpg
index e7b3197..5b89490 100644
Binary files a/public/og/strings.jpg and b/public/og/strings.jpg differ
diff --git a/public/og/structured-data-shapes.jpg b/public/og/structured-data-shapes.jpg
index 755bf9c..423eb0c 100644
Binary files a/public/og/structured-data-shapes.jpg and b/public/og/structured-data-shapes.jpg differ
diff --git a/public/og/subprocesses.jpg b/public/og/subprocesses.jpg
index 4e1ef05..515ac93 100644
Binary files a/public/og/subprocesses.jpg and b/public/og/subprocesses.jpg differ
diff --git a/public/og/testing.jpg b/public/og/testing.jpg
index 679ed14..ce23c7f 100644
Binary files a/public/og/testing.jpg and b/public/og/testing.jpg differ
diff --git a/public/og/threads-and-processes.jpg b/public/og/threads-and-processes.jpg
index a19cebb..9909dcf 100644
Binary files a/public/og/threads-and-processes.jpg and b/public/og/threads-and-processes.jpg differ
diff --git a/public/og/truth-and-size.jpg b/public/og/truth-and-size.jpg
index 62520ba..83d40d9 100644
Binary files a/public/og/truth-and-size.jpg and b/public/og/truth-and-size.jpg differ
diff --git a/public/og/truthiness.jpg b/public/og/truthiness.jpg
index ecfaf84..41c65c7 100644
Binary files a/public/og/truthiness.jpg and b/public/og/truthiness.jpg differ
diff --git a/public/og/tuples.jpg b/public/og/tuples.jpg
index 483750a..89ccecd 100644
Binary files a/public/og/tuples.jpg and b/public/og/tuples.jpg differ
diff --git a/public/og/type-aliases.jpg b/public/og/type-aliases.jpg
index 763d645..4be89e7 100644
Binary files a/public/og/type-aliases.jpg and b/public/og/type-aliases.jpg differ
diff --git a/public/og/type-hints.jpg b/public/og/type-hints.jpg
index 4fd22de..bb2d51f 100644
Binary files a/public/og/type-hints.jpg and b/public/og/type-hints.jpg differ
diff --git a/public/og/typed-dicts.jpg b/public/og/typed-dicts.jpg
index 0660b3d..d5fff22 100644
Binary files a/public/og/typed-dicts.jpg and b/public/og/typed-dicts.jpg differ
diff --git a/public/og/union-and-optional-types.jpg b/public/og/union-and-optional-types.jpg
index 9a06265..7ae90f0 100644
Binary files a/public/og/union-and-optional-types.jpg and b/public/og/union-and-optional-types.jpg differ
diff --git a/public/og/unpacking.jpg b/public/og/unpacking.jpg
index 0c24943..b2de0fe 100644
Binary files a/public/og/unpacking.jpg and b/public/og/unpacking.jpg differ
diff --git a/public/og/values.jpg b/public/og/values.jpg
index 5fca2c7..5fb608c 100644
Binary files a/public/og/values.jpg and b/public/og/values.jpg differ
diff --git a/public/og/variables.jpg b/public/og/variables.jpg
index a09ccbc..072fdd4 100644
Binary files a/public/og/variables.jpg and b/public/og/variables.jpg differ
diff --git a/public/og/virtual-environments.jpg b/public/og/virtual-environments.jpg
index 1b5e092..bda85fd 100644
Binary files a/public/og/virtual-environments.jpg and b/public/og/virtual-environments.jpg differ
diff --git a/public/og/warnings.jpg b/public/og/warnings.jpg
index 9203da1..96fa087 100644
Binary files a/public/og/warnings.jpg and b/public/og/warnings.jpg differ
diff --git a/public/og/while-loops.jpg b/public/og/while-loops.jpg
index 886a9fe..d49ecb1 100644
Binary files a/public/og/while-loops.jpg and b/public/og/while-loops.jpg differ
diff --git a/public/og/yield-from.jpg b/public/og/yield-from.jpg
index 1a484e8..4042f93 100644
Binary files a/public/og/yield-from.jpg and b/public/og/yield-from.jpg differ
diff --git a/public/prototyping/journey-figures-gestalt.html b/public/prototyping/journey-figures-gestalt.html
index 16a8320..7043602 100644
--- a/public/prototyping/journey-figures-gestalt.html
+++ b/public/prototyping/journey-figures-gestalt.html
@@ -52,7 +52,7 @@
Journey-figures gestalt
Every journey section's figure on one page so the set can be reasoned about as a whole. Score against the rubric; redesign anything below the 8.5 gate before shipping to /journeys/<slug>.
-
Runtime
This journey builds the smallest coherent model of Python at runtime: programs run statements, names refer to objects, objects have types, and operations ask those objects to do work.
Start with executable evidence.
Examples are evidence loops: source, a run step, and visible output stay together.
Separate value, identity, and absence.
Runtime objects answer separate questions: equal value, same identity, or the singleton that marks absence.
Read expressions as object operations.
Expression syntax enters the data model; object methods produce the result.
Control Flow
This journey follows how a Python program chooses which path runs, names facts at decision points, and exits early when the remaining work no longer applies.
Choose between paths.
Facts flow into one decision point; exactly one branch owns the next step.
Name and shape decisions.
Name a fact when a condition needs it; match shape when the data structure is the decision.
Stop as soon as the answer is known.
Early exits draw a boundary: once the answer is found, the tail stays unread.
Iteration
This journey follows repeated work from ordinary loops to the iterator protocol: consume values, stop deliberately, and produce lazy streams only when they help.
Choose the right loop shape.
Choose the loop from its stopping rule: exhaustion, condition, or sentinel marker.
See the protocol behind `for`.
for is surface syntax; iter() creates an iterator and next() pulls values until StopIteration.
Compose lazy value streams.
Lazy pipelines run from the consumer's pull: next() requests one value through each stage.
Shapes
This journey teaches the core Python habit of choosing a data shape, transforming it directly, and making the result visible.
Pick the container that matches the question.
Each container answers a different question: ordered, fixed, lookup, unique.
Move between shapes deliberately.
Most everyday code reshapes data: one input, one transform, one new value.
Cross text and data boundaries.
Programs receive text and produce structured data; parsing makes the boundary explicit.
Interfaces
This journey shows how Python grows from simple functions to callable APIs, object interfaces, protocols, and metaclasses.
Start with functions as named behavior.
A function is the first abstraction boundary: arguments in, body, return value out.
Use functions as values.
Functions are first-class values. A second name binds to the same function object.
Bundle behavior with state.
Classes group fields and methods so data and behavior move together behind one interface.
Types
This journey maps Python's runtime object model to optional static annotations so learners know what types can and cannot promise.
Keep runtime and static analysis separate.
Runtime values run the program; static tools inspect separate annotations and report before execution.
Describe realistic data shapes.
Real data contracts combine fields, variants, and expected absence instead of one scalar type.
Scale annotations for reusable libraries.
Reusable APIs carry caller contracts through the library boundary with generics, parameters, and overloads.
Reliability
This journey follows the boundaries where programs fail, clean up, split into modules, communicate with the outside world, and run concurrent work.
Make failure explicit.
Different failure shapes need explicit signals: assertions, recovery, chained causes, or warnings.
Control resource and module boundaries.
Reliable programs name their boundaries: resources clean up, modules import, environments constrain runtime.
Handle operations that outlive one expression.
Async, threaded, test, and logging work cross an operation boundary before evidence comes back.
+
Runtime
This journey builds the smallest coherent model of Python at runtime: programs run statements, names refer to objects, objects have types, and operations ask those objects to do work.
Start with executable evidence.
Examples are evidence loops: source, a run step, and visible output stay together.
Separate value, identity, and absence.
Runtime objects answer separate questions: equal value, same identity, or the singleton that marks absence.
Read expressions as object operations.
Expression syntax enters the data model; object methods produce the result.
Control Flow
This journey follows how a Python program chooses which path runs, names facts at decision points, and exits early when the remaining work no longer applies.
Choose between paths.
Facts flow into one decision point; exactly one branch owns the next step.
Name and shape decisions.
Name a fact when a condition needs it; match shape when the data structure is the decision.
Stop as soon as the answer is known.
Early exits draw a boundary: once the answer is found, the tail stays unread.
Iteration
This journey follows repeated work from ordinary loops to the iterator protocol: consume values, stop deliberately, and produce lazy streams only when they help.
Choose the right loop shape.
Choose the loop from its stopping rule: exhaustion, condition, or sentinel marker.
See the protocol behind `for`.
for is surface syntax; iter() creates an iterator and next() pulls values until StopIteration.
Compose lazy value streams.
Lazy pipelines run from the consumer's pull: next() requests one value through each stage.
Shapes
This journey teaches the core Python habit of choosing a data shape, transforming it directly, and making the result visible.
Pick the container that matches the question.
Each container answers a different question: ordered, fixed, lookup, unique.
Move between shapes deliberately.
Most everyday code reshapes data: one input, one transform, one new value.
Cross text and data boundaries.
Programs receive text and produce structured data; parsing makes the boundary explicit.
Interfaces
This journey shows how Python grows from simple functions to callable APIs, object interfaces, protocols, and metaclasses.
Start with functions as named behavior.
A function is the first abstraction boundary: arguments in, body, return value out.
Use functions as values.
Functions are first-class values. A second name binds to the same function object.
Bundle behavior with state.
Classes group fields and methods so data and behavior move together behind one interface.
Types
This journey maps Python's runtime object model to optional static annotations so learners know what types can and cannot promise.
Keep runtime and static analysis separate.
Runtime values run the program; static tools inspect separate annotations and report before execution.
Describe realistic data shapes.
Real data contracts combine fields, variants, and expected absence instead of one scalar type.
Scale annotations for reusable libraries.
Reusable APIs carry caller contracts through the library boundary with generics, parameters, and overloads.
Reliability
This journey follows the boundaries where programs fail, clean up, split into modules, communicate with the outside world, and run concurrent work.
Make failure explicit.
Different failure shapes need explicit signals: assertions, recovery, chained causes, or warnings.
Control resource and module boundaries.
Reliable programs name their boundaries: resources clean up, modules import, environments constrain runtime.
Handle operations that outlive one expression.
Async, threaded, test, and logging work cross an operation boundary before evidence comes back.