Skip to content

Commit d448b13

Browse files
committed
Tighten rubric and Lessons Learned from the contract-driven audit pass
Two docs updates capture what shipped over the last set of commits. docs/example-figure-rubric.md — Release Gates section expanded with the contract-backed hard gates that didn't exist (or were softer) in v2: * No clipping (Contract 1) — text width counts, not just baseline. * No element collision (Contract 2) — text in a rect must be fully contained by that rect, not partly overlapping the box above it. * No text-text overlap (Contract 3) — narrow object_boxes whose tag+value compete for horizontal space are caught. * Caption uniqueness across slugs (Contract 5b) — reused figures need bespoke captions per attachment. * Palette, font, and stroke-weight discipline (Contract 5a-c) — locked at the grammar; CI catches drift. * Emphasis scarcity, ENFORCED (Contract 9) — was soft v1, now hard. At most one accent per figure. * Banner-fit (Contract 8) — every figure's intrinsic width fits the 440px cell-banner--1 ceiling. * Twin consistency — when two figures depict parallel concepts, their coordinates must match coordinate-for-coordinate where the concepts coincide. A single-pixel drift in one breaks the visual rhyme. * Geometric termination — lines must land at element edges, not 1-2px short or inside the glyph. * Mono character alignment — vertical dividers marking positions in mono text must use the font's actual advance (~6px per char at fs=10), not eyeballed pixels. * Gestalt = production — review pages render the same paint code as production attachments; parallel paint code drifts and hides bugs. docs/lessons-learned.md — 10 new lessons added to the "Visualisations and marginalia" section, each captured from a real bug class we shipped a contract for: - audits without contracts rot - clipping ≠ collision; padding fixes one not the other - heuristic audits over-flag; trust the design, not the regex - structural twins must share coordinates exactly - reused figure → bespoke caption per slug - one paint registry, not two - tag-above vs tag-inside is a layout decision driven by stacking - mono character alignment uses the font's advance - lines must terminate AT elements - journey pages don't yet render section figures inline (open design question) Tests: 54 still green; no production code changed.
1 parent 945c37b commit d448b13

2 files changed

Lines changed: 62 additions & 1 deletion

File tree

docs/example-figure-rubric.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@ per-figure scoring.
9999

100100
## Release gates outside the score
101101

