The graph board

Talk to Claude in diagrams. graph is the first unique: a dark-violet node/edge board where you draw the thing you mean while you chat, and the companion (Doto) reads, points, suggests and edits the same board.

The graph board: an API gateway node connects to an Auth service node, which looks up a Users DB and leads to a 'Token valid?' decision; a sticky note asks 'rate-limit here?'; a minimap sits bottom-left and Doto sits bottom-right.
Two nodes drawn by hand, the rest added by Claude: a data node, a decision, an annotation, a tree layout.

Install

It comes with lets: graph is listed in the engine's defaults.json, so the engine installs it into ~/.lets/uniques/graph the first time it runs. Then:

/lets graph             # or /lets:lets graph without the alias

Manual: /lets store graph (inspect → confirm), or clone the repo into ~/.lets/uniques/graph.

What you can do

Node kinds

Keys 1–5 change the kind of the selection.

KindLooks likeUse it for
ideavioletA thought, a feature
stepcyanSomething that happens
decisionmagenta, diamond-ishA question with branches
datatealA store, a document
notedim stickyA comment; its body is the text
imagethe picture, with a captionA pasted or dropped screenshot
groupdashed frameNodes inside it belong to it

Mouse & keyboard

DoHow
Add a nodeDouble-click empty space, or the toolbar
ConnectDrag from a node's dot, or select A then Shift+click B
RenameDouble-click the label
SelectClick, Shift+click, or drag a marquee on empty space
PanSpace+drag, middle-drag, or two fingers (right-click belongs to Doto)
ZoomWheel, 0.25× to 3×, centred on the cursor
Undo / redoCtrl+Z / Ctrl+Y (50 steps)
Duplicate / select allCtrl+D / Ctrl+A
DeleteDelete
Change kind1–5

Doto on the board

Right-click summons or dismisses Doto. Click him for questions about what you selected or hovered: Explain this, What's missing around …?, Suggest what comes next, Break it into steps, Critique it, Better name?. On empty space he asks about the whole board: What do you see?, Suggest a structure, Review the flow. Each one becomes a graph.ask with the node ids and labels.

Doto points and pitches in; he never nags. He can roll over to a node and say one line ("This branch never ends."), and Claude speaks at most once per thing you do. While you are drawing, he stays quiet and waits for Send, Ask or a chat message.

With 0.4.0 the board also plays a few expressions: a wink when you connect two nodes, a cheer on Send to Claude. new in 0.4.0

Send to Claude & resume new in 0.4.0

Send to Claude hands the board over: the engine saves it as a session, the window says goodbye and closes, and Claude answers in the Claude Code chat with a summary, the gaps it sees, and additions you can say yes to. Say continue (or /lets graph resume) and the board comes back as you left it, with Claude's agreed suggestions drawn in.

The manifest's session block is what lets the engine save and resume boards:

"session": { "export": "graph.export", "snapshot": "graph.snapshot", "restore": "graph.load" }

If you set "handoff": "stay" in ~/.lets/config.json, the window stays open and Claude reviews right on the board instead.

The board's data

{ rev, nodes: [{ id, label, kind, x, y, w, h, color?, text?, image?, meta? }],
       edges: [{ id, from, to, label?, kind? }],
       viewport: { x, y, zoom } }

x, y is a node's top-left in graph units. Edge kinds: dashed, dotted, bold, muted. Generated ids look like n_ab12c / e_x81qz.

Events (up)

EventWhen
graph.export {graph}You pressed Send to Claude. With 0.4.0 the engine adds session and the window closes.
graph.ask {question, selection, labels, graph}The Ask button, or one of Doto's questions.
graph.node.select {ids, labels}Selection changed (debounced 150 ms). Ambient context only.
graph.changed {rev, summary, diff}Debounced 400 ms; also after Claude's commands, so Claude learns its new node ids.
graph.node.create · update · deleteYour own node edits (never echoed for Claude's).
graph.edge.create · deleteYour own edge edits.
graph.snapshot {graph}The reply to Claude's graph.snapshot command.
media.image {path, url, …}A screenshot was pasted or dropped (sent by the host, placed as an image node).

Commands (down)

Every graph-changing command animates (300 ms) and goes on your undo stack, so you can Ctrl+Z anything Claude did.

CommandDoes
graph.node.add {label, kind?, x?, y?, text?, near?, id?}Add a node; without x/y it is auto-placed without overlap, near grows it out of that node. An existing id patches.
graph.node.set {id, patch}Change a node.
graph.node.remove {ids}Remove nodes, with their edges and annotations.
graph.edge.add {from, to, label?, kind?}Connect two nodes (same pair again = update).
graph.edge.remove {ids}Remove edges.
graph.annotate {id, text}A sticky note attached to a node ("" removes it).
graph.highlight {ids, color?, ttl?}A pulsing glow while Claude talks about something.
graph.focus {ids}Pan/zoom to fit those nodes ([] fits everything).
graph.layout {mode, root?, ids?}Tree, grid or radial auto-layout.
graph.load {graph, merge?}Build a whole diagram at once; without merge it replaces the board. Also the restore command for resume.
graph.snapshot {}Ask the board for its current state.
graph.clear {}Empty the board.
// add: omit x/y to auto-place; "near" grows it out of that node
{"type":"graph.node.add","data":{"label":"Validate input","kind":"step","near":"n_ab12c"}}
{"type":"graph.edge.add","data":{"from":"n_a","to":"n_c","label":"no","kind":"dashed"}}
{"type":"graph.annotate","data":{"id":"n_b","text":"Retries need a backoff cap."}}
{"type":"graph.highlight","data":{"ids":["n_a","e_x81qz"],"color":"#6bf0ff","ttl":4000}}

Inline cards: component.create with slot:"inline" puts a card on the canvas, beside a node with props.near. An options card next to a decision works well.

Claude's etiquette

The board's GUIDE.md is what Claude reads when it opens the board. Its promises, in short:

More: lets-unique-graph has the full GUIDE.md, the build contract (CONTRACT.md) and JSON Schemas for the graph document and messages.