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.
diff --git a/public/prototyping/layout-banner-pair.html b/public/prototyping/layout-banner-pair.html index db4b647..4efcb2b 100644 --- a/public/prototyping/layout-banner-pair.html +++ b/public/prototyping/layout-banner-pair.html @@ -61,7 +61,7 @@

Mutability

second.append("workers") print(first) print(second)

Output

['python', 'workers']
-['python', 'workers']
Two names share one mutable list — appending through one name changes the object visible through both.
By contrast, a tuple is frozen — its contents cannot change in place, so aliasing carries no mutation hazard.

Immutable objects do not change in place. String methods such as upper() return a new string, leaving the original string unchanged.

Source

text = "python"
+['python', 'workers']
Two names share one mutable list — appending through one name changes the object visible through both.
By contrast, a tuple is frozen — its contents cannot change in place, so aliasing carries no mutation hazard.

Immutable objects do not change in place. String methods such as upper() return a new string, leaving the original string unchanged.

Source

text = "python"
 upper_text = text.upper()
 print(text)
 print(upper_text)

Output

python
diff --git a/public/prototyping/layout-banner-single.html b/public/prototyping/layout-banner-single.html
index f1f1815..991e3d0 100644
--- a/public/prototyping/layout-banner-single.html
+++ b/public/prototyping/layout-banner-single.html
@@ -61,7 +61,7 @@ 

Mutability

second.append("workers") print(first) print(second)

Output

['python', 'workers']
-['python', 'workers']
Two names share one mutable list — appending through one name changes the object visible through both.

Immutable objects do not change in place. String methods such as upper() return a new string, leaving the original string unchanged.

Source

text = "python"
+['python', 'workers']
Two names share one mutable list — appending through one name changes the object visible through both.

Immutable objects do not change in place. String methods such as upper() return a new string, leaving the original string unchanged.

Source

text = "python"
 upper_text = text.upper()
 print(text)
 print(upper_text)

Output

python
diff --git a/public/prototyping/layout-banner-trio.html b/public/prototyping/layout-banner-trio.html
index a0addd0..f232969 100644
--- a/public/prototyping/layout-banner-trio.html
+++ b/public/prototyping/layout-banner-trio.html
@@ -56,7 +56,7 @@
     

Mutability

Some objects change in place, while others return new values.

-
Two names share one mutable list — appending through one name changes the object visible through both.

Mutable objects can change in place. first and second point to the same list, so appending through one name changes the object seen through both names.

Source

first = ["python"]
+  
Two names share one mutable list — appending through one name changes the object visible through both.

Mutable objects can change in place. first and second point to the same list, so appending through one name changes the object seen through both names.

Source

first = ["python"]
 second = first
 second.append("workers")
 print(first)
@@ -65,11 +65,11 @@ 

Mutability

upper_text = text.upper() print(text) print(upper_text)

Output

python
-PYTHON
Mutable: change visible through any alias.
Immutable: aliases share a frozen value.

Some APIs make the boundary explicit. sorted() returns a new list, while methods such as append() and list.sort() mutate an existing list.

Source

numbers = [3, 1, 2]
+PYTHON
Mutable: change visible through any alias.
Immutable: aliases share a frozen value.

Some APIs make the boundary explicit. sorted() returns a new list, while methods such as append() and list.sort() mutate an existing list.

Source

numbers = [3, 1, 2]
 ordered = sorted(numbers)
 print(ordered)
 print(numbers)

Output

[1, 2, 3]
-[3, 1, 2]
When in doubt, reach for the immutable shape.
+[3, 1, 2]
When in doubt, reach for the immutable shape.

Notes

diff --git a/public/prototyping/marginalia-gestalt.html b/public/prototyping/marginalia-gestalt.html index 873544a..e8896e7 100644 --- a/public/prototyping/marginalia-gestalt.html +++ b/public/prototyping/marginalia-gestalt.html @@ -10,6 +10,7 @@ --ink: #521000; --ink-soft: rgba(82, 16, 0, 0.7); --rule: #EBD5C1; + --fig-ink: #521000; --fig-ink-soft: rgba(82, 16, 0, 0.7); --fig-accent: #FF4801; --fig-soft: rgba(82, 16, 0, 0.05); } *, *::before, *::after { box-sizing: border-box; } html, body { background: var(--paper); color: var(--ink); margin: 0; } @@ -67,805 +68,805 @@

Examples

Basics · 01

Hello World

- +

Every Python program starts as source and produces text on standard output. The smallest mental model.

9.0 · program → output, smallest mechanism

Basics · 02

Values

- -

Every literal is an object with a type; the type carries the behaviour, not the variable name.

-

9.0 · every literal is a typed object

+ +

text names a str object, and type(text) is str: the type carries upper() and the rest of the behaviour, not the name.

+

9.0 · 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

Basics · 03

Literals

- +

Each Python type has its own literal spellings; ints accept decimal, hex, and binary; strings accept either quote.

-

9.0 · literal spellings per type

+

8.5 · literal spellings per type; a reference table, kept until a mechanism figure replaces it

Basics · 04

Numbers

- +

Ints have unbounded precision; floats use IEEE doubles whose representable values thin out near the extremes.

9.0 · int unbounded vs float thinning, both registers

Basics · 05

Booleans

- +

`a and b` returns True only when both are True; otherwise it returns the first falsy value.

9.0 · 2×2 truth table

Basics · 06

Operators

- +

An expression like `(2 + 3) * 4` parses as a tree; operator precedence and parentheses determine its shape.

9.0 · expression tree mechanism

Basics · 07

None

- +

`None` is a single object: every name that points at None points at the same object.

9.0 · three names converging on one None

Basics · 08

Variables

- +

A name is a label that points at an object. Assignment binds the label; the object exists independently.

9.5 · the canonical name → object picture

Basics · 09

Constants

- +

UPPER_CASE is a naming convention, not a language constraint; the binding behaves like any other variable.

-

9.0 · name binding; UPPER_CASE is convention

+

9.0 · 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

Basics · 10

Truthiness

- +

bool(x) is True except for a small fixed set: 0, 0.0, "", [], {}, None, False.

9.0 · bool(x) with the falsy set as a strip

Data Model · 11

Equality and Identity

- +

Two names can share one object (`is` and `==` both true) or hold two equal-but-distinct objects (only `==` true).

9.0 · shared vs separate object, side-by-side

Data Model · 12

Mutability

- +

Two names share one mutable list — appending through one name changes the object visible through both.

9.5 · three-state small multiple of aliased mutation

Data Model · 12

Mutability

- +

By contrast, a tuple is frozen — its contents cannot change in place, so aliasing carries no mutation hazard.

9.5 · three-state small multiple of aliased mutation

Basics · 13

Object Lifecycle

- +

Names keep the same object reachable; deleting one name matters only when it removes the last reference.

9.0 · names keep object reachable until last reference disappears

Text · 14

Strings

- +

Strings are sequences of Unicode codepoints. UTF-8 encoding turns them into bytes; `é` takes two bytes, `c` takes one.

9.0 · codepoints + bytes registers

Text · 15

Bytes and Bytearray

- -

bytes is a frozen sequence of integers; bytearray is the mutable counterpart with append/extend/etc.

-

9.0 · frozen vs mutable contrast

+ +

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.

+

9.5 · 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

Text · 16

String Formatting

- -

The format spec is a railroad of named optional fields: alignment, sign, width, precision, type.

-

9.0 · format-spec railroad

+ +

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.

+

9.0 · 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

Control Flow · 17

Conditionals

- +

A predicate sorts a value into one of several branches; if/elif/else is the explicit spelling.

9.0 · predicate forks value to branch

Control Flow · 18

Guard Clauses

- +

Early returns handle the exceptional cases first so the main work is the body of the function, not its tail.

9.0 · early returns, main body at the tail

Control Flow · 19

Assignment Expressions

- +

The walrus binds a name during the surrounding expression; one expression, two outputs.

9.0 · walrus binds while comparing

Control Flow · 20

For Loops

- +

Each call to next() advances the caret one cell along the iterable — the same shape behind range(), strings, and any sequence.

9.0 · 4-row caret advance

Control Flow · 21

Break and Continue

- +

break exits the loop; continue skips to the next iteration. Both interrupt the natural fall-through.

9.0 · early exit at first match

Control Flow · 22

Loop Else

- +

The loop's else branch runs only when the loop falls through naturally; break skips it.

9.0 · fell-through vs broke, two outcomes

Iteration · 23

Iterating over Iterables

- +

`for` desugars to iter()+next(): one iter() call, then next() until StopIteration ends the loop.

9.0 · iter() exposes the iterator

Iteration · 24

Iterators

- +

iter() exposes the iterator behind for; next() pulls one value at a time until exhausted.

9.0 · three-state machine

Iteration · 25

Iterator vs Iterable

- +

An iterable knows how to produce an iterator (via iter()); the iterator knows how to produce values (via next()).

9.0 · the protocol exposed

Iteration · 25

Iterator vs Iterable

- +

The iterator's caret only moves forward — after the first `list(stream)` drains it, nothing is left for the second.

9.0 · the protocol exposed

Iteration · 26

Sentinel Iteration

- -

`iter(callable, sentinel)` calls the callable repeatedly, stopping when it returns the sentinel.

-

9.0 · iter(callable, sentinel) stop condition

+ +

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.

+

9.0 · 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

Control Flow · 27

Match Statements

- +

match dispatches by pattern shape; the value flows down the patterns and the first match wins.

9.0 · dispatch ladder; first match wins

Control Flow · 28

Advanced Match Patterns

- +

Capture, alternative, guard, and class patterns each name a different way a value can match a case.

9.0 · four pattern variants

Control Flow · 29

While Loops

- +

`while` repeats the body while the test stays true; the back-edge returns control to the test before each pass.

9.0 · back-edge mechanism plus the three stopping rules

Control Flow · 29

While Loops

- +

Each loop shape has its own stopping rule: `for` ends when the iterable is exhausted, `while` when the condition turns false, sentinel iteration when the marker value appears.

9.0 · back-edge mechanism plus the three stopping rules

Collections · 30

Lists

- +

Lists are mutable sequences. `.append` extends the same list object — no new list is created.

9.0 · cells with append mechanism

Collections · 31

Tuples

- +

Tuples are ordered, immutable sequences; positions matter, contents do not change once constructed.

9.0 · frozen sequence with struck-through .append

Collections · 31

Tuples

- +

A list is the other intent: a variable number of similar items, growing in place with `.append`.

9.0 · frozen sequence with struck-through .append

Collections · 32

Unpacking

- +

Left-side names bind to right-side positions; `*rest` gathers the middle into a list.

9.0 · binding-line mechanism with *rest

Collections · 33

Dictionaries

- -

Each key is hashed to a bucket; collisions chain into the next slot. Lookup is constant-time on average.

-

9.0 · hash buckets with collision chain

+ +

hash(key) picks the slot: "year" lands in an empty one, and profile["name"] goes straight to its slot without scanning the other entries.

+

9.0 · 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

Collections · 34

Sets

- +

Sets are hash buckets without values; `x in s` averages O(1) regardless of size.

9.0 · hash buckets without values

Collections · 35

Slices

- +

Slice indices sit between cells; [:3] and [3:] partition the sequence at index 3, never overlapping or losing an item.

9.0 · ruler with bracket overlay

Collections · 36

Comprehensions

- +

A comprehension is a compact spelling of the equivalent for-loop with append, made into one expression.

9.0 · comprehension over equivalent for-loop

Collections · 37

Comprehension Patterns

- +

Nested clauses compose left to right; the comprehension is still equivalent to a for-loop with append.

9.0 · nested clauses compose

Collections · 38

Sorting

- +

Python's sort is stable: items with equal keys keep their original order, so chained sorts compose predictably.

9.0 · stability ribbons preserved across keys

Collections · 39

Collections Module

- +

Four specialised containers for shapes the built-in types don't cover well: deque, Counter, defaultdict, namedtuple.

-

9.0 · deque / Counter / defaultdict / namedtuple

+

8.5 · deque / Counter / defaultdict / namedtuple; a catalogue, kept until the page is split

Collections · 40

Copying Collections

- +

Without copy() two names share the same object; mutating through one is visible through the other.

9.5 · same picture as mutability, perfect match

Functions · 41

Functions

- +

A function takes inputs, evaluates a body, and returns a value: `greet('Ada')` produces `'Hello, Ada'`.

9.0 · specific call: greet('Ada') → 'Hello, Ada'

Functions · 42

Keyword-only Arguments

- +

A bare `*` divides positional or keyword arguments from keyword-only ones; callers must pass `c` and `d` by name.

9.0 · signature with explicit `*` separator

Functions · 43

Positional-only Parameters

- +

A bare `/` divides positional-only arguments from positional-or-keyword ones; callers cannot name `a` or `b`.

9.0 · signature with explicit `/` separator

Functions · 43

Positional-only Parameters

- +

In the same signature, the bare `*` works the other way: parameters after it, such as `clamp`, must be named at the call site.

9.0 · signature with explicit `/` separator

Functions · 44

Args and Kwargs

- +

*args captures the extra positionals as a tuple; **kwargs captures the extra keywords as a dict.

9.0 · *args tuple, **kwargs dict regions

Functions · 45

Multiple Return Values

- +

A function returning multiple values really returns one tuple; the caller unpacks it into named bindings.

9.0 · function returns tuple; caller unpacks

Functions · 46

Closures

- -

The inner function keeps a reference into the outer scope's cell, so the captured factor survives the outer return.

-

9.0 · captured cell reference

+ +

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.

+

9.5 · 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

Functions · 47

Partial Functions

- +

`functools.partial(f, 1)` pre-fills `a=1`, returning a thinner callable `g(b, c)` that only needs the rest.

9.0 · f → partial(f, 1) → g

Functions · 48

Global and Nonlocal

- +

Name lookup walks LEGB — local, enclosing, global, built-in — outward, returning the first binding it finds.

9.0 · LEGB nested rings

Functions · 49

Recursion

- -

Each call pushes a new frame with the same name and a smaller argument; the base case unwinds back up the stack.

-

9.0 · stacked frames with same name, different argument

+ +

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.

+

9.0 · 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

Functions · 50

Lambdas

- +

A lambda is a function literal: parameters before the colon, a single expression after, no statement body.

9.0 · function literal: params / expression

Iteration · 51

Generators

- -

A generator's body is a timeline cut by yield gates: each next() advances to the next gate; locals survive the pause.

-

9.0 · ribbon cut by yield gates

+ +

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.

+

9.5 · 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

Iteration · 52

Yield From

- +

`yield from inner` delegates iteration to an inner generator; its yields surface here unchanged.

9.0 · stitched ribbons; delegation

Iteration · 53

Generator Expressions

- +

A generator expression composes filter and map lazily; values flow only when next() pulls them.

9.0 · lazy filter→map pipeline

Iteration · 54

Itertools

- +

chain stitches two iterables into one stream without materialising either: values arrive lazily.

9.0 · chain joins two iterables into one stream

Functions · 55

Decorators

- +

@dec rebinds the name to wrapper(f₀); the original function survives only in the wrapper's closure cell.

9.0 · before/after rebinding through cell

Classes · 56

Classes

- +

Every Python value sits on the instance → class → type triangle; the metaclass is the type of the class.

9.0 · instance/class/type triangle

Classes · 57

Inheritance and Super

- +

Multiple inheritance forms a graph; C3 linearisation flattens it into the MRO Python uses for attribute lookup.

9.0 · MRO chain with diamond ghost

Classes · 58

Classmethods and Staticmethods

- +

Three method kinds, three first-argument conventions: classmethod gets the class, staticmethod gets nothing, instance gets self.

9.0 · three method kinds, three first-arg conventions

Classes · 59

Dataclasses

- +

Field declarations become the generated __init__ signature: declaration is the constructor.

9.0 · fields → generated __init__ signature

Classes · 60

Properties

- +

When x is a property, attribute access routes through fget/fset instead of touching __dict__.

9.0 · obj.x routes through fget instead of __dict__

Data Model · 61

Special Methods

- +

Operators are method calls. `a + b` dispatches to `a.__add__(b)`; the data model exposes the syntax.

9.0 · syntax → method dispatch

Data Model · 62

Truth and Size

- +

bool(x) calls __bool__ first; if absent, __len__() != 0; if neither, defaults to True.

9.0 · __bool__ → __len__ → True fallback chain

Data Model · 63

Container Protocols

- +

Container syntax routes to narrow protocol methods: assignment to __setitem__, membership to __contains__, lookup to __getitem__.

9.0 · syntax routes to __setitem__, __contains__, __getitem__

Data Model · 64

Callable Objects

- +

Defining __call__ makes any object callable; functions are just one shape that satisfies this protocol.

9.0 · __call__ makes any object callable

Data Model · 65

Operator Overloading

- +

Defining `__add__` on a class lets `+` dispatch into the class's own behavior.

9.0 · dispatch arrow

Data Model · 66

Attribute Access

- +

obj.x checks instance __dict__, then class __dict__, then __getattr__; the first hit wins.

9.0 · instance __dict__ → class __dict__ → __getattr__

Data Model · 67

Bound and Unbound Methods

- +

Accessing a method via an instance binds self; accessing it via the class returns the underlying function.

9.0 · instance.method bound vs Class.method unbound

Data Model · 68

Descriptors

- +

Attribute access on an instance routes through the descriptor's __get__/__set__/__delete__ when the attribute is a descriptor.

9.0 · get/set/delete protocol routed through descriptor

Classes · 69

Metaclasses

- +

A metaclass is the type of a class, just as a class is the type of its instances; type is the default metaclass.

9.0 · extended triangle to metaclass

Classes · 69

Metaclasses

- +

The class makes instances — the same triangle one level down, with type as the default apex.

9.0 · extended triangle to metaclass

Data Model · 70

Context Managers

- +

A context manager pairs setup with reliable cleanup; the raise path still routes through __exit__.

9.0 · enter / body / exit bowtie

Data Model · 71

Delete Statements

- +

`del x` removes the name; the object survives if any other reference holds it, otherwise gets collected.

9.0 · name erased; object survives if referenced

Errors · 72

Exceptions

- +

try, except, else, and finally as parallel lanes; a single coral path traces what actually runs.

9.0 · try/except/else/finally lanes with traced path

Errors · 73

Assertions

- +

assert tests a condition; True passes silently, False raises AssertionError with the optional message.

9.0 · True passes, False raises

Errors · 74

Exception Chaining

- +

`raise X from Y` sets `__cause__` (explicit); raising during except sets `__context__` (implicit).

9.0 · __cause__ vs __context__ distinguished

Errors · 75

Exception Groups

- +

except* peels matched leaves out of an ExceptionGroup; survivors regroup and propagate.

9.0 · except* peels matching leaves

Errors · 76

Warnings

- +

A warning is a soft signal: the message is reported, but execution continues unless filters elevate it.

9.0 · soft signal; execution continues

Modules · 77

Modules

- +

An import walks sys.path entry by entry; the first directory containing the module wins.

9.0 · sys.path resolution; first hit wins

Modules · 78

Import Aliases

- +

`import x as y` binds the name y to the same module object x would have.

9.0 · two names bind to the same module

Modules · 79

Packages

- +

A directory with __init__.py becomes an importable package; submodules and subpackages nest beneath it.

9.0 · __init__.py + nested submodules

Modules · 80

Virtual Environments

- +

A venv carries its own interpreter and site-packages, isolating a project's dependencies from the system.

9.0 · project / venv boundary

Types · 81

Type Hints

- +

Annotations describe expected types for tools; the runtime accepts any object regardless.

9.0 · ghost annotations over runtime values

Types · 82

Runtime Type Checks

- +

isinstance and issubclass ask the runtime; the answer is a bool, not a static type refinement.

9.0 · isinstance returns bool

Types · 83

Union and Optional Types

- +

`int | str | None` says one slot may hold any of three shapes — including expected absence.

9.0 · type fork to several shapes

Types · 84

Type Aliases

- +

A type alias names a complex annotation once so call sites read as the domain meaning, not the type composition.

9.0 · complex annotation collapses to a name

Types · 85

TypedDict

- +

TypedDict gives each key a typed value, so `obj['x']` is checked against the declared shape.

9.0 · keys with declared value types

Classes · 86

Structured Data Shapes

- +

The same record can be a mutable dataclass, an immutable NamedTuple, or a runtime dict described by TypedDict.

9.0 · dataclass vs NamedTuple vs TypedDict runtime trade-offs

Types · 87

Literal and Final

- +

Literal narrows a slot to a fixed set of constant values; Final says the binding will not change.

9.0 · slot narrows to a fixed set

Types · 88

Callable Types

- +

A Callable annotation describes the callback slot: this argument list must produce this return type.

9.0 · callback slot shows argument list and return type

Types · 89

Generics and TypeVar

- +

A generic preserves the input type through the call: the same T flows in and out of fn[T].

9.0 · the same T flows in and out

Types · 90

ParamSpec

- +

ParamSpec preserves the wrapped function's signature through a decorator, parameter for parameter.

9.0 · P preserved through decorator

Types · 91

Overloads

- +

@overload declares static call signatures for double(int) and double(str); one runtime implementation handles both.

9.0 · static overload signatures contrasted with one runtime body

Types · 92

Casts and Any

- +

cast(T, x) tells the type checker to treat x as T; the runtime is unaffected.

9.0 · Any → cast(T, x) → T, runtime unchanged

Types · 93

NewType

- +

NewType creates a distinct static identity backed by the same runtime type — UserId is int with a name.

9.0 · same runtime, distinct static identity

Types · 94

Protocols

- +

An object satisfies a protocol structurally — by having the required methods — not by inheriting it.

9.0 · structural duck check

Classes · 95

Abstract Base Classes

- +

An ABC sits on the same triangle as concrete classes; subclasses inherit the abstract methods they must implement.

9.0 · same triangle as concrete classes

Types · 96

Enums

- +

An enum names a fixed set of symbolic values; no new members appear at runtime.

9.0 · closed set of symbolic values

Text · 97

Regular Expressions

- -

^ and $ anchor the pattern; quantifiers like {2} bound how many times a token repeats.

-

9.0 · pattern ruler with anchors

+ +

findall returns only what the parentheses capture: `([A-Za-z]+)` takes the name and `(\d+)` the score, one (name, score) pair per record.

+

9.0 · 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

Standard Library · 98

Number Parsing

- +

int() turns text into a typed number; malformed input raises ValueError instead of guessing.

9.0 · int() success path vs ValueError

Errors · 99

Custom Exceptions

- +

Subclassing an existing exception gains a domain name without changing semantics.

9.0 · subclass chain to a domain name

Standard Library · 100

JSON

- +

Six type pairs bridge the JSON text boundary; each json value maps to one Python type.

9.0 · two-column type mapping

Standard Library · 101

Logging

- -

Five severity levels; the logger's configured threshold drops everything below it.

-

9.0 · five thresholded levels

+ +

The handler's level is a gate: debug is below INFO and never reaches stdout, while info and warning pass through unchanged.

+

9.5 · 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

Standard Library · 102

Testing

- +

arrange-act-assert: set up the state, perform the behavior under test, compare the result to expectations.

9.0 · arrange-act-assert three-row pattern

Standard Library · 103

Subprocesses

- +

subprocess.run spawns a child process and captures its stdout, stderr, and exit code as portable evidence.

9.0 · spawn → child → captured output

Standard Library · 104

Threads and Processes

- +

Threads share memory but the GIL serialises Python bytecode; processes run in parallel with isolated memory.

9.0 · thread GIL sharing contrasted with process isolation

Standard Library · 105

Networking

- +

Text crosses the socket as bytes — `encode` marks the python → wire boundary, `decode` brings the bytes back to a Python `str`.

9.0 · text ↔ bytes across the socket boundary

Standard Library · 106

Dates and Times

- +

An aware datetime carries a UTC offset; one instant in time reads differently on two clocks.

9.0 · one instant, two clock offsets

Standard Library · 107

CSV Data

- +

CSV files are rows of records; each line has the same columns in the same order.

9.0 · rows × columns; same shape per line

Async · 108

Async Await

- +

On await, the coroutine yields to the loop; the loop runs other work and resumes when the awaitable is ready.

9.0 · loop/coro swimlane with await handoffs

Async · 109

Async Iteration and Context

- +

async iteration and async with both rest on the same loop-vs-coroutine handoff as await.

9.0 · loop/coro lanes with await yields

diff --git a/public/prototyping/production-figures-gestalt.html b/public/prototyping/production-figures-gestalt.html index 01ba998..0badca8 100644 --- a/public/prototyping/production-figures-gestalt.html +++ b/public/prototyping/production-figures-gestalt.html @@ -44,15 +44,15 @@ -
Prototype · All 124 figures currently registered in src/marginalia.py FIGURES; each card names where it renders. · all prototypes
+
Prototype · All 125 figures currently registered in src/marginalia.py FIGURES; each card names where it renders. · all prototypes
-

Production figure registry · 124 figures

+

Production figure registry · 125 figures

Production figures gestalt

Every figure currently registered in src/marginalia.py FIGURES. Each card names the figure, where it renders today (an example attachment, a journey section, or "not yet attached"), and the intrinsic viewBox dimensions. Use this page beside the example-figure rubric to triage which figures are shipping, which are journey-only, and which are sitting in the registry waiting for an example attachment.

-

aliasing-mutation

attached to /examples/mutability, copying-collections · viewBox 220×175

v2 scores: mutability 9.5 · copying-collections 9.5

tuple-no-mutation

attached to /examples/mutability · viewBox 220×185

v2 scores: mutability 9.5

iterator-unroll

attached to /examples/for-loops, iterator-vs-iterable · viewBox 220×130

v2 scores: for-loops 9.0 · iterator-vs-iterable 9.0

scope-rings

attached to /examples/scope-global-nonlocal · viewBox 216×116

v2 scores: scope-global-nonlocal 9.0

closure-cell

attached to /examples/closures · viewBox 240×120

v2 scores: closures 9.0

slice-ruler

attached to /examples/slices · viewBox 232×120

v2 scores: slices 9.0

branch-fork

attached to /examples/conditionals · viewBox 232×100

v2 scores: conditionals 9.0

loop-repetition

attached to /examples/while-loops · viewBox 260×100

v2 scores: while-loops 9.0

while-backedge

attached to /examples/while-loops · viewBox 252×80

v2 scores: while-loops 9.0

iter-protocol

attached to /examples/iterators, iterator-vs-iterable, iterating-over-iterables · viewBox 304×70

v2 scores: iterators 9.0 · iterator-vs-iterable 9.0 · iterating-over-iterables 9.0

program-output

attached to /examples/hello-world · viewBox 240×80

v2 scores: hello-world 9.0

identity-and-equality

attached to /examples/equality-and-identity · viewBox 304×96

v2 scores: equality-and-identity 9.0

operator-dispatch

attached to /examples/special-methods, operator-overloading · viewBox 260×70

v2 scores: special-methods 9.0 · operator-overloading 9.0

runtime-evidence-loop

attached to a journey section · viewBox 306×96

runtime-object-axes

attached to a journey section · viewBox 214×104

runtime-expression-model

attached to a journey section · viewBox 344×96

container-questions

attached to a journey section · viewBox 280×88

reshape-pipeline

attached to a journey section · viewBox 204×80

text-data-boundary

attached to a journey section · viewBox 172×70

function-signature

attached to a journey section · viewBox 310×82

function-as-value

attached to a journey section · viewBox 200×66

class-with-state

attached to a journey section · viewBox 152×108

annotation-ghost

attached to /examples/type-hints · viewBox 220×52

v2 scores: type-hints 9.0

union-types

attached to /examples/union-and-optional-types · viewBox 166×80

v2 scores: union-and-optional-types 9.0

generic-preservation

attached to /examples/generics-and-typevar · viewBox 250×70

v2 scores: generics-and-typevar 9.0

type-runtime-static-split

attached to a journey section · viewBox 224×126

type-shape-catalog

attached to a journey section · viewBox 290×108

type-library-contract

attached to a journey section · viewBox 364×104

exception-lanes

attached to /examples/exceptions · viewBox 320×100

v2 scores: exceptions 9.0

context-bowtie

attached to /examples/context-managers · viewBox 244×76

v2 scores: context-managers 9.0

async-swimlane

attached to /examples/async-await, async-iteration-and-context · viewBox 280×84

v2 scores: async-await 9.0 · async-iteration-and-context 9.0

reliability-signal-map

attached to a journey section · viewBox 270×128

reliability-boundary-map

attached to a journey section · viewBox 286×110

reliability-operation-boundary

attached to a journey section · viewBox 354×104

naming-decisions

attached to /examples/assignment-expressions · viewBox 310×98

v2 scores: assignment-expressions 9.0

early-exit

attached to /examples/break-and-continue · viewBox 144×116

v2 scores: break-and-continue 9.0

lazy-stream

attached to /examples/generator-expressions · viewBox 300×56

v2 scores: generator-expressions 9.0

control-decision-map

attached to a journey section · viewBox 244×104

control-fact-shape

attached to a journey section · viewBox 286×104

control-stop-boundary

attached to a journey section · viewBox 210×104

iteration-loop-selector

attached to a journey section · viewBox 276×104

iteration-protocol-map

attached to a journey section · viewBox 306×132

iteration-lazy-pull

attached to a journey section · viewBox 360×112

variables-bind

attached to /examples/variables, constants · viewBox 180×44

v2 scores: variables 9.5 · constants 9.0

call-stack

attached to /examples/recursion · viewBox 200×100

v2 scores: recursion 9.0

decorator-rebind

attached to /examples/decorators · viewBox 232×110

v2 scores: decorators 9.0

mro-chain

attached to /examples/inheritance-and-super · viewBox 200×152

v2 scores: inheritance-and-super 9.0

dataclass-fields

attached to /examples/dataclasses · viewBox 312×76

v2 scores: dataclasses 9.0

class-triangle

attached to /examples/classes, abstract-base-classes, metaclasses · viewBox 274×60

v2 scores: classes 9.0 · abstract-base-classes 9.0 · metaclasses 9.0

exception-cause-context

attached to /examples/exception-chaining · viewBox 282×70

v2 scores: exception-chaining 9.0

unpacking-bind

attached to /examples/unpacking · viewBox 152×80

v2 scores: unpacking 9.0

comprehension-equivalence

attached to /examples/comprehensions, comprehension-patterns · viewBox 280×76

v2 scores: comprehensions 9.0 · comprehension-patterns 9.0

list-append

attached to /examples/lists, tuples · viewBox 220×36

v2 scores: lists 9.0 · tuples 9.0

dict-buckets

attached to /examples/dicts · viewBox 270×88

v2 scores: dicts 9.0

number-lines

attached to /examples/numbers · viewBox 260×78

v2 scores: numbers 9.0

expression-tree

attached to /examples/operators · viewBox 220×92

v2 scores: operators 9.0

none-singleton

attached to /examples/none · viewBox 240×84

v2 scores: none 9.0

codepoints-bytes

attached to /examples/strings · viewBox 200×84

v2 scores: strings 9.0

sort-stability

attached to /examples/sorting · viewBox 270×100

v2 scores: sorting 9.0

kw-only-separator

attached to /examples/keyword-only-arguments, positional-only-parameters · viewBox 200×56

v2 scores: keyword-only-arguments 9.0 · positional-only-parameters 9.0

positional-only-separator

attached to /examples/positional-only-parameters · viewBox 200×56

v2 scores: positional-only-parameters 9.0

generator-ribbon

attached to /examples/generators · viewBox 260×50

v2 scores: generators 9.0

truth-and-size

attached to /examples/truth-and-size · viewBox 232×70

v2 scores: truth-and-size 9.0

descriptor-protocol

attached to /examples/descriptors · viewBox 222×76

v2 scores: descriptors 9.0

bound-unbound

attached to /examples/bound-and-unbound-methods · viewBox 296×56

v2 scores: bound-and-unbound-methods 9.0

method-kinds

attached to /examples/classmethods-and-staticmethods · viewBox 272×70

v2 scores: classmethods-and-staticmethods 9.0

callable-objects

attached to /examples/callable-objects · viewBox 220×44

v2 scores: callable-objects 9.0

attribute-lookup

attached to /examples/attribute-access · viewBox 242×70

v2 scores: attribute-access 9.0

guard-clauses

attached to /examples/guard-clauses · viewBox 264×104

v2 scores: guard-clauses 9.0

bytes-vs-bytearray

attached to /examples/bytes-and-bytearray · viewBox 308×86

v2 scores: bytes-and-bytearray 9.0

sentinel-iteration

attached to /examples/sentinel-iteration · viewBox 320×92

v2 scores: sentinel-iteration 9.0

partial-functions

attached to /examples/partial-functions · viewBox 334×36

v2 scores: partial-functions 9.0

args-kwargs

attached to /examples/args-and-kwargs · viewBox 280×68

v2 scores: args-and-kwargs 9.0

multiple-return

attached to /examples/multiple-return-values · viewBox 180×110

v2 scores: multiple-return-values 9.0

lambda-expression

attached to /examples/lambdas · viewBox 170×76

v2 scores: lambdas 9.0

property-fork

attached to /examples/properties · viewBox 232×72

v2 scores: properties 9.0

metaclass-triangle

attached to /examples/metaclasses · viewBox 300×60

v2 scores: metaclasses 9.0

sys-path-resolution

attached to /examples/modules · viewBox 258×100

v2 scores: modules 9.0

import-alias

attached to /examples/import-aliases · viewBox 212×56

v2 scores: import-aliases 9.0

protocol-check

attached to /examples/protocols · viewBox 220×78

v2 scores: protocols 9.0

enum-members

attached to /examples/enums · viewBox 280×60

v2 scores: enums 9.0

datetime-instant

attached to /examples/datetime · viewBox 280×88

v2 scores: datetime 9.0

json-python-mapping

attached to /examples/json · viewBox 220×116

v2 scores: json 9.0

regex-anchors

attached to /examples/regular-expressions · viewBox 200×92

v2 scores: regular-expressions 9.0

number-parse

attached to /examples/number-parsing · viewBox 204×64

v2 scores: number-parsing 9.0

format-spec

attached to /examples/string-formatting · viewBox 220×64

v2 scores: string-formatting 9.0

truthy-check

attached to /examples/truthiness · viewBox 240×70

v2 scores: truthiness 9.0

boolean-truth-table

attached to /examples/booleans · viewBox 132×64

v2 scores: booleans 9.0

set-buckets

attached to /examples/sets · viewBox 156×90

v2 scores: sets 9.0

tuple-frozen

attached to /examples/tuples · viewBox 280×48

v2 scores: tuples 9.0

value-types

attached to /examples/values · viewBox 160×116

v2 scores: values 9.0

yield-delegation

attached to /examples/yield-from · viewBox 240×84

v2 scores: yield-from 9.0

itertools-chain

attached to /examples/itertools · viewBox 246×82

v2 scores: itertools 9.0

assertion-check

attached to /examples/assertions · viewBox 304×76

v2 scores: assertions 9.0

custom-exception-chain

attached to /examples/custom-exceptions · viewBox 220×90

v2 scores: custom-exceptions 9.0

exception-group-peel

attached to /examples/exception-groups · viewBox 240×50

v2 scores: exception-groups 9.0

delete-name-erased

attached to /examples/delete-statements · viewBox 200×84

v2 scores: delete-statements 9.0

package-tree

attached to /examples/packages · viewBox 240×76

v2 scores: packages 9.0

venv-boundary

attached to /examples/virtual-environments · viewBox 274×76

v2 scores: virtual-environments 9.0

subprocess-spawn

attached to /examples/subprocesses · viewBox 324×60

v2 scores: subprocesses 9.0

logging-levels

attached to /examples/logging · viewBox 164×124

v2 scores: logging 9.0

aaa-pattern

attached to /examples/testing · viewBox 250×80

v2 scores: testing 9.0

socket-byte-boundary

attached to /examples/networking · viewBox 364×46

v2 scores: networking 9.0

gil-lanes

attached to /examples/threads-and-processes · viewBox 300×138

v2 scores: threads-and-processes 9.0

cast-escape

attached to /examples/casts-and-any · viewBox 184×56

v2 scores: casts-and-any 9.0

newtype-phantom

attached to /examples/newtype · viewBox 96×92

v2 scores: newtype 9.0

overload-signatures

attached to /examples/overloads · viewBox 304×64

v2 scores: overloads 9.0

paramspec-preserve

attached to /examples/paramspec · viewBox 294×60

v2 scores: paramspec 9.0

literal-constrained

attached to /examples/literal-and-final · viewBox 144×76

v2 scores: literal-and-final 9.0

callable-type

attached to /examples/callable-types · viewBox 352×42

v2 scores: callable-types 9.0

isinstance-check

attached to /examples/runtime-type-checks · viewBox 232×76

v2 scores: runtime-type-checks 9.0

collections-containers

attached to /examples/collections-module · viewBox 284×92

v2 scores: collections-module 9.0

container-methods

attached to /examples/container-protocols · viewBox 272×82

v2 scores: container-protocols 9.0

typed-dict-shape

attached to /examples/typed-dicts · viewBox 200×92

v2 scores: typed-dicts 9.0

structured-shapes

attached to /examples/structured-data-shapes · viewBox 266×82

v2 scores: structured-data-shapes 9.0

csv-records

attached to /examples/csv-data · viewBox 212×96

v2 scores: csv-data 9.0

warning-signal

attached to /examples/warnings · viewBox 292×80

v2 scores: warnings 9.0

object-lifecycle

attached to /examples/object-lifecycle · viewBox 366×80

v2 scores: object-lifecycle 9.0

type-alias-name

attached to /examples/type-aliases · viewBox 240×104

v2 scores: type-aliases 9.0

match-dispatch-ladder

attached to /examples/match-statements · viewBox 260×130

v2 scores: match-statements 9.0

match-pattern-variants

attached to /examples/advanced-match-patterns · viewBox 272×96

