blog · · release

announcing
outl 0.1.0.

A local-first markdown outliner. Vim-style TUI, code blocks that execute, and a sync algorithm that can't corrupt your tree when two devices edit offline.

A
5 min read

the version

Today I'm shipping outl 0.1.0. It's a markdown outliner you run from the terminal — like Logseq or Roam Research, but with three things I couldn't find anywhere else:

  • Plain markdown on disk, no UUIDs in your files. The CRDT IDs live in a sidecar.
  • A tree-CRDT (Kleppmann 2022) with five formal guarantees backed by property tests.
  • Code blocks that execute in Python, Lisp, JavaScript, Lua and Rust — sandboxed in WASM.

It's a single Rust binary. brew install outl (homebrew formula in progress) or cargo install outl today.

why I'm building this

I used Roam for years. Loved the outliner UX, hated the cloud lock-in and the silent data loss when I edited offline on two laptops. Switched to Logseq. The lock-in was gone, but every .md file was full of id:: 6624a82c-... UUIDs that polluted my diffs and made git blame unreadable. And the sync was still a paid rsync that overwrote on conflict.

The problem isn't UX or storage — both of those are solved. The problem is the merge. An outliner is a tree, and merging trees with line-oriented tools (git, rsync) destroys structure. The whole industry has been working around this for ten years.

The Kleppmann et al. 2022 paper "A highly-available move operation for replicated trees" solves it. It's not exotic — the algorithm fits in ~300 lines of Rust. The pieces that make it real are:

  • HLC timestamps for total order without coordination
  • An op log as the single source of truth (the tree is just a projection)
  • Yrs (Yjs in Rust) for character-level CRDT on block text
  • Fractional indexing for sibling order
  • Five formal guarantees + property tests for each one

If you want the long version, the manifesto walks through it in seven acts: outl.app/sync.

what's in 0.1.0

The TUI

Journal-first — opens on today's date. Vim modes (Normal / Insert / Visual) with chords (dd, gg, gx, yy, qq-to-quit). Notion-style slash commands. Fuzzy switcher (Ctrl+P). Hot reload on external .md edits — edit in VS Code, the TUI catches up. Eleven themes you can switch at runtime with /theme <name>.

Clean markdown

The .md file is what you wrote. Page properties (title::, icon::, tags::) at the top in plain key:: value lines. Outline body is standard CommonMark bullets. No id:: lines, no UUIDs, no HTML comments. Block IDs live in .foo.outl (JSON sidecar) — version it, or don't.

Code that runs

Drop a ```python, ```lisp, ```js, ```lua or ```rust block. The result lands as a > result: markdown blockquote subblock right under the source. Re-runs are idempotent. Set auto-run:: on and it re-runs every time you open the page — cache-aware via SHA-256 of the source.

Every language is a ~80-line adapter behind a Runtime trait. Adding a new one is a small PR. Rust blocks compile to wasm32-wasip1 on first run and cache in ~/.cache/outl/runtimes/ — ~20× faster re-runs.

Importers

outl import logseq ~/graph ~/notes and outl import roam ~/backup.json ~/notes strip id:: lines, resolve ((uid)) block refs, slugify filenames, seed the sidecars. Unresolved refs become ((unresolved:UID)) for manual triage. Your original graphs are read-only — nothing's modified there. Full walkthrough at outl.app/import.

what's not in 0.1.0

v0.1.0 is single-device. The op log and CRDT primitives are in. What's missing is the wire. Phase 2 of the public roadmap adds P2P sync over iroh — QUIC, automatic hole punching, no central server, E2E encrypted by default. The algorithm is already designed for this; the network is just plumbing.

Phase 5 brings a Tauri desktop GUI with a real graph view. Phase 6 brings mobile (SwiftUI for iOS, Compose for Android) on the same uniffi FFI surface over outl-core. Same core, three frontends.

where it slots in

If you're on Roam: outl is what Roam would be if it weren't a cloud service. the long comparison.

If you're on Logseq: outl is Logseq with clean markdown and a real merge. the long comparison.

If you're on Obsidian: outl is a real outliner instead of a prose editor with bullets. the long comparison.

try it

One command:

$ cargo install outl
$ outl init ~/notes
$ outl --path ~/notes

That last one drops you into today's journal. Press ? for the keymap, : for the command palette, Ctrl+P to fuzzy-jump to any page. gx runs a code block. brew install outl is coming soon.

thanks

To Martin Kleppmann and the move-op paper. To Conor White-Sullivan and the Roam team for proving the outliner could be the daily driver. To Tienson Qin and Logseq for showing local-first could work. To everyone who showed up early to try the prerelease and tell me what was broken.

The code is at github.com/avelino/outl. MIT.

— Avelino