102+
These are not scored; a figure that violates any of them does not
103+
ship. The geometry, palette, font, stroke, emphasis, registration,
104+
and caption gates are now enforced by automated contracts in
105+
`tests/test_marginalia_geometry.py` (Contracts 1-9). CI fails before
106+
the figure can merge.
107+
102108
- **One figure per cell, at most.** Two figures on one cell signal
103109
the cell is doing two things; split the cell instead.
104110
- **figcaption present and declarative.** Captions in the form
@@ -108,10 +114,55 @@ per-figure scoring.
108114
- **figcaption agrees with the cell's prose.** The cell's prose
109115
paragraph in the markdown and the figure's figcaption assert the
110116
same thing in different words. If they disagree, one is wrong.
117+
- **figcaption is unique across slugs.** A reused figure can serve
118+
multiple lessons (`iter-protocol` attaches to four), but each
119+
lesson must frame the figure in its own voice. Verbatim caption
120+
reuse copies the lesson voice the same way verbatim code reuse
121+
copies the example. *Contract 5b — FigureCaptionContract.*
122+
- **No clipping.** Every `<rect>`, `<text>`, `<line>`, `<circle>`,
123+
`<path>` lives inside the padded viewBox. Text width counts: a
124+
long mono string in a too-narrow box clips even if the geometry
125+
looks right at first glance. *Contract 1.*
126+
- **No element collision.** Text that overlaps a rect must be
127+
fully contained by that rect. A type tag sitting on top of the
128+
box above it (the `/examples/values` STR-LIST-DICT bug) is the
129+
canonical violation. *Contract 2.*
130+
- **No text-text overlap.** Two text elements may not occupy
131+
overlapping bounding boxes (the `itertools-chain` "ITER A" /
132+
"1 · 2" collision in a too-narrow box). *Contract 3.*
111133
- **Palette discipline.** Only `INK`, `INK_SOFT`, `EMPHASIS`,
112-
`SOFT_FILL`. No literal hex codes, no `rgba(0,0,0,…)` neutrals.
134+
`SOFT_FILL`, or `"none"` may appear as fill or stroke. *Contract
135+
5a — FigureGrammarContract.*
136+
- **Font discipline.** Only `FONT_SERIF`, `FONT_MONO`, `FONT_SANS`
137+
may appear as `font-family`. *Contract 5b.*
138+
- **Stroke-weight discipline.** Only `W_HAIRLINE`, `W_STROKE`,
139+
`W_EMPHASIS`, `W_GHOST`. *Contract 5c.*
140+
- **Emphasis scarcity, enforced.** At most ONE accent mark
141+
(`EMPHASIS`-coloured arrowhead, caret, dot, or rect stroke) per
142+
figure. Was a soft v1 criterion; now hard. *Contract 9.*
143+
- **Banner-fit, enforced.** Every figure's intrinsic width
144+
(Canvas.w + 2 · PAD_X) must fit `.cell-banner--1`'s 440px max
145+
ceiling. *Contract 8.*
146+
- **Twin consistency.** When two figures depict parallel concepts
147+
(`kw-only-separator``positional-only-separator`,
148+
`class-triangle``metaclass-triangle`), their metrics must
149+
match coordinate-for-coordinate where the concepts coincide. A
150+
fix to one is a fix to both, in the same commit.
151+
- **Geometric termination.** Lines that connect to dots, circles,
152+
or rects must terminate AT the element's edge — not 1-2px short
153+
(looks disconnected) and not inside the glyph (looks broken).
154+
When in doubt, end the line at the centre and let the dot draw
155+
on top.
156+
- **Mono character alignment.** When a vertical divider marks a
157+
position in mono text, its x must match the character's actual
158+
centre. JetBrains Mono advances ~6px per char at fs=10. A
159+
visually-similar `82` and `75` are not interchangeable.
113160
- **Pipeline invariants** (see spec) hold: SVG renders at intrinsic
114161
size; SVG contains no prose duplicating the caption.
162+
- **Gestalt = production.** Review pages under `/prototyping/*`
163+
must render the same paint code as the production attachments.
164+
Parallel `e_*` paint functions for "gestalt versions" drift from
165+
production and hide bugs; we eliminated 76 of them in May 2026.
115166

116167
## Page-level coherence (per slug, multi-figure)
117168

