Chapter 17: Notebooks in Markdown — The Jupyter Plugin

A Jupyter notebook is a good place to try code and a poor place to keep knowledge. Its .ipynb file is JSON, so git diff fills with base64 plots and execution counts. It is out of reach of your editor's search and links. And the prose around the code lives in cells that nothing else in your workspace can see.

AutoDoc's answer is the lab note (Chapter 4, §4.10): an ordinary Markdown note named *.lab.md, whose fenced code cells run in a Jupyter kernel through the Jupyter plugin (autodoc-jupyter, ADR 1792457255). The note is never edited by a run. Each cell's outputs are files in the note's companion folder, which AutoDoc's HTML export and --gui's Rendered View draw under the cell. Opening an .ipynb turns it into a lab note beside it, and Export writes one back.


17.1 Lab Notes, Cells and Kernels

A lab note names its kernel in its front matter. Its cells are its fences in that kernel's language; every other fence (a mermaid diagram, a csv table, a vega-lite chart, text) stays content and never runs:

---
kernel: python3
---

# Sign-ups

```python
import pandas as pd
df = pd.read_csv("signups.csv")
df.describe()
```

```mermaid
flowchart LR
  raw --> cleaned --> weekly
```

17.2 Installing the Plugin

The plugin needs an AutoDoc that speaks plugin protocol 9 (Chapter 15), which added [open], the hook that hands it .ipynb files.

  1. Plugins › Browse the registry… lists Jupyter. Choose it and confirm. You can also use Plugins › Add from a git URL… with https://github.com/yongjohnlee80/autodoc-jupyter.
  2. AutoDoc asks before it runs anything, then builds the plugin with Go (go build, from its manifest's [install]).
  3. Give it a letter on SPC p: this chapter writes j (AutoDoc assigns plugins no letter).

Its manifest declares:


17.3 The Card and Its Commands

SPC p j opens the card. It shows:

Command On the card What it does
Run cell or selection SPC p j r The cell under the cursor (its fence lines included). With a selection, the selection alone: its outputs show on the card but are not kept, since they belong to no cell.
Run all cells SPC p j a Every cell in order, stopping at the first that fails. When every cell has run, it then removes the output folders no fence of the note has any more; a Run all that fails or is interrupted leaves them.
Interrupt kernel SPC p j x Stops the run: SIGINT to the kernel's process group, or interrupt_request for a kernel whose spec asks for messages.
Restart kernel SPC p j s Starts the kernel afresh, its variables gone. One restart at a time.
Choose kernel… SPC p j k Lists the installed kernels (↑↓, Enter; q leaves). The pick holds for this session; add kernel: <name> to the front matter to keep it.
Export to .ipynb SPC p j e Writes nb.ipynb beside nb.lab.md, never over an existing file.
Open an .ipynb as a lab note (the [open] hook) See §17.6.
sequenceDiagram
  participant User as User in a lab note (plan.lab.md)
  participant Card as autodoc-jupyter (Protocol 9 Card)
  participant Kernel as Jupyter kernel (ipykernel, IRkernel, ...)
  participant Out as plan.lab/out/KEY/
  participant View as AutoDoc (HTML export, Rendered View)
  User->>Card: SPC p j r (plugin.command {id: "run", document})
  Note over Card: the cell under cursor_byte: a fence in the kernel's language
  Card->>Kernel: execute_request (ZeroMQ, HMAC-SHA256 signed)
  Kernel-->>Card: iopub stream, display_data, execute_result, error
  Card->>Out: 01.txt, 02.png, 03.chart.json, 04.csv (written aside, then swapped in)
  Card->>View: host.table {DataFrame} to the table panel
  View->>Out: lab.Outputs(OutDir, CellKey(lang, code))
  Note over View: draws each output right after its cell, in name order

A run goes on a goroutine of its own, so the card still takes Interrupt while a long cell runs.


17.4 Outputs Beside the Note

A cell's outputs are files in <note>.lab/out/<cell key>/, named 01, 02, … in the order the kernel sent them.

Kernel outputMIME typeKept asDrawn by
stdout / stderr stream (consecutive ones of a stream joined)streamNN.txtHTML export, Rendered View (as text)
An error (traceback with its colours stripped)errorNN.txtHTML export, Rendered View (as text)
A pandas DataFrame (table schema)application/vnd.dataresource+jsonNN.csv, and AutoDoc's table panelHTML export, Rendered View (as a table)
A plot or pictureimage/png or image/jpegNN.png / NN.jpgHTML export, Rendered View (the image)
A vector drawingimage/svg+xmlNN.svgHTML export and preview
A Vega-Lite chartapplication/vnd.vegalite.v5+json (or v4)NN.chart.jsonHTML export, Rendered View (the chart)
Anything elseits text/plainNN.txtHTML export, Rendered View (as text)

17.5 DataFrames and the Table Panel

When a Python kernel starts, the plugin quietly turns on pandas' display.html.table_schema. A DataFrame then arrives as application/vnd.dataresource+json, and is kept twice:

Without pandas, or with the option turned off again, a DataFrame is its text/plain, kept as NN.txt.


17.6 Opening and Exporting .ipynb

Opening. Open an .ipynb from the picker, File › Open file…, the explorer or file.open_path, and AutoDoc hands its path to the plugin's import command instead of opening its JSON as text. The plugin then:

When nb.lab.md already exists, nothing is written: it is the note to open, as it is.

Exporting. Export to .ipynb writes nb.ipynb (nbformat 4.5) beside the lab note:

Other fences stay in the markdown, as the note shows them. Export never writes over an existing notebook, so to export again, move the old one away first.


17.7 The Kernel's Lifecycle


17.8 Hands-On Tutorial: A Weekly Sign-Ups Lab Note

Create analysis/signups.lab.md, with signups.csv (columns week and signups) beside it:

---
kernel: python3
---

# Weekly sign-ups

```python
import pandas as pd
df = pd.read_csv("signups.csv")
df
```

```python
import matplotlib.pyplot as plt
df.plot(x="week", y="signups", kind="bar", legend=False)
plt.show()
```

```python
from IPython.display import display
display({"application/vnd.vegalite.v5+json": {
    "data": {"values": df.to_dict("records")},
    "mark": "bar",
    "encoding": {"x": {"field": "week", "type": "ordinal"},
                 "y": {"field": "signups", "type": "quantitative"}}}}, raw=True)
```
  1. Open the note and press SPC p j a. The card says starting Python 3 (ipykernel)…, then running 1 of 3…, and ends with ran 3 cells and the time.
  2. The first cell's DataFrame opens in the table panel, and is kept as analysis/signups.lab/out/<key>/01.csv.
  3. The second cell's plot is kept as 01.png in its own folder.
  4. The third cell's chart is kept as 01.chart.json, the chart below.
  5. Switch to the Rendered View (--gui): each output is drawn under its cell. Export the note to HTML, and they follow their fences there too.
Weekly sign-ups (the tutorial's chart cell keeps it as 01.chart.json)050100150200250Sign-upsW1W2W3W4W5W6Week
  1. Change "bar" to "line" in the third cell. Its chart disappears from the Rendered View, since its key changed; press SPC p j r on it to draw the new one.
  2. Press SPC p j e to write analysis/signups.ipynb for a colleague who uses Jupyter.

← Previous: Chapter 16: AutoDB Live SQL Workbench | Index | Next: Chapter 18: Neovim Integration & KB Migration →