Mind Map — notes, layers, AI and the internals (part 2)
The second half of the Mind Map page: hidden notes attached to a node, drilling into a layer behind one, themes and layout, importing, exporting and sharing a map, the two AI features (which cost money), how a map behaves under your finger on a phone, and what is still rough.
What it is
This is part 2 of the Mind Map page. It covers everything around the map itself — the notes hidden on a node, layers, how the map looks, getting one in and out, the AI features, touch, and the parts still worth knowing about.
Where to find it
The same Mind Map panel as part 1 — all of this is reached from inside an open map.
How it behaves
Hidden notes on a node (annotations)
Any node can carry a note — a free-text comment that stays hidden on the canvas (it's not the node's label). It's for a side thought, a reminder, or a comment an AI agent leaves for you.
- A node with a note shows a small note icon in its top-left corner (only nodes that have a note show it, so the map stays clean). Hover the icon to peek at the note (read-only); click / tap it to open an editable box.
- Add or edit a note four ways — the icon, right-click → Add note / Edit note,
the
nshortcut, or (on a phone) the Note button in the action bar — for the selected node. Any node can have one, including the center "main" node. - In the editor: type your note (it can be multiple lines — Enter makes a new line), then Save or just click away to keep it; Esc or Cancel discards. Clearing the text and saving removes the note (the icon disappears).
- Notes are undoable like node text (
Ctrl+Z), travel with a node when you copy/paste it, and are included in the JSON export. (They're intentionally left out of the Markdown/Mermaid text exports and the public Share page.) - An AI agent can annotate for you. A session (or any tool driving Omniscio's local API) can set or clear a single node's note without touching the rest of the map — and if you have that map open, the note appears live.
Layers (a sub-map drilled in behind a node)
Any node can hold one layer — a full mind map of its own, anchored to that node. Think of it as flipping the node over to a fresh canvas: the rest of the map steps aside, you work on that node's layer, then flip back. It's how you tuck a detail-heavy sub-topic out of the main picture until you want it. One layer per node — pressing "layer" again on a node just takes you back into the layer it already has; it never piles a second blank one on top. To go deeper, nest: a node inside a layer gets its own layer.
- Open a node's layer — select a node and press
l. If it has no layer yet, a fresh blank one opens with its root in edit mode, ready to type; if it already has a layer, this drops you straight back into it. (No second blank layer is ever stacked.) - Flip in and out —
]/[toggle between the base map and the selected node's layer. - See which nodes have a layer — a small layer badge sits at a node's top-right whenever it has one (distinct from the collapse-count circle). Tap the badge to open the layer — so it works on a phone with no keyboard.
- Know where you are / get back out — while you're inside a layer, a slim bar at the
canvas's top-left reads "In a layer" (with a Layers icon) and a
Maplink straight back to the main map, plus a back button and a crumb per drill step (e.g.In a layer · Map › Plans › Q3). So a blank sub-canvas never looks like your map vanished — it's clearly a separate layer, one click from home. On a plain (un-drilled) map the bar isn't shown at all. - Remove a layer from the trail — middle-click a crumb to delete that layer. If
the layer has more than one node it asks you to confirm first; a nearly-empty layer
goes instantly, and
Ctrl+Zbrings it back. ("Map" isn't a layer so it can't be removed; a normal left-click still just jumps to that depth.) - Nest freely — a node inside a layer has its own single layer, and so on, so you can drill as deep as a topic needs.
- Saving & undo — layers live inside the same saved map (nothing extra to
manage; deleting or copy/pasting a node carries its layer along). Anything you build
inside a layer saves right away — even on a brand-new map you haven't named yet.
Undo is per-view:
Ctrl+Zundoes what you're looking at, and moving in and out of layers never loses history. - No clutter — open a layer, change your mind without typing anything, and the empty layer removes itself (and its badge) when you leave, so blank layers never pile up. A map that never uses layers is byte-for-byte the same as before.
Layers are bounded by generous guardrails — one layer per node, 12 levels of nesting, and a 20,000-node budget across a map's layers. You won't hit the nesting or node limits by hand; they exist so a runaway or malicious import can't blow up the app.
Look & layout (themes, direction, background)
- Color mode button cycles Light / Dark / Follow-app (default Follow-app).
- Visual theme picker restyles the whole mind-map view — sidebar, header, canvas — with any of the app's 13 built-in themes, without changing the rest of the app. Both are global view preferences (the per-map override is set from the toolbar; the global defaults live in Settings → Appearance → Mind Map).
- Flow-direction button rotates the map through the four directions; the choice is saved per map.
- Tidy columns — nodes at the same level line up on the edge nearest the center (their left edges in the default left→right view; right / top / bottom in the other directions), so a column of different-width boxes reads as one clean line rather than a ragged stack. It's automatic — the widest box in a column anchors the line and the narrower ones slide in to meet it.
- Dotted-grid background toggle (on by default, remembered).
- The floating zoom controls in the canvas's bottom-right corner (the +/− and fit-to-view stack) wear the same glassy "bubble" look as the nodes — a translucent, softly-shadowed panel that recolors with the active theme (and frosts on the glass themes) — so they read as part of the map rather than a separate widget. They sit bottom-right so they stay clear of the bottom-left "Press ? for shortcuts" hint. Their behavior is unchanged.
- Smooth node motion — whenever the map rearranges (dropping a node on a new parent, adding/deleting, collapsing/expanding, flipping direction, undo/redo, or a drag that snaps back), the nodes glide to their new positions in a short (~180ms) ease instead of jumping. The node you're actively dragging still tracks your cursor exactly — the glide only plays as things settle. It switches off automatically on very large maps (to stay fast) and when your system reduce-motion / low-power setting is on.
Import, export & sharing
The toolbar's low-frequency actions are grouped into labeled dropdown menus so the bar stays uncluttered — map creation/import under a New menu, AI under an AI menu, and the formats below under an Export & Share menu — while the everyday view controls (fit, zoom, dotted background, flow direction, color mode, theme) stay visible on the bar. The Export & Share menu re-imports and exports several formats:
- JSON — the native format (
Import/ theJSONexport), fully re-importable. - Markdown outline — the
Markdownexport writes an indented-bullet list (.md);Import outlineturns a pasted indented list (or a.mdfile) back into a map, with several top-level items getting one synthesized root. - Mermaid —
View as Mermaidopens a read-only panel with a rendered picture (when small enough), copy-able source, and a.mmddownload. Pasting a Mermaidmindmap(or opening a.mmd/.mermaidfile) is auto-detected and becomes an editable map. - PNG — a raster image of the canvas.
- Share — publishes a self-contained, interactive, read-only web page (an
HTML+SVG view, mobile-friendly, themed to match the map). The viewer can zoom
(scroll-wheel / pinch / on-screen + / −), pan (drag), tap a node to fold
or unfold its branch, Fit to re-frame, Expand all (a button that appears once
they've folded something, then re-opens every branch and re-fits the view), and switch
light / dark with an on-page sun/moon button — all from one small inline script (no
external libraries), and the page renders full-screen. It opens fitted to the whole
map and can be pulled back to about half that size for breathing room before it stops
(bounded — it never shrinks to an unreadable blob). Folding is by tapping the node
(there is no per-node fold "circle" — that affordance was removed so the page matches the
canvas). The page carries the map's own theme colors (the surface palette + accent),
read from the app's live theme at share time so the baked page can't drift from a hardcoded
list, and opens in the map's chosen color mode — falling back to the viewer's device
setting, then a neutral palette if a theme can't be read. The share runs its own JavaScript
because an
htmlartifact's runnable doc is served undersandbox allow-scripts(the React-artifact mechanism); the only viewer-controlled bytes (node text + title) stay XML-escaped, and every theme color is validated as a plainR G Btriplet before it reaches the page. An existing link is a snapshot of the map at share time — re-share to update it. The page now ships the whole map fully expanded: branches you collapsed in the editor are included and open by default, so collapsing no longer hides anything from a viewer (it only sets your own editor view). A viewer can fold branches themselves and then Expand all to reopen everything.
AI assist (uses Claude — billable)
Both live in the toolbar's AI dropdown menu.
- Expand with AI asks Claude to brainstorm a handful of child ideas for the selected node and inserts them (undoable).
- Generate map with AI turns a typed topic into a whole starter map.
Both need an API-key account (an OAuth-only login can't call the model API → a
friendly "add an API key" message), use the cheap Haiku model, and are bounded
by a daily spend cap (mindmapAiCostCapUsd, default $1).
AI sessions (a chat that reads & edits your map)
A "Maps / Sessions" tab at the top of the saved-maps list opens a list of AI chat sessions for mind maps — the same session experience as the rest of Omniscio (Needs You / Live / Paused / … sections, a "+ New session" button, and the right-click pause / rename / archive / snooze menu). It reuses the shared session-host seam (the same one the SMS and KMS Sessions tabs use).
- "+ New session" (shown when a map is open) starts a Claude session that
already knows the open map and can read and rewrite it for you — ask it to
brainstorm, reorganize, expand a branch, summarize, or tidy up. It edits through
Omniscio's validated internal
/mindmapsAPI (so it can never save a broken map) and waits for your instruction rather than editing on its own. Unlike "Expand / Generate with AI", this uses your chosen session model, not the capped Haiku. - Opening a session opens a split view (desktop): the map on one side, the chat
on the other, resizable, so you watch the agent rebuild / expand / reorganize the
map live as you talk to it. A Split toggle on the Maps/Sessions strip
collapses to chat-only (and re-expands) — auto-split is on by default
(
mindmapSessionSplit); your divider width is remembered. On a phone the chat replaces the map (too narrow to split); the map auto-saves and is one tab away. - When the AI changes the map, the open editor refreshes live and the change stays undoable (Ctrl+Z reverts the AI) — this is what makes the split view show edits as they happen. If you're actively editing a node, your edit wins (last-writer-wins for that rare overlap).
- The sessions live in a hidden internal
__mindmap__project (never a sidebar row); each session is bound to the map it was started from.
On a phone (touch)
Mobile always opens full-screen. With no keyboard, the editor shows an on-screen
action bar at the bottom — Add child, Add sibling, Rename, Note (add/edit the
node's hidden note), Delete, Cut / Copy / Paste (Cut/Copy show only with a non-root
node selected; Paste shows only once you've copied something), Collapse/expand,
Expand all (an extra button that shows only while a branch is collapsed), Undo,
Redo — wired to the same actions the keyboard and the desktop right-click menu use. The bar scrolls sideways so every button stays
reachable. Add child and Add sibling carry distinct icons that picture where the
new node lands — a + nested down-and-right for a child, straight below for a
sibling — so you can tell them apart at a glance (a phone has no hover tooltip to lean
on). The saved-maps list opens as a bottom-sheet drawer. Pan, pinch-zoom,
tap-to-select, and drag-to-re-parent work on the canvas — a long-press just grabs the
node to drag it; it does not pop a menu (the desktop right-click menu would collide
with the drag, so on a phone Cut/Copy/Paste live on the action bar instead). In the
saved-maps drawer a long-press behaves the opposite way — there's no drag to
collide with, so press-and-hold a map row opens its Rename / Delete menu (the row
has no trash button to tap). Editing a
node slides it above the on-screen keyboard so the box you're typing into is never
hidden behind it. You reach Mind Map from the account menu (the entry appears only
when the feature is on).
Limitations / known issues
- Gated / in-development — hidden until a developer flips it to shipped.
- A published share link is a frozen content snapshot — the page itself is interactive (pan / zoom / fold / Expand all), but the map data is captured at share time, so re-share to push later edits.
- The shared page is always laid out left→right regardless of the map's flow direction (honoring the map's direction is a planned follow-up).
- Touch and drag gestures are unit-tested but not automated end-to-end (a real touchscreen can't be emulated in the test harness) — they get a manual check.
- AI needs an API-key account (OAuth-only logins can't call the model API), and is capped at $1/day by default.
- An agent can do everything you can, except take the in-app PNG. As of
2026-09-08 the CLI covers the whole feature: list, read, create, import, save, rename,
delete, export (JSON / Markdown / Mermaid / PNG), open, annotate a node, write a
whole map with AI (
POST /mindmaps/generate), expand one node with AI (POST /mindmaps/:id/nodes/:nodeId/expand), and publish the share page (POST /mindmaps/:id/share, which hands back the public link). Themes, flow direction, folds and layers have no route of their own — they live inside the map's saved data, so an agent changes them by saving the map. The one thing that stays in-app is the PNG taken from the live canvas; the CLI'sformat=pngrenders the share page instead, which is always the fully-expanded map. - 2000-node cap per map; the Mermaid picture is officially experimental and degrades to text when a given map can't render.
For agents
How it works (internals)
- Feature folder:
src/renderer/src/features/mindmap/— the canvas (MindMapCanvas.tsx), node (MindMapNode.tsx), toolbar, and the Zustand store (mindmap-store.ts). Every structural edit routes through the store's onecommit()chokepoint (which pushes undo history and schedules the debounced save); auto-layout islayout.ts(d3-hierarchy → screen axes per direction). Horizontal (left→right / right→left) sibling spacing keeps a constant visible gap — a d3separationsets each pair's center-to-center distance to their half-heights plus a fixed edge gap, so the whitespace you see between two stacked boxes is the same no matter how tall either one is (only the center distance grows to clear a taller box); a single-line pair stays at the original 40px gap (byte-for-byte). On the canvas the heights come from the browser's real text measurement (measureText, viamindmap-node-size.ts), so the gap is constant in rendered pixels; the share page and PNG use the shared char-estimate. Vertical (top→bottom / bottom→top) layouts use the same constant-visible-gap idea on the WIDTH axis, tuned so a vertical map's gaps match a horizontal map's: a sibling pair sits its half-widths plus a fixed edge gap apart (the same 10px whitespace horizontal uses), so narrow nodes sit tight and only a wide node spreads (just enough to clear); different-parent branches get a wider gap to set them apart (the same as horizontal's branch gap). Like horizontal, the canvas measures each node's real width so the gap stays a true constant on screen (off-DOM — the share page and tests — falls back to the char-estimate). (Before, vertical maps were spaced ~2× looser than horizontal — the reported "way too spread out".) Nodes are center-anchored (React FlownodeOrigin = [0.5, 0.5], the sharedNODE_ORIGIN_CENTERconstant), so a parent and a node stacked directly above/below it line up on center and the connector between them is a straight line regardless of the boxes' widths (a parent that fans out to several children keeps the gentle curve). The shared/exported web page centers its boxes the same way, and the PNG export frames that same center-anchored geometry — see invariant 61 in the contract. On top of that, same-level siblings align on their INNER edge — the edge facing the root (left edges in left→right, right in right→left, top/bottom for the vertical directions) — so a column of differently-sized boxes reads as one clean line instead of a ragged one.layoutGroupshifts each node toward the root along the growth axis by half its shortfall from the column's widest/tallest node; the widest/tallest box doesn't move and equal-size columns are byte-identical, so no overlap is introduced. On the canvas the shift uses each node's real rendered width (the browser's own text measurement,measureText), so the edges line up to the pixel — a character-count estimate can't, because a proportional font draws every letter a different width (the share page and PNG keep their own fixed box width, which already matches what they draw). This shift is on the depth (growth) axis only — orthogonal to the cross-axis that makes the stacked connectors straight, which is why both hold at once (invariant 63). The share page and PNG inherit it for free (they read the same laid-out positions). - Node-snap glide:
mindmap-anim.tsowns the animation — a pureshouldAnimatePositionsgate (off aboveMM_ANIM_MAX_NODES= 300 visible nodes, so the 2000-node path pays nothing) plus auseAnimationWindowhook that arms a short class window. The canvas addsmm-animate-positionsduring that window — armed by the[data]reflow effect for store-driven changes, and synchronously inonNodeDragStopfor the snap-back / no-op case + same-paint timing — and a CSStransition: transformon.react-flow__node:not(.dragging)(globals.css) animates the move. Killed underprefers-reduced-motion+html.low-power; layout output is byte-identical (invariant 57 in the contract). - Selection is O(changed nodes), not a re-layout: the d3 layout + structural node
derivation is
deriveBaseNodes, memoized on[data, spacing, fontSize, balance]— NOT selection. Selecting or arrow-navigating only flips theselected/editingflag on the ≤2 affected nodes through the reference-stablewithSelectionoverlay (the twin of the dragwithLockOn), andMindMapNodeisReact.memo'd — so the other nodes skip re-render and no layout runs on a selection. On a big map that's the difference between repainting ~2 nodes and re-laying-out + repainting the whole map (the old code re-ran the full layout twice per selection — the render effect AND the auto-pan reveal). The auto-pan reveal reads the SAME memoized base positions (posById), so the view can never glide to coordinates that disagree with what's drawn. See invariant 71 in the contract. - Data model: a
mindmapsSQLite table (id, title, a JSONdatablob of nodes, soft-delete flag, timestamps). A map is one rooted tree, capped at 2000 nodes, validated for a single root and no cycles on every save (src/shared/mindmap-structure.ts). - Node notes: an optional
note?: stringon each node (inert-when-unused, likeside/layers; bounded byMAX_NODE_NOTE_LENat the Zod boundary AND the layer normalizer). The store'ssetNodeNoteroutes throughcommit()(undoable + autosaved, root allowed, blank clears); the popover open-state isnotePopoverId(mirrorseditingId, pauses the canvas keyboard). The icon + hover-peek live inMindMapNode.tsx; the editor isMindMapNoteEditor.tsx(reusesAnchoredPopover). The AI writes one node's note viaPATCH /mindmaps/:id/nodes/:nodeId(read-modify-write the blob, emitsMINDMAP_CHANGED). Full invariants + tests: the mind-map contract. - Layers (one sub-map per node): a node carries an optional
layersarray (each entry{ id, data },dataa fullMindMapData) holding at most one layer. The store holdsrootData(the whole map) plus alayerPath(the drill trail);activeDatais always the projected view at that path (projectViewinlayer-nav.ts), so every existing structural action, undo/redo, and the autosave operate on the view andcommitfolds it back intorootData— one global undo timeline regardless of depth, and a layer-free map projects to itself byte-for-byte.addLayerToSelectedis enter-if-exists (it drills into a node's existing layer rather than stacking a second). Persistence + JSON/CLI import run through the iterative, depth-/visit-bounded normalizer insrc/shared/mindmap-layers.ts, which enforces one-layer-per-node by collapsing to the content-bearing layer (deepmindmapDataHasRealContent, never a blind drop) and caps nesting at 12 deep / a 20k-node budget; the Zod.maxstays a generous parse tolerance (its.catchwould bulk-drop a node's layers, so the real 1-cap lives in the normalizer). The same content walk powersisDraftBlank, so a fresh unnamed map with content only inside a layer still promotes + saves. The node's layer badge and themindmap-layer-breadcrumbbar are the only new UI surfaces. - IPC:
mindmap-handlers.tsexposes list/get/create/save/rename/delete, import / import-outline / export-file, and the two billable AI channels (mindmap:ai-expand,mindmap:ai-generate). Pure conversion modules are shared:mindmap-markdown.tsandmindmap-mermaid.ts. The share page is generated byshare-html.ts. - Deep link + CLI create: an
omniscio://mindmap/<id>link (built bymindmapDeepLinkinsrc/main/services/deep-link.ts) opens a specific saved map in-app — both the protocol/deep-link path and the in-chat clickable link funnel through ONE renderer chokepoint (src/renderer/src/lib/open-mindmap-by-id.ts): feature-gate →store.openMap(id)→ the existingopen-mindmapreveal event, with a "map not found" toast on a deleted map. It NAVIGATES only — never mutates. The CLIcreate/import/getroutes hand that link back as aurlfield so an agent can give the user a one-click link, andPOST /mindmapsadditionally accepts a nested{ text, children }treefor a one-shot structured create (flattened ITERATIVELY insrc/shared/mindmap-tree.ts, then the samevalidateMindMapStructure). - AI sessions (session-host): the "Sessions" tab is
useMindMapSessionHost(mindmap-session-host.ts) + the sharedSessionHostSidebar; sessions spawn into the hidden, spawnable__mindmap__virtual project (seeded byensure-mindmap-project.ts, hidden byfilterVisibleProjects). The agent reads / edits via the/mindmapsCLI routes; each mutating route emitsMINDMAP_CHANGED, whichuseMindmapPushEvents→applyExternalMindmapChangeturns into a live, undo-preserving reload of the open map. See.claude/memory/contracts/session-host-contract.md. - Gating: registered in
src/shared/unreleased-features.tsasmindmap(status: 'in-development'); the UI gates only throughisUnreleasedFeatureVisible/ the Optional Features toggle /AMC_SHOW_MINDMAP. Headless CLI routes (/mindmaps/*, read + non-AI mutations) return 404 when the feature is off and never expose the billable AI actions.
Mind Map skill (/mind-map)
A bundled Claude Code skill that generates a mind map from source material -- vault notes, local files, or Google Docs. Instead of dumping raw data into the import endpoint (which produces an unusable flat list), the skill interviews the user about scope, angle, and depth, synthesizes a properly structured Markdown outline, validates it against node/depth budgets, and creates the map via the CLI.
How to use it
In any Claude Code session, invoke it with:
/mind-map
(or say "make a mind map from my vault notes about X", "mind map from this file", "visualize as mind map", or similar). The skill walks through these steps:
- Feature gate -- checks whether Mind Map is enabled; if not, enables it automatically (or directs you to approve it in your inbox if approval-gated).
- Source resolution -- reads vault notes (
/kms/search+/kms/notes/:id), local files (Read tool), or Google Docs (via/gogskill). - Interview -- asks up to 4 questions (source, angle, depth tier, preview preference), skipping any the user already answered. Never guesses.
- Synthesis -- builds a Markdown outline following structural rules: 5-7 top branches, 2-6 word labels, rolled-up counts instead of flat lists.
- Validation -- six-point checklist (node count, depth, label length, single-child branches, fat branches, root check). All must pass.
- Preview -- shows the outline if the user opted in; accepts approve, change, or reject.
- Creation -- imports the outline via
POST /mindmaps/import, renames the map, and populates hidden annotations for rolled-up detail.
Depth tiers and limits
| Tier | Typical nodes | Typical levels | When to use |
|---|---|---|---|
| Overview | 15-25 | 3 | Presentations, quick summaries |
| Standard | 25-40 | 4 | Most maps (default) |
| Detailed | 40-100 | 5-10 | User must request this tier |
Hard ceilings: 100 nodes, 10 levels. These are never exceeded without explicit user instruction.
What it does NOT do
- Pick layout direction (that is a canvas setting the user controls).
- Use the built-in AI expand/generate endpoints.
- Modify existing mind maps (always creates a new one).
- Spawn additional sessions.
The skill file lives at .claude/skills/mind-map/SKILL.md.
Related
- Mind Map part 1 — opening a map, the library, and building one with the keyboard or the mouse.
- kms.md — the other panel where your notes live, and where a map can be imported from.
- artifact-sharing.md — publishing something you made so it can be opened elsewhere.
Last verified 2026-09-23