Build SimpleMind
A local mind map editor whose hard problems are refusing to treat evictable browser storage as an archive, keeping the layout identical every time the same file is opened, and exporting a picture that still contains its fonts and images once the browser rasterises it.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Builds the application; nothing runs server-side once it is built.
Get it nodejs.org or a version manager. open ↗
Verify
node --version prints v22 or higher - installfree
Why The archive design depends on writing back to a file the person chose, and the honesty design depends on being able to ask whether storage is persistent.
Get it A current desktop browser; check the support tables for both APIs before committing.
Verify
Opening a file, editing and saving writes back to the same file without a download - have readyfree with an open licence
Why The export embeds a subset of the font in the SVG, which is a use the font's licence either allows or does not, and finding out afterwards is the wrong order.
Get it Choose a font whose licence permits embedding and keep the licence file with the project.
Verify
The licence text permits embedding and the file is in the repository - have readyfree
Why Layout determinism and layout speed are both invisible on a map with twelve nodes, and both are things people abandon an editor over.
Get it Generate one, or import a large outline.
Verify
Layout time is measured and recorded and two layouts are identical
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- document(format_version, id, title, created_at, updated_at, root_id) · the envelope, versioned from the first release - nodes(id, parent_id, ordinal, text, note, collapsed, style_id, manual_x, manual_y) · manual_x and manual_y stay null until somebody drags that node - links(id, from_node, to_node, kind, label) · the cross connections that make this a map rather than an outline - styles(id, name, fill, stroke, text_colour, font_family, font_size) · referenced by id so restyling a branch is one write - assets(id, kind, mime, bytes_base64, sha256) · images live inside the document, because an image referenced by path is an image that goes missing - snapshots(id, at, sha256, bytes) · the last few versions that opened cleanly, kept separately from the working copy - The whole document is one JSON object that round-trips byte-identically through save and load. If it does not, you have a serialisation format rather than a file format, and the difference shows up the first time you diff two versions.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
APP_TITLE | required | Maps | Shown in the tab and in the export metadata. No secrets exist in this build. |
SNAPSHOT_KEEP | required | 5 | How many clean snapshots to retain for recovery. They are small. |
AUTOSAVE_IDLE_MS | required | 800 | Idle time before the working copy is written and undo entries are grouped. |
EXPORT_PNG_SCALE | required | 2 | Rasterisation scale for PNG export. Two is the sensible default. |
MAX_ASSET_BYTES | required | 2000000 | Largest image that can be embedded in a document, since the file carries it. |
The build, in order
A document that is a file
Saving means a file on disk, and the interface says what the browser promised about the working copy.
The interesting case is the browser saying no.
Files
src/storage/persist.tstests/persist.test.tsFalling back to a download where the browser has no handle.
Files
src/storage/file.tsThat is the only meaning that protects anybody.
Files
src/ui/DirtyDot.tsx
done when · tick each as it passeswatch out- Autosaving to IndexedDB and showing the word 'saved' is a promise the browser has not made, because the storage standard starts every bucket in best-effort mode and says an agent under storage pressure should clear best-effort buckets, so the map can vanish between sessions with no error anywhere; request persistence, display what the browser actually granted, and reserve the word 'saved' for bytes that reached a file.
Editing at the speed of thinking
A map can be built entirely from the keyboard and undo removes what a person would call one action.
One action to a person is one entry in the stack.
Files
src/history/group.tstests/undo-grouping.test.tsThis is a thinking tool, and the mouse is the slow path.
Files
src/keys.tsRestoring the parent but not the position is a half undo.
Files
src/history/inverse.ts
done when · tick each as it passeswatch out- One undo entry per keystroke means undo after typing a node title deletes a single letter, so the person hits it eleven times, overshoots into the previous action and loses a branch they wanted; group consecutive edits to the same node within an idle window into one entry, make every structural change its own entry, and test that undo after a reparent restores both the tree and the layout rather than only one of them.
A layout that holds still
Opening the same file twice produces pixel-identical geometry.
No clock, no random, no map iteration order.
Files
src/layout/tidy.tstests/layout-determinism.test.tsThe whole point of dragging a node is that it stays dragged.
Files
src/layout/pins.tsFive hundred nodes is a real map, not a stress test.
Files
tests/layout-perf.test.tsREADME.md
done when · tick each as it passeswatch out- A layout that iterates over a hash map, uses any randomness or depends on insertion order gives different coordinates for the same file on different runs, and because the difference is small people notice it as the map feeling unstable rather than as a bug they can report; sort children by explicit ordinal, make the layout a pure function with no clock and no random source, and assert in a test that laying out the same document twice produces identical numbers.
Export that still contains its text
The exported SVG and PNG look like the screen, including fonts and embedded images.
External references are not fetched when the SVG is an image.
Files
src/export/svg.tstests/export-font.test.tsTwo renderers is two sets of differences.
Files
src/export/png.tsText width is the measurement that catches a missing font.
Files
tests/export-parity.test.ts
done when · tick each as it passeswatch out- An SVG placed in an image element is processed in one of the secure modes defined by the SVG conformance rules, which forbid resolving external references, so your linked web font and your linked images simply do not render and the PNG comes out in a fallback face with holes where the pictures were, while the same SVG opened directly in a tab looks perfect; embed the font subset and every image as data inside the SVG, and compare the exported PNG against the screen rather than trusting that it worked.
Recovery, interchange and the honest limitsproduct builder
A crash costs nothing, a map can leave for another tool, and the README says what is not here.
A snapshot of a broken document is not a backup.
Files
src/storage/snapshots.tstests/recovery.test.tsThe person knows something you do not.
Files
src/ui/Recovery.tsxOne that works beats three that nearly work.
Files
src/io/interchange.tstests/interchange.test.ts
done when · tick each as it passeswatch out- A single autosave slot gets overwritten with the corrupted state the moment after a crash, so the recovery feature hands you exactly the broken document you were trying to escape; keep the last few snapshots that parsed cleanly, never overwrite the most recent good one, and offer recovery as a list with timestamps rather than as one silent restore that you cannot decline.
That is the whole plan for SimpleMind. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Collaboration, cursors and comments.
- High-fidelity import from proprietary formats.
- A presentation mode.
- Cloud sync.
- high-fidelity import and export
- presentation and enterprise controls
- multiplayer cursors and comments
- large template libraries
- A folder view across many maps with search over node text, which is where a personal map collection starts being useful
- Per-branch styling presets, once you have found out which three styles you actually use
- A print layout that paginates a large map sensibly, which is a genuinely hard problem and worth its own weekend
Need the files? The project pack on the verdict page hands your agent the whole brief · more diagrams.