Skip to content

Commit bbc52a7

Browse files
committed
PAD_X 8 → 14 so lane labels stop clipping on the left
User-reported: /examples/async-iteration-and-context showed the swimlane's "LOOP" and "CORO" labels clipped to "_OOP" and ":ORO" on the left edge of the figure. Root cause: the lane() primitive places tag-style labels at x0 - 6 with anchor="end". With x0=20 in async-swimlane, the label right-edge sits at viewBox x=14 and the text extends leftward by ~25 viewBox units (4 uppercase letters × ~6.25 wide at fs=8 with tracking 0.5) → starts at ~x=-11. The old PAD_X=8 set the viewBox left edge at x=-8, clipping the leftmost ~3 viewBox units of every lane label. The 1.6× render scale doubled the visible clip. The contract didn't catch this because my CHAR_WIDTH heuristic for sans (0.58) under-estimates uppercase. tag() upper-cases its input, so any tag with uppercase tracking is actually wider than the audit estimated. Bumping CHAR_WIDTH["sans"] to 0.65 brings the estimate in line with what Source Sans Pro renders at fs=8 with tracking 0.5; future lane-label drift will fire the contract. Two changes: src/marginalia_grammar.py: PAD_X bumped 8 → 14, matching PAD_TOP and PAD_BOTTOM. Every figure's viewBox now extends to x=-14, so labels and lines that draw into negative x have ~6 more units of margin. Net effect on rendered size: each figure grows by 12 × 1.6 = ~19 CSS px wider (gain absorbed by the 640px banner ceiling). tests/test_marginalia_geometry.py: PAD_X constant updated to 14 to match; CHAR_WIDTH["sans"] bumped to 0.65 to cover uppercase tags. Contracts 1, 2, 3, 8 all re-evaluate against the new bounds; 57 tests still green. Other lane-using figures (gil-lanes x0=54, exception-lanes x0=40 via lanes()) had enough room already; only async-swimlane was under-padded.
1 parent fcbe28b commit bbc52a7

17 files changed

Lines changed: 131 additions & 128 deletions

public/prototyping/journey-control-flow.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

public/prototyping/journey-figures-gestalt.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

public/prototyping/journey-interfaces.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

public/prototyping/journey-iteration.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

public/prototyping/journey-reliability.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

public/prototyping/journey-runtime.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

public/prototyping/journey-shapes.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

