No description
  • TypeScript 97.5%
  • JavaScript 2.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Nasonov Viktor 1855dfacbb Alpha 19
The suite reads the same on alpha.19 as on alpha.18 with the source
untouched. The one smoke failure, a frame in the idle second, is the
desktop's: it fails 2 runs in 10 on alpha.18 too, and a probe caught
the OS taking key focus off the window as it happened.

Both asks from the alpha.18 report landed. F90's `clip` on a float
goes on every node and twisty, so a node panned under the toolbar or
the status bar is cut at the canvas's edge and takes no click there;
the part inside still does. F91's headless `Ctx.size()` retires the
workaround: `init` reads `surface.size()` under either driver, and
`measure.ts` no longer parks a size the drivers passed by hand. The
source needs alpha.19 for it.

Taking the clip turned up this app's own bug: the zoom hint was
anchored to the window, 12px above its bottom, inside the 28px status
bar and over the zoom readout, since alpha.6. It sits in the canvas's
corner now, and the five previews are re-taken for it alone: one line
each, the hint's rect 28px up.

F88 keeps a hovered tool button's name ("undo", where alpha.18 read
"undo Undo (ctrl+z)"). Seven checks pin the clip, the hint and the
name, 97 from 90. The smoke measures its idle second again, up to
twice, when the window's focus, an owed transition or the cursor
shape shows the desktop touched it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-26 13:50:57 +03:00
preview Alpha 19 2026-09-26 13:50:57 +03:00
repro Alpha 14 2026-09-15 23:36:42 +03:00
src Alpha 19 2026-09-26 13:50:57 +03:00
.gitattributes Alpha 16 2026-09-20 20:06:58 +03:00
.gitignore Alpha 6 2026-09-06 15:21:46 +03:00
.npmrc Alpha 6 2026-09-06 15:21:46 +03:00
FINDINGS.md Alpha 19 2026-09-26 13:50:57 +03:00
package-lock.json Alpha 19 2026-09-26 13:50:57 +03:00
package.json Alpha 19 2026-09-26 13:50:57 +03:00
README.md Alpha 19 2026-09-26 13:50:57 +03:00
tsconfig.json Alpha 6 2026-09-06 15:21:46 +03:00

mind map

A mind map editor built on kui, as a way of finding out what the library is like to build a real application with. FINDINGS.md is what that turned up.

npm install
npm start          # a window
npm run headless   # the same app, driven by hand, with assertions
npm run smoke      # the real window, driven from the app's code, closed by it
npm run preview    # writes preview.svg from a headless frame
npm run preview:check   # renders all five states and compares them to preview/*.svg
npm run typecheck

npm run repro:transition   # a library bug this app ran into, on its own
npm run repro:rename-echo  # samples the rename field under real keystrokes (macOS)

The map is stored as JSON in mindmap.json (MINDMAP_FILE overrides the path) and is written on ctrl+s.

What it does

A tree of nodes fanning left and right from a root, laid out automatically: adding, renaming, deleting, folding, recolouring, re-parenting by drag, undo and redo, pan and zoom, a context menu, and a keyboard-first flow.

key
tab / insert add a child
enter add a sibling
f2 rename
delete delete the node and its subtree
space collapse or expand
arrows move the selection
shift+up/down reorder among siblings
c cycle the branch colour
+ / - / 0 zoom in, out, fit
ctrl+z / ctrl+shift+z undo / redo
ctrl+s save
? the same list, in the app

Dragging a node onto another re-parents it; dragging the background pans.

Layout

file
src/model.ts the document and its pure operations — no kui
src/layout.ts tidy tree layout in world coordinates, and the elbow connectors
src/measure.ts where the surface is parked so layout can measure text
src/app.tsx model, messages, one update for both drivers
src/view.tsx the camera transform and the kui props
src/main.tsx opens a window
src/headless.tsx drives the app without one, and asserts
src/preview.tsx renders a frame to SVG

Positions are computed in update, not in view. That started as a constraint — layout needs measureText, which lives on the surface, and under alpha.6 view(model) was not handed one — and alpha.7 lifted it: view now takes (model, window, surface) and init takes the surface too. The arrangement stays anyway, on its own merits: layout runs once per change rather than once per frame, and the headless checks get to assert against model.layout rather than infer it from what was drawn. So the model still carries the laid-out rectangles and the view is only (world - camera) * zoom.

Nodes are absolutely positioned with float={{ anchor: 'parent', at: ['start','start'], self: ['start','start'], dx, dy, clip: true }} — a canvas needs no special mode, just that float, and clip (alpha.19) keeps a node panned past the canvas inside it. Links are one <line points curve/> each, the stroke primitive alpha.7 added; before that the drawing primitives were rectangles and a link was three thin boxes making an elbow. A <line> takes its parent's clip on its own (alpha.17).

Testing

npm run headless runs the app against a headless core and checks nearly a hundred claims — that tab adds a child, that escape abandons a rename, that a node cannot be dropped into its own subtree, that the written file parses back — and finally that the core raised no warnings. It asserts on the access tree rather than on pixels, so the checks read as what a screen reader would find.

npm run smoke opens the real window and drives it from setup — access for its input, since a window refuses press and click, and win.close() for its exit — so the shipping driver's frame order is checked with an exit code: a dispatch from outside the loop, the rename seed it carries, a reopened editor. Runs in about 300 ms and needs a display, nothing else.

npm run preview renders a frame to SVG using the real display list (decodeQuads) for the rectangles and the connector strokes, and the access tree for the labels; PREVIEW_STATE=menu|help|collapsed|edit captures the overlays. The five states are committed under preview/ as baselines: npm run preview:check renders each into a scratch directory and compares byte for byte, naming the lines that moved; npm run preview:update rewrites them from the current build. A release upgrade is a drop-in, as far as pixels go, when the check is green before a line of the app changes.