v2 scores: advanced-match-patterns 9.0

loop-else-gate

attached to /examples/loop-else · viewBox 312×76

v2 scores: loop-else 9.0

literal-forms

attached to /examples/literals · viewBox 252×132

v2 scores: literals 9.0

function-with-body

attached to /examples/functions · viewBox 334×68

v2 scores: functions 9.0

+

value-type-lookup

attached to /examples/values · viewBox 280×80

v2 scores: values 9.0

logging-threshold

attached to /examples/logging · viewBox 266×130

v2 scores: logging 9.5

literal-forms

attached to /examples/literals · viewBox 252×132

v2 scores: literals 8.5

collections-containers

attached to /examples/collections-module · viewBox 284×92

v2 scores: collections-module 8.5

aliasing-mutation

attached to /examples/mutability, copying-collections · viewBox 220×175

v2 scores: mutability 9.5 · copying-collections 9.5

tuple-no-mutation

attached to /examples/mutability · viewBox 220×185

v2 scores: mutability 9.5

iterator-unroll

attached to /examples/for-loops, iterator-vs-iterable · viewBox 220×130

v2 scores: for-loops 9.0 · iterator-vs-iterable 9.0

scope-rings

attached to /examples/scope-global-nonlocal · viewBox 216×116

v2 scores: scope-global-nonlocal 9.0

closure-cell

attached to /examples/closures · viewBox 280×116

v2 scores: closures 9.5

slice-ruler

attached to /examples/slices · viewBox 232×120

v2 scores: slices 9.0

branch-fork

attached to /examples/conditionals · viewBox 232×100

v2 scores: conditionals 9.0

loop-repetition

attached to /examples/while-loops · viewBox 260×100

v2 scores: while-loops 9.0

while-backedge

attached to /examples/while-loops · viewBox 252×80

v2 scores: while-loops 9.0

iter-protocol

attached to /examples/iterators, iterator-vs-iterable, iterating-over-iterables · viewBox 304×70

v2 scores: iterators 9.0 · iterator-vs-iterable 9.0 · iterating-over-iterables 9.0

program-output

attached to /examples/hello-world · viewBox 240×80

v2 scores: hello-world 9.0

identity-and-equality

attached to /examples/equality-and-identity · viewBox 304×96

v2 scores: equality-and-identity 9.0

operator-dispatch

attached to /examples/special-methods, operator-overloading · viewBox 260×70

v2 scores: special-methods 9.0 · operator-overloading 9.0

runtime-evidence-loop

attached to a journey section · viewBox 306×96

runtime-object-axes

attached to a journey section · viewBox 214×104

runtime-expression-model

attached to a journey section · viewBox 344×96

container-questions

attached to a journey section · viewBox 280×88

reshape-pipeline

attached to a journey section · viewBox 204×80

text-data-boundary

attached to a journey section · viewBox 172×70

function-signature

attached to a journey section · viewBox 310×82

function-as-value

attached to a journey section · viewBox 200×66

class-with-state

attached to a journey section · viewBox 152×108

annotation-ghost

attached to /examples/type-hints · viewBox 220×52

v2 scores: type-hints 9.0

union-types

attached to /examples/union-and-optional-types · viewBox 166×80

v2 scores: union-and-optional-types 9.0

generic-preservation

attached to /examples/generics-and-typevar · viewBox 250×70

v2 scores: generics-and-typevar 9.0

type-runtime-static-split

attached to a journey section · viewBox 224×126

type-shape-catalog

attached to a journey section · viewBox 290×108

type-library-contract

attached to a journey section · viewBox 364×104

exception-lanes

attached to /examples/exceptions · viewBox 320×100

v2 scores: exceptions 9.0

context-bowtie

attached to /examples/context-managers · viewBox 244×76

v2 scores: context-managers 9.0

async-swimlane

attached to /examples/async-await, async-iteration-and-context · viewBox 280×84

v2 scores: async-await 9.0 · async-iteration-and-context 9.0

reliability-signal-map

attached to a journey section · viewBox 270×128

reliability-boundary-map

attached to a journey section · viewBox 286×110

reliability-operation-boundary

attached to a journey section · viewBox 354×104

naming-decisions

attached to /examples/assignment-expressions · viewBox 330×98

v2 scores: assignment-expressions 9.0

early-exit

attached to /examples/break-and-continue · viewBox 144×116

v2 scores: break-and-continue 9.0

lazy-stream

attached to /examples/generator-expressions · viewBox 300×56

v2 scores: generator-expressions 9.0

control-decision-map

attached to a journey section · viewBox 244×104

control-fact-shape

attached to a journey section · viewBox 286×104

control-stop-boundary

attached to a journey section · viewBox 210×104

iteration-loop-selector

attached to a journey section · viewBox 276×104

iteration-protocol-map

attached to a journey section · viewBox 306×132

iteration-lazy-pull

attached to a journey section · viewBox 360×112

variables-bind

attached to /examples/variables · viewBox 170×44

v2 scores: variables 9.5

constants-bind

attached to /examples/constants · viewBox 194×44

v2 scores: constants 9.0

call-stack

attached to /examples/recursion · viewBox 292×80

v2 scores: recursion 9.0

decorator-rebind

attached to /examples/decorators · viewBox 232×110

v2 scores: decorators 9.0

mro-chain

attached to /examples/inheritance-and-super · viewBox 200×152

v2 scores: inheritance-and-super 9.0

dataclass-fields

attached to /examples/dataclasses · viewBox 312×76

v2 scores: dataclasses 9.0

class-triangle

attached to /examples/classes, abstract-base-classes, metaclasses · viewBox 274×60

v2 scores: classes 9.0 · abstract-base-classes 9.0 · metaclasses 9.0

exception-cause-context

attached to /examples/exception-chaining · viewBox 282×70

v2 scores: exception-chaining 9.0

unpacking-bind

attached to /examples/unpacking · viewBox 152×80

v2 scores: unpacking 9.0

comprehension-equivalence

attached to /examples/comprehensions, comprehension-patterns · viewBox 280×76

v2 scores: comprehensions 9.0 · comprehension-patterns 9.0

list-append

attached to /examples/lists, tuples · viewBox 220×36

v2 scores: lists 9.0 · tuples 9.0

dict-buckets

attached to /examples/dicts · viewBox 288×92

v2 scores: dicts 9.0

number-lines

attached to /examples/numbers · viewBox 260×78

v2 scores: numbers 9.0

expression-tree

attached to /examples/operators · viewBox 220×92

v2 scores: operators 9.0

none-singleton

attached to /examples/none · viewBox 240×84

v2 scores: none 9.0

codepoints-bytes

attached to /examples/strings · viewBox 200×84

v2 scores: strings 9.0

sort-stability

attached to /examples/sorting · viewBox 270×100

v2 scores: sorting 9.0

kw-only-separator

attached to /examples/keyword-only-arguments, positional-only-parameters · viewBox 200×56

v2 scores: keyword-only-arguments 9.0 · positional-only-parameters 9.0

positional-only-separator

attached to /examples/positional-only-parameters · viewBox 200×56

v2 scores: positional-only-parameters 9.0

generator-resume

attached to /examples/generators · viewBox 318×72

v2 scores: generators 9.5

truth-and-size

attached to /examples/truth-and-size · viewBox 232×70

v2 scores: truth-and-size 9.0

descriptor-protocol

attached to /examples/descriptors · viewBox 222×76

v2 scores: descriptors 9.0

bound-unbound

attached to /examples/bound-and-unbound-methods · viewBox 296×56

v2 scores: bound-and-unbound-methods 9.0

method-kinds

attached to /examples/classmethods-and-staticmethods · viewBox 272×70

v2 scores: classmethods-and-staticmethods 9.0

callable-objects

attached to /examples/callable-objects · viewBox 220×44

v2 scores: callable-objects 9.0

attribute-lookup

attached to /examples/attribute-access · viewBox 242×70

v2 scores: attribute-access 9.0

guard-clauses

attached to /examples/guard-clauses · viewBox 264×104

v2 scores: guard-clauses 9.0

bytes-vs-bytearray

attached to /examples/bytes-and-bytearray · viewBox 200×90

v2 scores: bytes-and-bytearray 9.5

sentinel-iteration

attached to /examples/sentinel-iteration · viewBox 330×70

v2 scores: sentinel-iteration 9.0

partial-functions

attached to /examples/partial-functions · viewBox 334×36

v2 scores: partial-functions 9.0

args-kwargs

attached to /examples/args-and-kwargs · viewBox 280×68

v2 scores: args-and-kwargs 9.0

multiple-return

attached to /examples/multiple-return-values · viewBox 180×120

v2 scores: multiple-return-values 9.0

lambda-expression

attached to /examples/lambdas · viewBox 170×76

v2 scores: lambdas 9.0

property-fork

attached to /examples/properties · viewBox 232×72

v2 scores: properties 9.0

metaclass-triangle

attached to /examples/metaclasses · viewBox 300×60

v2 scores: metaclasses 9.0

sys-path-resolution

attached to /examples/modules · viewBox 258×100

v2 scores: modules 9.0

import-alias

attached to /examples/import-aliases · viewBox 212×56

v2 scores: import-aliases 9.0

protocol-check

attached to /examples/protocols · viewBox 220×78

v2 scores: protocols 9.0

enum-members

attached to /examples/enums · viewBox 280×60

v2 scores: enums 9.0

datetime-instant

attached to /examples/datetime · viewBox 280×88

v2 scores: datetime 9.0

json-python-mapping

attached to /examples/json · viewBox 220×116

v2 scores: json 9.0

regex-groups

attached to /examples/regular-expressions · viewBox 140×96

v2 scores: regular-expressions 9.0

number-parse

attached to /examples/number-parsing · viewBox 204×64

v2 scores: number-parsing 9.0

format-spec

attached to /examples/string-formatting · viewBox 372×82

v2 scores: string-formatting 9.0

truthy-check

attached to /examples/truthiness · viewBox 240×70

v2 scores: truthiness 9.0

boolean-truth-table

attached to /examples/booleans · viewBox 132×64

v2 scores: booleans 9.0

set-buckets

attached to /examples/sets · viewBox 156×90

v2 scores: sets 9.0

tuple-frozen

attached to /examples/tuples · viewBox 280×48

v2 scores: tuples 9.0

yield-delegation

attached to /examples/yield-from · viewBox 240×84

v2 scores: yield-from 9.0

itertools-chain

attached to /examples/itertools · viewBox 246×82

v2 scores: itertools 9.0

assertion-check

attached to /examples/assertions · viewBox 304×76

v2 scores: assertions 9.0

custom-exception-chain

attached to /examples/custom-exceptions · viewBox 220×90

v2 scores: custom-exceptions 9.0

exception-group-peel

attached to /examples/exception-groups · viewBox 240×50

v2 scores: exception-groups 9.0

delete-name-erased

attached to /examples/delete-statements · viewBox 200×84

v2 scores: delete-statements 9.0

package-tree

attached to /examples/packages · viewBox 240×76

v2 scores: packages 9.0

venv-boundary

attached to /examples/virtual-environments · viewBox 274×76

v2 scores: virtual-environments 9.0

subprocess-spawn

attached to /examples/subprocesses · viewBox 324×60

v2 scores: subprocesses 9.0

aaa-pattern

attached to /examples/testing · viewBox 250×80

v2 scores: testing 9.0

socket-byte-boundary

attached to /examples/networking · viewBox 364×46

v2 scores: networking 9.0

gil-lanes

attached to /examples/threads-and-processes · viewBox 300×138

v2 scores: threads-and-processes 9.0

cast-escape

attached to /examples/casts-and-any · viewBox 184×56

v2 scores: casts-and-any 9.0

newtype-phantom

attached to /examples/newtype · viewBox 96×92

v2 scores: newtype 9.0

overload-signatures

attached to /examples/overloads · viewBox 304×64

v2 scores: overloads 9.0

paramspec-preserve

attached to /examples/paramspec · viewBox 294×60

v2 scores: paramspec 9.0

literal-constrained

attached to /examples/literal-and-final · viewBox 144×76

v2 scores: literal-and-final 9.0

callable-type

attached to /examples/callable-types · viewBox 352×42

v2 scores: callable-types 9.0

isinstance-check

attached to /examples/runtime-type-checks · viewBox 232×76

v2 scores: runtime-type-checks 9.0

container-methods

attached to /examples/container-protocols · viewBox 272×82

v2 scores: container-protocols 9.0

typed-dict-shape

attached to /examples/typed-dicts · viewBox 200×92

v2 scores: typed-dicts 9.0

structured-shapes

attached to /examples/structured-data-shapes · viewBox 266×82

v2 scores: structured-data-shapes 9.0

csv-records

attached to /examples/csv-data · viewBox 212×96

v2 scores: csv-data 9.0

warning-signal

attached to /examples/warnings · viewBox 292×80

v2 scores: warnings 9.0

object-lifecycle

attached to /examples/object-lifecycle · viewBox 366×80

v2 scores: object-lifecycle 9.0

type-alias-name

attached to /examples/type-aliases · viewBox 240×104

v2 scores: type-aliases 9.0

match-dispatch-ladder

attached to /examples/match-statements · viewBox 260×130

v2 scores: match-statements 9.0

match-pattern-variants

attached to /examples/advanced-match-patterns · viewBox 272×96

v2 scores: advanced-match-patterns 9.0

loop-else-gate

attached to /examples/loop-else · viewBox 312×76

v2 scores: loop-else 9.0

function-with-body

attached to /examples/functions · viewBox 334×68

v2 scores: functions 9.0

