Limn

approvedunreviewed

by tednaleid

Keyboard-first mind mapping. Open .limn files as interactive mind map views.

★ 9 stars↓ 811 downloadsUpdated 3mo agoMIT

Limn

CI Coverage TypeScript

limn /lim/ verb -- to outline in clear sharp detail : delineate

A keyboard-first, offline-capable mind map progressive web app built with TypeScript, React, and SVG.

Try it: tednaleid.github.io/limn

Quick start

bun install
just serve       # starts Vite dev server at http://localhost:5173

Project structure

packages/core/       # Framework-agnostic TS library (no React, no browser APIs)
packages/web/        # React web app (rendering, input handling, persistence)
packages/obsidian/   # Obsidian plugin (opens .limn files inside Obsidian)

Development commands

All commands are available via just:

just               # list all available commands
just install       # install dependencies
just serve         # start Vite dev server
just test          # run all unit tests
just test-watch    # run tests in watch mode
just test-file drag  # run a specific test file (by name)
just lint          # run ESLint
just coverage      # run tests with coverage report
just check         # run tests (with coverage) + lint + typecheck (CI check)
just build         # build the Obsidian plugin (primary artifact)
just build-web     # build the web PWA (used by the Pages deploy)
just typecheck     # TypeScript type checking

You can also use bun run directly:

bun run test       # vitest
bun run dev        # vite dev server
bun run lint       # eslint
bun run build      # build the Obsidian plugin
bun run build:web  # build the web PWA

Architecture

  • Editor is the sole source of truth for all state
  • TestEditor enables testing all interactions without a browser
  • Keyboard-first: Tab creates child, Enter edits, arrows navigate spatially, ; for EasyMotion jump
  • Diff-based undo/redo (snapshot capture, no Command classes)
  • SVG rendering with pan/zoom viewport
  • IndexedDB auto-save with cross-tab sync
  • ZIP file format with embedded assets (data.json + assets/)

Storage

All data is stored locally in the browser using IndexedDB. Nothing is sent to a server.

  • Each document gets a unique ID shown in the URL hash (#local-doc=<uuid>)
  • Opening the bare URL (no hash) reopens the last-edited document
  • Opening a #data=... URL decompresses the inline document, assigns it a fresh UUID, and saves it locally
  • The "New" menu item creates a new document with its own UUID
  • Two tabs with the same #local-doc=<uuid> URL will stay in sync via BroadcastChannel
  • Clearing browser data (IndexedDB) deletes all locally stored documents
  • Use Cmd+S / Cmd+O to save/open .limn files for durable storage outside the browser

File format

.limn files are ZIP bundles containing data.json and an assets/ directory for images. The current format version is 1.

  • Schema definition: packages/core/src/serialization/schema.ts
  • Golden fixture: packages/core/src/serialization/fixtures/v1-complete.json
  • Migration pipeline: packages/core/src/serialization/migration.ts

The format uses integer versions. When a file is opened, the migration pipeline in migration.ts upgrades it from its stored version to the current version. Post-migration, the result is validated against the Zod schema.

Keyboard shortcuts

Navigation:

KeyAction
Arrows / hjklNavigate between nodes
;EasyMotion: labels appear on all visible nodes, type a label to jump
Cmd+EnterOpen link in selected node
EscapeDeselect

Node Operations:

KeyAction
TabCreate child node
EnterEdit selected node (or create root if nothing selected)
Shift+EnterCreate sibling node
BackspaceDelete node
SpaceToggle collapse
c / Shift+cCycle branch color forward / backward

Structure:

KeyAction
Alt+Up/Down or Alt+k/jReorder among siblings
Alt+Left/Right or Alt+h/lIndent / Outdent
Shift+TabDetach node to root
Alt+;Reparent to target (EasyMotion labels appear, type to attach)

Positioning:

KeyAction
Ctrl+Arrows / Ctrl+hjklNudge node (20px)
Ctrl+Alt+Left/Right / Ctrl+Alt+h/lResize node width or image (20px)
rReflow children to computed layout

Text Editing:

KeyAction
EnterExit edit, create sibling
TabExit edit, create child
Shift+EnterInsert newline
EscapeExit edit mode

Global:

KeyAction
Cmd+Z / Cmd+Shift+ZUndo / Redo
Cmd+S / Cmd+Shift+SSave / Save As
Cmd+OOpen file
Cmd+Shift+EExport SVG
Cmd+= / Cmd+-Zoom in / out
Cmd+0Zoom to fit
Cmd+1Zoom to selected node
Shift+Arrows / Shift+hjklPan canvas
Shift+Alt+Arrows / Shift+Alt+hjklPan canvas (fine)
mOpen menu
?Show keyboard shortcuts
Ctrl+Shift+KToggle keystroke overlay (for demos)

Mouse:

ActionEffect
Click nodeSelect node
Double-click nodeEnter edit mode
Double-click canvasCreate new root node
Cmd+Click linkOpen link in new tab
Drag nodeMove node (reparent when dropped on another node)

Note: On macOS, Ctrl+Arrow keys are bound to Mission Control by default (switching desktops). To use Ctrl+Arrow nudge and resize bindings, disable these in System Settings > Keyboard > Keyboard Shortcuts > Mission Control, or use the hjkl equivalents instead.

Obsidian plugin

Limn is available as an Obsidian community plugin that opens .limn files as interactive mind map views.

Install from the Community Plugins directory

  1. In Obsidian, open Settings -> Community plugins
  2. Click Browse, search for "Limn", and click Install
  3. Enable the plugin

Install via BRAT (beta / latest)

If you want the latest unreleased build, install via BRAT instead:

  1. Install the BRAT plugin from Obsidian community plugins
  2. In BRAT settings, click "Add Beta plugin" and enter: tednaleid/limn
  3. Enable "Limn" in Settings -> Community plugins

BRAT will install the latest release and keep it updated automatically.

Development

See OBSIDIAN-PLUGIN.md for local development setup, building, and architecture details.

Inline markdown

Node text supports inline markdown formatting. Raw markdown is shown while editing; rendered formatting is shown in nav mode.

SyntaxResult
**bold**bold
*italic*italic
`code`code
~~strikethrough~~strikethrough
[text](url)clickable link (Cmd+Click or Cmd+Enter to follow)

For plugin developers

Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.