Notebook Format
A Quill notebook is a CommonMark markdown file. Fenced code blocks with a language tag become executable code cells. Everything else becomes text cells. This page explains the mapping and the output serialization format.
Cell Types
A notebook contains two kinds of cells:
- Code cells: fenced code blocks with a language info string (e.g.,
```ocaml). The language tag identifies the execution kernel. - Text cells: all other markdown content between code blocks. Adjacent paragraphs, headings, lists, and other block elements form a single text cell.
For example, this notebook has three cells:
# My Notebook ← text cell
Some explanation.
```ocaml ← code cell
let x = 42
```
More text here. ← text cell
```ocaml ← code cell
let y = x + 1
```
Cell IDs
Quill assigns each cell a stable identifier stored as an HTML comment before the cell:
<!-- quill:cell id="c_a1b2c3d4e5f6" -->
```ocaml
let x = 42
```
Cell IDs are generated automatically for cells that lack them. They enable the TUI and session to track cells across file reloads and edits.
Users do not need to manage cell IDs. They are preserved by quill fmt
and quill eval --inplace. Deleting them is harmless — fresh IDs are
assigned on the next load.
Output Format
After executing a code cell, outputs are stored between marker comments:
```ocaml
let x = 42
let () = Printf.printf "x = %d\n" x
```
<!-- quill:output -->
<!-- out:stdout -->
x = 42
val x : int = 42
<!-- /quill:output -->
Each output section is tagged with its type:
<!-- out:stdout -->— captured standard output and toplevel value printing<!-- out:stderr -->— warnings and standard error<!-- out:error -->— execution errors (syntax errors, type errors, runtime exceptions)<!-- out:display MIME -->— rich output with a MIME type (e.g.,<!-- out:display text/html -->or<!-- out:display image/png -->)
A single code cell can produce multiple output sections. For example, a cell that prints to both stdout and stderr:
```ocaml
let () = Printf.printf "result: 42\n"
let () = Printf.eprintf "warning: something\n"
```
<!-- quill:output -->
<!-- out:stdout -->
result: 42
<!-- out:stderr -->
warning: something
<!-- /quill:output -->
Why HTML Comments?
The output format uses HTML comments for several reasons:
- Invisible in rendered markdown. GitHub, editors with preview, and documentation tools render the notebook without showing outputs. The document reads cleanly whether outputs are present or not.
- Valid markdown. HTML comments are part of the CommonMark specification. No custom syntax, no extensions, no preprocessing.
- Single file. Outputs live in the notebook itself. No sidecar
files, no
.ipynb_checkpoints, no separate output directories. - Clean stripping.
quill fmtremoves all output sections in one pass.quill eval --inplaceregenerates them. This makes it easy to commit clean notebooks and regenerate outputs in CI.
Non-OCaml Code Blocks
Code blocks without a language tag, or with a language other than
ocaml, are not executed. They pass through unchanged as code cells:
```bash
# This is not executed — it's documentation
quill eval notebook.md
```
```json
{ "this": "is also not executed" }
```
This lets you include shell commands, JSON examples, and other snippets in your notebook as documentation without affecting execution.
Roundtrip Guarantees
Parsing a markdown file with Quill_markdown.of_string and rendering it
back with Quill_markdown.to_string or to_string_with_outputs
preserves:
- Cell content and ordering
- Cell IDs
- Output content and types
- Text cell markdown (headings, lists, links, etc.)
The rendering normalizes some whitespace (consistent blank lines between cells), but the semantic content is preserved exactly.