Build & run

The three toolchain verbs, the flags a built binary understands, and the one file / three topologies idea: the same notebook is an interactive browser app, a headless batch job, and a served page — distinguished only by where you point the compiler.

The three verbs

The toolchain is go tool notebook (after go get -tool …) or go run github.com/scttfrdmn/go-notebook/cmd/notebook:

go tool notebook check .    # analyze — print the dependency graph, report errors
go tool notebook run   .    # build and serve in a browser; edit the source, it rebuilds
go tool notebook build .    # compile a standalone binary

check

Analyzes the notebook and prints the derived dependency graph — every cell, its inputs (with the producer each wires to), and its output. It is how you see the graph without running anything, and how wiring errors surface with a pointed message. --timing prints the graph-derivation wall time.

For the two-cell hello notebook, success looks like:

graph: 2 cells

  celsius  [pure]
    label: Temperature in Celsius.
    out  c float64

  fahrenheit  [pure]
    label: Temperature in Fahrenheit — wired in by the parameter name `c`.
    in   c float64  <- celsius
    out  f float64

The <- celsius on fahrenheit’s input is the edge — the graph read back to you. A wiring mistake prints a pointed diagnostic here instead (see troubleshooting).

The [pure] tag is the analyzer reporting that it proved each cell a pure function of its inputs — no I/O, no clock, no global state — so the engine can cache the result and skip recomputing it while nothing upstream changes. A cell that reads a file, the time, or randomness is marked [impure] and always recomputes (see the csv-native recipe, whose file-reading rows cell is [impure]). Purity is derived, never declared — you never annotate it.

run

Builds the notebook, serves it over HTTP, and opens it in your browser — then rebuilds on every source edit. This is the one command for “compile and run interactively”: go tool notebook run ./mynotebook. Flags: --addr (listen address, default 127.0.0.1:8080), --no-open (serve but don’t launch a browser), --timing.

build

Compiles the notebook. Flags:

Flag Meaning
-o <path> output path (a binary, or a directory for --target=wasm)
-target native|wasm native binary (default) or a self-contained WASM host directory
--showcase (wasm) lead with the dependency graph open — for gallery demos
--timing print codegen + compile wall time

The built binary’s flags

A native binary you built is itself runnable, with its own flags:

./tempconv                          # serve it (like `run`, but the standalone binary)
./tempconv --headless --json        # run once, print final cell values as JSON, exit
./tempconv --headless --set c=100 --json   # override an input by its RESULT name
Flag Meaning
--headless run once and exit, no server
--json print final cell values as JSON (implies --headless)
--set leaf=value override an input by its result name; repeatable
--addr listen address when serving
--head <file> where slider positions persist between runs

--set names the input by its result name — the same name that is the edge in the graph. --set c=100 sets the cell whose result is c.

A headless run (--headless/--json) is a pure function of the source and its --set overrides: it starts from a fresh head and does not read a notebook-head.json in the working directory, so the same binary and flags always emit the same result — the reproducibility the provenance record claims. A served (interactive) run does load and persist that file, so slider positions survive a restart. Pass --head <file> explicitly to opt a headless run back into a saved head — the deliberate, reproducible-by-path case.

A --json run prints the final values (and a provenance record):

{
  "provenance": {"sourceHash":"56f8…19bc","commit":"77cd3cc","dirty":true,"builtAt":"2026-07-19T11:03:31Z","goVersion":"go1.26.5"},
  "values": {
    "c": 20,
    "f": 68
  }
}

Keys under values are result names, so a downstream tool reads f the same way a cell wired to f does.

One file, three topologies

The same notebook file compiles three ways, differing only in the compiler target:

(These are three build targets. The design doc counts topologies on a different axis — a toolchain × compute 2×2 (local/local, local/remote, remote/remote, remote/browser) — so it says “four.” Same system, two framings: three ways to compile the file, four places the compiler and the compute can sit. See the design for the 2×2.)

The WASM portability gate

A notebook compiles to the browser only if no cell’s call graph reaches net, os, or cgo — there is no browser equivalent. The toolchain derives this from the graph; you do not annotate it. build -target=wasm refuses a notebook that isn’t portable and names the offending cells.

A common false positive: a cell that calls fmt on a number is flagged, because fmt transitively reaches os. The fix is to keep fmt in a Render() method (not a cell) — see rendering — or use strconv in the cell body.

Provenance

Every headless run and every built page carries a provenance record — see provenance — so a figure can be traced to the exact source and toolchain that produced it.

Getting it online

A --target=wasm build is a directory of static files. Publish & deploy covers serving it — the three emitted files, the one required application/wasm MIME type, the caching rule, GitHub Pages / S3+CloudFront / any static host, and a copy-paste GitHub Actions workflow.