Manage your ocean of markdown.
Motion is a local-first technical writing IDE. It edits markdown on your own filesystem, renders diagrams and queries inline, and can generate both from natural-language prompts using CLI tools you already have installed.
- Three view modes — WYSIWYG, raw Markdown, and Split, switchable at any time. Edits carry across modes without loss.
- Workspace management — open a folder and Motion lists every markdown file under it, recursively (flat list). Search notes filters by filename.
- New Note — creates
untitled-<timestamp>.mdin the open workspace. - Save — labeled Save control in the toolbar, plus ⌘S / Ctrl+S. Status shows Saving… / Saved / Save failed.
- Rich markdown — headings, lists, blockquotes, and code blocks with syntax highlighting across the common languages. (Tables are not supported yet — no table extension is registered, so pipe-table syntax renders as text.)
- AI Refine — per-document refine action (needs
claudeonPATH).
Five block types, insertable from the toolbar or by typing / at the start of a
line. Blocks survive save/reload as real blocks (not plain code).
| Block | What it does |
|---|---|
| Mermaid | Renders a diagram from Mermaid source, editable in place |
| Dataset | Registers a local CSV/JSON/JSONL file as a queryable table |
| Query | Runs SQL against registered datasets via DuckDB-WASM, in-browser |
| Image gen | Generates an image from a prompt via the imagen CLI |
| Diagram gen | Generates a Mermaid diagram from a prompt via the claude CLI |
Dataset/Query need the data files inside the open workspace (demo files ship
under public/demo/). SQL is restricted to SELECT/WITH with validated
identifiers and a clamped row limit — the query box cannot modify your data.
Synthesize summarizes every note in the workspace, clusters them by topic,
and writes a generated TOC.md and SKILL.md back into the folder. Its own
output is excluded from the input, so re-running does not feed the index back to
itself. Capped at 40 notes per run, and it reports what it skipped. Needs
claude on PATH.
- Filesystem access is jailed to the folder you opened. Paths are canonicalized,
so
..traversal and symlinks pointing outside the workspace are both refused. - Rendered markdown and generated SVG are sanitized before insertion.
- Dev server binds to localhost only.
- Bun 1.3+
- Rust — only for the desktop build
- Optional, for the generative blocks: the
claudeandimagenCLIs onPATH
bun installMotion runs two ways, and the difference matters:
bun tauri dev # desktop app — real filesystem access
bun run dev # browser at http://localhost:3000Desktop (bun tauri dev) is the real product: a native folder picker, and
reading and writing files anywhere inside the folder you open.
Browser (bun run dev) reads and writes real files too, against a workspace
directory set by MOTION_WORKSPACE (default public/demo/) instead of a folder
picker:
MOTION_WORKSPACE=~/notes bun run devBoth modes go through the same rules, enforced by one shared contract that both
test suites run — so behaviour you see in the browser is behaviour the desktop
app is held to. That is what makes browser automation a meaningful gate rather
than a rehearsal. The server binds to 127.0.0.1.
There is no hot reload. The dev server rebuilds the bundle when files change, but the page does not refresh itself — reload manually.
bun run verify # typecheck → client-bundle guard → unit tests → end-to-endThat one command is the gate. Individually:
| Command | Covers |
|---|---|
bun run typecheck |
tsc --noEmit, strict |
bun run guard:client |
rejects any Bun. API reachable from the browser bundle |
bun test src |
unit tests |
bunx playwright test |
end-to-end against the real dev server |
cd src-tauri && cargo test --lib |
the workspace filesystem jail |
End-to-end specs fail automatically on console errors, uncaught exceptions,
failed requests, and any response with status 400 or above — so a broken UI
cannot pass quietly. See CLAUDE.md for the full definition of done.
Recorded here rather than discovered later:
- Sidebar is flat — every
.mdunder the workspace, not a directory tree. Sort is name-only; no content (in-file) search yet. - Welcome demo datasets assume
sample-data.csv/sample-events.jsonlexist in the open workspace. Opening an unrelated folder shows load errors for those blocks until those files are present or sources are re-pointed. - Markdown tables are not supported. Pipe-table syntax renders as plain text.
- No hot reload. The dev server rebuilds the bundle on change but the page does not refresh itself — reload manually.
- Generative blocks and Synthesize need the relevant CLI (
claude,imagen) onPATH.
See CHANGELOG.md for release history and docs/roadmap.md for what is planned.
- Runtime: Bun
- Frontend: React 19, TypeScript, Tiptap 3
- Desktop shell: Tauri 2 (Rust)
- Data: DuckDB-WASM
- Testing: Playwright,
bun:test,cargo test
See LICENSE.