Build Ayoa

KINDAback to the verdict

0%0 of 16 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A browser canvas for mind maps and tasks whose hard problems are an undo history people actually trust, keeping tasks and map nodes as one object rather than two that drift, and producing an export that renders correctly outside the application, which is where most home-built canvases quietly fail.

estimated effort multi-daythe files for this build are in the project pack

RuntimeReact with TypeScript, built with Vite, running entirely in the browserStorageIndexedDB for documents, with an explicit file export as the durable copyModelOne node type that can carry task fields, never a separate task recordHistoryAn append-only command log with coalescing, not snapshots of the documentExportSelf-contained SVG with fonts embedded, rasterised through the same path

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Builds the application and runs the test suite.

    Get it nodejs.org, or your version manager. open ↗

    Verify node --version prints v22 or higher

  2. have readyfree with an open licence

    Why The export embeds the font bytes in the file, which is a licensing question as much as a technical one.

    Get it An open licence family whose terms allow embedding, downloaded as a subsettable file.

    Verify The licence permits embedding and the file subsets without error

  3. have readyfree

    Why Export fidelity is only testable somewhere else, and a new browser tab is not somewhere else.

    Get it An image viewer and a vector editor you already have.

    Verify The exported SVG opens in both with the intended typeface

  4. decidefree

    Why Every canvas project drifts towards collaboration, and deciding against it once is cheaper than deciding against it repeatedly.

    Get it Write down that there is no server, no presence and no shared document, and what you would build instead if you needed one.

    Verify The decision is in the README and no networking code exists

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- documents(id, title, created_at, updated_at, schema_version) · the version is what lets an old file still open
- nodes(id, doc_id, parent_id, text, x, y, width, height, colour, collapsed, style_json)
- edges(id, doc_id, from_node, to_node, kind, label) · kind covers the hierarchy edge and free connections
- task_fields(node_id, due_at, done_at, priority, assignee_label) · attached to a node, never a second object
- commands(doc_id, ordinal, at, kind, payload_json, inverse_json, selection_before, selection_after) · the undo history, with the selection on both sides
- assets(doc_id, id, sha256, mime, bytes, data) · images pasted into the map, stored inline because the export has to carry them
- A node with task fields is still a node. There is no board document and no map document, only two views over the same nodes.

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.

VariableNeededExampleWhere the value comes from
VITE_APP_TITLErequiredMapsWindow title and export metadata.
VITE_AUTOSAVE_DEBOUNCE_MSrequired800The most work a crash can cost, in milliseconds.
VITE_HISTORY_LIMITrequired500Commands retained before the oldest are dropped.
VITE_RECOVERY_SNAPSHOTSrequired5Size of the recovery ring kept in browser storage.
VITE_EXPORT_REMINDER_DAYSrequired7After this the last-export date turns into a prompt.

The build, in order

  1. A canvas that does not fight the pointer

    Nodes can be created, moved, connected and edited with a pointer or a keyboard, and the geometry stays sane.

    1. A saved layout should not remember how you were looking at it.

      Files src/canvas/coords.tstests/coords.test.ts

    2. Outlining speed is the only reason to use a mind map.

      Files src/canvas/keyboard.ts

    3. Screen-space hit testing drifts the moment you zoom.

      Files src/canvas/hit.ts

    done when · tick each as it passes
    watch out
    • Storing positions in screen coordinates makes every saved document depend on the zoom level it was edited at, so reopening at a different zoom scatters the map and no amount of later fixing recovers the intended layout. Store document coordinates, convert only at render and at hit test, and assert in the suite that no stored coordinate changes when the zoom does.
  2. Undo that returns you to where you were

    Every edit is undoable, typing collapses into one step, and undo restores the selection as well as the document.

    1. Undo is a navigation as much as an edit.

      Files src/history/command.tstests/undo-selection.test.ts

    2. One undo per keystroke is the same as no undo.

      Files src/history/coalesce.ts

    3. The one direct mutation is the one that breaks undo.

      Files tests/no-direct-mutation.test.ts

    done when · tick each as it passes
    watch out
    • Undo that restores the document but not the selection makes a user press undo again to find where they were, and the second press removes an edit they wanted to keep, which is why home-built canvases feel untrustworthy. Store the selection on both sides of every command and restore it, then test that undo followed by redo leaves both document and selection identical.
  3. A node that is also a task

    Adding a due date to a node makes it appear on the board without creating a second object.

    1. Two titles for one item is a synchronisation bug you chose.

      Files src/model/node.ts

    2. Undo should not care which view you were in.

      Files src/board/commands.tstests/board-undo.test.ts

    3. An orphaned task is the second half of the same bug.

      Files src/model/delete.ts

    done when · tick each as it passes
    watch out
    • Creating a separate task record linked to a node gives you two titles that must be kept in step, and within a week the board and the map disagree about what the item is called, with no obvious winner. Keep one record and two views, and make the board's edits ordinary commands so undo works across both.
  4. An export that opens correctly somewhere else

    The exported SVG renders identically in a browser, an image viewer and whatever your colleague uses.

    1. An SVG used as an image is not allowed to fetch anything.

      Files src/export/svg.tstests/no-external-refs.test.ts

    2. Then a broken export is visible before you send it.

      Files src/export/png.ts

    3. Opening a file from a later version should say so.

      Files src/io/json.ts

    done when · tick each as it passes
    watch out
    • Referencing a web font by URL from the exported SVG produces a file that looks perfect in your own tab and falls back to a default font everywhere else, because the SVG specification requires an SVG used in place of a raster image, including in an HTML image element, to be processed in secure static mode, where external references and scripting are disabled. Embed the font bytes in the file, and check the export by opening it in an image element rather than in a new tab.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more diagrams.