public/prototyping/journey-types.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
<h1>Types</h1>
3838
<p class="meta">This journey maps Python&#x27;s runtime object model to optional static annotations so learners know what types can and cannot promise.</p>
3939
</section>
40-
<section class="journey-section"><div><h2>Keep runtime and static analysis separate.</h2><p class="meta">The first lesson is that annotations describe expectations for tools while ordinary Python objects still run the program.</p><ul class="journey-list"><li><a class="text-link journey-item-title" href="/examples/type-hints">Type Hints</a><p class="meta">document expected types and feed type checkers</p></li><li><a class="text-link journey-item-title" href="/examples/protocols">Protocols</a><p class="meta">describe required behavior by structural shape</p></li><li><a class="text-link journey-item-title" href="/examples/enums">Enums</a><p class="meta">name a fixed set of symbolic values</p></li><li><a class="text-link journey-item-title" href="/examples/runtime-type-checks">Runtime Type Checks</a><p class="meta">show `type()`, `isinstance()`, and `issubclass()` without turning Python into Java</p></li></ul></div><figure class="journey-figure"><svg viewBox="-8 -14 236 80" width="378" height="128" xmlns="http://www.w3.org/2000/svg"><text x="0" y="36" font-family="'JetBrains Mono', 'IBM Plex Mono', Menlo, monospace" font-size="10" fill="#521000" text-anchor="start">def f(x: int, y: str) -> bool: …</text><line x1="54" y1="28" x2="76" y2="28" stroke="#521000" stroke-width="0.6" stroke-dasharray="2 2"/><line x1="102" y1="28" x2="124" y2="28" stroke="#521000" stroke-width="0.6" stroke-dasharray="2 2"/><line x1="150" y1="28" x2="192" y2="28" stroke="#521000" stroke-width="0.6" stroke-dasharray="2 2"/></svg><figcaption>Annotations describe expected types for tools; the runtime accepts any object regardless.</figcaption></figure></section><section class="journey-section"><div><h2>Describe realistic data shapes.</h2><p class="meta">Typed Python becomes useful when annotations explain optional values, unions, callables, and JSON-like records.</p><ul class="journey-list"><li><a class="text-link journey-item-title" href="/examples/union-and-optional-types">Union and Optional Types</a><p class="meta">show `X | Y` and `None`-aware APIs</p></li><li><a class="text-link journey-item-title" href="/examples/type-aliases">Type Aliases</a><p class="meta">name complex types with `type` statements or aliases</p></li><li><a class="text-link journey-item-title" href="/examples/typed-dicts">TypedDict</a><p class="meta">type dictionary records that come from JSON</p></li><li><a class="text-link journey-item-title" href="/examples/literal-and-final">Literal and Final</a><p class="meta">express constrained values and names that should not be rebound</p></li><li><a class="text-link journey-item-title" href="/examples/callable-types">Callable Types</a><p class="meta">type functions that are passed as arguments</p></li></ul></div><figure class="journey-figure"><svg viewBox="-8 -14 172 108" width="275" height="173" xmlns="http://www.w3.org/2000/svg"><text x="0" y="14" font-family="-apple-system, 'Source Sans Pro', sans-serif" font-size="8" fill="rgba(82, 16, 0, 0.7)" text-anchor="start" letter-spacing="0.5">X: INT|STR|NONE</text><rect x="0" y="22" width="44" height="28" fill="none" stroke="#521000" stroke-width="1.0"/><text x="22.0" y="40.0" font-family="'JetBrains Mono', 'IBM Plex Mono', Menlo, monospace" font-size="10" fill="#521000" text-anchor="middle">x</text><line x1="44" y1="36" x2="74.02702716443629" y2="17.650150066177822" stroke="#521000" stroke-width="1.0"/><polygon points="80,14 75.48708719090742,20.039339200403308 72.56696713796516,15.260960931952336" fill="#521000"/><line x1="44" y1="36" x2="73.0" y2="36.0" stroke="#521000" stroke-width="1.0"/><polygon points="80,36 73.0,38.8 73.0,33.2" fill="#521000"/><line x1="44" y1="36" x2="74.02702716443629" y2="54.34984993382218" stroke="#521000" stroke-width="1.0"/><polygon points="80,58 72.56696713796516,56.73903906804766 75.48708719090742,51.960660799596695" fill="#521000"/><rect x="82" y="4" width="70" height="22" fill="rgba(82, 16, 0, 0.05)" stroke="#521000" stroke-width="1.0"/><text x="117.0" y="19.0" font-family="'JetBrains Mono', 'IBM Plex Mono', Menlo, monospace" font-size="10" fill="#521000" text-anchor="middle">int</text><rect x="82" y="26" width="70" height="22" fill="rgba(82, 16, 0, 0.05)" stroke="#521000" stroke-width="1.0"/><text x="117.0" y="41.0" font-family="'JetBrains Mono', 'IBM Plex Mono', Menlo, monospace" font-size="10" fill="#521000" text-anchor="middle">str</text><rect x="82" y="48" width="70" height="22" fill="rgba(82, 16, 0, 0.05)" stroke="#521000" stroke-width="1.0"/><text x="117.0" y="63.0" font-family="'JetBrains Mono', 'IBM Plex Mono', Menlo, monospace" font-size="10" fill="#521000" text-anchor="middle">None</text></svg><figcaption>A typed slot can accept one of several shapes — `int | str | None` covers expected absence and alternatives.</figcaption></figure></section><section class="journey-section"><div><h2>Scale annotations for reusable libraries.</h2><p class="meta">Advanced typing exists to preserve information across reusable functions, containers, and decorators.</p><ul class="journey-list"><li><a class="text-link journey-item-title" href="/examples/generics-and-typevar">Generics and TypeVar</a><p class="meta">write reusable typed containers and functions</p></li><li><a class="text-link journey-item-title" href="/examples/paramspec">ParamSpec</a><p class="meta">preserve callable signatures through decorators</p></li><li><a class="text-link journey-item-title" href="/examples/overloads">Overloads</a><p class="meta">describe APIs whose return type depends on the input shape</p></li><li><a class="text-link journey-item-title" href="/examples/casts-and-any">Casts and Any</a><p class="meta">show escape hatches and their tradeoffs</p></li><li><a class="text-link journey-item-title" href="/examples/newtype">NewType</a><p class="meta">create distinct static identities for runtime-compatible values</p></li></ul></div><figure class="journey-figure"><svg viewBox="-8 -14 266 98" width="426" height="157" xmlns="http://www.w3.org/2000/svg"><rect x="0" y="30" width="36" height="28" fill="rgba(82, 16, 0, 0.05)" stroke="#521000" stroke-width="1.0"/><text x="18.0" y="48.0" font-family="'JetBrains Mono', 'IBM Plex Mono', Menlo, monospace" font-size="10" fill="#521000" text-anchor="middle">T</text><line x1="36" y1="44" x2="65.0" y2="44.0" stroke="#521000" stroke-width="1.0"/><polygon points="72,44 65.0,46.8 65.0,41.2" fill="#521000"/><rect x="74" y="26" width="100" height="36" fill="none" stroke="#521000" stroke-width="1.0"/><text x="80" y="23" font-family="-apple-system, 'Source Sans Pro', sans-serif" font-size="8" fill="rgba(82, 16, 0, 0.7)" text-anchor="start" letter-spacing="0.5">FN[T]</text><line x1="174" y1="44" x2="203.0" y2="44.0" stroke="#FF4801" stroke-width="1.4"/><polygon points="210,44 203.0,46.8 203.0,41.2" fill="#FF4801"/><rect x="212" y="30" width="36" height="28" fill="rgba(82, 16, 0, 0.05)" stroke="#521000" stroke-width="1.0"/><text x="230.0" y="48.0" font-family="'JetBrains Mono', 'IBM Plex Mono', Menlo, monospace" font-size="10" fill="#521000" text-anchor="middle">T</text></svg><figcaption>A generic type variable preserves shape across a call: the same T flows in and out.</figcaption></figure></section>
40+
<section class="journey-section"><div><h2>Keep runtime and static analysis separate.</h2><p class="meta">The first lesson is that annotations describe expectations for tools while ordinary Python objects still run the program.</p><ul class="journey-list"><li><a class="text-link journey-item-title" href="/examples/type-hints">Type Hints</a><p class="meta">document expected types and feed type checkers</p></li><li><a class="text-link journey-item-title" href="/examples/protocols">Protocols</a><p class="meta">describe required behavior by structural shape</p></li><li><a class="text-link journey-item-title" href="/examples/enums">Enums</a><p class="meta">name a fixed set of symbolic values</p></li><li><a class="text-link journey-item-title" href="/examples/runtime-type-checks">Runtime Type Checks</a><p class="meta">show `type()`, `isinstance()`, and `issubclass()` without turning Python into Java</p></li></ul></div><figure class="journey-figure"><svg viewBox="-14 -14 248 80" width="397" height="128" xmlns="http://www.w3.org/2000/svg"><text x="0" y="36" font-family="'JetBrains Mono', 'IBM Plex Mono', Menlo, monospace" font-size="10" fill="#521000" text-anchor="start">def f(x: int, y: str) -> bool: …</text><line x1="54" y1="28" x2="76" y2="28" stroke="#521000" stroke-width="0.6" stroke-dasharray="2 2"/><line x1="102" y1="28" x2="124" y2="28" stroke="#521000" stroke-width="0.6" stroke-dasharray="2 2"/><line x1="150" y1="28" x2="192" y2="28" stroke="#521000" stroke-width="0.6" stroke-dasharray="2 2"/></svg><figcaption>Annotations describe expected types for tools; the runtime accepts any object regardless.</figcaption></figure></section><section class="journey-section"><div><h2>Describe realistic data shapes.</h2><p class="meta">Typed Python becomes useful when annotations explain optional values, unions, callables, and JSON-like records.</p><ul class="journey-list"><li><a class="text-link journey-item-title" href="/examples/union-and-optional-types">Union and Optional Types</a><p class="meta">show `X | Y` and `None`-aware APIs</p></li><li><a class="text-link journey-item-title" href="/examples/type-aliases">Type Aliases</a><p class="meta">name complex types with `type` statements or aliases</p></li><li><a class="text-link journey-item-title" href="/examples/typed-dicts">TypedDict</a><p class="meta">type dictionary records that come from JSON</p></li><li><a class="text-link journey-item-title" href="/examples/literal-and-final">Literal and Final</a><p class="meta">express constrained values and names that should not be rebound</p></li><li><a class="text-link journey-item-title" href="/examples/callable-types">Callable Types</a><p class="meta">type functions that are passed as arguments</p></li></ul></div><figure class="journey-figure"><svg viewBox="-14 -14 184 108" width="294" height="173" xmlns="http://www.w3.org/2000/svg"><text x="0" y="14" font-family="-apple-system, 'Source Sans Pro', sans-serif" font-size="8" fill="rgba(82, 16, 0, 0.7)" text-anchor="start" letter-spacing="0.5">X: INT|STR|NONE</text><rect x="0" y="22" width="44" height="28" fill="none" stroke="#521000" stroke-width="1.0"/><text x="22.0" y="40.0" font-family="'JetBrains Mono', 'IBM Plex Mono', Menlo, monospace" font-size="10" fill="#521000" text-anchor="middle">x</text><line x1="44" y1="36" x2="74.02702716443629" y2="17.650150066177822" stroke="#521000" stroke-width="1.0"/><polygon points="80,14 75.48708719090742,20.039339200403308 72.56696713796516,15.260960931952336" fill="#521000"/><line x1="44" y1="36" x2="73.0" y2="36.0" stroke="#521000" stroke-width="1.0"/><polygon points="80,36 73.0,38.8 73.0,33.2" fill="#521000"/><line x1="44" y1="36" x2="74.02702716443629" y2="54.34984993382218" stroke="#521000" stroke-width="1.0"/><polygon points="80,58 72.56696713796516,56.73903906804766 75.48708719090742,51.960660799596695" fill="#521000"/><rect x="82" y="4" width="70" height="22" fill="rgba(82, 16, 0, 0.05)" stroke="#521000" stroke-width="1.0"/><text x="117.0" y="19.0" font-family="'JetBrains Mono', 'IBM Plex Mono', Menlo, monospace" font-size="10" fill="#521000" text-anchor="middle">int</text><rect x="82" y="26" width="70" height="22" fill="rgba(82, 16, 0, 0.05)" stroke="#521000" stroke-width="1.0"/><text x="117.0" y="41.0" font-family="'JetBrains Mono', 'IBM Plex Mono', Menlo, monospace" font-size="10" fill="#521000" text-anchor="middle">str</text><rect x="82" y="48" width="70" height="22" fill="rgba(82, 16, 0, 0.05)" stroke="#521000" stroke-width="1.0"/><text x="117.0" y="63.0" font-family="'JetBrains Mono', 'IBM Plex Mono', Menlo, monospace" font-size="10" fill="#521000" text-anchor="middle">None</text></svg><figcaption>A typed slot can accept one of several shapes — `int | str | None` covers expected absence and alternatives.</figcaption></figure></section><section class="journey-section"><div><h2>Scale annotations for reusable libraries.</h2><p class="meta">Advanced typing exists to preserve information across reusable functions, containers, and decorators.</p><ul class="journey-list"><li><a class="text-link journey-item-title" href="/examples/generics-and-typevar">Generics and TypeVar</a><p class="meta">write reusable typed containers and functions</p></li><li><a class="text-link journey-item-title" href="/examples/paramspec">ParamSpec</a><p class="meta">preserve callable signatures through decorators</p></li><li><a class="text-link journey-item-title" href="/examples/overloads">Overloads</a><p class="meta">describe APIs whose return type depends on the input shape</p></li><li><a class="text-link journey-item-title" href="/examples/casts-and-any">Casts and Any</a><p class="meta">show escape hatches and their tradeoffs</p></li><li><a class="text-link journey-item-title" href="/examples/newtype">NewType</a><p class="meta">create distinct static identities for runtime-compatible values</p></li></ul></div><figure class="journey-figure"><svg viewBox="-14 -14 278 98" width="445" height="157" xmlns="http://www.w3.org/2000/svg"><rect x="0" y="30" width="36" height="28" fill="rgba(82, 16, 0, 0.05)" stroke="#521000" stroke-width="1.0"/><text x="18.0" y="48.0" font-family="'JetBrains Mono', 'IBM Plex Mono', Menlo, monospace" font-size="10" fill="#521000" text-anchor="middle">T</text><line x1="36" y1="44" x2="65.0" y2="44.0" stroke="#521000" stroke-width="1.0"/><polygon points="72,44 65.0,46.8 65.0,41.2" fill="#521000"/><rect x="74" y="26" width="100" height="36" fill="none" stroke="#521000" stroke-width="1.0"/><text x="80" y="23" font-family="-apple-system, 'Source Sans Pro', sans-serif" font-size="8" fill="rgba(82, 16, 0, 0.7)" text-anchor="start" letter-spacing="0.5">FN[T]</text><line x1="174" y1="44" x2="203.0" y2="44.0" stroke="#FF4801" stroke-width="1.4"/><polygon points="210,44 203.0,46.8 203.0,41.2" fill="#FF4801"/><rect x="212" y="30" width="36" height="28" fill="rgba(82, 16, 0, 0.05)" stroke="#521000" stroke-width="1.0"/><text x="230.0" y="48.0" font-family="'JetBrains Mono', 'IBM Plex Mono', Menlo, monospace" font-size="10" fill="#521000" text-anchor="middle">T</text></svg><figcaption>A generic type variable preserves shape across a call: the same T flows in and out.</figcaption></figure></section>
4141
</article>
4242

4343
</body>

0 commit comments

Comments
 (0)