Documentation
Every feature
The complete surface: editor, palette, tags, backlinks, tasks, templates, calc.
The hard part of this app is not adding features. It's staying light while doing it. Every note app dies the same death: each feature is individually reasonable, and the sum is Obsidian's settings tree.
The rule that keeps it light
Core is what a majority of users touch weekly and costs under ~10KB. Everything else is a lazy-loaded plugin.
This is why the plugin host (M4) is not a "nice to have" — it's the pressure valve that lets us say yes to Mermaid, KaTeX, and graph views without any of them costing a byte to the person who never opens them.
A second rule, for the "simpler than Obsidian" goal:
Workflow configuration stays in notes. The compact Settings palette is for device presentation choices and direct links into vault-backed settings; deeper feature configuration belongs in a note or a plugin.
Tier 1 — earn their place in core
Ordered by value-per-byte. Sizes are rough.
Status: 1-9, 11 and 12 shipped (2026-08-27). Remaining: 10 (quick capture).
| # | Feature | Why it earns it | Cost |
|---|---|---|---|
| 1 | ✅ Daily notes — Space d opens journal/2026-08-25.md, creating from a template |
The single highest habit-forming feature in this category. It gives a new user a reason to open the app tomorrow. | ~40 lines |
| 2 | ✅ Jump list — Ctrl-o / Ctrl-i back and forward through visited notes |
Link-following is the core loop; without history it's a maze. Vim users expect it. The store already has everything needed. | ~30 lines |
| 3 | ✅ Frecency in the file palette — rank by recently/frequently opened, not alphabetically | Makes ⌘K feel telepathic. The single cheapest perceived-quality win available. | ~25 lines |
| 4 | ✅ Heading outline — @ in the palette jumps to a heading in the current note |
The palette exists, the markdown tree is already parsed. Nearly free, used constantly in long notes. | ~30 lines |
| 5 | ✅ Task rollup — a palette mode listing every - [ ] in the vault |
Obsidian's most-installed plugin category. Here it's a filter over the content cache we already keep warm. | ~50 lines |
| 6 | ✅ Search operators — tag:meta, path:notes/, -exclude, "exact phrase" |
Cheap on top of the existing scan, and it's the difference between "has search" and "search is good". | ~40 lines |
| 7 | ✅ Templates — templates/*.md, made from any note, applied with / in the editor or Space m / Space ⇧M. Placeholders, tab stops, prompts, property merging, per-folder defaults. See TEMPLATES.md |
Unlocks daily notes, meeting notes, and every personal workflow, without any of them being a feature we ship. | ~50 lines |
| 8 | ✅ Image paste → attachments — paste a screenshot, get ![[attachments/…png]] |
Not having this reads as broken. Expected baseline. | ~40 lines |
| 9 | ✅ Frontmatter properties — vault/frontmatter.ts, with aliases: honoured by the palette and by [[links]] |
Aliases make a note findable by every name you'd call it. Sets up filtering later without committing to a database UI. | ~60 lines |
| 10 | Quick capture — a one-line modal that appends to inbox.md and dismisses |
Catching a thought without leaving what you're doing. Becomes genuinely excellent on desktop with a Tauri global hotkey. | ~60 lines |
| 11 | src/panes/), ⌃⌥ bindings |
Read one note while writing another. Went past a single split because the layout tree costs the same as a boolean and gives dwindle tiling, directional focus, and per-workspace layouts for free. | shipped |
| 12 | ✅ Vim mode — @replit/codemirror-vim (the @codemirror/vim named here does not exist), lazy-loaded into a compartment, 120KB off the main bundle |
Given the omarchy audience this is close to mandatory. Lazy-loading means non-vim users pay nothing. | 1 import, ~30KB lazy |
Items 1-9, 11 and 12 have shipped. Everything except vim mode cost about 5KB gzipped in total — which is the whole argument for this list. Vim mode costs nothing to anyone who leaves it off, which is the argument for the other one.
Only quick capture (10) is left, and C3 in IDEAS.md ("log to today") is the cheaper, more-used version of it.
Panes deliberately keep exactly one live editor — the focused one. Two live CodeMirror views would mean two autosave loops and two dirty flags racing over the same vault; focus-then-type is both the tiling-WM convention and the cheaper invariant.
Tier 2 — plugins, never core
Each is genuinely loved by some users and dead weight for most. This list is the argument for building M4 before M5.
| Feature | Why it's a plugin |
|---|---|
| Math rendering | KaTeX is ~280KB — larger than the entire current app |
| Mermaid diagrams | ~500KB, and used by a small minority |
| Graph view | Force layout + canvas; visually impressive, rarely load-bearing |
| Git sync | Strong opinions, real setup cost, not everyone's workflow |
| Kanban / calendar views | A different app wearing this app's clothes |
| Citations / Zotero | Deep, narrow, academic |
| Spaced repetition | Deep, narrow, and its users are devoted |
| Excalidraw / canvas | Huge dependency, and not plain text |
Where the product outgrew this document (2026-09-01)
Two things below were written as refusals and then shipped as core. Recording
the reversal so the admission test in docs/Audit_Adjustments.md §4 has a
truthful baseline.
Encrypted sync is core now — not "refuse, ship a git plugin"
ACCOUNTS-AND-SYNC.md is the real design. The reasoning that changed: the
target user wants their own files and wants them on two machines without
standing up Syncthing or learning git. iCloud/Dropbox don't solve conflict
resolution for a folder of prose, and they see the plaintext. Slate's build is
end-to-end encrypted (two passwords, wrapped DEK, path HMAC), the server never
holds a key, and local-only use is untouched — the vault is still a folder of
.md files that works in any editor with sync switched off. It earns core on
the same "most users, and it compounds the core loop" test as daily notes: a
note you can't get to from your other machine is a note you stop trusting.
Still true: no telemetry, no network call the user didn't initiate. A git
sync plugin stays welcome for the people who want that instead.
AI is core code, but Tier-2 surface — enforced, not just promised
The implementation is a large subsystem (app/src/ai/), and that's fine. What
was wrong was the surface: ~22 always-visible commands, four leader chords, a
permanent statusline chip when nothing was configured. As of 2026-09-01 that's
gone — every AI command, shortcut, chord, and the statusline segment is
when-gated on a provider being picked (Command.when, aiEngaged). "Pick an
agent" is the one visible door. So AI is Tier 2 as the user experiences it —
absent until requested — while living in core as code. The shape stays the one
in IDEAS.md D9: output lands in the note as an editable blockquote, and the
statusline says when a request leaves the machine.
The map is core, lazily — not "graph view, never core"
Tier 2 below still says Graph view — force layout + canvas; visually
impressive, rarely load-bearing, and UX-ADDITIONS.md refused it outright.
Both were arguing about a vault-wide cloud, and both were right about that
one. What shipped is a neighbourhood: focused on the open note at one hop by
default, typed by connection kind (links, tags, contacts), with the legend
doubling as the filter. It answers the question a list cannot — the shape —
and Connections keeps answering the one it can.
The cost objection is answered by construction rather than by promise. The
builder (vault/graph.ts) is ~250 lines of pure function over indexes the
vault already keeps warm; the canvas and the force simulation live behind a
dynamic import() (components/MapCanvas.tsx), so a person who never opens
the map downloads none of it — the same bargain vim mode, xterm and mathjs
already make. That is what the "everything else is a lazy-loaded plugin" rule
was protecting; a plugin would have had to re-read the whole vault across
postMessage to rebuild an index that already exists.
The plugin-shaped piece is still coming and is genuinely better for having
waited: a graph block in the plugin vocabulary, host-rendered like
terminal, so a third-party map ships nodes and edges and never names a
colour. It is only possible because the host now has a renderer.
Tier 3 — refuse, at least through v1
| Feature | Why not |
|---|---|
| Real-time collaboration | Requires CRDTs and a server. It changes the data model from "files you own" to "a database we host" — it contradicts the product. |
| Notion-style databases | Fights plain text. The moment a view can't round-trip to markdown, the file-ownership promise is broken. |
| WYSIWYG that isn't markdown underneath | Same reason. Live preview gives the feel without the lie. |
| Mobile-responsive layout | This app's entire thesis is the keyboard. A phone has no keyboard. Mobile deserves a different design, not a media query — and not in v1. |
Foundation debt paid (2026-08-25)
Bugs found by testing, not by reading:
| Bug | Root cause |
|---|---|
| Editor rendered blank with no vault | Grid declared 2 columns; only 1 child rendered, so the surface got the 0px track |
New note put the caret before the # |
Note switch hard-coded anchor: 0 |
⌘⇧. blockquote silently did nothing |
Shift rewrites e.key (. → >); bindings now resolve punctuation via e.code |
Numbered lists emitted 1. on every line |
Line-prefix toggle had no counter |
| A tag you just typed never reached the sidebar | Incremental reindex updated links but not the tag map |
#123 and #2026 counted as tags |
Tag pattern allowed all-numeric names |
⌃O split the line instead of navigating back |
CodeMirror's macOS emacs keymap won; the app keymap now runs in capture phase |
| A pasted image vanished instead of rendering | The embed's replace decoration was nested inside a second one from its child marks; two overlapping replacements render as an empty span. Iteration now stops at the embed. |
A regression suite landed with this batch: 134 tests, ~1s, covering every pure module above. Six of the seven bugs listed here lived in code that is now under test.
Shipped since (2026-08-27)
| Work | Where |
|---|---|
| External-change detection and conflict resolution | vault/vaultStore.ts — SYNC.md §0 is closed |
[] → checkbox, Enter continues, Backspace removes |
editor/tasks.ts |
| Task board writes through to the open buffer | vault/vaultStore.ts applyExternalWrite |
| Timer sound + fact panel | plugins/sounds.ts, builtin/timer.plugin.js |
[[Note#Heading|Alias]] honoured end to end |
B1, B2, B3 in IDEAS.md |
| Configuration is a note | D1 — vault/config.ts, slate.md |
| The theme travels with the vault | D2, free once D1 existed |
| Local history | D4 — vault/history.ts, ⌃⌥Z |
| Full screen, as one dial with zen | store/useStore.ts viewMode, the top bar chip |
| Compact Settings palette | Device-local editor type, size, spacing, width and motion; vault Vim mode remains in slate.md |
| Generic Markdown folder import | Read-only source copy into imports/, deterministic names and a linked report |
| A plain question in the AI console | ai/catalog.ts prompt, first row |
| One press back to the AI action list | ai/useConsole.ts reset, ⇧esc and the ‹ |
| Accounts — sign in/up/out, no vendor UI | account/, AccountDialog.tsx, supabase/schema.sql |
| Selective sync policy — nine categories, folder exclusions | sync/selection.ts, the sync palette mode |
| End-to-end crypto — two passwords, wrapped DEK, path HMAC, recovery code | sync/crypto.ts |
| Sync engine — cursor pull, CAS push, blob upload, conflict detection | sync/engine.ts, sync/remote.ts, sync/state.ts |
| Sync wired up — vault password, recovery code, statusline, ⌘⌥S | sync/useSync.ts, VaultUnlockDialog.tsx |
| Automatic sync — realtime poke, local debounce, focus | sync/auto.ts, schema-003-realtime.sql |
| Three-way merge and conflict files | sync/merge.ts, the conflicts palette mode |
| Synced version history, with restore | the versions palette mode |
| The account, at the foot of the sidebar and on the welcome screen | SidebarAccount.tsx |
| First sync into a populated folder asks before writing | survey(), FirstSyncDialog.tsx |
The highest-leverage work isn't a feature
The stated goal is "simpler to pick up than Obsidian." Obsidian's real failure isn't its feature count — it's that a new user opens it to an empty vault, a ribbon of fourteen icons, and no idea what to do.
Ship a first run that teaches itself. On first open, offer to create a
three-note starter vault: one note that links to a second, a third tagged
#example, and a visible hint to press ⌘K. Sixty seconds to "I understand
this app," with no documentation and no settings screen.
That is worth more than any five features on this page.