Jupyter is great but the .ipynb format has always bothered me. It's JSON with embedded code — git diffs are a mess, linters don't see it, and you can't just open a cell in your editor and have things work. There are tools that help (jupytext, nbstripout) but they all work around the format rather than replacing it.
Looseleaf takes a different approach: a notebook is just a directory of .py files. Each file is a cell. They run in alphabetical order, so you use numbered prefixes (01_load.py, 02_train.py). One long-running Python process holds the shared namespace across all of them, so variables from cell 1 are available in cell 2 — same model as Jupyter, different storage.
When a cell runs, output is written to a .output sidecar file (JSON with stdout, stderr, images, timing). The browser frontend shows it, but so can anything else that can read a file.
A few things I find useful about this: edit cells in any editor — they're just .py files, a watchdog observer picks up external changes and the browser updates automatically. Git just works: git diff 02_train.py shows what you'd expect, no output noise. AI agents can work with it naturally — write a single cell without touching anything else, read the last output from the sidecar without running anything, drop in new cells by creating a file. And there are no dependencies on Jupyter: it's ~750 lines of Python (aiohttp + watchdog) and a single HTML file with Monaco loaded from CDN.
The frontend is intentionally minimal — two-mode editing like Jupyter (command/edit), Monaco for the editor, streaming output over WebSocket. No build step, no framework.
It's an opinionated tool. No markdown cells, no cell reordering in the UI, matplotlib only for plot capture. One kernel, one directory. That constraint is the point — it stays simple enough that you can read the whole thing in an afternoon.