21 · Block diagrams — Ensemble diagram language
Agents are the primary authors. People edit lightly. The language is line-oriented on purpose: one statement per line, ids you can point at, and a shape named in words. A later step turns that text into a JSON document, lays the blocks out, and draws them. You never have to write the JSON.
The editor stores the text. Dragging, connecting, renaming, locking, and reorganizing write back through the printer, into the same language, with positions kept in a layout section at the bottom.
Minimum window for the editor: 1024 × 640 CSS pixels. Below that, Ensemble shows a read-only preview with pan, zoom, Fit, SVG download and Back to diagrams. Editing controls, dragging nodes, connecting and renaming are disabled; the minimum is unchanged (apps/hub-web/components/diagrams/editor.tsx, canvas.tsx, nodes.tsx).
Pipeline
text → parse → model (JSON) → layout → render
- Parse. Each line is read on its own. A line that cannot be read is reported with its line number and skipped. Every other line still applies.
- Model. Version 1 document. Blocks, groups, lines, text boxes, and positions are maps keyed by stable ids (see JSON model).
- Layout. ELK layered layout, with crossing minimisation (
packages/block-diagrams/src/layout.ts). Groups are laid out as frames around their members, nested groups inside their parent, so sibling frames never cover each other or a block outside them. Line labels get room between layers; a label that would still sit on a block or a frame's title is moved into a gap by bending its line. Locked blocks keep their x,y. This runs in a web worker when the browser allows it. - Render. The Hub draws with React Flow. The same geometry (SVG paths and the 8 ports) lives in a DOM-free package, so another renderer can replace it.
parseDiagram(text) also accepts a Mermaid flowchart or graph. The editor then rewrites it as Ensemble text.
src/diagnostics.test.ts checks a 500-node input against a 250 ms CPU-time budget. Parallel-process preemption is not counted as parser work; browser/UI responsiveness is a separate measurement.
One diagram
title Login flow
direction down
node start "Start" shape circle
node form "Enter login" shape parallelogram
node check "Valid?" shape diamond
node home "Dashboard" shape rounded
node err "Show error" shape rounded color red
node db "Users DB" shape cylinder
edge start > form
edge form > check
edge check > home : "yes"
edge check > err : "no"
edge err > form
edge form -- db : "lookup"
Rules of thumb:
- Quote every label.
- Give every block an id (
start,check,db) and refer to that id from lines. - Put the shape on the block, not on the line.
- Leave positions out until someone has dragged or pressed Reorganize. The printer adds a
layoutsection.
Grammar
document = { statement }
statement = title | direction | curve | node | edge | group | text | layout | comment | blank
comment = "#" rest-of-line | "//" rest-of-line
blank = empty line
title = "title" [":"] rest-of-line
direction = ( "direction" | "dir" ) [":"] direction-name
direction-name
= "down" | "up" | "left" | "right"
| "tb" | "td" | "bt" | "lr" | "rl"
| "top-down" | "bottom-up" | "left-right" | "right-left"
curve = "curve" curve-name
curve-name = "straight" | "curved" | "elbow"
node = ( "node" | "block" ) id quoted-label { property }
| id quoted-label shape-marker
| label shape-marker
edge = [ "edge" | "connect" | "link" ] endpoint { arrow endpoint } [ ":" label ] [ "curve" curve-name ] { line-look }
| [ "edge" | "connect" | "link" ] endpoint arrow endpoint { "," endpoint } [ ":" label ] [ "curve" curve-name ] { line-look }
line-look = "width" number | "head" number | "bend" number | text-style
group = "group" id [quoted-label] ["locked"] "{" { statement } "}"
| "group" id [quoted-label] ["locked"] newline
indented statements
text = ( "text" | "textbox" ) id quoted-label ["locked"] { text-style }
layout = "layout" [ "{" ] { layout-line } [ "}" ]
layout-line= id x y [width height] ["locked" | "pinned"]
id = letter { letter | digit | "_" | "-" }
endpoint = id-or-quoted-label [ "." port ]
arrow = ">" | "->" | "=>" | "→" | "-->" | "~~>"
| "--" | ".."
| "<>" | "<->" | "<-->"
| "<" | "<-" | "<--"
property = "shape" shape | "color" color | "locked" | "pinned" | text-style
| "(" [shape-list] | "[" [shape-list]
text-style = "font" number | "bold" | "italic" | "underline"
shape-list = value { "," value } [missing ")" or "]"]
shape-marker = property that names a shape
Keywords are case-insensitive. A colon right after a keyword is optional (title: Login). Comments are whole lines only, so a label may contain #.
Ids are the map keys. Matching is case-insensitive. If a line names a label instead of an id and two blocks share that label, the first one wins and the parser warns.
Shapes
Nineteen shapes. The name on the right is what the printer writes.
| Write this | Means | Typical use |
|---|---|---|
rectangle, rect, box, square, process |
rectangle | a step, a service, a default block |
rounded, round, pill, stadium |
rounded | a screen or a softer step |
diamond, decision, choice, if, rhombus |
diamond | a question |
circle |
circle | start or end |
ellipse, oval |
ellipse | start or end when you want it wider |
cylinder, database, db, datastore, storage |
cylinder | a database or store |
server, rack |
server | a process or machine |
triangle, delta |
triangle | a merge or a marker |
parallelogram, io, input, output, data |
parallelogram | input or output |
document, doc, file |
document | a file or report |
cloud |
cloud | an external system or network |
actor, person, user, stick |
actor | a person |
hexagon, hex, prepare, preparation |
hexagon | preparation, or a firewall-style node |
note, sticky, comment |
note | a note stuck to the diagram |
trapezoid, trap, manual |
trapezoid | a manual step |
queue, mq, message-queue, topic, stream, kafka |
queue | a message queue or event stream (a cylinder on its side) |
subroutine, subprocess, predefined, function |
subroutine | a step defined elsewhere (flowchart "predefined process") |
browser, web, webapp, website, window, frontend, ui |
browser | a web app or site (a browser window) |
delay, wait, dshape |
delay | a wait (flat on the left, round on the right) |
Unknown names become a rectangle, with a warning. A close miss (dimond) becomes the nearest shape, with a warning. Names shorter than 4 letters are not guessed, so box stays an alias and red stays a color.
Colors: slate (also gray / grey), red, orange, amber (also yellow), green, teal, blue, indigo, purple (also violet), pink. A #rgb or #rrggbb value is also kept. Anything else is ignored, with a warning.
Leave color off and the shape picks its own soft fill, with a stronger border and readable text. The same names are drawn lighter on a light canvas and deeper on a dark one. color red (or any name, or a hex) replaces that default.
| Shape | Default colour | Used for |
|---|---|---|
| rectangle, rounded, parallelogram, subroutine | blue | a step or process |
| diamond, document, note | amber | a decision, or paper |
| circle, ellipse | green | a start or an end |
| cylinder, queue | teal | a database, store or stream |
| server, browser | indigo | a service, machine or web app |
| triangle, trapezoid, delay | orange | a marker, a manual step or a wait |
| cloud | purple | an outside system |
| actor | pink | a person |
| hexagon | red | a gate or a check |
Ports
Every shape has eight connection points on its outline:
n ne e se s sw w nw
Also accepted: top, right, bottom, left, topright, topleft, bottomright, bottomleft, and the compass words north, south, east, west, northeast, northwest, southeast, southwest.
title Query path
direction right
node api "API" shape server
node db "Users DB" shape cylinder
edge api.right > db.left : "query"
The printer writes the short names (api.e, db.w). If you leave the port off, the canvas picks the pair of outline points that are closest. That choice is not written back until you drag a connector from a specific point.
Rectangle points are the midpoints of the four sides and the four corners. Cylinder points sit on the top ellipse, the side of the body, and the bottom ellipse. A diamond's ne is the midpoint of the upper-right edge, not the corner of the bounding box. The other shapes follow the same rule: the point is on the stroke, not the box around it.
Lines
| You write | Meaning |
|---|---|
edge a > b |
solid arrow into b |
edge a -> b or edge a => b |
same as > |
edge a --> b |
dashed arrow |
edge a -- b |
solid line, no arrow |
edge a .. b |
dashed line, no arrow |
edge a <> b |
arrow at both ends |
edge a <--> b |
dashed, both ends |
edge a < b |
arrow into a |
edge a > b : "yes" |
label on the line |
curve curved |
every line on this diagram is a curve, unless a line says otherwise |
edge a > b curve elbow |
this line turns at right angles |
edge a > b curve straight |
this line stays straight even when the diagram default is curved |
edge a > b width 2.5 |
a thicker line, 0.5 to 8 px (default 1.5) |
edge a > b head 16 |
a longer arrowhead, 6 to 40 px (default 10) |
edge a > b bend 40 |
the middle of the line pulled 40 px to the left of a → b (negative is right); on an elbow line, the middle run moved 40 px |
edge a > b : "yes" font 12 bold |
the label's size and emphasis (see Text size and emphasis) |
A chain edge a > b > c is two lines. The label, if you add one, belongs to the last hop. A fan edge a > b, c is two lines that share the label.
curve is straight (the default), curved, or elbow. Elbow is an orthogonal path: it leaves a port, turns at a right angle, and enters the other port. Put curve once at the top of the diagram, and again on a line only when that line should differ. The printer omits a line's curve when it matches the diagram.
title Curves
direction right
curve elbow
node api "API" shape server
node db "Users DB" shape cylinder
node cache "Cache" shape cylinder
edge api > db
edge api > cache curve curved : "optional"
One line per pair of blocks. Two blocks have at most one line between them. A second line between the same two blocks (either way round) is folded into the first, with a note in Problems and a Remove this line fix: a line the other way round adds its arrowhead, so a > b and b > a become one two-way arrow, and different labels are kept, joined with /. The canvas does not draw a second line between joined blocks (it says they are already connected), and the assistant's add_edge on a joined pair changes the line that is there.
width, head, bend and the text style come after the label (when the label is quoted) and are written by the printer in that order: edge a > b : "calls" curve curved width 2 head 14 bend -30 font 12 italic. In an unquoted label, words such as bold stay part of the label.
A bend moves with the blocks: it is a distance from the straight line between the two ends, so the line keeps its curve when either block is dragged. On the canvas, select a line and drag its middle (its label, or the dot) to bend it; dragging it back near the straight line snaps it straight. A label whose line is not selected lets the pointer through to a block under it, so a label never stops a block from being dragged; click the line, or its label where no block is under it, to select it.
Text size and emphasis
Blocks, text boxes and line labels take font <px> (8 to 48) and the words bold, italic and underline, after the other properties. Without them the text is plain at its usual size: 13 px in a block, 12.5 in a text box, 11 on a line.
title Release
direction right
node build "Build" shape rectangle font 16 bold
node ship "Ship it" shape rounded color green italic
text warn "Freeze on Fridays" font 14 bold underline
edge build > ship : "green CI" font 12 bold width 2 head 14
A size outside 8–48 is kept inside it, with a note. A block that is too small for its text after a style change grows until the text fits.
Groups
Groups are labeled frames. They can nest. Members are the blocks, text boxes, and groups written inside them.
Braces:
title Checkout
direction down
group edge "Edge" {
node web "Web app" shape rounded
node mobile "Mobile app" shape rounded
}
group backend "Backend" {
group data "Data" {
node db "Orders" shape cylinder
node cache "Cache" shape cylinder
}
node api "API" shape server
}
edge web > api
edge mobile > api
edge api > db
edge api > cache
Or indentation, four spaces or a tab (a tab counts as two spaces). A less-indented line closes the open indented group. Braces close only with }.
title Indented group
direction down
group backend "Backend"
node api "API" shape server
node db "Orders" shape cylinder
node user "Customer" shape actor
edge user > api
edge api > db
Deleting a group on the canvas keeps its members and moves them up to the parent.
On the canvas a frame is drawn around its members: 16 px at the sides and bottom, 40 px on top for the title, and at least as wide as the title. Drag a group by its title or its border to move it with everything inside it, as one undo step. Click the title to select the group; Lock and Delete then act on it. The inside of a frame lets clicks through, so the blocks in it stay easy to grab (apps/hub-web/components/diagrams/canvas.tsx, nodes.tsx).
Text boxes
A text box is not a shape. It is free text you can drag. It does not take connectors.
title Cutoff
direction down
node job "Nightly job" shape server
text caption "Runs at 02:00. Pages the on-call person if it fails."
textbox is the same keyword as text. The note shape (shape note) is a block with a folded corner. Use text when you only want words.
Layout and locked blocks
Positions are not part of the story of the diagram. They live in layout, one block per line:
layout
id x y width height [locked]
x and y are the top-left of the block in CSS pixels. Reorganize rewrites this section.
Fit vertically lays the diagram top to bottom with tighter spacing and writes direction down. Fit horizontally lays it left to right and writes direction right. Both leave locked blocks where they are. Reorganize keeps the direction the diagram already has.
locked on a block, a text box, a group, or a layout line means Reorganize, Fit vertically, and Fit horizontally will not move it. A locked group also holds every member that already has a position. The first time something has no position, layout assigns one. After that, the lock keeps it.
On the canvas, a locked block, text box or group, and everything in a locked group, cannot be dragged or resized; a group that holds a locked block stays where it is. Unlock first (Lock, Alt+Shift+L). A locked group shows a lock on its title.
A dragged block lands exactly where it is dropped. Nothing else is pushed aside; group frames refit around their members. Reorganize is what separates blocks.
When a diagram opens with a saved layout whose blocks or group frames cover each other (layoutOverlaps), the editor shows Some blocks overlap. with Tidy up. Tidy up runs Reorganize as one undoable change and keeps locked items where they are. Dismissing the notice hides it until the diagram is opened again.
title Floor
direction right
node lobby "Lobby" shape rectangle locked
node desk "Desk" shape rectangle
node room "Room" shape rectangle
edge lobby > desk
edge desk > room
layout
lobby 40 48 168 64 locked
desk 280 48 168 64
room 520 48 168 64
What the parser forgives
These are warnings or skipped lines. They do not wipe the diagram.
- One statement per line. Blank lines are ignored. A line whose first non-space characters are
#or//is a comment. - A line that is not a statement is an error, named with its line number, and skipped. Blocks and lines around it still exist.
- A missing
),], or closing quote is a warning. The statement is still used. - A missing
:before a line label is a warning. The leftover words become the label. - Shape and color names ignore case, spaces, and underscores. The alias table above is exact and does not warn.
- A shape within two single-character edits of a known name (and at least 4 letters long) is a warning, and that shape is used. Otherwise the block is a rectangle and the parser warns.
- An unknown color is a warning and the block keeps the default color.
- An endpoint that was never declared creates a rectangle and warns. Prefer declaring blocks yourself so the shape is right.
- Declaring the same id twice warns. The later line wins.
- Keywords may be uppercase.
block,connect, andlinkmeannodeandedge.pinnedmeanslocked. - A
}with no group, or a layout row for an unknown id, is reported and skipped. - If the first real line is
flowchartorgraph, the whole text is read as Mermaid. Style, class, and click lines in Mermaid warn and are ignored.
title Loose reading
direction down
node start "Start" (circle
node choice "Valid?" shape dimond
edge start > choice yes
edge choice > "Dashboard"
The broken line in the middle does not remove the blocks around it:
title Still here
direction down
node start "Start" shape circle
this line is nonsense !!!
node next "Next" shape rectangle
edge start > next
Mermaid import
A flowchart copied from Mermaid is accepted as-is. After import, Save writes Ensemble text (ids, shape words, edge lines). Supported piece of Mermaid:
- Header
flowchartorgraph, withTD,TB,BT,LR,RL. - Shapes:
[rect],(rounded),([stadium]),[[subroutine]],[(cylinder)],((circle)),{diamond},{{hexagon}},[/parallelogram/],[\parallelogram\],[/trapezoid\],[\trapezoid/],>triangle]. - Arrows:
-->,---|label|,-->|label|,-- text -->,---,-.->,==>,<-->. subgraph id [Label]…end.
flowchart TD
start([Open the app]) --> check{Signed in?}
check -->|yes| home[Home]
check -->|no| login[Sign in]
login --> check
db[(Accounts)]
login --> db
Other Mermaid diagram types (sequence, class, ER) are not this importer. Describe them with the statements above. The sequence-style example below is just blocks in a row.
More examples
System architecture
title Billing system
direction right
node user "Customer" shape actor
node web "Billing site" shape rounded
node gw "Gateway" shape hexagon
group platform "Platform" {
node api "Billing API" shape server
node pay "Payments" shape rectangle
node db "Ledger" shape cylinder
node bus "Events" shape parallelogram
}
node mail "Email provider" shape cloud
edge user > web
edge web > gw
edge gw > api
edge api > pay
edge api > db
edge api > bus
edge pay > mail : "receipt"
Network
title Office network
direction right
node people "Staff" shape actor
node wifi "Wi-Fi" shape cloud
node fw "Firewall" shape hexagon
node sw "Core switch" shape rectangle
node app "App server" shape server
node db "Directory" shape cylinder
node wan "Internet" shape cloud
edge people > wifi
edge wifi > sw
edge sw > fw
edge fw > wan
edge sw > app
edge app > db
Data pipeline
title Nightly warehouse
direction right
node src "App database" shape cylinder
node extract "Extract" shape parallelogram
node clean "Clean rows" shape rectangle
node check "Quality ok?" shape diamond
node warehouse "Warehouse" shape cylinder
node report "Daily report" shape document
node alert "Page on-call" shape note
edge src > extract : "snapshot"
edge extract > clean
edge clean > check
edge check > warehouse : "yes"
edge warehouse > report
edge check > alert : "no"
Decision tree
title Refund
direction down
node ask "Refund asked" shape rectangle
node window "Inside 30 days?" shape diamond
node used "Item used?" shape diamond
node approve "Approve refund" shape rounded color green
node deny "Deny" shape rounded color red
node review "Manual review" shape note
edge ask > window
edge window > used : "yes"
edge window > deny : "no"
edge used > review : "yes"
edge used > approve : "no"
Org chart
title Support team
direction down
node lead "Team lead" shape actor
node alex "Alex" shape actor
node blair "Blair" shape actor
node chen "Chen" shape actor
edge lead > alex
edge lead > blair
edge lead > chen
Sequence-style flow
Straight lines only, so this is the order of messages, not a UML lifeline. Two blocks have one line between them, so a request and its answer share a two-way arrow (<>) with both words on it.
title Place an order
direction right
node user "Customer" shape actor
node shop "Shop" shape rounded
node pay "Payments" shape rectangle
node db "Orders" shape cylinder
edge user <> shop : "place order / confirmation"
edge shop <> pay : "charge / approved"
edge shop > db : "save"
Shapes on one diagram
title Shape key
direction right
node step "Step" shape rectangle
node screen "Screen" shape rounded
node ask "Ask" shape diamond
node begin "Begin" shape circle
node wide "Wide start" shape ellipse
node store "Store" shape cylinder
node host "Host" shape server
node mark "Mark" shape triangle
node io "Input" shape parallelogram
node file "File" shape document
node net "Network" shape cloud
node who "Person" shape actor
node prep "Prepare" shape hexagon
node sticky "Sticky" shape note
node hand "By hand" shape trapezoid
edge step > screen
edge screen > ask
edge ask > begin
edge begin > wide
edge wide > store
edge store > host
edge host > mark
edge mark > io
edge io > file
edge file > net
edge net > who
edge who > prep
edge prep > sticky
edge sticky > hand
Short form
The canonical form is node / edge. A short line is accepted when it is obviously a block (it has a shape) or obviously a line (it has an arrow).
title Short form
direction down
start "Start" (circle)
form "Enter login" (parallelogram)
start > form
JSON model
Schema version is 1. Do not author this by hand. It is the contract between the parser, the layout, the canvas, and the database. Ids are keys so a later collaboration layer can store each map as a Y.Map. The row in the database also has its own integer version that increments on each save. That number is the document revision, not this schema version.
{
"version": 1,
"meta": { "title": "Login flow", "direction": "down", "curve": "straight" },
"nodes": {
"start": { "label": "Start", "shape": "circle", "color": null, "group": null, "locked": false, "font": { "size": 16, "bold": true, "italic": false, "underline": false } },
"db": { "label": "Users DB", "shape": "cylinder", "color": null, "group": "backend", "locked": false }
},
"groups": {
"backend": { "label": "Backend", "parent": null, "locked": false }
},
"edges": {
"e1": {
"from": { "node": "start", "port": null },
"to": { "node": "db", "port": "n" },
"label": "lookup",
"line": "solid",
"arrow": { "start": "none", "end": "arrow" },
"curve": "straight",
"width": 2,
"head": 14,
"bend": -30
}
},
"texts": {
"caption": { "text": "Draft", "group": null, "locked": false }
},
"layout": {
"start": { "x": 40, "y": 40, "w": 104, "h": 104 }
}
}
font (on nodes, texts and edges), width, head and bend are optional; a document without them is plain text and default lines. direction is down, up, left, or right. curve is straight, curved, or elbow, on the diagram and on each line. A document saved before curves existed is read as straight. line is solid or dashed. color is a palette name, a hex color, or null. port is one of the eight names, or null.
direction: down flows top to bottom. right flows left to right.
Editor
The Diagrams list (app/(hub)/diagrams/page.tsx) is a grid of cards, each with the diagram's drawing (components/diagrams/diagram-thumb.tsx renders the SVG once the card nears the screen; a diagram whose blocks have no stored position yet, such as one written as text by an agent or the API, is laid out first with laidOut in layout-client.ts, as the page card and page exports do), its title and when it changed. Search filters by title; Recent or Name sorts. New diagram opens the templates (Blank first); a card's ⋯ menu downloads PNG, SVG, PDF or JPG without opening it, or deletes after a confirm on the card.
The editor (components/diagrams/editor.tsx, styles in diagrams.css) has one top bar: a Diagrams breadcrumb, the title (edit it in place), the save state, Share, Canvas only, More actions (Explain this diagram, Duplicate, Version history) and Export (PNG, SVG, PDF, JPG; Copy as image; Copy as Mermaid). On the canvas, controls float:
- The dock (bottom centre): rectangle, rounded, diamond, circle and database in one click; Add block with every shape; Text box; Lock; Reorganize with a menu for Fit vertically and Fit horizontally. On a canvas narrower than 640 px the one-click shapes hide.
- Opening a diagram whose blocks have no stored position (written as text): the canvas shows "Arranging blocks…" until layout has placed them (at most 6 seconds), then fades in fitted, instead of showing a rough stack first.
- Zoom (bottom left; centred with the dock on a narrow canvas): out, the zoom level (click for 100%), in, and fit (
ZoomPillincanvas.tsx). - The format bar (top centre) while something is selected.
- Canvas only puts the canvas in the browser's full screen with the title and save state at the top left and Export and Exit at the top right.
Every tool is a 32 px button (40 px on touch screens) with a tooltip that names it and its shortcut (components/diagrams/toolbar.tsx). Menus on the canvas are drawn inside it, so they still show in full screen; ↑ and ↓ move through them and Esc closes them.
- Reorganize runs layout again and writes the new
layoutsection. Locked blocks stay put. - Fit vertically and Fit horizontally do the same with a compact top-down or left-to-right direction, and they update the
directionline. - Lock on the selection, or
lockedin the text, is the same flag. - Add a text box inserts a
textstatement. - Add a block inserts a
nodestatement. - Resize a selected block or text box from its corners and sides. It cannot go smaller than its text needs: the label on at most three lines with its longest word unbroken, and about half the shape's default size (
minNodeSizeinshapes.ts; a circle stays round). The size is the block'slayoutwidth and height. Renaming keeps a size you chose unless the new text no longer fits. - Alignment guides. While you drag one block, its left, centre or right edge snaps to another block's (and its top, middle or bottom), within 6 screen pixels, and a red guide shows the match. While you resize, the edge you drag snaps the same way, and the width or height snaps to another block's, marked on both. Hold Alt to move or resize without snapping. Written for Ensemble (
components/diagrams/snap.ts) after how Excalidraw (MIT) and draw.io (Apache-2.0) snap; React Flow's helper-lines example is a paid Pro example and is not used. - The format bar floats over the canvas while something is selected: text size (−/+), B, I, U for blocks, text boxes and line labels (also ⌘B / ⌘I / ⌘U, Ctrl on Windows and Linux); five colours for blocks (blue, green, amber, red, purple — each with a light and a dark version) and A for the shape's own colour; line width, arrowhead size, straight / curved / elbow, dashed, Straighten and the label for lines; and align left, centre, right, top, middle, bottom, and space evenly (three or more) for several blocks. Every change is written to the text like any other canvas edit.
- Line labels. Double-click a line (or its label), or use the pen in the format bar, to type its label. Enter keeps it, Esc cancels. While typing, the label's pill is the one focus edge (accent border and a 1 px ring); the field inside draws none.
- Bend a line by dragging its middle when it is selected (see Lines).
- Lines are drawn by
edgeRoute(src/route.ts), shared by the canvas and the SVG export, so a bent or thick line and its arrowhead look the same in both. Each end follows its block live while it is dragged or resized. The stroke stops under the arrowhead, so a thick line never pokes past the tip. - Export is PNG, JPG, PDF or SVG of the whole diagram, from the same geometry, in the editor or from the download menu on each row of the Diagrams list (
components/diagrams/download-menu.tsx). Settings › Data › Download this space includes every diagram as PNG, SVG and source text. - Editing requires at least 1024 × 640 CSS pixels. Smaller windows show only a read-only pan/zoom preview and SVG download; they cannot edit the diagram.
- On a diagram, Alt+Shift+R reorganizes, Alt+Shift+L locks the selection, Alt+Shift+N adds a text box, Alt+Shift+F shows only the canvas, ⌘B / ⌘I / ⌘U style the selected text, and Delete removes the selection. ⌘/Ctrl + = (or +) and ⌘/Ctrl + − zoom the canvas in and out and ⌘/Ctrl + 0 fits the diagram, like the zoom buttons; the browser's page zoom keys do this only while the diagram is open and the cursor is not in the text. Lock needs a selection: with nothing selected its tooltip says to select blocks or text boxes first. Line labels sit above the blocks on a background, so a block does not hide them; a selected block comes above them (
components/diagrams/edge.tsx). Dragging several selected blocks moves them all and saves all of them. Esc leaves the canvas-only view.gthendopens the diagram list from anywhere.
Package: packages/block-diagrams. It has no React and no DOM. The Hub UI is only the renderer and the save buttons.
History browser
More actions › Version history (components/diagrams/diagram-more.tsx) opens the shared components/history/version-history.tsx dialog also used by pages. Entries are grouped by day and show the saved time and title; Restore asks once before it replaces the diagram. When the work a diagram was drawn from has changed, a notice under the top bar offers Refresh with AI, which asks the assistant with the current diagram as context.