Code folder — marimo notebooks

Purpose

This folder contains marimo notebooks that explain concepts visually and interactively. These notebooks exist because text-only explanations were insufficient. If you’re writing text that could be a diagram, you’ve already failed.

Rules for all notebooks

Draw first, label everything, explain last

  1. Every concept gets a diagram. If you mention “height”, draw the height line. If you mention “angle θ”, draw the arc and label it θ. If you mention “projection”, draw the original shape, draw the projected shape, and draw the projection lines connecting them.

  2. Label every element in the diagram. Vectors get names and component values. Lengths get formulas. Angles get symbols. Colors in the legend match colors in the plot. No unlabeled geometry.

  3. Markdown text is captions, not the explanation. A paragraph should say “Notice that…” pointing at something already visible in the diagram above. If the reader has to imagine what you’re describing, the notebook is broken.

Light theme — mandatory

The marimo user config is set to theme = "light". All notebooks must use a white / light background for all visualizations:

  • matplotlib: white facecolor on figure and axes. Use dark text, dark axis labels, dark titles. No #1a1a2e dark backgrounds.
  • graphviz: bgcolor="white" on the digraph. Use standard system fonts (Helvetica) — don’t assume JetBrains Mono or Inter are installed in all environments.
  • plotly: use template="plotly_white" (not plotly_dark).
  • altair: default light theme (no override needed).

Technical conventions

  • Use marimo run (app mode) as the primary way to view. All cells should have hide_code=True.
  • Use uv inline script metadata for dependencies. No pyproject.toml per notebook — keep them self-contained.
  • Prefix cell-local variables with _ (e.g. _fig, _svg) — marimo requires unique variable names across cells.
  • mise.toml at the code root defines run targets.

Math & interactive notebooks

For notebooks that compute something the reader interacts with (formulas, simulations, parameter exploration).

Sympy for all formulas

Use sympy in every math notebook. Whenever a notebook presents a formula, derivation, or symbolic result, render it with sympy rather than writing raw LaTeX strings in markdown. Sympy gives you:

  • Beautifully typeset formulas via sympy.init_printing() or mo.md(f"${sympy.latex(expr)}$")
  • Symbolic manipulation (simplify, expand, substitute, solve) so the reader sees the math being computed, not just displayed
  • Step-by-step derivations: show each algebraic step as a sympy transformation, not a wall of LaTeX

Pattern for showing a derivation:

import sympy as sp
x, y = sp.symbols("x y")
expr = (x + y)**2
expanded = sp.expand(expr)
mo.md(f"""
**Start:** ${sp.latex(expr)}$
 
**Expand:** ${sp.latex(expanded)}$
""")

Only fall back to raw LaTeX in mo.md() when there is no symbolic computation involved (e.g., a purely textual reference to a formula).

Diagram-specific patterns

  • “Base × height”: Draw the base, draw the perpendicular height as a dashed line, label both with their formulas, shade the resulting area. Show the angle with an arc.
  • “Projection / shadow”: Draw the 3D object. Draw dashed vertical lines from each vertex down to the target plane. Draw the projected shape on the plane. Label the area of both shapes.
  • “These two things are equal”: Compute both numerically and show them side by side. A table of numbers is fine as a supplement, but the primary evidence should be visual (overlapping curves, matching bars, etc.).

Library choices

  • Use matplotlib for 2D annotated diagrams (arcs, dashed lines, annotations with arrows). Plotly is better for interactive 3D.
  • Use altair when the chart benefits from interactivity (selections, tooltips, cross-filtering). Good for yield curves, vol surfaces, correlation heatmaps, time series.
  • Use graphviz (Python bindings) for block/memory/architecture diagrams. Render SVG via g.pipe(format="svg").decode("utf-8") and embed with mo.md(f"...{_svg}...").
  • vegafusion scales altair to larger datasets via server-side aggregation.

Figure presentation

  • Center all figures. Wrap mo.as_html(fig) in a centering helper:
    def centered_fig(fig):
        html = mo.as_html(fig).text
        return mo.Html(
            f'<div style="display:flex;justify-content:center;margin-top:1.5rem">'
            f"{html}</div>"
        )
    Define centered_fig in the init cell and export it. Never use bare mo.as_html(fig) in a vstack — it renders left-aligned with no spacing.
  • Add vertical spacing between elements in mo.vstack() calls. The centered_fig helper includes margin-top: 1.5rem. For other elements (tables, markdown), add spacing via wrapper <div> or gap parameter if available.
  • mo.ui.dropdown value must be the display label (dict key), not the option value. Using value="gauge" when the key is "Slowly changing gauge (...)" silently crashes the cell.

Concept explanation notebooks

For notebooks that explain non-mathematical concepts (programming language features, systems topics, architecture patterns) using code snippets, text, and pre-rendered diagrams.

Embedded code snippets

WASM export bundles only the .py file — sibling files are not included. Embed code snippets directly in the notebook using tagged regions:

_SNIPPETS_TEXT = _tw.dedent("""\
    // tag::example[]
    let x = 42;
    println!("{x}");
    // end::example[]
""")
 
def read_snippet(tag: str) -> str:
    pattern = rf"// tag::{re.escape(tag)}\[\]\n(.*?)// end::{re.escape(tag)}\[\]"
    m = re.search(pattern, _SNIPPETS_TEXT, re.DOTALL)
    return m.group(1).rstrip("\n") if m else f"// snippet '{tag}' not found"

Pre-rendered diagrams

C-extension libraries (matplotlib, numpy, scipy, graphviz) cannot run inside WASM. For concept notebooks, pre-render diagrams offline and load the output at runtime:

  1. Create render_diagrams.py in the notebook directory — a standalone uv-runnable script that generates SVGs into static/.
  2. Load SVGs via a dual-mode helper in the notebook’s init cell:
def load_svg(name: str) -> mo.Html:
    try:
        from pathlib import Path
        svg = (Path(__file__).parent / "static" / f"{name}.svg").read_text()
        return mo.Html(svg)
    except Exception:
        return mo.Html(f'<img src="../static/{name}.svg" style="max-width:100%">')

This reads from the filesystem locally (marimo run) and falls back to <img> tags in WASM export. build.sh copies static/ into the published site.

WASM indentation helper

WASM export re-indents all cell source by 4 spaces. Triple-quoted strings at column 0 lose their relative indentation. Use a dedent_md() helper:

def dedent_md(s):
    return mo.md(_tw.dedent(s))

Then indent all triple-quoted content to 4 spaces in source:

dedent_md(f"""
    ### Section title
 
    Explanation text here.
 
    ```rust
    {read_snippet('example')}
    ```
""")

WASM pitfalls

Three bugs that cost hours during development:

  1. _ prefix = cell-local in marimo. Variables starting with _ are invisible to other cells. Never use _ prefix for helpers shared between cells. Use dedent_md, not _md. Use load_svg, not _load_svg. Cell-local temporaries like _fig or _code are fine.

  2. WASM re-indentation. The export process re-indents cell source to function-body level (4 spaces). Content in triple-quoted strings at column 0 gets flattened. Always indent content and textwrap.dedent() at runtime.

  3. WASM bundles only the .py file. No sibling files ship — not images, not .rs files, not other .py modules. Embed everything inline or load from static/ via <img> tags.