Blog

Why I built another diagram tool

The editor behind diagrams.info was extracted from a desktop app I wrote for myself — including the connector routing I redesigned four times.


I did not set out to build a diagram tool. There are enough of them, most are better funded than anything I will ever ship, and “the world needs another canvas” is not a thesis.

What happened instead is that I built a desktop app for my own notes and tasks — a Tauri and SvelteKit thing called GrayTask — and one of the note types in it was a diagram. I think visually: I plan by drawing, document by drawing, and work things out by drawing. So that one view got a wildly disproportionate share of the attention, until it was the part I opened first and the rest was scaffolding around it. Eventually the obvious thing was obvious: the editor did not need the app around it.

Pulling it out

The extraction was less heroic than it sounds, and the reason is the whole argument for keeping a feature’s logic pure even when nothing forces you to.

The module was 41 production files, about 12,200 lines. Thirty-six of them had no dependency on the host app at all and moved across verbatim — the geometry, the routing, the serializer, the DBML parser, and the tests that proved the engine still worked afterwards. All the coupling sat in five files, and roughly ninety per cent of it in one class: the thing that loaded a board out of a note’s text field and wrote it back through the app’s API. It was replaced by a persistence adapter with two methods, load and save, backed today by localStorage and one day by a server.

A small joke about all this: the feature was called “Diagram view”, lived inside Notes, and had none of its code in the notes module — it was all in a folder called whiteboard. The repo behind this site is still called WhiteBoard and the folder on my machine is diagram-app. I have made peace with it.

What I lost on the way out, and why I am not sorry

Five features did not survive the move. All five were the host’s, not the editor’s:

  • Real-time collaboration. Boards synced live, free, because they rode the notes socket channel. Standalone, that is a sync transport I have to build and run.
  • Live database import. A button on a table element opened a picker of real tables from a real database and pulled their columns in with keys detected. It needed a connection service behind it.
  • Element-to-note links and element tasks, meaningless without notes and tasks.
  • A focus mode that hid application chrome. There is none to hide when the canvas is the page.

It is a decent illustration of how a feature hides its cost. Each was cheap in GrayTask because something else was already paying for it, and none of them were cheap once that something else was gone.

The connectors, which I redesigned four times

If there is one part of this I would defend in a code review it is the connection routing, and it took four full attempts to get there.

  1. Sides derived from centre to centre, and the anchor slid along the side. The lines wandered around on the edges of the boxes as you moved things. It looked alive in the bad way.
  2. Fix the sides by hand instead. The opposite complaint: a line was welded to one side and stayed there while you dragged its box halfway across the canvas.
  3. Automatic facing sides, still measured centre to centre. Better, still wrong: it decided where a line should attach without reference to where the line actually went.
  4. The current model: a draggable via point drives the sides. Every connection has a middle handle, and an unpinned endpoint picks its side from where that via is — so the anchor faces the direction the line genuinely leaves in. Drag the via past a box and that box’s anchor flips. Drag an endpoint and you pin it, the escape hatch for cases the automatic answer gets wrong.

Two smaller decisions inside that took longer than the redesign itself.

The via is stored in absolute canvas coordinates, not as an offset from the midpoint. As an offset, a bend you had deliberately placed drifted by half whenever either box moved — maddening, and hard to describe as a bug. Absolute coordinates keep it where you put it. The cost is that everything which moves elements now has to know: pasting shifts the via by the paste offset, and a multi-element drag translates it, but only for connections with both ends inside the selection.

Which side an endpoint picks is decided against the box’s edges, not the angle from its centre. A centre-angle test flips earlier the wider the horizontal gap gets, because the gap dominates the angle, and that reads as the anchor hopping hundreds of pixels too early. Against the edges, the flip happens when the box reaches the via’s level, whatever the gap.

I also threw one thing away. To detect a bend gone stale — the element moved to the far side, so the line loops back over its own box — I first tried asking “does this endpoint face away from the other one?” It false-positives on a perfectly legitimate U-shape where both ends leave downward. It is now a real geometric test for whether the route passes through a box.

Nobody will ever notice any of this, which is more or less the point. Connectors are only visible when they are wrong.

So why ship it

Not because I can beat draw.io. I can’t, and most people shouldn’t switch. The honest pitch is narrower:

  • It opens on a canvas. No account, no template chooser, no file dialog, no onboarding tour.
  • Nothing is uploaded. The board lives in your browser’s storage; the only network call is analytics. Plenty of developers simply cannot paste a production schema into a hosted tool, and that is a real constraint rather than a privacy slogan.
  • Paste DBML, get an ER diagram, and get DBML back out. The one thing here I would actually put in front of someone — with the parser’s limits written down rather than discovered.

What it can’t do

It is early alpha, and the gaps are structural rather than cosmetic:

  • No accounts, so no sharing link and nothing follows you to another device. Your board lives in the browser you drew it in; clear your site data and it is gone, which makes the export buttons a backup rather than a nicety.
  • No real-time collaboration, per the list above.
  • Desktop only. There is not a single touch handler in the editor. On a phone you can tap a shape into place and then drag precisely nothing.

The backend that fixes the first two is specified and deliberately deferred. I would rather find out whether people draw anything at all before I build somewhere to keep it.

If you try it and something is broken, I would like to know. It is a tool I built because I wanted it to exist, and that is still the only reason it does.