diff --git a/public/site.07c59ae07f87.css b/public/site.32ce5906d3c1.css similarity index 94% rename from public/site.07c59ae07f87.css rename to public/site.32ce5906d3c1.css index 088a60c..960f7ad 100644 --- a/public/site.07c59ae07f87.css +++ b/public/site.32ce5906d3c1.css @@ -1,4 +1,4 @@ -:root { color-scheme: light; --accent: #FF4801; --accent-text: #C83800; --accent-action: #C83800; --accent-hover: #FF7038; --accent-soft: rgba(255, 72, 1, 0.08); --text: #521000; --muted: rgba(82, 16, 0, 0.7); --page: #F5F1EB; --surface: #FFFBF5; --surface-2: #FFFDFB; --surface-3: #FEF7ED; --hairline: #EBD5C1; --hairline-soft: rgba(235, 213, 193, 0.5); --header-veil: rgba(245, 241, 235, 0.82); --header-veil-0: rgba(245, 241, 235, 0); --header-veil-solid: rgba(245, 241, 235, 0.95); --figure-paper: #F5F1EB; --terminal-bg: #0b1020; --terminal-ink: #f9fafb; --space-1: .5rem; --space-2: .75rem; --space-3: 1rem; --space-4: 1.5rem; --space-5: 2rem; --space-6: 3rem; } +:root { color-scheme: light; --accent: #FF4801; --accent-text: #C83800; --accent-action: #C83800; --accent-hover: #FF7038; --accent-soft: rgba(255, 72, 1, 0.08); --text: #521000; --muted: rgba(82, 16, 0, 0.7); --page: #F5F1EB; --surface: #FFFBF5; --surface-2: #FFFDFB; --surface-3: #FEF7ED; --hairline: #EBD5C1; --hairline-soft: rgba(235, 213, 193, 0.5); --header-veil: rgba(245, 241, 235, 0.82); --header-veil-0: rgba(245, 241, 235, 0); --header-veil-solid: rgba(245, 241, 235, 0.95); --fig-ink: #521000; --fig-ink-soft: rgba(82, 16, 0, 0.7); --fig-accent: #FF4801; --fig-soft: rgba(82, 16, 0, 0.05); --terminal-bg: #0b1020; --terminal-ink: #f9fafb; --space-1: .5rem; --space-2: .75rem; --space-3: 1rem; --space-4: 1.5rem; --space-5: 2rem; --space-6: 3rem; } * { box-sizing: border-box; } html { -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; } :where(a, button, input, textarea, [tabindex]):focus-visible { outline: 3px solid var(--accent-text); outline-offset: 3px; } @@ -216,17 +216,23 @@ keep their prose|code 2-column grid; banners between cells hold one or many figures (small multiples). See docs/visual-explainer-spec.md. */ .cell-banner { margin: var(--space-5) 0; padding: var(--space-4) 0; border-block: 1px dashed var(--hairline-soft); display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--space-4); justify-items: center; } - .cell-banner figure { margin: 0; padding: 0; width: 100%; max-width: clamp(240px, 45vw, 480px); } - .cell-banner svg { max-width: 100%; height: auto; display: block; } - .cell-banner figcaption { margin-top: var(--space-2); color: var(--muted); font-size: .92rem; font-style: italic; max-width: 44ch; } + /* Each figure is a two-row subgrid (drawing, caption) so a pair of + small multiples shares one baseline: drawings bottom-align in row + one and both captions start on the same line in row two, even + when one drawing is taller. */ + .cell-banner figure { display: grid; grid-template-rows: subgrid; grid-row: span 2; justify-items: center; margin: 0; padding: 0; width: 100%; max-width: clamp(240px, 45vw, 480px); } + .cell-banner svg { max-width: 100%; height: auto; display: block; align-self: end; } + .cell-banner figcaption { align-self: start; margin-top: var(--space-2); color: var(--muted); font-size: .92rem; font-style: italic; max-width: 44ch; } .cell-banner--1 { margin-block: var(--space-6); } .cell-banner--1 figure { width: 100%; max-width: clamp(280px, 65vw, 640px); } .cell-banner--1 figcaption { max-width: 42ch; } /* Dark palette: same warm hue family as the light theme, inverted. - Marginalia figures keep their locked light palette, so in dark - mode each SVG sits on a light "paper" chip instead of being - recoloured — the grammar stays untouched. Shiki emits dual-theme - CSS variables; the swap below activates the dark set. */ + Marginalia figures paint with the --fig-* tokens, so redefining + them here recolours every figure with the page: no light chip, no + second palette in the grammar. The values mirror + FIGURE_TOKENS_DARK in src/marginalia_grammar.py (a contract keeps + them equal). Shiki emits dual-theme CSS variables; the swap below + activates the dark set. */ @media (prefers-color-scheme: dark) { :root { color-scheme: dark; @@ -237,6 +243,10 @@ --accent-soft: rgba(255, 107, 46, 0.14); --text: #F3E7DC; --muted: rgba(243, 231, 220, 0.72); + --fig-ink: #F3E7DC; + --fig-ink-soft: rgba(243, 231, 220, 0.72); + --fig-accent: #FF6B2E; + --fig-soft: rgba(243, 231, 220, 0.07); --page: #1B120B; --surface: #241810; --surface-2: #281B12; @@ -247,7 +257,6 @@ --header-veil-0: rgba(27, 18, 11, 0); --header-veil-solid: rgba(27, 18, 11, 0.95); } - .cell-banner figure svg, .journey-section-figure svg { padding: var(--space-2); border-radius: .5rem; background: var(--figure-paper); } .button { color: #2A0B00; } } /* Non-motion accessibility signals. Reduced motion is handled by diff --git a/public/site.css b/public/site.css index 088a60c..960f7ad 100644 --- a/public/site.css +++ b/public/site.css @@ -1,4 +1,4 @@ -:root { color-scheme: light; --accent: #FF4801; --accent-text: #C83800; --accent-action: #C83800; --accent-hover: #FF7038; --accent-soft: rgba(255, 72, 1, 0.08); --text: #521000; --muted: rgba(82, 16, 0, 0.7); --page: #F5F1EB; --surface: #FFFBF5; --surface-2: #FFFDFB; --surface-3: #FEF7ED; --hairline: #EBD5C1; --hairline-soft: rgba(235, 213, 193, 0.5); --header-veil: rgba(245, 241, 235, 0.82); --header-veil-0: rgba(245, 241, 235, 0); --header-veil-solid: rgba(245, 241, 235, 0.95); --figure-paper: #F5F1EB; --terminal-bg: #0b1020; --terminal-ink: #f9fafb; --space-1: .5rem; --space-2: .75rem; --space-3: 1rem; --space-4: 1.5rem; --space-5: 2rem; --space-6: 3rem; } +:root { color-scheme: light; --accent: #FF4801; --accent-text: #C83800; --accent-action: #C83800; --accent-hover: #FF7038; --accent-soft: rgba(255, 72, 1, 0.08); --text: #521000; --muted: rgba(82, 16, 0, 0.7); --page: #F5F1EB; --surface: #FFFBF5; --surface-2: #FFFDFB; --surface-3: #FEF7ED; --hairline: #EBD5C1; --hairline-soft: rgba(235, 213, 193, 0.5); --header-veil: rgba(245, 241, 235, 0.82); --header-veil-0: rgba(245, 241, 235, 0); --header-veil-solid: rgba(245, 241, 235, 0.95); --fig-ink: #521000; --fig-ink-soft: rgba(82, 16, 0, 0.7); --fig-accent: #FF4801; --fig-soft: rgba(82, 16, 0, 0.05); --terminal-bg: #0b1020; --terminal-ink: #f9fafb; --space-1: .5rem; --space-2: .75rem; --space-3: 1rem; --space-4: 1.5rem; --space-5: 2rem; --space-6: 3rem; } * { box-sizing: border-box; } html { -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; } :where(a, button, input, textarea, [tabindex]):focus-visible { outline: 3px solid var(--accent-text); outline-offset: 3px; } @@ -216,17 +216,23 @@ keep their prose|code 2-column grid; banners between cells hold one or many figures (small multiples). See docs/visual-explainer-spec.md. */ .cell-banner { margin: var(--space-5) 0; padding: var(--space-4) 0; border-block: 1px dashed var(--hairline-soft); display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--space-4); justify-items: center; } - .cell-banner figure { margin: 0; padding: 0; width: 100%; max-width: clamp(240px, 45vw, 480px); } - .cell-banner svg { max-width: 100%; height: auto; display: block; } - .cell-banner figcaption { margin-top: var(--space-2); color: var(--muted); font-size: .92rem; font-style: italic; max-width: 44ch; } + /* Each figure is a two-row subgrid (drawing, caption) so a pair of + small multiples shares one baseline: drawings bottom-align in row + one and both captions start on the same line in row two, even + when one drawing is taller. */ + .cell-banner figure { display: grid; grid-template-rows: subgrid; grid-row: span 2; justify-items: center; margin: 0; padding: 0; width: 100%; max-width: clamp(240px, 45vw, 480px); } + .cell-banner svg { max-width: 100%; height: auto; display: block; align-self: end; } + .cell-banner figcaption { align-self: start; margin-top: var(--space-2); color: var(--muted); font-size: .92rem; font-style: italic; max-width: 44ch; } .cell-banner--1 { margin-block: var(--space-6); } .cell-banner--1 figure { width: 100%; max-width: clamp(280px, 65vw, 640px); } .cell-banner--1 figcaption { max-width: 42ch; } /* Dark palette: same warm hue family as the light theme, inverted. - Marginalia figures keep their locked light palette, so in dark - mode each SVG sits on a light "paper" chip instead of being - recoloured — the grammar stays untouched. Shiki emits dual-theme - CSS variables; the swap below activates the dark set. */ + Marginalia figures paint with the --fig-* tokens, so redefining + them here recolours every figure with the page: no light chip, no + second palette in the grammar. The values mirror + FIGURE_TOKENS_DARK in src/marginalia_grammar.py (a contract keeps + them equal). Shiki emits dual-theme CSS variables; the swap below + activates the dark set. */ @media (prefers-color-scheme: dark) { :root { color-scheme: dark; @@ -237,6 +243,10 @@ --accent-soft: rgba(255, 107, 46, 0.14); --text: #F3E7DC; --muted: rgba(243, 231, 220, 0.72); + --fig-ink: #F3E7DC; + --fig-ink-soft: rgba(243, 231, 220, 0.72); + --fig-accent: #FF6B2E; + --fig-soft: rgba(243, 231, 220, 0.07); --page: #1B120B; --surface: #241810; --surface-2: #281B12; @@ -247,7 +257,6 @@ --header-veil-0: rgba(27, 18, 11, 0); --header-veil-solid: rgba(27, 18, 11, 0.95); } - .cell-banner figure svg, .journey-section-figure svg { padding: var(--space-2); border-radius: .5rem; background: var(--figure-paper); } .button { color: #2A0B00; } } /* Non-motion accessibility signals. Reduced motion is handled by diff --git a/scripts/build_figure_contact_sheet.py b/scripts/build_figure_contact_sheet.py new file mode 100755 index 0000000..b0209f6 --- /dev/null +++ b/scripts/build_figure_contact_sheet.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python3 +"""Render a before/after contact sheet of every figure a branch changed. + +Reviewer tooling for figure pull requests. The script extracts the base +revision's ``src/`` and registry with ``git archive``, renders every +attached example figure and journey-section figure from both trees, +and keeps the ones whose drawing differs once colour, font and +text-length attributes are ignored (those change for every figure when +the palette moves, and are not what a reviewer needs to compare). Rows +are grouped so the reviewer's attention goes where it is needed: +"redrawn" rows need judgement, "adjusted" rows (an arrow lengthened, an +accent removed) need only a glance, "removed" rows show a page that lost +its figure. Each row shows the base drawing beside the head drawing with +its caption and element count, so "is the new figure busier?" is +answered by a number as well as a picture. Pass --notes to print a +one-line reason under each row. + + uv run --python 3.13 scripts/build_figure_contact_sheet.py --base main \\ + --output docs/pr-evidence/diagram-upgrade-contact-sheet + +writes a light and a dark PNG captured from a ``build/`` HTML page through +``scripts/capture_browser_screenshot.mjs``. In dark mode the base column +sits on the light paper chip the site used to draw, so both columns show +what a reader actually saw. +""" +from __future__ import annotations + +import argparse +import html +import json +import os +import re +import subprocess +import sys +import tempfile +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT)) + +DUMP = r""" +import json, sys +sys.path.insert(0, sys.argv[1]) +from src.marginalia import ATTACHMENTS, FIGURES, SECTION_FIGURES, _render_svg +rows = {} +for slug, items in ATTACHMENTS.items(): + rows[f"example · {slug}"] = [ + {"figure": name, "caption": caption or "", "svg": _render_svg(name)} for _anchor, name, caption in items + ] +for section, (name, caption) in SECTION_FIGURES.items(): + rows[f"journey section · {section}"] = [{"figure": name, "caption": caption, "svg": _render_svg(name)}] +print(json.dumps(rows)) +""" + +STRIP = re.compile(r'\s(?:fill|stroke|font-family|textLength|lengthAdjust)="[^"]*"') +ELEMENT = re.compile(r"<(?:rect|text|line|circle|path|polygon)\b") + + +def dump(tree: Path) -> dict[str, list[dict[str, str]]]: + out = subprocess.run( + [sys.executable, "-c", DUMP, str(tree)], capture_output=True, text=True, check=True, cwd=tree + ) + return json.loads(out.stdout) + + +def extract(base: str) -> Path: + tree = Path(tempfile.mkdtemp(prefix="figure-sheet-base-")) + archive = subprocess.run(["git", "archive", base, "src", "docs/quality-registries.toml"], cwd=ROOT, capture_output=True, check=True) + subprocess.run(["tar", "-x", "-C", str(tree)], input=archive.stdout, check=True) + return tree + + +def normalised(figures: list[dict[str, str]]) -> str: + return "|".join(STRIP.sub("", f["svg"]).replace(" ", "") for f in figures) + + +LABEL = re.compile(r"]*>([^<]*)") + + +def labels(figures: list[dict[str, str]]) -> list[str]: + return [m for f in figures for m in LABEL.findall(f["svg"])] + + +def elements(figures: list[dict[str, str]]) -> int: + return sum(len(ELEMENT.findall(f["svg"])) for f in figures) + + +def cell(figures: list[dict[str, str]], rationale: str | None) -> str: + if not figures: + note = f"

{html.escape(rationale)}

" if rationale else "" + return f"

no figure

{note}" + parts = [] + for f in figures: + parts.append( + f"
{f['svg']}
{html.escape(f['caption'])}
" + ) + names = " + ".join(html.escape(f["figure"]) for f in figures) + return f"

{names} · {elements(figures)} elements

{''.join(parts)}" + + +KINDS = ( + ("redrawn", "Redrawn: a different figure, or the same figure with a different drawing. Judge these."), + ("adjusted", "Adjusted: the same drawing with an arrow lengthened or an accent removed. Spot the difference."), + ("removed", "Removed: the page no longer carries a figure."), +) + + +def kind(before: list[dict[str, str]], after: list[dict[str, str]]) -> str: + if not after: + return "removed" + if not before: + return "redrawn" + same_names = [f["figure"] for f in before] == [f["figure"] for f in after] + same_labels = labels(before) == labels(after) + if same_names and same_labels and abs(elements(before) - elements(after)) <= 2: + return "adjusted" + return "redrawn" + + +def render(base: str, before: dict, after: dict, rationales: dict[str, str], notes: dict[str, str]) -> str: + from src.marginalia_grammar import FIGURE_TOKENS_DARK, FIGURE_TOKENS_LIGHT, figure_token_css + + grouped: dict[str, list[str]] = {name: [] for name, _ in KINDS} + for key in list(before) + [k for k in after if k not in before]: + b, a = before.get(key, []), after.get(key, []) + if normalised(b) == normalised(a): + continue + slug = key.split(" · ", 1)[1] + note = notes.get(key) or notes.get(slug) or "" + label = f"{html.escape(key)}" + (f"

{html.escape(note)}

" if note else "") + grouped[kind(b, a)].append(f"{label}{cell(b, None)}{cell(a, rationales.get(slug))}") + sections = [] + for name, blurb in KINDS: + if grouped[name]: + sections.append( + f"{name} · {len(grouped[name])} {html.escape(blurb)}" + + "".join(grouped[name]) + "" + ) + rows = "".join(sections) + return f""" +Figure contact sheet +
+

Figures changed since {html.escape(base)}

+

Left: the figure as rendered on {html.escape(base)} (in dark mode, on the light chip the site used to draw). Right: this branch. Element counts are drawable SVG elements; captions are the shipped figcaptions.

+ +{rows} +
pagebeforeafter
+""" + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--base", default="main") + parser.add_argument("--output", required=True, help="PNG path stem; writes -light.png and -dark.png") + parser.add_argument("--notes", help="JSON file mapping a row key (or slug) to a one-line note on what changed and why") + args = parser.parse_args() + + before = dump(extract(args.base)) + after = dump(ROOT) + from src.editorial_registry import load_registry + + rationales = {slug: entry["reason"] for slug, entry in load_registry().get("no_figure_rationales", {}).items()} + stem = Path(args.output).resolve() + stem.parent.mkdir(parents=True, exist_ok=True) + page = ROOT / "build" / "figure-contact-sheet" / f"{stem.name}.html" + page.parent.mkdir(parents=True, exist_ok=True) + notes = json.loads(Path(args.notes).read_text()) if args.notes else {} + page.write_text(render(args.base, before, after, rationales, notes)) + changed = sum(1 for k in set(before) | set(after) if normalised(before.get(k, [])) != normalised(after.get(k, []))) + print(f"wrote {page.relative_to(ROOT)} — {changed} changed figure rows") + for scheme in ("light", "dark"): + output = stem.parent / f"{stem.name}-{scheme}.png" + env = { + **os.environ, + "TARGET_URL": page.resolve().as_uri(), + "CAPTURE_SELECTOR": ".sheet", + "WIDTH": "1500", + "HEIGHT": "12000", + "COLOR_SCHEME": scheme, + "OUTPUT": str(output), + } + subprocess.run(["node", str(ROOT / "scripts" / "capture_browser_screenshot.mjs")], env=env, check=True, capture_output=True) + print(f"wrote {output.relative_to(ROOT)}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/build_marginalia.py b/scripts/build_marginalia.py index e0a52c3..5362c47 100755 --- a/scripts/build_marginalia.py +++ b/scripts/build_marginalia.py @@ -19,7 +19,7 @@ from _common import ROOT, load_catalog from src.marginalia import ATTACHMENTS, FIGURES, SCORES -from src.marginalia_grammar import Card +from src.marginalia_grammar import FIGURE_TOKENS_LIGHT, Card, figure_token_css OUT = ROOT / "public" / "prototyping" / "marginalia-gestalt.html" @@ -76,6 +76,7 @@ def _example_cards() -> list[Card]: --ink: #521000; --ink-soft: rgba(82, 16, 0, 0.7); --rule: #EBD5C1; + __FIGURE_TOKENS__ } *, *::before, *::after { box-sizing: border-box; } html, body { background: var(--paper); color: var(--ink); margin: 0; } @@ -131,7 +132,7 @@ def _example_cards() -> list[Card]: def render() -> str: - out = [HEAD] + out = [HEAD.replace("__FIGURE_TOKENS__", figure_token_css(FIGURE_TOKENS_LIGHT))] out.append('

Examples

\n
') for card in EXAMPLES: out.append(card.render_html()) diff --git a/scripts/build_prototypes.py b/scripts/build_prototypes.py index 0d49e44..d96f43d 100755 --- a/scripts/build_prototypes.py +++ b/scripts/build_prototypes.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 """Generate exploratory prototypes under public/prototyping/. -The canonical layout is "figure between prose and code", with the cell -dropping to single-column when a figure is attached. These prototypes -demonstrate that layout on representative examples and journeys, plus -keep the marginalia-gestalt and operators-comparison review pages. +The production layout keeps every cell in its prose|code grid and places +figures in banner rows between cells (docs/visual-explainer-spec.md). +These prototypes demonstrate that layout with one, two and three figures +per banner on representative examples and journeys, and build the +gestalt review pages. """ from __future__ import annotations diff --git a/scripts/build_social_cards.py b/scripts/build_social_cards.py index fb14aee..1606516 100755 --- a/scripts/build_social_cards.py +++ b/scripts/build_social_cards.py @@ -22,6 +22,7 @@ from src.app import JOURNEYS, PYTHON_VERSION, list_examples from src.marginalia import render_first_figure +from src.marginalia_grammar import FIGURE_TOKENS_LIGHT, figure_token_css CARD_DIR = ROOT / "build" / "social-cards" OUTPUT_DIR = ROOT / "public" / "og" @@ -31,6 +32,7 @@ PROVENANCE_VERSION = 1 _CARD_CSS = """ + :root { """ + figure_token_css(FIGURE_TOKENS_LIGHT) + """ } * { box-sizing: border-box; margin: 0; } body { width: 1200px; height: 630px; overflow: hidden; display: flex; align-items: stretch; gap: 48px; padding: 64px 72px; color: #521000; font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"DejaVu Sans\", sans-serif; background: radial-gradient(circle at top left, rgba(255, 72, 1, 0.12), transparent 40rem), #F5F1EB; } .copy { flex: 1.2; display: flex; flex-direction: column; min-width: 0; } diff --git a/src/asset_manifest.py b/src/asset_manifest.py index d242400..061c7c6 100644 --- a/src/asset_manifest.py +++ b/src/asset_manifest.py @@ -1,3 +1,3 @@ # Generated by scripts/fingerprint_assets.py. Do not edit by hand. -ASSET_PATHS = {'SITE_CSS': '/site.07c59ae07f87.css', 'SYNTAX_JS': '/syntax-highlight.743e2535f133.js', 'EDITOR_JS': '/editor.3659b44c8480.js', 'RUNNER_JS': '/runner.05ddb60aa9e7.js', 'SEARCH_JS': '/search.e5a5822d9917.js', 'SEARCH_INDEX': '/search-index.332712b2e502.json'} -HTML_CACHE_VERSION = 'deed3f3ebbb1' +ASSET_PATHS = {'SITE_CSS': '/site.32ce5906d3c1.css', 'SYNTAX_JS': '/syntax-highlight.743e2535f133.js', 'EDITOR_JS': '/editor.3659b44c8480.js', 'RUNNER_JS': '/runner.05ddb60aa9e7.js', 'SEARCH_JS': '/search.e5a5822d9917.js', 'SEARCH_INDEX': '/search-index.332712b2e502.json'} +HTML_CACHE_VERSION = '93bb0fecab44' diff --git a/src/editorial_registry_data.py b/src/editorial_registry_data.py index 0b896f8..62f94aa 100644 --- a/src/editorial_registry_data.py +++ b/src/editorial_registry_data.py @@ -1,2 +1,2 @@ # Generated by scripts/embed_editorial_registry.py. Do not edit by hand. -REGISTRY_TOML = '# Quality and editorial registries.\n#\n# Source of truth for rubric checks, journey structure, edge labels,\n# figure attachments/captions, and curated scores. Executable paint\n# functions stay in `src/marginalia.py`; mutable editorial data lives here.\n\n[[confusable_pairs]]\nname = "__str__ vs __repr__"\nowner = "special-methods"\ntokens = ["__str__", "__repr__"]\n\n[[confusable_pairs]]\nname = "is vs =="\nowner = "equality-and-identity"\ntokens = [" is ", "=="]\n\n[[confusable_pairs]]\nname = "list vs tuple"\nowner = "tuples"\ntokens = ["list", "tuple"]\n\n[[confusable_pairs]]\nname = "classmethod vs staticmethod vs instance method"\nowner = "classmethods-and-staticmethods"\ntokens = ["@classmethod", "@staticmethod", "self"]\n\n[[confusable_pairs]]\nname = "isinstance vs type=="\nowner = "runtime-type-checks"\ntokens = ["isinstance(", "type("]\n\n[[confusable_pairs]]\nname = "generator vs class iterator"\nowner = "generators"\ntokens = ["yield", "__next__"]\n\n[[confusable_pairs]]\nname = "iterator vs iterable"\nowner = "iterator-vs-iterable"\ntokens = ["iterable", "iterator", "iter("]\n\n[[confusable_pairs]]\nname = "mutable vs immutable class attributes"\nowner = "classes"\ntokens = ["class attribute", "__init__"]\n\n[[confusable_pairs]]\nname = "eager vs lazy production"\nowner = "generators"\ntokens = ["return", "yield"]\n\n[[confusable_pairs]]\nname = "Protocol vs ABC"\nowner = "abstract-base-classes"\ntokens = ["Protocol", "ABC"]\n\n[[confusable_pairs]]\nname = "dataclass vs NamedTuple vs TypedDict"\nowner = "structured-data-shapes"\ntokens = ["@dataclass", "NamedTuple", "TypedDict"]\n\n[[confusable_pairs]]\nname = "bound vs unbound methods"\nowner = "bound-and-unbound-methods"\ntokens = ["bound method", "Class.method"]\n\n[[confusable_pairs]]\nname = "yield vs return"\nowner = "generators"\ntokens = ["yield", "return"]\n\n[[confusable_pairs]]\nname = "shallow vs deep copy"\nowner = "copying-collections"\ntokens = ["copy(", "deepcopy("]\n\n[[confusable_pairs]]\nname = "sync vs async functions"\nowner = "async-await"\ntokens = ["async def", "def "]\n\n[broad_surface_tours]\n\n[broad_surface_tours.special-methods]\nrequired_tokens = [\n "__init__", "__repr__", "__str__", "__eq__", "__hash__", "__lt__",\n "__len__", "__iter__", "__contains__", "__getitem__", "__setitem__",\n "__call__", "__enter__", "__exit__", "__bool__",\n]\n\n[broad_surface_tours.operators]\nrequired_tokens = ["+", "==", " is ", " in ", "and", "or", "&", ":="]\n\n[broad_surface_tours.type-hints]\nrequired_tokens = ["list[", " | ", "Optional", "TypeAlias"]\n\n[broad_surface_tours.testing]\nrequired_tokens = ["TestCase", "assertEqual", "assertRaises", "setUp"]\n\n[broad_surface_tours.async-await]\nrequired_tokens = ["async def", "await", "asyncio.run", "asyncio.gather", "async for", "async with"]\n\n[broad_surface_tours.packages]\nrequired_tokens = ["__init__.py", "from .", "__all__"]\n\n[broad_surface_tours.regular-expressions]\nrequired_tokens = ["re.match", "re.search", "re.findall", "re.compile", "re.IGNORECASE", "re.sub"]\n\n[broad_surface_tours.literals]\nrequired_tokens = ["0x", "0b", "_", "f\\"", "True", "None"]\n\n[[footguns]]\nname = "Mutable default class attribute"\nowner = "classes"\nbroken_tokens = ["items = []"]\nfixed_tokens = ["self.items = []"]\n\n[[footguns]]\nname = "Mutable default function argument"\nowner = "functions"\nbroken_tokens = ["items=[]", "append_broken"]\nfixed_tokens = ["items=None", "append_fixed"]\n\n[[footguns]]\nname = "Late-binding closure in a loop"\nowner = "closures"\nbroken_tokens = ["lambda: i", "[2, 2, 2]"]\nfixed_tokens = ["lambda i=i", "[0, 1, 2]"]\n\n[[footguns]]\nname = "Integer identity caching"\nowner = "equality-and-identity"\nbroken_tokens = ["small_a is small_b", "big_a is big_b"]\nfixed_tokens = ["big_a == big_b", "small-integer cache"]\n\n[[footguns]]\nname = "Shallow vs deep copy"\nowner = "copying-collections"\nbroken_tokens = ["copy("]\nfixed_tokens = ["deepcopy("]\n\n[[footguns]]\nname = "Generator one-pass exhaustion"\nowner = "generators"\nbroken_tokens = ["yield"]\nfixed_tokens = ["list("]\n\n[[footguns]]\nname = "Dictionary mutation during iteration"\nowner = "dicts"\nbroken_tokens = ["for ", "del "]\nfixed_tokens = ["list(", ".keys("]\n\n[[footguns]]\nname = "Floating-point equality"\nowner = "numbers"\nbroken_tokens = ["0.1", "0.2"]\nfixed_tokens = ["isclose", "math"]\n\n[[footguns]]\nname = "bool as a subclass of int"\nowner = "booleans"\nbroken_tokens = ["isinstance(True, int)", "True + True"]\nfixed_tokens = ["not isinstance(value, bool)", "is_strict_int"]\n\n[[footguns]]\nname = "Bare except swallowing KeyboardInterrupt"\nowner = "exceptions"\nbroken_tokens = ["except Exception"]\nfixed_tokens = ["except ValueError"]\n\n[paired_pages]\n# Pages whose titles differ only by a suffix or modifier; at least one\n# member of each pair must demonstrate the relationship in a cell.\npairs = [\n ["iterators", "iterating-over-iterables"],\n ["iterators", "iterator-vs-iterable"],\n ["generators", "generator-expressions"],\n ["comprehensions", "comprehension-patterns"],\n]\n\n[paired_pages.cell_tokens]\n"iterators|iterating-over-iterables" = ["iterable", "iterator"]\n"iterators|iterator-vs-iterable" = ["iterable", "iterator", "iter("]\n"generators|generator-expressions" = ["generator", "expression"]\n"comprehensions|comprehension-patterns" = ["nested loops", "explicit loop"]\n\n[quality_gates]\nexample_target = 9.0\nexample_hard_min = 8.5\njourney_section_min = 8.5\njourney_average_min = 8.8\n\n[quality_waivers.hello-world]\naccepted_min = 7.0\nreason = "Traditional first example is intentionally tiny: program -> output is the goal, not a full concept tour."\nexpires = "2026-12-01"\n\n[no_figure_rationales]\n# Slugs may be added here only when the page is constraint-shaped,\n# infrastructure-shaped, or aggregator-shaped and a single mechanism\n# figure would distort the lesson. Current production attaches figures\n# to every example, so this registry is intentionally empty.\n\n[journey_outcomes]\n\n[journey_outcomes."runtime::Start with executable evidence."]\njourney = "runtime"\nsection = "Start with executable evidence."\nsupport = [\n "hello-world",\n "values",\n "literals",\n "variables",\n "constants",\n]\noutcomes = [\n "start with a complete program and its output",\n "see that Python programs manipulate runtime objects",\n "write small values directly in source code",\n "understand that names bind to objects rather than storing values themselves",\n]\n\n[journey_outcomes."runtime::Separate value, identity, and absence."]\njourney = "runtime"\nsection = "Separate value, identity, and absence."\nsupport = [\n "none",\n "booleans",\n "truthiness",\n "equality-and-identity",\n "mutability",\n "object-lifecycle",\n]\noutcomes = [\n "represent expected absence with a singleton object",\n "combine facts with boolean operators",\n "predict how objects behave in boolean contexts",\n "distinguish value equality from object identity",\n]\n\n[journey_outcomes."runtime::Read expressions as object operations."]\njourney = "runtime"\nsection = "Read expressions as object operations."\nsupport = [\n "numbers",\n "operators",\n "strings",\n "string-formatting",\n "bytes-and-bytearray",\n]\noutcomes = [\n "use numeric objects and arithmetic operators",\n "combine, compare, and test values with expression syntax",\n "treat text as Unicode rather than raw bytes",\n "turn objects into readable text at output boundaries",\n]\n\n[journey_outcomes."control-flow::Choose between paths."]\njourney = "control-flow"\nsection = "Choose between paths."\nsupport = [\n "booleans",\n "truthiness",\n "operators",\n "conditionals",\n]\noutcomes = [\n "combine facts into readable conditions",\n "use object truth values without hiding intent",\n "build comparisons and boolean expressions for conditions",\n "choose between branches with clear predicates",\n]\n\n[journey_outcomes."control-flow::Name and shape decisions."]\njourney = "control-flow"\nsection = "Name and shape decisions."\nsupport = [\n "assignment-expressions",\n "match-statements",\n "advanced-match-patterns",\n]\noutcomes = [\n "name an intermediate value inside a condition when it improves clarity",\n "dispatch on the shape of data rather than only on boolean tests",\n "combine destructuring, alternatives, and guards in pattern matching",\n]\n\n[journey_outcomes."control-flow::Stop as soon as the answer is known."]\njourney = "control-flow"\nsection = "Stop as soon as the answer is known."\nsupport = [\n "guard-clauses",\n "assertions",\n "exceptions",\n]\noutcomes = [\n "show how early returns reduce nested conditional code",\n "state assumptions that should fail loudly while developing",\n "leave the current path when ordinary return values are not enough",\n]\n\n[journey_outcomes."iteration::Choose the right loop shape."]\njourney = "iteration"\nsection = "Choose the right loop shape."\nsupport = [\n "for-loops",\n "while-loops",\n "break-and-continue",\n "loop-else",\n "sentinel-iteration",\n]\noutcomes = [\n "consume values from an iterable",\n "repeat while a condition must be rechecked",\n "interrupt or skip loop work intentionally",\n "attach completion logic to loops that did not break",\n]\n\n[journey_outcomes."iteration::See the protocol behind `for`."]\njourney = "iteration"\nsection = "See the protocol behind `for`."\nsupport = [\n "iterating-over-iterables",\n "iterator-vs-iterable",\n "iterators",\n "generators",\n]\noutcomes = [\n "separate value producers from value consumers",\n "use `iter()` and `next()` to expose the protocol behind `for`",\n "write functions that produce values lazily",\n]\n\n[journey_outcomes."iteration::Compose lazy value streams."]\njourney = "iteration"\nsection = "Compose lazy value streams."\nsupport = [\n "generator-expressions",\n "itertools",\n "yield-from",\n]\noutcomes = [\n "create lazy one-pass streams with expression syntax",\n "compose iterator streams without materializing every value",\n "delegate part of a generator to another iterable",\n]\n\n[journey_outcomes."shapes::Pick the container that matches the question."]\njourney = "shapes"\nsection = "Pick the container that matches the question."\nsupport = [\n "lists",\n "tuples",\n "dicts",\n "sets",\n "collections-module",\n]\noutcomes = [\n "store ordered mutable data",\n "group fixed-position values",\n "look up values by key",\n "model uniqueness and membership",\n]\n\n[journey_outcomes."shapes::Move between shapes deliberately."]\njourney = "shapes"\nsection = "Move between shapes deliberately."\nsupport = [\n "unpacking",\n "slices",\n "comprehensions",\n "comprehension-patterns",\n "sorting",\n "copying-collections",\n]\noutcomes = [\n "bind names from structured values",\n "select ranges from sequences",\n "build concrete collections from compact loops",\n "compose filters and nested transformations",\n]\n\n[journey_outcomes."shapes::Cross text and data boundaries."]\njourney = "shapes"\nsection = "Cross text and data boundaries."\nsupport = [\n "number-parsing",\n "json",\n "regular-expressions",\n "datetime",\n "csv-data",\n]\noutcomes = [\n "turn text into numbers safely",\n "move structured data across a text boundary",\n "extract structure from text patterns",\n "represent dates, times, and durations as typed values",\n]\n\n[journey_outcomes."interfaces::Start with functions as named behavior."]\njourney = "interfaces"\nsection = "Start with functions as named behavior."\nsupport = [\n "functions",\n "keyword-only-arguments",\n "positional-only-parameters",\n "args-and-kwargs",\n "multiple-return-values",\n]\noutcomes = [\n "package behavior behind a name",\n "make important call-site choices explicit",\n "hide parameter names that should remain implementation details",\n "accept flexible call shapes when forwarding or adapting APIs",\n]\n\n[journey_outcomes."interfaces::Use functions as values."]\njourney = "interfaces"\nsection = "Use functions as values."\nsupport = [\n "scope-global-nonlocal",\n "closures",\n "recursion",\n "lambdas",\n "decorators",\n "partial-functions",\n]\noutcomes = [\n "control where assignment happens",\n "capture state in nested functions",\n "solve self-similar problems with a base case",\n "write small unnamed functions for expression positions",\n]\n\n[journey_outcomes."interfaces::Bundle behavior with state."]\njourney = "interfaces"\nsection = "Bundle behavior with state."\nsupport = [\n "classes",\n "inheritance-and-super",\n "dataclasses",\n "properties",\n "classmethods-and-staticmethods",\n "special-methods",\n "truth-and-size",\n "container-protocols",\n "callable-objects",\n "operator-overloading",\n "attribute-access",\n "bound-and-unbound-methods",\n "descriptors",\n "metaclasses",\n]\noutcomes = [\n "bundle state and behavior into a new object type",\n "reuse and extend behavior through parent classes",\n "generate common methods for data containers",\n "keep attribute syntax while adding computation or validation",\n]\n\n[journey_outcomes."types::Keep runtime and static analysis separate."]\njourney = "types"\nsection = "Keep runtime and static analysis separate."\nsupport = [\n "type-hints",\n "protocols",\n "abstract-base-classes",\n "enums",\n "runtime-type-checks",\n]\noutcomes = [\n "document expected types and feed type checkers",\n "describe required behavior by structural shape",\n "name a fixed set of symbolic values",\n "show `type()`, `isinstance()`, and `issubclass()` without turning Python into Java",\n]\n\n[journey_outcomes."types::Describe realistic data shapes."]\njourney = "types"\nsection = "Describe realistic data shapes."\nsupport = [\n "union-and-optional-types",\n "type-aliases",\n "typed-dicts",\n "structured-data-shapes",\n "literal-and-final",\n "callable-types",\n]\noutcomes = [\n "show `X | Y` and `None`-aware APIs",\n "name complex types with `type` statements or aliases",\n "type dictionary records that come from JSON",\n "express constrained values and names that should not be rebound",\n]\n\n[journey_outcomes."types::Scale annotations for reusable libraries."]\njourney = "types"\nsection = "Scale annotations for reusable libraries."\nsupport = [\n "generics-and-typevar",\n "paramspec",\n "overloads",\n "casts-and-any",\n "newtype",\n]\noutcomes = [\n "write reusable typed containers and functions",\n "preserve callable signatures through decorators",\n "describe APIs whose return type depends on the input shape",\n "show escape hatches and their tradeoffs",\n]\n\n[journey_outcomes."reliability::Make failure explicit."]\njourney = "reliability"\nsection = "Make failure explicit."\nsupport = [\n "exceptions",\n "assertions",\n "exception-chaining",\n "exception-groups",\n "custom-exceptions",\n "warnings",\n]\noutcomes = [\n "signal and recover from errors",\n "state internal assumptions",\n "preserve the cause while translating an error",\n "handle multiple failures together",\n]\n\n[journey_outcomes."reliability::Control resource and module boundaries."]\njourney = "reliability"\nsection = "Control resource and module boundaries."\nsupport = [\n "context-managers",\n "delete-statements",\n "modules",\n "import-aliases",\n "packages",\n "virtual-environments",\n]\noutcomes = [\n "pair setup with reliable cleanup",\n "remove names, attributes, and items intentionally",\n "split code into importable files",\n "make imported names clear at use sites",\n]\n\n[journey_outcomes."reliability::Handle operations that outlive one expression."]\njourney = "reliability"\nsection = "Handle operations that outlive one expression."\nsupport = [\n "async-await",\n "async-iteration-and-context",\n "logging",\n "testing",\n "subprocesses",\n "threads-and-processes",\n "networking",\n]\noutcomes = [\n "await concurrent I/O-shaped work",\n "consume async streams and cleanup protocols",\n "record operational events without using `print()`",\n "write deterministic tests with `unittest` or `pytest`",\n]\n\n[score_model]\n# Criterion weights mirror docs/example-quality-rubric.md. The registry\n# makes the scoring model inspectable even before every example is broken\n# down into per-criterion subscores.\nconceptual_payoff = 1.0\nrationale = 0.75\nalternatives_and_boundaries = 0.75\nexecutable_determinism = 1.0\npython_idiom_and_accuracy = 1.0\nliterate_fit = 0.75\nsource_result_pairing = 0.75\nconcept_decomposition = 0.75\nprogressive_walkthrough = 0.75\nrepresentative_coverage = 0.75\npractical_usefulness = 0.75\neditorial_progression = 1.0\n\n# Editorial registries loaded by the site at runtime.\n# Keep prose, captions, scores, journey ordering, and edge labels here;\n# keep executable paint functions in src/marginalia.py.\n\n[[journeys]]\nslug = "runtime"\ntitle = "Runtime"\nsummary = "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."\n\n[[journeys.sections]]\ntitle = "Start with executable evidence."\nsummary = "Learners first need to see that every page is a runnable program with visible output."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "hello-world"\ndescription = "start with a complete program and its output"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "values"\ndescription = "see that Python programs manipulate runtime objects"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "literals"\ndescription = "write small values directly in source code"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "variables"\ndescription = "understand that names bind to objects rather than storing values themselves"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "constants"\ndescription = "learn the convention Python uses for values that should not change"\n\n[[journeys.sections]]\ntitle = "Separate value, identity, and absence."\nsummary = "This section prevents early confusion about equality, object identity, missing values, and truth tests."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "none"\ndescription = "represent expected absence with a singleton object"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "booleans"\ndescription = "combine facts with boolean operators"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "truthiness"\ndescription = "predict how objects behave in boolean contexts"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "equality-and-identity"\ndescription = "distinguish value equality from object identity"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "mutability"\ndescription = "predict when operations change an object in place"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "object-lifecycle"\ndescription = "explain references, garbage collection, and why identity can outlive a single name"\n\n[[journeys.sections]]\ntitle = "Read expressions as object operations."\nsummary = "This section connects operators, text, and formatting to Python\'s data model."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "numbers"\ndescription = "use numeric objects and arithmetic operators"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "operators"\ndescription = "combine, compare, and test values with expression syntax"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "strings"\ndescription = "treat text as Unicode rather than raw bytes"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "string-formatting"\ndescription = "turn objects into readable text at output boundaries"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "bytes-and-bytearray"\ndescription = "contrast text with binary data and explicit decoding"\n\n[[journeys]]\nslug = "control-flow"\ntitle = "Control Flow"\nsummary = "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."\n\n[[journeys.sections]]\ntitle = "Choose between paths."\nsummary = "Start with ordinary branching and boolean predicates before reaching for more compact forms."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "booleans"\ndescription = "combine facts into readable conditions"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "truthiness"\ndescription = "use object truth values without hiding intent"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "operators"\ndescription = "build comparisons and boolean expressions for conditions"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "conditionals"\ndescription = "choose between branches with clear predicates"\n\n[[journeys.sections]]\ntitle = "Name and shape decisions."\nsummary = "Some branches become clearer when the code names an intermediate value or dispatches on data shape."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "assignment-expressions"\ndescription = "name an intermediate value inside a condition when it improves clarity"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "match-statements"\ndescription = "dispatch on the shape of data rather than only on boolean tests"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "advanced-match-patterns"\ndescription = "combine destructuring, alternatives, and guards in pattern matching"\n\n[[journeys.sections]]\ntitle = "Stop as soon as the answer is known."\nsummary = "Early exits make the successful path easier to read by moving exceptional or completed cases out of the way."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "guard-clauses"\ndescription = "show how early returns reduce nested conditional code"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "assertions"\ndescription = "state assumptions that should fail loudly while developing"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "exceptions"\ndescription = "leave the current path when ordinary return values are not enough"\n\n[[journeys]]\nslug = "iteration"\ntitle = "Iteration"\nsummary = "This journey follows repeated work from ordinary loops to the iterator protocol: consume values, stop deliberately, and produce lazy streams only when they help."\n\n[[journeys.sections]]\ntitle = "Choose the right loop shape."\nsummary = "Loops differ by what they consume, when they stop, and whether completion itself carries meaning."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "for-loops"\ndescription = "consume values from an iterable"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "while-loops"\ndescription = "repeat while a condition must be rechecked"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "break-and-continue"\ndescription = "interrupt or skip loop work intentionally"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "loop-else"\ndescription = "attach completion logic to loops that did not break"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "sentinel-iteration"\ndescription = "show `iter(callable, sentinel)` for repeated reads until a marker appears"\n\n[[journeys.sections]]\ntitle = "See the protocol behind `for`."\nsummary = "The important mental shift is that loops consume producers through a protocol rather than special-casing lists."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "iterating-over-iterables"\ndescription = "separate value producers from value consumers"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "iterator-vs-iterable"\ndescription = "distinguish re-iterable collections from one-pass iterators"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "iterators"\ndescription = "use `iter()` and `next()` to expose the protocol behind `for`"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "generators"\ndescription = "write functions that produce values lazily"\n\n[[journeys.sections]]\ntitle = "Compose lazy value streams."\nsummary = "Iterator pipelines are useful when code can transform values one at a time instead of materializing every intermediate result."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "generator-expressions"\ndescription = "create lazy one-pass streams with expression syntax"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "itertools"\ndescription = "compose iterator streams without materializing every value"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "yield-from"\ndescription = "delegate part of a generator to another iterable"\n\n[[journeys]]\nslug = "shapes"\ntitle = "Shapes"\nsummary = "This journey teaches the core Python habit of choosing a data shape, transforming it directly, and making the result visible."\n\n[[journeys.sections]]\ntitle = "Pick the container that matches the question."\nsummary = "Lists, tuples, dictionaries, and sets answer different questions about order, position, lookup, and uniqueness."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "lists"\ndescription = "store ordered mutable data"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "tuples"\ndescription = "group fixed-position values"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "dicts"\ndescription = "look up values by key"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "sets"\ndescription = "model uniqueness and membership"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "collections-module"\ndescription = "show `deque`, `Counter`, `defaultdict`, and `namedtuple` as specialized shapes"\n\n[[journeys.sections]]\ntitle = "Move between shapes deliberately."\nsummary = "Most everyday Python code is data reshaping, so learners need the idioms for selecting, unpacking, and rebuilding values."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "unpacking"\ndescription = "bind names from structured values"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "slices"\ndescription = "select ranges from sequences"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "comprehensions"\ndescription = "build concrete collections from compact loops"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "comprehension-patterns"\ndescription = "compose filters and nested transformations"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "sorting"\ndescription = "order records with key functions"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "copying-collections"\ndescription = "contrast shallow copies, deep copies, and shared nested data"\n\n[[journeys.sections]]\ntitle = "Cross text and data boundaries."\nsummary = "Programs often receive text and produce structured data, so parsing and serialization belong in the data journey."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "number-parsing"\ndescription = "turn text into numbers safely"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "json"\ndescription = "move structured data across a text boundary"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "regular-expressions"\ndescription = "extract structure from text patterns"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "datetime"\ndescription = "represent dates, times, and durations as typed values"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "csv-data"\ndescription = "show row-shaped text data and dictionary records"\n\n[[journeys]]\nslug = "interfaces"\ntitle = "Interfaces"\nsummary = "This journey shows how Python grows from simple functions to callable APIs, object interfaces, protocols, and metaclasses."\n\n[[journeys.sections]]\ntitle = "Start with functions as named behavior."\nsummary = "Functions are the first abstraction boundary because they name behavior and control how callers provide information."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "functions"\ndescription = "package behavior behind a name"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "keyword-only-arguments"\ndescription = "make important call-site choices explicit"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "positional-only-parameters"\ndescription = "hide parameter names that should remain implementation details"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "args-and-kwargs"\ndescription = "accept flexible call shapes when forwarding or adapting APIs"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "multiple-return-values"\ndescription = "return multiple related values as a tuple"\n\n[[journeys.sections]]\ntitle = "Use functions as values."\nsummary = "Python functions can capture state, be passed around, and wrap other functions."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "scope-global-nonlocal"\ndescription = "control where assignment happens"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "closures"\ndescription = "capture state in nested functions"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "recursion"\ndescription = "solve self-similar problems with a base case"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "lambdas"\ndescription = "write small unnamed functions for expression positions"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "decorators"\ndescription = "wrap behavior without changing call sites"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "partial-functions"\ndescription = "show how to pre-fill arguments with `functools.partial`"\n\n[[journeys.sections]]\ntitle = "Bundle behavior with state."\nsummary = "Classes become useful when data and behavior need to move together behind a stable interface."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "classes"\ndescription = "bundle state and behavior into a new object type"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "inheritance-and-super"\ndescription = "reuse and extend behavior through parent classes"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "dataclasses"\ndescription = "generate common methods for data containers"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "properties"\ndescription = "keep attribute syntax while adding computation or validation"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "classmethods-and-staticmethods"\ndescription = "choose between instance, class, and static behavior on a class"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "special-methods"\ndescription = "connect objects to Python syntax and built-ins"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "truth-and-size"\ndescription = "make objects work with truth tests and `len()`"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "container-protocols"\ndescription = "support membership, lookup, and assignment syntax"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "callable-objects"\ndescription = "make stateful instances callable like functions"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "operator-overloading"\ndescription = "define operators only when the operation is unsurprising"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "attribute-access"\ndescription = "customize fallback lookup and assignment carefully"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "bound-and-unbound-methods"\ndescription = "explain how attribute access turns a function into a bound method"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "descriptors"\ndescription = "explain the protocol behind methods, properties, and managed attributes"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "metaclasses"\ndescription = "customize class creation when ordinary class tools are not enough"\n\n[[journeys]]\nslug = "types"\ntitle = "Types"\nsummary = "This journey maps Python\'s runtime object model to optional static annotations so learners know what types can and cannot promise."\n\n[[journeys.sections]]\ntitle = "Keep runtime and static analysis separate."\nsummary = "The first lesson is that annotations describe expectations for tools while ordinary Python objects still run the program."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "type-hints"\ndescription = "document expected types and feed type checkers"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "protocols"\ndescription = "describe required behavior by structural shape"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "abstract-base-classes"\ndescription = "contrast runtime-enforced nominal interfaces with structural protocols"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "enums"\ndescription = "name a fixed set of symbolic values"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "runtime-type-checks"\ndescription = "show `type()`, `isinstance()`, and `issubclass()` without turning Python into Java"\n\n[[journeys.sections]]\ntitle = "Describe realistic data shapes."\nsummary = "Typed Python becomes useful when annotations explain optional values, unions, callables, and JSON-like records."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "union-and-optional-types"\ndescription = "show `X | Y` and `None`-aware APIs"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "type-aliases"\ndescription = "name complex types with `type` statements or aliases"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "typed-dicts"\ndescription = "type dictionary records that come from JSON"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "structured-data-shapes"\ndescription = "choose between dataclass, NamedTuple, and TypedDict for records"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "literal-and-final"\ndescription = "express constrained values and names that should not be rebound"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "callable-types"\ndescription = "type functions that are passed as arguments"\n\n[[journeys.sections]]\ntitle = "Scale annotations for reusable libraries."\nsummary = "Advanced typing exists to preserve information across reusable functions, containers, and decorators."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "generics-and-typevar"\ndescription = "write reusable typed containers and functions"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "paramspec"\ndescription = "preserve callable signatures through decorators"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "overloads"\ndescription = "describe APIs whose return type depends on the input shape"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "casts-and-any"\ndescription = "show escape hatches and their tradeoffs"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "newtype"\ndescription = "create distinct static identities for runtime-compatible values"\n\n[[journeys]]\nslug = "reliability"\ntitle = "Reliability"\nsummary = "This journey follows the boundaries where programs fail, clean up, split into modules, communicate with the outside world, and run concurrent work."\n\n[[journeys.sections]]\ntitle = "Make failure explicit."\nsummary = "Robust Python code distinguishes expected absence, broken assumptions, recoverable errors, and domain-specific failures."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "exceptions"\ndescription = "signal and recover from errors"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "assertions"\ndescription = "state internal assumptions"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "exception-chaining"\ndescription = "preserve the cause while translating an error"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "exception-groups"\ndescription = "handle multiple failures together"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "custom-exceptions"\ndescription = "name failures in the language of the problem domain"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "warnings"\ndescription = "signal soft problems and deprecations"\n\n[[journeys.sections]]\ntitle = "Control resource and module boundaries."\nsummary = "Cleanup, deletion, imports, and modules define where responsibilities begin and end."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "context-managers"\ndescription = "pair setup with reliable cleanup"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "delete-statements"\ndescription = "remove names, attributes, and items intentionally"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "modules"\ndescription = "split code into importable files"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "import-aliases"\ndescription = "make imported names clear at use sites"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "packages"\ndescription = "show package directories, `__init__.py`, and public module boundaries"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "virtual-environments"\ndescription = "isolate dependencies for a project"\n\n[[journeys.sections]]\ntitle = "Handle operations that outlive one expression."\nsummary = "I/O, testing, logging, subprocesses, and concurrency require different control boundaries from ordinary expressions."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "async-await"\ndescription = "await concurrent I/O-shaped work"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "async-iteration-and-context"\ndescription = "consume async streams and cleanup protocols"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "logging"\ndescription = "record operational events without using `print()`"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "testing"\ndescription = "write deterministic tests with `unittest` or `pytest`"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "subprocesses"\ndescription = "run external commands safely"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "threads-and-processes"\ndescription = "contrast concurrency choices beyond `asyncio`"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "networking"\ndescription = "make HTTP or socket boundaries explicit"\n\n# Explicit see_also edge labels; default labels are inferred in src/app.py.\n[[see_also_edge_labels]]\nsource = "break-and-continue"\ntarget = "loop-else"\nlabel = "contrast"\n\n[[see_also_edge_labels]]\nsource = "assignment-expressions"\ntarget = "conditionals"\nlabel = "contrast"\n\n[[see_also_edge_labels]]\nsource = "yield-from"\ntarget = "generators"\nlabel = "prerequisite"\n\n[[see_also_edge_labels]]\nsource = "async-iteration-and-context"\ntarget = "async-await"\nlabel = "prerequisite"\n\n[[see_also_edge_labels]]\nsource = "delete-statements"\ntarget = "mutability"\nlabel = "shared mechanism"\n\n[[see_also_edge_labels]]\nsource = "positional-only-parameters"\ntarget = "keyword-only-arguments"\nlabel = "contrast"\n\n[[see_also_edge_labels]]\nsource = "assertions"\ntarget = "exceptions"\nlabel = "alternative"\n\n[[see_also_edge_labels]]\nsource = "exception-chaining"\ntarget = "exceptions"\nlabel = "builds on"\n\n[[see_also_edge_labels]]\nsource = "exception-groups"\ntarget = "exceptions"\nlabel = "alternative"\n\n[[see_also_edge_labels]]\nsource = "operators"\ntarget = "numbers"\nlabel = "related syntax"\n\n[[see_also_edge_labels]]\nsource = "operators"\ntarget = "booleans"\nlabel = "condition building"\n\n[[see_also_edge_labels]]\nsource = "operators"\ntarget = "assignment-expressions"\nlabel = "specialized expression"\n\n[[see_also_edge_labels]]\nsource = "literals"\ntarget = "values"\nlabel = "value surface"\n\n[[see_also_edge_labels]]\nsource = "literals"\ntarget = "strings"\nlabel = "text literal"\n\n[[see_also_edge_labels]]\nsource = "literals"\ntarget = "sets"\nlabel = "container literal"\n\n# Example-cell figure attachments. figure names refer to FIGURES paint functions.\n[[figure_attachments]]\nslug = "mutability"\nanchor = "cell-0"\nfigure = "aliasing-mutation"\ncaption = "Two names share one mutable list — appending through one name changes the object visible through both."\n\n[[figure_attachments]]\nslug = "mutability"\nanchor = "cell-0"\nfigure = "tuple-no-mutation"\ncaption = "By contrast, a tuple is frozen — its contents cannot change in place, so aliasing carries no mutation hazard."\n\n[[figure_attachments]]\nslug = "variables"\nanchor = "cell-0"\nfigure = "variables-bind"\ncaption = "A name is a label that points at an object. Assignment binds the label; the object exists independently."\n\n[[figure_attachments]]\nslug = "lists"\nanchor = "cell-0"\nfigure = "list-append"\ncaption = "Lists are mutable sequences. `.append` extends the same list object — no new list is created."\n\n[[figure_attachments]]\nslug = "dicts"\nanchor = "cell-0"\nfigure = "dict-buckets"\ncaption = "Each key is hashed to a bucket; collisions chain into the next slot. Lookup is constant-time on average."\n\n[[figure_attachments]]\nslug = "unpacking"\nanchor = "cell-0"\nfigure = "unpacking-bind"\ncaption = "Left-side names bind to right-side positions; `*rest` gathers the middle into a list."\n\n[[figure_attachments]]\nslug = "comprehensions"\nanchor = "cell-0"\nfigure = "comprehension-equivalence"\ncaption = "A comprehension is a compact spelling of the equivalent for-loop with append, made into one expression."\n\n[[figure_attachments]]\nslug = "classes"\nanchor = "cell-0"\nfigure = "class-triangle"\ncaption = "Every Python value sits on the instance → class → type triangle; the metaclass is the type of the class."\n\n[[figure_attachments]]\nslug = "inheritance-and-super"\nanchor = "cell-0"\nfigure = "mro-chain"\ncaption = "Multiple inheritance forms a graph; C3 linearisation flattens it into the MRO Python uses for attribute lookup."\n\n[[figure_attachments]]\nslug = "dataclasses"\nanchor = "cell-0"\nfigure = "dataclass-fields"\ncaption = "Field declarations become the generated __init__ signature: declaration is the constructor."\n\n[[figure_attachments]]\nslug = "special-methods"\nanchor = "cell-0"\nfigure = "operator-dispatch"\ncaption = "Operators are method calls. `a + b` dispatches to `a.__add__(b)`; the data model exposes the syntax."\n\n[[figure_attachments]]\nslug = "decorators"\nanchor = "cell-0"\nfigure = "decorator-rebind"\ncaption = "@dec rebinds the name to wrapper(f₀); the original function survives only in the wrapper\'s closure cell."\n\n[[figure_attachments]]\nslug = "recursion"\nanchor = "cell-1"\nfigure = "call-stack"\ncaption = "Each call pushes a new frame with the same name and a smaller argument; the base case unwinds back up the stack."\n\n[[figure_attachments]]\nslug = "exception-chaining"\nanchor = "cell-0"\nfigure = "exception-cause-context"\ncaption = "`raise X from Y` sets `__cause__` (explicit); raising during except sets `__context__` (implicit)."\n\n[[figure_attachments]]\nslug = "hello-world"\nanchor = "cell-0"\nfigure = "program-output"\ncaption = "Every Python program starts as source and produces text on standard output. The smallest mental model."\n\n[[figure_attachments]]\nslug = "numbers"\nanchor = "cell-0"\nfigure = "number-lines"\ncaption = "Ints have unbounded precision; floats use IEEE doubles whose representable values thin out near the extremes."\n\n[[figure_attachments]]\nslug = "operators"\nanchor = "cell-0"\nfigure = "expression-tree"\ncaption = "An expression like `(2 + 3) * 4` parses as a tree; operator precedence and parentheses determine its shape."\n\n[[figure_attachments]]\nslug = "none"\nanchor = "cell-0"\nfigure = "none-singleton"\ncaption = "`None` is a single object: every name that points at None points at the same object."\n\n[[figure_attachments]]\nslug = "equality-and-identity"\nanchor = "cell-0"\nfigure = "identity-and-equality"\ncaption = "Two names can share one object (`is` and `==` both true) or hold two equal-but-distinct objects (only `==` true)."\n\n[[figure_attachments]]\nslug = "strings"\nanchor = "cell-0"\nfigure = "codepoints-bytes"\ncaption = "Strings are sequences of Unicode codepoints. UTF-8 encoding turns them into bytes; `é` takes two bytes, `c` takes one."\n\n[[figure_attachments]]\nslug = "for-loops"\nanchor = "cell-1"\nfigure = "iterator-unroll"\ncaption = "Each call to next() advances the caret one cell along the iterable — the same shape behind range(), strings, and any sequence."\n\n[[figure_attachments]]\nslug = "sorting"\nanchor = "cell-1"\nfigure = "sort-stability"\ncaption = "Python\'s sort is stable: items with equal keys keep their original order, so chained sorts compose predictably."\n\n[[figure_attachments]]\nslug = "keyword-only-arguments"\nanchor = "cell-0"\nfigure = "kw-only-separator"\ncaption = "A bare `*` divides positional or keyword arguments from keyword-only ones; callers must pass `c` and `d` by name."\n\n[[figure_attachments]]\nslug = "positional-only-parameters"\nanchor = "cell-0"\nfigure = "positional-only-separator"\ncaption = "A bare `/` divides positional-only arguments from positional-or-keyword ones; callers cannot name `a` or `b`."\n\n[[figure_attachments]]\nslug = "positional-only-parameters"\nanchor = "cell-0"\nfigure = "kw-only-separator"\ncaption = "In the same signature, the bare `*` works the other way: parameters after it, such as `clamp`, must be named at the call site."\n\n[[figure_attachments]]\nslug = "closures"\nanchor = "cell-0"\nfigure = "closure-cell"\ncaption = "The inner function keeps a reference into the outer scope\'s cell, so the captured factor survives the outer return."\n\n[[figure_attachments]]\nslug = "scope-global-nonlocal"\nanchor = "cell-0"\nfigure = "scope-rings"\ncaption = "Name lookup walks LEGB — local, enclosing, global, built-in — outward, returning the first binding it finds."\n\n[[figure_attachments]]\nslug = "generators"\nanchor = "cell-0"\nfigure = "generator-ribbon"\ncaption = "A generator\'s body is a timeline cut by yield gates: each next() advances to the next gate; locals survive the pause."\n\n[[figure_attachments]]\nslug = "type-hints"\nanchor = "cell-0"\nfigure = "annotation-ghost"\ncaption = "Annotations describe expected types for tools; the runtime accepts any object regardless."\n\n[[figure_attachments]]\nslug = "exceptions"\nanchor = "cell-0"\nfigure = "exception-lanes"\ncaption = "try, except, else, and finally as parallel lanes; a single coral path traces what actually runs."\n\n[[figure_attachments]]\nslug = "context-managers"\nanchor = "cell-0"\nfigure = "context-bowtie"\ncaption = "A context manager pairs setup with reliable cleanup; the raise path still routes through __exit__."\n\n[[figure_attachments]]\nslug = "async-await"\nanchor = "cell-1"\nfigure = "async-swimlane"\ncaption = "On await, the coroutine yields to the loop; the loop runs other work and resumes when the awaitable is ready."\n\n[[figure_attachments]]\nslug = "iterators"\nanchor = "cell-0"\nfigure = "iter-protocol"\ncaption = "iter() exposes the iterator behind for; next() pulls one value at a time until exhausted."\n\n[[figure_attachments]]\nslug = "slices"\nanchor = "cell-0"\nfigure = "slice-ruler"\ncaption = "Slice indices sit between cells; [:3] and [3:] partition the sequence at index 3, never overlapping or losing an item."\n\n[[figure_attachments]]\nslug = "operator-overloading"\nanchor = "cell-0"\nfigure = "operator-dispatch"\ncaption = "Defining `__add__` on a class lets `+` dispatch into the class\'s own behavior."\n\n[[figure_attachments]]\nslug = "iterator-vs-iterable"\nanchor = "cell-0"\nfigure = "iter-protocol"\ncaption = "An iterable knows how to produce an iterator (via iter()); the iterator knows how to produce values (via next())."\n\n[[figure_attachments]]\nslug = "iterator-vs-iterable"\nanchor = "cell-1"\nfigure = "iterator-unroll"\ncaption = "The iterator\'s caret only moves forward — after the first `list(stream)` drains it, nothing is left for the second."\n\n[[figure_attachments]]\nslug = "type-aliases"\nanchor = "cell-0"\nfigure = "type-alias-name"\ncaption = "A type alias names a complex annotation once so call sites read as the domain meaning, not the type composition."\n\n[[figure_attachments]]\nslug = "typed-dicts"\nanchor = "cell-0"\nfigure = "typed-dict-shape"\ncaption = "TypedDict gives each key a typed value, so `obj[\'x\']` is checked against the declared shape."\n\n[[figure_attachments]]\nslug = "union-and-optional-types"\nanchor = "cell-0"\nfigure = "union-types"\ncaption = "`int | str | None` says one slot may hold any of three shapes — including expected absence."\n\n[[figure_attachments]]\nslug = "generics-and-typevar"\nanchor = "cell-0"\nfigure = "generic-preservation"\ncaption = "A generic preserves the input type through the call: the same T flows in and out of fn[T]."\n\n[[figure_attachments]]\nslug = "abstract-base-classes"\nanchor = "cell-0"\nfigure = "class-triangle"\ncaption = "An ABC sits on the same triangle as concrete classes; subclasses inherit the abstract methods they must implement."\n\n[[figure_attachments]]\nslug = "copying-collections"\nanchor = "cell-0"\nfigure = "aliasing-mutation"\ncaption = "Without copy() two names share the same object; mutating through one is visible through the other."\n\n[[figure_attachments]]\nslug = "truth-and-size"\nanchor = "cell-0"\nfigure = "truth-and-size"\ncaption = "bool(x) calls __bool__ first; if absent, __len__() != 0; if neither, defaults to True."\n\n[[figure_attachments]]\nslug = "descriptors"\nanchor = "cell-0"\nfigure = "descriptor-protocol"\ncaption = "Attribute access on an instance routes through the descriptor\'s __get__/__set__/__delete__ when the attribute is a descriptor."\n\n[[figure_attachments]]\nslug = "bound-and-unbound-methods"\nanchor = "cell-0"\nfigure = "bound-unbound"\ncaption = "Accessing a method via an instance binds self; accessing it via the class returns the underlying function."\n\n[[figure_attachments]]\nslug = "classmethods-and-staticmethods"\nanchor = "cell-0"\nfigure = "method-kinds"\ncaption = "Three method kinds, three first-argument conventions: classmethod gets the class, staticmethod gets nothing, instance gets self."\n\n[[figure_attachments]]\nslug = "callable-objects"\nanchor = "cell-0"\nfigure = "callable-objects"\ncaption = "Defining __call__ makes any object callable; functions are just one shape that satisfies this protocol."\n\n[[figure_attachments]]\nslug = "attribute-access"\nanchor = "cell-0"\nfigure = "attribute-lookup"\ncaption = "obj.x checks instance __dict__, then class __dict__, then __getattr__; the first hit wins."\n\n[[figure_attachments]]\nslug = "guard-clauses"\nanchor = "cell-0"\nfigure = "guard-clauses"\ncaption = "Early returns handle the exceptional cases first so the main work is the body of the function, not its tail."\n\n[[figure_attachments]]\nslug = "bytes-and-bytearray"\nanchor = "cell-0"\nfigure = "bytes-vs-bytearray"\ncaption = "bytes is a frozen sequence of integers; bytearray is the mutable counterpart with append/extend/etc."\n\n[[figure_attachments]]\nslug = "sentinel-iteration"\nanchor = "cell-0"\nfigure = "sentinel-iteration"\ncaption = "`iter(callable, sentinel)` calls the callable repeatedly, stopping when it returns the sentinel."\n\n[[figure_attachments]]\nslug = "partial-functions"\nanchor = "cell-0"\nfigure = "partial-functions"\ncaption = "`functools.partial(f, 1)` pre-fills `a=1`, returning a thinner callable `g(b, c)` that only needs the rest."\n\n[[figure_attachments]]\nslug = "args-and-kwargs"\nanchor = "cell-0"\nfigure = "args-kwargs"\ncaption = "*args captures the extra positionals as a tuple; **kwargs captures the extra keywords as a dict."\n\n[[figure_attachments]]\nslug = "multiple-return-values"\nanchor = "cell-0"\nfigure = "multiple-return"\ncaption = "A function returning multiple values really returns one tuple; the caller unpacks it into named bindings."\n\n[[figure_attachments]]\nslug = "lambdas"\nanchor = "cell-0"\nfigure = "lambda-expression"\ncaption = "A lambda is a function literal: parameters before the colon, a single expression after, no statement body."\n\n[[figure_attachments]]\nslug = "properties"\nanchor = "cell-0"\nfigure = "property-fork"\ncaption = "When x is a property, attribute access routes through fget/fset instead of touching __dict__."\n\n[[figure_attachments]]\nslug = "metaclasses"\nanchor = "cell-0"\nfigure = "metaclass-triangle"\ncaption = "A metaclass is the type of a class, just as a class is the type of its instances; type is the default metaclass."\n\n[[figure_attachments]]\nslug = "metaclasses"\nanchor = "cell-0"\nfigure = "class-triangle"\ncaption = "The class makes instances — the same triangle one level down, with type as the default apex."\n\n[[figure_attachments]]\nslug = "modules"\nanchor = "cell-0"\nfigure = "sys-path-resolution"\ncaption = "An import walks sys.path entry by entry; the first directory containing the module wins."\n\n[[figure_attachments]]\nslug = "import-aliases"\nanchor = "cell-0"\nfigure = "import-alias"\ncaption = "`import x as y` binds the name y to the same module object x would have."\n\n[[figure_attachments]]\nslug = "protocols"\nanchor = "cell-0"\nfigure = "protocol-check"\ncaption = "An object satisfies a protocol structurally — by having the required methods — not by inheriting it."\n\n[[figure_attachments]]\nslug = "enums"\nanchor = "cell-0"\nfigure = "enum-members"\ncaption = "An enum names a fixed set of symbolic values; no new members appear at runtime."\n\n[[figure_attachments]]\nslug = "datetime"\nanchor = "cell-0"\nfigure = "datetime-instant"\ncaption = "An aware datetime carries a UTC offset; one instant in time reads differently on two clocks."\n\n[[figure_attachments]]\nslug = "json"\nanchor = "cell-0"\nfigure = "json-python-mapping"\ncaption = "Six type pairs bridge the JSON text boundary; each json value maps to one Python type."\n\n[[figure_attachments]]\nslug = "regular-expressions"\nanchor = "cell-0"\nfigure = "regex-anchors"\ncaption = "^ and $ anchor the pattern; quantifiers like {2} bound how many times a token repeats."\n\n[[figure_attachments]]\nslug = "number-parsing"\nanchor = "cell-0"\nfigure = "number-parse"\ncaption = "int() turns text into a typed number; malformed input raises ValueError instead of guessing."\n\n[[figure_attachments]]\nslug = "string-formatting"\nanchor = "cell-1"\nfigure = "format-spec"\ncaption = "The format spec is a railroad of named optional fields: alignment, sign, width, precision, type."\n\n[[figure_attachments]]\nslug = "truthiness"\nanchor = "cell-0"\nfigure = "truthy-check"\ncaption = "bool(x) is True except for a small fixed set: 0, 0.0, \\"\\", [], {}, None, False."\n\n[[figure_attachments]]\nslug = "booleans"\nanchor = "cell-0"\nfigure = "boolean-truth-table"\ncaption = "`a and b` returns True only when both are True; otherwise it returns the first falsy value."\n\n[[figure_attachments]]\nslug = "sets"\nanchor = "cell-0"\nfigure = "set-buckets"\ncaption = "Sets are hash buckets without values; `x in s` averages O(1) regardless of size."\n\n[[figure_attachments]]\nslug = "tuples"\nanchor = "cell-3"\nfigure = "tuple-frozen"\ncaption = "Tuples are ordered, immutable sequences; positions matter, contents do not change once constructed."\n\n[[figure_attachments]]\nslug = "tuples"\nanchor = "cell-3"\nfigure = "list-append"\ncaption = "A list is the other intent: a variable number of similar items, growing in place with `.append`."\n\n[[figure_attachments]]\nslug = "values"\nanchor = "cell-0"\nfigure = "value-types"\ncaption = "Every literal is an object with a type; the type carries the behaviour, not the variable name."\n\n[[figure_attachments]]\nslug = "yield-from"\nanchor = "cell-0"\nfigure = "yield-delegation"\ncaption = "`yield from inner` delegates iteration to an inner generator; its yields surface here unchanged."\n\n[[figure_attachments]]\nslug = "itertools"\nanchor = "cell-0"\nfigure = "itertools-chain"\ncaption = "chain stitches two iterables into one stream without materialising either: values arrive lazily."\n\n[[figure_attachments]]\nslug = "assertions"\nanchor = "cell-0"\nfigure = "assertion-check"\ncaption = "assert tests a condition; True passes silently, False raises AssertionError with the optional message."\n\n[[figure_attachments]]\nslug = "custom-exceptions"\nanchor = "cell-0"\nfigure = "custom-exception-chain"\ncaption = "Subclassing an existing exception gains a domain name without changing semantics."\n\n[[figure_attachments]]\nslug = "exception-groups"\nanchor = "cell-0"\nfigure = "exception-group-peel"\ncaption = "except* peels matched leaves out of an ExceptionGroup; survivors regroup and propagate."\n\n[[figure_attachments]]\nslug = "delete-statements"\nanchor = "cell-0"\nfigure = "delete-name-erased"\ncaption = "`del x` removes the name; the object survives if any other reference holds it, otherwise gets collected."\n\n[[figure_attachments]]\nslug = "conditionals"\nanchor = "cell-0"\nfigure = "branch-fork"\ncaption = "A predicate sorts a value into one of several branches; if/elif/else is the explicit spelling."\n\n[[figure_attachments]]\nslug = "match-statements"\nanchor = "cell-0"\nfigure = "match-dispatch-ladder"\ncaption = "match dispatches by pattern shape; the value flows down the patterns and the first match wins."\n\n[[figure_attachments]]\nslug = "assignment-expressions"\nanchor = "cell-0"\nfigure = "naming-decisions"\ncaption = "The walrus binds a name during the surrounding expression; one expression, two outputs."\n\n[[figure_attachments]]\nslug = "iterating-over-iterables"\nanchor = "cell-0"\nfigure = "iter-protocol"\ncaption = "`for` desugars to iter()+next(): one iter() call, then next() until StopIteration ends the loop."\n\n[[figure_attachments]]\nslug = "generator-expressions"\nanchor = "cell-1"\nfigure = "lazy-stream"\ncaption = "A generator expression composes filter and map lazily; values flow only when next() pulls them."\n\n[[figure_attachments]]\nslug = "async-iteration-and-context"\nanchor = "cell-0"\nfigure = "async-swimlane"\ncaption = "async iteration and async with both rest on the same loop-vs-coroutine handoff as await."\n\n[[figure_attachments]]\nslug = "loop-else"\nanchor = "cell-0"\nfigure = "loop-else-gate"\ncaption = "The loop\'s else branch runs only when the loop falls through naturally; break skips it."\n\n[[figure_attachments]]\nslug = "break-and-continue"\nanchor = "cell-0"\nfigure = "early-exit"\ncaption = "break exits the loop; continue skips to the next iteration. Both interrupt the natural fall-through."\n\n[[figure_attachments]]\nslug = "comprehension-patterns"\nanchor = "cell-0"\nfigure = "comprehension-equivalence"\ncaption = "Nested clauses compose left to right; the comprehension is still equivalent to a for-loop with append."\n\n[[figure_attachments]]\nslug = "container-protocols"\nanchor = "cell-0"\nfigure = "container-methods"\ncaption = "Container syntax routes to narrow protocol methods: assignment to __setitem__, membership to __contains__, lookup to __getitem__."\n\n[[figure_attachments]]\nslug = "functions"\nanchor = "cell-0"\nfigure = "function-with-body"\ncaption = "A function takes inputs, evaluates a body, and returns a value: `greet(\'Ada\')` produces `\'Hello, Ada\'`."\n\n[[figure_attachments]]\nslug = "constants"\nanchor = "cell-0"\nfigure = "variables-bind"\ncaption = "UPPER_CASE is a naming convention, not a language constraint; the binding behaves like any other variable."\n\n[[figure_attachments]]\nslug = "while-loops"\nanchor = "cell-0"\nfigure = "while-backedge"\ncaption = "`while` repeats the body while the test stays true; the back-edge returns control to the test before each pass."\n\n[[figure_attachments]]\nslug = "while-loops"\nanchor = "cell-0"\nfigure = "loop-repetition"\ncaption = "Each loop shape has its own stopping rule: `for` ends when the iterable is exhausted, `while` when the condition turns false, sentinel iteration when the marker value appears."\n\n[[figure_attachments]]\nslug = "advanced-match-patterns"\nanchor = "cell-0"\nfigure = "match-pattern-variants"\ncaption = "Capture, alternative, guard, and class patterns each name a different way a value can match a case."\n\n[[figure_attachments]]\nslug = "literals"\nanchor = "cell-0"\nfigure = "literal-forms"\ncaption = "Each Python type has its own literal spellings; ints accept decimal, hex, and binary; strings accept either quote."\n\n[[figure_attachments]]\nslug = "packages"\nanchor = "cell-0"\nfigure = "package-tree"\ncaption = "A directory with __init__.py becomes an importable package; submodules and subpackages nest beneath it."\n\n[[figure_attachments]]\nslug = "virtual-environments"\nanchor = "cell-0"\nfigure = "venv-boundary"\ncaption = "A venv carries its own interpreter and site-packages, isolating a project\'s dependencies from the system."\n\n[[figure_attachments]]\nslug = "subprocesses"\nanchor = "cell-0"\nfigure = "subprocess-spawn"\ncaption = "subprocess.run spawns a child process and captures its stdout, stderr, and exit code as portable evidence."\n\n[[figure_attachments]]\nslug = "logging"\nanchor = "cell-0"\nfigure = "logging-levels"\ncaption = "Five severity levels; the logger\'s configured threshold drops everything below it."\n\n[[figure_attachments]]\nslug = "testing"\nanchor = "cell-0"\nfigure = "aaa-pattern"\ncaption = "arrange-act-assert: set up the state, perform the behavior under test, compare the result to expectations."\n\n[[figure_attachments]]\nslug = "networking"\nanchor = "cell-0"\nfigure = "socket-byte-boundary"\ncaption = "Text crosses the socket as bytes — `encode` marks the python → wire boundary, `decode` brings the bytes back to a Python `str`."\n\n[[figure_attachments]]\nslug = "threads-and-processes"\nanchor = "cell-0"\nfigure = "gil-lanes"\ncaption = "Threads share memory but the GIL serialises Python bytecode; processes run in parallel with isolated memory."\n\n[[figure_attachments]]\nslug = "casts-and-any"\nanchor = "cell-0"\nfigure = "cast-escape"\ncaption = "cast(T, x) tells the type checker to treat x as T; the runtime is unaffected."\n\n[[figure_attachments]]\nslug = "newtype"\nanchor = "cell-0"\nfigure = "newtype-phantom"\ncaption = "NewType creates a distinct static identity backed by the same runtime type — UserId is int with a name."\n\n[[figure_attachments]]\nslug = "overloads"\nanchor = "cell-0"\nfigure = "overload-signatures"\ncaption = "@overload declares static call signatures for double(int) and double(str); one runtime implementation handles both."\n\n[[figure_attachments]]\nslug = "paramspec"\nanchor = "cell-0"\nfigure = "paramspec-preserve"\ncaption = "ParamSpec preserves the wrapped function\'s signature through a decorator, parameter for parameter."\n\n[[figure_attachments]]\nslug = "literal-and-final"\nanchor = "cell-0"\nfigure = "literal-constrained"\ncaption = "Literal narrows a slot to a fixed set of constant values; Final says the binding will not change."\n\n[[figure_attachments]]\nslug = "callable-types"\nanchor = "cell-0"\nfigure = "callable-type"\ncaption = "A Callable annotation describes the callback slot: this argument list must produce this return type."\n\n[[figure_attachments]]\nslug = "runtime-type-checks"\nanchor = "cell-0"\nfigure = "isinstance-check"\ncaption = "isinstance and issubclass ask the runtime; the answer is a bool, not a static type refinement."\n\n[[figure_attachments]]\nslug = "collections-module"\nanchor = "cell-0"\nfigure = "collections-containers"\ncaption = "Four specialised containers for shapes the built-in types don\'t cover well: deque, Counter, defaultdict, namedtuple."\n\n[[figure_attachments]]\nslug = "structured-data-shapes"\nanchor = "cell-0"\nfigure = "structured-shapes"\ncaption = "The same record can be a mutable dataclass, an immutable NamedTuple, or a runtime dict described by TypedDict."\n\n[[figure_attachments]]\nslug = "csv-data"\nanchor = "cell-0"\nfigure = "csv-records"\ncaption = "CSV files are rows of records; each line has the same columns in the same order."\n\n[[figure_attachments]]\nslug = "warnings"\nanchor = "cell-0"\nfigure = "warning-signal"\ncaption = "A warning is a soft signal: the message is reported, but execution continues unless filters elevate it."\n\n[[figure_attachments]]\nslug = "object-lifecycle"\nanchor = "cell-0"\nfigure = "object-lifecycle"\ncaption = "Names keep the same object reachable; deleting one name matters only when it removes the last reference."\n\n# Journey-section figures keyed by the visible section title.\n[[journey_section_figures]]\nsection = "Start with executable evidence."\nfigure = "runtime-evidence-loop"\ncaption = "Examples are evidence loops: source, a run step, and visible output stay together."\n\n[[journey_section_figures]]\nsection = "Separate value, identity, and absence."\nfigure = "runtime-object-axes"\ncaption = "Runtime objects answer separate questions: equal value, same identity, or the singleton that marks absence."\n\n[[journey_section_figures]]\nsection = "Read expressions as object operations."\nfigure = "runtime-expression-model"\ncaption = "Expression syntax enters the data model; object methods produce the result."\n\n[[journey_section_figures]]\nsection = "Choose between paths."\nfigure = "control-decision-map"\ncaption = "Facts flow into one decision point; exactly one branch owns the next step."\n\n[[journey_section_figures]]\nsection = "Name and shape decisions."\nfigure = "control-fact-shape"\ncaption = "Name a fact when a condition needs it; match shape when the data structure is the decision."\n\n[[journey_section_figures]]\nsection = "Stop as soon as the answer is known."\nfigure = "control-stop-boundary"\ncaption = "Early exits draw a boundary: once the answer is found, the tail stays unread."\n\n[[journey_section_figures]]\nsection = "Choose the right loop shape."\nfigure = "iteration-loop-selector"\ncaption = "Choose the loop from its stopping rule: exhaustion, condition, or sentinel marker."\n\n[[journey_section_figures]]\nsection = "See the protocol behind `for`."\nfigure = "iteration-protocol-map"\ncaption = "for is surface syntax; iter() creates an iterator and next() pulls values until StopIteration."\n\n[[journey_section_figures]]\nsection = "Compose lazy value streams."\nfigure = "iteration-lazy-pull"\ncaption = "Lazy pipelines run from the consumer\'s pull: next() requests one value through each stage."\n\n[[journey_section_figures]]\nsection = "Pick the container that matches the question."\nfigure = "container-questions"\ncaption = "Each container answers a different question: ordered, fixed, lookup, unique."\n\n[[journey_section_figures]]\nsection = "Move between shapes deliberately."\nfigure = "reshape-pipeline"\ncaption = "Most everyday code reshapes data: one input, one transform, one new value."\n\n[[journey_section_figures]]\nsection = "Cross text and data boundaries."\nfigure = "text-data-boundary"\ncaption = "Programs receive text and produce structured data; parsing makes the boundary explicit."\n\n[[journey_section_figures]]\nsection = "Start with functions as named behavior."\nfigure = "function-signature"\ncaption = "A function is the first abstraction boundary: arguments in, body, return value out."\n\n[[journey_section_figures]]\nsection = "Use functions as values."\nfigure = "function-as-value"\ncaption = "Functions are first-class values. A second name binds to the same function object."\n\n[[journey_section_figures]]\nsection = "Bundle behavior with state."\nfigure = "class-with-state"\ncaption = "Classes group fields and methods so data and behavior move together behind one interface."\n\n[[journey_section_figures]]\nsection = "Keep runtime and static analysis separate."\nfigure = "type-runtime-static-split"\ncaption = "Runtime values run the program; static tools inspect separate annotations and report before execution."\n\n[[journey_section_figures]]\nsection = "Describe realistic data shapes."\nfigure = "type-shape-catalog"\ncaption = "Real data contracts combine fields, variants, and expected absence instead of one scalar type."\n\n[[journey_section_figures]]\nsection = "Scale annotations for reusable libraries."\nfigure = "type-library-contract"\ncaption = "Reusable APIs carry caller contracts through the library boundary with generics, parameters, and overloads."\n\n[[journey_section_figures]]\nsection = "Make failure explicit."\nfigure = "reliability-signal-map"\ncaption = "Different failure shapes need explicit signals: assertions, recovery, chained causes, or warnings."\n\n[[journey_section_figures]]\nsection = "Control resource and module boundaries."\nfigure = "reliability-boundary-map"\ncaption = "Reliable programs name their boundaries: resources clean up, modules import, environments constrain runtime."\n\n[[journey_section_figures]]\nsection = "Handle operations that outlive one expression."\nfigure = "reliability-operation-boundary"\ncaption = "Async, threaded, test, and logging work cross an operation boundary before evidence comes back."\n\n# Curated scores for example-cell figures.\n[[example_figure_scores]]\nslug = "variables"\nscore = 9.5\ncomment = "the canonical name → object picture"\n\n[[example_figure_scores]]\nslug = "mutability"\nscore = 9.5\ncomment = "three-state small multiple of aliased mutation"\n\n[[example_figure_scores]]\nslug = "copying-collections"\nscore = 9.5\ncomment = "same picture as mutability, perfect match"\n\n[[example_figure_scores]]\nslug = "hello-world"\nscore = 9.0\ncomment = "program → output, smallest mechanism"\n\n[[example_figure_scores]]\nslug = "numbers"\nscore = 9.0\ncomment = "int unbounded vs float thinning, both registers"\n\n[[example_figure_scores]]\nslug = "operators"\nscore = 9.0\ncomment = "expression tree mechanism"\n\n[[example_figure_scores]]\nslug = "none"\nscore = 9.0\ncomment = "three names converging on one None"\n\n[[example_figure_scores]]\nslug = "equality-and-identity"\nscore = 9.0\ncomment = "shared vs separate object, side-by-side"\n\n[[example_figure_scores]]\nslug = "strings"\nscore = 9.0\ncomment = "codepoints + bytes registers"\n\n[[example_figure_scores]]\nslug = "for-loops"\nscore = 9.0\ncomment = "4-row caret advance"\n\n[[example_figure_scores]]\nslug = "sorting"\nscore = 9.0\ncomment = "stability ribbons preserved across keys"\n\n[[example_figure_scores]]\nslug = "keyword-only-arguments"\nscore = 9.0\ncomment = "signature with explicit `*` separator"\n\n[[example_figure_scores]]\nslug = "positional-only-parameters"\nscore = 9.0\ncomment = "signature with explicit `/` separator"\n\n[[example_figure_scores]]\nslug = "closures"\nscore = 9.0\ncomment = "captured cell reference"\n\n[[example_figure_scores]]\nslug = "scope-global-nonlocal"\nscore = 9.0\ncomment = "LEGB nested rings"\n\n[[example_figure_scores]]\nslug = "recursion"\nscore = 9.0\ncomment = "stacked frames with same name, different argument"\n\n[[example_figure_scores]]\nslug = "lists"\nscore = 9.0\ncomment = "cells with append mechanism"\n\n[[example_figure_scores]]\nslug = "dicts"\nscore = 9.0\ncomment = "hash buckets with collision chain"\n\n[[example_figure_scores]]\nslug = "slices"\nscore = 9.0\ncomment = "ruler with bracket overlay"\n\n[[example_figure_scores]]\nslug = "comprehensions"\nscore = 9.0\ncomment = "comprehension over equivalent for-loop"\n\n[[example_figure_scores]]\nslug = "type-hints"\nscore = 9.0\ncomment = "ghost annotations over runtime values"\n\n[[example_figure_scores]]\nslug = "generators"\nscore = 9.0\ncomment = "ribbon cut by yield gates"\n\n[[example_figure_scores]]\nslug = "exceptions"\nscore = 9.0\ncomment = "try/except/else/finally lanes with traced path"\n\n[[example_figure_scores]]\nslug = "context-managers"\nscore = 9.0\ncomment = "enter / body / exit bowtie"\n\n[[example_figure_scores]]\nslug = "async-await"\nscore = 9.0\ncomment = "loop/coro swimlane with await handoffs"\n\n[[example_figure_scores]]\nslug = "classes"\nscore = 9.0\ncomment = "instance/class/type triangle"\n\n[[example_figure_scores]]\nslug = "inheritance-and-super"\nscore = 9.0\ncomment = "MRO chain with diamond ghost"\n\n[[example_figure_scores]]\nslug = "dataclasses"\nscore = 9.0\ncomment = "fields → generated __init__ signature"\n\n[[example_figure_scores]]\nslug = "decorators"\nscore = 9.0\ncomment = "before/after rebinding through cell"\n\n[[example_figure_scores]]\nslug = "special-methods"\nscore = 9.0\ncomment = "syntax → method dispatch"\n\n[[example_figure_scores]]\nslug = "unpacking"\nscore = 9.0\ncomment = "binding-line mechanism with *rest"\n\n[[example_figure_scores]]\nslug = "exception-chaining"\nscore = 9.0\ncomment = "__cause__ vs __context__ distinguished"\n\n[[example_figure_scores]]\nslug = "iterating-over-iterables"\nscore = 9.0\ncomment = "iter() exposes the iterator"\n\n[[example_figure_scores]]\nslug = "iterators"\nscore = 9.0\ncomment = "three-state machine"\n\n[[example_figure_scores]]\nslug = "iterator-vs-iterable"\nscore = 9.0\ncomment = "the protocol exposed"\n\n[[example_figure_scores]]\nslug = "container-protocols"\nscore = 9.0\ncomment = "syntax routes to __setitem__, __contains__, __getitem__"\n\n[[example_figure_scores]]\nslug = "operator-overloading"\nscore = 9.0\ncomment = "dispatch arrow"\n\n[[example_figure_scores]]\nslug = "union-and-optional-types"\nscore = 9.0\ncomment = "type fork to several shapes"\n\n[[example_figure_scores]]\nslug = "abstract-base-classes"\nscore = 9.0\ncomment = "same triangle as concrete classes"\n\n[[example_figure_scores]]\nslug = "conditionals"\nscore = 9.0\ncomment = "predicate forks value to branch"\n\n[[example_figure_scores]]\nslug = "match-statements"\nscore = 9.0\ncomment = "dispatch ladder; first match wins"\n\n[[example_figure_scores]]\nslug = "advanced-match-patterns"\nscore = 9.0\ncomment = "four pattern variants"\n\n[[example_figure_scores]]\nslug = "loop-else"\nscore = 9.0\ncomment = "fell-through vs broke, two outcomes"\n\n[[example_figure_scores]]\nslug = "while-loops"\nscore = 9.0\ncomment = "back-edge mechanism plus the three stopping rules"\n\n[[example_figure_scores]]\nslug = "type-aliases"\nscore = 9.0\ncomment = "complex annotation collapses to a name"\n\n[[example_figure_scores]]\nslug = "typed-dicts"\nscore = 9.0\ncomment = "keys with declared value types"\n\n[[example_figure_scores]]\nslug = "comprehension-patterns"\nscore = 9.0\ncomment = "nested clauses compose"\n\n[[example_figure_scores]]\nslug = "lambdas"\nscore = 9.0\ncomment = "function literal: params / expression"\n\n[[example_figure_scores]]\nslug = "string-formatting"\nscore = 9.0\ncomment = "format-spec railroad"\n\n[[example_figure_scores]]\nslug = "regular-expressions"\nscore = 9.0\ncomment = "pattern ruler with anchors"\n\n[[example_figure_scores]]\nslug = "json"\nscore = 9.0\ncomment = "two-column type mapping"\n\n[[example_figure_scores]]\nslug = "metaclasses"\nscore = 9.0\ncomment = "extended triangle to metaclass"\n\n[[example_figure_scores]]\nslug = "datetime"\nscore = 9.0\ncomment = "one instant, two clock offsets"\n\n[[example_figure_scores]]\nslug = "values"\nscore = 9.0\ncomment = "every literal is a typed object"\n\n[[example_figure_scores]]\nslug = "literals"\nscore = 9.0\ncomment = "literal spellings per type"\n\n[[example_figure_scores]]\nslug = "booleans"\nscore = 9.0\ncomment = "2×2 truth table"\n\n[[example_figure_scores]]\nslug = "sets"\nscore = 9.0\ncomment = "hash buckets without values"\n\n[[example_figure_scores]]\nslug = "yield-from"\nscore = 9.0\ncomment = "stitched ribbons; delegation"\n\n[[example_figure_scores]]\nslug = "generator-expressions"\nscore = 9.0\ncomment = "lazy filter→map pipeline"\n\n[[example_figure_scores]]\nslug = "async-iteration-and-context"\nscore = 9.0\ncomment = "loop/coro lanes with await yields"\n\n[[example_figure_scores]]\nslug = "assignment-expressions"\nscore = 9.0\ncomment = "walrus binds while comparing"\n\n[[example_figure_scores]]\nslug = "break-and-continue"\nscore = 9.0\ncomment = "early exit at first match"\n\n[[example_figure_scores]]\nslug = "delete-statements"\nscore = 9.0\ncomment = "name erased; object survives if referenced"\n\n[[example_figure_scores]]\nslug = "exception-groups"\nscore = 9.0\ncomment = "except* peels matching leaves"\n\n[[example_figure_scores]]\nslug = "custom-exceptions"\nscore = 9.0\ncomment = "subclass chain to a domain name"\n\n[[example_figure_scores]]\nslug = "modules"\nscore = 9.0\ncomment = "sys.path resolution; first hit wins"\n\n[[example_figure_scores]]\nslug = "protocols"\nscore = 9.0\ncomment = "structural duck check"\n\n[[example_figure_scores]]\nslug = "enums"\nscore = 9.0\ncomment = "closed set of symbolic values"\n\n[[example_figure_scores]]\nslug = "functions"\nscore = 9.0\ncomment = "specific call: greet(\'Ada\') → \'Hello, Ada\'"\n\n[[example_figure_scores]]\nslug = "constants"\nscore = 9.0\ncomment = "name binding; UPPER_CASE is convention"\n\n[[example_figure_scores]]\nslug = "import-aliases"\nscore = 9.0\ncomment = "two names bind to the same module"\n\n[[example_figure_scores]]\nslug = "number-parsing"\nscore = 9.0\ncomment = "int() success path vs ValueError"\n\n[[example_figure_scores]]\nslug = "tuples"\nscore = 9.0\ncomment = "frozen sequence with struck-through .append"\n\n[[example_figure_scores]]\nslug = "truthiness"\nscore = 9.0\ncomment = "bool(x) with the falsy set as a strip"\n\n[[example_figure_scores]]\nslug = "itertools"\nscore = 9.0\ncomment = "chain joins two iterables into one stream"\n\n[[example_figure_scores]]\nslug = "assertions"\nscore = 9.0\ncomment = "True passes, False raises"\n\n[[example_figure_scores]]\nslug = "descriptors"\nscore = 9.0\ncomment = "get/set/delete protocol routed through descriptor"\n\n[[example_figure_scores]]\nslug = "attribute-access"\nscore = 9.0\ncomment = "instance __dict__ → class __dict__ → __getattr__"\n\n[[example_figure_scores]]\nslug = "bound-and-unbound-methods"\nscore = 9.0\ncomment = "instance.method bound vs Class.method unbound"\n\n[[example_figure_scores]]\nslug = "classmethods-and-staticmethods"\nscore = 9.0\ncomment = "three method kinds, three first-arg conventions"\n\n[[example_figure_scores]]\nslug = "callable-objects"\nscore = 9.0\ncomment = "__call__ makes any object callable"\n\n[[example_figure_scores]]\nslug = "generics-and-typevar"\nscore = 9.0\ncomment = "the same T flows in and out"\n\n[[example_figure_scores]]\nslug = "truth-and-size"\nscore = 9.0\ncomment = "__bool__ → __len__ → True fallback chain"\n\n[[example_figure_scores]]\nslug = "bytes-and-bytearray"\nscore = 9.0\ncomment = "frozen vs mutable contrast"\n\n[[example_figure_scores]]\nslug = "sentinel-iteration"\nscore = 9.0\ncomment = "iter(callable, sentinel) stop condition"\n\n[[example_figure_scores]]\nslug = "partial-functions"\nscore = 9.0\ncomment = "f → partial(f, 1) → g"\n\n[[example_figure_scores]]\nslug = "guard-clauses"\nscore = 9.0\ncomment = "early returns, main body at the tail"\n\n[[example_figure_scores]]\nslug = "packages"\nscore = 9.0\ncomment = "__init__.py + nested submodules"\n\n[[example_figure_scores]]\nslug = "virtual-environments"\nscore = 9.0\ncomment = "project / venv boundary"\n\n[[example_figure_scores]]\nslug = "subprocesses"\nscore = 9.0\ncomment = "spawn → child → captured output"\n\n[[example_figure_scores]]\nslug = "logging"\nscore = 9.0\ncomment = "five thresholded levels"\n\n[[example_figure_scores]]\nslug = "testing"\nscore = 9.0\ncomment = "arrange-act-assert three-row pattern"\n\n[[example_figure_scores]]\nslug = "networking"\nscore = 9.0\ncomment = "text ↔ bytes across the socket boundary"\n\n[[example_figure_scores]]\nslug = "casts-and-any"\nscore = 9.0\ncomment = "Any → cast(T, x) → T, runtime unchanged"\n\n[[example_figure_scores]]\nslug = "newtype"\nscore = 9.0\ncomment = "same runtime, distinct static identity"\n\n[[example_figure_scores]]\nslug = "paramspec"\nscore = 9.0\ncomment = "P preserved through decorator"\n\n[[example_figure_scores]]\nslug = "literal-and-final"\nscore = 9.0\ncomment = "slot narrows to a fixed set"\n\n[[example_figure_scores]]\nslug = "runtime-type-checks"\nscore = 9.0\ncomment = "isinstance returns bool"\n\n[[example_figure_scores]]\nslug = "collections-module"\nscore = 9.0\ncomment = "deque / Counter / defaultdict / namedtuple"\n\n[[example_figure_scores]]\nslug = "structured-data-shapes"\nscore = 9.0\ncomment = "dataclass vs NamedTuple vs TypedDict runtime trade-offs"\n\n[[example_figure_scores]]\nslug = "csv-data"\nscore = 9.0\ncomment = "rows × columns; same shape per line"\n\n[[example_figure_scores]]\nslug = "warnings"\nscore = 9.0\ncomment = "soft signal; execution continues"\n\n[[example_figure_scores]]\nslug = "object-lifecycle"\nscore = 9.0\ncomment = "names keep object reachable until last reference disappears"\n\n[[example_figure_scores]]\nslug = "args-and-kwargs"\nscore = 9.0\ncomment = "*args tuple, **kwargs dict regions"\n\n[[example_figure_scores]]\nslug = "multiple-return-values"\nscore = 9.0\ncomment = "function returns tuple; caller unpacks"\n\n[[example_figure_scores]]\nslug = "properties"\nscore = 9.0\ncomment = "obj.x routes through fget instead of __dict__"\n\n[[example_figure_scores]]\nslug = "overloads"\nscore = 9.0\ncomment = "static overload signatures contrasted with one runtime body"\n\n[[example_figure_scores]]\nslug = "callable-types"\nscore = 9.0\ncomment = "callback slot shows argument list and return type"\n\n[[example_figure_scores]]\nslug = "threads-and-processes"\nscore = 9.0\ncomment = "thread GIL sharing contrasted with process isolation"\n\n# Curated scores for journey-section figures.\n[[journey_section_figure_scores]]\nsection = "Start with executable evidence."\nscore = 9.0\ncomment = "journey-native source/run/output evidence loop"\n\n[[journey_section_figure_scores]]\nsection = "Separate value, identity, and absence."\nscore = 9.0\ncomment = "three runtime questions separated"\n\n[[journey_section_figure_scores]]\nsection = "Read expressions as object operations."\nscore = 9.0\ncomment = "syntax enters the data-model method surface"\n\n[[journey_section_figure_scores]]\nsection = "Choose between paths."\nscore = 9.0\ncomment = "branch map independent of one if-statement example"\n\n[[journey_section_figure_scores]]\nsection = "Name and shape decisions."\nscore = 9.0\ncomment = "name-vs-shape decision map"\n\n[[journey_section_figure_scores]]\nsection = "Stop as soon as the answer is known."\nscore = 9.0\ncomment = "early boundary leaves tail unread"\n\n[[journey_section_figure_scores]]\nsection = "Choose the right loop shape."\nscore = 9.0\ncomment = "loop choice by stopping rule"\n\n[[journey_section_figure_scores]]\nsection = "See the protocol behind `for`."\nscore = 9.5\ncomment = "for surface mapped to iter()/next()/StopIteration"\n\n[[journey_section_figure_scores]]\nsection = "Compose lazy value streams."\nscore = 9.0\ncomment = "consumer pull drives the lazy pipeline"\n\n[[journey_section_figure_scores]]\nsection = "Pick the container that matches the question."\nscore = 9.0\ncomment = "list/tuple/dict/set per question"\n\n[[journey_section_figure_scores]]\nsection = "Move between shapes deliberately."\nscore = 9.0\ncomment = "input → transform → result"\n\n[[journey_section_figure_scores]]\nsection = "Cross text and data boundaries."\nscore = 9.0\ncomment = "text in, structured value out"\n\n[[journey_section_figure_scores]]\nsection = "Start with functions as named behavior."\nscore = 9.0\ncomment = "concrete call, named body, and return boundary"\n\n[[journey_section_figure_scores]]\nsection = "Use functions as values."\nscore = 9.0\ncomment = "second name binds same function"\n\n[[journey_section_figure_scores]]\nsection = "Bundle behavior with state."\nscore = 9.0\ncomment = "class groups state + methods"\n\n[[journey_section_figure_scores]]\nsection = "Keep runtime and static analysis separate."\nscore = 9.0\ncomment = "runtime track separated from static-tool track"\n\n[[journey_section_figure_scores]]\nsection = "Describe realistic data shapes."\nscore = 9.0\ncomment = "field, variant, and absence contracts in one map"\n\n[[journey_section_figure_scores]]\nsection = "Scale annotations for reusable libraries."\nscore = 9.0\ncomment = "caller contracts survive library API boundary"\n\n[[journey_section_figure_scores]]\nsection = "Make failure explicit."\nscore = 9.0\ncomment = "failure shapes mapped to explicit signals"\n\n[[journey_section_figure_scores]]\nsection = "Control resource and module boundaries."\nscore = 9.0\ncomment = "resource/module/env boundaries shown together"\n\n[[journey_section_figure_scores]]\nsection = "Handle operations that outlive one expression."\nscore = 9.0\ncomment = "operation boundary returns evidence later"\n\n# Curated example quality scores.\n[[example_quality_scores]]\nslug = "hello-world"\nscore = 7.1\ncomment = "waived: traditional smallest complete program"\n\n[[example_quality_scores]]\nslug = "values"\nscore = 9.0\ncomment = "object/type/operation map with prerequisite graph edges"\n\n[[example_quality_scores]]\nslug = "literals"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich, note-heavy, multi-cell"\n\n[[example_quality_scores]]\nslug = "numbers"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "booleans"\nscore = 9.0\ncomment = "truth operations, short-circuiting, bool-as-int footgun"\n\n[[example_quality_scores]]\nslug = "operators"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich, note-heavy, multi-cell"\n\n[[example_quality_scores]]\nslug = "none"\nscore = 9.0\ncomment = "None, mapping default, and exception absence boundaries"\n\n[[example_quality_scores]]\nslug = "variables"\nscore = 9.0\ncomment = "binding, rebinding, augmented-assignment, lifecycle graph"\n\n[[example_quality_scores]]\nslug = "constants"\nscore = 9.0\ncomment = "constant convention, Final boundary, runtime rebinding evidence"\n\n[[example_quality_scores]]\nslug = "truthiness"\nscore = 9.0\ncomment = "truth-value categories, explicit-comparison boundary, graph-linked"\n\n[[example_quality_scores]]\nslug = "equality-and-identity"\nscore = 9.0\ncomment = "== versus is, singleton check, identity-cache warning"\n\n[[example_quality_scores]]\nslug = "mutability"\nscore = 9.0\ncomment = "in-place mutation, rebinding, alias visibility"\n\n[[example_quality_scores]]\nslug = "object-lifecycle"\nscore = 9.0\ncomment = "references, rebinding, and last-reference boundary"\n\n[[example_quality_scores]]\nslug = "strings"\nscore = 9.0\ncomment = "Unicode text, immutability, encoding boundary"\n\n[[example_quality_scores]]\nslug = "bytes-and-bytearray"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "string-formatting"\nscore = 9.0\ncomment = "f-string expression, format spec, debug/repr boundary"\n\n[[example_quality_scores]]\nslug = "conditionals"\nscore = 9.0\ncomment = "branch ordering, truthiness, and ternary boundary"\n\n[[example_quality_scores]]\nslug = "guard-clauses"\nscore = 9.0\ncomment = "nested contrast plus flattened guard path"\n\n[[example_quality_scores]]\nslug = "assignment-expressions"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "for-loops"\nscore = 9.0\ncomment = "direct iteration, range, and enumerate progression"\n\n[[example_quality_scores]]\nslug = "break-and-continue"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "loop-else"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "iterating-over-iterables"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "iterators"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "iterator-vs-iterable"\nscore = 9.0\ncomment = "graph-rich"\n\n[[example_quality_scores]]\nslug = "sentinel-iteration"\nscore = 9.0\ncomment = "call-until-marker shape contrasted with manual while"\n\n[[example_quality_scores]]\nslug = "match-statements"\nscore = 9.0\ncomment = "shape dispatch and guarded pattern payoff"\n\n[[example_quality_scores]]\nslug = "advanced-match-patterns"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "while-loops"\nscore = 9.0\ncomment = "state-driven repetition contrasted with for loops"\n\n[[example_quality_scores]]\nslug = "lists"\nscore = 9.0\ncomment = "ordered mutable sequence operations plus tuple/set/copy boundaries"\n\n[[example_quality_scores]]\nslug = "tuples"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "unpacking"\nscore = 9.0\ncomment = "sequence, starred, and mapping unpacking shapes"\n\n[[example_quality_scores]]\nslug = "dicts"\nscore = 9.0\ncomment = "lookup/default/update/delete dictionary boundaries"\n\n[[example_quality_scores]]\nslug = "sets"\nscore = 9.0\ncomment = "uniqueness, membership, set algebra, ordering boundary"\n\n[[example_quality_scores]]\nslug = "slices"\nscore = 9.0\ncomment = "range bounds, step, reverse, and unchanged-source evidence"\n\n[[example_quality_scores]]\nslug = "comprehensions"\nscore = 9.0\ncomment = "loop-equivalent collection building with filter/projection boundaries"\n\n[[example_quality_scores]]\nslug = "comprehension-patterns"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "sorting"\nscore = 9.0\ncomment = "key functions, reverse order, sorted/list.sort boundary"\n\n[[example_quality_scores]]\nslug = "collections-module"\nscore = 9.0\ncomment = "Counter, defaultdict, deque, namedtuple mapped to data shapes"\n\n[[example_quality_scores]]\nslug = "copying-collections"\nscore = 9.0\ncomment = "alias, shallow copy, and deepcopy boundaries"\n\n[[example_quality_scores]]\nslug = "functions"\nscore = 9.0\ncomment = "definition/call/return/default-boundary walkthrough"\n\n[[example_quality_scores]]\nslug = "keyword-only-arguments"\nscore = 9.0\ncomment = "call-site readability and boolean flag boundary"\n\n[[example_quality_scores]]\nslug = "positional-only-parameters"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "args-and-kwargs"\nscore = 9.0\ncomment = "flexible argument collection plus forwarding/API-boundary graph"\n\n[[example_quality_scores]]\nslug = "multiple-return-values"\nscore = 9.0\ncomment = "tuple return plus unpacking boundary"\n\n[[example_quality_scores]]\nslug = "closures"\nscore = 9.0\ncomment = "closed-over state, factory payoff, late-binding boundary"\n\n[[example_quality_scores]]\nslug = "partial-functions"\nscore = 9.0\ncomment = "before/after callable adaptation and introspection"\n\n[[example_quality_scores]]\nslug = "scope-global-nonlocal"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "recursion"\nscore = 9.0\ncomment = "base case and recursive tree-shape payoff"\n\n[[example_quality_scores]]\nslug = "lambdas"\nscore = 9.0\ncomment = "expression callable use contrasted with named def reuse"\n\n[[example_quality_scores]]\nslug = "generators"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "yield-from"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "generator-expressions"\nscore = 9.0\ncomment = "eager list contrast, lazy one-pass consumption, reducer use"\n\n[[example_quality_scores]]\nslug = "itertools"\nscore = 9.0\ncomment = "lazy standard-library pipeline and iterator-composition payoff"\n\n[[example_quality_scores]]\nslug = "decorators"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "classes"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "inheritance-and-super"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "classmethods-and-staticmethods"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "dataclasses"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "properties"\nscore = 9.0\ncomment = "managed attribute API without caller syntax change"\n\n[[example_quality_scores]]\nslug = "special-methods"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich, note-heavy, multi-cell"\n\n[[example_quality_scores]]\nslug = "truth-and-size"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "container-protocols"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "callable-objects"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "operator-overloading"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "attribute-access"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "bound-and-unbound-methods"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "descriptors"\nscore = 9.0\ncomment = "descriptor lookup, __set_name__, and validation mechanics"\n\n[[example_quality_scores]]\nslug = "metaclasses"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "context-managers"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "delete-statements"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "exceptions"\nscore = 9.0\ncomment = "specific handling, else/finally cleanup, broad-catch boundary"\n\n[[example_quality_scores]]\nslug = "assertions"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "exception-chaining"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "exception-groups"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "warnings"\nscore = 9.0\ncomment = "capture and escalate soft failures with filters"\n\n[[example_quality_scores]]\nslug = "modules"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "import-aliases"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "packages"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "virtual-environments"\nscore = 9.0\ncomment = "standard venv workflow plus explicit Worker deployment boundary"\n\n[[example_quality_scores]]\nslug = "type-hints"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich, note-heavy, multi-cell"\n\n[[example_quality_scores]]\nslug = "runtime-type-checks"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "union-and-optional-types"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "type-aliases"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "typed-dicts"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "structured-data-shapes"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "literal-and-final"\nscore = 9.0\ncomment = "Literal value set, Final rebinding promise, runtime caveat"\n\n[[example_quality_scores]]\nslug = "callable-types"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "generics-and-typevar"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "paramspec"\nscore = 9.0\ncomment = "Callable erasure contrasted with ParamSpec-preserving decorator"\n\n[[example_quality_scores]]\nslug = "overloads"\nscore = 9.0\ncomment = "static overload stubs contrasted with one runtime implementation"\n\n[[example_quality_scores]]\nslug = "casts-and-any"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "newtype"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "protocols"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "abstract-base-classes"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "enums"\nscore = 9.0\ncomment = "closed symbolic choice set contrasted with raw strings"\n\n[[example_quality_scores]]\nslug = "regular-expressions"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich, note-heavy, multi-cell"\n\n[[example_quality_scores]]\nslug = "number-parsing"\nscore = 9.0\ncomment = "decimal, explicit base, and recoverable ValueError boundary"\n\n[[example_quality_scores]]\nslug = "custom-exceptions"\nscore = 9.0\ncomment = "domain error naming, raise point, recovery boundary"\n\n[[example_quality_scores]]\nslug = "json"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "logging"\nscore = 9.0\ncomment = "logger, handler, formatter, and threshold boundaries"\n\n[[example_quality_scores]]\nslug = "testing"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "subprocesses"\nscore = 9.0\ncomment = "standard subprocess contract plus explicit Worker boundary"\n\n[[example_quality_scores]]\nslug = "threads-and-processes"\nscore = 9.0\ncomment = "thread/process executor contrast plus explicit Worker boundary"\n\n[[example_quality_scores]]\nslug = "networking"\nscore = 9.0\ncomment = "protocol byte boundary plus explicit socket runtime caveat"\n\n[[example_quality_scores]]\nslug = "datetime"\nscore = 9.0\ncomment = "date/time/delta parsing with timezone scope made explicit"\n\n[[example_quality_scores]]\nslug = "csv-data"\nscore = 9.0\ncomment = "DictReader, conversion, and DictWriter row boundary"\n\n[[example_quality_scores]]\nslug = "async-await"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "async-iteration-and-context"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n' +REGISTRY_TOML = '# Quality and editorial registries.\n#\n# Source of truth for rubric checks, journey structure, edge labels,\n# figure attachments/captions, and curated scores. Executable paint\n# functions stay in `src/marginalia.py`; mutable editorial data lives here.\n\n[[confusable_pairs]]\nname = "__str__ vs __repr__"\nowner = "special-methods"\ntokens = ["__str__", "__repr__"]\n\n[[confusable_pairs]]\nname = "is vs =="\nowner = "equality-and-identity"\ntokens = [" is ", "=="]\n\n[[confusable_pairs]]\nname = "list vs tuple"\nowner = "tuples"\ntokens = ["list", "tuple"]\n\n[[confusable_pairs]]\nname = "classmethod vs staticmethod vs instance method"\nowner = "classmethods-and-staticmethods"\ntokens = ["@classmethod", "@staticmethod", "self"]\n\n[[confusable_pairs]]\nname = "isinstance vs type=="\nowner = "runtime-type-checks"\ntokens = ["isinstance(", "type("]\n\n[[confusable_pairs]]\nname = "generator vs class iterator"\nowner = "generators"\ntokens = ["yield", "__next__"]\n\n[[confusable_pairs]]\nname = "iterator vs iterable"\nowner = "iterator-vs-iterable"\ntokens = ["iterable", "iterator", "iter("]\n\n[[confusable_pairs]]\nname = "mutable vs immutable class attributes"\nowner = "classes"\ntokens = ["class attribute", "__init__"]\n\n[[confusable_pairs]]\nname = "eager vs lazy production"\nowner = "generators"\ntokens = ["return", "yield"]\n\n[[confusable_pairs]]\nname = "Protocol vs ABC"\nowner = "abstract-base-classes"\ntokens = ["Protocol", "ABC"]\n\n[[confusable_pairs]]\nname = "dataclass vs NamedTuple vs TypedDict"\nowner = "structured-data-shapes"\ntokens = ["@dataclass", "NamedTuple", "TypedDict"]\n\n[[confusable_pairs]]\nname = "bound vs unbound methods"\nowner = "bound-and-unbound-methods"\ntokens = ["bound method", "Class.method"]\n\n[[confusable_pairs]]\nname = "yield vs return"\nowner = "generators"\ntokens = ["yield", "return"]\n\n[[confusable_pairs]]\nname = "shallow vs deep copy"\nowner = "copying-collections"\ntokens = ["copy(", "deepcopy("]\n\n[[confusable_pairs]]\nname = "sync vs async functions"\nowner = "async-await"\ntokens = ["async def", "def "]\n\n[broad_surface_tours]\n\n[broad_surface_tours.special-methods]\nrequired_tokens = [\n "__init__", "__repr__", "__str__", "__eq__", "__hash__", "__lt__",\n "__len__", "__iter__", "__contains__", "__getitem__", "__setitem__",\n "__call__", "__enter__", "__exit__", "__bool__",\n]\n\n[broad_surface_tours.operators]\nrequired_tokens = ["+", "==", " is ", " in ", "and", "or", "&", ":="]\n\n[broad_surface_tours.type-hints]\nrequired_tokens = ["list[", " | ", "Optional", "TypeAlias"]\n\n[broad_surface_tours.testing]\nrequired_tokens = ["TestCase", "assertEqual", "assertRaises", "setUp"]\n\n[broad_surface_tours.async-await]\nrequired_tokens = ["async def", "await", "asyncio.run", "asyncio.gather", "async for", "async with"]\n\n[broad_surface_tours.packages]\nrequired_tokens = ["__init__.py", "from .", "__all__"]\n\n[broad_surface_tours.regular-expressions]\nrequired_tokens = ["re.match", "re.search", "re.findall", "re.compile", "re.IGNORECASE", "re.sub"]\n\n[broad_surface_tours.literals]\nrequired_tokens = ["0x", "0b", "_", "f\\"", "True", "None"]\n\n[[footguns]]\nname = "Mutable default class attribute"\nowner = "classes"\nbroken_tokens = ["items = []"]\nfixed_tokens = ["self.items = []"]\n\n[[footguns]]\nname = "Mutable default function argument"\nowner = "functions"\nbroken_tokens = ["items=[]", "append_broken"]\nfixed_tokens = ["items=None", "append_fixed"]\n\n[[footguns]]\nname = "Late-binding closure in a loop"\nowner = "closures"\nbroken_tokens = ["lambda: i", "[2, 2, 2]"]\nfixed_tokens = ["lambda i=i", "[0, 1, 2]"]\n\n[[footguns]]\nname = "Integer identity caching"\nowner = "equality-and-identity"\nbroken_tokens = ["small_a is small_b", "big_a is big_b"]\nfixed_tokens = ["big_a == big_b", "small-integer cache"]\n\n[[footguns]]\nname = "Shallow vs deep copy"\nowner = "copying-collections"\nbroken_tokens = ["copy("]\nfixed_tokens = ["deepcopy("]\n\n[[footguns]]\nname = "Generator one-pass exhaustion"\nowner = "generators"\nbroken_tokens = ["yield"]\nfixed_tokens = ["list("]\n\n[[footguns]]\nname = "Dictionary mutation during iteration"\nowner = "dicts"\nbroken_tokens = ["for ", "del "]\nfixed_tokens = ["list(", ".keys("]\n\n[[footguns]]\nname = "Floating-point equality"\nowner = "numbers"\nbroken_tokens = ["0.1", "0.2"]\nfixed_tokens = ["isclose", "math"]\n\n[[footguns]]\nname = "bool as a subclass of int"\nowner = "booleans"\nbroken_tokens = ["isinstance(True, int)", "True + True"]\nfixed_tokens = ["not isinstance(value, bool)", "is_strict_int"]\n\n[[footguns]]\nname = "Bare except swallowing KeyboardInterrupt"\nowner = "exceptions"\nbroken_tokens = ["except Exception"]\nfixed_tokens = ["except ValueError"]\n\n[paired_pages]\n# Pages whose titles differ only by a suffix or modifier; at least one\n# member of each pair must demonstrate the relationship in a cell.\npairs = [\n ["iterators", "iterating-over-iterables"],\n ["iterators", "iterator-vs-iterable"],\n ["generators", "generator-expressions"],\n ["comprehensions", "comprehension-patterns"],\n]\n\n[paired_pages.cell_tokens]\n"iterators|iterating-over-iterables" = ["iterable", "iterator"]\n"iterators|iterator-vs-iterable" = ["iterable", "iterator", "iter("]\n"generators|generator-expressions" = ["generator", "expression"]\n"comprehensions|comprehension-patterns" = ["nested loops", "explicit loop"]\n\n[quality_gates]\nexample_target = 9.0\nexample_hard_min = 8.5\njourney_section_min = 8.5\njourney_average_min = 8.8\n\n[quality_waivers.hello-world]\naccepted_min = 7.0\nreason = "Traditional first example is intentionally tiny: program -> output is the goal, not a full concept tour."\nexpires = "2026-12-01"\n\n[no_figure_rationales]\n# Slugs may be added here only when the page is constraint-shaped,\n# infrastructure-shaped, or aggregator-shaped and a single mechanism\n# figure would distort the lesson. A weak figure is redrawn, not removed:\n# a page that had a figure keeps one. Current production attaches\n# figures to every example, so this registry is intentionally empty.\n\n[journey_outcomes]\n\n[journey_outcomes."runtime::Start with executable evidence."]\njourney = "runtime"\nsection = "Start with executable evidence."\nsupport = [\n "hello-world",\n "values",\n "literals",\n "variables",\n "constants",\n]\noutcomes = [\n "start with a complete program and its output",\n "see that Python programs manipulate runtime objects",\n "write small values directly in source code",\n "understand that names bind to objects rather than storing values themselves",\n]\n\n[journey_outcomes."runtime::Separate value, identity, and absence."]\njourney = "runtime"\nsection = "Separate value, identity, and absence."\nsupport = [\n "none",\n "booleans",\n "truthiness",\n "equality-and-identity",\n "mutability",\n "object-lifecycle",\n]\noutcomes = [\n "represent expected absence with a singleton object",\n "combine facts with boolean operators",\n "predict how objects behave in boolean contexts",\n "distinguish value equality from object identity",\n]\n\n[journey_outcomes."runtime::Read expressions as object operations."]\njourney = "runtime"\nsection = "Read expressions as object operations."\nsupport = [\n "numbers",\n "operators",\n "strings",\n "string-formatting",\n "bytes-and-bytearray",\n]\noutcomes = [\n "use numeric objects and arithmetic operators",\n "combine, compare, and test values with expression syntax",\n "treat text as Unicode rather than raw bytes",\n "turn objects into readable text at output boundaries",\n]\n\n[journey_outcomes."control-flow::Choose between paths."]\njourney = "control-flow"\nsection = "Choose between paths."\nsupport = [\n "booleans",\n "truthiness",\n "operators",\n "conditionals",\n]\noutcomes = [\n "combine facts into readable conditions",\n "use object truth values without hiding intent",\n "build comparisons and boolean expressions for conditions",\n "choose between branches with clear predicates",\n]\n\n[journey_outcomes."control-flow::Name and shape decisions."]\njourney = "control-flow"\nsection = "Name and shape decisions."\nsupport = [\n "assignment-expressions",\n "match-statements",\n "advanced-match-patterns",\n]\noutcomes = [\n "name an intermediate value inside a condition when it improves clarity",\n "dispatch on the shape of data rather than only on boolean tests",\n "combine destructuring, alternatives, and guards in pattern matching",\n]\n\n[journey_outcomes."control-flow::Stop as soon as the answer is known."]\njourney = "control-flow"\nsection = "Stop as soon as the answer is known."\nsupport = [\n "guard-clauses",\n "assertions",\n "exceptions",\n]\noutcomes = [\n "show how early returns reduce nested conditional code",\n "state assumptions that should fail loudly while developing",\n "leave the current path when ordinary return values are not enough",\n]\n\n[journey_outcomes."iteration::Choose the right loop shape."]\njourney = "iteration"\nsection = "Choose the right loop shape."\nsupport = [\n "for-loops",\n "while-loops",\n "break-and-continue",\n "loop-else",\n "sentinel-iteration",\n]\noutcomes = [\n "consume values from an iterable",\n "repeat while a condition must be rechecked",\n "interrupt or skip loop work intentionally",\n "attach completion logic to loops that did not break",\n]\n\n[journey_outcomes."iteration::See the protocol behind `for`."]\njourney = "iteration"\nsection = "See the protocol behind `for`."\nsupport = [\n "iterating-over-iterables",\n "iterator-vs-iterable",\n "iterators",\n "generators",\n]\noutcomes = [\n "separate value producers from value consumers",\n "use `iter()` and `next()` to expose the protocol behind `for`",\n "write functions that produce values lazily",\n]\n\n[journey_outcomes."iteration::Compose lazy value streams."]\njourney = "iteration"\nsection = "Compose lazy value streams."\nsupport = [\n "generator-expressions",\n "itertools",\n "yield-from",\n]\noutcomes = [\n "create lazy one-pass streams with expression syntax",\n "compose iterator streams without materializing every value",\n "delegate part of a generator to another iterable",\n]\n\n[journey_outcomes."shapes::Pick the container that matches the question."]\njourney = "shapes"\nsection = "Pick the container that matches the question."\nsupport = [\n "lists",\n "tuples",\n "dicts",\n "sets",\n "collections-module",\n]\noutcomes = [\n "store ordered mutable data",\n "group fixed-position values",\n "look up values by key",\n "model uniqueness and membership",\n]\n\n[journey_outcomes."shapes::Move between shapes deliberately."]\njourney = "shapes"\nsection = "Move between shapes deliberately."\nsupport = [\n "unpacking",\n "slices",\n "comprehensions",\n "comprehension-patterns",\n "sorting",\n "copying-collections",\n]\noutcomes = [\n "bind names from structured values",\n "select ranges from sequences",\n "build concrete collections from compact loops",\n "compose filters and nested transformations",\n]\n\n[journey_outcomes."shapes::Cross text and data boundaries."]\njourney = "shapes"\nsection = "Cross text and data boundaries."\nsupport = [\n "number-parsing",\n "json",\n "regular-expressions",\n "datetime",\n "csv-data",\n]\noutcomes = [\n "turn text into numbers safely",\n "move structured data across a text boundary",\n "extract structure from text patterns",\n "represent dates, times, and durations as typed values",\n]\n\n[journey_outcomes."interfaces::Start with functions as named behavior."]\njourney = "interfaces"\nsection = "Start with functions as named behavior."\nsupport = [\n "functions",\n "keyword-only-arguments",\n "positional-only-parameters",\n "args-and-kwargs",\n "multiple-return-values",\n]\noutcomes = [\n "package behavior behind a name",\n "make important call-site choices explicit",\n "hide parameter names that should remain implementation details",\n "accept flexible call shapes when forwarding or adapting APIs",\n]\n\n[journey_outcomes."interfaces::Use functions as values."]\njourney = "interfaces"\nsection = "Use functions as values."\nsupport = [\n "scope-global-nonlocal",\n "closures",\n "recursion",\n "lambdas",\n "decorators",\n "partial-functions",\n]\noutcomes = [\n "control where assignment happens",\n "capture state in nested functions",\n "solve self-similar problems with a base case",\n "write small unnamed functions for expression positions",\n]\n\n[journey_outcomes."interfaces::Bundle behavior with state."]\njourney = "interfaces"\nsection = "Bundle behavior with state."\nsupport = [\n "classes",\n "inheritance-and-super",\n "dataclasses",\n "properties",\n "classmethods-and-staticmethods",\n "special-methods",\n "truth-and-size",\n "container-protocols",\n "callable-objects",\n "operator-overloading",\n "attribute-access",\n "bound-and-unbound-methods",\n "descriptors",\n "metaclasses",\n]\noutcomes = [\n "bundle state and behavior into a new object type",\n "reuse and extend behavior through parent classes",\n "generate common methods for data containers",\n "keep attribute syntax while adding computation or validation",\n]\n\n[journey_outcomes."types::Keep runtime and static analysis separate."]\njourney = "types"\nsection = "Keep runtime and static analysis separate."\nsupport = [\n "type-hints",\n "protocols",\n "abstract-base-classes",\n "enums",\n "runtime-type-checks",\n]\noutcomes = [\n "document expected types and feed type checkers",\n "describe required behavior by structural shape",\n "name a fixed set of symbolic values",\n "show `type()`, `isinstance()`, and `issubclass()` without turning Python into Java",\n]\n\n[journey_outcomes."types::Describe realistic data shapes."]\njourney = "types"\nsection = "Describe realistic data shapes."\nsupport = [\n "union-and-optional-types",\n "type-aliases",\n "typed-dicts",\n "structured-data-shapes",\n "literal-and-final",\n "callable-types",\n]\noutcomes = [\n "show `X | Y` and `None`-aware APIs",\n "name complex types with `type` statements or aliases",\n "type dictionary records that come from JSON",\n "express constrained values and names that should not be rebound",\n]\n\n[journey_outcomes."types::Scale annotations for reusable libraries."]\njourney = "types"\nsection = "Scale annotations for reusable libraries."\nsupport = [\n "generics-and-typevar",\n "paramspec",\n "overloads",\n "casts-and-any",\n "newtype",\n]\noutcomes = [\n "write reusable typed containers and functions",\n "preserve callable signatures through decorators",\n "describe APIs whose return type depends on the input shape",\n "show escape hatches and their tradeoffs",\n]\n\n[journey_outcomes."reliability::Make failure explicit."]\njourney = "reliability"\nsection = "Make failure explicit."\nsupport = [\n "exceptions",\n "assertions",\n "exception-chaining",\n "exception-groups",\n "custom-exceptions",\n "warnings",\n]\noutcomes = [\n "signal and recover from errors",\n "state internal assumptions",\n "preserve the cause while translating an error",\n "handle multiple failures together",\n]\n\n[journey_outcomes."reliability::Control resource and module boundaries."]\njourney = "reliability"\nsection = "Control resource and module boundaries."\nsupport = [\n "context-managers",\n "delete-statements",\n "modules",\n "import-aliases",\n "packages",\n "virtual-environments",\n]\noutcomes = [\n "pair setup with reliable cleanup",\n "remove names, attributes, and items intentionally",\n "split code into importable files",\n "make imported names clear at use sites",\n]\n\n[journey_outcomes."reliability::Handle operations that outlive one expression."]\njourney = "reliability"\nsection = "Handle operations that outlive one expression."\nsupport = [\n "async-await",\n "async-iteration-and-context",\n "logging",\n "testing",\n "subprocesses",\n "threads-and-processes",\n "networking",\n]\noutcomes = [\n "await concurrent I/O-shaped work",\n "consume async streams and cleanup protocols",\n "record operational events without using `print()`",\n "write deterministic tests with `unittest` or `pytest`",\n]\n\n[score_model]\n# Criterion weights mirror docs/example-quality-rubric.md. The registry\n# makes the scoring model inspectable even before every example is broken\n# down into per-criterion subscores.\nconceptual_payoff = 1.0\nrationale = 0.75\nalternatives_and_boundaries = 0.75\nexecutable_determinism = 1.0\npython_idiom_and_accuracy = 1.0\nliterate_fit = 0.75\nsource_result_pairing = 0.75\nconcept_decomposition = 0.75\nprogressive_walkthrough = 0.75\nrepresentative_coverage = 0.75\npractical_usefulness = 0.75\neditorial_progression = 1.0\n\n# Editorial registries loaded by the site at runtime.\n# Keep prose, captions, scores, journey ordering, and edge labels here;\n# keep executable paint functions in src/marginalia.py.\n\n[[journeys]]\nslug = "runtime"\ntitle = "Runtime"\nsummary = "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."\n\n[[journeys.sections]]\ntitle = "Start with executable evidence."\nsummary = "Learners first need to see that every page is a runnable program with visible output."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "hello-world"\ndescription = "start with a complete program and its output"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "values"\ndescription = "see that Python programs manipulate runtime objects"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "literals"\ndescription = "write small values directly in source code"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "variables"\ndescription = "understand that names bind to objects rather than storing values themselves"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "constants"\ndescription = "learn the convention Python uses for values that should not change"\n\n[[journeys.sections]]\ntitle = "Separate value, identity, and absence."\nsummary = "This section prevents early confusion about equality, object identity, missing values, and truth tests."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "none"\ndescription = "represent expected absence with a singleton object"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "booleans"\ndescription = "combine facts with boolean operators"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "truthiness"\ndescription = "predict how objects behave in boolean contexts"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "equality-and-identity"\ndescription = "distinguish value equality from object identity"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "mutability"\ndescription = "predict when operations change an object in place"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "object-lifecycle"\ndescription = "explain references, garbage collection, and why identity can outlive a single name"\n\n[[journeys.sections]]\ntitle = "Read expressions as object operations."\nsummary = "This section connects operators, text, and formatting to Python\'s data model."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "numbers"\ndescription = "use numeric objects and arithmetic operators"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "operators"\ndescription = "combine, compare, and test values with expression syntax"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "strings"\ndescription = "treat text as Unicode rather than raw bytes"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "string-formatting"\ndescription = "turn objects into readable text at output boundaries"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "bytes-and-bytearray"\ndescription = "contrast text with binary data and explicit decoding"\n\n[[journeys]]\nslug = "control-flow"\ntitle = "Control Flow"\nsummary = "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."\n\n[[journeys.sections]]\ntitle = "Choose between paths."\nsummary = "Start with ordinary branching and boolean predicates before reaching for more compact forms."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "booleans"\ndescription = "combine facts into readable conditions"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "truthiness"\ndescription = "use object truth values without hiding intent"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "operators"\ndescription = "build comparisons and boolean expressions for conditions"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "conditionals"\ndescription = "choose between branches with clear predicates"\n\n[[journeys.sections]]\ntitle = "Name and shape decisions."\nsummary = "Some branches become clearer when the code names an intermediate value or dispatches on data shape."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "assignment-expressions"\ndescription = "name an intermediate value inside a condition when it improves clarity"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "match-statements"\ndescription = "dispatch on the shape of data rather than only on boolean tests"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "advanced-match-patterns"\ndescription = "combine destructuring, alternatives, and guards in pattern matching"\n\n[[journeys.sections]]\ntitle = "Stop as soon as the answer is known."\nsummary = "Early exits make the successful path easier to read by moving exceptional or completed cases out of the way."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "guard-clauses"\ndescription = "show how early returns reduce nested conditional code"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "assertions"\ndescription = "state assumptions that should fail loudly while developing"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "exceptions"\ndescription = "leave the current path when ordinary return values are not enough"\n\n[[journeys]]\nslug = "iteration"\ntitle = "Iteration"\nsummary = "This journey follows repeated work from ordinary loops to the iterator protocol: consume values, stop deliberately, and produce lazy streams only when they help."\n\n[[journeys.sections]]\ntitle = "Choose the right loop shape."\nsummary = "Loops differ by what they consume, when they stop, and whether completion itself carries meaning."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "for-loops"\ndescription = "consume values from an iterable"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "while-loops"\ndescription = "repeat while a condition must be rechecked"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "break-and-continue"\ndescription = "interrupt or skip loop work intentionally"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "loop-else"\ndescription = "attach completion logic to loops that did not break"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "sentinel-iteration"\ndescription = "show `iter(callable, sentinel)` for repeated reads until a marker appears"\n\n[[journeys.sections]]\ntitle = "See the protocol behind `for`."\nsummary = "The important mental shift is that loops consume producers through a protocol rather than special-casing lists."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "iterating-over-iterables"\ndescription = "separate value producers from value consumers"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "iterator-vs-iterable"\ndescription = "distinguish re-iterable collections from one-pass iterators"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "iterators"\ndescription = "use `iter()` and `next()` to expose the protocol behind `for`"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "generators"\ndescription = "write functions that produce values lazily"\n\n[[journeys.sections]]\ntitle = "Compose lazy value streams."\nsummary = "Iterator pipelines are useful when code can transform values one at a time instead of materializing every intermediate result."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "generator-expressions"\ndescription = "create lazy one-pass streams with expression syntax"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "itertools"\ndescription = "compose iterator streams without materializing every value"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "yield-from"\ndescription = "delegate part of a generator to another iterable"\n\n[[journeys]]\nslug = "shapes"\ntitle = "Shapes"\nsummary = "This journey teaches the core Python habit of choosing a data shape, transforming it directly, and making the result visible."\n\n[[journeys.sections]]\ntitle = "Pick the container that matches the question."\nsummary = "Lists, tuples, dictionaries, and sets answer different questions about order, position, lookup, and uniqueness."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "lists"\ndescription = "store ordered mutable data"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "tuples"\ndescription = "group fixed-position values"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "dicts"\ndescription = "look up values by key"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "sets"\ndescription = "model uniqueness and membership"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "collections-module"\ndescription = "show `deque`, `Counter`, `defaultdict`, and `namedtuple` as specialized shapes"\n\n[[journeys.sections]]\ntitle = "Move between shapes deliberately."\nsummary = "Most everyday Python code is data reshaping, so learners need the idioms for selecting, unpacking, and rebuilding values."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "unpacking"\ndescription = "bind names from structured values"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "slices"\ndescription = "select ranges from sequences"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "comprehensions"\ndescription = "build concrete collections from compact loops"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "comprehension-patterns"\ndescription = "compose filters and nested transformations"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "sorting"\ndescription = "order records with key functions"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "copying-collections"\ndescription = "contrast shallow copies, deep copies, and shared nested data"\n\n[[journeys.sections]]\ntitle = "Cross text and data boundaries."\nsummary = "Programs often receive text and produce structured data, so parsing and serialization belong in the data journey."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "number-parsing"\ndescription = "turn text into numbers safely"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "json"\ndescription = "move structured data across a text boundary"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "regular-expressions"\ndescription = "extract structure from text patterns"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "datetime"\ndescription = "represent dates, times, and durations as typed values"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "csv-data"\ndescription = "show row-shaped text data and dictionary records"\n\n[[journeys]]\nslug = "interfaces"\ntitle = "Interfaces"\nsummary = "This journey shows how Python grows from simple functions to callable APIs, object interfaces, protocols, and metaclasses."\n\n[[journeys.sections]]\ntitle = "Start with functions as named behavior."\nsummary = "Functions are the first abstraction boundary because they name behavior and control how callers provide information."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "functions"\ndescription = "package behavior behind a name"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "keyword-only-arguments"\ndescription = "make important call-site choices explicit"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "positional-only-parameters"\ndescription = "hide parameter names that should remain implementation details"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "args-and-kwargs"\ndescription = "accept flexible call shapes when forwarding or adapting APIs"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "multiple-return-values"\ndescription = "return multiple related values as a tuple"\n\n[[journeys.sections]]\ntitle = "Use functions as values."\nsummary = "Python functions can capture state, be passed around, and wrap other functions."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "scope-global-nonlocal"\ndescription = "control where assignment happens"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "closures"\ndescription = "capture state in nested functions"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "recursion"\ndescription = "solve self-similar problems with a base case"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "lambdas"\ndescription = "write small unnamed functions for expression positions"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "decorators"\ndescription = "wrap behavior without changing call sites"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "partial-functions"\ndescription = "show how to pre-fill arguments with `functools.partial`"\n\n[[journeys.sections]]\ntitle = "Bundle behavior with state."\nsummary = "Classes become useful when data and behavior need to move together behind a stable interface."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "classes"\ndescription = "bundle state and behavior into a new object type"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "inheritance-and-super"\ndescription = "reuse and extend behavior through parent classes"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "dataclasses"\ndescription = "generate common methods for data containers"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "properties"\ndescription = "keep attribute syntax while adding computation or validation"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "classmethods-and-staticmethods"\ndescription = "choose between instance, class, and static behavior on a class"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "special-methods"\ndescription = "connect objects to Python syntax and built-ins"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "truth-and-size"\ndescription = "make objects work with truth tests and `len()`"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "container-protocols"\ndescription = "support membership, lookup, and assignment syntax"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "callable-objects"\ndescription = "make stateful instances callable like functions"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "operator-overloading"\ndescription = "define operators only when the operation is unsurprising"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "attribute-access"\ndescription = "customize fallback lookup and assignment carefully"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "bound-and-unbound-methods"\ndescription = "explain how attribute access turns a function into a bound method"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "descriptors"\ndescription = "explain the protocol behind methods, properties, and managed attributes"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "metaclasses"\ndescription = "customize class creation when ordinary class tools are not enough"\n\n[[journeys]]\nslug = "types"\ntitle = "Types"\nsummary = "This journey maps Python\'s runtime object model to optional static annotations so learners know what types can and cannot promise."\n\n[[journeys.sections]]\ntitle = "Keep runtime and static analysis separate."\nsummary = "The first lesson is that annotations describe expectations for tools while ordinary Python objects still run the program."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "type-hints"\ndescription = "document expected types and feed type checkers"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "protocols"\ndescription = "describe required behavior by structural shape"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "abstract-base-classes"\ndescription = "contrast runtime-enforced nominal interfaces with structural protocols"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "enums"\ndescription = "name a fixed set of symbolic values"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "runtime-type-checks"\ndescription = "show `type()`, `isinstance()`, and `issubclass()` without turning Python into Java"\n\n[[journeys.sections]]\ntitle = "Describe realistic data shapes."\nsummary = "Typed Python becomes useful when annotations explain optional values, unions, callables, and JSON-like records."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "union-and-optional-types"\ndescription = "show `X | Y` and `None`-aware APIs"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "type-aliases"\ndescription = "name complex types with `type` statements or aliases"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "typed-dicts"\ndescription = "type dictionary records that come from JSON"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "structured-data-shapes"\ndescription = "choose between dataclass, NamedTuple, and TypedDict for records"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "literal-and-final"\ndescription = "express constrained values and names that should not be rebound"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "callable-types"\ndescription = "type functions that are passed as arguments"\n\n[[journeys.sections]]\ntitle = "Scale annotations for reusable libraries."\nsummary = "Advanced typing exists to preserve information across reusable functions, containers, and decorators."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "generics-and-typevar"\ndescription = "write reusable typed containers and functions"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "paramspec"\ndescription = "preserve callable signatures through decorators"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "overloads"\ndescription = "describe APIs whose return type depends on the input shape"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "casts-and-any"\ndescription = "show escape hatches and their tradeoffs"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "newtype"\ndescription = "create distinct static identities for runtime-compatible values"\n\n[[journeys]]\nslug = "reliability"\ntitle = "Reliability"\nsummary = "This journey follows the boundaries where programs fail, clean up, split into modules, communicate with the outside world, and run concurrent work."\n\n[[journeys.sections]]\ntitle = "Make failure explicit."\nsummary = "Robust Python code distinguishes expected absence, broken assumptions, recoverable errors, and domain-specific failures."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "exceptions"\ndescription = "signal and recover from errors"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "assertions"\ndescription = "state internal assumptions"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "exception-chaining"\ndescription = "preserve the cause while translating an error"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "exception-groups"\ndescription = "handle multiple failures together"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "custom-exceptions"\ndescription = "name failures in the language of the problem domain"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "warnings"\ndescription = "signal soft problems and deprecations"\n\n[[journeys.sections]]\ntitle = "Control resource and module boundaries."\nsummary = "Cleanup, deletion, imports, and modules define where responsibilities begin and end."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "context-managers"\ndescription = "pair setup with reliable cleanup"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "delete-statements"\ndescription = "remove names, attributes, and items intentionally"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "modules"\ndescription = "split code into importable files"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "import-aliases"\ndescription = "make imported names clear at use sites"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "packages"\ndescription = "show package directories, `__init__.py`, and public module boundaries"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "virtual-environments"\ndescription = "isolate dependencies for a project"\n\n[[journeys.sections]]\ntitle = "Handle operations that outlive one expression."\nsummary = "I/O, testing, logging, subprocesses, and concurrency require different control boundaries from ordinary expressions."\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "async-await"\ndescription = "await concurrent I/O-shaped work"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "async-iteration-and-context"\ndescription = "consume async streams and cleanup protocols"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "logging"\ndescription = "record operational events without using `print()`"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "testing"\ndescription = "write deterministic tests with `unittest` or `pytest`"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "subprocesses"\ndescription = "run external commands safely"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "threads-and-processes"\ndescription = "contrast concurrency choices beyond `asyncio`"\n\n[[journeys.sections.items]]\nkind = "example"\nvalue = "networking"\ndescription = "make HTTP or socket boundaries explicit"\n\n# Explicit see_also edge labels; default labels are inferred in src/app.py.\n[[see_also_edge_labels]]\nsource = "break-and-continue"\ntarget = "loop-else"\nlabel = "contrast"\n\n[[see_also_edge_labels]]\nsource = "assignment-expressions"\ntarget = "conditionals"\nlabel = "contrast"\n\n[[see_also_edge_labels]]\nsource = "yield-from"\ntarget = "generators"\nlabel = "prerequisite"\n\n[[see_also_edge_labels]]\nsource = "async-iteration-and-context"\ntarget = "async-await"\nlabel = "prerequisite"\n\n[[see_also_edge_labels]]\nsource = "delete-statements"\ntarget = "mutability"\nlabel = "shared mechanism"\n\n[[see_also_edge_labels]]\nsource = "positional-only-parameters"\ntarget = "keyword-only-arguments"\nlabel = "contrast"\n\n[[see_also_edge_labels]]\nsource = "assertions"\ntarget = "exceptions"\nlabel = "alternative"\n\n[[see_also_edge_labels]]\nsource = "exception-chaining"\ntarget = "exceptions"\nlabel = "builds on"\n\n[[see_also_edge_labels]]\nsource = "exception-groups"\ntarget = "exceptions"\nlabel = "alternative"\n\n[[see_also_edge_labels]]\nsource = "operators"\ntarget = "numbers"\nlabel = "related syntax"\n\n[[see_also_edge_labels]]\nsource = "operators"\ntarget = "booleans"\nlabel = "condition building"\n\n[[see_also_edge_labels]]\nsource = "operators"\ntarget = "assignment-expressions"\nlabel = "specialized expression"\n\n[[see_also_edge_labels]]\nsource = "literals"\ntarget = "values"\nlabel = "value surface"\n\n[[see_also_edge_labels]]\nsource = "literals"\ntarget = "strings"\nlabel = "text literal"\n\n[[see_also_edge_labels]]\nsource = "literals"\ntarget = "sets"\nlabel = "container literal"\n\n# Example-cell figure attachments. figure names refer to FIGURES paint functions.\n[[figure_attachments]]\nslug = "mutability"\nanchor = "cell-0"\nfigure = "aliasing-mutation"\ncaption = "Two names share one mutable list — appending through one name changes the object visible through both."\n\n[[figure_attachments]]\nslug = "mutability"\nanchor = "cell-0"\nfigure = "tuple-no-mutation"\ncaption = "By contrast, a tuple is frozen — its contents cannot change in place, so aliasing carries no mutation hazard."\n\n[[figure_attachments]]\nslug = "variables"\nanchor = "cell-0"\nfigure = "variables-bind"\ncaption = "A name is a label that points at an object. Assignment binds the label; the object exists independently."\n\n[[figure_attachments]]\nslug = "lists"\nanchor = "cell-0"\nfigure = "list-append"\ncaption = "Lists are mutable sequences. `.append` extends the same list object — no new list is created."\n\n[[figure_attachments]]\nslug = "dicts"\nanchor = "cell-0"\nfigure = "dict-buckets"\ncaption = "hash(key) picks the slot: \\"year\\" lands in an empty one, and profile[\\"name\\"] goes straight to its slot without scanning the other entries."\n\n[[figure_attachments]]\nslug = "unpacking"\nanchor = "cell-0"\nfigure = "unpacking-bind"\ncaption = "Left-side names bind to right-side positions; `*rest` gathers the middle into a list."\n\n[[figure_attachments]]\nslug = "comprehensions"\nanchor = "cell-0"\nfigure = "comprehension-equivalence"\ncaption = "A comprehension is a compact spelling of the equivalent for-loop with append, made into one expression."\n\n[[figure_attachments]]\nslug = "classes"\nanchor = "cell-0"\nfigure = "class-triangle"\ncaption = "Every Python value sits on the instance → class → type triangle; the metaclass is the type of the class."\n\n[[figure_attachments]]\nslug = "inheritance-and-super"\nanchor = "cell-0"\nfigure = "mro-chain"\ncaption = "Multiple inheritance forms a graph; C3 linearisation flattens it into the MRO Python uses for attribute lookup."\n\n[[figure_attachments]]\nslug = "dataclasses"\nanchor = "cell-0"\nfigure = "dataclass-fields"\ncaption = "Field declarations become the generated __init__ signature: declaration is the constructor."\n\n[[figure_attachments]]\nslug = "special-methods"\nanchor = "cell-0"\nfigure = "operator-dispatch"\ncaption = "Operators are method calls. `a + b` dispatches to `a.__add__(b)`; the data model exposes the syntax."\n\n[[figure_attachments]]\nslug = "decorators"\nanchor = "cell-0"\nfigure = "decorator-rebind"\ncaption = "@dec rebinds the name to wrapper(f₀); the original function survives only in the wrapper\'s closure cell."\n\n[[figure_attachments]]\nslug = "recursion"\nanchor = "cell-1"\nfigure = "call-stack"\ncaption = "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."\n\n[[figure_attachments]]\nslug = "exception-chaining"\nanchor = "cell-0"\nfigure = "exception-cause-context"\ncaption = "`raise X from Y` sets `__cause__` (explicit); raising during except sets `__context__` (implicit)."\n\n[[figure_attachments]]\nslug = "hello-world"\nanchor = "cell-0"\nfigure = "program-output"\ncaption = "Every Python program starts as source and produces text on standard output. The smallest mental model."\n\n[[figure_attachments]]\nslug = "numbers"\nanchor = "cell-0"\nfigure = "number-lines"\ncaption = "Ints have unbounded precision; floats use IEEE doubles whose representable values thin out near the extremes."\n\n[[figure_attachments]]\nslug = "operators"\nanchor = "cell-0"\nfigure = "expression-tree"\ncaption = "An expression like `(2 + 3) * 4` parses as a tree; operator precedence and parentheses determine its shape."\n\n[[figure_attachments]]\nslug = "none"\nanchor = "cell-0"\nfigure = "none-singleton"\ncaption = "`None` is a single object: every name that points at None points at the same object."\n\n[[figure_attachments]]\nslug = "equality-and-identity"\nanchor = "cell-0"\nfigure = "identity-and-equality"\ncaption = "Two names can share one object (`is` and `==` both true) or hold two equal-but-distinct objects (only `==` true)."\n\n[[figure_attachments]]\nslug = "strings"\nanchor = "cell-0"\nfigure = "codepoints-bytes"\ncaption = "Strings are sequences of Unicode codepoints. UTF-8 encoding turns them into bytes; `é` takes two bytes, `c` takes one."\n\n[[figure_attachments]]\nslug = "for-loops"\nanchor = "cell-1"\nfigure = "iterator-unroll"\ncaption = "Each call to next() advances the caret one cell along the iterable — the same shape behind range(), strings, and any sequence."\n\n[[figure_attachments]]\nslug = "sorting"\nanchor = "cell-1"\nfigure = "sort-stability"\ncaption = "Python\'s sort is stable: items with equal keys keep their original order, so chained sorts compose predictably."\n\n[[figure_attachments]]\nslug = "keyword-only-arguments"\nanchor = "cell-0"\nfigure = "kw-only-separator"\ncaption = "A bare `*` divides positional or keyword arguments from keyword-only ones; callers must pass `c` and `d` by name."\n\n[[figure_attachments]]\nslug = "positional-only-parameters"\nanchor = "cell-0"\nfigure = "positional-only-separator"\ncaption = "A bare `/` divides positional-only arguments from positional-or-keyword ones; callers cannot name `a` or `b`."\n\n[[figure_attachments]]\nslug = "positional-only-parameters"\nanchor = "cell-0"\nfigure = "kw-only-separator"\ncaption = "In the same signature, the bare `*` works the other way: parameters after it, such as `clamp`, must be named at the call site."\n\n[[figure_attachments]]\nslug = "closures"\nanchor = "cell-0"\nfigure = "closure-cell"\ncaption = "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."\n\n[[figure_attachments]]\nslug = "scope-global-nonlocal"\nanchor = "cell-0"\nfigure = "scope-rings"\ncaption = "Name lookup walks LEGB — local, enclosing, global, built-in — outward, returning the first binding it finds."\n\n[[figure_attachments]]\nslug = "generators"\nanchor = "cell-0"\nfigure = "generator-resume"\ncaption = "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."\n\n[[figure_attachments]]\nslug = "type-hints"\nanchor = "cell-0"\nfigure = "annotation-ghost"\ncaption = "Annotations describe expected types for tools; the runtime accepts any object regardless."\n\n[[figure_attachments]]\nslug = "exceptions"\nanchor = "cell-0"\nfigure = "exception-lanes"\ncaption = "try, except, else, and finally as parallel lanes; a single coral path traces what actually runs."\n\n[[figure_attachments]]\nslug = "context-managers"\nanchor = "cell-0"\nfigure = "context-bowtie"\ncaption = "A context manager pairs setup with reliable cleanup; the raise path still routes through __exit__."\n\n[[figure_attachments]]\nslug = "async-await"\nanchor = "cell-1"\nfigure = "async-swimlane"\ncaption = "On await, the coroutine yields to the loop; the loop runs other work and resumes when the awaitable is ready."\n\n[[figure_attachments]]\nslug = "iterators"\nanchor = "cell-0"\nfigure = "iter-protocol"\ncaption = "iter() exposes the iterator behind for; next() pulls one value at a time until exhausted."\n\n[[figure_attachments]]\nslug = "slices"\nanchor = "cell-0"\nfigure = "slice-ruler"\ncaption = "Slice indices sit between cells; [:3] and [3:] partition the sequence at index 3, never overlapping or losing an item."\n\n[[figure_attachments]]\nslug = "operator-overloading"\nanchor = "cell-0"\nfigure = "operator-dispatch"\ncaption = "Defining `__add__` on a class lets `+` dispatch into the class\'s own behavior."\n\n[[figure_attachments]]\nslug = "iterator-vs-iterable"\nanchor = "cell-0"\nfigure = "iter-protocol"\ncaption = "An iterable knows how to produce an iterator (via iter()); the iterator knows how to produce values (via next())."\n\n[[figure_attachments]]\nslug = "iterator-vs-iterable"\nanchor = "cell-1"\nfigure = "iterator-unroll"\ncaption = "The iterator\'s caret only moves forward — after the first `list(stream)` drains it, nothing is left for the second."\n\n[[figure_attachments]]\nslug = "type-aliases"\nanchor = "cell-0"\nfigure = "type-alias-name"\ncaption = "A type alias names a complex annotation once so call sites read as the domain meaning, not the type composition."\n\n[[figure_attachments]]\nslug = "typed-dicts"\nanchor = "cell-0"\nfigure = "typed-dict-shape"\ncaption = "TypedDict gives each key a typed value, so `obj[\'x\']` is checked against the declared shape."\n\n[[figure_attachments]]\nslug = "union-and-optional-types"\nanchor = "cell-0"\nfigure = "union-types"\ncaption = "`int | str | None` says one slot may hold any of three shapes — including expected absence."\n\n[[figure_attachments]]\nslug = "generics-and-typevar"\nanchor = "cell-0"\nfigure = "generic-preservation"\ncaption = "A generic preserves the input type through the call: the same T flows in and out of fn[T]."\n\n[[figure_attachments]]\nslug = "abstract-base-classes"\nanchor = "cell-0"\nfigure = "class-triangle"\ncaption = "An ABC sits on the same triangle as concrete classes; subclasses inherit the abstract methods they must implement."\n\n[[figure_attachments]]\nslug = "copying-collections"\nanchor = "cell-0"\nfigure = "aliasing-mutation"\ncaption = "Without copy() two names share the same object; mutating through one is visible through the other."\n\n[[figure_attachments]]\nslug = "truth-and-size"\nanchor = "cell-0"\nfigure = "truth-and-size"\ncaption = "bool(x) calls __bool__ first; if absent, __len__() != 0; if neither, defaults to True."\n\n[[figure_attachments]]\nslug = "descriptors"\nanchor = "cell-0"\nfigure = "descriptor-protocol"\ncaption = "Attribute access on an instance routes through the descriptor\'s __get__/__set__/__delete__ when the attribute is a descriptor."\n\n[[figure_attachments]]\nslug = "bound-and-unbound-methods"\nanchor = "cell-0"\nfigure = "bound-unbound"\ncaption = "Accessing a method via an instance binds self; accessing it via the class returns the underlying function."\n\n[[figure_attachments]]\nslug = "classmethods-and-staticmethods"\nanchor = "cell-0"\nfigure = "method-kinds"\ncaption = "Three method kinds, three first-argument conventions: classmethod gets the class, staticmethod gets nothing, instance gets self."\n\n[[figure_attachments]]\nslug = "callable-objects"\nanchor = "cell-0"\nfigure = "callable-objects"\ncaption = "Defining __call__ makes any object callable; functions are just one shape that satisfies this protocol."\n\n[[figure_attachments]]\nslug = "attribute-access"\nanchor = "cell-0"\nfigure = "attribute-lookup"\ncaption = "obj.x checks instance __dict__, then class __dict__, then __getattr__; the first hit wins."\n\n[[figure_attachments]]\nslug = "guard-clauses"\nanchor = "cell-0"\nfigure = "guard-clauses"\ncaption = "Early returns handle the exceptional cases first so the main work is the body of the function, not its tail."\n\n[[figure_attachments]]\nslug = "bytes-and-bytearray"\nanchor = "cell-3"\nfigure = "bytes-vs-bytearray"\ncaption = "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."\n\n[[figure_attachments]]\nslug = "sentinel-iteration"\nanchor = "cell-0"\nfigure = "sentinel-iteration"\ncaption = "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."\n\n[[figure_attachments]]\nslug = "partial-functions"\nanchor = "cell-0"\nfigure = "partial-functions"\ncaption = "`functools.partial(f, 1)` pre-fills `a=1`, returning a thinner callable `g(b, c)` that only needs the rest."\n\n[[figure_attachments]]\nslug = "args-and-kwargs"\nanchor = "cell-0"\nfigure = "args-kwargs"\ncaption = "*args captures the extra positionals as a tuple; **kwargs captures the extra keywords as a dict."\n\n[[figure_attachments]]\nslug = "multiple-return-values"\nanchor = "cell-0"\nfigure = "multiple-return"\ncaption = "A function returning multiple values really returns one tuple; the caller unpacks it into named bindings."\n\n[[figure_attachments]]\nslug = "lambdas"\nanchor = "cell-0"\nfigure = "lambda-expression"\ncaption = "A lambda is a function literal: parameters before the colon, a single expression after, no statement body."\n\n[[figure_attachments]]\nslug = "properties"\nanchor = "cell-0"\nfigure = "property-fork"\ncaption = "When x is a property, attribute access routes through fget/fset instead of touching __dict__."\n\n[[figure_attachments]]\nslug = "metaclasses"\nanchor = "cell-0"\nfigure = "metaclass-triangle"\ncaption = "A metaclass is the type of a class, just as a class is the type of its instances; type is the default metaclass."\n\n[[figure_attachments]]\nslug = "metaclasses"\nanchor = "cell-0"\nfigure = "class-triangle"\ncaption = "The class makes instances — the same triangle one level down, with type as the default apex."\n\n[[figure_attachments]]\nslug = "modules"\nanchor = "cell-0"\nfigure = "sys-path-resolution"\ncaption = "An import walks sys.path entry by entry; the first directory containing the module wins."\n\n[[figure_attachments]]\nslug = "import-aliases"\nanchor = "cell-0"\nfigure = "import-alias"\ncaption = "`import x as y` binds the name y to the same module object x would have."\n\n[[figure_attachments]]\nslug = "protocols"\nanchor = "cell-0"\nfigure = "protocol-check"\ncaption = "An object satisfies a protocol structurally — by having the required methods — not by inheriting it."\n\n[[figure_attachments]]\nslug = "enums"\nanchor = "cell-0"\nfigure = "enum-members"\ncaption = "An enum names a fixed set of symbolic values; no new members appear at runtime."\n\n[[figure_attachments]]\nslug = "datetime"\nanchor = "cell-0"\nfigure = "datetime-instant"\ncaption = "An aware datetime carries a UTC offset; one instant in time reads differently on two clocks."\n\n[[figure_attachments]]\nslug = "json"\nanchor = "cell-0"\nfigure = "json-python-mapping"\ncaption = "Six type pairs bridge the JSON text boundary; each json value maps to one Python type."\n\n[[figure_attachments]]\nslug = "regular-expressions"\nanchor = "cell-0"\nfigure = "regex-groups"\ncaption = "findall returns only what the parentheses capture: `([A-Za-z]+)` takes the name and `(\\\\d+)` the score, one (name, score) pair per record."\n\n[[figure_attachments]]\nslug = "number-parsing"\nanchor = "cell-0"\nfigure = "number-parse"\ncaption = "int() turns text into a typed number; malformed input raises ValueError instead of guessing."\n\n[[figure_attachments]]\nslug = "string-formatting"\nanchor = "cell-1"\nfigure = "format-spec"\ncaption = "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."\n\n[[figure_attachments]]\nslug = "truthiness"\nanchor = "cell-0"\nfigure = "truthy-check"\ncaption = "bool(x) is True except for a small fixed set: 0, 0.0, \\"\\", [], {}, None, False."\n\n[[figure_attachments]]\nslug = "booleans"\nanchor = "cell-0"\nfigure = "boolean-truth-table"\ncaption = "`a and b` returns True only when both are True; otherwise it returns the first falsy value."\n\n[[figure_attachments]]\nslug = "sets"\nanchor = "cell-0"\nfigure = "set-buckets"\ncaption = "Sets are hash buckets without values; `x in s` averages O(1) regardless of size."\n\n[[figure_attachments]]\nslug = "tuples"\nanchor = "cell-3"\nfigure = "tuple-frozen"\ncaption = "Tuples are ordered, immutable sequences; positions matter, contents do not change once constructed."\n\n[[figure_attachments]]\nslug = "tuples"\nanchor = "cell-3"\nfigure = "list-append"\ncaption = "A list is the other intent: a variable number of similar items, growing in place with `.append`."\n\n[[figure_attachments]]\nslug = "yield-from"\nanchor = "cell-0"\nfigure = "yield-delegation"\ncaption = "`yield from inner` delegates iteration to an inner generator; its yields surface here unchanged."\n\n[[figure_attachments]]\nslug = "itertools"\nanchor = "cell-0"\nfigure = "itertools-chain"\ncaption = "chain stitches two iterables into one stream without materialising either: values arrive lazily."\n\n[[figure_attachments]]\nslug = "assertions"\nanchor = "cell-0"\nfigure = "assertion-check"\ncaption = "assert tests a condition; True passes silently, False raises AssertionError with the optional message."\n\n[[figure_attachments]]\nslug = "custom-exceptions"\nanchor = "cell-0"\nfigure = "custom-exception-chain"\ncaption = "Subclassing an existing exception gains a domain name without changing semantics."\n\n[[figure_attachments]]\nslug = "exception-groups"\nanchor = "cell-0"\nfigure = "exception-group-peel"\ncaption = "except* peels matched leaves out of an ExceptionGroup; survivors regroup and propagate."\n\n[[figure_attachments]]\nslug = "delete-statements"\nanchor = "cell-0"\nfigure = "delete-name-erased"\ncaption = "`del x` removes the name; the object survives if any other reference holds it, otherwise gets collected."\n\n[[figure_attachments]]\nslug = "conditionals"\nanchor = "cell-0"\nfigure = "branch-fork"\ncaption = "A predicate sorts a value into one of several branches; if/elif/else is the explicit spelling."\n\n[[figure_attachments]]\nslug = "match-statements"\nanchor = "cell-0"\nfigure = "match-dispatch-ladder"\ncaption = "match dispatches by pattern shape; the value flows down the patterns and the first match wins."\n\n[[figure_attachments]]\nslug = "assignment-expressions"\nanchor = "cell-0"\nfigure = "naming-decisions"\ncaption = "The walrus binds a name during the surrounding expression; one expression, two outputs."\n\n[[figure_attachments]]\nslug = "iterating-over-iterables"\nanchor = "cell-0"\nfigure = "iter-protocol"\ncaption = "`for` desugars to iter()+next(): one iter() call, then next() until StopIteration ends the loop."\n\n[[figure_attachments]]\nslug = "generator-expressions"\nanchor = "cell-1"\nfigure = "lazy-stream"\ncaption = "A generator expression composes filter and map lazily; values flow only when next() pulls them."\n\n[[figure_attachments]]\nslug = "async-iteration-and-context"\nanchor = "cell-0"\nfigure = "async-swimlane"\ncaption = "async iteration and async with both rest on the same loop-vs-coroutine handoff as await."\n\n[[figure_attachments]]\nslug = "loop-else"\nanchor = "cell-0"\nfigure = "loop-else-gate"\ncaption = "The loop\'s else branch runs only when the loop falls through naturally; break skips it."\n\n[[figure_attachments]]\nslug = "break-and-continue"\nanchor = "cell-0"\nfigure = "early-exit"\ncaption = "break exits the loop; continue skips to the next iteration. Both interrupt the natural fall-through."\n\n[[figure_attachments]]\nslug = "comprehension-patterns"\nanchor = "cell-0"\nfigure = "comprehension-equivalence"\ncaption = "Nested clauses compose left to right; the comprehension is still equivalent to a for-loop with append."\n\n[[figure_attachments]]\nslug = "container-protocols"\nanchor = "cell-0"\nfigure = "container-methods"\ncaption = "Container syntax routes to narrow protocol methods: assignment to __setitem__, membership to __contains__, lookup to __getitem__."\n\n[[figure_attachments]]\nslug = "functions"\nanchor = "cell-0"\nfigure = "function-with-body"\ncaption = "A function takes inputs, evaluates a body, and returns a value: `greet(\'Ada\')` produces `\'Hello, Ada\'`."\n\n[[figure_attachments]]\nslug = "constants"\nanchor = "cell-0"\nfigure = "constants-bind"\ncaption = "UPPER_CASE is a naming convention, not a language constraint; the binding behaves like any other variable."\n\n[[figure_attachments]]\nslug = "values"\nanchor = "cell-0"\nfigure = "value-type-lookup"\ncaption = "text names a str object, and type(text) is str: the type carries upper() and the rest of the behaviour, not the name."\n\n[[figure_attachments]]\nslug = "literals"\nanchor = "cell-0"\nfigure = "literal-forms"\ncaption = "Each Python type has its own literal spellings; ints accept decimal, hex, and binary; strings accept either quote."\n\n[[figure_attachments]]\nslug = "logging"\nanchor = "cell-0"\nfigure = "logging-threshold"\ncaption = "The handler\'s level is a gate: debug is below INFO and never reaches stdout, while info and warning pass through unchanged."\n\n[[figure_attachments]]\nslug = "collections-module"\nanchor = "cell-0"\nfigure = "collections-containers"\ncaption = "Four specialised containers for shapes the built-in types don\'t cover well: deque, Counter, defaultdict, namedtuple."\n\n[[figure_attachments]]\nslug = "while-loops"\nanchor = "cell-0"\nfigure = "while-backedge"\ncaption = "`while` repeats the body while the test stays true; the back-edge returns control to the test before each pass."\n\n[[figure_attachments]]\nslug = "while-loops"\nanchor = "cell-0"\nfigure = "loop-repetition"\ncaption = "Each loop shape has its own stopping rule: `for` ends when the iterable is exhausted, `while` when the condition turns false, sentinel iteration when the marker value appears."\n\n[[figure_attachments]]\nslug = "advanced-match-patterns"\nanchor = "cell-0"\nfigure = "match-pattern-variants"\ncaption = "Capture, alternative, guard, and class patterns each name a different way a value can match a case."\n\n[[figure_attachments]]\nslug = "packages"\nanchor = "cell-0"\nfigure = "package-tree"\ncaption = "A directory with __init__.py becomes an importable package; submodules and subpackages nest beneath it."\n\n[[figure_attachments]]\nslug = "virtual-environments"\nanchor = "cell-0"\nfigure = "venv-boundary"\ncaption = "A venv carries its own interpreter and site-packages, isolating a project\'s dependencies from the system."\n\n[[figure_attachments]]\nslug = "subprocesses"\nanchor = "cell-0"\nfigure = "subprocess-spawn"\ncaption = "subprocess.run spawns a child process and captures its stdout, stderr, and exit code as portable evidence."\n\n[[figure_attachments]]\nslug = "testing"\nanchor = "cell-0"\nfigure = "aaa-pattern"\ncaption = "arrange-act-assert: set up the state, perform the behavior under test, compare the result to expectations."\n\n[[figure_attachments]]\nslug = "networking"\nanchor = "cell-0"\nfigure = "socket-byte-boundary"\ncaption = "Text crosses the socket as bytes — `encode` marks the python → wire boundary, `decode` brings the bytes back to a Python `str`."\n\n[[figure_attachments]]\nslug = "threads-and-processes"\nanchor = "cell-0"\nfigure = "gil-lanes"\ncaption = "Threads share memory but the GIL serialises Python bytecode; processes run in parallel with isolated memory."\n\n[[figure_attachments]]\nslug = "casts-and-any"\nanchor = "cell-0"\nfigure = "cast-escape"\ncaption = "cast(T, x) tells the type checker to treat x as T; the runtime is unaffected."\n\n[[figure_attachments]]\nslug = "newtype"\nanchor = "cell-0"\nfigure = "newtype-phantom"\ncaption = "NewType creates a distinct static identity backed by the same runtime type — UserId is int with a name."\n\n[[figure_attachments]]\nslug = "overloads"\nanchor = "cell-0"\nfigure = "overload-signatures"\ncaption = "@overload declares static call signatures for double(int) and double(str); one runtime implementation handles both."\n\n[[figure_attachments]]\nslug = "paramspec"\nanchor = "cell-0"\nfigure = "paramspec-preserve"\ncaption = "ParamSpec preserves the wrapped function\'s signature through a decorator, parameter for parameter."\n\n[[figure_attachments]]\nslug = "literal-and-final"\nanchor = "cell-0"\nfigure = "literal-constrained"\ncaption = "Literal narrows a slot to a fixed set of constant values; Final says the binding will not change."\n\n[[figure_attachments]]\nslug = "callable-types"\nanchor = "cell-0"\nfigure = "callable-type"\ncaption = "A Callable annotation describes the callback slot: this argument list must produce this return type."\n\n[[figure_attachments]]\nslug = "runtime-type-checks"\nanchor = "cell-0"\nfigure = "isinstance-check"\ncaption = "isinstance and issubclass ask the runtime; the answer is a bool, not a static type refinement."\n\n[[figure_attachments]]\nslug = "structured-data-shapes"\nanchor = "cell-0"\nfigure = "structured-shapes"\ncaption = "The same record can be a mutable dataclass, an immutable NamedTuple, or a runtime dict described by TypedDict."\n\n[[figure_attachments]]\nslug = "csv-data"\nanchor = "cell-0"\nfigure = "csv-records"\ncaption = "CSV files are rows of records; each line has the same columns in the same order."\n\n[[figure_attachments]]\nslug = "warnings"\nanchor = "cell-0"\nfigure = "warning-signal"\ncaption = "A warning is a soft signal: the message is reported, but execution continues unless filters elevate it."\n\n[[figure_attachments]]\nslug = "object-lifecycle"\nanchor = "cell-0"\nfigure = "object-lifecycle"\ncaption = "Names keep the same object reachable; deleting one name matters only when it removes the last reference."\n\n# Journey-section figures keyed by the visible section title.\n[[journey_section_figures]]\nsection = "Start with executable evidence."\nfigure = "runtime-evidence-loop"\ncaption = "Examples are evidence loops: source, a run step, and visible output stay together."\n\n[[journey_section_figures]]\nsection = "Separate value, identity, and absence."\nfigure = "runtime-object-axes"\ncaption = "Runtime objects answer separate questions: equal value, same identity, or the singleton that marks absence."\n\n[[journey_section_figures]]\nsection = "Read expressions as object operations."\nfigure = "runtime-expression-model"\ncaption = "Expression syntax enters the data model; object methods produce the result."\n\n[[journey_section_figures]]\nsection = "Choose between paths."\nfigure = "control-decision-map"\ncaption = "Facts flow into one decision point; exactly one branch owns the next step."\n\n[[journey_section_figures]]\nsection = "Name and shape decisions."\nfigure = "control-fact-shape"\ncaption = "Name a fact when a condition needs it; match shape when the data structure is the decision."\n\n[[journey_section_figures]]\nsection = "Stop as soon as the answer is known."\nfigure = "control-stop-boundary"\ncaption = "Early exits draw a boundary: once the answer is found, the tail stays unread."\n\n[[journey_section_figures]]\nsection = "Choose the right loop shape."\nfigure = "iteration-loop-selector"\ncaption = "Choose the loop from its stopping rule: exhaustion, condition, or sentinel marker."\n\n[[journey_section_figures]]\nsection = "See the protocol behind `for`."\nfigure = "iteration-protocol-map"\ncaption = "for is surface syntax; iter() creates an iterator and next() pulls values until StopIteration."\n\n[[journey_section_figures]]\nsection = "Compose lazy value streams."\nfigure = "iteration-lazy-pull"\ncaption = "Lazy pipelines run from the consumer\'s pull: next() requests one value through each stage."\n\n[[journey_section_figures]]\nsection = "Pick the container that matches the question."\nfigure = "container-questions"\ncaption = "Each container answers a different question: ordered, fixed, lookup, unique."\n\n[[journey_section_figures]]\nsection = "Move between shapes deliberately."\nfigure = "reshape-pipeline"\ncaption = "Most everyday code reshapes data: one input, one transform, one new value."\n\n[[journey_section_figures]]\nsection = "Cross text and data boundaries."\nfigure = "text-data-boundary"\ncaption = "Programs receive text and produce structured data; parsing makes the boundary explicit."\n\n[[journey_section_figures]]\nsection = "Start with functions as named behavior."\nfigure = "function-signature"\ncaption = "A function is the first abstraction boundary: arguments in, body, return value out."\n\n[[journey_section_figures]]\nsection = "Use functions as values."\nfigure = "function-as-value"\ncaption = "Functions are first-class values. A second name binds to the same function object."\n\n[[journey_section_figures]]\nsection = "Bundle behavior with state."\nfigure = "class-with-state"\ncaption = "Classes group fields and methods so data and behavior move together behind one interface."\n\n[[journey_section_figures]]\nsection = "Keep runtime and static analysis separate."\nfigure = "type-runtime-static-split"\ncaption = "Runtime values run the program; static tools inspect separate annotations and report before execution."\n\n[[journey_section_figures]]\nsection = "Describe realistic data shapes."\nfigure = "type-shape-catalog"\ncaption = "Real data contracts combine fields, variants, and expected absence instead of one scalar type."\n\n[[journey_section_figures]]\nsection = "Scale annotations for reusable libraries."\nfigure = "type-library-contract"\ncaption = "Reusable APIs carry caller contracts through the library boundary with generics, parameters, and overloads."\n\n[[journey_section_figures]]\nsection = "Make failure explicit."\nfigure = "reliability-signal-map"\ncaption = "Different failure shapes need explicit signals: assertions, recovery, chained causes, or warnings."\n\n[[journey_section_figures]]\nsection = "Control resource and module boundaries."\nfigure = "reliability-boundary-map"\ncaption = "Reliable programs name their boundaries: resources clean up, modules import, environments constrain runtime."\n\n[[journey_section_figures]]\nsection = "Handle operations that outlive one expression."\nfigure = "reliability-operation-boundary"\ncaption = "Async, threaded, test, and logging work cross an operation boundary before evidence comes back."\n\n# Curated scores for example-cell figures.\n[[example_figure_scores]]\nslug = "variables"\nscore = 9.5\ncomment = "the canonical name → object picture"\n\n[[example_figure_scores]]\nslug = "mutability"\nscore = 9.5\ncomment = "three-state small multiple of aliased mutation"\n\n[[example_figure_scores]]\nslug = "copying-collections"\nscore = 9.5\ncomment = "same picture as mutability, perfect match"\n\n[[example_figure_scores]]\nslug = "hello-world"\nscore = 9.0\ncomment = "program → output, smallest mechanism"\n\n[[example_figure_scores]]\nslug = "numbers"\nscore = 9.0\ncomment = "int unbounded vs float thinning, both registers"\n\n[[example_figure_scores]]\nslug = "operators"\nscore = 9.0\ncomment = "expression tree mechanism"\n\n[[example_figure_scores]]\nslug = "none"\nscore = 9.0\ncomment = "three names converging on one None"\n\n[[example_figure_scores]]\nslug = "equality-and-identity"\nscore = 9.0\ncomment = "shared vs separate object, side-by-side"\n\n[[example_figure_scores]]\nslug = "strings"\nscore = 9.0\ncomment = "codepoints + bytes registers"\n\n[[example_figure_scores]]\nslug = "for-loops"\nscore = 9.0\ncomment = "4-row caret advance"\n\n[[example_figure_scores]]\nslug = "sorting"\nscore = 9.0\ncomment = "stability ribbons preserved across keys"\n\n[[example_figure_scores]]\nslug = "keyword-only-arguments"\nscore = 9.0\ncomment = "signature with explicit `*` separator"\n\n[[example_figure_scores]]\nslug = "positional-only-parameters"\nscore = 9.0\ncomment = "signature with explicit `/` separator"\n\n[[example_figure_scores]]\nslug = "closures"\nscore = 9.5\ncomment = "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"\n\n[[example_figure_scores]]\nslug = "scope-global-nonlocal"\nscore = 9.0\ncomment = "LEGB nested rings"\n\n[[example_figure_scores]]\nslug = "recursion"\nscore = 9.0\ncomment = "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"\n\n[[example_figure_scores]]\nslug = "lists"\nscore = 9.0\ncomment = "cells with append mechanism"\n\n[[example_figure_scores]]\nslug = "dicts"\nscore = 9.0\ncomment = "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"\n\n[[example_figure_scores]]\nslug = "slices"\nscore = 9.0\ncomment = "ruler with bracket overlay"\n\n[[example_figure_scores]]\nslug = "comprehensions"\nscore = 9.0\ncomment = "comprehension over equivalent for-loop"\n\n[[example_figure_scores]]\nslug = "type-hints"\nscore = 9.0\ncomment = "ghost annotations over runtime values"\n\n[[example_figure_scores]]\nslug = "generators"\nscore = 9.5\ncomment = "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"\n\n[[example_figure_scores]]\nslug = "exceptions"\nscore = 9.0\ncomment = "try/except/else/finally lanes with traced path"\n\n[[example_figure_scores]]\nslug = "context-managers"\nscore = 9.0\ncomment = "enter / body / exit bowtie"\n\n[[example_figure_scores]]\nslug = "async-await"\nscore = 9.0\ncomment = "loop/coro swimlane with await handoffs"\n\n[[example_figure_scores]]\nslug = "classes"\nscore = 9.0\ncomment = "instance/class/type triangle"\n\n[[example_figure_scores]]\nslug = "inheritance-and-super"\nscore = 9.0\ncomment = "MRO chain with diamond ghost"\n\n[[example_figure_scores]]\nslug = "dataclasses"\nscore = 9.0\ncomment = "fields → generated __init__ signature"\n\n[[example_figure_scores]]\nslug = "decorators"\nscore = 9.0\ncomment = "before/after rebinding through cell"\n\n[[example_figure_scores]]\nslug = "special-methods"\nscore = 9.0\ncomment = "syntax → method dispatch"\n\n[[example_figure_scores]]\nslug = "unpacking"\nscore = 9.0\ncomment = "binding-line mechanism with *rest"\n\n[[example_figure_scores]]\nslug = "exception-chaining"\nscore = 9.0\ncomment = "__cause__ vs __context__ distinguished"\n\n[[example_figure_scores]]\nslug = "iterating-over-iterables"\nscore = 9.0\ncomment = "iter() exposes the iterator"\n\n[[example_figure_scores]]\nslug = "iterators"\nscore = 9.0\ncomment = "three-state machine"\n\n[[example_figure_scores]]\nslug = "iterator-vs-iterable"\nscore = 9.0\ncomment = "the protocol exposed"\n\n[[example_figure_scores]]\nslug = "container-protocols"\nscore = 9.0\ncomment = "syntax routes to __setitem__, __contains__, __getitem__"\n\n[[example_figure_scores]]\nslug = "operator-overloading"\nscore = 9.0\ncomment = "dispatch arrow"\n\n[[example_figure_scores]]\nslug = "union-and-optional-types"\nscore = 9.0\ncomment = "type fork to several shapes"\n\n[[example_figure_scores]]\nslug = "abstract-base-classes"\nscore = 9.0\ncomment = "same triangle as concrete classes"\n\n[[example_figure_scores]]\nslug = "conditionals"\nscore = 9.0\ncomment = "predicate forks value to branch"\n\n[[example_figure_scores]]\nslug = "match-statements"\nscore = 9.0\ncomment = "dispatch ladder; first match wins"\n\n[[example_figure_scores]]\nslug = "advanced-match-patterns"\nscore = 9.0\ncomment = "four pattern variants"\n\n[[example_figure_scores]]\nslug = "loop-else"\nscore = 9.0\ncomment = "fell-through vs broke, two outcomes"\n\n[[example_figure_scores]]\nslug = "while-loops"\nscore = 9.0\ncomment = "back-edge mechanism plus the three stopping rules"\n\n[[example_figure_scores]]\nslug = "type-aliases"\nscore = 9.0\ncomment = "complex annotation collapses to a name"\n\n[[example_figure_scores]]\nslug = "typed-dicts"\nscore = 9.0\ncomment = "keys with declared value types"\n\n[[example_figure_scores]]\nslug = "comprehension-patterns"\nscore = 9.0\ncomment = "nested clauses compose"\n\n[[example_figure_scores]]\nslug = "lambdas"\nscore = 9.0\ncomment = "function literal: params / expression"\n\n[[example_figure_scores]]\nslug = "string-formatting"\nscore = 9.0\ncomment = "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"\n\n[[example_figure_scores]]\nslug = "regular-expressions"\nscore = 9.0\ncomment = "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"\n\n[[example_figure_scores]]\nslug = "json"\nscore = 9.0\ncomment = "two-column type mapping"\n\n[[example_figure_scores]]\nslug = "metaclasses"\nscore = 9.0\ncomment = "extended triangle to metaclass"\n\n[[example_figure_scores]]\nslug = "datetime"\nscore = 9.0\ncomment = "one instant, two clock offsets"\n\n[[example_figure_scores]]\nslug = "booleans"\nscore = 9.0\ncomment = "2×2 truth table"\n\n[[example_figure_scores]]\nslug = "sets"\nscore = 9.0\ncomment = "hash buckets without values"\n\n[[example_figure_scores]]\nslug = "yield-from"\nscore = 9.0\ncomment = "stitched ribbons; delegation"\n\n[[example_figure_scores]]\nslug = "generator-expressions"\nscore = 9.0\ncomment = "lazy filter→map pipeline"\n\n[[example_figure_scores]]\nslug = "async-iteration-and-context"\nscore = 9.0\ncomment = "loop/coro lanes with await yields"\n\n[[example_figure_scores]]\nslug = "assignment-expressions"\nscore = 9.0\ncomment = "walrus binds while comparing"\n\n[[example_figure_scores]]\nslug = "break-and-continue"\nscore = 9.0\ncomment = "early exit at first match"\n\n[[example_figure_scores]]\nslug = "delete-statements"\nscore = 9.0\ncomment = "name erased; object survives if referenced"\n\n[[example_figure_scores]]\nslug = "exception-groups"\nscore = 9.0\ncomment = "except* peels matching leaves"\n\n[[example_figure_scores]]\nslug = "custom-exceptions"\nscore = 9.0\ncomment = "subclass chain to a domain name"\n\n[[example_figure_scores]]\nslug = "modules"\nscore = 9.0\ncomment = "sys.path resolution; first hit wins"\n\n[[example_figure_scores]]\nslug = "protocols"\nscore = 9.0\ncomment = "structural duck check"\n\n[[example_figure_scores]]\nslug = "enums"\nscore = 9.0\ncomment = "closed set of symbolic values"\n\n[[example_figure_scores]]\nslug = "functions"\nscore = 9.0\ncomment = "specific call: greet(\'Ada\') → \'Hello, Ada\'"\n\n[[example_figure_scores]]\nslug = "constants"\nscore = 9.0\ncomment = "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"\n\n[[example_figure_scores]]\nslug = "values"\nscore = 9.0\ncomment = "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"\n\n[[example_figure_scores]]\nslug = "literals"\nscore = 8.5\ncomment = "literal spellings per type; a reference table, kept until a mechanism figure replaces it"\n\n[[example_figure_scores]]\nslug = "logging"\nscore = 9.5\ncomment = "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"\n\n[[example_figure_scores]]\nslug = "collections-module"\nscore = 8.5\ncomment = "deque / Counter / defaultdict / namedtuple; a catalogue, kept until the page is split"\n\n[[example_figure_scores]]\nslug = "import-aliases"\nscore = 9.0\ncomment = "two names bind to the same module"\n\n[[example_figure_scores]]\nslug = "number-parsing"\nscore = 9.0\ncomment = "int() success path vs ValueError"\n\n[[example_figure_scores]]\nslug = "tuples"\nscore = 9.0\ncomment = "frozen sequence with struck-through .append"\n\n[[example_figure_scores]]\nslug = "truthiness"\nscore = 9.0\ncomment = "bool(x) with the falsy set as a strip"\n\n[[example_figure_scores]]\nslug = "itertools"\nscore = 9.0\ncomment = "chain joins two iterables into one stream"\n\n[[example_figure_scores]]\nslug = "assertions"\nscore = 9.0\ncomment = "True passes, False raises"\n\n[[example_figure_scores]]\nslug = "descriptors"\nscore = 9.0\ncomment = "get/set/delete protocol routed through descriptor"\n\n[[example_figure_scores]]\nslug = "attribute-access"\nscore = 9.0\ncomment = "instance __dict__ → class __dict__ → __getattr__"\n\n[[example_figure_scores]]\nslug = "bound-and-unbound-methods"\nscore = 9.0\ncomment = "instance.method bound vs Class.method unbound"\n\n[[example_figure_scores]]\nslug = "classmethods-and-staticmethods"\nscore = 9.0\ncomment = "three method kinds, three first-arg conventions"\n\n[[example_figure_scores]]\nslug = "callable-objects"\nscore = 9.0\ncomment = "__call__ makes any object callable"\n\n[[example_figure_scores]]\nslug = "generics-and-typevar"\nscore = 9.0\ncomment = "the same T flows in and out"\n\n[[example_figure_scores]]\nslug = "truth-and-size"\nscore = 9.0\ncomment = "__bool__ → __len__ → True fallback chain"\n\n[[example_figure_scores]]\nslug = "bytes-and-bytearray"\nscore = 9.5\ncomment = "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"\n\n[[example_figure_scores]]\nslug = "sentinel-iteration"\nscore = 9.0\ncomment = "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"\n\n[[example_figure_scores]]\nslug = "partial-functions"\nscore = 9.0\ncomment = "f → partial(f, 1) → g"\n\n[[example_figure_scores]]\nslug = "guard-clauses"\nscore = 9.0\ncomment = "early returns, main body at the tail"\n\n[[example_figure_scores]]\nslug = "packages"\nscore = 9.0\ncomment = "__init__.py + nested submodules"\n\n[[example_figure_scores]]\nslug = "virtual-environments"\nscore = 9.0\ncomment = "project / venv boundary"\n\n[[example_figure_scores]]\nslug = "subprocesses"\nscore = 9.0\ncomment = "spawn → child → captured output"\n\n[[example_figure_scores]]\nslug = "testing"\nscore = 9.0\ncomment = "arrange-act-assert three-row pattern"\n\n[[example_figure_scores]]\nslug = "networking"\nscore = 9.0\ncomment = "text ↔ bytes across the socket boundary"\n\n[[example_figure_scores]]\nslug = "casts-and-any"\nscore = 9.0\ncomment = "Any → cast(T, x) → T, runtime unchanged"\n\n[[example_figure_scores]]\nslug = "newtype"\nscore = 9.0\ncomment = "same runtime, distinct static identity"\n\n[[example_figure_scores]]\nslug = "paramspec"\nscore = 9.0\ncomment = "P preserved through decorator"\n\n[[example_figure_scores]]\nslug = "literal-and-final"\nscore = 9.0\ncomment = "slot narrows to a fixed set"\n\n[[example_figure_scores]]\nslug = "runtime-type-checks"\nscore = 9.0\ncomment = "isinstance returns bool"\n\n[[example_figure_scores]]\nslug = "structured-data-shapes"\nscore = 9.0\ncomment = "dataclass vs NamedTuple vs TypedDict runtime trade-offs"\n\n[[example_figure_scores]]\nslug = "csv-data"\nscore = 9.0\ncomment = "rows × columns; same shape per line"\n\n[[example_figure_scores]]\nslug = "warnings"\nscore = 9.0\ncomment = "soft signal; execution continues"\n\n[[example_figure_scores]]\nslug = "object-lifecycle"\nscore = 9.0\ncomment = "names keep object reachable until last reference disappears"\n\n[[example_figure_scores]]\nslug = "args-and-kwargs"\nscore = 9.0\ncomment = "*args tuple, **kwargs dict regions"\n\n[[example_figure_scores]]\nslug = "multiple-return-values"\nscore = 9.0\ncomment = "function returns tuple; caller unpacks"\n\n[[example_figure_scores]]\nslug = "properties"\nscore = 9.0\ncomment = "obj.x routes through fget instead of __dict__"\n\n[[example_figure_scores]]\nslug = "overloads"\nscore = 9.0\ncomment = "static overload signatures contrasted with one runtime body"\n\n[[example_figure_scores]]\nslug = "callable-types"\nscore = 9.0\ncomment = "callback slot shows argument list and return type"\n\n[[example_figure_scores]]\nslug = "threads-and-processes"\nscore = 9.0\ncomment = "thread GIL sharing contrasted with process isolation"\n\n# Curated scores for journey-section figures.\n[[journey_section_figure_scores]]\nsection = "Start with executable evidence."\nscore = 9.0\ncomment = "journey-native source/run/output evidence loop"\n\n[[journey_section_figure_scores]]\nsection = "Separate value, identity, and absence."\nscore = 9.0\ncomment = "three runtime questions separated"\n\n[[journey_section_figure_scores]]\nsection = "Read expressions as object operations."\nscore = 9.0\ncomment = "syntax enters the data-model method surface"\n\n[[journey_section_figure_scores]]\nsection = "Choose between paths."\nscore = 9.0\ncomment = "branch map independent of one if-statement example"\n\n[[journey_section_figure_scores]]\nsection = "Name and shape decisions."\nscore = 9.0\ncomment = "name-vs-shape decision map"\n\n[[journey_section_figure_scores]]\nsection = "Stop as soon as the answer is known."\nscore = 9.0\ncomment = "early boundary leaves tail unread"\n\n[[journey_section_figure_scores]]\nsection = "Choose the right loop shape."\nscore = 9.0\ncomment = "loop choice by stopping rule"\n\n[[journey_section_figure_scores]]\nsection = "See the protocol behind `for`."\nscore = 9.5\ncomment = "for surface mapped to iter()/next()/StopIteration"\n\n[[journey_section_figure_scores]]\nsection = "Compose lazy value streams."\nscore = 9.0\ncomment = "consumer pull drives the lazy pipeline"\n\n[[journey_section_figure_scores]]\nsection = "Pick the container that matches the question."\nscore = 9.0\ncomment = "list/tuple/dict/set per question"\n\n[[journey_section_figure_scores]]\nsection = "Move between shapes deliberately."\nscore = 9.0\ncomment = "input → transform → result"\n\n[[journey_section_figure_scores]]\nsection = "Cross text and data boundaries."\nscore = 9.0\ncomment = "text in, structured value out"\n\n[[journey_section_figure_scores]]\nsection = "Start with functions as named behavior."\nscore = 9.0\ncomment = "concrete call, named body, and return boundary"\n\n[[journey_section_figure_scores]]\nsection = "Use functions as values."\nscore = 9.0\ncomment = "second name binds same function"\n\n[[journey_section_figure_scores]]\nsection = "Bundle behavior with state."\nscore = 9.0\ncomment = "class groups state + methods"\n\n[[journey_section_figure_scores]]\nsection = "Keep runtime and static analysis separate."\nscore = 9.0\ncomment = "runtime track separated from static-tool track"\n\n[[journey_section_figure_scores]]\nsection = "Describe realistic data shapes."\nscore = 9.0\ncomment = "field, variant, and absence contracts in one map"\n\n[[journey_section_figure_scores]]\nsection = "Scale annotations for reusable libraries."\nscore = 9.0\ncomment = "caller contracts survive library API boundary"\n\n[[journey_section_figure_scores]]\nsection = "Make failure explicit."\nscore = 9.0\ncomment = "failure shapes mapped to explicit signals"\n\n[[journey_section_figure_scores]]\nsection = "Control resource and module boundaries."\nscore = 9.0\ncomment = "resource/module/env boundaries shown together"\n\n[[journey_section_figure_scores]]\nsection = "Handle operations that outlive one expression."\nscore = 9.0\ncomment = "operation boundary returns evidence later"\n\n# Curated example quality scores.\n[[example_quality_scores]]\nslug = "hello-world"\nscore = 7.1\ncomment = "waived: traditional smallest complete program"\n\n[[example_quality_scores]]\nslug = "values"\nscore = 9.0\ncomment = "object/type/operation map with prerequisite graph edges"\n\n[[example_quality_scores]]\nslug = "literals"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich, note-heavy, multi-cell"\n\n[[example_quality_scores]]\nslug = "numbers"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "booleans"\nscore = 9.0\ncomment = "truth operations, short-circuiting, bool-as-int footgun"\n\n[[example_quality_scores]]\nslug = "operators"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich, note-heavy, multi-cell"\n\n[[example_quality_scores]]\nslug = "none"\nscore = 9.0\ncomment = "None, mapping default, and exception absence boundaries"\n\n[[example_quality_scores]]\nslug = "variables"\nscore = 9.0\ncomment = "binding, rebinding, augmented-assignment, lifecycle graph"\n\n[[example_quality_scores]]\nslug = "constants"\nscore = 9.0\ncomment = "constant convention, Final boundary, runtime rebinding evidence"\n\n[[example_quality_scores]]\nslug = "truthiness"\nscore = 9.0\ncomment = "truth-value categories, explicit-comparison boundary, graph-linked"\n\n[[example_quality_scores]]\nslug = "equality-and-identity"\nscore = 9.0\ncomment = "== versus is, singleton check, identity-cache warning"\n\n[[example_quality_scores]]\nslug = "mutability"\nscore = 9.0\ncomment = "in-place mutation, rebinding, alias visibility"\n\n[[example_quality_scores]]\nslug = "object-lifecycle"\nscore = 9.0\ncomment = "references, rebinding, and last-reference boundary"\n\n[[example_quality_scores]]\nslug = "strings"\nscore = 9.0\ncomment = "Unicode text, immutability, encoding boundary"\n\n[[example_quality_scores]]\nslug = "bytes-and-bytearray"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "string-formatting"\nscore = 9.0\ncomment = "f-string expression, format spec, debug/repr boundary"\n\n[[example_quality_scores]]\nslug = "conditionals"\nscore = 9.0\ncomment = "branch ordering, truthiness, and ternary boundary"\n\n[[example_quality_scores]]\nslug = "guard-clauses"\nscore = 9.0\ncomment = "nested contrast plus flattened guard path"\n\n[[example_quality_scores]]\nslug = "assignment-expressions"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "for-loops"\nscore = 9.0\ncomment = "direct iteration, range, and enumerate progression"\n\n[[example_quality_scores]]\nslug = "break-and-continue"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "loop-else"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "iterating-over-iterables"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "iterators"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "iterator-vs-iterable"\nscore = 9.0\ncomment = "graph-rich"\n\n[[example_quality_scores]]\nslug = "sentinel-iteration"\nscore = 9.0\ncomment = "call-until-marker shape contrasted with manual while"\n\n[[example_quality_scores]]\nslug = "match-statements"\nscore = 9.0\ncomment = "shape dispatch and guarded pattern payoff"\n\n[[example_quality_scores]]\nslug = "advanced-match-patterns"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "while-loops"\nscore = 9.0\ncomment = "state-driven repetition contrasted with for loops"\n\n[[example_quality_scores]]\nslug = "lists"\nscore = 9.0\ncomment = "ordered mutable sequence operations plus tuple/set/copy boundaries"\n\n[[example_quality_scores]]\nslug = "tuples"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "unpacking"\nscore = 9.0\ncomment = "sequence, starred, and mapping unpacking shapes"\n\n[[example_quality_scores]]\nslug = "dicts"\nscore = 9.0\ncomment = "lookup/default/update/delete dictionary boundaries"\n\n[[example_quality_scores]]\nslug = "sets"\nscore = 9.0\ncomment = "uniqueness, membership, set algebra, ordering boundary"\n\n[[example_quality_scores]]\nslug = "slices"\nscore = 9.0\ncomment = "range bounds, step, reverse, and unchanged-source evidence"\n\n[[example_quality_scores]]\nslug = "comprehensions"\nscore = 9.0\ncomment = "loop-equivalent collection building with filter/projection boundaries"\n\n[[example_quality_scores]]\nslug = "comprehension-patterns"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "sorting"\nscore = 9.0\ncomment = "key functions, reverse order, sorted/list.sort boundary"\n\n[[example_quality_scores]]\nslug = "collections-module"\nscore = 9.0\ncomment = "Counter, defaultdict, deque, namedtuple mapped to data shapes"\n\n[[example_quality_scores]]\nslug = "copying-collections"\nscore = 9.0\ncomment = "alias, shallow copy, and deepcopy boundaries"\n\n[[example_quality_scores]]\nslug = "functions"\nscore = 9.0\ncomment = "definition/call/return/default-boundary walkthrough"\n\n[[example_quality_scores]]\nslug = "keyword-only-arguments"\nscore = 9.0\ncomment = "call-site readability and boolean flag boundary"\n\n[[example_quality_scores]]\nslug = "positional-only-parameters"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "args-and-kwargs"\nscore = 9.0\ncomment = "flexible argument collection plus forwarding/API-boundary graph"\n\n[[example_quality_scores]]\nslug = "multiple-return-values"\nscore = 9.0\ncomment = "tuple return plus unpacking boundary"\n\n[[example_quality_scores]]\nslug = "closures"\nscore = 9.0\ncomment = "closed-over state, factory payoff, late-binding boundary"\n\n[[example_quality_scores]]\nslug = "partial-functions"\nscore = 9.0\ncomment = "before/after callable adaptation and introspection"\n\n[[example_quality_scores]]\nslug = "scope-global-nonlocal"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "recursion"\nscore = 9.0\ncomment = "base case and recursive tree-shape payoff"\n\n[[example_quality_scores]]\nslug = "lambdas"\nscore = 9.0\ncomment = "expression callable use contrasted with named def reuse"\n\n[[example_quality_scores]]\nslug = "generators"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "yield-from"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "generator-expressions"\nscore = 9.0\ncomment = "eager list contrast, lazy one-pass consumption, reducer use"\n\n[[example_quality_scores]]\nslug = "itertools"\nscore = 9.0\ncomment = "lazy standard-library pipeline and iterator-composition payoff"\n\n[[example_quality_scores]]\nslug = "decorators"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "classes"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "inheritance-and-super"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "classmethods-and-staticmethods"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "dataclasses"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "properties"\nscore = 9.0\ncomment = "managed attribute API without caller syntax change"\n\n[[example_quality_scores]]\nslug = "special-methods"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich, note-heavy, multi-cell"\n\n[[example_quality_scores]]\nslug = "truth-and-size"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "container-protocols"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "callable-objects"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "operator-overloading"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "attribute-access"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "bound-and-unbound-methods"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "descriptors"\nscore = 9.0\ncomment = "descriptor lookup, __set_name__, and validation mechanics"\n\n[[example_quality_scores]]\nslug = "metaclasses"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "context-managers"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "delete-statements"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "exceptions"\nscore = 9.0\ncomment = "specific handling, else/finally cleanup, broad-catch boundary"\n\n[[example_quality_scores]]\nslug = "assertions"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "exception-chaining"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "exception-groups"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "warnings"\nscore = 9.0\ncomment = "capture and escalate soft failures with filters"\n\n[[example_quality_scores]]\nslug = "modules"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "import-aliases"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "packages"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "virtual-environments"\nscore = 9.0\ncomment = "standard venv workflow plus explicit Worker deployment boundary"\n\n[[example_quality_scores]]\nslug = "type-hints"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich, note-heavy, multi-cell"\n\n[[example_quality_scores]]\nslug = "runtime-type-checks"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "union-and-optional-types"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "type-aliases"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "typed-dicts"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "structured-data-shapes"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "literal-and-final"\nscore = 9.0\ncomment = "Literal value set, Final rebinding promise, runtime caveat"\n\n[[example_quality_scores]]\nslug = "callable-types"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "generics-and-typevar"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "paramspec"\nscore = 9.0\ncomment = "Callable erasure contrasted with ParamSpec-preserving decorator"\n\n[[example_quality_scores]]\nslug = "overloads"\nscore = 9.0\ncomment = "static overload stubs contrasted with one runtime implementation"\n\n[[example_quality_scores]]\nslug = "casts-and-any"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "newtype"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "protocols"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "abstract-base-classes"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "enums"\nscore = 9.0\ncomment = "closed symbolic choice set contrasted with raw strings"\n\n[[example_quality_scores]]\nslug = "regular-expressions"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich, note-heavy, multi-cell"\n\n[[example_quality_scores]]\nslug = "number-parsing"\nscore = 9.0\ncomment = "decimal, explicit base, and recoverable ValueError boundary"\n\n[[example_quality_scores]]\nslug = "custom-exceptions"\nscore = 9.0\ncomment = "domain error naming, raise point, recovery boundary"\n\n[[example_quality_scores]]\nslug = "json"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "logging"\nscore = 9.0\ncomment = "logger, handler, formatter, and threshold boundaries"\n\n[[example_quality_scores]]\nslug = "testing"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n\n[[example_quality_scores]]\nslug = "subprocesses"\nscore = 9.0\ncomment = "standard subprocess contract plus explicit Worker boundary"\n\n[[example_quality_scores]]\nslug = "threads-and-processes"\nscore = 9.0\ncomment = "thread/process executor contrast plus explicit Worker boundary"\n\n[[example_quality_scores]]\nslug = "networking"\nscore = 9.0\ncomment = "protocol byte boundary plus explicit socket runtime caveat"\n\n[[example_quality_scores]]\nslug = "datetime"\nscore = 9.0\ncomment = "date/time/delta parsing with timezone scope made explicit"\n\n[[example_quality_scores]]\nslug = "csv-data"\nscore = 9.0\ncomment = "DictReader, conversion, and DictWriter row boundary"\n\n[[example_quality_scores]]\nslug = "async-await"\nscore = 9.0\ncomment = "graph-rich, note-heavy"\n\n[[example_quality_scores]]\nslug = "async-iteration-and-context"\nscore = 9.0\ncomment = "criterion-level pass: graph-rich"\n' diff --git a/src/marginalia.py b/src/marginalia.py index faa6edd..1dcbe68 100644 --- a/src/marginalia.py +++ b/src/marginalia.py @@ -102,17 +102,22 @@ def scope_rings(c: Canvas) -> None: def closure_cell(c: Canvas) -> None: - """Inner function references a cell created by the outer call. - - Outer scope holds the `cell` (the captured `factor`); the inner function - keeps a reference into it, so the call survives the outer return. + """Closures · the call that made `double` has returned; its cell survives. + + The environment picture (Composing Programs / Python Tutor): frames + on the left, objects on the right. make_multiplier(2) ran, bound + factor → 2, and returned, so its frame is ghosted. double now names + a function object on the heap whose __closure__ still points into + that frame, which is why the captured factor outlives the call. The + one accent is that surviving reference. """ - c.frame(0, 16, 240, 96, label="make_multiplier") - c.tag(16, 32, "cell") - c.cell(16, 38, "factor=2", w=84, h=22) - c.frame(112, 38, 122, 60, label="multiply") - c.label(173, 76, "uses cell", anchor="middle") - c.closed_arrow(128, 76, 102, 56, emphasis=True) + c.env(0, 14, "make_multiplier(2) · returned", [("factor", "2")], ghost=True) + c.name_box(0, 80, "double") + c.closed_arrow(62, 92, 148, 92, emphasis=False) + c.frame(150, 62, 130, 48, label="function · multiply") + c.mono(215, 84, "value * factor") + c.label(114, 60, "__closure__", anchor="end") + c.closed_arrow(150, 74, 112, 32, emphasis=True) def slice_ruler(c: Canvas) -> None: @@ -365,9 +370,9 @@ def type_shape_catalog(c: Canvas) -> None: c.cell(0, 44, "data", w=62, h=24, soft=True) rows = [(8, "fields", "TypedDict"), (42, "variant", "Union"), (76, "absence", "Optional")] for y, question, shape in rows: - c.closed_arrow(62, 56, 108, y + 11, emphasis=(shape == "Union")) + c.closed_arrow(62, 56, 108, y + 11, emphasis=False) c.cell(110, y, question, w=78, h=22) - c.cell(198, y, shape, w=90, h=22, soft=(shape == "Union")) + c.cell(198, y, shape, w=90, h=22) def type_library_contract(c: Canvas) -> None: @@ -423,9 +428,9 @@ def reliability_signal_map(c: Canvas) -> None: c.cell(0, 52, "problem", w=70, h=24) rows = [(4, "assume", "assert"), (36, "recover", "except"), (68, "cause", "chain"), (100, "soft", "warn")] for y, kind, signal in rows: - c.closed_arrow(70, 64, 112, y + 11, emphasis=(signal == "except")) + c.closed_arrow(70, 64, 112, y + 11, emphasis=False) c.cell(114, y, kind, w=70, h=22) - c.cell(194, y, signal, w=72, h=22, soft=(signal == "except")) + c.cell(194, y, signal, w=72, h=22) def reliability_boundary_map(c: Canvas) -> None: @@ -468,9 +473,9 @@ def naming_decisions(c: Canvas) -> None: c.tag(0, 56, "dispatch shape") c.cell(0, 68, "value", w=70, h=22) for i, label in enumerate(["case int", "case [x,y]", "case _"]): - x = 96 + i * 70 - c.closed_arrow(70 if i == 0 else x - 12, 79, x, 79, emphasis=(i == 1)) - c.cell(x, 68, label, w=62, h=22, soft=(i == 1)) + x = 96 + i * 84 + c.closed_arrow(70 if i == 0 else x - 22, 79, x, 79, emphasis=False) + c.cell(x, 68, label, w=62, h=22) def early_exit(c: Canvas) -> None: @@ -523,10 +528,10 @@ def control_stop_boundary(c: Canvas) -> None: def iteration_loop_selector(c: Canvas) -> None: """Iteration journey · choose the loop shape from the stopping rule.""" c.cell(0, 40, "stop rule", w=86, h=24) - choices = [(6, "for", "exhausted", False), (40, "while", "condition", False), (74, "sentinel", "marker", True)] - for y, loop, rule, chosen in choices: - c.closed_arrow(86, 52, 128, y + 11, emphasis=chosen) - c.cell(130, y, loop, w=78, h=22, soft=chosen) + choices = [(6, "for", "exhausted"), (40, "while", "condition"), (74, "sentinel", "marker")] + for y, loop, rule in choices: + c.closed_arrow(86, 52, 128, y + 11, emphasis=False) + c.cell(130, y, loop, w=78, h=22) c.label(218, y + 15, rule) @@ -563,25 +568,51 @@ def iteration_lazy_pull(c: Canvas) -> None: # ─── Example figures (promoted from the gestalt) ────────────────────── -def variables_bind(c: Canvas) -> None: - """Variables · names bind to objects: the canonical Python picture.""" - c.bind(0, 6, "x", "int", "42", object_w=70, gap=20) +def _bind_figure(name: str, type_tag: str, value: str, *, name_w: int = 60, object_w: int = 70): + """Factory for the canonical name → object picture. + + One paint function per lesson, parametrised with that lesson's own + running name and value, so the mechanism is shared while the figure + matches the cell it sits under (rubric v1 wanted the match, v2 gave + it up to allow reuse; the factory gives both). + """ + + def paint(c: Canvas) -> None: + c.bind(0, 6, name, type_tag, value, object_w=object_w, gap=28, name_w=name_w) + + paint.__name__ = f"bind_{name.lower()}" + paint.__doc__ = f"Binding · {name} → {type_tag} {value}: the canonical name → object picture." + return paint + + +variables_bind = _bind_figure("message", "str", '"hi"') +constants_bind = _bind_figure("MAX_RETRIES", "int", "3", name_w=84) def call_stack(c: Canvas) -> None: - """Recursion · stacked frames of the same function with different arguments.""" - chain = [3, 2, 1, 0] - for i, n in enumerate(chain): - suffix = " ← base" if n == 0 else "" - c.cell(0, i * 22, f"factorial({n}){suffix}", w=180, h=20) - c.dashed(192, 90, 192, 18) - c.closed_arrow(192, 30, 192, 18, emphasis=True) + """Recursion · the frames alive at the deepest point of total(tree), and what each returns. + + The example's tree is 1 → (2, 3 → 4). When total reaches the leaf + with value 4, three frames of the same function are stacked, each + waiting on the one below; the accent is the return path that + unwinds them: 4, then 3 + 4 = 7, then 1 + 2 + 7 = 10. + """ + frames = [("total(tree)", "value 1", "returns 10"), ("total(child)", "value 3", "returns 7"), + ("total(child)", "value 4 · leaf", "returns 4")] + for i, (call, node, ret) in enumerate(frames): + y = i * 26 + c.cell(0, y, call, w=104, h=22) + c.cell(104, y, node, w=92, h=22, soft=True) + c.label(224, y + 15, ret) + c.closed_arrow(208, 74, 208, 12, emphasis=True) + + def decorator_rebind(c: Canvas) -> None: """Decorators · before: name binds to function. After @dec: name binds to wrapper.""" c.tag(0, 12, "before") - c.bind(0, 18, "f", "fn", "f₀", object_w=50, gap=20) + c.bind(0, 18, "f", "fn", "f₀", object_w=50, gap=26) c.tag(0, 70, "after @dec") c.name_box(0, 78, "f") c.closed_arrow(60, 90, 96, 90, emphasis=True) @@ -671,16 +702,23 @@ def list_append(c: Canvas) -> None: def dict_buckets(c: Canvas) -> None: - """Dictionaries · hashed buckets; collisions chain into a neighbouring slot.""" - c.tag(0, 12, "hash → bucket") - rows = [("0", '"a" → 1'), ("1", '"b" → 2'), ("2", '"c" → 3')] - for i, (idx, body) in enumerate(rows): - y = 18 + i * 24 - c.label(0, y + 16, idx, anchor="start") - c.cell(14, y, body, w=80, h=24) - c.closed_arrow(96, 54, 132, 54, emphasis=True) - c.cell(134, 42, '"d" → 4', w=80, h=24, soft=True) - c.label(218, 58, "collision", anchor="start") + """Dictionaries · hash(key) picks the slot; the cell's insert lands in an empty one. + + profile holds "name" and "language"; the cell adds "year". The hash + of the new key selects a slot directly, which is why neither the + insert nor profile["name"] scans the other entries. The accent is + the insert. + """ + c.tag(120, 6, "slots") + rows = [('"name" → "Ada"', False), ('"language" → "Python"', False), ('"year" → 1843', True)] + for i, (body, new) in enumerate(rows): + y = 12 + i * 26 + c.cell(120, y, body, w=150, h=22, soft=new) + c.label(278, y + 15, str(i), anchor="start") + c.cell(0, 64, 'hash("year")', w=78, h=22) + c.closed_arrow(78, 75, 118, 75, emphasis=True) + + # ─── Examples promoted from the gestalt: new paint code ────────────── @@ -764,14 +802,25 @@ def positional_only_separator(c: Canvas) -> None: c.label(120, 50, "positional or kw", anchor="middle") -def generator_ribbon(c: Canvas) -> None: - """Generators · execution paused between yields, resumed by next().""" - c.tag(0, 8, "paused between yields · resumed by next()") - c.ribbon(0, 16, 260, h=30, gates=[64, 136, 208], soft_segments=[(0, 64), (136, 208)]) - c.mono(32, 36, "…") - c.mono(100, 36, "yield") - c.mono(172, 36, "…") - c.mono(244, 36, "yield") +def generator_resume(c: Canvas) -> None: + """Generators · the cell's two next() calls on countdown(3), one ribbon each. + + State over time (the iterator-unroll pattern applied to a generator): + each row is the body's timeline after one more next(). The soft + segment is the code that has run so far, the gates are the yields, + and the local n beside the row is the value that survived the pause. + The gates are the figure's single accent system. + """ + gates = [60, 126, 192] + for gx in gates: + c.label(gx, 8, "yield", anchor="middle") + rows = [("3", "n=3"), ("2", "n=2")] + for i, (value, local) in enumerate(rows): + y = 14 + i * 34 + # The ribbon runs past the last gate: after the third yield the + # body still has `n -= 1` and the loop test left to run. + c.ribbon(0, y, 224, h=22, gates=gates, soft_segments=[(0, gates[i])]) + c.label(236, y + 15, f"next() → {value} · {local}") def truth_and_size(c: Canvas) -> None: @@ -847,24 +896,38 @@ def guard_clauses(c: Canvas) -> None: def bytes_vs_bytearray(c: Canvas) -> None: - """Bytes vs bytearray · frozen sequence of integers vs mutable counterpart.""" + """Bytes vs bytearray · the cell's own two bytes, frozen in one object and rewritten in the other. + + Header-plus-buffer drawing (the Rust Book / Go-slices convention): + the bytes are shown as the integer slots they are. b"py" is frozen; + packet is the same two bytes in a bytearray, and packet[0] = ord("P") + overwrites slot 0 in place — the caret is the one accent. + """ c.tag(0, 4, "bytes — frozen") - c.cell(0, 12, "b'\\\\x63\\\\x61\\\\x66'", w=160, h=24) - c.tag(0, 50, "bytearray — mutable") - c.cell(0, 58, "bytearray(b'\\\\x63\\\\x61')", w=180, h=24) - c.closed_arrow(180, 70, 218, 70, emphasis=True) - c.label(222, 67, ".append(0x66)", anchor="start") + c.cells(0, 12, ["0x70", "0x79"], w=44, h=22) + c.label(98, 27, 'b"py"') + c.tag(0, 48, "bytearray — mutable") + c.caret(22, 62, emphasis=True) + c.cells(0, 62, ["0x50", "0x79"], w=44, h=22) + c.label(98, 70, 'packet[0] = ord("P")') + c.label(98, 84, "bytearray(b'Py')") def sentinel_iteration(c: Canvas) -> None: - """Sentinel iteration · `iter(callable, sentinel)` calls until the sentinel returns.""" - c.cell(0, 22, "iter(read, '')", w=120, h=24) - c.closed_arrow(120, 34, 152, 34, emphasis=True) - c.cell(154, 0, "value", w=70, h=20) - c.cell(154, 22, "value", w=70, h=20) - c.cell(154, 44, "value", w=70, h=20) - c.cell(154, 66, "''", w=70, h=20, ghost=True) - c.label(228, 80, "sentinel · stop", anchor="start") + """Sentinel iteration · iter(read_chunk, "") calls read_chunk until it returns the sentinel. + + The cell's chunks are "py", "thon" and "": the first two are yielded, + the empty string is the sentinel that stops the loop and is never + yielded, which is why list(...) prints ['py', 'thon']. + """ + c.cell(0, 22, 'iter(read_chunk, "")', w=140, h=24) + c.closed_arrow(140, 34, 172, 34, emphasis=True) + c.cell(174, 0, '"py"', w=70, h=20) + c.cell(174, 22, '"thon"', w=70, h=20) + c.cell(174, 44, '""', w=70, h=20, ghost=True) + c.label(250, 58, "sentinel · stop", anchor="start") + + def partial_functions(c: Canvas) -> None: @@ -893,10 +956,10 @@ def args_kwargs(c: Canvas) -> None: def multiple_return(c: Canvas) -> None: """Multiple return values · the function returns a tuple; the caller unpacks it.""" c.cell(0, 0, "def f(): return a, b", w=180, h=24) - c.closed_arrow(90, 26, 90, 44, emphasis=True) - c.cell(58, 44, "(a, b)", w=64, h=22, soft=True) - c.closed_arrow(90, 68, 90, 86, emphasis=False) - c.cell(50, 86, "x, y", w=80, h=22) + c.closed_arrow(90, 26, 90, 48, emphasis=True) + c.cell(58, 48, "(a, b)", w=64, h=22, soft=True) + c.closed_arrow(90, 72, 90, 94, emphasis=False) + c.cell(50, 94, "x, y", w=80, h=22) def lambda_expression(c: Canvas) -> None: @@ -992,13 +1055,25 @@ def json_python_mapping(c: Canvas) -> None: c.mono(132, y, b, anchor="start", size=10) -def regex_anchors(c: Canvas) -> None: - """Regular expressions · anchors and quantifiers shape what the pattern matches.""" +def regex_groups(c: Canvas) -> None: + """Regular expressions · the cell's own pattern, its two capture groups landing on one record. + + findall returns only what the parentheses capture: ([A-Za-z]+) takes + the name and (\\d+) the score out of "Ada: 10". The groups are the + shaded stations of the pattern; dashed drops show where each one + lands in the text. No accent: the two groups are equals. + """ c.tag(0, 4, "pattern") - c.mono(0, 24, "^\\d{2}-\\d{2}$", anchor="start") - c.tag(0, 56, "input") - c.cell(0, 64, "", w=200, h=20) - c.cell(40, 64, "12-34", w=120, h=20, soft=True) + c.cell(0, 10, "([A-Za-z]+)", w=76, h=20, soft=True) + c.cell(76, 10, ": ", w=22, h=20) + c.cell(98, 10, "(\\d+)", w=40, h=20, soft=True) + c.cell(0, 56, "Ada", w=28, h=20, soft=True) + c.cell(28, 56, ": ", w=22, h=20) + c.cell(50, 56, "10", w=22, h=20, soft=True) + c.dashed(38, 30, 14, 56) + c.dashed(118, 30, 61, 56) + c.label(14, 90, "name", anchor="middle") + c.label(61, 90, "score", anchor="middle") def number_parse(c: Canvas) -> None: @@ -1011,14 +1086,35 @@ def number_parse(c: Canvas) -> None: def format_spec(c: Canvas) -> None: - """String formatting · the format spec is a railroad of named optional fields.""" - c.tag(0, 4, "format spec") - stations = [("align", 36), ("sign", 30), ("width", 40), (",", 22), (".prec", 44), ("type", 32)] - x = 0 - for label_text, w in stations: - c.cell(x, 16, label_text, w=w, h=18) - x += w + 2 - c.label(0, 54, "{:>6,.2f}") + """String formatting · the format spec as a railroad: every station optional, order fixed. + + A railroad in the SQLite-diagram sense: the main rail runs left to + right through every station in the grammar's fixed order. Drawing a + bypass over all seven stations made 53 elements, so the figure + traces one path instead: the cell's own spec, 05.1f. Stations it + visits are shaded; stations it skips are ghosted under a bypass rail. + """ + stations = [("align", 38, False), ("sign", 32, False), ("0", 14, True), ("width", 38, True), + (",", 14, False), ("precision", 62, True), ("type", 32, True)] + rail_y = 36 + lead, bypass = 10, 18 + c.hairline(0, rail_y, lead, rail_y) + x = lead + for text, w, visited in stations: + if not visited: + # bypass rail: this spec skips the station, so the path + # goes up, across and down around it and the station ghosts + c.hairline(x, rail_y, x, rail_y - bypass) + c.hairline(x, rail_y - bypass, x + w + 12, rail_y - bypass) + c.hairline(x + w + 12, rail_y - bypass, x + w + 12, rail_y) + # rail through the station + c.hairline(x, rail_y, x + 6, rail_y) + c.cell(x + 6, rail_y - 9, text, w=w, h=18, soft=visited, ghost=not visited) + c.hairline(x + w + 6, rail_y, x + w + 12, rail_y) + x += w + 12 + 6 + c.hairline(x - 6, rail_y, x + lead - 6, rail_y) + c.tag(0, 62, "this cell") + c.mono(0, 76, "{score:05.1f}", anchor="start") def truthy_check(c: Canvas) -> None: @@ -1069,30 +1165,6 @@ def tuple_frozen(c: Canvas) -> None: c.dashed(190, 24, 270, 24) -def value_types(c: Canvas) -> None: - """Values · every literal is a typed object: int, str, list, dict each carry their behaviour.""" - rows = [("int", "42"), ("str", '"hi"'), ("list", "[1,2,3]"), ("dict", "{k:v}")] - for i, (t, v) in enumerate(rows): - y = i * 30 - c.object_box(0, y, t, v, w=160, h=26, tag_position="inside") - - -def literal_forms(c: Canvas) -> None: - """Literals · each type has its own literal spellings; the source spelling determines the value type.""" - rows = [ - ("int", "42 · 0x2a · 0b101"), - ("float", "3.14 · 1e-3"), - ("str", '"hi" · \'hi\''), - ("list", "[1, 2, 3]"), - ("dict", "{k: v}"), - ("set", "{1, 2, 3}"), - ] - for i, (t, spellings) in enumerate(rows): - y = i * 22 - c.cell(0, y, t, w=50, h=20, soft=True) - c.cell(52, y, spellings, w=200, h=20) - - def function_with_body(c: Canvas) -> None: """Functions · `def greet(name): return "Hello, " + name` takes input, computes, returns output.""" c.closed_arrow(0, 36, 30, 36, emphasis=False) @@ -1208,14 +1280,6 @@ def subprocess_spawn(c: Canvas) -> None: c.cell(254, 22, "output", w=70, h=24) -def logging_levels(c: Canvas) -> None: - """Logging · five levels; messages below the configured threshold are dropped.""" - levels = [("CRITICAL", "50"), ("ERROR", "40"), ("WARNING", "30"), ("INFO", "20"), ("DEBUG", "10")] - for i, (name, num) in enumerate(levels): - c.cell(0, i * 22, name, w=120, h=20) - c.cell(122, i * 22, num, w=40, h=20, soft=True) - - def aaa_pattern(c: Canvas) -> None: """Testing · arrange-act-assert: set up, run the behavior, compare the result.""" rows = [("arrange", "set up state"), ("act", "perform behavior"), ("assert", "compare result")] @@ -1320,14 +1384,6 @@ def isinstance_check(c: Canvas) -> None: c.cell(172, 50, "False", w=60, h=20) -def collections_containers(c: Canvas) -> None: - """Collections module · four specialised containers for shapes the built-in types don't cover well.""" - rows = [("deque", "fast appends both ends"), ("Counter", "key → count"), ("defaultdict", "missing key default"), ("namedtuple", "tuple with names")] - for i, (name, role) in enumerate(rows): - c.cell(0, i * 22, name, w=110, h=20) - c.cell(112, i * 22, role, w=170, h=20, soft=True) - - def typed_dict_shape(c: Canvas) -> None: """Structured data shapes · TypedDict names each key's value type; the dict obeys the declared shape.""" c.frame(0, 0, 200, 86, label="User TypedDict") @@ -1374,7 +1430,7 @@ def object_lifecycle(c: Canvas) -> None: def type_alias_name(c: Canvas) -> None: """Type aliases · complex annotation collapses to a single readable name.""" c.cell(0, 30, "dict[str, list[tuple[int, str]]]", w=240, h=24, ghost=True) - c.closed_arrow(120, 54, 120, 70, emphasis=True) + c.closed_arrow(120, 54, 120, 76, emphasis=True) c.label(96, 66, "type Index = …", anchor="middle") c.cell(80, 76, "Index", w=80, h=24, soft=True) @@ -1385,9 +1441,9 @@ def match_dispatch_ladder(c: Canvas) -> None: cases = ["case 0:", "case [x, y]:", "case Point(0, _):", "case _:"] for i, txt in enumerate(cases): c.cell(0, 30 + i * 22, txt, w=170, h=20) - c.dashed(186, 32, 186, 122) + c.dashed(186, 32, 186, 104) c.dot(186, 74) - c.closed_arrow(186, 110, 186, 124, emphasis=True) + c.closed_arrow(186, 104, 186, 124, emphasis=True) c.label(196, 76, "first match", anchor="start") @@ -1428,8 +1484,8 @@ def container_methods(c: Canvas) -> None: for i, (syntax, method) in enumerate(rows): y = i * 28 c.cell(0, y, syntax, w=120, h=22) - c.closed_arrow(120, y + 11, 158, y + 11, emphasis=(i == 1)) - c.cell(160, y, method, w=110, h=22, soft=(i == 1)) + c.closed_arrow(120, y + 11, 158, y + 11, emphasis=False) + c.cell(160, y, method, w=110, h=22) def structured_shapes(c: Canvas) -> None: @@ -1442,12 +1498,76 @@ def structured_shapes(c: Canvas) -> None: c.cell(134, y, tradeoff, w=130, h=22, soft=(i == 2)) # Registry: figure_name -> (paint_fn, viewbox_w, viewbox_h) +def value_type_lookup(c: Canvas) -> None: + """Values · text names a str object; the type, not the name, carries upper(). + + The cell's own binding, then the lookup the cell prints: + type(text) is str, and str is where upper() (which the next cell + calls) lives. The dashed hop is type(). No accent: the binding and + the hop are equals. + """ + c.bind(0, 30, "text", "str", '"python"', object_w=80, gap=28) + c.dashed(168, 46, 204, 46) + c.label(186, 40, "type()", anchor="middle") + c.frame(206, 22, 72, 44, label="class str") + c.mono(242, 48, "upper()") + + +def logging_threshold(c: Canvas) -> None: + """Logging · records cross the handler's level gate; below it they are dropped. + + The cell's own three records: debug falls below the handler's INFO + level and stops at the gate (dashed, ghosted); info and warning pass + through to stdout. The gate is the one accent. + """ + c.tag(0, 6, "records") + c.tag(202, 6, "stdout") + c.frame(96, 0, 76, 112, label="handler") + for y, name, passes in [(14, "debug", False), (46, "info", True), (78, "warning", True)]: + c.cell(0, y, name, w=64, h=22, ghost=not passes) + if passes: + c.closed_arrow(64, y + 11, 200, y + 11, emphasis=False) + c.cell(202, y, name, w=64, h=22, soft=True) + else: + c.dashed(64, y + 11, 130, y + 11) + c.gate(134, 4, 108) + c.label(134, 124, "level=INFO", anchor="middle") + + +def literal_forms(c: Canvas) -> None: + """Literals · each type has its own literal spellings; the source spelling determines the value type.""" + rows = [ + ("int", "42 · 0x2a · 0b101"), + ("float", "3.14 · 1e-3"), + ("str", '"hi" · \'hi\''), + ("list", "[1, 2, 3]"), + ("dict", "{k: v}"), + ("set", "{1, 2, 3}"), + ] + for i, (t, spellings) in enumerate(rows): + y = i * 22 + c.cell(0, y, t, w=50, h=20, soft=True) + c.cell(52, y, spellings, w=200, h=20) + + +def collections_containers(c: Canvas) -> None: + """Collections module · four specialised containers for shapes the built-in types don't cover well.""" + rows = [("deque", "fast appends both ends"), ("Counter", "key → count"), ("defaultdict", "missing key default"), ("namedtuple", "tuple with names")] + for i, (name, role) in enumerate(rows): + c.cell(0, i * 22, name, w=110, h=20) + c.cell(112, i * 22, role, w=170, h=20, soft=True) + + FIGURES: dict[str, tuple[Callable[[Canvas], None], int, int]] = { + "value-type-lookup": (value_type_lookup, 280, 80), + "logging-threshold": (logging_threshold, 266, 130), + "literal-forms": (literal_forms, 252, 132), + "collections-containers": (collections_containers, 284, 92), "aliasing-mutation": (aliasing_mutation, 220, 175), "tuple-no-mutation": (tuple_no_mutation, 220, 185), "iterator-unroll": (iterator_unroll, 220, 130), "scope-rings": (scope_rings, 216, 116), - "closure-cell": (closure_cell, 240, 120), + "closure-cell": (closure_cell, 280, 116), "slice-ruler": (slice_ruler, 232, 120), "branch-fork": (branch_fork, 232, 100), "loop-repetition": (loop_repetition, 260, 100), @@ -1483,7 +1603,7 @@ def structured_shapes(c: Canvas) -> None: "reliability-boundary-map": (reliability_boundary_map, 286, 110), "reliability-operation-boundary": (reliability_operation_boundary, 354, 104), # Control flow + Iteration coverage gap (see audit) - "naming-decisions": (naming_decisions, 310, 98), + "naming-decisions": (naming_decisions, 330, 98), "early-exit": (early_exit, 144, 116), "lazy-stream": (lazy_stream, 300, 56), "control-decision-map": (control_decision_map, 244, 104), @@ -1493,8 +1613,9 @@ def structured_shapes(c: Canvas) -> None: "iteration-protocol-map": (iteration_protocol_map, 306, 132), "iteration-lazy-pull": (iteration_lazy_pull, 360, 112), # Promoted from the gestalt — wired to example pages via ATTACHMENTS - "variables-bind": (variables_bind, 180, 44), - "call-stack": (call_stack, 200, 100), + "variables-bind": (variables_bind, 170, 44), + "constants-bind": (constants_bind, 194, 44), + "call-stack": (call_stack, 292, 80), "decorator-rebind": (decorator_rebind, 232, 110), "mro-chain": (mro_chain, 200, 152), "dataclass-fields": (dataclass_fields, 312, 76), @@ -1503,7 +1624,7 @@ def structured_shapes(c: Canvas) -> None: "unpacking-bind": (unpacking_bind, 152, 80), "comprehension-equivalence": (comprehension_equivalence, 280, 76), "list-append": (list_append, 220, 36), - "dict-buckets": (dict_buckets, 270, 88), + "dict-buckets": (dict_buckets, 288, 92), # Newly designed paint code for examples that lacked a figure "number-lines": (number_lines, 260, 78), "expression-tree": (expression_tree, 220, 92), @@ -1512,7 +1633,7 @@ def structured_shapes(c: Canvas) -> None: "sort-stability": (sort_stability, 270, 100), "kw-only-separator": (kw_only_separator, 200, 56), "positional-only-separator": (positional_only_separator, 200, 56), - "generator-ribbon": (generator_ribbon, 260, 50), + "generator-resume": (generator_resume, 318, 72), "truth-and-size": (truth_and_size, 232, 70), "descriptor-protocol": (descriptor_protocol, 222, 76), "bound-unbound": (bound_unbound, 296, 56), @@ -1520,12 +1641,12 @@ def structured_shapes(c: Canvas) -> None: "callable-objects": (callable_objects, 220, 44), "attribute-lookup": (attribute_lookup, 242, 70), "guard-clauses": (guard_clauses, 264, 104), - "bytes-vs-bytearray": (bytes_vs_bytearray, 308, 86), - "sentinel-iteration": (sentinel_iteration, 320, 92), + "bytes-vs-bytearray": (bytes_vs_bytearray, 200, 90), + "sentinel-iteration": (sentinel_iteration, 330, 70), "partial-functions": (partial_functions, 334, 36), # Third coverage push: 24 more figures "args-kwargs": (args_kwargs, 280, 68), - "multiple-return": (multiple_return, 180, 110), + "multiple-return": (multiple_return, 180, 120), "lambda-expression": (lambda_expression, 170, 76), "property-fork": (property_fork, 232, 72), "metaclass-triangle": (metaclass_triangle, 300, 60), @@ -1535,14 +1656,13 @@ def structured_shapes(c: Canvas) -> None: "enum-members": (enum_members, 280, 60), "datetime-instant": (datetime_instant, 280, 88), "json-python-mapping": (json_python_mapping, 220, 116), - "regex-anchors": (regex_anchors, 200, 92), + "regex-groups": (regex_groups, 140, 96), "number-parse": (number_parse, 204, 64), - "format-spec": (format_spec, 220, 64), + "format-spec": (format_spec, 372, 82), "truthy-check": (truthy_check, 240, 70), "boolean-truth-table": (boolean_truth_table, 132, 64), "set-buckets": (set_buckets, 156, 90), "tuple-frozen": (tuple_frozen, 280, 48), - "value-types": (value_types, 160, 116), "yield-delegation": (yield_delegation, 240, 84), "itertools-chain": (itertools_chain, 246, 82), "assertion-check": (assertion_check, 304, 76), @@ -1553,7 +1673,6 @@ def structured_shapes(c: Canvas) -> None: "package-tree": (package_tree, 240, 76), "venv-boundary": (venv_boundary, 274, 76), "subprocess-spawn": (subprocess_spawn, 324, 60), - "logging-levels": (logging_levels, 164, 124), "aaa-pattern": (aaa_pattern, 250, 80), "socket-byte-boundary": (socket_byte_boundary, 364, 46), "gil-lanes": (gil_lanes, 300, 138), @@ -1564,7 +1683,6 @@ def structured_shapes(c: Canvas) -> None: "literal-constrained": (literal_constrained, 144, 76), "callable-type": (callable_type, 352, 42), "isinstance-check": (isinstance_check, 232, 76), - "collections-containers": (collections_containers, 284, 92), "container-methods": (container_methods, 272, 82), "typed-dict-shape": (typed_dict_shape, 200, 92), "structured-shapes": (structured_shapes, 266, 82), @@ -1577,7 +1695,6 @@ def structured_shapes(c: Canvas) -> None: "match-pattern-variants": (match_pattern_variants, 272, 96), "loop-else-gate": (loop_else_gate, 312, 76), # Sixth pass: lift the lingering 8.0-band figures with slug-specific paint - "literal-forms": (literal_forms, 252, 132), "function-with-body": (function_with_body, 334, 68), } diff --git a/src/marginalia_grammar.py b/src/marginalia_grammar.py index bf548cf..e47de23 100644 --- a/src/marginalia_grammar.py +++ b/src/marginalia_grammar.py @@ -25,19 +25,44 @@ PAD_BOTTOM = 14 # ─── Palette ─────────────────────────────────────────────────────────── -# Aligned with public/site.css design tokens. These are the only -# colours figures may use; cards never pick a colour directly. -# INK site --text (warm dark brown) -# INK_SOFT site --muted (--text at 70%) -# EMPHASIS site --accent (the brand orange) -# SOFT_FILL site --accent-soft (--accent at ~8%) -INK = "#521000" -INK_SOFT = "rgba(82, 16, 0, 0.7)" -EMPHASIS = "#FF4801" -# A neutral warm tint built from --text at 5%. Object boxes need to read as -# quiet containers; tinting them with --accent-soft made every box look -# highlighted, which broke the "emphasis is scarce" rule. -SOFT_FILL = "rgba(82, 16, 0, 0.05)" +# Figures paint with CSS custom properties, not literal colours. Inline +# SVG inherits custom properties from the page, so one paint function +# renders correctly in light mode, in dark mode, and on the social +# cards: whichever stylesheet wraps the figure decides the ink. The +# four names below are the only colours figures may use; cards never +# pick a colour directly. +# INK --fig-ink the page's --text +# INK_SOFT --fig-ink-soft the page's --muted +# EMPHASIS --fig-accent the page's --accent +# SOFT_FILL --fig-soft a quiet 5% tint of the ink (object boxes +# must read as containers, not highlights) +INK = "var(--fig-ink)" +INK_SOFT = "var(--fig-ink-soft)" +EMPHASIS = "var(--fig-accent)" +SOFT_FILL = "var(--fig-soft)" + +# The concrete values behind those names, one set per colour scheme. +# public/site.css must define both (a contract checks it); the gestalt +# review pages and the social-card shell embed the light set through +# figure_token_css() so a figure never renders with undefined ink. +FIGURE_TOKENS_LIGHT = { + "--fig-ink": "#521000", + "--fig-ink-soft": "rgba(82, 16, 0, 0.7)", + "--fig-accent": "#FF4801", + "--fig-soft": "rgba(82, 16, 0, 0.05)", +} +FIGURE_TOKENS_DARK = { + "--fig-ink": "#F3E7DC", + "--fig-ink-soft": "rgba(243, 231, 220, 0.72)", + "--fig-accent": "#FF6B2E", + "--fig-soft": "rgba(243, 231, 220, 0.07)", +} + + +def figure_token_css(tokens: dict[str, str]) -> str: + """CSS declarations for one token set, ready to drop inside a rule.""" + return " ".join(f"{name}: {value};" for name, value in tokens.items()) + # ─── Stroke weights ──────────────────────────────────────────────────── W_HAIRLINE = 0.6 @@ -54,6 +79,11 @@ TICK_LEN = 6 NODE_R = 14 ARROW_CLOSED = 7 +# Shortest arrow (shaft plus head) that still reads as an arrow. Below +# this an arrow is mostly head — the naming-decisions "case" chain drew +# 12-unit arrows that looked like stray wedges between boxes. Widen the +# gap between the boxes instead of shrinking the arrow. +ARROW_MIN = 20 NAME_W = 60 NAME_H = 24 @@ -63,9 +93,15 @@ WORD_W = 44 # ─── Typography ──────────────────────────────────────────────────────── +# Mono and sans follow the page's own stacks (public/site.css sets body +# text to system-ui and code to ui-monospace) so a figure's labels are +# set in the same faces as the code cell beside it. The site loads no +# web fonts, so asking for JetBrains Mono or Source Sans here only +# meant "whatever the OS falls back to". The italic serif stays: it is +# the one deliberate contrast, reserved for identifiers. FONT_SERIF = "'Iowan Old Style', Charter, Georgia, serif" -FONT_MONO = "'JetBrains Mono', 'IBM Plex Mono', Menlo, monospace" -FONT_SANS = "-apple-system, 'Source Sans Pro', sans-serif" +FONT_MONO = "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" +FONT_SANS = "system-ui, -apple-system, 'Segoe UI', sans-serif" SIZE_BODY = 11 SIZE_MONO = 10 SIZE_SMALL = 9 @@ -74,12 +110,20 @@ # ─── Text metrics ────────────────────────────────────────────────────── # Single source of truth for character advance. Paint code uses -# MONO_ADVANCE (the font's exact advance) to compute positions; the -# geometry contracts use BBOX_ADVANCE (deliberately conservative -# over-estimates) to detect clipping and collision. Keeping both here — -# instead of one in a paint comment and one in the test file — means a -# recalibration happens in one place and both consumers move together. -MONO_ADVANCE = 0.6 # JetBrains Mono advances 600/1000 units per em. +# MONO_ADVANCE to compute positions; the geometry contracts use +# BBOX_ADVANCE (deliberately conservative over-estimates) to detect +# clipping and collision. Keeping both here — instead of one in a paint +# comment and one in the test file — means a recalibration happens in +# one place and both consumers move together. +# +# MONO_ADVANCE is not a guess about the visitor's font: every mono run +# is emitted with textLength = len * MONO_ADVANCE * size and +# lengthAdjust="spacing", so the browser spaces the glyphs to exactly +# that advance whatever face the stack resolves to. Menlo and SF Mono +# advance 0.6em natively; Consolas advances 0.55em and gets 0.05em of +# letter-spacing. Dividers computed from MONO_ADVANCE therefore land on +# the right character on every platform. +MONO_ADVANCE = 0.6 BBOX_ADVANCE = { "mono": 0.62, # JetBrains Mono / IBM Plex Mono "sans_upper": 0.65, # Source Sans Pro uppercase (tag font) @@ -128,6 +172,11 @@ class Canvas: # shaft+head pair and a standalone gate line are indistinguishable. _accents: int = 0 _gates_painted: bool = False + # Length of every closed arrow painted, for the minimum-length + # contract (ARROW_MIN). Recorded here because the shaft is shortened + # by the head in the emitted SVG, so the drawn geometry understates + # the arrow the author asked for. + _arrow_lengths: list[float] = field(default_factory=list) def accent_count(self) -> int: """Number of accent marks on the canvas, per the scarcity rule. @@ -186,6 +235,7 @@ def closed_arrow(self, x1, y1, x2, y2, *, emphasis=False): weight = W_EMPHASIS if emphasis else W_STROKE dx, dy = x2 - x1, y2 - y1 L = math.hypot(dx, dy) or 1 + self._arrow_lengths.append(L) ux, uy = dx / L, dy / L end_x, end_y = x2 - ARROW_CLOSED * ux, y2 - ARROW_CLOSED * uy self._line(x1, y1, end_x, end_y, color=color, weight=weight) @@ -194,17 +244,23 @@ def closed_arrow(self, x1, y1, x2, y2, *, emphasis=False): self._add(f'') # ── text ────────────────────────────────────────────────────────── - def _text(self, x, y, s, *, family, size, anchor, color, italic=False, tracking=None): + def _text(self, x, y, s, *, family, size, anchor, color, italic=False, tracking=None, length=None): attrs = [f'x="{x}"', f'y="{y}"', f'font-family="{family}"', f'font-size="{size}"', f'fill="{color}"', f'text-anchor="{anchor}"'] if italic: attrs.append('font-style="italic"') if tracking: attrs.append(f'letter-spacing="{tracking}"') + if length: + attrs.append(f'textLength="{length:g}" lengthAdjust="spacing"') self._add(f"{xml_escape(s)}") def mono(self, x, y, s, *, anchor="middle", size=SIZE_MONO, color=INK): - self._text(x, y, s, family=FONT_MONO, size=size, anchor=anchor, color=color) + # Pin the run to MONO_ADVANCE per character so positions computed + # from it (mono_divider, register ticks under a literal) hold on + # every platform's monospace face. + length = len(s) * MONO_ADVANCE * size if s else None + self._text(x, y, s, family=FONT_MONO, size=size, anchor=anchor, color=color, length=length) def ident(self, x, y, s, *, anchor="middle", color=INK): self._text(x, y, s, family=FONT_SERIF, size=SIZE_BODY, anchor=anchor, color=color, italic=True) @@ -217,14 +273,19 @@ def tag(self, x, y, s, *, anchor="start"): anchor=anchor, color=INK_SOFT, tracking="0.5") # ── words ───────────────────────────────────────────────────────── - def name_box(self, x, y, name): - """Open rect with italic identifier. Returns right-edge midpoint.""" + def name_box(self, x, y, name, *, w=NAME_W): + """Open rect with italic identifier. Returns right-edge midpoint. + + Pass a wider `w` when the running example's name is longer than + the default box (MAX_RETRIES needs ~84); the figure should carry + the lesson's own names, not placeholders chosen to fit the box. + """ self._add( - f'' ) - self.ident(x + NAME_W / 2, y + NAME_H / 2 + BASELINE, name) - return (x + NAME_W, y + NAME_H / 2) + self.ident(x + w / 2, y + NAME_H / 2 + BASELINE, name) + return (x + w, y + NAME_H / 2) def object_box(self, x, y, type_tag, value, *, w=OBJECT_W, h=OBJECT_H, soft=True, tag_position="above"): """Filled rect with type tag and value centered. Returns left-edge midpoint. @@ -345,13 +406,40 @@ def lane(self, y, *, x0=20, x1=300, label=None): self.tag(x0 - 6, y + 3, label, anchor="end") # ── phrases ─────────────────────────────────────────────────────── - def bind(self, x, y, name, type_tag, value, *, object_w=OBJECT_W, gap=40): - """name → object. The foundational picture.""" - nx, ny = self.name_box(x, y + (OBJECT_H - NAME_H) / 2, name) + def bind(self, x, y, name, type_tag, value, *, object_w=OBJECT_W, gap=40, name_w=NAME_W): + """name → object. The foundational picture. + + `gap` is the space between the two boxes; the arrow inside it is + gap - 4 long, so keep gap >= ARROW_MIN + 4. + """ + nx, ny = self.name_box(x, y + (OBJECT_H - NAME_H) / 2, name, w=name_w) ox, oy = self.object_box(nx + gap, y, type_tag, value, w=object_w) self.closed_arrow(nx + 2, ny, ox - 2, oy) return (x, y, nx + gap + object_w, y + OBJECT_H) + def env(self, x, y, label, bindings, *, name_w=52, value_w=44, row_h=20, gap=6, pad=8, ghost=False): + """An environment frame with its bindings — the Python Tutor picture. + + Each binding is (name, value): the name is an italic identifier, + the value sits in a mono cell beside it. Pass value "" to leave + the cell empty so the caller can arrow from it to a heap object. + Draw the frame ghost when the call has returned; the cells stay + solid because bindings can outlive their frame (that is what a + closure is). Returns (right_x, bottom_y, anchors) where + anchors[i] is the right-edge midpoint of binding i's value cell. + """ + w = pad + name_w + value_w + pad + h = pad + len(bindings) * (row_h + gap) - gap + pad + self.frame(x, y, w, h, label=label, ghost=ghost) + anchors = [] + for i, (name, value) in enumerate(bindings): + ry = y + pad + i * (row_h + gap) + self.ident(x + pad + name_w / 2, ry + row_h / 2 + BASELINE, name) + vx = x + pad + name_w + self.cell(vx, ry, value, w=value_w, h=row_h) + anchors.append((vx + value_w, ry + row_h / 2)) + return (x + w, y + h, anchors) + def two_names_one_object(self, x, y, tag_text, name_a, name_b, value, *, object_w=OBJECT_W): """Two names bound to one shared object — the aliasing picture. diff --git a/src/templates/about.html b/src/templates/about.html index 5aff8d7..60bf4c1 100644 --- a/src/templates/about.html +++ b/src/templates/about.html @@ -27,7 +27,7 @@

The quality gates

The design language

-

Every page draws from the tokens below, and this section reads them from the same stylesheet it documents. In dark mode the warm palette inverts; --figure-paper stays light, so figures sit on a paper chip instead of being recoloured, and the terminal tokens keep the runner's output chip dark in both schemes.

+

Every page draws from the tokens below, and this section reads them from the same stylesheet it documents. In dark mode the warm palette inverts, and the figures invert with it: the drawing grammar paints only with the four --fig-* tokens, so every figure is recoloured by the page rather than sitting on a light chip. The terminal tokens keep the runner's output chip dark in both schemes.

Color

diff --git a/tests/test_app.py b/tests/test_app.py index 83762dc..3be62ae 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -585,12 +585,14 @@ def test_css_defines_a_dark_palette(self): for token in ["--text:", "--muted:", "--page:", "--surface:", "--hairline:"]: self.assertIn(token, dark_block) - def test_dark_mode_keeps_marginalia_figures_on_light_paper(self): + def test_dark_mode_recolours_marginalia_figures_through_tokens(self): css = (ROOT / "public" / "site.css").read_text() dark_block = css.split("@media (prefers-color-scheme: dark)", 1)[1] - self.assertIn("--figure-paper", css) - self.assertIn(".cell-banner figure svg", dark_block) - self.assertIn(".journey-section-figure svg", dark_block) + for token in ("--fig-ink", "--fig-ink-soft", "--fig-accent", "--fig-soft"): + self.assertIn(token, dark_block) + # No light "paper chip" behind figures: the tokens do the work. + self.assertNotIn("--figure-paper", css) + self.assertNotIn(".cell-banner figure svg", dark_block) def test_optional_highlighters_retain_readable_server_fallbacks(self): page = render_example_page(get_example("values")) diff --git a/tests/test_marginalia_geometry.py b/tests/test_marginalia_geometry.py index 1d3e329..6f088e7 100644 --- a/tests/test_marginalia_geometry.py +++ b/tests/test_marginalia_geometry.py @@ -782,5 +782,101 @@ def test_no_solid_line_crosses_a_label(self): self.assertEqual(failures, [], "\n " + "\n ".join(failures)) +class FigureContentContract(unittest.TestCase): + """Contract 13: figure text is faithful to the page it sits on. + + The bytes-and-bytearray figure shipped with doubled backslashes + (b'\\\\x63…') for months because every contract checked geometry and + none checked content. Two checks close that hole: no rendered text + may carry an escape artefact, and any mono label that contains a + backslash (an escape sequence the author typed by hand) must appear + verbatim in the Markdown source of an example it is attached to. + """ + + def test_no_text_carries_a_doubled_backslash(self): + failures: list[str] = [] + for name, (paint, w, h) in ALL_FIGURES.items(): + canvas = Canvas(w=w, h=h) + paint(canvas) + for kind, _attrs, content in parse_parts(canvas.parts): + if kind == "text" and "\\\\" in content: + failures.append(f"{name}: {content!r}") + self.assertEqual(failures, [], "\n " + "\n ".join(failures)) + + def test_escape_sequences_in_attached_figures_appear_in_the_example(self): + from html import unescape + + from src.example_loader import EXAMPLES_DIR + + failures: list[str] = [] + for slug, items in ATTACHMENTS.items(): + source = (EXAMPLES_DIR / f"{slug}.md").read_text() + for _anchor, name, _caption in items: + paint, w, h = FIGURES[name] + canvas = Canvas(w=w, h=h) + paint(canvas) + for kind, _attrs, content in parse_parts(canvas.parts): + label = unescape(content) + if kind == "text" and "\\" in label and label not in source: + failures.append(f"{slug} / {name}: {label!r} is not in the example source") + self.assertEqual(failures, [], "\n " + "\n ".join(failures)) + + +class FigureArrowLengthContract(unittest.TestCase): + """Contract 14: every arrow is at least ARROW_MIN long. + + closed_arrow paints a 7-unit head; an arrow shorter than ~20 units + is mostly head and reads as a wedge wedged between two boxes rather + than a relation between them. The census comes from the grammar + (Canvas._arrow_lengths) because the emitted shaft is already + shortened by the head. Fix a failure by widening the gap between the + boxes, never by shrinking the head. + """ + + def test_no_arrow_is_shorter_than_the_minimum(self): + from src.marginalia_grammar import ARROW_MIN + + failures: list[str] = [] + for name, (paint, w, h) in ALL_FIGURES.items(): + canvas = Canvas(w=w, h=h) + paint(canvas) + for length in canvas._arrow_lengths: + if length < ARROW_MIN: + failures.append(f"{name}: arrow of {length:.1f} units (minimum {ARROW_MIN})") + self.assertEqual(failures, [], "\n " + "\n ".join(failures)) + + +class FigureThemeTokenContract(unittest.TestCase): + """Contract 15: the page defines the figure tokens the grammar paints with. + + Figures emit var(--fig-ink) and friends instead of literal colours so + one paint function renders in both colour schemes. That only holds + if public/site.css defines every token in both the light :root and + the dark media block, with exactly the values the grammar documents; + a missing token renders as black in light mode and invisible ink in + dark mode, and neither is caught by the geometry contracts. + """ + + def test_site_css_defines_every_token_in_both_schemes(self): + from pathlib import Path + + from src.marginalia_grammar import FIGURE_TOKENS_DARK, FIGURE_TOKENS_LIGHT + + css = (Path(__file__).resolve().parents[1] / "public" / "site.css").read_text() + light, dark = css.split("@media (prefers-color-scheme: dark)", 1) + failures: list[str] = [] + for scheme, block, tokens in (("light", light, FIGURE_TOKENS_LIGHT), ("dark", dark, FIGURE_TOKENS_DARK)): + for name, value in tokens.items(): + if f"{name}: {value};" not in block: + failures.append(f"{scheme}: expected `{name}: {value};`") + self.assertEqual(failures, [], "\n " + "\n ".join(failures)) + + def test_every_painted_colour_is_a_token_reference(self): + from src.marginalia_grammar import EMPHASIS, INK, INK_SOFT, SOFT_FILL + + for constant in (INK, INK_SOFT, EMPHASIS, SOFT_FILL): + self.assertRegex(constant, r"^var\(--fig-[a-z-]+\)$") + + if __name__ == "__main__": unittest.main()