docs/lessons-learned.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,13 @@ git diff --check
101101
- **Tests against the cell layout must allow the `has-figure` class.** When the renderer adds `has-figure` to cells with attached figures, assertions on the literal string `class="lesson-step lp-cell"` fail. Change those tests to check the substring `lesson-step lp-cell` so both variants match.
102102
- **Score what's shipping, not what was designed.** A scoring dict on the gestalt is design-time review. Production figures live in `src/marginalia.py` `FIGURES` and may have been redesigned during promotion. Scoring should track the production version with the gestalt as separate history.
103103
- **Some examples should never have figures.** Constraint-shaped, infrastructure-shaped, and aggregator-shaped slugs lack a single mechanism to depict. Force-fitting figures on them scores below the gate. Leave them figure-less and document why rather than ship weak figures.
104+
- **Audits without contracts rot; bug classes need automated gates.** When you find a bug class — clipping, collision, off-palette colour, drifting twin coordinates, duplicate caption — write a unit test that asserts the invariant across every figure. The geometry contracts in `tests/test_marginalia_geometry.py` started as ad-hoc scripts and were promoted to CI gates after the same bug class recurred. 54 tests today cover 9 contract families; new figures pass them automatically because each test iterates `FIGURES`.
105+
- **Clipping ≠ collision; padding the viewBox fixes one but not the other.** The `value-types` bug had two components: the first `INT` tag was clipped above the viewBox (geometry escapes its frame), and the `STR`/`LIST`/`DICT` tags overlapped the boxes above them (geometry collides internally). Padding the viewBox solved (1) and disguised (2). Element-element collision needs its own audit that walks every text-rect, text-text, and (for label-on-edge cases) text-line bounding-box pair.
106+
- **Heuristic audits over-flag; trust the design, not the regex.** Probes for "prose duplication" (SVG text matching caption substring), "text crossing a line" (label bbox bisected by a hairline), and "text overlapping a circle" each surfaced ~3-10 hits — all false positives. Diagrammatic labels naturally appear in captions (`__getattr__`, `yield from inner`); dashed strikes through `.append` are deliberate; text inside node circles is the design. Don't promote heuristic findings to contracts without confirming each is a real bug.
107+
- **Structural twins must share coordinates exactly.** When two figures depict parallel concepts — `kw-only-separator` and `positional-only-separator`, `class-triangle` and `metaclass-triangle` — they read as a pair. A single-pixel drift in one breaks the visual rhyme. Treat a coordinate change in one as a forced change in both, in the same commit. The audit caught `kw-only-separator` at the old `x=82` after `positional-only-separator` had moved to the corrected `x=75`.
108+
- **Reused figure → bespoke caption per slug.** The 8 library figures (`iter-protocol` across 4 iteration slugs, `aliasing-mutation` across mutability + copying-collections, etc.) shouldn't all carry the same caption. Each slug is a separate lesson; the figure stays, the framing changes. `FigureCaptionContract` enforces uniqueness across slugs after one verbatim duplicate slipped through.
109+
- **One paint registry, not two.** The marginalia-gestalt review page once rendered its own `e_*` paint functions parallel to `src/marginalia.py FIGURES`. Reviewers saw a different picture than readers, and the gestalt's bugs (overlapping labels, misaligned dashed lines) didn't ship. The gestalt is now a thin view over production: same paint, same drift surface, same audit coverage. 862 lines of duplicated paint code went away.
110+
- **Tag-above vs tag-inside is a layout decision driven by stacking.** `object_box(tag_position="above")` is natural for an isolated box; `tag_position="inside"` is required when boxes stack vertically with less than ~13px of gap (the tag's footprint). Defaults to `"above"` for the common isolated case; stacked callers opt in. The grammar carries the choice, not the caller's hand-positioned `tag()` call.
111+
- **Mono character alignment uses the font's advance, not eyeballed pixels.** JetBrains Mono advances ~6px per char at fs=10. A dashed line marking the `/` at index 12 of `def f(a, b, /, c, d): …` lives at `x=12*6+3=75`, not `x=82`. Hand-tuned positions drift; computed positions match the rendered glyph.
112+
- **Lines must terminate AT elements, not in their gaps or interiors.** A 1.5px gap between a tree edge and a leaf dot reads as "the tree is disconnected" (the `exception-group-peel` bug). A line endpoint 2px inside a circle reads as "the arrow pierces the node" (the `context-bowtie` bug). When connecting to a dot, end the line at the dot's centre and let the dot draw on top — the visual termination is the circumference, with zero gap or overshoot.
113+
- **Journey pages don't yet render section figures inline.** Production journey pages (`/journeys/<slug>`) currently render the section overview, title, and example list — but no figures. The journey-section figures defined in `scripts/build_prototypes.py JOURNEY_SECTION_FIGURES` only show on the `/prototyping/journey-figures-gestalt` review page. The figures are production-quality and audited, but the rendering surface that would carry them on `www.pythonbyexample.dev` hasn't been built. Open question: do journey pages benefit from inline figures, or do they belong to the lesson pages only?

0 commit comments

Comments
 (0)