- Rust 98.8%
- Shell 0.7%
- NSIS 0.4%
- Nix 0.1%
The step swept the whole allow-list on every push: ~2 800 mutants, an hour of pipeline, all to say something about the dozen lines the commit touched. `.githooks/pre-commit` runs `cargo mutants --in-diff` over the staged diff instead, which is four mutants and half a minute for an ordinary change, and nothing at all — one second, no build — for a commit that touches no Rust. Install it once per clone with `git config core.hooksPath .githooks`. `git commit --no-verify` skips it once, `RUSTMIND_SKIP_MUTANTS=1` for as long as it is set, and `bin/verify.sh --full` still runs the whole sweep. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012tSQgSyek9Lnkis3Znf8aZ |
||
|---|---|---|
| .agents | ||
| .cargo | ||
| .githooks | ||
| .woodpecker | ||
| apps/rustmind-desktop | ||
| bin | ||
| crates | ||
| docs | ||
| packaging | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CHANGELOG.md | ||
| cliff.toml | ||
| clippy.toml | ||
| CONTRIBUTING.de.md | ||
| CONTRIBUTING.md | ||
| CONTRIBUTING.pt-BR.md | ||
| flake.nix | ||
| LICENSE | ||
| NOTICE | ||
| README.md | ||
| rust-toolchain.toml | ||
| rustfmt.toml | ||
RustMind
A Rust mind-map engine that uses Markdown as the single source of truth.
[RustMind]
|
+---------------+---------------+
| | |
[Parser] [Layout] [Render]
Markdown engines SVG
| | |
+---------------+---------------+
|
[Document Model]
RustMind lets you author, version-control, and render mind maps from plain Markdown files. The same file can be edited in your favorite text editor, validated and exported from the command line, or opened in a visual desktop application.
Features
- Markdown-first: mind maps are stored as extended Markdown with YAML frontmatter, headings, nested lists, IDs, tags, relations, and position/style overrides.
- Deterministic SVG rendering: the same document always produces the same SVG output.
- Multiple layouts: freeform (manual positions), tree (top-down, bottom-up, left-right, right-left), and radial (clockwise and counter-clockwise).
- Complete CLI:
new,validate,render,layout,format,export. - Visual editor: Iced-based desktop app with canvas, zoom, pan, node selection, drag-to-move, and undo/redo.
- Undo/redo: all document mutations in the desktop app go through a command history.
- Clean architecture: domain crates have no GUI dependencies; screen pixels are only used at rendering and input boundaries.
Installation
You need a recent Rust toolchain (MSRV 1.98).
git clone https://forgejo.singularjourney.org/devjonatas/rustmind.git
cd rustmind
# Build everything
cargo build --workspace
# Build only the CLI
cargo build -p rustmind-cli
# Build only the desktop app
cargo build -p rustmind-desktop
The CLI binary is named rustmind and is produced by the rustmind-cli crate.
The desktop binary is named rustmind-desktop and is produced by
apps/rustmind-desktop.
Quick start
# Create a new mind map
cargo run -p rustmind-cli -- new my-map.md
# Edit the file with your favorite editor, then validate it
cargo run -p rustmind-cli -- validate my-map.md
# Apply a radial layout and write positions back to the file
cargo run -p rustmind-cli -- layout my-map.md --type radial
# Render to SVG (stdout)
cargo run -p rustmind-cli -- render my-map.md --format svg > my-map.svg
# Or export directly to a file with the same base name
cargo run -p rustmind-cli -- export my-map.md
# Canonicalize / reformat the Markdown
cargo run -p rustmind-cli -- format my-map.md
# Run the desktop app
cargo run -p rustmind-desktop
CLI reference
| Command | Description |
|---|---|
rustmind new <file> |
Create a new mind map with default frontmatter and a root node. |
rustmind validate <file> |
Parse the file and report errors. |
rustmind render <file> [--format svg|png|pdf|opml|terminal] [--output <file>] |
Render the document. Defaults to SVG on stdout. Terminal output is a stub for future work. |
rustmind layout <file> [--type radial|tree|freeform] [--direction <dir>] |
Apply a layout and write positions back to the file. Preserves manual positions. |
rustmind format <file> |
Parse and serialize the file back to canonical Markdown. |
rustmind import xmind <file> [--output <file>] [--arrange] |
Convert an XMind workbook (.xmind, new JSON or old XML) to a RustMind map. What could not be converted is reported on stderr. |
rustmind export <file> [--format svg|png|pdf|opml] |
Export next to the source file, e.g. <file>.pdf. |
Direction values depend on the layout:
radial:clockwise,counter-clockwisetree:top-down,bottom-up,left-right,right-leftfreeform: ignored
Markdown format
A RustMind document is a Markdown file with optional YAML frontmatter.
---
layout: mindmap
direction: clockwise
version: 1
---
# Root {#root}
## Child A {#child-a}
### Grandchild {#grandchild}
## Child B {#child-b}
@relation child-a -> child-b {type: dependency, label: "depends on"}
@position root {x: 0.0, y: 0.0, mode: manual}
@style root {fill: "#e3f2fd", shape: rounded_rect}
Frontmatter
| Key | Description |
|---|---|
title |
Document title. |
author |
Document author. |
layout |
Default layout: mindmap, tree, or freeform. |
direction |
Default direction for the layout. |
root |
Optional explicit root node ID. |
version |
Document format version. |
Nodes
Headings and nested lists both create nodes. IDs are written with {#id};
tags with {tag1,tag2}. If no ID is provided, a slug is generated from the
title.
Relations
Relations are declared on their own lines:
@relation source-id -> target-id
@relation source-id -> target-id {type: dependency, label: "label text"}
Supported relation types: relation, dependency, reference, causes,
blocks, implements, extends.
Pictures and icons
A node can carry a Font Awesome icon and a picture:
@icon root star
@image root .rustmind/images/logo.png
Pictures are PNG, JPEG, GIF, WebP or SVG, stored relative to the workspace so
they travel with the map. Import one from Inspector → Picture → Import…;
the file is copied into .rustmind/images/.
Markers and labels
Markers state something about a node — priority, progress, task state or mood — and come from a fixed catalog, so a map means the same thing in every renderer. At most one marker per group sticks: setting a second priority replaces the first.
@marker root priority-high, task-doing
| Group | Ids |
|---|---|
| Priority | priority-high, priority-medium, priority-low |
| Progress | progress-start, progress-half, progress-done |
| Task | task-todo, task-doing, task-done, task-blocked |
| Mood | mood-idea, mood-question, mood-star, mood-heart |
Labels are free text and ride in the heading itself:
## Backend {infra, q3} {#backend}
Both are edited under Inspector → Markers & labels.
Links
A node can carry links: web addresses, and jumps to another topic written
node:<id>. One per line, so a URL may contain commas:
@link backend https://example.com/spec
@link docs node:backend
They are edited under Inspector → Links. Clicking a topic link selects the
target and centers the map on it; clicking a web address hands it to the
desktop's browser — only http, https and mailto links are opened, since
opening a link starts whatever program the desktop registered for its scheme.
A node with a note or a link shows a small badge in its top-right corner.
Filtering
The toolbar filter (Ctrl + F) dims what does not match instead of hiding it,
so the layout stays put while you type. A bare word looks in the title and the
note, #word looks in the labels, and @word looks in the markers — a group
name such as @priority catches every marker in the group. Terms combine:
roadmap #infra @priority
Position, size and style overrides
@position node-id {x: 120.0, y: 80.0, mode: manual}
@size node-id {width: 220.00, height: 90.00}
@style node-id {fill: "#ff0000", stroke: "#000000", shape: diamond}
@size is a floor, not a cap: a topic never shrinks below the text it holds,
so a smaller number just lets the title decide again.
Branch style
@edge node-id {type: bezier, color: "#ff8800", width: 2, dash: dashed, taper: true}
@edge styles the line arriving at a topic, and everything below it wears
the same line — set it on a top-level topic to restyle a whole branch. A
topic deeper down can still say something different for its own subtree.
dash is solid, dashed or dotted; branch_color: true makes the line
wear the colour its branch's topics wear.
bend: 12 -30 pulls the middle of the line that far off the straight run
between the two topics, which is what dragging the ring in the middle of a
selected topic's line writes. It is an offset rather than a place, so the curve
keeps its bulge when either topic is moved; Endireitar a linha in the
inspector takes it back off.
taper: true draws the branch thick where it leaves the parent and thinner at
every level below, until the far leaves hang from a hairline — the way a tree
grows. A tapered branch is a filled shape rather than a stroke, so it carries
no arrowhead and no dashes: the thickness says which way it flows. To taper the
whole map, put taper: true in the frontmatter, or use Format → Taper every
branch.
Drawing on the map
F8, or Caneta in the toolbar, turns the map into paper: dragging draws
instead of moving topics. Pick an ink and a width beside the button, or reach
for the eraser, which rubs out the stroke under the pointer. Each scribble is
one entry on the undo stack.
@stroke pen-1 {color: "#e04a4a", width: 3, points: "10 20 14 26 21 30"}
A stroke belongs to the map rather than to a topic: it stays where it was put when a topic is dragged out from under it, which is what drawing on top of something means. Points are pairs, in document coordinates, kept to two decimals — finer than the screen shows.
Packaging
bin/package.sh target/release 0.0.1 writes into dist/: a tarball, a .deb,
an .rpm, an AppImage and the checksums of all of them. Each package whose
tool is missing on the machine is skipped with a line saying which — the
tarball is always produced, and it is what somebody packaging RustMind
themselves starts from.
Flatpak and Nix live beside the script, because their own tools drive the build:
flatpak-builder build/ packaging/com.rustmind.RustMind.yml
nix build # or: nix run .#rustmind, nix develop
Reading other tools' files
rustmind import xmind workbook.xmind
rustmind import freemind plan.mm
rustmind import opml outline.opml
rustmind import docx report.docx
rustmind import mindmanager plan.mmap
rustmind import mindnode plan.mindnode
rustmind import textbundle map.textbundle
The same formats have buttons in the project browser. An import is never
lossless — every tool has features the others do not — so anything that could
not be carried over is reported rather than dropped in silence. A Word document
is read by its heading levels: Heading 1 becomes a root, Heading 2 its
child, and the paragraphs under a heading become that topic's note.
TextBundle
A TextBundle is a folder holding text.markdown, info.json and an assets/
directory — a map and the pictures it uses, in one thing to move. Arquivo →
Exportar como TextBundle writes one, the browser's Importar TextBundle
reads one back, and the CLI has both:
rustmind export map.md --format textbundle
rustmind export map.md --format docx
rustmind export map.md --format xlsx
rustmind import textbundle map.textbundle --output back.md
Pictures are copied in and the map's @image paths are pointed at the copies,
so the folder stands on its own; on the way back they land in the workspace's
.rustmind/images. A name already taken is numbered rather than overwritten.
Map shot
Recorte, beside the pen, puts a frame in your hand: drag a rectangle over the map and everything outside it dims. Exportar o recorte then writes what is inside as SVG, PNG or PDF.
The frame decides the picture, not the topics: a shot of a corner of the map is a shot of whatever happens to be there. A topic reaching over the edge is kept and clipped by the frame, the way a photograph clips; a line with one end outside is left out, since it would be drawn from nowhere.
Audio notes
The inspector's Recording row records a note for the selected topic: the
same button starts and stops, counting up while the microphone is on. It lands
in the workspace's .rustmind/audio/ as a 16-bit WAV and the topic gets a
speaker badge, since a recording is otherwise invisible on a map.
@audio root .rustmind/audio/root.wav
WAV because the map has to outlive this app: a format every player already reads is worth more here than a smaller file. Taking a recording off a topic leaves the file alone — it is something somebody made.
Equations
The inspector's Equation button opens a box for LaTeX. What it draws is an SVG written into the workspace and set as the topic's picture, so a formula travels through every renderer the way any other picture does — and the source stays on the topic, because an SVG cannot be edited back into LaTeX.
\frac{-b \pm \sqrt{b^2 - 4ac}}{2a}
It is not TeX. Variables, numbers, operators, fractions, roots, sub- and
superscripts are what a formula on a topic needs; the rest of LaTeX is left to
LaTeX. The parsing is pulldown-latex, the layout and the SVG are ours — the
alternative was a TeX engine that has to be installed.
Illustrations
The inspector's Illustrations button opens the drawings that ship with the
app — 151 hand-drawn doodles that pair with the sketch style. Choosing one
copies it into the workspace's .rustmind/images/ and points the topic's
@image at it, so the map keeps its picture with or without this app.
They are the Mega Doodles Pack by Maria Letta, under CC BY-SA 4.0. The credit
is beside them in the picker and in NOTICE: a published map with one in it is
handing the drawing on, and the licence asks for the name to travel with it.
The picker windows
The icon and illustration pickers are windows, not rectangles drawn over the
map: a tiling compositor can float, move and tile a window, and can do none of
those to a drawing. They carry the application id rustmind-picker, which is
what a rule is written against:
# Hyprland
windowrulev2 = float, class:^(rustmind-picker)$
Imported textures
Five fill textures are drawn in code — dots, grid, diagonal, cross. A sixth
kind comes from a file: the button beside the texture picker imports a GIMP
.pat, which lands in the workspace's .rustmind/textures/ and joins the
list. It is written the same way in the file:
@style root {texture: bricks}
A name that is not one of the five is an imported pattern. A map naming one this machine has not imported still draws — the topic loses its texture, the way a map naming a missing font still draws — because the map is right and the machine is the one missing a file.
Stickers
Adesivo, beside the pen, opens the icon picker and sticks the chosen icon
on the map — loose from any topic, in the middle of what is on screen. Drag it
to move it, +/− to size it, Del to peel it off.
@sticker sticker-1 {icon: star, x: 120, y: -40, size: 48, color: "#e0a52f"}
The x/y are the middle of it, because a sticker grows and shrinks about
where it was put rather than walking off from its top-left corner.
Boundaries, areas and summaries
Three ways to group topics without changing the tree.
An @boundary draws an outline around a set of topics — either a whole
branch or a hand-picked list:
@boundary discovery {subtree: research, title: "Discovery", fill: "#89b4fa22", stroke: "#89b4fa"}
@boundary delivery {nodes: build prototype tests, title: "Delivery", shape: rounded, dash: dotted}
| Field | Values | Default |
|---|---|---|
subtree |
a topic id: it and everything below it | — |
nodes |
topic ids, space-separated | — |
title |
label drawn at the top-left | none |
kind |
boundary, zone |
boundary |
shape |
rounded, rect, ellipse |
rounded |
fill |
hex, alpha allowed (#89b4fa22) |
hollow |
stroke |
hex | theme edge |
width |
outline width | 1.5 |
dash |
solid, dashed, dotted |
dashed |
corner_radius |
rounding for rounded |
14 |
padding |
gap between the topics and the outline | 16 |
collapsed |
zones only: fold the area away | false |
Either subtree or nodes is required — an outline around nothing is a parse
error rather than an empty box. A boundary that names a topic which is not
there drops it and keeps the rest; one left with no members at all is dropped.
A zone (kind: zone) is the same outline with a fold: collapsed: true
hides its topics and the lines that reached them, and leaves a title bar where
the outline was.
A @summary braces a run of siblings and hangs one topic off the brace:
@summary s1 {parent: plan, from: 0, to: 2, topic: result, stroke: "#f9e2af", width: 2}
from and to are child indices, inclusive; a range written backwards reads
the same, and one running past the last child stops there. The brace brackets
the whole branches, not just the sibling boxes, and sits on the far side from
the parent — so it never crosses the topics it is summarising. The summary
topic is placed at the brace's tip and drops its parent line: the brace is its
connection.
In the app: the Groups section of the inspector, and Format → Outline this branch / Make an area / Summarise from here.
Title alignment
@style node-id {text_align: right, text_valign: bottom}
text_align is left, center (the default) or right; text_valign is
top, middle (the default) or bottom. Both are in the desktop app under
Format in the menu bar and in the inspector's typography section. Vertical
alignment shows on a topic whose box is taller than its title — one given a
@size, or one carrying a picture, markers or labels.
Desktop app
The first time you launch the desktop app it asks for a workspace folder. That
folder becomes the root for all your RustMind projects. Each project is a
sub-folder and can contain any number of .md map files.
Configuration is stored in ~/.config/rustmind/config.toml:
workspace_path = "/home/you/RustMindWorkspace"
first_run = false
Fonts and icon fonts imported from Settings → Fonts & icons are copied into
<workspace>/.rustmind/, so they travel with the maps that use them. See
docs/FONTS.md.
UI themes
Settings → UI theme dresses the app in any of the built-in palettes — Dracula, the four Catppuccins, Gruvbox, Tokyo Night, Nord, One Dark, Solarized, Everforest, Kanagawa, Rosé Pine, Nightfox, Monokai — and gives the open map the matching map theme at the same time, so the chrome and the map agree.
Beside the picker, Import UI theme… reads a palette from a TOML file and Export UI theme… writes the active one back out:
name = "Midnight Harbor"
dark = true
background = "#101820"
surface = "#1b2733"
text = "#e6edf3"
muted = "#8b9aa8"
primary = "#4fa3d1"
success = "#63c187"
warning = "#e0b352"
danger = "#e06c75"
colors = ["#4fa3d1", "#63c187", "#e0b352", "#e06c75", "#a78bfa"]
Only name is required; anything left out takes a dark default. An imported
theme is kept in ~/.config/rustmind/ui-themes/ and reloaded at start-up, and
from then on it sits beside the built-ins in the picker, in the palette gallery,
and as a map theme. See docs/THEMES.md.
Quick entry
Ctrl + E, or the Quick entry button in the browser, opens a box for the
moment before a map exists: type what you are thinking and watch the map build
itself next to the text.
Ctrl + Alt + E does the same from any window — it brings RustMind forward and
opens the box, so a thought that arrives while you are somewhere else has
somewhere to go. Whether the shortcut can be taken at all is the desktop's
call: X11, Windows and macOS hand it over, Wayland does not, and when it
refuses the settings say so rather than leaving a key that does nothing.
Projeto
Backend
API
Frontend
Indenting makes a child of the line above; bullets (-, *, +, •) and
list numbers are optional and stripped; blank lines only separate. When the
text leaves several top-level lines, the name typed for the map holds them —
or, with no name, the first line does. Pick the layout and theme next to the
text, then Create map writes it into the selected project and opens it.
Importing XMind
rustmind import xmind plano.xmind # writes plano.md, laid out
rustmind import xmind plano.xmind -o m.md # somewhere else
Both workbook formats are read: content.json from XMind 8 and later, and
content.xml from XMind 2012 and earlier. Topics, notes, labels, markers,
hyperlinks (links to another topic included) and relationships come through;
the structure class picks the layout.
An import is never lossless, so nothing is dropped in silence — boundaries, summaries, sheets past the first and markers with no equivalent here are each reported on stderr:
warning: the sheet "Rascunho" was not imported; a RustMind map holds one sheet
warning: the marker "people-red" has no equivalent here (1 topic)
Exporting
File → Export writes the map as SVG, PNG, PDF or OPML, chosen by the extension you give the file.
SVG, PNG and PDF are the map as drawn, pictures included — a focused branch exports focused, because that is what is on screen. The PDF goes through the same SVG renderer as the others and keeps text as text and shapes as vectors, so it is not a picture of a PNG.
OPML is the document instead of the drawing: every topic, whatever is focused
or collapsed, since an outline has no geometry. Titles, notes, labels, markers
and links travel; anything RustMind-specific rides in an attribute prefixed
with _, which readers that do not know it ignore.
Starting maps
The browser's Start from: row creates a map already laid out for a job — SWOT, project, meeting, study — in the interface language, because a map that starts in the wrong language is a map someone has to retype. A skeleton is just an outline in the same indented form quick entry reads, so adding one is writing a few lines of text.
The Theme menu lists every theme the map knows — the built-in palettes included — with a check on the one in use, so a whole new colour scheme is two clicks away from the map itself. Settings → Map theme shows the same palettes as cards of swatches, for picking by colour rather than by name. Either way, shapes, spacing and fonts stay where they were.
Hand-drawn style
The toolbar's Sketch button (or View → Hand-drawn style) redraws the
map as if by hand: wobbling outlines, drawn twice the way a pen goes back over
a line it just made. It is a property of the map, not of its theme — the same
colours can be wanted clean or sketched — and it saves as sketch: true in the
frontmatter.
The wobble is generated once in rustmind-scene and read by every renderer, so
the screen, the SVG and the PNG draw the same line, and it is seeded from each
node's id, so a map looks the same on every open and every export.
The text is written by hand too, in Caveat (SIL Open Font License, embedded), a topic that asked for a face of its own keeping it. A hand-drawn box with typeset text in it looks like a drawing somebody gave up on halfway.
Presentation
F5 turns the map into slides: the whole map first, then one slide per branch
of the root, in the order the root's children are in — so rehearsing a talk is
rearranging the map. The camera eases from one slide to the next rather than
cutting, and the slide's topic is selected as it arrives, which is what points
at what is being talked about.
Arrows, Space, PageUp/PageDown, Home and End move; Esc leaves and
puts the camera back where it was. While presenting, the keyboard drives the
deck and nothing else — a stray Tab in front of an audience must not add a
node.
Zen mode
F10 clears everything but the map: no menu bar, no toolbar, no panels, and
fullscreen. Esc (or the ✕ in the corner) leaves and puts the panels back the
way they were found, not the way the defaults describe them.
The one strip that stays carries a night toggle, which darkens the canvas background without touching the map's theme — it dims the room, not the drawing — and a clock: a stopwatch, or a 25- or 5-minute countdown. The clock only ticks while it runs, so an idle app stays idle.
Outline view
Ctrl + Shift + O swaps the canvas for the same topics as an indented list,
and back. The selection is shared, so switching keeps you where you were, and a
collapsed node hides its subtree in both views. With a branch focused, the
outline lists that branch alone.
In the outline, Tab and Shift + Tab indent and outdent (indenting makes the
node a child of the sibling above it), the arrow keys walk the list, and
Alt + ↑ / Alt + ↓ reorder — the same commands the map uses, so undo works
across both views.
Controls
| Action | Control |
|---|---|
| Pan | Left-click and drag on empty canvas. |
| Zoom | Mouse wheel (zooms around cursor) or Ctrl + / Ctrl -. |
| Select node | Left-click on a node, or click an entry in the navigator sidebar. |
| Drag node | Left-click and drag a selected node. |
| Add child / sibling | Toolbar buttons, Tab (child), Enter (sibling), or context menu. |
| Duplicate node | Context menu. |
| Edit node | Toolbar Edit button, F2, or context menu. |
| Delete node | Toolbar Delete button, Delete, or context menu. |
| Collapse / expand | Toolbar Collapse button or context menu. |
| Cycle shape | Toolbar Shape button or context menu. |
| Lock / unlock position | Context menu. |
| Reset position | Context menu. |
| Apply layout | Toolbar Radial / Tree buttons. |
| Add relation | Inspector panel. |
| Filter nodes | Toolbar filter field or Ctrl + F. |
| Focus a branch | Toolbar Focus button or Ctrl + B (press again to leave). |
| Quick entry | Ctrl + E, or the Quick entry button in the browser. |
| Map / outline | Toolbar toggle or Ctrl + Shift + O. |
| Zen mode | Toolbar Zen button or F10; Esc leaves. |
| Presentation | Toolbar Present button or F5; arrows move, Esc leaves. |
| Indent / outdent | Tab / Shift + Tab, in the outline. |
| Undo / redo | Toolbar buttons or Ctrl + Z / Ctrl + Y. |
| Fit scene | Toolbar Fit button or context menu. |
| Reset camera | Toolbar Reset button or context menu. |
| Context menu | Right-click on the canvas or a node. |
| Save map | Ctrl + S. |
Architecture
The workspace is organized into focused crates:
| Crate / App | Responsibility |
|---|---|
rustmind-core |
Document model, geometry, node styles, themes, commands, undo/redo primitives. |
rustmind-parser |
Extended Markdown parser and deterministic serializer. |
rustmind-scene |
Intermediate scene graph and text measurement heuristics. |
rustmind-layout |
Layout engines: FreeformLayout, TreeLayout, RadialLayout. |
rustmind-svg |
Deterministic SVG renderer. |
rustmind-cli |
Command-line interface built with clap. |
rustmind-iced |
Reusable Iced UI state, canvas program, and message loop. |
apps/rustmind-desktop |
Desktop application entry point. |
Domain crates never depend on Iced or any GUI library. The GUI crate depends on domain crates and only converts between screen and document space at the rendering and input boundaries.
Development workflow
# Format, check, test, and lint (run these before committing)
cargo fmt --all -- --check
cargo check --workspace
cargo test --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo doc --workspace
The Woodpecker CI pipeline runs the same steps.
Testing
- Unit tests live next to the code they exercise.
- Snapshot tests use
instafor parser output, layouts, and SVG. - Property-based tests use
proptestfor geometry and serialization. - CLI integration tests live in
crates/rustmind-cli/tests/cli.rsand exercise the binary viaassert_cmd.
Documentation
Every public API item is documented with rustdoc comments. Build and open the workspace docs with:
cargo doc --workspace --open
Roadmap
This project is under active development. Near-term work includes:
- Persistent desktop settings and recent files.
- Terminal renderer stub replacement.
- Import/export plugins (future, out of MVP scope).
See .agents/backlog/mvp.md and .omo/plans/rustmind-mvp.md for the current
backlog and detailed plan.
Contributing
See CONTRIBUTING.md for guidelines.
License
Dual-licensed under MIT OR Apache-2.0.