v0.1.0 — single-device editor, daily-driver-ready

notes that don't
corrupt when you
sync them.

outl is a local-first outliner. Plain markdown is the source of truth. The CRDT lives in a sidecar, not in your files.
Two devices, both offline — they sync to the same tree, no data loss, no server.

5
formal guarantees
0
id:: in your .md
10k
files benched
~/notes — outl May 26, 2026
journal · 🚀 launch-day ctrl-p ⌃k ?
synced 3 blocks 2 backlinks tree-crdt · op-log: 247
01 · the bet

two devices edit
offline.
they sync to the
same tree.

Roam keeps your notes on their servers — the later write silently wins. Logseq scatters id::abc123 UUIDs through your .md so rsync has something to match on; concurrent moves still lose data. git gives you conflict markers across nested bullets every time.

outl uses the Kleppmann et al. 2022 tree CRDT — the algorithm that backs Automerge and Y.js, adapted for trees.

  • Strong eventual consistency. Same ops → same tree, any order.
  • Commutative after reordering. Late arrivals don't break the result.
  • Idempotent. Apply twice = apply once.
  • Tree invariant always holds. No node has two parents, no cycles.
  • No silent loss. Every op stays in the log — even ones turned into no-ops.
laptop A offline
laptop B offline
op-log step 0/5
02 · notes that compute

your code
your data
your notebook.

Markdown fences aren't just for syntax highlighting. Drop a ```python, ```lisp, ```js, ```lua or ```rust block. The result lands as a > result: subblock right underneath it. Re-runs are idempotent.

Set auto-run:: on a block and it re-runs every time you open the page — cache-aware by source hash. Adding a new language is an ~80-line adapter.

python
lisp
js
lua
rust
~/notes/algorithms.md ▷ run
title:: algorithms
fibonacci in 4 lines
```python
def fib(n):
    return n if n < 2 else fib(n-1) + fib(n-2)
print([fib(i) for i in range(10)])
```
idle
› result:
cached · hash: auto-run:: on
the result block is just a markdown blockquote — your file stays diff-able.
03 · markdown stays markdown

the markdown you see
is the markdown you wrote.

No id:: lines. No UUIDs. No HTML comments smuggling metadata. The IDs the CRDT needs live in a separate sidecar file (.foo.outl) — version it, or don't. Your .md is yours.

logseq · pages/post.md
- ## ship outl 0.1.0 id:: 6624a82c-3b11-4d44-9d3f-d9c7e8f0a1b3 collapsed:: false - draft post #launch id:: 6624a82c-9a22-4e55-be40-eaf8d9015c2a - ship binaries id:: 6624a82c-7c33-4f66-cf51-f0a9eb126d3b - linux x86_64 id:: 6624a82c-5d44-4077-d062-01baf1237e4c - macos arm64 id:: 6624a82c-3e55-411a-e173-12cbf2348f5d - test sync id:: 6624a82c-1f66-422b-f284-23dcf3459a6e - <!-- logseq.order-list-type:: number -->
outl · post.md + .post.outl (sidecar)
title:: ship outl 0.1.0 icon:: 🚀 tags:: launch - draft post #launch - ship binaries - linux x86_64 - macos arm64 - test sync
diff-friendly
your git diff is what you changed — not 30 UUID lines that shifted parents.
tool-friendly
works with pandoc, hugo, mdbook, your editor, your scripts. plain commonmark.
future-friendly
delete outl tomorrow. your notes still read the same in any text editor.
04 · vs them

we like the ideas.
we don't like the trade-offs.

Roam, Logseq and Obsidian got a lot right — graph thinking, backlinks, daily journals, block addressability. outl picks those up and fixes the sync, the file format, and the runtime.

feature
outl MIT · rust
Roam closed · cloud
Logseq AGPL · electron
Obsidian closed · electron
markdown is the source of truth partial
no UUIDs in your .md n/a
block-level outliner plugin
[[wiki-links]] + backlinks
daily journal first-class
code blocks that execute 5 langs limited plugin
tree-CRDT algorithm (provably correct)
P2P device sync (offline-safe) in dev · phase 2 cloud paid paid
works offline, no account
open source MIT AGPL
ships as a native binary
TUI / terminal editor
vim-style keys built-in plugin setting
desktop GUI app in dev · phase 5
mobile app in dev · phase 6
graph view in dev · phase 5
plugin system in dev · phase 4 partial
shipped (outl) shipped (others) in dev · phase X on the roadmap not supported
coming from logseq or roam?
$ outl import logseq ~/graph ~/notes
$ outl import roam ~/backup.json ~/notes

