Rust Language - Mind Mapping Software
David Avery
August 13, 2026
I like using different methods to visualise problems, projects and tasks. I had a lifelong licence for piece of software, which then somehow no longer works, needless to say I just started programming my own.
The project is long and I am just heading towards the part where i can save, load and actually implement SVG libraries to make the whole thing look a bit less boring, so far its a good hundred hours learning rust, and implementing the frameworks, I am breaking this up into phases
'MindCanvas' (Phase 1 Currently) is a native desktop mind-mapping application built using Tauri — a Rust backend with a TypeScript canvas frontend. The goal is a fast, offline-first tool with a clean native feel: infinite canvas, radial layouts, a proper file format. built sprint by sprint - when i have time.
Phase 1 is the foundation. When it's done, MindCanvas will be a fully functional mind-mapping tool — not a prototype, it just wont look all that pretty UI yet.
## What's Been Built
### S1.1 — The Infinite Canvas
The first thing to land was the canvas itself: infinite pan and zoom, a grid that scales with zoom level, and an origin indicator that pulses when you reset your view. This sounds simple but it sets the architectural tone for everything that follows. The `Transform` class handles all coordinate conversions between canvas millimetres and screen pixels, and that separation has paid dividends in every sprint since — nothing upstream ever has to think about device pixel ratio or zoom level.
Keyboard shortcuts landed here too: `Ctrl+0` to reset zoom, `Ctrl+scroll` to zoom in and out. The `F` key fit-to-content came later but was already planned.
I plan to reuse this to create a simple moodboard for images and videos for projects too when i get the time as its a useful tool to be able to flick through mood boards and sets for project inspiration instead of folders of refernce images.
### S1.2 — Nodes
The first nodes: rounded rectangles with labels, rendered directly on the canvas. A `NodeRenderer` class reads CSS design tokens for colours so theming works without touching drawing code. Hit testing was implemented here — click detection in screen space with a small padding margin so the border is easy to click.
The `NodeStore` was introduced as the in-memory source of truth for all node data. No persistence yet, but the shape of the data model was already being thought about, with expansion and a defined structure to ensure that later everything can be expanded upon without constantly breaking the software.
### S1.3 — Node Creation and Text Editing
Double-click on empty canvas to create a root node. Double-click a node to edit its label. Since the canvas is a raw `<canvas>` element with no native text input, editing works via a `<textarea>` overlay positioned and sized to match the node exactly in screen space. On commit it dispatches through the command bus and updates the store.
This is also where the **command bus** became central. Every state-changing action — including node creation — goes through `dispatch_command` in Rust. The bus assigns a UUID to each command, logs it with a timestamp and payload, and returns the result. The UUID generation living in Rust rather than TypeScript was a deliberate decision: it keeps the log authoritative and makes undo reliable. This is also a part of the future ability to save and open, then re-render the file.
### S1.4 — Branch Rendering
Parent-child relationships became a thing with a 1 to many structure for the initial build, this can be changed though to link different sections together as there can be multiple originators and branches will be combine-able. Branches are drawn as curved lines from parent to child, sitting above the grid but below nodes in the render order. The `BranchRenderer` handles this layer cleanly — the renderer delegates to it in one call per frame.
### S1.5 — Radial Layout Engine
Two layout modes arrived together: **CHAIN** (Enter key — each new node becomes the parent of the next, building a linear chain) and **STAR** (Tab key — all new nodes attach to the same anchor, building a radial fan). The layout engine calculates placement angles and distances based on existing siblings, so nodes don't collide (its still a bit janky though, i spent a while here, and decided to come back and implement this properly in the future after looking at how computer games manage the kind of problems as its principally the same) . The Tab anchor locks on the first Tab press and stays locked until Escape or a click-away — this makes rapid mind-map construction feel natural.
### S1.6 — Undo/Redo and Delete
The undo system was built in S1.4/1.5 and verified in S1.6. Every `affects_document` command in the bus log carries a `before` snapshot — sent by the frontend since the Rust bus is stateless. Undo walks the log by command ID, not by position, so it's robust to log entries that don't participate in the undo stack (housekeeping commands, theme changes).
Delete got proper handling in S1.6: leaf nodes delete immediately, nodes with children show a prompt overlay with two options — delete the entire subtree, or reparent direct children to the grandparent. Both paths use `node.compound` on the bus to wrap multi-step operations into a single atomic undo entry.
### S1.7 — File Format and Save/Load *(current sprint)*
The native `.mindcanvas` file format is being implemented now. It's a standard zip archive containing three XML files:
- **`manifest.xml`** — format version, app version, creation date, node count
- **`content.mcx`** — the semantic layer: node text, hierarchy, depth, direction
- **`visual.mcv`** — the layout layer: positions, sizes, colour overrides, the `manuallyPositioned` flag
The split between content and visual layers is deliberate. The content file is the *document*; the visual file is the *layout*. A future import tool or accessibility reader only needs `content.mcx`. The layout engine can reconstruct positions from scratch if `visual.mcv` is absent or incompatible. This mirrors the EPUB pattern — a well-understood, proven approach.
The Rust side owns all file I/O. TypeScript never touches the filesystem. The `file.save` and `file.load` commands go through the command bus like everything else, keeping the audit log complete. Dirty-state detection uses a clean marker in the log — on quit, the app checks whether any `affects_document` entry has been recorded since the last save.
---
Random thoughts on the project:
The Command Bus from Day One, The bus wasn't strictly necessary in S1.1. A few global variables would have worked for the first sprint. But building it early meant that every subsequent feature — node creation, text editing, undo, delete, theming — arrived already wired into a consistent audit log with before-state snapshots. When file save arrived in S1.7, dirty-state detection was essentially free: the `affects_document` flag was already on every relevant log entry.
Had the bus been bolted on later, retrofitting before-state snapshots into every existing command would have been painful and error-prone.
The `manuallyPositioned` flag on `CanvasNode` was added in S1.6 (AMD-004), one sprint before file serialisation. This matters because the layout engine's contract is: *never move a manually positioned node*. If this flag had been added after the file format was locked, it would have required a format version bump and a migration path. Adding it one sprint early cost almost nothing and meant `visual.mcv` could carry it from day one.
Coordinate System Discipline: All node positions are stored in canvas millimetres throughout — in memory, in the bus log, and on disk. Screen pixels and device pixel ratio are never written anywhere persistent. This means save files are DPR-agnostic: a file created on a HiDPI display opens correctly on a 1x display, and vice versa which is pretty good, This wasn't enforced by a linter — it was a conscious discipline applied consistently from S1.2 onwards. I managed these mostly by keeping a proper project log for once and having the plan road mapped before, these all helped to keep everything neat including the ability to output prints. a feature that will be present later is the excel inspired bounding box for prints, this will be a togge-able reference view to know whats in a print envelope at the current scale with A4 & A3 indicated. these will go a little further as they should be able to be created in multiples so as to create print zones.
Log as Source of Truth: The undo stack in the frontend holds command IDs, not state snapshots. State is reconstructed by reading the bus log. This means the renderer doesn't maintain shadow state for undo purposes, and the log is always the single authoritative record of what happened. The `_getLastCreatedNode()` function — used to anchor Tab/Enter — reads the log rather than tracking a `lastCreatedId` variable. This approach scaled cleanly through S1.5 and S1.6 without any refactoring. As mentioned before this creates a way to rerender and is based intended to be a sort of proxy of how parametric metric modelling works with a timeline. whilst the project is still in its infancy I plan to use the structures again so I am aim for as clean as possible.
No Shortcuts on Delete: The delete flow in S1.6 could have been simple: press Delete, node is gone, children orphaned. That would have been faster but would also create more problems later. Instead it got a proper prompt, a subtree delete path, and a reparent-children path — all wrapped in compound undo so the whole thing reverses in one Ctrl+Z. The undo handler for `node.compound` dispatches sub-entries recursively, which made the reparent+delete flow correct by construction rather than by careful ordering. The function has different cases and depending on what comes before or after it takes the designated actions. this also brought up the start of defining the way that nodes and all children can be selected, so this took some time to consider use cases and ensure that the way it works is sensible.
When Phase 1 Ends: S1.11 is the current goal for minimum userable program, MindCanvas will hopefully be: 'A complete, usable mind-mapping desktop tool' You'll be able to create a mind map from scratch, navigate it with keyboard shortcuts, save it to a native file, reload it exactly, and export it to the open FreeMind `.mm` format for interchange with other tools. Every action will be undoable. The UI will have proper menus, a toolbar, and configurable keyboard shortcuts. Notes can be attached to nodes. Multi-select, alignment, and group move will work. The full Phase 1 integration test suite will pass — no known regressions. it will still be visually a bit baren though.
Phase 2.** The command bus, coordinate system, file format, and undo architecture are all designed to extend cleanly so they are not the focus of phase 2, it covers visual styling (node fill, border, fonts, themes), branch styling, zones (logical pages), auto-save, layout re-layout with compound undo, find and replace, hyperlinks, and more. None of that requires rethinking the foundation — it builds on it.
**A published file format.** `FORMAT.md` will be in the repo root, openly licensed. Anyone will be able to write a parser for `.mindcanvas` files. The schema is simple and will be intentionally public.
Phase 1 is 75% there currently, I will be updating again shortly. If you made i this far and want to try it, drop me a message and i will send you a copy to try.
13/08/2026 MindCanvas — Phase 1 in progress. Last updated: session #007.*
Newest
Building an Open Source E-Reader for Under €40
Hive Monitor: An Ongoing Build Log