This document describes the v0.9 Markdown-like format read by parseMindMap in @xiangfa/mindmap/core. The parser creates a MindMapDocument; a Viewer, Editor, Static surface, or SVG exporter then projects that document. Syntax that belongs to an Extension is enabled only when the same Extension is passed to the parse and render paths.
import { parseMindMap, serializeMindMap } from '@xiangfa/mindmap/core'
import { basicMindMapExtensions, crossLinkExtension, latexExtension } from '@xiangfa/mindmap/extensions'
const extensions = [
...basicMindMapExtensions(),
crossLinkExtension(),
latexExtension(),
]
const document = parseMindMap(markdown, { extensions })
const markdownAgain = serializeMindMap(document, { extensions })basicMindMapExtensions() enables tags, folding, multiline content, and dotted lines. Cross-links and LaTeX are explicit additions. Frontmatter metadata is recognized by the core parser; frontmatterExtension() is available as a named Extension entry when an application keeps an explicit extension registry.
Keep the Extension list stable and pass it to every operation that needs the syntax: parseMindMap, layoutMindMap, renderMindMapToSvg, serializeMindMap, or a React surface's extensions prop. If an extension-owned attribute is serialized without its Extension, the attribute remains in the Document but its Markdown marker may not be emitted.
A line without a list marker is a root node. A line beginning with - is a list item; indentation determines its parent and depth. Blank lines are ignored and are useful for making independent root trees readable. A blank line alone does not create a node.
Product strategy
- Research
- Customer interviews
- Positioning
- Delivery
- Prototype
Operations
- Deploy
- Observe
The parser also accepts * and + as list markers. + is an ordinary list marker unless the Folding Extension is enabled. -. is a dotted list marker when the Dotted-line Extension is enabled. Tabs count as two spaces for indentation; use consistent indentation so the intended parent is unambiguous.
A list-only root is valid as well:
- Product strategy
- Research
The parser records whether a root was authored with list syntax so serializeMindMap can preserve that form. A bare source such as Product strategy\n- Research and the list-only form above produce the same tree shape. If no usable node is supplied, the Document contains one generated root named Root.
The parser rejects input that exceeds any of these limits:
| Limit | Value |
|---|---|
| Markdown source or aggregate Document content | 1,000,000 UTF-16 code units |
| Nodes | 20,000 |
| Nesting depth | 256 levels |
| Node ID length | 256 code units |
| Metadata entries | 256 |
| Comments | 4,096 |
| Values in one attribute collection | 1,024 |
| Aggregate attribute entries | 100,000 |
| Tags on one node | 128 |
| Multiline and remark continuation lines on one node | 256 |
| Cross-links on one node | 64 |
| Inline tokens in one label or multiline line | 4,096 |
| Image tokens in one Document | 64 |
| Projected render primitives | 100,000 |
| Patches in one direct batch | 256 |
Attribute values must form an acyclic JSON tree; shared object identities, accessors, non-finite numbers, functions, and other non-JSON values are rejected. Large controller diffs may be represented by one validated replace-document Patch instead of hundreds of individual moves. Raw SVG passed to renderSvgToPng has a separate 16 MiB pre-encoding limit.
Node IDs generated from a parsed path are stable while that path is stable: the first root is mm-0, its first child is mm-0-0, and so on. IDs must be non-empty, control-free strings. A valid cross-link anchor changes the anchored node's ID to mm-anchor-<anchor>.
Node labels and multiline lines use the shared tokenizeMindMapInline tokenizer. It recognizes the following forms:
Product plan
- **Important** decision
- __Also bold__ wording
- *Open question*
- _Also italic_ wording
- ~~Deprecated~~ approach
- `component-name`
- ==Key concept==
- $E = mc^2$
- $$\int_0^1 x^2 dx$$
| Syntax | Token | Rendered effect |
|---|---|---|
**text** or __text__ |
bold |
Bold text |
*text* or _text_ |
italic |
Italic text |
~~text~~ |
strikethrough |
Line-through text |
`text` |
code |
Monospace text |
==text== |
highlight |
Emphasized highlighted text |
$formula$ |
math |
Inline math when the LaTeX path is enabled |
$$formula$$ |
math with display: true |
Display math when the LaTeX path is enabled |
The tokenizer is deliberately small and Markdown-like; it is not a general Markdown parser. Unrecognized markup stays in the text token and remains readable.
Use link and image tokens inside a node label or multiline line:
Resources
- [Project brief](https://example.com/brief)
- [Local notes](./notes)
- [Jump to the API](#api)
- 
- 
The shared sanitizeMindMapUrl policy is applied before a URL reaches the React DOM or portable SVG:
| Token | Accepted URLs |
|---|---|
Link [label](url) |
http: and https: with a valid hostname, mailto:, tel:, relative paths, and fragments |
Image  |
A bounded base64 data:image/png, image/jpeg, image/gif, or image/webp URL; sanitized http: and https: URLs only after explicit host authorization |
The runtime rejects empty values, control characters, backslashes, protocol-relative URLs such as //cdn.example.com/a.png, executable or local schemes, SVG data URLs, and relative image sources. Raster data URLs are limited to 1,000,000 characters. A rejected or unauthorized image falls back to its alt text.
Remote HTTP(S) images are denied by default. Set a surface's remoteImagePolicy to a predicate that allowlists intended origins, or to "allow" only when every sanitized remote URL is trusted. A policy configured on createMindMapController is inherited by a surface using that controller; the surface prop takes precedence. Authorized DOM images use anonymous CORS and a no-referrer policy.
Portable PNG export must be self-contained: pass an explicitly authorized imageResolver through prepareMindMapSvg to turn selected remote image URLs into safe raster data URLs before calling renderSvgToPng. The resolver must return a PNG, JPEG, GIF, or WebP data URL. An unresolved URL still follows remoteImagePolicy; PNG conversion rejects any remaining remote image reference. Browser canvas and CORS behavior still applies to the resulting image.
A line beginning with > attaches a remark to the most recently parsed node. Leading whitespace and one optional space after > are ignored. Consecutive remarks are joined with newline characters and do not become children.
Machine Learning
- Supervised Learning
> Learn a mapping from labeled examples.
> Use it to predict new inputs.
- Classification
> The output is a discrete category.
Remarks are stored as attributes.remark.text. Interactive surfaces expose them as supporting node information; portable SVG output includes them in an accessible <title>. A > line without a preceding node is ordinary text, so malformed or partial streams remain representable.
A line beginning with %%, optionally after whitespace, is a source comment:
%% This stays in the source view
Research plan
- Interviews
%% This note is not rendered as a node
- Capture questions
Comments are stored in document.comments with the source text and the ID of the preceding node (afterNodeId). They are not rendered in the map, but serializeMindMap and the optional Markdown editor can preserve them. An inline occurrence such as test%%demo is ordinary node text; only a line prefix starts a comment.
Task markers are recognized at the beginning of a line's node text. The marker is removed from the label and stored in attributes.task.status:
Learning Plan
- [x] Linear Algebra
- [X] Probability Theory
- [-] Optimization Theory
- [ ] Information Theory
| Source marker | task.status |
Runtime marker |
|---|---|---|
[ ] text |
todo |
Empty task box |
[-] text |
doing |
In-progress marker |
[x] text or [X] text |
done |
Completed marker |
Tasks are core attributes, so parsing and serialization preserve their state without an Extension. The Editor task command cycles todo → doing → done → todo for the selected node.
Frontmatter is recognized only when --- is the first source line and a matching closing --- is present. Each body line uses a simple key: value form; keys may contain letters, numbers, _, ., and -. Values are stored as strings in document.metadata.
---
direction: right
theme: dark
owner: platform
---
Product strategy
- Research
- Delivery
The core Document projects these two keys when their values are valid:
| Key | Accepted values | Document field |
|---|---|---|
direction |
left, right, both |
document.direction |
theme |
light, dark, auto |
document.theme |
Other keys remain in document.metadata for round trips. The frontmatter parser does not implement YAML nesting, arrays, quoted-value decoding, or arbitrary YAML types. An unclosed opening delimiter stays provisional and does not commit metadata as a completed frontmatter block.
The parser keeps extension data under the namespaced attributes object so the core Node shape remains stable:
interface MindMapNode {
id: string
text: string
children?: MindMapNode[]
attributes?: MindMapNodeAttributes
}
interface MindMapDocument {
roots: MindMapNode[]
direction?: 'left' | 'right' | 'both'
theme?: 'light' | 'dark' | 'auto'
metadata?: Record<string, string>
comments?: { text: string; afterNodeId: string | null }[]
}
interface MindMapNodeAttributes {
task?: { status: 'todo' | 'doing' | 'done' }
remark?: { text: string }
tags?: { values: string[] }
folding?: { collapsed: boolean }
multiline?: { lines: string[] }
connection?: { dotted?: boolean; label?: string }
[namespace: string]: unknown
}Treat Documents and controller snapshots returned by the runtime as read-only. Use controller commands or a transaction to publish edits.
---
direction: both
theme: dark
---
Product roadmap
- **Discovery** #research
> Confirm the problem before building.
- [x] Interviews
| 12 customer conversations
- **Delivery**
+ Prototype
- API -> {#service} "HTTP"
- Service {#service}
| $p(success) = 0.8$
%% Kept in the Markdown source
Operations
- [ ] Monitor the service
This example uses the core syntax plus tags, folding, multiline content, cross-links, and LaTeX. Enable the corresponding Extensions before parsing and pass the same list to the selected renderer and serializer.
| Feature | Syntax | Ownership |
|---|---|---|
| Root / child | Plain text / - text |
Core |
| Alternate list marker | * text or + text |
Core marker; folding behavior is an Extension |
| Inline formatting | **bold**, *italic*, ~~strike~~, `code`, ==highlight== |
Core tokenizer |
| Math tokens | $...$, $$...$$ |
Core tokenizer; LaTeX rendering Extension/runtime |
| Link / image | [label](url) /  |
Core tokenizer and URL policy |
| Remark / comment | > note / %% note |
Core |
| Task | [ ], [-], [x] after a list marker |
Core |
| Dotted connection | -. text |
Dotted-line Extension |
| Multiline content | ` | text` |
| Tags | #tag |
Tags Extension |
| Collapsed branch | + text |
Folding Extension |
| Cross-link | {#id}, -> {#id}, -.> {#id} |
Cross-link Extension |
| Document metadata | Opening and closing --- block |
Core frontmatter parser |
See Extended Mindmap Syntax Support for Extension setup and Custom Styling for runtime classes and theme tokens.