How ForkLeaf works
ForkLeaf has an unusual shape for a notes app: there is no notes database. This page explains what there is instead, and what follows from that.
The shape of the thing
Your browser Our server GitHub
──────────── ────────── ──────
[ editor ] [ session cookie ] [ your repo ]
│ │ │
├──► IndexedDB (source of truth, │ │
│ instant, offline) │ │
│ │ │
└──► sync queue ──► /api/gh/* ──────────►│──► GitHub API ──────►│
(adds your token, commits .md files
never returns it)Notes live in two places: IndexedDB in your browser, and files in your GitHub repository. There is no third copy on a ForkLeaf server. The server exists only to hold your encrypted session cookie and to proxy calls to GitHub with the token attached.
Local-first, not offline-mode
“Offline mode” usually means an app degrades when the network drops. ForkLeaf is the other way round: the local copy is always the one you are editing, and the network is a background job that catches the repository up.
Every keystroke goes to IndexedDB first
Debounced by a few hundred milliseconds, then written. This is why the editor never blocks on a request, and why a flaky connection cannot lose a sentence.
Changes queue as intents, not snapshots
The queue holds “this path changed, here is the new content, here is the SHA it was based on”. Repeated edits to one note collapse into one pending change instead of stacking up.
The queue drains when it can
On reconnect, on a timer, or immediately when you press ⌘S. A failed push leaves the change in the queue and says so in the status bar.
Why commits, specifically
A note could have been stored as a row in a table. Storing it as a file in a git repository buys several things that are hard to add later:
- Version history you already trust. Not a bespoke “note history” feature — actual commits, with diffs, that you can revert with
git revert. - Storage that is not ours to meter. GitHub is already hosting the files, which is why there is no storage tier to buy.
- An exit that costs nothing.
git cloneand you have everything, in a format every other Markdown tool reads. - Interoperability by default. The same file renders on github.com, opens in Obsidian, and builds in Hugo or Jekyll.
Multi-file operations go through GitHub’s tree API as a single atomic commit, so renaming a note — which is a delete plus a create — can never half-apply.
How the code is arranged
ForkLeaf is a pnpm monorepo. Each package has one job and no knowledge of the UI, which is what keeps the sync logic testable without a browser.
| Package | Responsibility |
|---|---|
@forkleaf/types | The shared domain model. No logic. |
@forkleaf/markdown-engine | Front matter, parsing, sanitised rendering, path helpers, document stats. |
@forkleaf/github-client | The GitHub REST client: trees, file reads, atomic multi-file commits, commit squashing. |
@forkleaf/store | IndexedDB storage, the change queue, the sync engine, and conflict detection. |
@forkleaf/diagrams | Mermaid rendering, the graph model behind the visual builder, templates, error mapping. |
@forkleaf/editor | The editing surfaces: Tiptap for rich text, CodeMirror for source, and the diagram studio. |
@forkleaf/exporter | Markdown, HTML, Word and PDF generation. |
apps/web | The Next.js application: routes, API proxy, and chrome. |
What ForkLeaf deliberately does not do
Being clear about this is more useful than a longer feature list.
- Real-time collaborative editing. Two cursors in one document needs a server holding shared state, which is exactly the thing this design does not have. Concurrent edits are handled as conflicts, not merged live.
- Server-side rendering of your notes. Exports run in your browser. Nothing is uploaded to be turned into a PDF.
- Storing your notes. There is no table to leak.