strips id::, resolves ((uid)) refs, slugifies filenames, seeds sidecars. unresolved refs stay as ((unresolved:UID)) for triage.

honest about what's not done

v0.1.0 ships the tree-CRDT algorithm + op-log infrastructure (validated by 10+ property tests) and a daily-driver-ready TUI. The pieces marked in dev above are on a public roadmap with phase numbers: P2P transport via iroh (phase 2), queries (phase 3), plugins via rhai (phase 4), Tauri desktop + graph view (phase 5), mobile via uniffi (phase 6). See the roadmap.

frequently asked

questions people actually ask.

What is outl?

+

outl is a local-first outliner — a bullet-point note-taking app where every line is a block you can move, reference and search. It stores plain markdown on disk (no UUIDs, no metadata pollution), has a tree-CRDT under the hood for sync that doesn't lose data, and ships as a single Rust binary with a vim-style TUI.

How is outl different from Logseq, Roam or Obsidian?

+

Three things. (1) Sync: Roam loses data on offline conflicts, Logseq Sync is rsync-flavored and overwrites, Obsidian Sync is a paid black box. outl uses a Kleppmann tree-CRDT with five formal guarantees. (2) File format: Logseq writes UUIDs into your .md files. outl never does — IDs live in a sidecar. (3) Stack: outl is open source (MIT) and ships as a native binary; the others are Electron or proprietary.

Is outl open source and free?

+

Yes. outl is licensed under MIT and the entire codebase is on GitHub at github.com/avelino/outl. There is no paid tier, no account, no telemetry. Free forever.

Does outl work offline?

+

outl is local-first. Your notes live as .md files on your machine. There is no server, no account, no cloud requirement. You can use outl on a plane and lose nothing.

What does "local-first outliner" mean?

+

Local-first means your data lives on your device as plain files you own. The app reads and writes them. If outl disappears tomorrow, you can open every note in VS Code, cat, or any markdown editor — nothing is trapped. Outliner means the editor is built around nested bullets (blocks) instead of long prose, so you can collapse, reorder and link individual thoughts.

Can I import my notes from Logseq or Roam Research?

+

Yes. Run `outl import logseq <path-to-graph> <dst>` for Logseq, or `outl import roam <backup.json> <dst>` for Roam. The importer strips id:: lines, resolves ((uid)) block references, slugifies filenames and seeds the sidecars. Unresolved refs are kept as ((unresolved:UID)) for manual triage.

Does outl have backlinks and a graph view?

+

Backlinks are first-class — every page has a backlinks panel showing what references it. Wiki-style [[page name]] links and #tags work out of the box. A visual graph view is on the roadmap for phase 5 (Tauri desktop).

Can I run code inside my notes?

+

Yes — this is a core feature, not a plugin. Drop a fenced code block in Python, Lisp, JavaScript, Lua or Rust. The result lands as a markdown blockquote subblock right under the source. Re-runs are idempotent and cache-aware (SHA-256 of source). Set `auto-run:: on` and it re-runs every time you open the page.

Is there a mobile app or desktop GUI?

+

Not yet. v0.1.0 is the TUI (terminal interface). The Tauri desktop GUI is roadmap phase 5; mobile (SwiftUI for iOS, Compose for Android) is phase 6. Both will share the same outl-core and outl-md Rust crates, so the algorithm and file format don't fork.

How do I install outl?

+

`brew install outl` (Homebrew formula coming soon) or `cargo install outl` (works today). Then `outl init ~/notes && outl --path ~/notes` opens the TUI on today's journal. First-run cost is ~30 seconds.

05 · ship it

one binary.
your notes.

No electron. No account. No cloud. Press ▷ and you're typing in a journal.

homebrew recommended coming soon
$ brew install outl
cargo
$ cargo install outl
from source
$ git clone https://github.com/avelino/outl && cd outl && cargo build --release
first run
$ outl init ~/notes && outl --path ~/notes

homebrew formula is in progress — for now, install via cargo. a one-line install script lands with the brew tap.