reladraw 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1602 @@
1
+ # Syntax reference — 0.22.0
2
+
3
+ What the language accepts. The changelog at the end lists what changed in each published version; 0.17.0's changes are large, and its entry has a table of every old spelling and what to write instead.
4
+
5
+ One idea runs through all of it: **the file says where things go, and the tool draws what it says.** It never chooses an arrangement, never moves a box to make room for something, and never finds its own way round anything. Where the result may not read as meant (two boxes on one spot, a line through a box, a text on a text), it draws it as written and prints a *finding* saying where and by how much. The fix is always something you write.
6
+
7
+ The worked example is `examples/arch.reladraw`, transcribed from the hand-drawn reference beside it. Every file in `examples/` renders without findings.
8
+
9
+ ## The file
10
+
11
+ ### Statements and lines
12
+
13
+ A file is a list of statements. Each starts at the beginning of a line, and nothing nests. A long statement may continue onto the lines after it by indenting them:
14
+
15
+ ```
16
+ node desktop1 "Desktop 1 / Linux" (inside top, align: left)
17
+ badge: desktop
18
+ node desktop1.files "shared folder"
19
+ ```
20
+
21
+ A line that starts with whitespace continues the statement above it, and a blank line or the next unindented line ends it. How much whitespace makes no difference, so nothing can depend on indentation you can't see. A bracket may stay open across lines. Indentation only lets a statement wrap; it never puts one thing inside another.
22
+
23
+ An indented line with nothing to continue, at the top of the file or after a blank line, is an error, and so is an indented line that starts with a statement keyword such as `node`. Both are almost always a stray indent, and both say to remove it.
24
+
25
+ `//` starts a comment, which runs to the end of the line and may trail a statement. An indented comment line sits inside the statement it follows; an unindented one ends it. `#` is not a comment, because it opens a hex color.
26
+
27
+ ### The statements
28
+
29
+ | keyword | makes |
30
+ |---|---|
31
+ | `node` | a box, a picture, or a bare text ([Nodes](#nodes)) |
32
+ | `edge` | a line between two things ([Lines](#lines)) |
33
+ | `point` | a named point ([Points and regions](#points-and-regions)) |
34
+ | `region` | a named rectangle ([Points and regions](#points-and-regions)) |
35
+ | `bundle` | a path several lines share ([Bundles](#bundles)) |
36
+ | `guide` | a first word that makes a `node`, `edge`, `point` or `region` reference-only ([Guides](#guides)) |
37
+ | `style` | a named bundle of appearance ([Styles](#styles)) |
38
+ | `default` | appearance every thing of one kind wears ([Defaults](#defaults)) |
39
+ | `diagram` | settings for the whole drawing ([The diagram](#the-diagram)) |
40
+ | `icon` | a picture of your own ([Your own icons](#your-own-icons)) |
41
+
42
+ `box`, `link`, `note`, `deck`, `rect` and `arrow` at the start of a statement are refused by name, each quoting what to write instead.
43
+
44
+ ### Heads, keys and phrases
45
+
46
+ A statement is a positional head (the keyword, a name and a text) followed by attributes and placements in any order:
47
+
48
+ ```
49
+ node server "Home Server" (inside top) left of cluster badge: desktop
50
+ // ^ ^ ^ ^ ^
51
+ // name text its bracket a placement an attribute
52
+ ```
53
+
54
+ What every statement of a kind has is positional; what is optional is a key. A word ending in a colon opens an attribute and nothing else does, so the two never need telling apart by position. A key may touch its value: `gap:tight` is `gap: tight`. Relations stay phrases, `right of api` or `between c and d`, because a phrase is the sentence a person would say and a key like `right-of: a` falls apart with several targets, a gap or a part.
55
+
56
+ **A value ends when its own shape is complete.** `height: 0.5 group height right of a` reads a size, then a placement. `to: top, right of z` keeps `right of z` inside `to:`, because it is part of that value. So word order stays free and nothing is ambiguous.
57
+
58
+ **A bracket modifies the one thing it follows.** A setting that belongs to one part of a statement is written in brackets right after that part: a placement's `(gap: wide)`, a text's `(size: small, wrap: 30)`, a line's `line: (path: square)`, a layout's `layout: column (gap: none)`, a waypoint's `b top (gap: wide)`. The scope is visible instead of inferred. Inside a bracket the comma is optional, and spaces around it don't matter: `(corners: rounded,path: square)` and `(corners: rounded , path: square)` are both `(corners: rounded, path: square)`.
59
+
60
+ ### Names and words
61
+
62
+ A name is unique across nodes, points, regions, lines and bundles. It may not contain `:`, which would make it a key, or `;`, which is reserved. A dot means a child: `server.docker` is inside `server`, and the parent must be declared first. Moving a node to another parent is a rename.
63
+
64
+ Every word the language owns that has more than one part is hyphenated (`from-mark:`, `bottom-left`, `min-height:`, `align-children:`), while a phrase of separate words stays spaced (`left of`, `even with`). A name you choose should look chosen: the examples use underscores (`some_region`). `to_mark:` is refused by name, pointing to `to-mark:`.
65
+
66
+ **One spelling for each thing.** An alternative spelling is refused by name, pointing to the one the language uses: `below of a` names `below a`, and `left a` names `left of a`.
67
+
68
+ ### Texts
69
+
70
+ A text is quoted. ` / `, a slash with whitespace on both sides, breaks the line, so `TCP/IP`, `16/9` and URLs survive as written. The escapes are `\"`, `\\`, `\/` (a spaced slash that doesn't break) and `\[` (a literal bracket, since `[` opens markup).
71
+
72
+ A node with no text shows its name, and a dotted name shows only its last part: `node server.docker` reads "docker". The name is never prettified. `""` says a node deliberately has none. A node drawn as a picture shows no text unless one is written.
73
+
74
+ A `"""` string takes everything between its marks as written, quotes and line breaks included. It is accepted only for an icon's SVG.
75
+
76
+ ### Numbers and the file's unit
77
+
78
+ A distance (a gap, a size, a padding, `2 right of a`) is measured in the file's unit, which is small: an ordinary one-line box is about 4 units tall. The gap words are whole numbers of it:
79
+
80
+ | word | units |
81
+ |---|---|
82
+ | `none` | 0 |
83
+ | `tight` | 2 |
84
+ | `normal` | 4 (the default) |
85
+ | `wide` | 8 |
86
+
87
+ A distance below the words is a number, `1 right of a` or `0.5 right of a`, and a number may start at its decimal point, `.5`. A unit is 12 pixels at the normal text size.
88
+
89
+ An appearance number is a multiple of its own normal: `(size: 1.2)` is text 20% bigger, `thickness: 2` a line twice the normal width. So a bare number has two scales, and `thickness: 1` is not `gap: 1`. The one exception is a region's `opacity:`, an amount from `0` (none) to `1.0` (solid), not a multiple of anything.
90
+
91
+ A suffix `px` fixes any of these in pixels, so it doesn't follow the file: `(size: 16px)`, `thickness: 3px`, `(gap: 12px)`. A bare number never means pixels.
92
+
93
+ A fraction is written `1/3` or as a decimal, `0.25`, and is measured from the first-named thing. **No distance is negative**: `-4 right of a` is refused, pointing to `4 left of a`, or to `inside` or `on` for a box meant to overlap.
94
+
95
+ Named values are preferred where they exist: change what `tight` means and every tight gap follows.
96
+
97
+ ## Nodes
98
+
99
+ ```
100
+ node <name> ["<text>" [(<text settings>)]] [<placement> | <attribute>] ...
101
+ ```
102
+
103
+ ### The body
104
+
105
+ Every node has one body, the thing drawn where it is, named by one of two keys.
106
+
107
+ `shape:` is the outline. A shape geometry has a word for is named by it, and takes settings in a bracket after its name for what its box doesn't decide: an amount, in the file's unit, and a direction, whose first word below is the default.
108
+
109
+ | shape | draws | its bracket |
110
+ |---|---|---|
111
+ | `rectangle` | the plain box, its corners rounded unless `corners: sharp`; the default | |
112
+ | `document` | the box with its top-right corner folded, for an artifact rather than a process; its other corners follow `corners:` | `fold:` |
113
+ | `circle` | a circle sized to hold its text; given a `width:` and a `height:` that differ, an ellipse | |
114
+ | `diamond` | a diamond, a flowchart's decision | |
115
+ | `pill` | a box with both ends round, a flowchart's start or end | |
116
+ | `parallelogram` | a slanted box, a flowchart's input or output | `slant:`, `lean: right \| left` |
117
+ | `hexagon` | a box with pointed ends, a flowchart's preparation | `point:` |
118
+ | `trapezoid` | a box narrower at one end, a flowchart's manual operation | `taper:`, `narrow: bottom \| top` |
119
+ | `triangle` | a triangle, a flowchart's merge (down) or extract (up) | `aim: down \| up \| left \| right` |
120
+ | `cylinder` | a cylinder standing, or `lying` on its side, for stored data | `cap:`, `lying` |
121
+ | `none` | no outline, fill or padding: bare text, or an invisible container | |
122
+
123
+ ```
124
+ node in "read input" shape: parallelogram (slant: 2, lean: left)
125
+ node queue "queue" shape: cylinder (cap: 1.5, lying) right of in
126
+ node flat "valid?" shape: diamond width: 20 height: 5 below in
127
+ ```
128
+
129
+ A flowchart shape geometry has no word for is named `flow-` and what it means, and drawn the way the flowchart standard draws it, with no settings:
130
+
131
+ | shape | draws |
132
+ |---|---|
133
+ | `flow-subprocess` | a step drawn as its own chart: a box with a bar inside each side |
134
+ | `flow-document` | a document as the standard draws it, its bottom wavy |
135
+ | `flow-off-page` | continued on another page: a box pointed at the bottom |
136
+ | `flow-delay` | a delay or a wait: a box with its right end round |
137
+ | `flow-manual-input` | typed in by hand: a box whose top slopes |
138
+ | `flow-stored-data` | the standard's other storage: curved ends, the right one hollow |
139
+ | `flow-loop-start`, `flow-loop-end` | the start and end of a loop: a box with two corners cut, at the top or the bottom |
140
+ | `flow-card` | a punched card: a box with its top-left corner cut |
141
+ | `flow-display` | a screen: pointed on the left, round on the right |
142
+ | `flow-tape` | magnetic tape: a circle with a tail |
143
+ | `flow-punched-tape` | paper tape: wavy top and bottom |
144
+ | `flow-collate` | collate: two triangles point to point, its text on a patch where they meet |
145
+
146
+ `icon:` is a picture the node is drawn *as*, with no box:
147
+
148
+ | icon | means |
149
+ |---|---|
150
+ | `disk` | a physical drive |
151
+ | `desktop` | a workstation |
152
+ | `laptop` | a portable machine |
153
+ | `package` | something stored as a whole: an archive, a bucket, a sync root |
154
+ | `cubes` | several interchangeable units; always three, since it is the symbol for "several", not a count |
155
+ | `cube` | one of them |
156
+ | `database` | a store queried rather than read as files |
157
+
158
+ ```
159
+ node dump "db dump / (app 1)" shape: document
160
+ node aside "Written by a nightly cron job." (wrap: 30) shape: none below dump (gap: tight)
161
+ node svc icon: cube right of dump
162
+ ```
163
+
164
+ Writing both keys is an error naming both. An unknown name is refused with the whole set, and so is a setting the shape doesn't take. A shape is named for how it looks, `diamond` and never `decision`, and no picture has two names. An icon is named for what it is: `package`, not `open-box`.
165
+
166
+ **Size and text.** A shape grows until its text, with its padding, fits at the shape's middle, the center of its area: a diamond is twice the padded text each way, and a triangle's text sits a third of the way from its base. A stated `width:` and `height:` stretch the shape to fill them, which is how a diamond is made flatter; a text that no longer fits is drawn as it is and reported as `text-overflow`. The text-position words of a [node's text](#where-it-sits), such as `(inside bottom)`, move it off the middle. `flow-collate`'s text sits on a patch of its own fill where its two triangles meet; `(fill: none)` in the text's bracket draws it without one.
167
+
168
+ **Sides.** A side of a shape is the stretch of its outline between the lines from its middle to two corners of its box: a rectangle's sides, a circle's quarters, and on a diamond the stretch round each point. So `from: right` leaves a diamond from its right point, as a flowchart's "no" does, and means the same on every shape. A corner such as `top-right` is where the line from the middle to that corner of the box meets the outline: on a circle halfway between its sides, on a diamond the middle of an edge. Several line ends on one side spread along it.
169
+
170
+ A picture can't contain anything. A `shape: none` node with children is an invisible container ([Containers](#containers)). A circle can hold other nodes, centered in it; the other shapes don't hold nodes yet, and one with children is refused.
171
+
172
+ **Corners.** `corners: sharp` draws a box's corners square, and `corners: rounded`, the default, draws them as they always were. It is the same word and values as a line's `corners:`. It reaches every outline with corners that round: a rectangle, a container, a `flow-subprocess`, and a document's three round corners, whose fold stays as it is. Every other shape is drawn with its points sharp, so `corners:` written on one is refused; from a style or a default it lands only on the nodes it can. A picture and bare text have no outline, so they refuse it too. Like a color, it never moves anything.
173
+
174
+ ```
175
+ style plain corners: sharp
176
+ node api "API" style: plain
177
+ node spec "spec" shape: document corners: sharp right of api
178
+ ```
179
+
180
+ **Bare text.** `shape: none` is how to write a note, a caption or a title over a diagram. Its text starts one step smaller than a box's, because an aside at full size reads as a statement; `(size: normal)` overrides that. Nothing bounds bare text the way a border bounds a box, so give a sentence-length one a `wrap:`.
181
+
182
+ ### Your own icons
183
+
184
+ `icon <name>` declares a picture, and from then on `icon: <name>` and `badge: <name>` use it as they would a built-in one. The picture is SVG, pasted between `"""` marks or read from a file:
185
+
186
+ ```
187
+ icon crate """
188
+ <svg viewBox="0 0 24 24">
189
+ <path d="M3 7 L12 3 L21 7 L21 17 L12 21 L3 17 Z" fill="none" stroke="currentColor"/>
190
+ </svg>
191
+ """
192
+
193
+ node store "Warehouse" badge: crate
194
+ node more icon: crate right of store
195
+ ```
196
+
197
+ - **It keeps its own colors** on every theme. What it leaves to `currentColor` takes the theme's icon color, which is how a one-color icon follows the theme.
198
+ - **It is fitted to the icon's square** by its `viewBox`, or with none by its `width` and `height`; with neither it is refused, since nothing says how big it is.
199
+ - **Its ids and class names are renamed**, so two icons that both call a gradient `a` don't collide.
200
+ - **Script is refused**: a `<script>` element or an `on…` attribute.
201
+
202
+ A file (`icon rack "./rack.svg"`) is found relative to the diagram's own file. Only the command line can read one; in the playground or a web page, paste the SVG. A declared name replaces a built-in of the same name, and declaring one name twice is an error. Every picture is drawn into the output as path data, so the SVG stays standalone.
203
+
204
+ ### Badges
205
+
206
+ `badge: <icon>` makes a picture part of the node's label: the whole text, however many lines, with the picture beside it, placed as one unit.
207
+
208
+ ```
209
+ node drive "External HD" badge: disk
210
+ ```
211
+
212
+ It is shorthand for a child joined to the text ([Joined to the text](#joined-to-the-text)), and this is what it stands for:
213
+
214
+ ```
215
+ node drive "External HD"
216
+ node drive.badge icon: disk 1 right of drive text join-with: drive text
217
+ ```
218
+
219
+ The text keeps its own position words, so `node d1 "Desktop 1 / Linux" (inside top, align: left) badge: desktop` puts the label, picture and all, in the top-left corner. On a box with nothing else in it the label is centered; on a box with children it takes the title's place and the children go under it. The picture is two lines of text tall and can be named like any child (`below drive.badge`, `edge a -> drive.badge`). Declaring `drive.badge` yourself beside a `badge:` is an error naming both ways out. Only a box can wear one.
220
+
221
+ With an empty text, `node flag "" badge: disk`, the node is exactly the picture and its padding, since an empty text takes no room.
222
+
223
+ **A picture that is a thing of its own is a child**, placed like any other: `node drive.mark icon: disk 1 right of drive text` beside the text. Placed against the text without `join-with:`, it is not part of the label: the text stays where it would be without it, and the box grows to hold both. In the corner of a box its other children size, `node laptop.mark icon: laptop inside laptop top-right stays above laptop.photos` ([Against its own parent](#against-its-own-parent)). A corner of a box with nothing in it but its text is on the text.
224
+
225
+ ### Padding
226
+
227
+ A stack of cards, which `deck:` used to draw, is a container: [`layout: deck`](#a-stack-of-cards-layout-deck).
228
+
229
+ `padding:` is the room between a box's edge and what it holds, in the file's unit: `padding: 2`. On a region it is measured outward instead ([Points and regions](#points-and-regions)). One side's padding is `padding-top:`, `padding-right:`, `padding-bottom:` or `padding-left:`, over `padding:` on that side only: `padding: 2 padding-right: 4` is 2 all round and 4 on the right. A container's title keeps `padding:` from the edge, so a side's padding widens the room round the children without moving the title out of its corner. A circle has no sides, so a side's padding on one is refused.
230
+
231
+ ## Placing a box
232
+
233
+ A *placement* says one thing about where a node goes, and a node carries as many as it needs. Nothing else places a box: no line, no text, no neighbor.
234
+
235
+ ### Directions and gaps
236
+
237
+ ```
238
+ above X below X left of X right of X
239
+ above-left of X above-right of X below-left of X below-right of X
240
+ ```
241
+
242
+ **A gap is exactly what was said.** `right of a` puts the box a `normal` gap from `a`'s right side. The gap goes in the placement's bracket, `right of a (gap: tight)`, or as a distance in front, `2 right of a`; writing both is refused as said twice. A diagonal distance is per axis: `2 below-right of a` is 2 across and 2 down.
243
+
244
+ **A lone direction also centers on its target.** `right of a` alone puts the box level with `a`'s center, because walking right from something keeps you on its center line. That half gives way as soon as another placement says how high, whichever box it is written on: with `node b right of a` and `node a even with b.1`, B goes where B.1 is level with A, rather than centered on it.
245
+
246
+ ```
247
+ node parser
248
+ node resolver right of parser
249
+ node renderer right of resolver (gap: wide)
250
+ node notes "notes" below resolver (gap: tight) shape: none
251
+ ```
252
+
253
+ **A gap belongs to the relationship, not the node.** One box may want different distances on different sides, so each placement carries its own. `gap:` on a node or in a style is refused, quoting the bracket form.
254
+
255
+ `right of server.docker` places a top-level node against something nested: containers scope names, not placement.
256
+
257
+ ### Several targets at once
258
+
259
+ A placement may name several targets, joined by `and` or commas: `right of a, b and c`. It places against the box bounding them all, a region nobody had to declare. A lone direction centers on that region, which is how one row is offset against another:
260
+
261
+ ```
262
+ node a icon: cube
263
+ node b right of a (gap: tight) icon: cube
264
+ node c right of b (gap: tight) icon: cube
265
+ node d below a and b (gap: tight) icon: cube
266
+ node e below b and c (gap: tight) icon: cube
267
+ ```
268
+
269
+ A comma promises another target, so `right of a, gap: tight` is refused, naming the comma.
270
+
271
+ ### Lining up: `even with`
272
+
273
+ `even with a` shares `a`'s horizontal center line and says nothing across. A side word in front lines that side up instead: `top even with a` puts this box's top on `a`'s top. A part after the target lines up with that part: `top even with a bottom`, `top even with a center`. A side on the other axis, `top even with a left`, names no one height and is refused.
274
+
275
+ To name the axis rather than a side, put `x` or `y` in front. `x even with a` shares `a`'s vertical center line, so this box's middle across is `a`'s middle across, and says nothing about height. `y even with a` says what the bare `even with a` says: the same height.
276
+
277
+ ```
278
+ node a
279
+ node b right of a top even with a
280
+ node c below a (gap: wide)
281
+ node d right of c even with c
282
+ ```
283
+
284
+ A fraction of the way across a box is written fraction first: `top even with 3/4 down a` puts this box's top three quarters of the way down `a` (`1/4 up a` says the same). Across, `left even with 1/3 across a`, counted from the left unless `from right` follows.
285
+
286
+ `even with a and b` lines up with the region round both. An alignment takes no gap, since sharing a line leaves no distance, and `(gap: …)` on one is refused.
287
+
288
+ ### Part against part
289
+
290
+ `center 12 right of a center` puts the two centers exactly 12 apart, the even steps of a timeline whose boxes differ in width. `top 3 below a bottom` is the same for edges. The direction means what it always means, so with nothing else said the box also sits level with `a`'s center. A placement naming parts must give its distance: `center right of a center` is refused ("say how far").
291
+
292
+ Two distances, one per axis, are accepted in any placement: `3 below and 2 right of a`, or `top-left 3 below and 2 right of a bottom-right`.
293
+
294
+ **How far along the side it faces.** `right of server 3/4 down` puts the box the usual gap right of `server`, its middle level with a spot three quarters of the way down `server`'s right side. `down` and `up` run along a left or right side, `across` along a top or bottom one. A word that doesn't run along the side the direction faces is refused, listing the words that do.
295
+
296
+ ### Each axis is stated once
297
+
298
+ A box's position across and its position up and down each come from exactly one statement. Pinning one edge positions the box; pinning two opposite edges, `top even with a bottom even with c`, positions and sizes it, and counts as one statement. Refused by name:
299
+
300
+ - **Said twice.** Two statements on one axis, even where they happen to agree, so a file is never right by coincidence. A bare direction beside a pin doesn't count; see below.
301
+ - **Inside out.** Two pinned edges that would give a negative size.
302
+ - **Circular.** A box measured from something measured from it.
303
+ - **An axis left open.** `right of a left of b` with nothing up and down: centering on one or the other would decide which row the box shares, so the error names the axis.
304
+
305
+ **A direction beside a pin on its own axis gives up its distance and keeps its side.** `below trx` says two things: underneath `trx`, lined up with it across, and one gap down. A pin says how far down instead:
306
+
307
+ ```
308
+ node host.bin below host.trx top even with cat0 bottom even with nice
309
+ ```
310
+
311
+ `bin` is under `trx` across, and runs from `cat0`'s top to `nice`'s bottom. The side still has to hold: if the pins put `bin` beside or above `trx`, that is refused. A distance written on the direction, `below host.trx (gap: wide)` or `2 below host.trx`, beside a pin is refused too, since the pin would overrule it.
312
+
313
+ **Opposite placements on one axis center the box between them**, at its own size: `node c right of a left of b even with a` sits in the middle of the space `a` and `b` already leave. It doesn't move `a` or `b`. If the space is too narrow, `c` overlaps them and that is reported; to make room, state how far apart `a` and `b` are.
314
+
315
+ A target need not be written first, but there must be an order in which each box can be worked out from boxes already worked out.
316
+
317
+ ### One anchor
318
+
319
+ Exactly one box in the file places itself, and every other is placed relative to it, directly or through others. Two unplaced boxes are refused by name. The anchor may be split between two boxes, one saying nothing across and the other nothing up and down: with `node a "A" even with b.1` and `node b "B" right of a`, A says nothing across, and B, once its centering on A gives way, nothing up and down. Chaining, `b right of a` then `c right of b`, is the plain way to write a few in a row; `layout:` ([Containers](#containers)) is for "these, in a line, in written order". Place against neighbors rather than hanging many boxes off one small one: a gap from a small box says nothing about the large ones also hanging off it.
320
+
321
+ ### A box goes where it is placed
322
+
323
+ Nothing moves a box but its own statements. A line's text doesn't widen a gap, an arrowhead doesn't, and a line ending on a side too short for it doesn't grow the box (that is reported as `crowded-edge-ends`). **Nothing pushes overlapping boxes apart**: two boxes whose placements put them on one spot are drawn there and reported as `node-overlap`. `overlap: allow` on a node says the overlap is meant.
324
+
325
+ Identical placements don't make a list. `node b right of a` and `node c right of a` put `b` and `c` on one spot, drawn and reported. A list is a container with a layout.
326
+
327
+ ### Against a part of a node
328
+
329
+ A placement's target may be a part of a node, with one of three words, each taking one part of one thing:
330
+
331
+ | | |
332
+ |---|---|
333
+ | `inside hub top-right` | wholly within, against that part |
334
+ | `outside hub right` | wholly beyond it |
335
+ | `on hub bottom-left` | centered on it, straddling it; takes no gap |
336
+
337
+ ```
338
+ node bob "Bob the builder" (inside top) height: 7
339
+ node bob_link "bob.example.com" (size: small) inside bob bottom-center shape: none
340
+ node mark "!" on bob top-right shape: circle
341
+ ```
342
+
343
+ *Inside* is the direction from the part toward the box's center, so `inside right` is `left of` that side and `inside top-right` is `below-left of` that corner. `inside hub center` and `inside hub text` are refused, having no inward direction. The inset for `inside` is a gap on the placement: `tight` against another node, the parent's padding against one's own parent. Saying `inside` or `on` states the overlap, so it isn't reported.
344
+
345
+ A direction and a part stay two vocabularies: `above-left of hub` is outside it, `inside hub top-left` is a point of it, and `above hub` could never mean "the top of hub". For several boxes, name the region round them and use its part.
346
+
347
+ ## Points and regions
348
+
349
+ Anywhere the language wants a position, it takes the same points: a box placed at one, a line passing through one, a line ending at one, a text placed by one.
350
+
351
+ ### The parts of a box
352
+
353
+ A box's parts are its `text`, its four sides `top`, `bottom`, `left` and `right`, and its nine points:
354
+
355
+ ```
356
+ top-left top-center top-right
357
+ left-center center right-center
358
+ bottom-left bottom-center bottom-right
359
+ ```
360
+
361
+ **A side is a segment and a point is a point.** Bare `top` is the whole side; `top-center` is the one point halfway along it, which is why every midpoint carries `-center`. Naming a side leaves the other axis free. On any shape but a box, a point is where the line from the shape's middle toward that point of its box meets the outline ([Sides](#the-body)): on a circle a corner is halfway between its two sides.
362
+
363
+ A part is written as the name, then the part: `b top`, `e bottom-left`, `hub text`.
364
+
365
+ ### Points
366
+
367
+ - **Between two things.** `between a and b` is the center of the space between them: halfway between their facing sides, not their centers. `between a and b at 1/3` is a third of the way across, from `a`. It always joins exactly two things with `and`; `between a, b` is refused.
368
+ - **From two things.** `down from a and left from b` is where a line straight down from `a`'s center meets a line straight left from `b`'s. A line may start from a part (`down from a left`) or a point (`left from 1 above b top-left`); from a whole side, such as `up from c top`, it starts at the side's middle. The direction words also say which way the point lies from each box, and that is checked: lines that don't meet are refused. Either half may be `even with a` instead, the level line through `a`: `up from c and even with tall top` is straight above `c`, level with `tall`'s top. It says no way the point lies, so only the other half is checked.
369
+ - **Moved by a distance.** `2 below c bottom` is 2 units below `c`'s bottom. What is moved is a point or a side, so `2 below c` is refused, asking which. A moved side is still a side: `1 above tall top` works wherever `tall top` does. A named point moves the same way: `2 above over_c`. `c bottom + 2` is refused too, since `+` adds lengths and doesn't say up or down.
370
+ - **On a line.** `req start`, `req end`, `req at 1/3` ([Points on a line](#points-on-a-line)).
371
+
372
+ A box is placed at a point with `on`, and against one with a direction:
373
+
374
+ ```
375
+ node a
376
+ node b right of a (gap: wide) below a
377
+ node c on down from a and left from b shape: circle
378
+ ```
379
+
380
+ `c` sits under `a` and level with `b`.
381
+
382
+ ### Named points and regions
383
+
384
+ ```
385
+ point funnel between c2 and api at 2/3
386
+ region trust web top-left to db bottom-right
387
+ region slot between left_rack and right_rack
388
+ ```
389
+
390
+ A named point can be used by several things (`via: funnel`, `below funnel`), and moving it is one edit. A named point must come out as one point, so `point p a bottom` (a whole side) is refused.
391
+
392
+ A region is the rectangle spanned by two points, in either order. `region r between a and b` is the space between the two boxes: on an axis where they are apart, the gap; where they overlap, only the stretch they share. `(span: both)` stretches it across both boxes on the other axis. Once named, a region has every part a box has, so `via: r center`, `right of r` and `inside r top-left` need nothing new. `padding:` pushes its edges out.
393
+
394
+ A region bounds things without owning them: they keep their own parent, aren't moved by it, and may sit in several regions, and a region may span containers. That is what separates it from a container. A point is drawn as a dot and a region as a light tint with a quiet border, unless written as a [guide](#guides). A region's fill, the tint or one it states with `fill:`, is drawn at half opacity, so overlapping regions show both colors and a region never hides what it covers; `opacity: 1.0` makes it solid, and `opacity:` takes any amount from `0` to `1.0`.
395
+
396
+ ```
397
+ node web "Web"
398
+ node api "API" right of web
399
+ node db "DB" below api
400
+ region trust web top-left to db bottom-right padding: 1
401
+ ```
402
+
403
+ ### A point's and a region's text
404
+
405
+ A point or a region may have a text, quoted after its name, with a bracket like any text's.
406
+
407
+ **A point's text sits above its dot** unless its bracket says which way: `(below)`, `(left)`, `(right)`, or a corner, `(above-right)`, `(above-left)`, `(below-right)`, `(below-left)`. There is no target, since a point's text has nothing else to sit beside. A side centers the text on the dot; a corner puts the text's nearest corner off the dot diagonally. `gap:` is the distance from the dot, half a unit unless it says.
408
+
409
+ ```
410
+ node a "A"
411
+ node b "B" right of a (gap: 14)
412
+ point mid "halfway" (above-right, gap: 1) between a and b
413
+ ```
414
+
415
+ **A region's text takes a container title's words**: `inside`, `on` or `outside` a side, `align:` along it, and `gap:` for its distance from the region. With nothing said it sits above the region, outside it, centered. Inside, the region makes room for it, as a container does for its title: it grows a band on that side to hold the text, and widens if the text is wider. Nothing it spans moves, but its own edges do, so whatever is placed against that side of the region moves with them. `(center)` is refused, since a region holds nothing and its middle is whatever it covers.
416
+
417
+ ```
418
+ node web "Web"
419
+ node api "API" right of web
420
+ node db "DB" below api
421
+ region trust "trust boundary" (inside top, align: left) web top-left to db bottom-right padding: 1
422
+ ```
423
+
424
+ Both start one step smaller than a box's text, as bare text does, and take `color:`, `size:`, `wrap:`, `align:` and `reads: up` or `down`.
425
+
426
+ ## Containers
427
+
428
+ A node is a container when it has children, named with a dot. **A container is the bounding box of its children, plus padding.** The children are arranged in one of two ways, never by inference.
429
+
430
+ ### By hand
431
+
432
+ With no `layout:`, a container follows the file's own rule: exactly one child is unplaced, the anchor, and every other child is placed relative to it or to other children. A second unplaced child is refused, naming `layout:` as the other answer.
433
+
434
+ ```
435
+ node server "Server"
436
+ node server.web "web"
437
+ node server.db "database" below server.web
438
+ node server.cache "cache" right of server.db
439
+ ```
440
+
441
+ A container scopes names, not placement: a child may be placed against a box outside its container, and a box outside against a child. Here `bin` is as tall as a column of boxes outside `host`:
442
+
443
+ ```
444
+ node host "host"
445
+ node host.trx
446
+ node host.bin left even with host.trx top even with cat0 bottom even with nice
447
+ node src left of host.trx (gap: wide)
448
+ node cat0 below src
449
+ node nice below cat0 (gap: 6)
450
+ ```
451
+
452
+ When no child is unplaced and a child is placed against a box outside, the container is placed by what it holds: it goes wherever that puts the child, and it doesn't count as a box that says nothing about where it goes.
453
+
454
+ ```
455
+ node abc
456
+ node lot below abc (gap: 4)
457
+ node host "host"
458
+ node host.trx right of abc (gap: 6)
459
+ node host.dog right of lot (gap: 6)
460
+ ```
461
+
462
+ A container placed by its own statement can't also be placed by a child against a box outside it on the same axis. Both would say where the container goes, so it is refused as placed twice, even when the two agree; keep one of them.
463
+
464
+ ### By a stated layout
465
+
466
+ `layout: column` puts every child in a column, in written order, and `layout: row` in a row. A child of a laid-out container never places itself; that is refused.
467
+
468
+ ```
469
+ node svc "Ingestion and enrichment pipeline" layout: column (widths: match)
470
+ node svc.read "read"
471
+ node svc.parse "parse and validate"
472
+ node svc.store "store"
473
+ ```
474
+
475
+ The layout's bracket takes:
476
+
477
+ | setting | says |
478
+ |---|---|
479
+ | `gap:` | the space between children; by default the same as the container's padding |
480
+ | `widths: match` | every child as wide as the widest |
481
+ | `widths: fill` | every child across the whole box; a column only |
482
+ | `align-children:` | how the children line up with each other: in a column `left`, `center` (the default) or `right`; in a row `top`, `center` or `bottom`; in a venn, the two sets, `top`, `center` or `bottom` |
483
+ | `centers: 5 apart` | space the children by their centers, not their facing sides: the even ticks of a timeline |
484
+
485
+ `centers:` beside `gap:` is refused as said twice. `layout:` on a node with no children is refused. The bare `align:` in a layout is refused, naming `align-children:` and `gather-children:`, so `align:` means one thing everywhere: where along a side.
486
+
487
+ ### Two sets that overlap: `layout: venn`
488
+
489
+ A thing that belongs to two groups at once goes in the strip where they overlap. `layout: venn` takes exactly three children, in written order: the left set, the overlap, and the right set. Each keeps one parent and one dotted name, so the shared box is a child of the overlap.
490
+
491
+ ```
492
+ node phases "" layout: venn
493
+ node phases.design "Design time"
494
+ node phases.both ""
495
+ node phases.runtime "Runtime"
496
+
497
+ node phases.design.editor "Editor"
498
+ node phases.both.template "PDF template"
499
+ node phases.runtime.python "Python Lib"
500
+ ```
501
+
502
+ Each set wraps its own children and the overlap's, and the two cross over the overlap. Inside each part, children are placed by hand as in any container, against their own siblings. Nothing lines boxes up across parts unless you say so: a part's anchor may be placed against a box in another part, up and down only, and its whole part moves to keep it there. `node phases.runtime.python "Python Lib" even with phases.both.template` puts Python Lib level with the template, whatever else Runtime holds. Otherwise a shorter set is centered on the taller; `layout: venn (align-children: top)` (or `bottom`) lines the two sets up by their tops instead, as a row lines up its children. The same can be said on a set, `top even with phases.design` (or `bottom even with`) naming the other, which is the only placement a part may have; saying it both ways is refused. Either set may carry it, and either form is refused only when boxes placed across parts already decide where the sets sit. `layout: venn (heights: equal)` gives both sets the same top and bottom, each reaching as far as the other.
503
+
504
+ A set may also sit a distance lower or higher than the other: `top even with 6 below phases.design top` (or `bottom even with 2 above phases.design bottom`), the other set's same side moved, as any side can be. With `heights: equal` the two are then the same height, staggered by that much. When something inside the set is placed across parts, such as Python Lib `even with phases.both.template`, that decides where the contents go and the stagger places only the set's box, which still has to hold them; one that would cut into them is refused with how far. Across, each crossing border sits the set's padding from what is inside it and a normal gap from what is outside it; `layout: venn (gap: wide)` widens that gap, to make room for a line's text between parts.
505
+
506
+ The overlap has no border of its own. Its title follows every container's rule: drawn if it has one, nothing for `""`, and it sits centered at the top of the strip. The venn itself draws no box unless it states a fill or border. Each part gets a different fill unless it states one: the left set the theme's primary-subtle, the right its secondary-subtle, and the overlap the two laid over each other. A venn's sets crossing, and what is in the overlap sitting inside both, is never reported.
507
+
508
+ ### A stack of cards: `layout: deck`
509
+
510
+ A stack of cards says "there are several of these and they are the same". `layout: deck` stacks its children, the first in front and each next one a step up and to the left behind it. When the cards are only texts, `cards:` lists them, front first:
511
+
512
+ ```
513
+ node drives "" layout: deck cards: "Drive 1" "Drive 2" "Drive 3"
514
+ ```
515
+
516
+ That is the same as writing each card as a child, which is how a card that holds anything is written, and then every card is, front first; `cards:` beside cards written out is refused, quoting them:
517
+
518
+ ```
519
+ node drives "" layout: deck
520
+ node drives.1 "Drive 1" layout: column
521
+ node drives.1.backup "backup"
522
+ node drives.2 "Drive 2"
523
+ node drives.3 "Drive 3"
524
+ ```
525
+
526
+ `cards:` names them `drives.1`, `drives.2` and on, so a line or a placement can name one card: `edge laptop -> drives.1` ends on the front card. `drives` itself is the whole stack: something placed beside, above or `even with` it measures from the stack, and a line to it ends on the stack's edge. Every card is as big as the biggest, a stated size included, and takes the front card's colors unless it states its own. A card's text sits at its top left, where it shows above the card in front, unless it says where. A card places nothing itself; the deck places it.
527
+
528
+ The deck draws nothing of its own: no outline, and nothing round its cards. A `fill:` or `border:` on it draws an outline, a padding away from the cards. Its title follows every container's rule, so it is titled with its name unless it says `""`, and it goes above the stack, centered across it, unless it says where. Cards stacked on each other are never reported as overlapping.
529
+
530
+ ### Where the children sit
531
+
532
+ `gather-children:` on the box says where the group of its children sits when the box has spare room, from a stated size or a title wider than they are: against a side (`left`, `right`, `top`, `bottom`), in a corner (`top-left` and the others), or in the middle (`center`, the default). It works on any container, laid out or hand-placed; hand-placed children move as one piece.
533
+
534
+ ```
535
+ node box "Services, gathered left" gather-children: left layout: column (align-children: right)
536
+ node box.a "a"
537
+ node box.b "a longer one"
538
+ ```
539
+
540
+ That is a column with its right edges even, against the box's left side.
541
+
542
+ ### A heading
543
+
544
+ A box may name one child as its heading, `heading: d1.head`, followed by the position words a title takes. The heading sits where the title would, the box makes room for it, and the other children are arranged under it. Use it for a group that is a title: an icon, a name and a version tag in a row.
545
+
546
+ ```
547
+ node d1 heading: d1.head (inside top, align: left) layout: column
548
+ node d1.head "" shape: none layout: row (gap: tight)
549
+ node d1.head.icon icon: desktop
550
+ node d1.head.name "Desktop 1" (size: normal) shape: none
551
+ node d1.files "shared folder"
552
+ ```
553
+
554
+ The heading child places nothing itself and isn't the anchor. Refused by name: a quoted text beside `heading:` (two headings), a heading that isn't a direct child, and a placement on the heading child.
555
+
556
+ ### Joined to the text
557
+
558
+ `join-with: a` makes a node and `a` one unit for positioning. The node's placements still say where it sits against `a`; the join says the two move together wherever something places `a`.
559
+
560
+ ```
561
+ node tall "Top" height: 8
562
+ node tall.disk icon: disk 1 right of tall text top even with tall text join-with: tall text
563
+ ```
564
+
565
+ A unit that includes a box's text is placed by the text's position words: on a leaf it is centered as a pair, on a container it takes the title's place. Joins chain. In a container whose title its side words place and which has an unplaced child, a child placed only against the text, or against what is joined to it, is joined without saying so: `node d1.nw "3" right of d1 text even with d1 text` goes with the title. The box then holds two groups nothing relates, each fully stated; if they land on each other, that is reported as an overlap. `a` must be something one of the node's placements is against, and must be its own container's text or a sibling. A node joined to the text may be placed only against the text and what is joined to it. A box whose every child is joined to its text draws as a leaf. `badge:` is the short form.
566
+
567
+ ### Against its own parent
568
+
569
+ A child placed against its parent's side or corner, `inside server right` or `inside server top-left`, is placed against the finished box and never grows it. That holds one axis at a time: `node c.j "j" right of c.k inside c top` is beside `c.k` across, so the box holds it across, and against the box's top down. A corner and a sibling saying the same axis, `inside c top-right right of c.k`, are refused: the error offers `right of c.k inside c top` and `inside c top-right stays right of c.k` (below). If it covers something, that is reported. So is a child the box has no room for in that corner, such as a two-line picture in the corner of a box one line tall, as `node-overflow`; the box doesn't grow to fit it, since a box only just tall enough would put it level with the middle, not in the corner. To make the box hold something, place it relative to the other children, or keep it in the corner and clear of them with `stays`. Something the box holds may not be placed against a thing on its own side or corner, since the box would then depend on itself; that is refused.
570
+
571
+ **`stays` keeps a child clear of the rest of its box.** A picture in the corner of a box its children size can land on one of them. `stays above`, `stays below`, `stays left of` and `stays right of` say which way to keep clear:
572
+
573
+ ```
574
+ node laptop "Laptop" (inside top, align: left)
575
+ node laptop.docs "Documents"
576
+ node laptop.photos "Photos" right of laptop.docs
577
+ node laptop.mark icon: laptop inside laptop top-right stays above laptop.photos
578
+ ```
579
+
580
+ The laptop is still in the corner; `stays` never says where something goes. It says the laptop is at least the usual gap above Photos, and when it isn't, the box makes room: everything else it holds moves down together, so no gap a placement states changes, and the box grows by just enough. `(gap: wide)` or a number after the target asks for a different distance. Beside the box's own text, `stays right of drive text`, the box widens until the picture clears the text.
581
+
582
+ A limit relates two things in one box that nothing else ties together: a child placed against the box's side or corner and another child, or the box's text; two children in different corners; something beside the text and the children below it. Between two children placed against each other, or across boxes, every distance is already stated, so a limit there is refused. So is one the box can't meet by growing, such as `inside laptop bottom-right stays above laptop.photos`, and one a stated size leaves no room for.
583
+
584
+ A title inside the border counts as content, so the box makes room for it. So does anything placed against the text. A title outside the border hangs off the box and doesn't count.
585
+
586
+ ### Invisible containers
587
+
588
+ `shape: none` on a container draws no box: its edges are a tight bounding box round the children, with no padding, and anything outside can be placed against the group as one shape. Its text, if it has one, is a bare heading over the children. `node a "" shape: none` is a pure group.
589
+
590
+ ```
591
+ node cluster "" shape: none
592
+ node cluster.hub "Cloud sync"
593
+ node cluster.desktop1 "Desktop 1" above-left of cluster.hub (gap: tight)
594
+ node cluster.laptop1 "Laptop 1" below-left of cluster.hub (gap: tight)
595
+
596
+ node server "Home Server" left of cluster (gap: wide)
597
+ ```
598
+
599
+ `server` clears the whole cluster. Placed `left of cluster.hub`, it would only clear the hub. A [guide](#guides) node does the same and can be shown while you work.
600
+
601
+ ## Sizes
602
+
603
+ A box is sized from its contents unless a size is stated, with `width:` and `height:`. A stated size is a measurement, or two pinned edges ([Each axis is stated once](#each-axis-is-stated-once)).
604
+
605
+ - **A number**, in the file's unit: `width: 30`.
606
+ - **Another box's measurement**: `height: group height`, or `height: group` for the same dimension. A box's own other dimension is the bare word: `height: 0.75 width`.
607
+ - **Scaled and added**: `height: 0.5 group height`, `height: a + b + c`, `height: group + 2`, `height: 1.25 (a + b + c)`. Multiplication before addition; parentheses only group. `*` is refused, pointing to the bare form.
608
+ - **The larger or smaller of two**: `height: larger of a and b`, `smaller of a and b`.
609
+ - **A span**: `height: a top to c bottom`.
610
+ - **Limits**, each its own key: `min-width:`, `max-width:`, `min-height:`, `max-height:`.
611
+
612
+ ```
613
+ node menu "Menu" layout: column
614
+ node menu.a "Home"
615
+ node menu.b "Settings"
616
+ node menu.c "Sign out"
617
+ node panel "Content" right of menu height: menu width: 2 menu width
618
+ ```
619
+
620
+ `group.height` is refused, since the dot names a child. A bracket of limits, `height: group (at most: 30)`, is refused, pointing to `max-height:`. A text that needs more room than a stated size gives is drawn as it is and reported as `text-overflow`; nothing grows a box to fit its text once its size is stated.
621
+
622
+ ## A node's text
623
+
624
+ ### Its bracket
625
+
626
+ Everything a text says about itself goes in brackets right after it:
627
+
628
+ ```
629
+ node aside "Written nightly." (size: small, wrap: 30) shape: none
630
+ ```
631
+
632
+ | setting | takes |
633
+ |---|---|
634
+ | `color` | a color or a theme color |
635
+ | `size` | `small`, `normal` or `large`; a multiple of normal (`1.2`); or pixels (`16px`) |
636
+ | `wrap` | a count of characters to fold at, on top of ` / ` |
637
+ | `align` | how the text's own lines range: `left`, `center`, `right`, or where along a side for a title (below) |
638
+ | `fill` | on a `flow-collate` only, the patch behind its text: a color, or `none` |
639
+
640
+ `small` is 21/25 of normal and `large` the same step the other way. The names are preferred: bundled into a style, a size says *why* one text is smaller. `wrap:` is a character count, not a distance: it is how a text is made narrow and tall to sit beside something.
641
+
642
+ A style has no text of its own, so it hangs the bracket off a key: `style aside text: (size: small, color: theme-muted)`. On a node or an edge, `text:` is refused: the bracket goes after the text.
643
+
644
+ ### Where it sits
645
+
646
+ A leaf's text is centered unless its bracket says otherwise. A container's title sits against a side, said with `inside`, `on` or `outside` and the side:
647
+
648
+ | written | the title sits |
649
+ |---|---|
650
+ | `(inside top)` | along the top inside the border; the default |
651
+ | `(on top)` | on the border line, breaking it, like a form's legend |
652
+ | `(outside top)` | just above the border |
653
+ | `(center)` | in the middle of the box, as a leaf's does |
654
+
655
+ `align:` then says where along the side, and how the title's own lines range: on a top or bottom side `left`, `center` (the default) or `right`; on a left or right side `top`, `center` or `bottom`. A word that doesn't fit the side is refused, listing the three that do. The same words work on a leaf with room to spare: `node t "A tall box" (inside top) height: 20`.
656
+
657
+ ```
658
+ node hub "Hub" (inside top, align: left) layout: row
659
+ node hub.a
660
+ node hub.b
661
+ node legend "Legend" (on top) right of hub
662
+ node legend.a "solid: synced" shape: none
663
+ ```
664
+
665
+ **A title may be placed against the box's own children**, with any placement a child takes, the box implied: `(right even with r.d, top even with r.a)` puts the title in the top-right corner beside `r.a`, with no row of its own. A side word places the title across the side, and `align:` or a placement places it along; two words for one direction are refused. Opposite placements center the title between them, as they center a box: `(right of r.a left of r.b, even with r.a)` sits it in the middle of the gap the two leave. A direction nothing states takes the default, `inside top` and `align: center`.
666
+
667
+ ```
668
+ node r "Deployment" (right even with r.d, top even with r.a)
669
+ node r.a "north-west"
670
+ node r.d "south-east" below-right of r.a
671
+ ```
672
+
673
+ **`reads: up` and `reads: down`** turn a node's text a quarter turn, on a leaf or a title. A turned title on a left or right side takes a column one line wide rather than one word wide. The side and `align:` words still name directions on the page, so `(outside left, reads: up, align: top)` is a turned title against the upper end of the box's left side.
674
+
675
+ ```
676
+ node rack "Rack 1" (inside left, reads: up) layout: column
677
+ node rack.a "web-1"
678
+ node rack.b "web-2"
679
+ ```
680
+
681
+ `at:` on a node's text is refused by name, quoting the side form, or for `at: center`, quoting `(center)` on a container and saying to write nothing on a leaf.
682
+
683
+ ### Markup
684
+
685
+ A stretch of text can borrow a style's look:
686
+
687
+ ```
688
+ style dim text: (color: theme-muted)
689
+ node files "shared folder / [dim]synced[/dim]"
690
+ ```
691
+
692
+ The opener names a style, never a color, so the marked words borrow a meaning the file already has. A name no style answers to is refused, and so is a style that says nothing about text. The closer repeats the name, so a mismatched close is refused by name. Marks don't nest, and a mark may cross a line break. A bare `[` is an error; write `\[` for a literal one.
693
+
694
+ ## Lines
695
+
696
+ ```
697
+ edge [<name>] <from> <arrow> <to> ["<text>" [(<text settings>)] ...] [<attribute>] ...
698
+ ```
699
+
700
+ ### The ends and their marks
701
+
702
+ ```
703
+ edge a -> b // an arrow at b
704
+ edge a <- b // an arrow at a
705
+ edge a <-> b // one at each end
706
+ edge a -- b // a plain line
707
+ ```
708
+
709
+ The arrow is a mark, a shaft, a mark. The shaft is `-` or `--`, and either mark may be left off. A mark is a word, or a glyph standing for one:
710
+
711
+ | mark | draws | glyph |
712
+ |---|---|---|
713
+ | `arrow` | a filled arrowhead | `>` on the right, `<` on the left |
714
+ | `oarrow` | an outlined arrowhead | `\|>` on the right, `<\|` on the left |
715
+ | `dot` | a filled circle | `*` |
716
+ | `odot` | an outlined circle | `o` |
717
+ | `diamond` | a filled diamond | |
718
+ | `odiamond` | an outlined diamond | |
719
+ | `bar` | a stroke across the line | `\|` |
720
+ | `none` | nothing | |
721
+
722
+ The `o` prefix means outlined. `o-->`, `odot--arrow`, `(odot)--(arrow)` and `[odot] -- [arrow]` are one picture. A mark must touch the shaft or be bracketed, since `edge a -- dot b` reads like a line to a node named `dot`.
723
+
724
+ `from-mark:` and `to-mark:` say the same one end at a time, which is how a style carries marks. **An end with nothing written is open, not empty**: a style may fill it, and `none` says "nothing here, whatever the style says". **A mark written in the arrow is final**: the line's own `to-mark:` may repeat it but not contradict it.
725
+
726
+ ```
727
+ style inherits to-mark: oarrow
728
+ node animal
729
+ node dog below-left of animal
730
+ node cat below-right of animal
731
+ edge dog -- animal style: inherits
732
+ edge cat -- animal style: inherits
733
+ ```
734
+
735
+ **Writing order decides which end is which, never the arrow.** `a <- b` draws what `b -> a` draws, but `a` is the subject: `from:` is always the first name's end and `to:` the second's, and a text's fractions count from the first name.
736
+
737
+ **A line may be named** by a word before its ends: `edge req client -> api`. Other lines can then end on it, pass along it, or be drawn behind it.
738
+
739
+ **Each end is one name**: a box, a line, or a named point or region. `from:` and `to:` say where on it. The word before the ends, if there is one, is always the line's name, so `edge req end -> client` is a line named `req` from `end` to `client`.
740
+
741
+ ```
742
+ node c
743
+ node d right of c (gap: wide)
744
+ point a 2 below c bottom-center
745
+ point b 2 above d top-center
746
+ region pair c top-left to d bottom-right
747
+ guide edge rail a -> b
748
+ edge c -> d via: rail
749
+ edge pair -> a
750
+ ```
751
+
752
+ **An end at a named point is exactly on it.** A point has no sides, so `from:` or `to:` on that end is refused; the line heads out toward its far end, along the axis facing it, as a side would. Several lines at one point meet there, and the arrowhead or dot sits on the point, as on another line. **An end at a region is an end at a box** the size of the region: sides, spreading and every `from:` and `to:` work as on a node.
753
+
754
+ ### Where a line ends
755
+
756
+ `from:` and `to:` name a side: `edge a -> b from: right to: top`. The line heads straight out of that side, or straight into it. Where a side is needed and none is named, it is the side facing the other box: top and bottom for boxes sharing a stretch across, left and right for boxes sharing a stretch up and down, and for boxes placed diagonally the side the line between their centers crosses. A box and one inside it face each other with no side, so a line between them must name one.
757
+
758
+ **An end may name a point instead of a side.** `from: bottom-center` leaves from that one point, any of the nine; several ends naming one point meet there. A point along a side is written side first, then how far: `to: left 3/4 down`, `to: left 1/4 up`, `to: bottom 1/4 across`, `to: bottom 1/4 across from right`.
759
+
760
+ A square or curved line heading out of a bare corner takes whichever way gives fewer bends. To get the other, name the corner as part of a side (`from: bottom 1/1 across`), which sets the heading. Where both ways bend equally the line is refused, quoting both forms.
761
+
762
+ `from: center` aims from the box's center at the far end and starts where that aim crosses the outline, so the start slides round the box as the far end moves.
763
+
764
+ **An end may be even with another thing.** `from: right, even with cat1` is on the right side, level with `cat1`'s center; on a top or bottom side it is at `cat1`'s place across. The line heads straight out of the side, as with a bare side. The thing is anything a placement's `even with` takes: a box, a part of one (`even with cat1 top`), a fraction (`even with 3/4 down cat1`), a named point, or several (`even with a and b`, the middle of the region round them). Where that height misses the side, the end is refused, never bent. It already says where along the side, so a fraction or `, right of z` written with it is refused as saying it twice. With `straight right` it says where the line leaves from (below).
765
+
766
+ ```
767
+ node bin height: 12
768
+ node a left of bin top even with bin
769
+ node b below a (gap: tight)
770
+ node engine right of bin even with bin height: 12
771
+ edge a -> bin straight right
772
+ edge b -> bin straight right
773
+ edge bin -> engine from: right, even with a to: left, even with a
774
+ edge bin -> engine from: right, even with b to: left, even with b
775
+ ```
776
+
777
+ **Several ends on one side spread evenly**, ordered by where each far box sits along the side: on a top side, the line from the leftmost box takes the leftmost slot. A line states its own place among the others with `to: top, right of z` or `from: top, right of z`, where `z` is a named line on that side; on a left or right side the words are `above` and `below`. Places stated against each other form one chain, whatever order they are written in, and a loop of them is refused. Or the box lists the whole order once, in reading order, top to bottom on a left or right side and left to right on a top or bottom one:
778
+
779
+ ```
780
+ node a right-edge-order: ae, ad, ac
781
+ edge ae a -> e from: right
782
+ edge ad a -> d from: right
783
+ edge ac a -> c from: right
784
+ ```
785
+
786
+ The list names every line spreading on that side, so a line left off, a name that isn't there, a line stating its own place as well, or an end pinned on that side is refused. The keys are `top-edge-order:`, `bottom-edge-order:`, `left-edge-order:` and `right-edge-order:`, written on the node itself. An end pinned to a point, or even with something, splits the side, so the others spread on either side of it.
787
+
788
+ **An end may be on another line.** `edge audit -> req` ends on the line `req`, at its middle, and `to: at 1/3` a third of the way along it. An end may also start where another line meets the same box: on a line out of `api`, `from: req end` leaves from the point where `req` ends on `api`; naming a box the line doesn't start at is refused. Lines ending on lines must be drawable in some order, so a loop of them is refused.
789
+
790
+ **A line may go from a box back to itself**, as a loop round one of its sides: `edge job -> job from: left to: right via: job top line: (path: square)` leaves the left side, runs over the top and comes back into the right side. A straight line can't loop, so a line to itself without a square or curved path is refused, quoting this form.
791
+
792
+ **A line may leave straight one way and stop at what it meets.** `edge d1 -- bus straight down` ends where the line straight down from `d1`'s center meets `bus`. A miss is refused, never bent. This is also how a line runs level: `straight right` leaves at its own height. **`from:` may name another spot on the side it leaves from**, and the line leaves from there instead of the middle: `edge bin -> engine from: right 1/4 down straight right` runs level a quarter of the way down `bin`. A `from:` on any other side is refused, since the line couldn't leave it that way, and so is a `to:`, since `straight` already says where the line ends. The longer form names any box or named point or region: `to: down from z3`, or `from: left from bin` to take the height from the far end.
793
+
794
+ ```
795
+ node bus "bus" width: 40
796
+ node d1 above bus (gap: wide)
797
+ node d2 right of d1 even with d1
798
+ edge d1 -- bus straight down
799
+ edge d2 -- bus straight down
800
+ ```
801
+
802
+ ### A line is straight until told
803
+
804
+ `edge a -> c` with nothing else said is one straight segment between the two boxes, clipped at their borders, through whatever is in between. **Nothing is avoided**: a line through a box it doesn't connect is drawn over it and reported as `edge-node-overlap`. Its shape and its waypoints are how the file says otherwise, and each has one fixed meaning that never looks at the rest of the diagram.
805
+
806
+ `line: (path: straight)` is the default; `square` and `curved` are opt-in.
807
+
808
+ **A square line's route is read off its own two boxes and sides.** It has one free leg, which runs halfway between the boxes. Where both ends face the same way, it runs a step-out beyond the farther side. Where the ends face away from each other, each steps out and the line crosses halfway between the boxes the other way; if the boxes overlap that way there is no gap to cross, so the line runs through both and the finding names the fix, `via: a top` or `via: a bottom`. At right angles it is one corner where it can be, and otherwise goes round.
809
+
810
+ - `turn: 1/3` in the line's bracket moves the free leg a third of the way across, from the start's box. Without it, halfway.
811
+ - `step-out:` is the short first and last leg, `normal` by default: `line: (path: square, step-out: tight)`.
812
+
813
+ **A curved line follows what the file states exactly, and between two stated things it is one free sweep.** Its ends, waypoints and runs along sides are kept as written; from one to the next it is a single curve that leaves the way the line travels there and arrives the way it travels at the next. Where nothing is stated it takes the short way, over a box if one is in it, and that is reported. `curve: strong`, `normal` or `subtle` says how widely it swings. `turn:` on a curved line is a stated thing: the line passes the point where the square line's free leg would be, so the sweep becomes two.
814
+
815
+ A `turn:` that places nothing on the line it is written on is refused; from a style or `default edge` it is ignored where it can't apply.
816
+
817
+ ```
818
+ node a
819
+ node b below-right of a (gap: wide)
820
+ edge a -> b from: right to: left line: (path: square)
821
+ edge a -> b from: bottom to: top line: (path: curved)
822
+ ```
823
+
824
+ ### Waypoints
825
+
826
+ `via:` lists the places a line passes, in order:
827
+
828
+ ```
829
+ node a
830
+ node b right of a
831
+ node c right of b
832
+ edge a -> c from: top to: top via: b top
833
+ ```
834
+
835
+ - **A bare part means just outside it**, a `tight` gap away. `via: b top` runs along `b`'s top from end to end, nearer end first. `via: b top (gap: wide)` sets the distance; `via: on b top` is on the border and `via: inside b top` just inside it. `via: outside b top` is refused: outside is already what `via:` means.
836
+ - **A point is one spot**: `via: b top-center`, `via: server right 3/4 down`.
837
+ - **A box's name goes through its center**: `via: proxy`.
838
+ - **Any point**: `between c and d at 1/3`, `down from a and left from b`, a named point, `req at 1/3`.
839
+ - **Through a region**: `via: through slot horizontal` runs from the region's left edge to its right. The axis is always written.
840
+ - **Alongside a named leg**: `via: above thru (gap: tight)` runs the whole length of `thru`, a tight gap above it. The side is required.
841
+
842
+ On a square line, the stretch between each pair of neighboring stops is shaped as a line between two boxes is, with one free leg; `turn:` sets all of them. A curved line sweeps from stop to stop.
843
+
844
+ **A point and a gap are different stops.** `via: between b.1 and b.2` is a point: the line goes exactly there. To cross a gap like a doorway, run through the pair's region: `region slot between b.1 and b.2`, then `via: through slot horizontal`.
845
+
846
+ Lines through one point pinch there; they are never spread on their own. `at 1/3` and `at 2/3` part them, or a [bundle](#bundles) runs them side by side.
847
+
848
+ ### Legs
849
+
850
+ Legs are named in the `via:` list: `via: leg climb, leg over_b b top, leg descent` names the three legs of a line from `a` over `b` to `c`. A `leg` entry with a place names the run along it; one without names the stretch between its neighbors. A leg's own settings go in its bracket after its name, `leg out (path: square, turn: 1/3)`, and a stop's after the stop. Legs are never counted, so adding a stop renames nothing. `leg` is required, since a bare word in `via:` is a box.
851
+
852
+ ### Points on a line
853
+
854
+ A line named `req` has parts. `req start` and `req end` are where it was written to start and end. `req at 1/3` is a third of the way along it, bends included. `req b top` is `req` where it runs along `b`'s top. A named leg works anywhere a line does: `climb at 1/3`. A reference that matches two runs is refused, pointing to a `leg` name.
855
+
856
+ ### Copies fan out
857
+
858
+ Copies are lines with the same two boxes on the same sides, in either direction, with nothing stated about their path. Texts and colors don't count, so a request and its response are copies. Copies are drawn as one shape, each shifted sideways from it by a fixed step like lanes on a road, so they never cross. Straight copies with no side named run parallel, a `tight` gap apart, each end landing where its lane meets the border. A line that says anything about where it goes is no longer a copy, and is drawn as written: a stated landing (`to: top, right of z`), a place in a side's `right-edge-order:` list, or a `step-out:` or `turn:` in its own bracket. That is how to override the fan. A `step-out:` or `turn:` from `default edge` or a style says the same for every line, so copies sharing it still fan, each taking it from the first.
859
+
860
+ ```
861
+ node client
862
+ node api right of client (gap: wide)
863
+ edge client -> api "request"
864
+ edge api -> client "response"
865
+ ```
866
+
867
+ ### Bundles
868
+
869
+ A bundle is a path several lines share. `bundle trunk via: through slot horizontal` is built like a line with no ends, and a member joins with `via: trunk`, running its whole length.
870
+
871
+ ```
872
+ node app1 "App 1"
873
+ node app2 "App 2" below app1 (gap: wide)
874
+ node web "Web" right of app1 (gap: 12)
875
+ node db "DB" below web (gap: wide)
876
+ guide region slot between web and db
877
+ node hub right of slot (gap: 12)
878
+ bundle trunk via: through slot horizontal
879
+ edge app1 -> hub via: trunk
880
+ edge app2 -> hub via: trunk
881
+ ```
882
+
883
+ The two lines meet the gap between Web and DB, run through it side by side, and leave it together into the hub.
884
+
885
+ Its arrangement is `layout:`:
886
+
887
+ | layout | draws |
888
+ |---|---|
889
+ | `parallel` (the default) | members side by side a gap apart, `tight` unless the bracket says, so adding one widens the strip |
890
+ | `collapsed` | a bus: members merge onto the path at its start and split at its end, and the shared stretch is drawn once |
891
+ | `fan` | only on a bundle between two boxes: one curve each, widest apart at the middle |
892
+
893
+ **A parallel bundle holds one order**, so members never cross inside it. Either the bundle states it, `order: y, z, x`, listing every member by name, or members take written order and one places itself with `via: trunk (after: z)` or `(before: z)`. The two don't mix.
894
+
895
+ **A bundle may name two boxes instead of a path**: `bundle calls a -> b layout: fan` is the direct way between them, for copies of one line. Members must join those two boxes; `from:`, `to:` and `path:` on a member are refused, since the bundle says where its lines land.
896
+
897
+ **A bundle between two boxes can say how it gets there, as a line does**, with `from:`, `to:` and `line: (path: …)`, and `turn:` and `step-out:` in the same bracket:
898
+
899
+ ```
900
+ node web "Web tier"
901
+ node ops "Ops" below web (gap: 6) right of web (gap: 4)
902
+ bundle logs web -> ops from: bottom to: top line: (path: square)
903
+ edge web -> ops via: logs
904
+ edge web -> ops via: logs
905
+ edge web -> ops via: logs
906
+ ```
907
+
908
+ The bundle's middle is routed as one line from Web's bottom to Ops's top would be. Each member runs its own lane beside it and lands where its lane meets the box, so the three leave Web side by side and arrive at Ops the same way. Where a side is too short to take the lanes at their gap, they draw closer all the way. `from:` and `to:` name a side or a point on it; `layout: fan` keeps to the direct way and is refused with any of these.
909
+
910
+ A parallel bundle's path is drawn only with guides; a collapsed one is drawn, styled with the line's own keys. A member joining or leaving partway is not built.
911
+
912
+ ### A line from each child
913
+
914
+ `edge each child of a -> b` is one line from each direct child of `a` into `b`, and stays true as children are added; `a -> each child of b` likewise. Both ends is refused: pairing the children of two containers has no order to go by.
915
+
916
+ ### Lines to several ends
917
+
918
+ `edge a -> b, c, d` is one line from `a` to each of `b`, `c` and `d`; `b, c and d -> a` likewise into `a`. Everything after the ends applies to every line, so `from: right-center` sends all three out of one point, to fan from there:
919
+
920
+ ```
921
+ edge a -> b, c, d from: right-center to: left
922
+ ```
923
+
924
+ Lines one statement draws share their way on purpose, so a stretch they share isn't reported as `edge-overlap`; three separate statements naming the same point are. A text is drawn once, on the line to the first name, so `"fan" (at: start)` labels the group where its lines leave together. A list on both ends is refused, for the same reason as `each child of` on both, and so is a name, which belongs to one line.
925
+
926
+ ### Crossings
927
+
928
+ Where two lines cross, the later-written one is drawn on top and jumps the earlier: `crossing: gap` (the default, a short break), `arc`, `square` or `none`, on the later line. A line never jumps within reach of its own ends. Lines are drawn over boxes. [`depth:`](#drawing-order) changes either.
929
+
930
+ ### The line's bracket
931
+
932
+ Everything about the line goes in its bracket, on an edge, a style or `default edge`:
933
+
934
+ ```
935
+ edge web -> api line: (color: #d2904e, path: square, corners: rounded, thickness: thick)
936
+ ```
937
+
938
+ | property | takes |
939
+ |---|---|
940
+ | `color` | a color |
941
+ | `path` | `straight` (the default), `square`, `curved` |
942
+ | `corners` | `sharp` (the default), `rounded`; wherever a line has corners |
943
+ | `crossing` | `gap` (the default), `arc`, `square`, `none` |
944
+ | `pattern` | `solid` (the default), `dashed`, `dotted`, `dash-dot`; measured in thicknesses |
945
+ | `thickness` | `thin`, `normal` (the default), `thick`, a multiple of normal, or pixels (`3px`) |
946
+ | `curve` | `strong`, `normal` (the default), `subtle`; curved lines only |
947
+ | `turn` | a fraction; square and curved lines only |
948
+ | `step-out` | a distance; square and curved lines only |
949
+
950
+ `line: red` is short for `line: (color: red)`. `line: square` is refused with the bracket it meant. `corners: rounded` on a curved line is refused; from a style it lands only on lines with corners. A thick line's arrowhead grows with it, and that changes only its own drawing.
951
+
952
+ ## A line's text
953
+
954
+ ```
955
+ edge user -> order "1" (at: start) "places" "many" (at: end)
956
+ ```
957
+
958
+ A line may carry several texts, one after another, each with its own bracket. **With nothing said, a text sits at the middle of its line**, on it, with the line broken behind it. Several unplaced texts spread evenly in written order (one at the middle, three at a quarter, a half and three quarters), and placed ones cut the line into stretches: an unplaced text spreads within the stretch between the placed texts written either side of it. So write a line's texts in the order they run along it.
959
+
960
+ **Along the line:**
961
+
962
+ - `(at: 1/4)`: a fraction along the whole line, bends included, from the first name written.
963
+ - `(on: rise, at: 1/3)`: on one named leg, a fraction along it.
964
+ - `(on: longest level)`: on the line's longest piece that runs left to right, at its middle. The way to move a text off a corner. Refused on a line with no level piece.
965
+ - `(at: start)`, `(at: end)`: near an end, clear of its mark, however long the line is.
966
+
967
+ **By a box:**
968
+
969
+ - `(straight right from c)`: where a line straight right from `c`'s center first meets this line.
970
+ - `(above s1)`: just above `s1` and centered on it, as a box placed `above s1` would be, off the line if need be. A distance goes in front, as a box writes it: `(2 above s1)`. `below`, `left of` and `right of` likewise.
971
+ - `(above s1, on the line)`: stays on the line, where it first reaches that height. `gap:` sets how far above.
972
+
973
+ **Beside the line:** `(right of edge)`, `(left of edge)`, `(above edge)`, `(below edge)` move the text out the stated way until it is clear of the part of the line it landed on.
974
+
975
+ **Lined up with a box on one axis:** `(left even with a right)` puts the left edge of the patch behind the text at `a`'s right side, and leaves its height where it would otherwise sit; `(x even with mid1)` centers the text across on `mid1`. Any `even with` a box takes works, one per axis, and the other axis comes from the line. On a level stretch it slides the text along the line; on an upright one, it moves the text off the line sideways. With a relation that already says the same axis, such as `right of edge`, it is refused. Separate it from the bracket's next item with a comma.
976
+
977
+ **Which way it reads:** `reads: up` and `reads: down` turn it a quarter turn; `reads: along` takes the line's angle where it sits, never upside down.
978
+
979
+ `gap:` is the distance from the box for `at: start`, `at: end` and the box relations; `edge-gap:` the distance off the line for a text beside it. Each defaults to half a unit, written in the text's own bracket.
980
+
981
+ ```
982
+ node a
983
+ node b right of a (gap: 16)
984
+ edge a -> b "1" (at: start) "calls" (above edge) "n" (at: end)
985
+ ```
986
+
987
+ **The line is broken behind its text** by a patch the color of whatever is drawn under the text's middle: the page, a container's fill, or a region's tint over it. The text's bracket can say otherwise, in a box's words: `fill:` is the patch's color, `none` for no patch, and `padding:` the room it leaves round the text, all round. By default it reaches a little past the words either side and not at all above or below.
988
+
989
+ ```
990
+ node a
991
+ node b right of a (gap: 16)
992
+ edge a -> b "beside" (above edge, fill: none) "boxed" (at: 3/4, fill: theme-fill, padding: 0.5)
993
+ ```
994
+
995
+ **A text never moves anything.** The line is drawn first and each text is measured on it. Texts that overlap are drawn and reported as `text-overlap`. A line's text takes the line's color unless its bracket says otherwise. `align:` in a line text's bracket is refused, pointing to the placement words.
996
+
997
+ ## Guides
998
+
999
+ `guide` as the first word makes a `node`, `edge`, `point` or `region` reference-only:
1000
+
1001
+ ```
1002
+ node a
1003
+ node b right of a (gap: wide)
1004
+ guide point funnel between a and b
1005
+ guide region slot a top-left to b bottom-right
1006
+ guide node column "" below a width: 10 height: 20
1007
+ ```
1008
+
1009
+ **A guide does everything its kind does except draw.** It places things, holds children, is placed against, and shapes the lines through it. A `guide node` container's children are drawn; only its box and title aren't. That gives a reserved empty space, a column to align against, or a group with no outline.
1010
+
1011
+ Guides are shown by whoever renders the file, not by the file: `--guides` on the command line, the Guides box in the playground. A file that explains its own construction, such as a tutorial, may say `diagram guides: show`. Shown guides are dashed, in the theme's secondary color, and unfilled. Their look changes per kind with `default guide node`, `default guide edge`, `default guide point` and `default guide region`; a bare `default guide` is refused, listing the four.
1012
+
1013
+ A container styled with no border is different: the same picture with guides hidden, but showing guides never reveals it. A guide box draws nothing, so it is left out of the overlap and overflow findings.
1014
+
1015
+ ## Drawing order
1016
+
1017
+ The default order: a container before its own children, boxes before lines, an earlier line before a later one. A drawn region goes in front of every box except the ones it names, which it goes behind with everything inside them. Points are drawn last.
1018
+
1019
+ `depth: behind b` or `depth: in front of req` moves one thing to just behind or in front of another, and moves nothing else. A leg's name limits it to the crossings on that leg. A box moves with everything inside it. A contradiction needs a cycle, and the cycle is in the file, so it is refused naming both statements: `depth: behind a, in front of a.api`, or two things each behind the other. A style may not carry `depth:`.
1020
+
1021
+ ## Appearance
1022
+
1023
+ ### A color names the part it colors
1024
+
1025
+ | key | colors | on |
1026
+ |---|---|---|
1027
+ | `fill` | the inside of the outline | a box |
1028
+ | `border` | the outline | a box |
1029
+ | `(color: …)` after a text | the text | any node, an edge; `text: (color: …)` in a style or default |
1030
+ | `line` | the line and its end marks | an edge |
1031
+
1032
+ A key means one thing everywhere, so it can be wrong: `border:` on a node with no outline is refused, and the error lists what that kind has. A color is written as the viewer receives it: a hex value, any CSS color, or a theme color. There is no list of color words: `green` works because it is a CSS color. A quoted color is refused, and so is a hex value without its `#`. `none` means the part isn't painted: `fill: none`.
1033
+
1034
+ **Changing a color never moves anything.** Size is different: a bigger text or a thicker line takes more room.
1035
+
1036
+ ### A border's bracket
1037
+
1038
+ ```
1039
+ node host "Host" border: (color: #d2904e, thickness: thick)
1040
+ ```
1041
+
1042
+ `border: red` is short for `border: (color: red)`. `thickness:` takes `thin`, `normal` (the default), `thick`, a multiple of normal such as `3`, or pixels such as `4px`; `border: thick` is refused with the bracket it meant. A thick border is drawn inside the box's outline and the box grows by the extra, so its padding is still measured from the inside of the stroke, a gap to its neighbor from the outside, and a line still ends on its outer edge. A stated `width:` or `height:` is the outside of the box, stroke included. A region's border is drawn inside the edge its points state, and the region doesn't grow.
1043
+
1044
+ ### Styles
1045
+
1046
+ ```
1047
+ style backup border: #d2904e line: #d2904e
1048
+ node mirror "shared folder / mirror" style: backup
1049
+ node nas "NAS" right of mirror
1050
+ edge mirror -> nas style: backup
1051
+ ```
1052
+
1053
+ A style is a named bundle of appearance, worn with `style:`. Color carries meaning through the style's name rather than being written per thing. A style takes `fill`, `border`, `line`, `text`, `badge`, `icon`, `shape`, `from-mark`, `to-mark`, `padding`, `corners` and a region's `opacity`, and a title's position inside `text: (…)`: `style zone text: (outside top, align: left)`. It never says where a thing goes, so it can't carry a placement, a `gap:`, a `depth:`, or a line's `from:` or `to:`, and it can't carry a `url:` or a deck's `cards:`.
1054
+
1055
+ - **A style may span kinds.** It gives each thing only the keys that thing has, so the style above gives nodes their border and lines their color.
1056
+ - **A style must give a thing at least one key it has.** A style that dresses a thing in nothing is on the wrong thing.
1057
+ - **What the statement says beats any style**, wherever `style:` sits on the line. Between styles, the later wins: `style: equipment, critical and alarm` applies in that order.
1058
+ - **A key written twice on one statement is refused**, quoting both values: two words on one line are equally explicit. So is a setting twice in one bracket, `style:` twice, one style twice in a list, and a style name declared twice.
1059
+
1060
+ ### Themes
1061
+
1062
+ `diagram theme: nord` names a theme, which supplies every color the file doesn't state.
1063
+
1064
+ | theme | |
1065
+ |---|---|
1066
+ | `dark` | the default |
1067
+ | `light` | the same roles on a white page |
1068
+ | `solarized-dark`, `solarized-light` | [Solarized](https://ethanschoonover.com/solarized/) |
1069
+ | `gruvbox-dark`, `gruvbox-light` | [Gruvbox](https://github.com/morhetz/gruvbox) |
1070
+ | `catppuccin-mocha`, `catppuccin-latte` | [Catppuccin](https://catppuccin.com) |
1071
+ | `nord` | [Nord](https://www.nordtheme.com) |
1072
+ | `dracula` | [Dracula](https://draculatheme.com) |
1073
+ | `vesper` | [Vesper](https://github.com/raunofreiberg/vesper) |
1074
+ | `high-contrast-dark`, `high-contrast-light` | no fills to lean on, every line at full strength |
1075
+ | `print` | a white page, black lines and no fills, for paper |
1076
+
1077
+ Each borrowed palette is its scheme read as a diagram (a page, a box, a border, text, a line), not a copy; their licenses are in `NOTICE`. An unknown name is refused with the list. `--theme light` on the command line overrides the file's theme, so one source renders a light and a dark image.
1078
+
1079
+ A theme draws a container quieter than a leaf, keyed on having children. A box whose only children are its badge, its heading or things joined to its text draws as a leaf.
1080
+
1081
+ ### Theme colors
1082
+
1083
+ A color you write stays that color under every theme. To follow the theme, name a theme color. Every theme defines the same set, all beginning `theme-`:
1084
+
1085
+ | color | is |
1086
+ |---|---|
1087
+ | `theme-page` | the page |
1088
+ | `theme-text` | text |
1089
+ | `theme-muted` | quieter, secondary text |
1090
+ | `theme-fill` | a box's fill |
1091
+ | `theme-border` | a box's outline |
1092
+ | `theme-line` | a line |
1093
+ | `theme-primary` | the theme's signature color |
1094
+ | `theme-primary-subtle` | the same, mixed toward the page |
1095
+ | `theme-on-primary` | text that reads on a solid `theme-primary` |
1096
+ | `theme-secondary`, `theme-secondary-subtle`, `theme-on-secondary` | the same for its second color |
1097
+
1098
+ ```
1099
+ style synced fill: theme-primary-subtle border: theme-primary
1100
+ style live fill: theme-primary
1101
+ style dim text: (color: theme-muted)
1102
+ ```
1103
+
1104
+ A box with a theme fill and no text color of its own gets text that reads on that fill, so `style live` needs no `text:`. A hand-picked fill gets no such help. A `theme-` name no theme defines is refused with the list.
1105
+
1106
+ ### Defaults
1107
+
1108
+ ```
1109
+ default node border: #4a8a5c
1110
+ default leaf fill: #2e5d3a
1111
+ default container fill: #1a2620 border: #2a3a30
1112
+ default edge line: #7a8a99 text: (color: #9aa5b1, size: small)
1113
+ ```
1114
+
1115
+ A default is a style every thing of one kind wears without naming it: `node`, `leaf` (no children but those of its label), `container` (some), `edge`, and the four `default guide` kinds. The more specific word wins, all the way down: the theme, `diagram`, `default node`, `default leaf` or `default container` or `default edge`, the thing's styles in order, and what its own line says.
1116
+
1117
+ A container inside another container is filled a shade further from the page than the one it sits in, so the two read as two backdrops. An invisible container (`shape: none`) doesn't count, and a stated `fill:` wins as always.
1118
+
1119
+ A default takes the words a style does and may wear a style of its own (`default edge style: quiet`), laid beneath its own words. It is strict about kind, since it names its kind: `default edge fill: red` is refused, pointing to `line:`. `icon:` is a leaf's alone. A default carries no `url:`. Each kind is written once, and `default leaf node` is refused, naming `default leaf`.
1120
+
1121
+ ### The diagram
1122
+
1123
+ ```
1124
+ diagram theme: nord background: #1e2229 text: (color: #e0e0e0)
1125
+ diagram title: "How a login reaches the database"
1126
+ ```
1127
+
1128
+ `diagram` holds what covers the whole drawing: `theme`, `background` (the page behind everything), `text: (color: …)` for every text, `guides: show`, `checks: (…)` ([Findings](#errors-and-findings)), and `title`. It has no name, since a file holds one diagram, and a second one is an error. `diagram text:` takes a color only; a text size belongs on a default.
1129
+
1130
+ `title:` names the drawing, in quotes. It isn't drawn: the SVG carries it as its own title, which a browser shows on the tab and a screen reader announces, and a page made with `--page` is headed with it.
1131
+
1132
+ ### Destinations
1133
+
1134
+ ```
1135
+ node docs "Documentation" url: "https://example.com/docs"
1136
+ node store "Store" right of docs (gap: 12)
1137
+ edge docs -> store "read first" url: "https://example.com/order"
1138
+ ```
1139
+
1140
+ `url:` makes a thing clickable in a viewer that follows links. The whole thing is the target: a node's box and everything in it, a line's text. The value is quoted, since an unquoted `//` starts a comment. Nothing about a destination is visible. A container's destination reaches children that name none. A line with no text is refused one, since a line is a target nobody can hit. A style may not carry one.
1141
+
1142
+ ## Errors and findings
1143
+
1144
+ An **error** means the file can't be drawn: an unknown key, two placements on one axis, a cycle. Nothing is drawn, and the message names what to write instead. A **finding** is about a picture that was drawn and may not read as meant. A render with findings succeeds.
1145
+
1146
+ | kind | reports | default |
1147
+ |---|---|---|
1148
+ | `text-overlap` | a text on another text, on a line it doesn't belong to, or on a node it doesn't belong to; a text running out past the border of the container it sits in; a node over its container's title | on |
1149
+ | `text-overflow` | a text running out of a box whose stated size is too small for it | on |
1150
+ | `edge-node-overlap` | a line across a node that isn't one of its ends, or through its own ends | on |
1151
+ | `edge-overlap` | two lines on one run for a stretch, however short; a collapsed bundle's bus, and lines from one statement such as `edge a -> b, c`, shared on purpose, are not reported | on |
1152
+ | `crowded-edge-ends` | several line ends bunched on a side too short to space them: two ends' marks would touch, or, where neither has a mark, the lines come within a third of a unit | on |
1153
+ | `node-overlap` | two nodes on one spot that no layout, `inside`, `on` or `overlap: allow` accounts for; `inside hub top-right` accounts for covering `hub`, not `hub`'s other children, and a box that draws nothing (no fill, border or text) can't be covered | on |
1154
+ | `node-overflow` | a node placed against its container's side or corner that the container has no room for there: less room on the far side than it keeps from the side it names | on |
1155
+ | `edge-no-length` | a line between two boxes that touch, drawn as its marks at the seam | on |
1156
+ | `edge-crossings` | two lines crossing | off |
1157
+
1158
+ A finding is one line, in the form editors already read:
1159
+
1160
+ ```
1161
+ diagram.reladraw:12: warning(text-overlap): text "statements" covers the top-left corner of node resolver; the overlap is 3 units wide and 2 units tall
1162
+ ```
1163
+
1164
+ The file and line of the statement at fault, `warning` in a fixed spot, the kind, then what happened, saying where and how much in the file's unit, so the fix is plain without the picture. A finding never chooses a fix, though it may list the ways to write one.
1165
+
1166
+ Kinds are turned on or off by name, in the file or on the command line: `diagram checks: (edge-crossings: on, crowded-edge-ends: off)`, or `--checks "edge-crossings: on"`. A kind not mentioned keeps its default. A deliberate overlap is stated on the thing itself (`overlap: allow`), not turned off for the whole file.
1167
+
1168
+ In the playground, where you are looking at the picture, checks run only when you press Check. The findings are listed under the diagram, apart from errors, and clicking one selects its line.
1169
+
1170
+ ## The command line
1171
+
1172
+ ```
1173
+ reladraw render diagram.reladraw -o diagram.svg
1174
+ reladraw check diagram.reladraw
1175
+ ```
1176
+
1177
+ `render` draws the SVG and prints any findings. `check` does the same layout and prints only the findings, writing nothing, so an agent iterating leaves a good SVG alone. The bare `reladraw diagram.reladraw` is refused, pointing to `reladraw render`, and a mistyped command is reported with the near match.
1178
+
1179
+ | option | |
1180
+ |---|---|
1181
+ | `-o`, `--out` | where `render` writes; defaults to the input with `.svg`; `-` for standard output |
1182
+ | `--page` | `render` writes a page for looking at the diagram instead, defaulting to the input with `.html` |
1183
+ | `--theme <name>` | render in another theme, whatever the file says |
1184
+ | `--guides` | draw the guides |
1185
+ | `--checks "<kind>: on, …"` | turn kinds of finding on or off, over the file |
1186
+ | `--no-checks` | print no findings; errors still print |
1187
+ | `-v`, `--version` | print the version |
1188
+
1189
+ `reladraw install-skill` installs the agent skill that came with this version of reladraw, by running `npx skills add` on it with `--copy`. What follows it goes to `skills`: `-g` to install for every project, `-a claude-code` for one agent.
1190
+
1191
+ The output is a standalone SVG: every picture is drawn into it as path data, never from a font or a linked file. A web page can draw a diagram in place with `<reladraw-diagram>`; see "In a web page" in the README.
1192
+
1193
+ ### A page for looking at a diagram
1194
+
1195
+ ```
1196
+ reladraw render arch.reladraw --page
1197
+ ```
1198
+
1199
+ `--page` writes `arch.html`: one self-contained file, for opening in a browser, attaching or committing, that needs nothing running and fetches nothing. The page is in the diagram's own colors, so a dark diagram doesn't sit on a white page, and it is headed with `diagram title:`, or the file's name when there is none. Drag to pan; scroll, pinch or double-click to zoom; Fit shows the whole diagram and 100% returns it to its own scale. The zoom is always shown, with "Fit" beside it while the whole diagram is in view. There is a full-screen button. The Export menu copies the SVG, or saves it or a PNG, as `render` would draw it. The findings are listed beside it in a panel that folds away, and clicking one lights up what its line drew. A box's or a line's name shows under the pointer. Keys: `+` and `-` zoom, `0` fits, `1` is actual size, `f` is full screen.
1200
+
1201
+ ### Formatting
1202
+
1203
+ ```
1204
+ reladraw format diagram.reladraw other.reladraw
1205
+ reladraw format --check diagram.reladraw
1206
+ reladraw format - < diagram.reladraw
1207
+ reladraw format --init
1208
+ ```
1209
+
1210
+ `format` lays each file out again in place. By default every statement is on one line, and neighboring statements with the same keyword line their clauses up in columns, each as wide as its longest clause plus two spaces; a blank line or a new keyword starts a new set of columns, and a comment line between them doesn't. A comment at the end of a line lines up with the others in its set. A statement written over several lines with a comment among them keeps its lines, with the indent tidied. Blank lines stay as written, and so do Windows line endings.
1211
+
1212
+ Inside a clause, spacing comes out as these pages write it, whatever was typed: one space after a key's colon and between words, none just inside a bracket, and a comma against the word before it, so `line:( corners:rounded , path:square )` becomes `line: (corners: rounded, path: square)`.
1213
+
1214
+ Only whitespace changes, and the order of clauses when `clause-order` asks: every word is copied as it was written, and the result is checked to draw exactly what the original did. A file that doesn't parse is reported and left alone, and the other files still run. `--check` changes nothing, prints each file that would change, and fails if there is one, for a pre-commit hook or CI. A shell expands `*.reladraw` into the files; folders aren't searched.
1215
+
1216
+ `-` in place of the files reads the diagram from standard input and prints it formatted, touching no file. That is how an editor formats a buffer, saved or not: Vim's `:%!reladraw format -`, or the command a format-on-save package runs. Its settings come from the nearest `.reladraw-format` to the current folder.
1217
+
1218
+ The settings are in the nearest `.reladraw-format`, in the file's folder or a folder above it; with none, the defaults. A project that puts one at its top keeps everyone's formatting the same. `reladraw format --init` writes one in the current folder with every setting at its default and a comment on what each value does, and refuses if one is there already. One setting a line, as in a diagram:
1219
+
1220
+ ```
1221
+ // keep each clause straight after the last
1222
+ columns: off
1223
+ clause-gap: 2
1224
+ ```
1225
+
1226
+ | setting | values, default first |
1227
+ |---|---|
1228
+ | `wrap-at` | `off`, or a number of characters: past it, a statement wraps onto as few lines as fit |
1229
+ | `clause-per-line-at` | `off`, a number of characters, or `always`: past it, every clause gets its own line |
1230
+ | `first-line` | `text` (keyword, name and texts), `name`: where a broken statement's first line ends |
1231
+ | `clause-order` | `as-written`, or a list of kinds: `placement`, and keys by name, such as `style, placement, fill` |
1232
+ | `columns` | `aligned`, `off` |
1233
+ | `columns-shared-by` | `same-keyword-neighbors`, `neighbors`, `same-keyword` (across blank lines), `file` |
1234
+ | `clause-gap` | a number of spaces, `2`; or `tab`, with `columns: off` only |
1235
+ | `trailing-comments` | `shared-column`, `after-statement` |
1236
+ | `comments-inside` | `keep-lines`, `move-above` (joined, comments above it), `refuse` (left alone, comment named) |
1237
+ | `continued-lines-indent` | where a broken or kept statement's later lines start: `4` or another number, `tab`, `under-name`, `under-name + 2`, `under-text` (under the name when there's no text) |
1238
+ | `blank-lines` | `as-written`, `exactly 2`, `at-most 3` |
1239
+ | `edge-name-and-ends` | `one-column`, `own-columns` (name, each end and the arrow a column each) |
1240
+
1241
+ An unknown setting, one written twice, or a value it doesn't take is an error naming it.
1242
+
1243
+ **Breaking a long statement.** With both at a number, a statement up to `wrap-at` stays on one line, one up to `clause-per-line-at` wraps, and a longer one goes one clause to a line. Its length is measured on its own, before columns, so whether it breaks never depends on its neighbors; its first line still lines up with theirs. A break falls only between clauses, never inside one or inside a bracket, so a clause longer than the width stands alone on its line, past it. A statement broken by hand is broken again by these rules, or joined when they leave it short enough; one kept on its lines for a comment among them is left as it is. A comment at the end of a broken statement follows its last line.
1244
+
1245
+ ```
1246
+ // wrap-at: 60
1247
+ node confirm "Confirm"
1248
+ below accept (gap: tight) left of accept (gap: wide)
1249
+ style: plain
1250
+ ```
1251
+
1252
+ **Ordering clauses.** `clause-order` lists the kinds of clause in the order they are written: `placement` for every relation, and a key by its name (`style`, `fill`, `via`). Kinds it leaves out follow, in the order written, and clauses of one kind keep theirs. A node's or edge's texts never move, since the language takes them only straight after the name; `text` in the list means a style's `text:` key. A clause never moves where moving it would change what the statement says.
1253
+
1254
+ ## Attributes
1255
+
1256
+ Every attribute, and what takes one. A node's kind is what its body is: a box (`shape:`), a picture (`icon:`) or bare text (`shape: none`).
1257
+
1258
+ | attribute | box | picture | bare | line | says |
1259
+ |---|---|---|---|---|---|
1260
+ | `style` | ✓ | ✓ | ✓ | ✓ | the styles to wear, one or a list |
1261
+ | `shape` | ✓ | | ✓ | | the outline |
1262
+ | `icon` | | ✓ | | | the picture the node is drawn as |
1263
+ | `badge` | ✓ | | | | a picture joined to the text |
1264
+ | `cards` | ✓ | | | | a deck's cards, when they are only texts, front first |
1265
+ | `fill`, `border` | ✓ | | | | colors |
1266
+ | `corners` | ✓ | | | | `sharp` or `rounded`; only on a box's outline. A line's go in `line: (…)` |
1267
+ | `text` | ✓ | ✓ | ✓ | ✓ | in a style only; on a statement, the bracket after the text |
1268
+ | `layout` | ✓ | | ✓ | | how the children are arranged |
1269
+ | `gather-children` | ✓ | | ✓ | | where the children sit in spare room |
1270
+ | `heading` | ✓ | | ✓ | | the child that is the title |
1271
+ | `join-with` | ✓ | ✓ | ✓ | | the thing this moves with |
1272
+ | `padding` | ✓ | | ✓ | | room inside the edge |
1273
+ | `padding-top`, `-right`, `-bottom`, `-left` | ✓ | | ✓ | | one side's room, over `padding` |
1274
+ | `width`, `height`, `min-…`, `max-…` | ✓ | | ✓ | | a stated size |
1275
+ | `overlap` | ✓ | ✓ | ✓ | | `allow`: an overlap is meant |
1276
+ | `depth` | ✓ | ✓ | ✓ | ✓ | drawn behind or in front of something |
1277
+ | `url` | ✓ | ✓ | ✓ | ✓ | a destination |
1278
+ | `from`, `to` | | | | ✓ | where the line leaves and arrives |
1279
+ | `via` | | | | ✓ | the places it passes |
1280
+ | `from-mark`, `to-mark` | | | | ✓ | the marks at its ends |
1281
+ | `line` | | | | ✓ | its color, or everything about it in a bracket |
1282
+
1283
+ A point takes `style`, `fill` and `depth`; a region those, `opacity`, `border` and `padding`; a bundle `via`, `layout`, `order`, `line`, `from-mark` and `to-mark`.
1284
+
1285
+ **A word the table doesn't give the kind is an error.** A word that is an attribute nowhere is a misspelling, and the error lists what the kind takes. A word that is an attribute somewhere else is usually written on the wrong thing, so the error says where it belongs: `background:` on a node points at `fill:`, and `theme:` on one points at `diagram theme:`.
1286
+
1287
+ ## What the language refuses
1288
+
1289
+ Deliberate omissions. What they protect is that the tool never *chooses* an arrangement: it computes the one you described.
1290
+
1291
+ - **Coordinates**, in any form. Every position is said relative to things the diagram contains, so it survives those things moving.
1292
+ - **Guessing.** An axis nobody stated, an order nobody gave, a direction to separate two boxes, a way round a box. Each is refused or drawn as written and reported, never decided.
1293
+ - **Avoiding.** A line goes where it is told and is reported where it crosses something. A box stays where it is placed and is reported where it overlaps.
1294
+ - **Synonyms.** One spelling for each thing, and the others refused by name.
1295
+ - **Silence.** An unknown key, an unknown icon, a word on the wrong kind of thing, a removed spelling: each is an error naming what to write instead.
1296
+
1297
+ Not on this list: saying more about where something goes. A statement that lets you be more precise is not a step toward auto-layout.
1298
+
1299
+ ## Not built yet
1300
+
1301
+ Designed and absent from the code.
1302
+
1303
+ - **A line joining or leaving a bundle partway.**
1304
+ - **Several marks at one end of a line**, such as a database's crow's feet.
1305
+ - **Attributes, templates and functions**: definitions of your own that add placements, nodes and lines.
1306
+
1307
+ ## Undecided
1308
+
1309
+ - `widths: match` on a hand-placed container, so "as wide as the widest of them" doesn't go stale.
1310
+ - A text in a heading starts small, as bare text does, so a hand-built heading needs `(size: normal)` on its text. Whether it should start at a box's size is open.
1311
+ - How a file sets its own unit.
1312
+ - Spreading one side's line ends over the whole side.
1313
+ - More layouts: `grid`, `star`, `circle`.
1314
+ - Which side "faces" for two boxes placed diagonally. Today it is the side the line between their centers crosses; always across first is the other reading.
1315
+
1316
+ ## Changelog
1317
+
1318
+ Pre-1.0, so the minor number is where a breaking change goes. Every removal is refused by name with the replacement quoted, rather than dropped in silence: an older file stops with an error saying what to write instead.
1319
+
1320
+ **0.22.0**
1321
+
1322
+ - **`reladraw install-skill`** installs the agent skill that came with your reladraw, so it teaches what your copy accepts rather than what the main branch does. It hands the work to `skills`, passing on `-g`, `-a` and the rest. The npm package now carries the skill.
1323
+ - **The agent skill opens each diagram it draws as a page in the default browser**, with `reladraw render --page` as the last step of every drawing rather than only when asked to show it. It doesn't open one when there is no display, or when the user has said not to.
1324
+ - **The page says when it is showing the whole diagram**: its zoom reads "Fit · 62%" while fitted, and just the number once you zoom or pan. The Actual size button is now 100%, which is what it always did. Save SVG and Save PNG are under an Export button, which can also copy the SVG.
1325
+ - **The playground pans and zooms as the page does**: − and +, Fit, 100%, the wheel, a pinch, a drag, a double-click, and the keys + - 0 1, with the zoom shown between − and +. Its one Actual size button, whose label named the view you would go to rather than the one you were in, is gone. The view stays where you left it as you type. Copy SVG, Save SVG and Save PNG are now under one Export button, so the row of controls fits.
1326
+
1327
+ **0.21.0**
1328
+
1329
+ - **A diagram can have a title**: `diagram title: "How a login reaches the database"`. The SVG carries it as its own title, which a browser shows on the tab. See [The diagram](#the-diagram).
1330
+ - **`reladraw render --page`** writes a page for looking at a diagram: one self-contained HTML file in the diagram's colors, headed with its title, that pans, zooms, goes full screen, saves the SVG or a PNG, lists the findings and lights up what each one is about. See [A page for looking at a diagram](#a-page-for-looking-at-a-diagram).
1331
+ - **A border can be thicker or thinner**: `border: (color: #d2904e, thickness: thick)`, mirroring the line's bracket. A thick border is drawn inside the box, and the box grows to keep its padding, so it may move what is placed beside it. See [A border's bracket](#a-borders-bracket).
1332
+ - **A box can be lined up with something inside a box placed against it.** `node a "Node A" even with b.1` with `node b "Node B" right of a` used to be refused, "nothing anchors the diagram", because `right of a` also centered B on A. That centering now gives way, as it already did to a placement written on B itself, and B goes where B.1 is level with A.
1333
+
1334
+ **0.20.0**
1335
+
1336
+ - **`stays above`, `stays below`, `stays left of` and `stays right of`** keep a child placed in its box's corner clear of the other children: `node laptop.mark icon: laptop inside laptop top-right stays above laptop.photos`. The laptop stays in the corner, and the box makes room. See [Against its own parent](#against-its-own-parent).
1337
+ - **Breaking: a corner and a sibling saying the same axis are refused.** `node c.j "j" inside c top-right right of c.k` used to be centered between the two, neither in the corner nor beside `c.k`, straddling the box's edge. The error offers `right of c.k inside c top` to sit beside `c.k`, and `stays right of c.k` to keep the corner. The same goes for `inside c top-right above c.k` up and down.
1338
+ - **A new finding, `node-overflow`**: a child placed in its box's corner or against its side that the box has no room for there, such as `node drive.mark icon: disk inside drive top-right` on a box one line tall. It used to run out of the box with nothing said.
1339
+ - **A child in its box's corner that covers one of the box's other children is reported** as `node-overlap`, with the `stays` that would keep it clear. Being placed `inside` a box used to excuse it from covering anything in that box, not only the box.
1340
+ - **A child beside a sibling and against its parent's top stays inside the parent.** `node c.j "j" right of c.k inside c top` used to be drawn wholly outside `c`, with nothing reported. Being against the parent's top only measures its height from the finished box; across, the box holds it like any other child.
1341
+ - **Breaking: a hex color without its `#` is refused.** `fill: 444444` used to be passed to the viewer as is, which drew nothing. The error says to write `#444444`.
1342
+ - **The playground saves a PNG** with Save PNG, at twice the picture's size, in the fonts the page shows it in. Download is now Save SVG beside it.
1343
+
1344
+ **0.19.1**
1345
+
1346
+ - **A copy that states its own `step-out:` or `turn:` keeps it.** Lines between the same two sides of the same two boxes fan into lanes, and each used to be drawn as the first one's shape shifted over, so a `step-out:` or `turn:` written on any later line was ignored. A line naming either is now drawn as written and leaves the fan, and so does a line named in its side's `right-edge-order:` list. Where that puts two lines on one run, it is reported as `edge-overlap`. Prompted by #19.
1347
+ - **Errors and findings name a named line by its name**, as written: `line e2 b.1 -- b.2 runs on line e1 b.1 -- b.2`. Two lines between the same boxes used to read identically.
1348
+
1349
+ **0.19.0**
1350
+
1351
+ - **`reladraw format`** lays a file out again: each statement on one line, neighboring statements with the same keyword lined up in columns, comments kept beside what they are about. No word is respelled, and the result is checked to draw what the original did. `--check` fails on a file that would change, and `.reladraw-format` holds a project's settings; `reladraw format --init` writes one with every setting explained. `reladraw format -` reads standard input and prints the result, for editors. A long statement can wrap past a width (`wrap-at`) or go one clause to a line (`clause-per-line-at`), and `clause-order` puts placements and keys in a chosen order. The playground has a Format button (Shift+Alt+F, undone with Ctrl+Z) and a panel for its settings, which can copy them as a `.reladraw-format`. Spacing inside a clause is evened out too: `gap:tight` becomes `gap: tight`, `( gap: tight )` becomes `(gap: tight)`. Prompted by #14, where a long statement was hard to read in a plain editor.
1352
+ - **A comma may touch the next word or stand apart from both.** `(corners: rounded,path: square)` used to be refused with "`corners` has no value", and `(at: start , above edge)` was refused in a text's bracket; both now read as `rounded, path:` and `start, above` do.
1353
+ - **The playground opens and saves `.reladraw` files**, with Open… and Save… above the source. Save suggests the name of the file last opened or saved; Chrome and Edge ask where to put it, other browsers save it with the downloads. Opening a file over edits that haven't been saved to one asks first.
1354
+ - **The playground's error message can be copied**, with a Copy button beside it or by selecting it. Only the underlined line number jumps to the line now; a click anywhere on the message used to, which took a selection away.
1355
+ - **The playground no longer fails on some diagrams with 0.18.0's shapes**, which stopped with "insidePolygon is not defined". The command line was unaffected.
1356
+ - **The agent skill checks it matches the installed tool.** It compares its own version with `reladraw --version` and tells the user which of the two to update.
1357
+
1358
+ **0.18.0**
1359
+
1360
+ - **More shapes, for flowcharts and the rest** (#18): `diamond`, `pill`, `parallelogram`, `hexagon`, `trapezoid`, `triangle` and `cylinder`, named for how they look, and the flowchart standard's shapes that have no geometric name as `flow-` and what they mean, from `flow-subprocess` and `flow-delay` to `flow-collate`. A shape's settings go in a bracket after its name: `shape: parallelogram (slant: 2, lean: left)`, `shape: cylinder (lying)`, `shape: document (fold: 1.5)`.
1361
+ - **A shape grows to hold its text at its middle**, and a stated `width:` and `height:` stretch it. Its sides are the stretches of outline round its points, so `from: right` leaves a diamond from its right point.
1362
+ - **A circle given a `width:` and a `height:` that differ is an ellipse.** It used to stay round and say nothing about the height.
1363
+
1364
+ **0.17.1**
1365
+
1366
+ - **`reladraw --version`**, or `-v`, prints the version.
1367
+ - **The playground waits for a pause in typing** before it redraws a diagram that is slow to draw, so the keystrokes aren't held up behind it. A quick one still redraws on every keystroke. Suggested in #6.
1368
+
1369
+ **0.17.0**
1370
+
1371
+ The redesign. The tool no longer decides anything the file doesn't say: a gap is exact, nothing moves a box to make room, lines are straight unless told and go only where they are told, and anything that may not read as meant is drawn as written and reported as a finding. Almost every diagram written for 0.16.0 needs changes; the table below gives each old spelling and what to write instead.
1372
+
1373
+ Breaking, in how things are drawn:
1374
+
1375
+ - **Gaps are exact.** A gap used to be a minimum that other things could widen. Now it is the distance, and nothing widens it.
1376
+ - **The gap words changed size**, and a number is in the file's unit, not pixels. `tight`, `normal` and `wide` were 24, 56 and 110 pixels; they are now 2, 4 and 8 units of 12 pixels (24, 48 and 96). `gap: 12` was 12 pixels and is now 144; write `12px`, or `1`.
1377
+ - **Nothing pushes overlapping boxes apart.** Two boxes placed on one spot are drawn there and reported as `node-overlap`.
1378
+ - **Identical placements no longer make a list.** Three nodes `right of hub` used to stack into a column; now they sit on one spot, reported. Put them in a container with `layout: column`.
1379
+ - **A container's unplaced children no longer stack.** A container needs `layout: column` or `row`, or exactly one unplaced child with the rest placed against it.
1380
+ - **A child placed against its parent's side no longer grows the parent.** It is placed against the finished box.
1381
+ - **Opposite placements no longer push their targets apart.** `right of a left of b` centers the box in the space already there.
1382
+ - **A line's text no longer makes room for itself**, and no longer slides off other texts. It sits at the middle of its line unless placed, and overlaps are reported.
1383
+ - **The patch behind a line's text takes the color beneath it**, such as a container's fill, where it was always the page's color and showed as a dark box on a tint. `fill:` in the text's bracket sets it, `none` for none.
1384
+ - **A deck is a container, `layout: deck`, and its cards are its children**, the front one first, so each card has a name and the deck is the whole stack. `node drive "Drive 1" deck: "Drive 2" "Drive 3"` is written `node drive "" layout: deck cards: "Drive 1" "Drive 2" "Drive 3"`, and a front card that holds things is written out with every card as a child. `deck:` is refused, quoting the new form.
1385
+ - **Lines are straight by default**, `path: straight`. They were curved. `default edge line: (path: curved)` keeps the old look.
1386
+ - **Lines no longer find their way round boxes.** A line goes straight, or square or curved along a route read off its own two boxes, and through anything in between, which is reported. `via:` says where it goes. Lines no longer take lanes from each other; copies of one line still fan.
1387
+ - **A line with no side named meets the side facing the other box.** It used to aim at the other box's center; `from: center` does that.
1388
+ - **A line out of a side flush against another box, or between two touching boxes, is drawn and reported**, where it used to be refused.
1389
+ - **A container's title is centered along its top.** It was at the top left; `(inside top, align: left)` keeps that.
1390
+ - **A container inside a container is a shade lighter** (darker on a light theme) than the one it sits in. It used to take the same fill, so only a faint border told them apart. `fill:` sets it as before.
1391
+ - **A badge is part of the label.** The text and its picture are placed as one unit, so a badged box with nothing else in it draws as a leaf, and `badge:` is allowed on `default leaf` and `default node`.
1392
+ - **A text's `size:` number and a line's `thickness:` number are multiples of normal.** `size: 22` used to be 22 pixels, and is now 22 times normal, with no error; write `size: 22px`. `thickness: 3` likewise; write `3px`.
1393
+ - **The command takes a verb**: `reladraw render diagram.reladraw`. The bare form is refused.
1394
+
1395
+ Breaking, in spelling:
1396
+
1397
+ | 0.16.0 | now |
1398
+ |---|---|
1399
+ | `level with a`, `top level with a` | `even with a`, `top even with a` |
1400
+ | `gap: wide` on a node | in the placement's bracket: `right of a (gap: wide)`, or `8 right of a` |
1401
+ | `contents: (widths: match, align: center)` | `layout: column (widths: match)`; `gather-children: center` for where the group sits |
1402
+ | `align:` in a layout's bracket | `align-children:` in the bracket, or `gather-children:` on the box |
1403
+ | `below of a`, `left a` | `below a`, `left of a` |
1404
+ | `edge a -> b between c and d` | `via: between c and d` |
1405
+ | `edge a -> b between c and d vertically` | `via: between c and d`, or `via: through r horizontal` through a named region |
1406
+ | `edge a -> b below c` | `via: c bottom` |
1407
+ | `edge a -> b above c`, `left of c`, `right of c` | `via: c top`, `via: c left`, `via: c right` |
1408
+ | `"Title" (at: top-left)` on a node | `(inside top, align: left)` |
1409
+ | `(at: center)` on a container's text | `(center)` |
1410
+ | `(at: …)` on a leaf's text | nothing; a leaf's text is centered, or `(inside top)` and the other sides |
1411
+ | `reladraw diagram.reladraw` | `reladraw render diagram.reladraw` |
1412
+ | `size: 22` | `size: 22px` |
1413
+ | `thickness: 3` | `thickness: 3px` |
1414
+ | `gap: 12` | `gap: 12px`, or `1` |
1415
+ | `from:` or `to:` in a style, which did nothing | on each edge: `edge a -> b from: right to: left` |
1416
+ | `node drive "Drive 1" deck: "Drive 2" "Drive 3"` | `node drive "" layout: deck cards: "Drive 1" "Drive 2" "Drive 3"`; a front card holding things is written as a child, `node drive.1 "Drive 1"`, and so is every card |
1417
+
1418
+ New:
1419
+
1420
+ - **Points and regions.** `between a and b`, `down from a and left from b`, `up from c and even with d top`, `2 below c bottom`, named with `point` and `region`, usable anywhere a position is.
1421
+ - **Waypoints.** `via:` lists the places a line passes: a side, a point, a box, a region, a leg of another line. Legs can be named.
1422
+ - **Square and curved routes** read off a line's own two boxes, with `turn:`, `step-out:` and `curve:`.
1423
+ - **Line ends at points**: `from: bottom-center`, `to: left 3/4 down`, `from: center`, an end on another line, `straight down`, and a line's place among others on a side, `to: top, right of z`.
1424
+ - **A line's end even with another thing**: `from: right, even with cat1` leaves the right side level with `cat1`, and heads straight out. Lines out of a tall box can each run level with a box beside it without working out fractions of its height. A height that misses the side is refused.
1425
+ - **Named lines** and points on them: `req start`, `req end`, `req at 1/3`.
1426
+ - **A line between points and regions**: `edge a -> b` where `a` and `b` are named points, regions or boxes, in any mix, guide lines included. A point end is exactly on the point; a region end is like a box's.
1427
+ - **Bundles**, parallel, collapsed or fanned, with a stated `order:`; a bundle between two boxes routed as a line is, `bundle logs web -> ops from: bottom to: top line: (path: square)`, its lines side by side the whole way; `edge each child of a -> b`, and lines to several ends, `edge a -> b, c, d`.
1428
+ - **Texts on lines placed** along the line, on a leg, on its longest level piece, by a box, or beside the line, and turned with `reads:`; the patch behind one takes `fill:` and `padding:`.
1429
+ - **A line from a box back to itself**, as a loop round a side: `edge job -> job from: left to: right via: job top line: (path: square)`.
1430
+ - **Placing across containers**: a child may be placed against a box outside its container, `node host.bin top even with cat0`, and a container whose children are placed that way, with none unplaced, goes where they put it.
1431
+ - **A direction beside a pin**: `below trx top even with cat0 bottom even with nice` keeps `below` for lining up under `trx` and checking it holds, and takes how far down from the pins.
1432
+ - **The order of the lines on a side, listed on the box**: `node a right-edge-order: ae, ad, ac`. A line's own `below ae` works on `from:` as on `to:`, and several form one chain whatever order they are written in.
1433
+ - **`straight` from a spot on its side**: `from: right 1/4 down straight right` leaves from that spot rather than the side's middle.
1434
+ - **A curved line runs straight under its arrowhead**, so it goes into the head along its axis instead of bending out of its side.
1435
+ - **The dark theme's `theme-secondary-subtle` is quieter**, `#311b1b` where it was `#460000`, mixed toward the page as every other theme's is; the deep red glared across a venn's whole set.
1436
+ - **One side's padding**: `padding-right: 4`, and `padding-top:`, `padding-bottom:`, `padding-left:`, over `padding:` on that side, on a box or a region.
1437
+ - **A text running out of its container is reported**, as `text-overlap`, naming the side and how far: a text never moves a border, so the room is the writer's to make.
1438
+ - **A venn's sets lined up by their tops or bottoms**: `layout: venn (align-children: top)`, or `top even with` on either set, which draws the same.
1439
+ - **A venn's set staggered**: `top even with 6 below phases.design top` sits it 6 units lower than the other set; with `heights: equal` the two stay the same height.
1440
+ - **`heights: equal` gives a venn's sets the same top and bottom**, where before the shorter grew about its middle, so with `align-children: top` its top rose out of line.
1441
+ - **A line's text lined up with a box on one axis**: `"a > b" (left even with a right)`, its other axis left where the line put it.
1442
+ - **A line's text placed by a box as a box would be**: `"ack" (2 above service)` centers the text over `service`, two units up, off the line if need be; `(above service, on the line)` keeps it on the line, where the line passes above `service`. Before, `(above service)` meant the second; a file that relies on that adds `, on the line`.
1443
+ - **The axis named in front of `even with`**: `x even with a` shares `a`'s middle across, on a box or a line's text; `y even with a` is the bare `even with a` said outright.
1444
+ - **Exact placement**: a distance in front (`2 right of a`), part against part (`center 12 right of a center`), two distances (`3 below and 2 right of a`), a fraction along a side (`right of server 3/4 down`), a fraction of a box (`top even with 3/4 down a`), and a part of the target (`top even with a bottom`).
1445
+ - **Sizes**: `width:`, `height:` and the four limits, from numbers, measurements of other boxes, sums, spans and `larger of`.
1446
+ - **Layouts**: `layout: column` and `row`, with `gap:`, `widths:`, `align-children:` and `centers:`; `gather-children:` on any container.
1447
+ - **Titles placed** against any side, inside, on or outside the border, or against the box's own children, centered between two with opposite placements, and turned with `reads:`; `heading:`; `join-with:`, which a child placed only against a title its side words place takes without saying so.
1448
+ - **A point's and a region's text**: `point mid "halfway" (above-right)`, `region trust "trust boundary" (inside top)`.
1449
+ - **A region's fill is drawn at half opacity**, stated or not, so overlapping regions show both; `opacity:` from `0` to `1.0` says otherwise.
1450
+ - **Invisible containers**: `shape: none` with children.
1451
+ - **Square-cornered boxes**: `corners: sharp` on a node, a style or a default draws a box's corners square, a document's too, fold aside. Boxes are still rounded unless told, so no existing diagram changes. A circle refuses it, having no corners.
1452
+ - **`padding:`** on a box or region.
1453
+ - **Guides**: `guide` before any node, line, point or region, shown with `--guides`, the playground's Guides box, or `diagram guides: show`.
1454
+ - **Drawing order**: `depth: behind a`, `depth: in front of a`.
1455
+ - **Findings**, eight kinds, printed one per line, with `reladraw check`, `checks:`, `--checks` and `--no-checks`. In the playground, a Check button lists them under the diagram.
1456
+ - **Numbers**: `px` on any number, a number starting at its decimal point (`.5`), and `size:` and `thickness:` as multiples.
1457
+
1458
+ **0.16.0**
1459
+
1460
+ A name containing `:` or `;` is now refused; nothing else that worked is. No example diagram draws differently.
1461
+
1462
+ - A key may be written against its value: `gap:tight` is `gap: tight`, and `(gap:wide)` and `line:(color:red)` work too. The amount of whitespace never mattered, and now none counts as an amount.
1463
+ - Breaking: a name may no longer contain `:` or `;`. A colon now makes the word before it a key, and a semicolon is reserved. `node a:b` used to declare a node called `a:b`, and is now refused with an error saying why.
1464
+ - `right of a, gap: tight` names the comma as the mistake. A comma after a target promises another, and the error used to blame `tight`, two words further on.
1465
+
1466
+ **0.15.1**
1467
+
1468
+ No syntax changes, and no file that worked is refused. No example diagram draws differently.
1469
+
1470
+ - A fix: many copies of one line slowed down sharply past fifty-five. With fifty-six or more copies of `edge a -> b from: left to: right`, the ends on each side sat under half a pixel apart, and every copy was then routed on its own instead of being drawn as a copy, so sixty took five and a half seconds. They now draw as copies however close their ends sit, and sixty take under a second.
1471
+
1472
+ **0.15.0**
1473
+
1474
+ No file that worked is refused. No example diagram draws differently.
1475
+
1476
+ - Icons of your own. `icon crate """<svg …>…</svg>"""` declares one from pasted SVG, or `icon crate "./crate.svg"` from a file, and `icon: crate` and `badge: crate` then use it like a built-in. One with a built-in's name replaces it. See [Your own icons](#your-own-icons).
1477
+
1478
+ **0.14.4**
1479
+
1480
+ No syntax changes, and no file that worked is refused. No example diagram draws differently.
1481
+
1482
+ - Faster: lines steered with `between` in a busy diagram. When looking for its way, such a line didn't account for the gap it still had to pass through, so it searched much of the diagram before finding it. A test diagram with four of them among forty-four other lines drew in 6.8 seconds, and now draws in about 5.5.
1483
+
1484
+ **0.14.3**
1485
+
1486
+ No syntax changes, and no file that worked is refused. No example diagram draws differently.
1487
+
1488
+ - A fix: many curved lines into one side could hang the page. With twenty-two copies of `edge a -> b from: right to: left`, some lines had no room left to curve before the arrowhead, and the tool kept trying ever smaller curves thousands of times. It now stops at the smallest, and the same file draws in a tenth of a second.
1489
+
1490
+ **0.14.2**
1491
+
1492
+ No syntax changes, and no file that worked is refused.
1493
+
1494
+ - Many copies of one line draw far faster. Lines with the same two ends, the same sides, the same clauses and no text are now drawn as one line and its copies, each a lane further out, instead of each finding its own way round the copies before it. Fifteen copies of `edge a -> b from: left to: right` took about a third of a second and forty took two and a half; forty now take half a second. Where such copies used to split, some going over the top and some underneath, they now stay together. A copy that would run into a box or cross a line the first one does not is still found as before.
1495
+ - Curved lines in busy diagrams draw faster. No picture changes.
1496
+
1497
+ **0.14.1**
1498
+
1499
+ No syntax changes, and no file that worked is refused.
1500
+
1501
+ - Diagrams with many lines draw far faster. Since 0.13.0, the time to find the lines' ways grew much faster than the number of lines: a tangled diagram of 45 boxes and 48 lines took over half an hour. It now takes a few seconds, and ten lines take under one. No example diagram draws differently.
1502
+ - After every line is placed, lines that cross are placed again in the other order, to cross fewer. That now stops after as many attempts as there are lines. In a very tangled diagram a line may cross another where 0.13.0 and 0.14.0 found a way round; `from:`/`to:`, `below` and `between` say where a line should go.
1503
+
1504
+ **0.14.0**
1505
+
1506
+ One breaking change: on an edge written with `<-`, `from:` and `to:` now name the sides of the first and second names written. They used to follow the arrow, so `disk <- writer from: bottom` meant Writer's bottom; it now means Disk's. Swap the two on any `<-` edge that names a side. No other file draws differently.
1507
+
1508
+ - A line can have no arrowhead: `edge a -- b`, or `edge a - b`.
1509
+ - Each end of a line can carry a mark: an arrowhead filled or outlined, a dot, a diamond or a bar, written in the arrow (`a o--> b`, `a diamond--arrow b`) or as `from-mark:` and `to-mark:`, which a style can carry. See [The ends and their marks](#the-ends-and-their-marks).
1510
+
1511
+ **0.13.0**
1512
+
1513
+ No syntax changes, and no file that worked is refused. Lines move: every line that goes round something, and every line with `below`/`above`/`left of`/`right of` or `between`, may be drawn differently.
1514
+
1515
+ - A fix, no syntax change. Lines told which side of a node to pass (`below a`) or which gap to go through (`between a and b`) now find their way as every other line does, so they take lanes and keep their distance from boxes, and two lines through one gap no longer cross where they need not. Several identical `below a` lines used to be drawn on one path, reading as one line with three arrowheads, and a `between` line could run a couple of pixels from a box and come into its target down the target's own edge.
1516
+ - A curved line that goes round something now sweeps through its turns instead of cornering, as widely as the room allows; a line that steps sideways and carries on is one S. `path: square, corners: rounded` is the way to get the cornered look. Existing diagrams with a line going round a box, a clause or a `between` will look different.
1517
+ - A line with `below`/`left of` clauses that its direct shape already keeps is drawn direct, as a line without them would be, rather than always with corners. Where it does have to turn, it takes the fewest turns that keep every clause; the old "across first, then down" rule no longer decides. A line told `below m` that would otherwise never reach M's underside is now sent round under M.
1518
+
1519
+ **0.12.0**
1520
+
1521
+ - Diagrams can be written straight into a web page. Load `dist/element.js` from the package, and each `<reladraw-diagram>` element is drawn in place from the source inside it, with `theme=` choosing the colors. See "In a web page" in the README.
1522
+
1523
+ **0.11.1**
1524
+
1525
+ - Several diagrams inlined into one web page no longer lose their arrowheads. Every drawing used to name its arrowheads the same way, so a page took them all from the first drawing, and drew none when that drawing was hidden. The gaps where lines cross were named the same way, so a second drawing's lines were cut where the first drawing's crossed. Each drawing's names now carry a prefix of its own, taken from the drawing, so the same source still gives the same SVG.
1526
+
1527
+ **0.11.0**
1528
+
1529
+ - A line never passes through a box. Every edge takes the shortest way between its ends that goes through none, and goes round whatever is in its way, including edges that name no side. This replaces the separate rules for lines going over a row, turning back and going round a node behind, which kept missing cases; a fan of three from one side into a column was the latest.
1530
+ - Of two ways round that are equally short, the line goes over the top; otherwise the shorter way wins. It used to go over the top always.
1531
+ - A curve finishes straight at an arrowhead, so the head points square into its side. Most diagrams with a named side look slightly different.
1532
+ - Where two lines cross, the later one now breaks for a short gap, as though it passed underneath. It used to draw straight across; `crossing: none` keeps that.
1533
+ - Lines that turn down the same gap, such as two into one side of a box, take a lane each in the order that keeps them from crossing, instead of curving across each other.
1534
+ - A line's text no longer lands on another line's text: where two would overlap, the later one slides along its own line to the nearest clear spot.
1535
+ - **Breaking:** a line whose named side is flush against another box, with no way out, is refused with the reason. It used to be drawn through the box.
1536
+
1537
+ **0.10.0**
1538
+
1539
+ - A statement may continue onto the lines after it by indenting them. An error in such a statement names the line the mistake is on.
1540
+ - **Breaking:** indentation used to be ignored, so an indented `node` line was a statement of its own. It now continues the statement above and is refused, with a message saying to remove the indentation.
1541
+
1542
+ **0.9.0**
1543
+
1544
+ - Theme colors: `theme-page`, `theme-text`, `theme-muted`, `theme-fill`, `theme-border` and `theme-line` name the theme's own color for each part, and `theme-primary` and `theme-secondary`, with `-subtle` and `theme-on-` forms, name its two accents. Any attribute that takes a color takes them, and they follow the theme when it changes, so a diagram colored with them reads in light and dark themes alike.
1545
+ - A box with a theme fill and no text color of its own gets text that reads on that fill.
1546
+ - **Breaking:** `muted` is now `theme-muted`. `color: muted` is refused with the new name.
1547
+ - The examples use theme colors where they used to hand-pick colors for a dark page, so they read in every theme.
1548
+ - A fourteenth theme, `vesper`, from [Vesper](https://github.com/raunofreiberg/vesper).
1549
+
1550
+ **0.8.1**
1551
+
1552
+ - A fix, no syntax change. An edge naming a side at both ends could be drawn through one of its own nodes in two cases. With the nodes stacked and the sides at right angles, such as `from: bottom to: left`, the line went down past the lower node and came back up through it; it is now one curve round the corner. With both ends naming the same side, such as `from: left to: left`, the line ran straight through the node behind; it now goes over the top of that node, or round the right in a column. Existing diagrams with such an edge will look different, and correct.
1553
+
1554
+ - A fix, no syntax change. Children of a container stack close together, and an edge with no text between two of them used to get only that step, so it drew as an arrowhead with no line behind it. The gap now widens to show a short run of line behind each head, as an edge with text already did for its text. Existing diagrams with such an edge will look different, and correct.
1555
+
1556
+ **0.8.0**
1557
+
1558
+ - `shape: circle`. A circle is sized to hold its text and is always round. Edges on one of its sides spread round that quarter of the circle, and a corner of it is the point on the circle halfway round. It does not yet take a badge, a deck or a node placed against its own edge. Nothing that already renders changes.
1559
+
1560
+ **0.7.1**
1561
+
1562
+ - A fix, no syntax change. An edge with `line: (path: straight)` and a clause such as `below m` could draw its text on top of `m`. The line now bends through the room left for the text, so the text sits clear of the node. Existing diagrams with such an edge will look different, and correct.
1563
+
1564
+ **0.7.0**
1565
+
1566
+ - `line:` takes a bracket for everything about how the line is drawn: `path: curved | square | straight`, `corners: sharp | rounded`, `crossing: none | arc | gap | square`, `pattern: solid | dashed | dotted | dash-dot` and `thickness: thin | normal | thick` or a number. `line: red` still works, as the short form of `line: (color: red)`. Every default is how lines were drawn before, so no existing file changes.
1567
+
1568
+ **0.6.0**
1569
+
1570
+ - An edge whose ends face away from each other, such as `from: left to: right` with the far node to the right, goes around instead of through its own nodes: through the gap between them if there is room, over the top of the row if not. Several over one row take a lane each. Sides at right angles, such as `from: left to: top`, go around the same way when one faces away.
1571
+ - `above`, `below`, `left of` and `right of` on an edge say which side of a node the line passes: `edge a -> b below c`. Any number, one per node; one naming several nodes covers the stretch between them too. One edge may mix the two kinds, `below m left of n`, and the line turns between them as often as they need. Clauses that cannot all be drawn are refused with the reason.
1572
+
1573
+ **0.5.0**
1574
+
1575
+ - Named themes: `diagram theme: nord`, one of thirteen, and `--theme` on the command line to render a file in another without editing it. The playground has a theme picker, which writes the `theme:` line into the source.
1576
+ - `diagram text: (color: …)` sets every text's color at once.
1577
+ - `default node | leaf | container | edge` sets what every thing of that kind looks like unless it says otherwise.
1578
+
1579
+ **0.4.0**
1580
+
1581
+ - A node with any children takes the theme's container colors, however the children are placed. A node with a `badge:` or a single child in a corner now draws as a container too; in 0.3.0 only a node with a title band did. Its text still sits where it did. Say `fill:` and `border:` to keep one looking like a leaf.
1582
+ - A text's `size` takes a plain number of pixels beside `small`, `normal` and `large`: `(size: 22)`.
1583
+ - **Breaking:** `deck` is an attribute of the node, not a statement. `deck drive "Drive 2" "Drive 3"` is written `deck: "Drive 2" "Drive 3"` on `node drive`, and the old statement is an error that quotes that line back. It is refused on a picture and on a `shape: none` node, where the statement used to draw nothing.
1584
+ - **Breaking:** a key written twice on one statement is an error that quotes both values — `fill: red fill: blue`, `style:` twice, or the same property twice inside one set of brackets. In 0.3.0 one of the two was dropped in silence. A node's own word overriding its style's is unaffected.
1585
+ - `style:` takes a list, `style: equipment, critical and alarm`, applied in order with a later style winning where two set the same key.
1586
+
1587
+ **0.3.0** — the vocabulary, reworked in one breaking version so there is one migration rather than five.
1588
+
1589
+ - `box` is `node` and `link` is `edge`. The string on either is its *text*; "label" is not a word the language has.
1590
+ - A node's **body** is `shape: rectangle | document | none` or `icon: <name>`, and writing both is an error naming both. `note` is gone — a note is `node … shape: none`. `shape: instance` is gone — the icon is `cube`, and a node drawn as one is `icon: cube`.
1591
+ - A node drawn as a picture and given no text of its own shows none. A node with a body still falls back to its name.
1592
+ - The decoration icon is `badge:`, which freed `icon:` for the body. It is a shorthand for a child placed beside the text, so a container's badge now sits beside its title rather than in the far corner. It is refused on a picture and on a `shape: none` node, where it used to draw nothing.
1593
+ - A placement may target a *part* of a node — its text, a side or one of nine named points — with `inside`, `outside` and `on` reading a direction off it.
1594
+ - Everything a text says about itself is in the brackets after it — `color`, `size`, `wrap`, `align`, `at`. The top-level `size:`, `wrap:`, `align:` and the text color `text:` are gone; a style says them as `text: (…)`. `at:` grew from two positions to the nine.
1595
+ - `subtext:` is gone, replaced by inline markup `[style]word[/style]`, which names a style and reaches a word anywhere in a text. `\[` escapes a literal bracket.
1596
+ - `align: widths` is `contents: (widths: match)`, and the same brackets take `align:` for where the block of contents sits.
1597
+ - `url:` is new: a destination on a node or an edge.
1598
+ - Attributes and placements may be written in any order after the text.
1599
+
1600
+ **0.2.0** — `stroke:` split into the part it colors (`border:`, `line:`, and the text's own color); `width:` became `wrap:`; every attribute is refused by name on a kind that has no use for it, where an unknown one used to be parsed and dropped.
1601
+
1602
+ **0.1.0** — first published version.