reladraw 0.16.0 → 0.17.1

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.
package/SYNTAX.md CHANGED
@@ -1,1006 +1,1013 @@
1
- # Syntax reference — 0.16.0
1
+ # Syntax reference — 0.17.1
2
2
 
3
- What the language accepts. The parser, resolver and SVG renderer implement all of it; the sections at the end record what is defective, unchecked or undecided.
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
4
 
5
- The worked example is `examples/arch.reladraw`, transcribed from the reference render beside it. Nearly every construct here exists because that diagram demanded it. The exception is non-overlap, which that diagram never triggers, so `examples/separation.reladraw` covers it instead.
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
6
 
7
- ## Shape of the file
7
+ The worked example is `examples/arch.reladraw`, transcribed from the hand-drawn reference beside it. Every file in `examples/` renders without findings.
8
8
 
9
- A statement starts at the beginning of a line. A long one may continue onto the lines after it by indenting them:
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:
10
14
 
11
15
  ```
12
- node desktop1 "Desktop 1 / Linux"
13
- above-left of hub gap: tight
14
- badge: desktop style: synced
16
+ node desktop1 "Desktop 1 / Linux" (inside top, align: left)
17
+ badge: desktop
18
+ node desktop1.files "shared folder"
15
19
  ```
16
20
 
17
- 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 — one space and a tab mean the same — so nothing can depend on indentation you cannot see. None after a key's colon is an amount too: `gap:tight` is `gap: tight`. A bracket may stay open across lines, as in `line: (color: red,` with `pattern: dashed)` on the next. There are no blocks: indentation continues a statement, and never puts one thing inside another.
18
-
19
- An indented line with no statement to continue — at the top of the file, or after a blank line or an unindented comment — is an error rather than a statement of its own, and so is an indented line starting with a statement keyword such as `node`. Both are almost always a stray indent, and both say to remove it.
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.
20
22
 
21
- Blank lines and `//` comments are ignored. A comment runs to the end of the line and may trail a statement, so `node a "Docker" // the one that matters` is fine. An indented comment line sits inside the statement it follows; an unindented one ends it.
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.
22
24
 
23
- A lone `/` is an ordinary character rather than the start of a comment, and `#` is ordinary too — it opens a hex color. Comments were spelled `#` in an earlier version and moved to `//` so that `fill: #14532d` could be written the way every other tool writes a color.
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.
24
26
 
25
- A statement is a positional head — the keyword, a name and a text — followed by attributes and placements in any order. A token ending in a colon opens an attribute and nothing else does, so the two never have to be told apart by position.
27
+ ### The statements
26
28
 
27
- ```
28
- node server "Home Server" left of cluster gap: wide badge: desktop
29
- ^ ^ ^ ^
30
- | | | attributes
31
- | | a placement
32
- | text
33
- name
34
- ```
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)) |
35
41
 
36
- `node aside "…" shape: none below dumps (gap: tight) overlap: allow` is one statement with a placement in the middle of its attributes, and reads the same as any other arrangement of those three. Until 0.3.0 placements had to come first; that rule existed only because a bare `gap:` between two placements could not be told from the node-wide default, and [bracketing a gap onto its placement](#a-gap-belongs-to-the-placement) removed the ambiguity that made it necessary.
42
+ `box`, `link`, `note`, `deck`, `rect` and `arrow` at the start of a statement are refused by name, each quoting what to write instead.
37
43
 
38
- Attribute values are a single bare word unless quoted. No commas between attributes.
44
+ ### Heads, keys and phrases
39
45
 
40
- ## Nodes
46
+ A statement is a positional head (the keyword, a name and a text) followed by attributes and placements in any order:
41
47
 
42
48
  ```
43
- node <name> ["<text>" [(<text properties>)]] [<placement> | <attribute>] ...
49
+ node server "Home Server" (inside top) left of cluster badge: desktop
50
+ // ^ ^ ^ ^ ^
51
+ // name text its bracket a placement an attribute
44
52
  ```
45
53
 
46
- `name` identifies the node and must be unique. It may not contain `:`, which would make it a key, or `;`, which is reserved. `text` is what appears inside it, with ` / ` — a slash with whitespace on both sides — marking a line break.
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.
47
55
 
48
- The text is optional, and a node without it takes its own name as its text:
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.
49
57
 
50
- ```
51
- node parser
52
- node resolver right of parser
53
- ```
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.
54
59
 
55
- draws two nodes reading "parser" and "resolver". A dotted name shows its last segment only — `node server.docker` reads "docker", because the containment is already drawn and repeating it in the text says nothing new.
60
+ ### Names and words
56
61
 
57
- Write `""` for a node that is deliberately blank: an invisible container, a node that is nothing but its badge. The empty string is the way to say a node has no text, and leaving the text out entirely is the way to say the name is the text. A node drawn as a picture is the one exception — there, leaving the text out means no text, and `""` says the same thing.
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.
58
63
 
59
- A name written this way is doing two jobs, so renaming such a node changes the picture. That is the trade, and the escape from it is to state the text.
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:`.
60
65
 
61
- The whitespace is part of the marker, not decoration. A slash inside a word is an ordinary character, so `TCP/IP`, `16/9`, `I/O` and `https://example.com/x` all render as written. An earlier version broke on every `/` and quietly tore those texts in half.
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`.
62
67
 
63
- For a text that wants a spaced slash and no break, `\/` escapes it: `"Before \/ After"` is one line. The escapes are `\"`, `\\`, `\/` and `\[`.
68
+ ### Texts
64
69
 
65
- Everything else a text has to say about itself it says in brackets after it — see "The text and its brackets" below.
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).
66
71
 
67
- Containment is a dotted name. A node named `server.docker` is inside `server`. The parent must be declared before the child.
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.
68
73
 
69
- ```
70
- node server "Home Server" left of cluster
71
- node server.mirror "shared folder / mirror"
72
- node server.deploy "deploy dir"
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.
74
75
 
75
- A container is sized by its contents. Children stack vertically in written order unless a child carries a placement of its own.
76
+ ### Numbers and the file's unit
76
77
 
77
- A container with empty text and no fill or border takes up no room of its own and draws nothing. It exists so that everything inside it can be placed against as a single shape:
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:
78
79
 
79
- ```
80
- style invisible fill: none border: none
80
+ | word | units |
81
+ |---|---|
82
+ | `none` | 0 |
83
+ | `tight` | 2 |
84
+ | `normal` | 4 (the default) |
85
+ | `wide` | 8 |
81
86
 
82
- node cluster "" style: invisible
83
- node cluster.hub "Cloud sync"
84
- node cluster.desktop1 "Desktop 1" above-left of cluster.hub
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.
85
88
 
86
- node server "Home Server" left of cluster
87
- ```
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.
88
90
 
89
- Here the server clears the whole cluster. Placed `left of cluster.hub` instead, it would only clear the hub, and the machines around the hub would be free to grow into it.
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.
90
92
 
91
- A container's text can say where in the node it goes — see "The text and its brackets" below.
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.
92
94
 
93
- ### How the contents sit
95
+ Named values are preferred where they exist: change what `tight` means and every tight gap follows.
94
96
 
95
- A container is as wide as the wider of its title and its contents. When the title wins there is slack, and every child sits at the smallest position its constraints allow — so all of it ends up on the right and the column reads as ragged inside a node whose own text may well be centered. `contents:` is what says otherwise:
97
+ ## Nodes
96
98
 
97
99
  ```
98
- node svc "Ingestion and enrichment pipeline" contents: (widths: match, align: center)
100
+ node <name> ["<text>" [(<text settings>)]] [<placement> | <attribute>] ...
99
101
  ```
100
102
 
101
- `widths:` sizes the children and `align:` positions the block of them. They are independent, and the bracket is what keeps the levels apart: `contents:` is a property of the node, `widths:` a property of the contents.
103
+ ### The body
102
104
 
103
- | written | means |
104
- |---|---|
105
- | nothing | natural widths, ranged left |
106
- | `contents: (widths: match)` | every child as wide as the widest, ranged left |
107
- | `contents: (widths: fill)` | every child spans the whole content band; `align` then has no slack to work in. A single column only: children side by side cannot all be the full width, and are refused by name |
108
- | `contents: (align: center)` | natural widths, the block of them centered |
109
- | `contents: (widths: match, align: center)` | the tidy centered column |
105
+ Every node has one body, the thing drawn where it is, named by one of two keys.
110
106
 
111
- `align:` takes `left`, `center` and `right` here, the same three words it takes in a text's brackets. Widths are the only thing `widths:` touches and position the only thing `align:` touches; neither reaches a child's own contents.
107
+ `shape:` is the outline:
112
108
 
113
- **Changed 2026-09-20.** This replaced `align: widths`, which is now an error naming its substitution. That key was a size operation wearing an alignment's name, and its value set had one member — a flag in a property's clothes. With it gone, `align` means one thing everywhere in the language: how a block's lines or children range against each other.
109
+ | shape | draws |
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:` |
113
+ | `circle` | a circle sized to hold its text, always round |
114
+ | `none` | no outline, fill or padding: bare text, or an invisible container |
114
115
 
115
- ### The text and its brackets
116
+ `icon:` is a picture the node is drawn *as*, with no box:
116
117
 
117
- Everything a text says about itself rides in brackets after it. The keys are `color`, `size`, `wrap`, `align` and `at`, in any order, with the comma optional punctuation:
118
+ | icon | means |
119
+ |---|---|
120
+ | `disk` | a physical drive |
121
+ | `desktop` | a workstation |
122
+ | `laptop` | a portable machine |
123
+ | `package` | something stored as a whole: an archive, a bucket, a sync root |
124
+ | `cubes` | several interchangeable units; always three, since it is the symbol for "several", not a count |
125
+ | `cube` | one of them |
126
+ | `database` | a store queried rather than read as files |
118
127
 
119
128
  ```
120
- node docker "Docker" (at: bottom-center, align: center) below deploy
121
- node aside "Written by a nightly cron job." (size: small, wrap: 30) shape: none
129
+ node dump "db dump / (app 1)" shape: document
130
+ node aside "Written by a nightly cron job." (wrap: 30) shape: none below dump (gap: tight)
131
+ node svc icon: cube right of dump
122
132
  ```
123
133
 
124
- They are bracketed onto the text rather than written among the node's attributes for the same reason [a gap is bracketed onto its placement](#a-gap-belongs-to-the-placement): they modify that one thing, and the brackets make the scope visible instead of leaving it to be inferred from what happens to sit nearby. What is left at the top level is then about the node — its body, its color, where it goes.
134
+ Writing both keys is an error naming both. An unknown name is refused with the whole set. A name says what a thing is, never what the picture looks like: `document`, not `folded-corner`. The exception is a picture with no single meaning, such as `circle` or `cube`.
125
135
 
126
- An edge's text takes the same brackets, less `at`: a node's text sits somewhere in a box and an edge's rides at the middle of its line, so there is no position to name.
136
+ 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. A side of a circle is the quarter of it about that compass point, so `from: right` means the same on every shape; several line ends on one side spread round that arc, each meeting the circle square on.
127
137
 
128
- ```
129
- edge a -> b "rclone" (color: theme-muted, size: small)
130
- ```
131
-
132
- A **style** has no text of its own for a bracket to hang off, so it hangs the bracket off a key instead:
138
+ **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: a rectangle, a container, and a document's three round corners, whose fold stays as it is. A circle has none, so `corners:` written on a circle is refused; from a style or a default it lands only on the nodes with corners. A picture and bare text have no outline, so they refuse it too. Like a color, it never moves anything.
133
139
 
134
140
  ```
135
- style aside text: (size: small, color: theme-muted)
141
+ style plain corners: sharp
142
+ node api "API" style: plain
143
+ node spec "spec" shape: document corners: sharp right of api
136
144
  ```
137
145
 
138
- Bundling a text's properties into a style is how they come to mean something: `style aside` applied to several nodes says those are the same kind of remark, which `(size: small)` written out at each of them does not.
146
+ **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:`.
139
147
 
140
- `color` takes a color written as the viewer will receive it — `#8b8b8b`, or any CSS color — or a theme color such as `theme-muted`, the theme's secondary text color, which survives a change of theme. See "Theme colors".
148
+ ### Your own icons
141
149
 
142
- `size: small | normal | large` sets how big the text is set, and so does a plain number of pixels, `size: 22`. The names are the default and the preferred form: they follow the document's size if it is ever set differently, where `22` stays 22. A number says nothing about *why* one text is bigger than another, so bundle it into a style that does. `14px` is refused, since the number is already in pixels, and so is zero or less. Every kind of text has a default and `size` overrides it, exactly as `fill:` overrides the theme's color; only `shape: none` defaults to anything but `normal`, and it defaults to `small`.
150
+ `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:
143
151
 
144
- `wrap: <n>` folds the text at word boundaries every `n` characters, on top of whatever ` / ` already breaks. It is how you make a block of text narrow and tall so it can sit snugly beside something, rather than wide and short so it cannot. The number is a count of characters and not a distance: it says how much fits on a line and nothing about where anything sits.
152
+ ```
153
+ icon crate """
154
+ <svg viewBox="0 0 24 24">
155
+ <path d="M3 7 L12 3 L21 7 L21 17 L12 21 L3 17 Z" fill="none" stroke="currentColor"/>
156
+ </svg>
157
+ """
145
158
 
146
- `at: <position>` is where the block of text sits in the node, named from [the nine positions](#against-a-part-of-a-node) — `top-left`, `bottom-center`, `center` and the rest. In a container the vertical half of the word says which end of the node the text's band is at, and the contents take the other end; a container whose text names neither end is an error, since there would be no other end left for the contents. The default is `top-left` in a container and `center` in a leaf.
159
+ node store "Warehouse" badge: crate
160
+ node more icon: crate right of store
161
+ ```
147
162
 
148
- `align: left | center | right` is a different question and stays one: it says how the block's own *lines* range against each other, which matters whenever they are of unequal length and is not the same as where the block is. Its default is `left` in a container and `center` in a leaf.
163
+ - **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.
164
+ - **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.
165
+ - **Its ids and class names are renamed**, so two icons that both call a gradient `a` don't collide.
166
+ - **Script is refused**: a `<script>` element or an `on…` attribute.
149
167
 
150
- Where the node is exactly the size of what it holds — which is most leaves, since a leaf is sized from its own text — there is no slack and `at` changes nothing. It bites where there is some: an icon placed `inside <node> right` is two lines tall, so a one-line text beside it has room to sit at either end of. A badge does not give it that room, because a badge is placed against the text and goes wherever the text goes.
168
+ 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.
151
169
 
152
- #### Markup in a text
170
+ ### Badges
153
171
 
154
- A word or a stretch of a text can borrow the look of a style:
172
+ `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.
155
173
 
156
174
  ```
157
- style dim text: (color: theme-muted)
158
-
159
- node pc.files "shared folder / [dim]synced[/dim]"
175
+ node drive "External HD" badge: disk
160
176
  ```
161
177
 
162
- The opener names a **style**, never a color, so the marked words borrow a meaning the file already has instead of restating a value that goes stale the day the thing it means is recolored. A style that says nothing about text is an error naming the missing part, and so is a name no style answers to.
178
+ 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:
163
179
 
164
- The closer repeats the name. `[/dim]`, not `[/]` — a reader should never have to count openers, and a mismatched close is then refused by name. Marks do not nest.
180
+ ```
181
+ node drive "External HD"
182
+ node drive.badge icon: disk 1 right of drive text join-with: drive text
183
+ ```
165
184
 
166
- A mark may cross a line break or a fold, so `"[dim]placement is / an output[/dim]"` quiets both lines. That is what it buys over the `subtext:` it replaced, which could only quiet everything after the first line and did it by counting, in a style somewhere else in the file, where a reader of the text could not see it.
185
+ 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.
167
186
 
168
- `[` opens a mark, so a literal one is written `\[`:
187
+ With an empty text, `node flag "" badge: disk`, the node is exactly the picture and its padding, since an empty text takes no room.
169
188
 
170
- ```
171
- node sizes "sizes \[small, normal, large]" shape: none
172
- ```
189
+ **A picture that is a thing of its own is a child**, placed like any other: `node drive.mark icon: disk inside drive top-right` in a corner. 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.
173
190
 
174
- ### The body
191
+ ### Padding
175
192
 
176
- Every node has a *body*: the thing that is drawn where the node is. Two keys name it, and each names one part.
193
+ A stack of cards, which `deck:` used to draw, is a container: [`layout: deck`](#a-stack-of-cards-layout-deck).
177
194
 
178
- `shape: <name>` is the outline the node is drawn with.
195
+ `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.
179
196
 
180
- | Value | What it draws |
181
- | --- | --- |
182
- | `rectangle` | The plain rounded box. The default, so nothing has to say it. |
183
- | `document` | The same box with its top-right corner folded — the flowchart symbol saying *this is an artifact, not a process*. |
184
- | `circle` | A circle just big enough to hold the text, and always round — a longer text makes a bigger circle, never an oval. |
185
- | `none` | No outline, no fill, no padding. The node is its text and nothing else. |
197
+ ## Placing a box
186
198
 
187
- `icon: <name>` is a picture the node is drawn **as**, with no box at all.
199
+ 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.
188
200
 
189
- | Name | What it means |
190
- | --- | --- |
191
- | `disk` | A physical drive — the hardware, not the filesystem on it. |
192
- | `desktop` | A workstation. |
193
- | `laptop` | A portable machine. |
194
- | `package` | Something stored as a whole rather than run: an archive, a bucket, a sync root. |
195
- | `cubes` | Several interchangeable units of the same kind. |
196
- | `cube` | One of them. |
197
- | `database` | A store queried rather than read as files. |
201
+ ### Directions and gaps
198
202
 
199
203
  ```
200
- node dump "db dump / (app 1)" shape: document
201
- node aside "Written by a nightly cron job." (wrap: 30) shape: none
202
- node svc icon: cube
204
+ above X below X left of X right of X
205
+ above-left of X above-right of X below-left of X below-right of X
203
206
  ```
204
207
 
205
- A node has one body, so writing both keys is an error naming both.
208
+ **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.
206
209
 
207
- The `document` fold is worth having because a shape is a second channel alongside color, and a stronger one. A fill means whatever you assigned it and a reader has to learn it from the diagram; a folded corner has meant "a document" for as long as there have been flowcharts, and reads with no legend. Most diagrams lose the difference between a thing that runs and a thing that is produced, because every node is a rectangle. `diamond` will join these when a diagram asks for one.
210
+ **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.
208
211
 
209
- A circle is an ordinary node in every other way, and the file means the same thing whichever shape its nodes are. `from: right` on a circle leaves from its rightmost point, and several edges on one side spread round the quarter of the circle about that point, each meeting it square on. A corner such as `top-right` is the point on the circle halfway between those two sides, not the corner of the square around it. A circle can hold other nodes; they sit in the middle of it. A circle cannot yet take a `badge:`, a `deck:`, or a node placed against its own edge or text, and each is refused with an error saying so.
212
+ ```
213
+ node parser
214
+ node resolver right of parser
215
+ node renderer right of resolver (gap: wide)
216
+ node notes "notes" below resolver (gap: tight) shape: none
217
+ ```
210
218
 
211
- `circle` is the one name that describes the picture rather than the meaning, because a circle has no single meaning to name it after: it is a state in one diagram, a step in another, a person in a third.
219
+ **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.
212
220
 
213
- A name says what the thing *is*, never what the picture looks like: naming the meaning is what lets the drawing be improved later without every diagram that uses it changing sense. `document`, not `folded-corner`. The one place that rule stops is a picture with no single meaning — the cube stands for a container in one diagram, a VM in another, a service in a third — which is why it is called `cube` and not `instance`. An unrecognized name is an error listing the whole set, rather than a node that quietly draws nothing.
221
+ `right of server.docker` places a top-level node against something nested: containers scope names, not placement.
214
222
 
215
- The icon set is small on purpose, and it is not the trade a drawing tool makes. There you pick a shape out of a visual palette and hundreds are browsable; here you type the word from memory, which caps the useful vocabulary at what fits in a head. A picture outside it is one you declare yourself — see [Your own icons](#your-own-icons).
223
+ ### Several targets at once
216
224
 
217
- `cubes` draws three whatever the number, because it is the symbol for "several" and not a count. Where the number matters — where one of them is the end of an arrow — they are separate nodes, each with `icon: cube`.
225
+ 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:
218
226
 
219
- A node drawn as a picture is an ordinary node in every other way. It takes placements, it takes edges and sides, other nodes keep clear of it. A [badge](#badges) is one of these, written for you beside a node's text. It cannot contain anything, though, and a picture with children is an error, as is a `shape: none` node with children — neither has a box for anything to go inside.
227
+ ```
228
+ node a icon: cube
229
+ node b right of a (gap: tight) icon: cube
230
+ node c right of b (gap: tight) icon: cube
231
+ node d below a and b (gap: tight) icon: cube
232
+ node e below b and c (gap: tight) icon: cube
233
+ ```
220
234
 
221
- **A picture with no text of its own shows none.** Everywhere else a node with no text is labelled with its name, because `node a` and `node b right of a` mean the two boxes to read "a" and "b". A picture usually *is* the statement, so the default flips: `node svc icon: cube` draws the cube and no caption, and a row of five of them does not come out reading a, b, c, d, e. Write the text if you want one.
235
+ A comma promises another target, so `right of a, gap: tight` is refused, naming the comma.
222
236
 
223
- The built-in icons are drawn from path data inside the tool, never from a font or a linked file. The output is a standalone SVG and has to stay one — an icon font renders as blank boxes on a machine that does not have it, and a linked image has to travel beside the file.
237
+ ### Lining up: `even with`
224
238
 
225
- ### Badges
239
+ `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.
226
240
 
227
- `badge: <name>` puts one of the same pictures beside the node's text, as a small mark on a node that keeps its own body.
241
+ 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.
228
242
 
229
243
  ```
230
- node drive "External HD" badge: disk
244
+ node a
245
+ node b right of a top even with a
246
+ node c below a (gap: wide)
247
+ node d right of c even with c
231
248
  ```
232
249
 
233
- **`badge:` is a shorthand, and this is what it stands for:**
250
+ 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.
234
251
 
235
- ```
236
- node drive "External HD"
237
- node drive.badge icon: disk right of drive text
238
- ```
252
+ `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.
239
253
 
240
- A child drawn as a picture, placed beside its parent's text. Everything a badge does follows from that line and from the rules for [a child against its own parent](#against-its-own-parent) — there is no rule of its own:
254
+ ### Part against part
241
255
 
242
- - The node grows to hold the text and the badge side by side, because a node grows to hold its children.
243
- - On a leaf the text and the badge center in the box as one group. On a container the badge sits beside the title in the band, and the band is as tall as the two together.
244
- - The picture is two lines of the node's text tall, so it follows `(size: …)` down and up with the text.
245
- - With no text at all — `node flag "" badge: disk` — the badge sits where the text would have been, with no gap beside it, so the node is exactly the badge and its padding. That makes a badge usable as a marker and not only as a title-block ornament.
246
- - A text sent to one end with `at:` does not take the badge with it. A leaf is as tall as the badge, the badge is centered on the text, and so the text sits in the middle. To give a text room to move, put the picture against a side instead — see `at:` under [The text and its brackets](#the-text-and-its-brackets).
256
+ `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").
247
257
 
248
- The child is called `<node>.badge` and can be named like any other: `below drive.badge`, or an edge to it. Declaring your own `drive.badge` beside a `badge:` is an error naming both ways out. Something else placed `right of drive text` is placed exactly as the badge is, so the two form [one list](#several-nodes-saying-the-same-thing), badge first.
258
+ 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`.
249
259
 
250
- A badge in the far corner of a container is not what the shorthand says. Write the child yourself, `node drive.mark icon: disk inside drive top-right`.
260
+ **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.
251
261
 
252
- **A badge decorates a node; `icon:` replaces its body.** The test is whether the node still sizes itself from its text: a badged node does, a picture does not. The same artwork serves both. Only a box can wear a badge. On a picture or a `shape: none` node the word is an error, since neither has a box to grow around it.
262
+ ### Each axis is stated once
253
263
 
254
- `badge` is appearance, so a style can carry one and every store in a diagram then looks alike without the word being written more than once:
264
+ 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:
265
+
266
+ - **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.
267
+ - **Inside out.** Two pinned edges that would give a negative size.
268
+ - **Circular.** A box measured from something measured from it.
269
+ - **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.
270
+
271
+ **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:
255
272
 
256
273
  ```
257
- style store fill: #142814 border: #486544 badge: database
258
- node records "Records" style: store
274
+ node host.bin below host.trx top even with cat0 bottom even with nice
259
275
  ```
260
276
 
261
- ### Your own icons
277
+ `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.
262
278
 
263
- `icon <name>` declares a picture of your own, and from then on `icon: <name>` and `badge: <name>` use it exactly as they would a built-in one. The picture is SVG, pasted between `"""` marks or read from a file:
279
+ **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.
264
280
 
265
- ```
266
- icon crate """
267
- <svg viewBox="0 0 24 24">
268
- <path d="M3 7 L12 3 L21 7 L21 17 L12 21 L3 17 Z" fill="none" stroke="currentColor"/>
269
- </svg>
270
- """
281
+ 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.
271
282
 
272
- icon rack "./rack.svg"
283
+ ### One anchor
273
284
 
274
- node store "Warehouse" badge: crate
275
- node servers icon: rack right of store
276
- ```
285
+ 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. 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.
277
286
 
278
- Between `"""` marks everything is taken as written, double quotes and line breaks included, so an SVG can be pasted in unchanged. The opening `"""` may start the next line rather than end the `icon` line. A `"""` string is accepted only in an `icon` declaration for now.
287
+ ### A box goes where it is placed
279
288
 
280
- A file is found relative to the diagram's own file. Only the command-line tool can read one; in the playground or a web page, paste the SVG instead.
289
+ 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.
281
290
 
282
- - **It keeps its own colors.** A pasted drawing looks the same on every theme. What it leaves to `currentColor` takes the theme's icon color, which is how a one-color icon follows the theme.
283
- - **It is fitted to the icon's square,** in proportion and centered, by its `viewBox`. With no `viewBox`, its `width` and `height` stand in; with neither it is refused, since nothing says how big the drawing is.
284
- - **Its ids and class names are renamed** on the way in, so two icons that both call a gradient `a` don't pick up each other's.
285
- - **Script is refused:** a `<script>` element or an `on…` attribute.
291
+ 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.
286
292
 
287
- An icon of your own with a built-in's name replaces the built-in throughout the diagram, so `icon disk "./my-disk.svg"` redraws every disk. Declaring one name twice is an error.
293
+ ### Against a part of a node
288
294
 
289
- The drawing is written into the output, so the SVG stays standalone however the icon was supplied.
295
+ A placement's target may be a part of a node, with one of three words, each taking one part of one thing:
290
296
 
291
- ## Placement
292
-
293
- A *placement* says one thing about where a node goes. A node carries as many as it needs.
297
+ | | |
298
+ |---|---|
299
+ | `inside hub top-right` | wholly within, against that part |
300
+ | `outside hub right` | wholly beyond it |
301
+ | `on hub bottom-left` | centered on it, straddling it; takes no gap |
294
302
 
295
303
  ```
296
- above X below X left of X right of X
297
- above-left of X above-right of X below-left of X below-right of X
298
- level with X top level with X bottom level with X
299
- left level with X right level with X
304
+ node bob "Bob the builder" (inside top) height: 7
305
+ node bob_link "bob.example.com" (size: small) inside bob bottom-center shape: none
306
+ node mark "!" on bob top-right shape: circle
300
307
  ```
301
308
 
302
- Any of them may name more than one target — `right of borg and bare`, `level with borg, bare and media`. See [Several targets at once](#several-targets-at-once).
303
-
304
- `of` is optional after any direction, so `below X` and `below of X` both parse. Write whichever reads as English.
309
+ *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.
305
310
 
306
- There are no coordinates. Each direction leaves a gap, one of `none`, `tight`, `normal` (the default) and `wide`, or a plain number of pixels — `gap: 12`. Prefer the names: change what `tight` means and every tight gap follows. A number is for the distance you actually mean, and it is no less relative than a name, since it is still measured from the target. Write it in brackets on the placement itself, or as `gap:` on a node to set the default for every relationship that node is in — including the ones named against it. See [A gap belongs to the placement](#a-gap-belongs-to-the-placement).
311
+ 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.
307
312
 
308
- Exactly one node in the document may be left unplaced. Everything else is positioned, directly or transitively, relative to it.
313
+ ## Points and regions
309
314
 
310
- ### How placements combine
315
+ 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.
311
316
 
312
- **A gap sets a minimum distance rather than an exact one,** so everything ends up as close together as your placements allow.
317
+ ### The parts of a box
313
318
 
314
- That single rule is what makes a corridor work. Say two things sit side by side, then put a third between them:
319
+ A box's parts are its `text`, its four sides `top`, `bottom`, `left` and `right`, and its nine points:
315
320
 
316
321
  ```
317
- node hub "Hub"
318
- node side "Side" left of hub
319
- node wedge "Wedged" right of side left of hub level with hub
322
+ top-left top-center top-right
323
+ left-center center right-center
324
+ bottom-left bottom-center bottom-right
320
325
  ```
321
326
 
322
- Nothing states how far apart `hub` and `side` are. They start one gap apart, and adding `wedge` between them pushes them to exactly `wedge`'s width plus two gaps. Delete `wedge` and they close back up. You never pick a number, and no number goes stale when a text grows.
323
-
324
- This is the step you would otherwise do by hand: shove two things apart to make room, then drag everything back together so the diagram isn't full of holes. The file states the relationships and the distances fall out.
327
+ **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 a circle, a corner is the point on the circle halfway between its two sides.
325
328
 
326
- **A lone directional placement still sets both axes.** `right of docker` on its own also centers the node vertically on Docker, because walking right from something keeps you on its center line. That half is dropped as soon as another placement binds the axis, so it never fights anything you wrote.
329
+ A part is written as the name, then the part: `b top`, `e bottom-left`, `hub text`.
327
330
 
328
- **Two placements on one axis and nothing on the other is an error.** `right of docker left of macbook` with no vertical placement would have to choose between Docker's center line and the Macbook's, and that choice decides which row of the diagram the node shares. The tool refuses and names the axis you left unstated. Add `level with docker`, or `below` something, and it resolves.
331
+ ### Points
329
332
 
330
- **Placements that cannot all hold are an error naming them,** rather than a picture with one node on top of another. Because gaps are minimums, this only happens when something is pinned exactly — by `level with`, or by a loop of placements that each demand more room than the last.
333
+ - **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.
334
+ - **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.
335
+ - **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.
336
+ - **On a line.** `req start`, `req end`, `req at 1/3` ([Points on a line](#points-on-a-line)).
331
337
 
332
- `level with X` is the one placement that fixes a distance outright: share a center line, no gap involved. It binds the vertical only.
338
+ A box is placed at a point with `on`, and against one with a direction:
333
339
 
334
340
  ```
335
- node dumps "" right of server.docker left of cluster level with server.docker
341
+ node a
342
+ node b right of a (gap: wide) below a
343
+ node c on down from a and left from b shape: circle
336
344
  ```
337
345
 
338
- Horizontally between two different targets, vertically level with a third. No single relation can say that, which is why a node can carry several.
339
-
340
- Naming a side in front of it aligns that side instead of the center. `top level with media` puts the node's top side on the media node's top side; `bottom`, `left` and `right` work the same way. `top` and `bottom` bind the vertical, `left` and `right` the horizontal — so `left level with X` and `left of X` are different statements, and the word after `left` is what tells them apart.
341
-
342
- ### Several targets at once
346
+ `c` sits under `a` and level with `b`.
343
347
 
344
- A placement may name several targets joined by `and`, with optional commas. It then places the node against the box that just bounds them all — a region you never have to declare.
348
+ ### Named points and regions
345
349
 
346
350
  ```
347
- node swapped "Swapped weekly …" shape: none left of drive.mirror and drive.clone gap: tight
348
- node kept "never rotated …" shape: none right of drive.archive and drive.old gap: tight
351
+ point funnel between c2 and api at 2/3
352
+ region trust web top-left to db bottom-right
353
+ region slot between left_rack and right_rack
349
354
  ```
350
355
 
351
- Neither annotation says anything about its own vertical position, and neither needs to. A lone directional placement centers on what it names, and what these name is the region covering two nodes, so each one lands centered on the pair it explains.
356
+ 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.
352
357
 
353
- This is why it is one placement with two targets rather than two placements. Two separate `level with` statements are two demands that both have to hold, and nodes at different heights cannot both share a center line with the same node, so that combination is a contradiction. One statement naming two targets is a single demand about a single region.
358
+ 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.
354
359
 
355
- Combined with the rule that a lone directional placement also binds the other axis, this is how you offset one row against another. `below a and b` reads as "under the pair, centered between them", because the direction binds the vertical against the region and the horizontal falls on the region's center line:
360
+ 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`.
356
361
 
357
362
  ```
358
- node a icon: cube
359
- node b right of a gap: tight icon: cube
360
- node c right of b gap: tight icon: cube
361
- node d below a and b gap: tight icon: cube
362
- node e below b and c gap: tight icon: cube
363
+ node web "Web"
364
+ node api "API" right of web
365
+ node db "DB" below api
366
+ region trust web top-left to db bottom-right padding: 1
363
367
  ```
364
368
 
365
- Three above, two below, each sitting in the gap between two of them. Adding a second placement to bind the horizontal is what would left-justify the lower row instead — the centering is dropped as soon as anything else claims that axis.
366
-
367
- For a direction the region is a floor, so `right of one and three` clears whichever of them sticks out furthest, and it works wherever the targets sit. For an alignment the region is an exact position, and there is one restriction worth knowing: it must not depend on the node being aligned to it. Aligning a node to the region covering `A` and `B` while `B` is placed relative to that same node is refused by name, because there is no order in which each could wait for the other.
369
+ ### A point's and a region's text
368
370
 
369
- ### A gap belongs to the placement
371
+ A point or a region may have a text, quoted after its name, with a bracket like any text's.
370
372
 
371
- A gap is a fact about a relationship, not about a node, so it is written on the placement that names that relationship:
373
+ **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.
372
374
 
373
375
  ```
374
- node dumps "" right of server.docker left of machines (gap: wide) level with server.docker
376
+ node a "A"
377
+ node b "B" right of a (gap: 14)
378
+ point mid "halfway" (above-right, gap: 1) between a and b
375
379
  ```
376
380
 
377
- The dumps come straight out of Docker, so they sit at the default distance from it; the two curves on the other side need room to fan out before they reach the machines, so that gap is wide. One number could not say both.
378
-
379
- `gap:` written as an ordinary attribute still works and is the node's default, used by every placement that does not name its own:
381
+ **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.
380
382
 
381
383
  ```
382
- node stack "Stack" below wedge right of wall (gap: tight) gap: wide
384
+ node web "Web"
385
+ node api "API" right of web
386
+ node db "DB" below api
387
+ region trust "trust boundary" (inside top, align: left) web top-left to db bottom-right padding: 1
383
388
  ```
384
389
 
385
- Wide below the wedge, tight to the right of the wall.
390
+ 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`.
386
391
 
387
- **A node's `gap:` reaches every relationship it is in, not only the ones it wrote down.** A relationship exists regardless of which of its two ends happened to name the other, so `gap:` on a node also applies to placements written *against* it:
392
+ ## Containers
388
393
 
389
- ```
390
- node parser "Parser" gap: wide
391
- node renderer "Renderer" right of parser
392
- ```
394
+ 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.
393
395
 
394
- `parser` names nothing, and the gap still opens. Without this, the only way to push those two apart would be to know that `renderer` is the one that mentioned `parser` and to edit that line instead — which is a fact about how the file was typed, not about the picture.
396
+ ### By hand
395
397
 
396
- Where both ends state a gap the larger applies, since a gap is a minimum either way. A gap in brackets is not a default and is not overruled: it is the specific statement about that one pair, so it wins outright. If you mean a gap to govern one placement and not the whole node, that is what the brackets are for.
398
+ 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.
397
399
 
398
- The brackets take `gap:` and nothing else at present; anything else in them is an error naming it. An alignment is an error too — `level with x (gap: tight)` — because sharing a line leaves no distance for a gap to set, and a word that quietly does nothing looks like a fault in the tool.
399
-
400
- ### On a box
400
+ ```
401
+ node server "Server"
402
+ node server.web "web"
403
+ node server.db "database" below server.web
404
+ node server.cache "cache" right of server.db
405
+ ```
401
406
 
402
- A placement's target may be a *part* of a node rather than the whole of it — `inside hub top-right`, `right of hub text`, `on hub bottom-left`. That is where `inside`, `outside` and `on` come in, and it has a section of its own: [Against a part of a node](#against-a-part-of-a-node).
407
+ 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`:
403
408
 
404
- ### Side to side
409
+ ```
410
+ node host "host"
411
+ node host.trx
412
+ node host.bin left even with host.trx top even with cat0 bottom even with nice
413
+ node src left of host.trx (gap: wide)
414
+ node cat0 below src
415
+ node nice below cat0 (gap: 6)
416
+ ```
405
417
 
406
- A zero gap turns an offset into contact. `below docker (gap: none)` puts the node's top side flat against Docker's bottom side, so exact side relations need no vocabulary of their own.
418
+ 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.
407
419
 
408
- ### Targets
420
+ ```
421
+ node abc
422
+ node lot below abc (gap: 4)
423
+ node host "host"
424
+ node host.trx right of abc (gap: 6)
425
+ node host.dog right of lot (gap: 6)
426
+ ```
409
427
 
410
- The target of a placement may be a child of another container. `right of server.docker` places a top-level node against something nested. Containers scope names; they do not scope placement.
428
+ 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.
411
429
 
412
- Every axis is solved as one system, so a target does not have to come first. What cannot be satisfied is a loop of placements each demanding more room than the last, and that is an error naming the placements in it.
430
+ ### By a stated layout
413
431
 
414
- ## Nodes do not overlap
432
+ `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.
415
433
 
416
- You never have to say that two nodes must not sit on top of each other. Every pair carries that already, and `overlap: allow` on either one is the opt-out for the rare case where one is meant to cover another. A container never counts as overlapping its own contents, and neither does a node placed `inside` or `on` a part of another and the box it is stamped on — naming a part and saying `inside` is the author stating the overlap, so there is nothing to report. That exemption is for the pair and nothing else, so the node is still kept clear of everything else in the drawing.
434
+ ```
435
+ node svc "Ingestion and enrichment pipeline" layout: column (widths: match)
436
+ node svc.read "read"
437
+ node svc.parse "parse and validate"
438
+ node svc.store "store"
439
+ ```
417
440
 
418
- **The tool never picks which way to separate two nodes.** It reads the direction off the arrangement you already stated. Say `A` is left of `B`, put `x` between them, and hang a wide node below `x`: because `x` is right of `A` and the wide node is centered under `x`, the file lets the wide node travel rightward away from `A` and offers no way back. So the only separation it allows is `A` moving further left. Nothing is chosen. Where nothing in the file orders a pair on either axis, the tool refuses and names the pair rather than guessing — which is what happens if you hang two nodes off the same side of the same target and expect them to sort themselves out.
441
+ The layout's bracket takes:
419
442
 
420
- **One place the tool decides something you didn't write.** Sometimes both axes already imply an order. The wide node is rightward of `A` and also below it, so the overlap clears either by pushing `A` and `B` apart or by dropping the wide node lower, and both satisfy everything you wrote. The rule is to **separate along the axis where the two nodes overlap least**, which is also the smallest movement, and which matches what a person does by hand.
443
+ | setting | says |
444
+ |---|---|
445
+ | `gap:` | the space between children; by default the same as the container's padding |
446
+ | `widths: match` | every child as wide as the widest |
447
+ | `widths: fill` | every child across the whole box; a column only |
448
+ | `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` |
449
+ | `centers: 5 apart` | space the children by their centers, not their facing sides: the even ticks of a timeline |
421
450
 
422
- If you want that pinned down rather than defaulted, group the nodes: put `x` and the wide node in an invisible container. The container becomes the thing that must not overlap `A` and `B`, and it moves as one.
451
+ `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.
423
452
 
424
- Separation leaves a tight gap, deliberately small — enough to read as two nodes rather than one, never enough to look like a distance somebody asked for. Say `gap:` if you want breathing room there. Between two things inside one box it leaves the step the box's contents are stacked by instead, since that is the spacing everything else in there has.
453
+ ### Two sets that overlap: `layout: venn`
425
454
 
426
- ## Edges
455
+ 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.
427
456
 
428
457
  ```
429
- edge <from> <arrow> <to> ["<text>"] [between <a> and <b> [vertically|horizontally]] [attributes]
430
- ```
458
+ node phases "" layout: venn
459
+ node phases.design "Design time"
460
+ node phases.both ""
461
+ node phases.runtime "Runtime"
431
462
 
432
- ```
433
- edge a -> b // an arrow at b
434
- edge a <- b // an arrow at a
435
- edge a <-> b // one at each end
436
- edge a -- b // a plain line
463
+ node phases.design.editor "Editor"
464
+ node phases.both.template "PDF template"
465
+ node phases.runtime.python "Python Lib"
437
466
  ```
438
467
 
439
- `a <- b` draws the same picture as `b -> a`. What changes is which name you write first, and that is worth having: the first name reads as the subject of the line, and plenty of edges are about the thing the arrow points at rather than the thing it leaves. **`from:` and `to:` follow the writing order**, never the arrow: `from:` is always the first name's side and `to:` the second's, whichever way the arrow points.
468
+ 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.
440
469
 
441
- ### What is drawn at each end
470
+ 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.
442
471
 
443
- The arrow is a mark, a line, a mark. The line is `-` or `--`, the two meaning the same, and either mark may be left off. A mark is a word, or a glyph that stands for one:
472
+ 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.
444
473
 
445
- | mark | draws | glyph |
446
- |---|---|---|
447
- | `arrow` | a filled arrowhead | `>` at the right end, `<` at the left |
448
- | `oarrow` | an outlined arrowhead | `\|>` at the right end, `<\|` at the left |
449
- | `dot` | a filled circle | `*` |
450
- | `odot` | an outlined circle | `o` |
451
- | `diamond` | a filled diamond | |
452
- | `odiamond` | an outlined diamond | |
453
- | `bar` | a stroke across the line | `\|` |
454
- | `none` | nothing | |
474
+ ### A stack of cards: `layout: deck`
455
475
 
456
- The `o` in front means outlined, for every shape. A glyph that points is spelled the way it points, which is why `<->` reads as it does. So these are all the same arrow, and you write whichever reads best to you:
476
+ 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:
457
477
 
458
478
  ```
459
- edge a o--> b
460
- edge a odot--arrow b
461
- edge a (odot)--(arrow) b
462
- edge a [odot] -- [arrow] b
479
+ node drives "" layout: deck cards: "Drive 1" "Drive 2" "Drive 3"
463
480
  ```
464
481
 
465
- A mark touches the dashes or is in brackets. `edge a -- dot b` is an error, because it reads just as well as a plain line to a node named `dot`.
466
-
467
- **Marks as attributes.** `from-mark:` and `to-mark:` say the same thing, one end at a time, and they are how a style carries marks:
482
+ 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:
468
483
 
469
484
  ```
470
- style inherits to-mark: oarrow
471
- edge dog -- animal style: inherits
472
- edge cat -- animal style: inherits
485
+ node drives "" layout: deck
486
+ node drives.1 "Drive 1" layout: column
487
+ node drives.1.backup "backup"
488
+ node drives.2 "Drive 2"
489
+ node drives.3 "Drive 3"
473
490
  ```
474
491
 
475
- **An end with nothing written is left open, not empty.** `dog -- animal` is plain until something says otherwise, and here the style does. `none` is the word for "nothing at this end, whatever the style says": `edge a none--> b`, or `from-mark: none`.
476
-
477
- **A mark written in the arrow is final.** The edge's own `to-mark:` may repeat it but not contradict it — `edge a --> b to-mark: dot` is an error, since both halves are on one line and one of them would be dead text. A style fills only the ends the arrow leaves open: a style giving every end a `bar`, applied to `a --> b`, draws a bar at `a` and still the arrow at `b`.
478
-
479
- Endpoints may be nested (`desktop1.files`). An edge never says where a node goes and routing is the renderer's problem, with one exception: an edge with text claims room in the gap it crosses, which is the next section.
492
+ `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.
480
493
 
481
- An edge's text breaks on ` / ` exactly as a node's does, and the block centers on the point the text would otherwise have occupied, so ``"run `deploy` / shell command"`` stacks its two lines around the midpoint of the line rather than running off along it. An edge's text takes the same brackets a node's does, less `at`, so `(wrap: 20)` folds it and `(color: theme-muted)` quiets it.
494
+ 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.
482
495
 
483
- ### A text makes room for itself
496
+ ### Where the children sit
484
497
 
485
- Putting something between two nodes is what pushes them apart, and a text drawn in a corridor is something in that corridor. So an edge with text widens the gap it crosses by what its text needs — the text, a run of line either side of it, and the arrowhead that covers part of that run — and by no more than that.
498
+ `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.
486
499
 
487
500
  ```
488
- node parser "Parser"
489
- node resolver "Resolver" right of parser
490
- edge parser -> resolver "statements" from: right to: left
501
+ node box "Services, gathered left" gather-children: left layout: column (align-children: right)
502
+ node box.a "a"
503
+ node box.b "a longer one"
491
504
  ```
492
505
 
493
- Nothing there says how far apart those two nodes are. The default gap is sized for two nodes to breathe rather than to hold a word, so without this the text would be drawn across both of them. Delete the text and the gap closes back to the default. Write `gap: wide` on that placement and nothing further happens, because the minimum you asked for is already the larger of the two — a gap is a minimum, and a text is one more thing bidding into it.
494
-
495
- Which gap the text lands in is derived, never stated. Two nodes clear of each other on exactly one axis have exactly one corridor between them, and that is the one that widens. It is judged where the nodes finally sit — after anything centered has been centered and anything overlapping pulled apart — and if widening a gap moves something that was centered on what moved, it is centered again. The same holds between the children of a node, however they are placed. Two sitting corner to corner have no single corridor, because the line runs diagonally through open space, so nothing is widened for them. The room is measured along the run: an edge traveling horizontally needs the text's width, one traveling vertically needs only its depth, so a long text across a vertical gap opens it by a single line and hangs out either side.
496
-
497
- An edge with no text asks for nothing, since every gap is wide enough for an arrowhead. An edge carrying a `between` clause asks for nothing here either — its text rides in the channel it named rather than in the gap between its own two ends, and what that does *not* do yet is at the end of the next section but one.
506
+ That is a column with its right edges even, against the box's left side.
498
507
 
499
- ### Which side an edge leaves and arrives on
508
+ ### A heading
500
509
 
501
- `from:` and `to:` name a side of the node at each end — `top`, `bottom`, `left` or `right`.
510
+ 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.
502
511
 
503
512
  ```
504
- edge desktop1.files <-> hub from: right to: top
513
+ node d1 heading: d1.head (inside top, align: left) layout: column
514
+ node d1.head "" shape: none layout: row (gap: tight)
515
+ node d1.head.icon icon: desktop
516
+ node d1.head.name "Desktop 1" (size: normal) shape: none
517
+ node d1.files "shared folder"
505
518
  ```
506
519
 
507
- That line leaves the right side of `desktop1.files` heading right, and arrives at the top of `hub` heading down. Naming a side is a statement about how the line should leave or arrive, so the edge is drawn as a curve that actually does. An edge naming neither side is a straight line from center to center, unless a box is in the way (see "Going round boxes"). Either end may be named on its own; the unnamed one aims at wherever its partner ended up.
520
+ 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.
508
521
 
509
- You name a side and never a point on it. Alone on a side, an edge lands at its center. Sharing a side with other edges, the attachments space themselves apart, and which one goes where is derived from where the far ends actually sit — of two edges arriving at one top side, the one coming from further left arrives further left. Move a node and the order follows it. This is the same rule as nodes not overlapping: the tool separates things by default and reads the direction off the solved layout rather than asking you.
522
+ ### Joined to the text
510
523
 
511
- The space it leaves is deliberately small, and shrinks further if the side is too short to hold the whole group. On a side short enough, it shrinks to nothing and the attachments coincide. Their texts still come apart, because the lines bow in the middle to make up what the side could not give them, but the arrowheads themselves land on one point and nothing warns you — so a small node with several edges arriving on one side is worth a look.
512
-
513
- #### Going round boxes
514
-
515
- A line never passes through a box. It takes the shortest way between its ends that goes through none, leaving a named side heading straight out and arriving at one heading straight in, with as few turns as that length allows. When the direct line is clear, that is the line you get. When it is not, the line goes round whatever is in the way. A line you have steered with `between` or `below <node>` finds its way the same way, keeping to what you said as well.
524
+ `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`.
516
525
 
517
526
  ```
518
- node parser "Parser"
519
- node resolver "Resolver" right of parser
520
- node renderer "Renderer" right of resolver
521
- edge parser -> renderer "test edge" from: left to: right
527
+ node tall "Top" height: 8
528
+ node tall.disk icon: disk 1 right of tall text top even with tall text join-with: tall text
522
529
  ```
523
530
 
524
- That line leaves Parser heading left and has to arrive at Renderer from the right, so it must turn back on itself somewhere. The shortest way is out of Parser's left, over the top of the row, and down into Renderer's right. Under the row would be exactly as long, and when two ways round tie, the line goes over the top, or round the right in a column. A node stepped down far enough leaves a gap between the two, and the line turns back through the gap, because that is shorter.
525
-
526
- The same rule covers every other case: sides at right angles where one faces away from the other end, two ends naming the same side with one node behind the other, a node back to itself, and a line with no side named that has a box between its ends. Each goes the shortest way round. An end with no side named leaves or arrives by whichever of its box's sides makes the way shortest.
527
-
528
- Several lines along the same stretch take a lane each, side by side, instead of drawing on top of one another, nested so that none crosses another where it need not. Where one of them carries its text on that stretch, the others keep clear of the text as well as the line. The text rides on the line's longest straight piece that it does not land on a box from, and never changes which way the line goes.
531
+ 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.
529
532
 
530
- A curved line and a square one take the same way. The square one turns at corners. The curved one sweeps through them, each sweep as wide as the room around it allows without coming near a box or another line, and a line stepping sideways and carrying on the way it was going is one S. Where the room is tight a sweep comes close to a rounded corner; where there is room it flows. At an arrowhead or any other mark, the line always finishes straight, so the mark points square into the side.
533
+ ### Against its own parent
531
534
 
532
- When there is no way at all — the side you named is flush against another box — the file is refused with the reason: `edge a -> b: cannot leave a's right side, b is against it`.
535
+ 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. If it covers something, that is reported. To make the box hold something, place it relative to the other children. 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.
533
536
 
534
- To send a line round the other way, say which side of a node it passes: `below resolver`. See "Passing on one side of a node".
537
+ 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.
535
538
 
536
- #### Several edges between the same two sides
539
+ ### Invisible containers
537
540
 
538
- Edges that run between the *same* pair of sides are a case of their own, because "where the far ends sit" cannot order them: every one of them goes to the same node.
541
+ `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.
539
542
 
540
543
  ```
541
- edge gateway -> queue "publishes messages" from: bottom to: left
542
- edge queue -> gateway "receives messages" from: left to: bottom
543
- ```
544
+ node cluster "" shape: none
545
+ node cluster.hub "Cloud sync"
546
+ node cluster.desktop1 "Desktop 1" above-left of cluster.hub (gap: tight)
547
+ node cluster.laptop1 "Laptop 1" below-left of cluster.hub (gap: tight)
544
548
 
545
- Both of those join the bottom of `gateway` to the left of `queue`, so they run in nested lanes. Whichever lane an edge takes on one side, it takes the matching lane on the other — further left along `gateway`'s bottom is further down `queue`'s left — so the lines never cross each other. Any number of edges between one pair of sides nests the same way.
546
-
547
- Which edge takes the outer lane is derived where the diagram says anything: two edges pointing opposite ways each keep to one side of their own run, so a reciprocal pair reads as a circulation and re-ordering the two lines changes nothing. Edges pointing the *same* way give the tool nothing to read, and there the order you wrote them in is what decides.
549
+ node server "Home Server" left of cluster (gap: wide)
550
+ ```
548
551
 
549
- The lanes are as wide as the texts riding in them, so texts come apart with the lines. Note which way that pushes: on a horizontal run the texts stack, a line apart, but on a vertical run they all sit at the same height and have to clear each other sideways, so each lane is a whole text wide.
552
+ `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.
550
553
 
551
- A side too short to hold the whole group is squeezed, exactly as above — and the room the ends could not give is then made up in the middle, each line bowing across its run by its own share of the shortfall. The captions come apart even where the attachments are packed together. A group that fits its sides is drawn exactly as it was, because the shortfall is nothing.
554
+ ## Sizes
552
555
 
553
- #### Several edges with no side named at all
556
+ 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)).
554
557
 
555
- Edges between the same two nodes that name no side anywhere have the same problem in a harder form: an unnamed end has no side to be spread along. It aims at the far node's center and attaches wherever that ray crosses the border, so every edge between one pair produces the same point, and three of them come out as one visible line with three texts stacked on it.
558
+ - **A number**, in the file's unit: `width: 30`.
559
+ - **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`.
560
+ - **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.
561
+ - **The larger or smaller of two**: `height: larger of a and b`, `smaller of a and b`.
562
+ - **A span**: `height: a top to c bottom`.
563
+ - **Limits**, each its own key: `min-width:`, `max-width:`, `min-height:`, `max-height:`.
556
564
 
557
565
  ```
558
- node a
559
- node b right of a
560
- edge a -> b "first"
561
- edge a -> b "second"
562
- edge a -> b "third"
566
+ node menu "Menu" layout: column
567
+ node menu.a "Home"
568
+ node menu.b "Settings"
569
+ node menu.c "Sign out"
570
+ node panel "Content" right of menu height: menu width: 2 menu width
563
571
  ```
564
572
 
565
- Where an edge would attach is not changed by there being others. What changes is only that they no longer do it in the same place: each takes the line it would have drawn alone and moves it sideways, across its own run, by a lane. The lines are parallel and a lane apart, and where each end lands falls out of that. With the two nodes level, all three attach further up and down the same two sides. With them on a diagonal — where a single line would leave through a corner — the two lines straddle it, and one end lands on each of the two sides meeting there. Neither of those is a case you have to know about; they are the same rule seen from two positions.
566
-
567
- Lane order follows the rule above: opposite-pointing edges keep to their own side of the run, same-pointing ones fall back to the order you wrote them in. A lone edge is in no group and is untouched, and naming a side on either end takes an edge out of the group, since it then has a side of its own to be spread along.
568
-
569
- Lanes are as wide as the texts riding in them, as with named sides — so two edges between the same two nodes come apart far enough for their captions to clear.
570
-
571
- Past a point they cannot: every line still has to attach on the same two sides, and those are only as tall as the nodes. When the group wants more room than the edges can give, the attachments squeeze evenly to fit and each line makes up the shortfall in the middle, bowing across its own run by exactly what its ends could not give it. The texts ride at the midpoints, so they still come apart even though the arrowheads crowd together. The innermost line has no shortfall and stays straight, which is what every group small enough for its nodes looks like.
573
+ `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.
572
574
 
573
- The bow is the shortfall, not a style — nothing bends until the side is full, and a group that fits is drawn with straight lines exactly as it always was.
575
+ ## A node's text
574
576
 
575
- ### Passing between two things
577
+ ### Its bracket
576
578
 
577
- `between <a> and <b>` says that the line travels down the gap between two named nodes.
579
+ Everything a text says about itself goes in brackets right after it:
578
580
 
579
581
  ```
580
- edge dumps.db1 -> hub "rclone" between desktop1 and laptop1 from: right to: left
582
+ node aside "Written nightly." (size: small, wrap: 30) shape: none
581
583
  ```
582
584
 
583
- It says nothing about the rest of the line. The clause binds only the stretch where the line is actually passing that pair — where it enters the span the two of them occupy, it is in the gap between them, and before and after it goes wherever its ends take it. The line takes the shortest way into the gap that goes round every box and keeps its distance from them, runs straight along it, and takes the shortest way out to its far end, as any line does (see "Going round boxes"). Its text rides in the gap.
584
-
585
- Which gap is meant is usually derived, not stated. One of the two is above the other, or one is left of the other, and whichever it is says which axis the gap binds — so a channel between something above and something below constrains height, and one between something left and something right constrains width, and in neither case does the file mention an axis at all.
586
-
587
- Two nodes sitting diagonally have *two* gaps between them, and there the derivation has nothing to go on. Add `vertically` or `horizontally`:
588
-
589
- ```
590
- edge c -> d "threaded" between a and b vertically
591
- ```
585
+ | setting | takes |
586
+ |---|---|
587
+ | `color` | a color or a theme color |
588
+ | `size` | `small`, `normal` or `large`; a multiple of normal (`1.2`); or pixels (`16px`) |
589
+ | `wrap` | a count of characters to fold at, on top of ` / ` |
590
+ | `align` | how the text's own lines range: `left`, `center`, `right`, or where along a side for a title (below) |
592
591
 
593
- `vertically` is the gap you measure with a vertical ruler — under the upper one, over the lower one — so a line running along it travels horizontally. The word describes the gap, not the direction of travel, which is the reading to watch for.
592
+ `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.
594
593
 
595
- Leave it out on a diagonal pair and the error asks for it, in your own node names. Write it where it was not needed and it is checked rather than quietly dropped, so a pair that is only apart vertically will tell you that `horizontally` is wrong. A pair that touches or overlaps has no gap at all, whatever you write, and naming a pair the edge never actually passes is an error too.
594
+ 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.
596
595
 
597
- Several edges may share one channel, and they take a lane each, found as every line's lane is (see "Going round boxes"): side by side, arranged so they do not cross where they need not. The lanes are spaced by what is actually running along them: clear of a text where an edge with text runs, an arrow's width apart where none does.
596
+ ### Where it sits
598
597
 
599
- A named channel does not widen. It is measured off the layout you described, so if you name a gap too narrow for the lines you put through it they crowd together rather than pushing the two nodes apart. That is the difference between this and a text making room for itself, above: there, the corridor is the gap between the edge's own two ends, and opening it moves them apart exactly as anything else put between them would. Here the pair is named by an edge merely passing through, and nothing yet lets an edge bid into a gap it is only a visitor in. It is the remaining half and it is not built.
598
+ 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:
600
599
 
601
- ### Passing on one side of a node
600
+ | written | the title sits |
601
+ |---|---|
602
+ | `(inside top)` | along the top inside the border; the default |
603
+ | `(on top)` | on the border line, breaking it, like a form's legend |
604
+ | `(outside top)` | just above the border |
605
+ | `(center)` | in the middle of the box, as a leaf's does |
602
606
 
603
- `above`, `below`, `left of` and `right of`, written on an edge, say which side of a node the line passes.
607
+ `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`.
604
608
 
605
609
  ```
606
- node parser "Parser"
607
- node resolver "Resolver" right of parser
608
- node renderer "Renderer" right of resolver
609
- edge parser -> renderer "underneath" from: left to: right below resolver
610
+ node hub "Hub" (inside top, align: left) layout: row
611
+ node hub.a
612
+ node hub.b
613
+ node legend "Legend" (on top) right of hub
614
+ node legend.a "solid: synced" shape: none
610
615
  ```
611
616
 
612
- They are the placement words, and they mean what `between` means: only where the line passes that node. `below resolver` says the line is below Resolver where it goes by Resolver, not that the whole line is. That edge would go over the top of the row on its own; with the clause it leaves Parser, drops under the row and comes up into Renderer.
613
-
614
- Write as many as you need, one for each node you have something to say about. The line meets them in the order they sit along its way, so no clause says an order:
617
+ **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`.
615
618
 
616
619
  ```
617
- node a "A"
618
- node b "B" below a
619
- edge a -> b from: top to: bottom left of a right of b
620
+ node r "Deployment" (right even with r.d, top even with r.a)
621
+ node r.a "north-west"
622
+ node r.d "south-east" below-right of r.a
620
623
  ```
621
624
 
622
- That line leaves the top of A, goes round A's left, crosses over in the gap between the two, passes B on the right, and turns up into B's bottom. It finds its way as any line does (see "Going round boxes"): each clause closes off one side of its node — `below resolver` everything above Resolver, within Resolver's width — and the line takes the shortest way that keeps out of those as well as out of every box. Lines told the same things take a lane each rather than one path between them.
623
-
624
- One clause may name several nodes, and it is not shorthand for several clauses. `below a and b` is below the box bounding both, so it covers the stretch between them too:
625
+ **`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.
625
626
 
626
627
  ```
627
- node t1 "T1"
628
- node t2 "T2" right of t1 (gap: wide)
629
- node peak "Peak" right of t1 left of t2 above t1
630
- edge t1 -> t2 from: bottom to: bottom below t1 above peak below t2
628
+ node rack "Rack 1" (inside left, reads: up) layout: column
629
+ node rack.a "web-1"
630
+ node rack.b "web-2"
631
631
  ```
632
632
 
633
- Those three clauses let the line rise over Peak in the gap. Write `below t1 and t2 above peak` instead and it is refused, because Peak sits over the stretch `below t1 and t2` covers, and the line cannot be below the one and above the other at the same point.
633
+ `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.
634
634
 
635
- One edge may pass some nodes above or below and others left or right. Passing above or below is done travelling across the page, and passing left or right travelling up or down it, so the line turns between the two, as few times as it can:
635
+ ### Markup
636
+
637
+ A stretch of text can borrow a style's look:
636
638
 
637
639
  ```
638
- node a "A"
639
- node n "N" below a
640
- node m "M" below n
641
- node b "B" right of m (gap: 80)
642
- edge a -> b left of n above m
640
+ style dim text: (color: theme-muted)
641
+ node files "shared folder / [dim]synced[/dim]"
643
642
  ```
644
643
 
645
- With no clauses that line would run straight from A to B. `left of n` sends it down N's left, and `above m` brings it across between N and M into B, rather than round under M. The clauses read as before: `left of n` binds only where the line is level with N, so the stretch running across below N is not held to it.
644
+ 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.
646
645
 
647
- A side named at an end says which way the line goes first: `from: right` starts it across, `from: bottom` starts it down. With no side named, the line takes whichever way keeps the clauses with the fewest turns, and a straight line that already keeps them stays straight.
648
-
649
- Clauses of one kind may sit on different legs. Here the line goes over P on the way out, down past N, and under M on the way in, turning twice:
646
+ ## Lines
650
647
 
651
648
  ```
652
- node a "A"
653
- node p "P" right of a
654
- node n "N" below p right of p
655
- node m "M" below n right of n
656
- node b "B" right of m
657
- edge a -> b from: right to: bottom above p right of n below m
649
+ edge [<name>] <from> <arrow> <to> ["<text>" [(<text settings>)] ...] [<attribute>] ...
658
650
  ```
659
651
 
660
- Each clause binds whichever legs pass its node, so a node wide enough to sit under two legs holds both of them to its side.
652
+ ### The ends and their marks
661
653
 
662
- Refused, each by name:
654
+ ```
655
+ edge a -> b // an arrow at b
656
+ edge a <- b // an arrow at a
657
+ edge a <-> b // one at each end
658
+ edge a -- b // a plain line
659
+ ```
663
660
 
664
- - **Clauses that cannot all hold** at one point, as above.
665
- - **A crossing with no room.** Two clauses on opposite sides need a gap between their nodes to cross over in. Placed flush — `gap: 0` — there is none, and the error says to give the placement a gap or drop a clause. The gap is never opened for the line: you wrote how far apart those two are.
666
- - **A node the line never passes.** `below z` on a line that never goes by Z says nothing.
667
- - **No way keeps them all.** Where the clauses and the boxes between them leave no way through, the error names every clause.
668
- - **`between` with a side.** An edge passing between two things already has a side of each.
669
- - **A part of a node, or a gap.** A line passes a whole box, and passes it as close as reads clearly.
661
+ 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:
670
662
 
671
- An end with no side named leaves by whichever side makes the way shortest.
663
+ | mark | draws | glyph |
664
+ |---|---|---|
665
+ | `arrow` | a filled arrowhead | `>` on the right, `<` on the left |
666
+ | `oarrow` | an outlined arrowhead | `\|>` on the right, `<\|` on the left |
667
+ | `dot` | a filled circle | `*` |
668
+ | `odot` | an outlined circle | `o` |
669
+ | `diamond` | a filled diamond | |
670
+ | `odiamond` | an outlined diamond | |
671
+ | `bar` | a stroke across the line | `\|` |
672
+ | `none` | nothing | |
672
673
 
673
- ### How the line is drawn
674
+ 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`.
674
675
 
675
- Everything about the line itself goes in its bracket:
676
+ `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.
676
677
 
677
678
  ```
678
- edge api -> db line: (path: square, corners: rounded)
679
- edge api -> cache line: (pattern: dashed, thickness: thin)
680
- edge web -> api line: (color: #d2904e, thickness: thick, crossing: arc)
679
+ style inherits to-mark: oarrow
680
+ node animal
681
+ node dog below-left of animal
682
+ node cat below-right of animal
683
+ edge dog -- animal style: inherits
684
+ edge cat -- animal style: inherits
681
685
  ```
682
686
 
683
- `line: red` is short for `line: (color: red)`, and is still the way to write a color alone. Every other property needs the bracket, and `line: square` is refused with the bracket it meant. Like any attribute, the bracket may go on an edge, in a style, or on `default edge` — `default edge line: (path: square, corners: rounded)` draws a whole diagram with right angles.
687
+ **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.
684
688
 
685
- | property | takes | says |
686
- |---|---|---|
687
- | `color` | a color | as `line:` alone |
688
- | `path` | `curved` (the default), `square`, `straight` | how the line joins the points it has to meet |
689
- | `corners` | `sharp` (the default), `rounded` | how a square or straight line turns |
690
- | `crossing` | `gap` (the default), `none`, `arc`, `square` | how the line is drawn where it crosses an earlier one |
691
- | `pattern` | `solid` (the default), `dashed`, `dotted`, `dash-dot` | |
692
- | `thickness` | `thin`, `normal` (the default), `thick`, or a number of pixels | |
689
+ **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.
690
+
691
+ **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`.
692
+
693
+ ```
694
+ node c
695
+ node d right of c (gap: wide)
696
+ point a 2 below c bottom-center
697
+ point b 2 above d top-center
698
+ region pair c top-left to d bottom-right
699
+ guide edge rail a -> b
700
+ edge c -> d via: rail
701
+ edge pair -> a
702
+ ```
693
703
 
694
- **A path is the rule for joining what the file fixed**: where the line leaves and which way it heads, where it arrives and from which direction, and any sides it passes. Out of A's top and into B's left, with B up and to the right:
704
+ **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.
695
705
 
696
- - `curved` bends round from heading up to heading right. It is how every line was drawn before there was a choice, including the straight line an edge naming no sides has always been.
697
- - `square` goes up, then across into B — one right angle, because leaving upward and arriving from the left fixes which way it turns. A square line naming no sides picks them: boxes sharing a column join top to bottom, boxes sharing a row side to side, and otherwise the line goes across first, then down. An end naming no side, beside one that does, takes whichever side needs the fewest turns.
698
- - `straight` is one straight piece from A's top to B's left, ignoring which way each faces. Given `below c` or `between a and b`, it bends where that clause puts a point it has to pass, and nowhere else.
706
+ ### Where a line ends
699
707
 
700
- **Corners** apply wherever a line has them: every bend in a square line, and the bends a clause puts in a straight one. A curved line has none, so `corners: rounded` written on a curved edge is refused. From a style or a default it rounds the edges that have corners and leaves curved ones alone, as a style's `fill:` lands only on the things that have an inside.
708
+ `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.
701
709
 
702
- **A crossing is drawn on the later line**: the edge written further down the file jumps the one written above it. `gap`, the default, is a short break in the later line, as though it passed underneath; `arc` is a small half-circle hop, `square` a three-sided jump, and `none` draws the two lines straight across each other. A line never jumps within reach of its own ends, where two lines meeting at a box are not passing each other.
710
+ **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`.
703
711
 
704
- **A pattern is measured in thicknesses**, so dashes keep their proportions on a thick line.
712
+ 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.
705
713
 
706
- **A thick line takes room.** Its arrowhead grows with it, and the room an edge's text makes for itself counts the arrowhead it will actually have, so thickening a line with text between two close nodes can push them apart — the same way a larger text grows its node. Color, path, corners, crossings and pattern move nothing.
714
+ `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.
707
715
 
708
- ## Against a part of a node
716
+ **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).
709
717
 
710
718
  ```
711
- <direction> of <node> <part>
712
- inside <node> <part>
713
- outside <node> <part>
714
- on <node> <part>
719
+ node bin height: 12
720
+ node a left of bin top even with bin
721
+ node b below a (gap: tight)
722
+ node engine right of bin even with bin height: 12
723
+ edge a -> bin straight right
724
+ edge b -> bin straight right
725
+ edge bin -> engine from: right, even with a to: left, even with a
726
+ edge bin -> engine from: right, even with b to: left, even with b
715
727
  ```
716
728
 
717
- A placement's target may be a node, as everywhere else, or **a part of a node** — written as the node's name and the part as a separate word. The parts are a node's `text`, its four sides `top`, `bottom`, `left` and `right`, and its nine points:
729
+ **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:
718
730
 
719
731
  ```
720
- top-left top-center top-right
721
- left-center center right-center
722
- bottom-left bottom-center bottom-right
732
+ node a right-edge-order: ae, ad, ac
733
+ edge ae a -> e from: right
734
+ edge ad a -> d from: right
735
+ edge ac a -> c from: right
723
736
  ```
724
737
 
725
- Those nine are the whole set, and every part of the language that has a position accepts all of them. They are words anybody can point at without measuring, which is what makes them allowed where `x: 140` is not — and a diagram written in them still moves correctly when a box moves, which is the property that refusal exists to protect.
738
+ 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.
726
739
 
727
- **A side is a segment and a point is a point,** and the spelling is what says which. Bare `top`, `bottom`, `left` and `right` name a side, which is why every midpoint carries `-center`: `right` is the whole right edge, `right-center` is the one point halfway down it. Naming a side leaves the other axis free, so `inside plate right` sits against that edge and centers down it; `inside plate right-center` pins it to the midpoint.
740
+ **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.
728
741
 
729
- Three direction words go with a part target:
742
+ **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.
730
743
 
731
- | | |
732
- |---|---|
733
- | `inside` | wholly within, against that part |
734
- | `outside` | wholly beyond it |
735
- | `on` | centered on it, so a node on a corner straddles it |
744
+ **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.
736
745
 
737
746
  ```
738
- node bob "Bob the builder"
739
- node bob_link "bob.example.com" inside bob bottom-center
747
+ node bus "bus" width: 40
748
+ node d1 above bus (gap: wide)
749
+ node d2 right of d1 even with d1
750
+ edge d1 -- bus straight down
751
+ edge d2 -- bus straight down
740
752
  ```
741
753
 
742
- **`inside` and `outside` are shorthands, and their long form is derived rather than listed.** *Inside* is the direction from the named part toward the box's center and *outside* is away from it, so `inside right` is `left of`, `inside top-right` is `below-left of`, and one rule covers every part. `inside <node> center` and `inside <node> text` are errors: neither part is on the boundary, so there is no direction toward the interior from them.
754
+ ### A line is straight until told
743
755
 
744
- **`on` is the one that is not a shorthand.** It is a center alignment on both axes at once, which the language did not otherwise have — `level with` gives the vertical and the edge alignments give whichever axis their edge belongs to, and there is no horizontal center alignment at all. It takes no gap: a center sits on a point rather than a distance from it.
756
+ `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.
745
757
 
746
- The inset for `inside` is a gap on the placement. If nothing says, it is `tight` against another node's box and the box's own padding against the node's own parent — see [Against its own parent](#against-its-own-parent). `(gap: none)` puts the node hard against the edge. It is deliberately *not* the node's own `gap:`, which says how the node stands off its neighbours — a node marked `gap: wide` so its siblings keep clear should not thereby wear its mark 110 pixels in from the corner.
758
+ `line: (path: straight)` is the default; `square` and `curved` are opt-in.
747
759
 
748
- **Saying `inside` or `on` is saying the overlap,** so there is nothing for the overlap error to report about that pair. Everything else in the drawing still keeps clear in the ordinary way.
760
+ **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.
749
761
 
750
- `inside`, `outside` and `on` take one target. A direction may name several — `right of a and b` means "clear of the box bounding both", which is a floor and decomposes into one demand per target — but these three read a direction off one part of one box.
762
+ - `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.
763
+ - `step-out:` is the short first and last leg, `normal` by default: `line: (path: square, step-out: tight)`.
751
764
 
752
- ### Against its own parent
765
+ **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.
753
766
 
754
- A child may name a part of the node it is inside. **The dotted name is what decides what happens:** a child grows its parent to hold it, and a stranger does not.
767
+ 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.
755
768
 
756
769
  ```
757
- node server "Server"
758
- node server.web "web"
759
- node server.db "database"
760
- node server.mark "!" inside server right level with server.db
761
- node stamp "!" on server top-right
762
- ```
763
-
764
- `server` widens so `server.mark` sits against its right edge, held in by the padding, on the database's row. A side word straight after `inside`, `outside` or `on <node>` is always the part, so `right level with` here is not read as the right-edge alignment. `stamp` is not part of `server`, so nothing moves for it: it lies over the corner, half in and half out.
765
-
766
- The title band is not something a node has; it is what happens when things stack below the text. Children that say nothing about where they go stack below the text in written order, as they always have, and that is what makes the band. A node whose children all sit beside its text or against its frame has nothing below its text, so it has no band and draws as a leaf — its text centered, and whatever is placed against the text centered with it as one group:
767
-
768
- ```
769
- node hub "Cloud sync"
770
- node hub.star "★" right of hub text
770
+ node a
771
+ node b below-right of a (gap: wide)
772
+ edge a -> b from: right to: left line: (path: square)
773
+ edge a -> b from: bottom to: top line: (path: curved)
771
774
  ```
772
775
 
773
- **A child against its own parent is spaced as that parent spaces what it holds.** `inside` insets it by the padding, a placement against the parent's text stands off by the step the contents are stacked by, and the parent's own `gap:` — which says how the parent stands off its neighbours — does not reach it. A gap written on the placement still wins.
776
+ ### Waypoints
774
777
 
775
- To give a node a band on purpose, place things below its text. The band is the same one the contents would have made — title at the top, what was placed beneath it one step below:
778
+ `via:` lists the places a line passes, in order:
776
779
 
777
780
  ```
778
- node q "Deployment"
779
- node q.a "north-west" below q text inside q left
780
- node q.b "north-east" below q text inside q right
781
- node q.c "south-west" inside q bottom-left
782
- node q.d "south-east" inside q bottom-right
781
+ node a
782
+ node b right of a
783
+ node c right of b
784
+ edge a -> c from: top to: top via: b top
783
785
  ```
784
786
 
785
- A text given an `at:` in a node with no band ranges in the room its own row and column leave it — between whatever sits beside, above or below it — not across the whole box, so a `top-right` title stops short of a child in the top-right corner rather than pushing it out.
787
+ - **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.
788
+ - **A point is one spot**: `via: b top-center`, `via: server right 3/4 down`.
789
+ - **A box's name goes through its center**: `via: proxy`.
790
+ - **Any point**: `between c and d at 1/3`, `down from a and left from b`, a named point, `req at 1/3`.
791
+ - **Through a region**: `via: through slot horizontal` runs from the region's left edge to its right. The axis is always written.
792
+ - **Alongside a named leg**: `via: above thru (gap: tight)` runs the whole length of `thru`, a tight gap above it. The side is required.
786
793
 
787
- A child placed against one of its parent's sides stands beside the rest of the box, not above or below it: if it would cover the title or the contents, the box grows across for a left or right side and down for a top or bottom one. `overlap: allow` on the child is the other answer — "I am inside my parent, do not grow for me" — and it lies over whatever is there.
794
+ 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.
788
795
 
789
- **A child placed `outside` its parent is still part of it.** The parent does not grow for it, but everything that keeps clear of the parent keeps clear of the child too: `right of router` lands beyond the note hanging off Router's right side, not on top of it, and a container holding Router widens to hold the note. An alignment still reads the parent's own box, so `below router` centers under Router and not under Router and its note together.
796
+ **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`.
790
797
 
791
- ### Several nodes saying the same thing
798
+ 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.
792
799
 
793
- ```
794
- node hub "hub"
795
- node a "one" right of hub
796
- node b "two" right of hub
797
- node c "three" right of hub
798
- ```
800
+ ### Legs
799
801
 
800
- Three nodes that say the identical thing are one list, not three boxes on one spot. It runs down the page in the order they were written, and is centered on `hub` as a whole — the balanced picture that "hub points at three things" means, which no chain of placements can draw. The same goes for a part: three children `inside p right` are a column against that edge, and three at `inside p top-right` stack into the corner and grow down. The list runs down the page whatever the direction, so three nodes `below hub` are a column under it.
802
+ 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.
801
803
 
802
- Only the identical placement makes a list; add `level with x` to one of them and it places itself. `overlap: allow` asks for the literal pile instead.
804
+ ### Points on a line
803
805
 
804
- **A direction and a position are two vocabularies and stay two.** A *direction* is a relation between two nodes and puts this one outside the other, clear of it by a gap: `above-left of hub`. A *position* is a point of one box: `inside hub top-left`. They reach the same corner with different words on purpose, because `above hub` could never become `top of hub` — "the top of the hub" is unambiguously its edge.
806
+ 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.
805
807
 
806
- ## Notes and other bare text
808
+ ### Copies fan out
807
809
 
808
- There is no `note` statement. A note is a node with no body:
810
+ 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 whose landing is stated, such as `to: top, right of z`, is no longer a copy, which is how to override the fan.
809
811
 
810
812
  ```
811
- node aside "Written by a nightly cron job." (wrap: 30) shape: none below dumps gap: tight
813
+ node client
814
+ node api right of client (gap: wide)
815
+ edge client -> api "request"
816
+ edge api -> client "response"
812
817
  ```
813
818
 
814
- A keyword names a picture, and "note" names a use. The picture is *text with no box*, and that serves plenty of uses which are not asides — a caption on a brace, a title over a diagram. So the keyword went and the picture stayed. An older file writing `note` gets an error quoting the replacement.
819
+ ### Bundles
815
820
 
816
- Nothing bounds bare text the way a border bounds a node, so a sentence-length one without a `wrap` is drawn as one very long line and will cross whatever is beside it. Give every one a wrap.
817
-
818
- `shape: none` starts one step smaller than a node's text, because an aside at the same size reads as a statement. That is a default, not a ceiling: say `(size: …)` and it does what you said.
819
-
820
- ## Decks
821
+ 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.
821
822
 
822
823
  ```
823
- node drive "Drive 1" deck: "Drive 2" "Drive 3"
824
+ node app1 "App 1"
825
+ node app2 "App 2" below app1 (gap: wide)
826
+ node web "Web" right of app1 (gap: 12)
827
+ node db "DB" below web (gap: wide)
828
+ guide region slot between web and db
829
+ node hub right of slot (gap: 12)
830
+ bundle trunk via: through slot horizontal
831
+ edge app1 -> hub via: trunk
832
+ edge app2 -> hub via: trunk
824
833
  ```
825
834
 
826
- `deck:` draws offset copies behind a box, one per text, back to front, to say "there are several of these and they are the same." Only the front copy shows its contents. It goes on a box only; a picture or a `shape: none` node has no outline to repeat, and refuses it. A style may carry it.
835
+ The two lines meet the gap between Web and DB, run through it side by side, and leave it together into the hub.
827
836
 
828
- **Changed after 0.3.0.** This was a statement of its own, `deck drive "Drive 2" "Drive 3"`, which created nothing and only said more about a node declared elsewhere. The statement is now an error that quotes the attribute to write instead.
837
+ Its arrangement is `layout:`:
829
838
 
830
- ## Attributes
839
+ | layout | draws |
840
+ |---|---|
841
+ | `parallel` (the default) | members side by side a gap apart, `tight` unless the bracket says, so adding one widens the strip |
842
+ | `collapsed` | a bus: members merge onto the path at its start and split at its end, and the shared stretch is drawn once |
843
+ | `fan` | only on a bundle between two boxes: one curve each, widest apart at the middle |
831
844
 
832
- Every attribute, and what takes one. The kinds here are what a node's **body** is rather than which keyword declared it — every one of the three is written `node`, and each takes a different set.
845
+ **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.
833
846
 
834
- | attribute | `shape:` | `icon:` | `shape: none` | edge | says |
835
- |---|---|---|---|---|---|
836
- | `style` | ✓ | ✓ | ✓ | ✓ | the named bundles to take appearance from, one or a list |
837
- | `gap` | ✓ | ✓ | ✓ | | the default distance to whatever it is placed against |
838
- | `overlap` | ✓ | ✓ | ✓ | | `allow`, to opt out of non-overlap |
839
- | `contents` | ✓ | | | | how the children are sized and where the block of them sits, in brackets |
840
- | `badge` | ✓ | | | | the picture beside the text |
841
- | `deck` | ✓ | | | | offset copies behind the box, one quoted text each |
842
- | `shape` | ✓ | | ✓ | | the outline the node is drawn with, `none` included |
843
- | `icon` | | ✓ | | | the picture the node is drawn as |
844
- | `from` `to` | | | | ✓ | which side the line leaves and arrives on |
845
- | `from-mark` `to-mark` | | | | ✓ | what is drawn at each end — see "What is drawn at each end" |
846
- | `fill` | ✓ | | | | color — see "A color names the part it colors" |
847
- | `border` | ✓ | | | | color |
848
- | `text` | ✓ | ✓ | ✓ | ✓ | the text's properties, in brackets — a style's form of what a node or an edge writes after its own words |
849
- | `line` | | | | ✓ | color, or everything about the line in brackets — see "How the line is drawn" |
850
- | `url` | ✓ | ✓ | ✓ | ✓ | a destination to open when the thing is clicked |
851
-
852
- The `diagram` statement has a vocabulary of its own — `theme`, `background` and `text` — which is checked the same way. Writing `background:` on a node is an error that points at `fill:`, and `theme:` on one points at `diagram theme:`. A `default` takes a narrower list for each kind; see "Defaults".
847
+ **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.
853
848
 
854
- **A word this table does not give the kind is an error.** The two ways of being wrong get different answers, because they have different remedies. A word that is an attribute nowhere is a misspelling, and the error lists what the kind does take. A word that is an attribute *somewhere else* is usually a real statement written on the wrong half of the diagram, so the error says where it belongs:
849
+ **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:
855
850
 
856
851
  ```
857
- "one" is a node and has from: left. `from:` belongs to an edge — a node takes style, gap, ...
852
+ node web "Web tier"
853
+ node ops "Ops" below web (gap: 6) right of web (gap: 4)
854
+ bundle logs web -> ops from: bottom to: top line: (path: square)
855
+ edge web -> ops via: logs
856
+ edge web -> ops via: logs
857
+ edge web -> ops via: logs
858
858
  ```
859
859
 
860
- Some of the gaps in the table are worth saying out loud, because none of them looks like a mistake while you are writing it. A picture and a bodiless node take no `fill:` or `border:` — there is no outline for either to reach. Neither takes `contents:` either, which says how a node's children sit, and neither may have any. `shape:` and `icon:` each appear only on the kind they make, and writing both is an error naming both. An edge takes no `gap:` or `overlap:` — those say where a node sits, and an edge is not placed, it joins two things that are.
860
+ 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.
861
861
 
862
- **A key is written once per statement.** `fill: red fill: blue` is an error that quotes both values and asks you to keep one: two words on one line are equally explicit, so nothing says which was meant, and keeping either would drop the other in silence. It is almost always an edit that forgot to delete the old value. The same holds inside brackets — `(size: small, size: large)`, a placement's `(gap: wide, gap: tight)` — for `style:` written twice, and for a `style` statement that repeats one of its own keys. Two `text: (…)` brackets in one style are fine as long as they say different things. A node overriding its style is not a repeat; see "Styles".
862
+ 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.
863
863
 
864
- **Changed 2026-09-09.** Until then a node or edge attribute the tool did not recognize was parsed, stored and never read: `wibble: red` on a node drew nothing and said nothing. This was the last place in the language where a key could silently do nothing, and the rule everywhere else — an unknown `diagram` key, an unknown placement modifier, a color naming a part the kind has not got — has always been that a key which silently does nothing looks like the tool being broken rather than like a typo. A file that rendered with a stray word in it will now stop with an error naming it.
864
+ ### A line from each child
865
865
 
866
- ## Destinations
866
+ `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.
867
867
 
868
- ```
869
- node docs "Documentation" url: "https://example.com/docs"
870
- edge docs -> store "read first" url: "https://example.com/order"
871
- ```
868
+ ### Lines to several ends
872
869
 
873
- `url:` makes the thing clickable in a viewer that follows links — a browser showing the SVG, or a page it is embedded in. The whole thing is the target: a node's box and everything drawn in it, an edge's text. A rasteriser ignores it, so a PNG is unaffected.
870
+ `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:
874
871
 
875
- **The value is quoted.** Without the quotes everything from the `//` onwards is a comment, so `url: https://example.com` would set the destination to `https:`. The error for the unquoted form says so rather than reporting a missing value.
872
+ ```
873
+ edge a -> b, c, d from: right-center to: left
874
+ ```
876
875
 
877
- **Nothing about a destination is visible.** A clickable node looks like any other. Color was considered and dropped: an edge with no text and a node that is nothing but a picture have nothing to color, so it would be a decoration that sometimes applies — and a destination is *content*, while color is appearance, and nothing else in the language lets one reach the other. An author who wants a destination to look like one writes the color themselves.
876
+ 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.
878
877
 
879
- **A style may not carry one**, for the same reason: a style is a bundle worn by many things, and one `url:` in it would point every one of them at the same place.
878
+ ### Crossings
880
879
 
881
- **A container's destination reaches its children.** A child that names none of its own is clickable with its container's, and one that names its own overrules it inside its own box — so a container catches every click its children do not.
880
+ 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.
882
881
 
883
- **An edge with no text is refused one.** The line is a pixel and a half wide, which is a target nobody can hit; a destination that technically works and practically does not is the silent defect this language refuses everywhere else.
882
+ ### The line's bracket
884
883
 
885
- ## Styles
884
+ Everything about the line goes in its bracket, on an edge, a style or `default edge`:
886
885
 
887
886
  ```
888
- style <name> <attributes>
887
+ edge web -> api line: (color: #d2904e, path: square, corners: rounded, thickness: thick)
889
888
  ```
890
889
 
891
- A named bundle of appearance, applied with `style: <name>` on a node or edge. Color carries meaning through the style name rather than being written per node.
892
-
893
- ```
894
- style backup border: #d2904e
895
- node server.mirror "shared folder / mirror" style: backup
896
- ```
890
+ | property | takes |
891
+ |---|---|
892
+ | `color` | a color |
893
+ | `path` | `straight` (the default), `square`, `curved` |
894
+ | `corners` | `sharp` (the default), `rounded`; wherever a line has corners |
895
+ | `crossing` | `gap` (the default), `arc`, `square`, `none` |
896
+ | `pattern` | `solid` (the default), `dashed`, `dotted`, `dash-dot`; measured in thicknesses |
897
+ | `thickness` | `thin`, `normal` (the default), `thick`, a multiple of normal, or pixels (`3px`) |
898
+ | `curve` | `strong`, `normal` (the default), `subtle`; curved lines only |
899
+ | `turn` | a fraction; square and curved lines only |
900
+ | `step-out` | a distance; square and curved lines only |
897
901
 
898
- The appearance attributes are `fill`, `border`, `line`, `text`, `badge`, `icon` and `shape`. The first three each take a color written as the viewer will receive it — `#142814`, or any CSS color, or `none`; `text` takes the bracket described under "The text and its brackets".
902
+ `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.
899
903
 
900
- **What a statement says itself beats what its style says**, wherever `style:` sits on the line. Here the server's border is red, not orange:
904
+ ## A line's text
901
905
 
902
906
  ```
903
- style backup border: #d2904e
904
- node server "Server" border: red style: backup
907
+ edge user -> order "1" (at: start) "places" "many" (at: end)
905
908
  ```
906
909
 
907
- Whoever wrote `border: red` on the node meant it for that node, so it is an override rather than a contradiction, and nothing is refused. The rest of the style still applies.
908
-
909
- **A thing may take several styles**, listed the way a placement lists its targets — commas and `and` both separate:
910
+ 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.
910
911
 
911
- ```
912
- node pump "Pump 3" style: equipment, critical and alarm
913
- ```
912
+ **Along the line:**
914
913
 
915
- They apply in the order written, and a later style wins where two set the same key; the statement's own words still win over all of them. Each style in the list is checked on its own, so one that gives the thing nothing is refused even when its neighbors do not. A style named twice in one list is refused, and so is `style:` written twice on one statement — the list is the way to say several.
914
+ - `(at: 1/4)`: a fraction along the whole line, bends included, from the first name written.
915
+ - `(on: rise, at: 1/3)`: on one named leg, a fraction along it.
916
+ - `(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.
917
+ - `(at: start)`, `(at: end)`: near an end, clear of its mark, however long the line is.
916
918
 
917
- ### A color names the part it colors
919
+ **By a box:**
918
920
 
919
- A color attribute says which part of a thing it colors, and a part exists only on the kinds that have one:
921
+ - `(straight right from c)`: where a line straight right from `c`'s center first meets this line.
922
+ - `(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.
923
+ - `(above s1, on the line)`: stays on the line, where it first reaches that height. `gap:` sets how far above.
920
924
 
921
- | attribute | colors | on |
922
- |---|---|---|
923
- | `fill` | the area inside the outline | a node |
924
- | `border` | the outline | a node |
925
- | `text: (color: …)` | the text | every node, and an edge |
926
- | `line` | the drawn line and the marks at its ends | an edge |
925
+ **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.
927
926
 
928
- A word written on a kind that has no such part is refused by name, and the error lists the parts that kind does have — `border:` on a node with no body is a mistake, not something to ignore, for the same reason an unknown `diagram` key is.
927
+ **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.
929
928
 
930
- An edge's text takes the line's color unless its own brackets say otherwise, so an edge that means something by being orange means it in its words too, and there is still a way to say the words are not orange.
929
+ **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.
931
930
 
932
- A style contributes a part only to the kinds that have it, so a style shared between nodes and edges writes one key for each:
931
+ `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.
933
932
 
934
933
  ```
935
- style backup border: #d2904e line: #d2904e
934
+ node a
935
+ node b right of a (gap: 16)
936
+ edge a -> b "1" (at: start) "calls" (above edge) "n" (at: end)
936
937
  ```
937
938
 
938
- The nodes take the border, the edges take the line, and neither sees the other's word. Writing only `border` there would color the nodes and leave the edges plain.
939
+ **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.
939
940
 
940
- ### A style may carry what a thing cannot use
941
+ ```
942
+ node a
943
+ node b right of a (gap: 16)
944
+ edge a -> b "beside" (above edge, fill: none) "boxed" (at: 3/4, fill: theme-fill, padding: 0.5)
945
+ ```
941
946
 
942
- The table under "Attributes" is checked against what you wrote *on the statement*, never against what a style handed it. That is what makes a bundle spanning kinds possible at all: the benchmark's `style synced` carries a fill and a border for five nodes and a `line` for the four edges joining them, and every use of it leaves some of its keys unused. That is the style doing its job, not a mistake, so nothing is said about it.
947
+ **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.
943
948
 
944
- What is refused is a style that gives a thing **nothing at all**:
949
+ ## Guides
950
+
951
+ `guide` as the first word makes a `node`, `edge`, `point` or `region` reference-only:
945
952
 
946
953
  ```
947
- style boxy fill: #142814 badge: disk
948
- node n "An aside" shape: none style: boxy
954
+ node a
955
+ node b right of a (gap: wide)
956
+ guide point funnel between a and b
957
+ guide region slot a top-left to b bottom-right
958
+ guide node column "" below a width: 10 height: 20
949
959
  ```
950
960
 
951
- A node with no body is bare text, with neither a fill nor a badge, so `boxy` dresses it in nothing whatever and the name is on the wrong sort of thing. Partial overlap is the normal case; zero overlap is never anything else. A style that named every attribute in the language would slip through this, since it contributes to everything by construction — nobody writes one by accident, and the hole is left open rather than closed with a rule that would fire on `synced`.
961
+ **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.
952
962
 
953
- A style's own keys are checked against the whole vocabulary, since a word that is an attribute of nothing is a misspelling wherever it sits. `style s wibble: red` is an error; a style was the last place one could hide.
963
+ 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.
954
964
 
955
- **Removed: `stroke`.** It named no part — it meant the border of a node, the *text* of one drawn as a picture or with no body at all, and the line of an edge, whichever the thing happened to have. That is coherent one kind at a time and ambiguous read across them; it meant no ink attribute could ever be *wrong*; and it left one thing with no way to be said at all, the color of the text on an ordinary node. An older file carrying it gets an error naming the word to use instead.
965
+ 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.
956
966
 
957
- A color is never written in quotes, and a quoted one is refused. There is nothing to check a color *against* — the tool keeps no list of color words, as below — so this is the one thing that can be checked, and it is the mistake that actually gets made: every attribute that takes a color would otherwise accept a quoted string, find it is not a color, and draw nothing without saying so.
967
+ ## Drawing order
958
968
 
959
- There is no list of color words the tool knows. An earlier version had one, and it was wrong in the way such lists always are: `dark-green` existed only because somebody added it to a map in the renderer, and the next color a diagram wanted would have needed a code change to say. Writing the color directly removes both the list and the reason to grow it. `green` still works, because it is a CSS color, not because this tool has heard of it.
969
+ 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.
960
970
 
961
- A style carrying `text: (color: theme-muted)` is what a marked-up word borrows from, which is how a node carries a name with a quieter qualifier under it:
962
-
963
- ```
964
- style synced fill: #142814 border: #486544
965
- style dim text: (color: theme-muted)
966
- node pc.files "shared folder / [dim]synced[/dim]" style: synced
967
- ```
971
+ `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:`.
968
972
 
969
- **Removed: `subtext`.** It colored every text line after the first, which is a positional slice: the rule lived in a style elsewhere in the file and was applied by counting, so a reader of `"shared folder / synced"` could not see that the second line was quiet. The mark says what is quiet where it is quiet, and reaches a word in the middle of a line, which the slice never could. An older file carrying it gets an error naming the mark to write instead.
973
+ ## Appearance
970
974
 
971
- `badge`, `icon` and `shape` belong in a style for the same reason a color does: they say what kind of thing this is, and a kind wants to look alike everywhere it appears. `style artifact fill: theme-secondary-subtle shape: document` puts the folded corner on every dump in the diagram, and the use site stays one word.
975
+ ### A color names the part it colors
972
976
 
973
- `theme-muted` means the theme's secondary text color rather than a fixed one, so a quiet line stays readable when the theme changes. Writing `#8b8b8b` instead would pin it to one theme. Say nothing and every line of a text reads alike, which is what most texts want — `Computer 1 / Ubuntu` is two lines of one name, not a name and a qualifier, and the distinction is the author's to make rather than the renderer's to guess.
977
+ | key | colors | on |
978
+ |---|---|---|
979
+ | `fill` | the inside of the outline | a box |
980
+ | `border` | the outline | a box |
981
+ | `(color: …)` after a text | the text | any node, an edge; `text: (color: …)` in a style or default |
982
+ | `line` | the line and its end marks | an edge |
974
983
 
975
- ## The diagram itself
984
+ 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. `none` means the part isn't painted: `fill: none`.
976
985
 
977
- ```
978
- diagram <attributes>
979
- ```
986
+ **Changing a color never moves anything.** Size is different: a bigger text or a thicker line takes more room.
980
987
 
981
- Settings that belong to the drawing as a whole rather than to anything in it. There is no name, because a file holds one diagram, and a second `diagram` statement is an error rather than a second opinion.
988
+ ### Styles
982
989
 
983
990
  ```
984
- diagram theme: nord background: #1e2229 text: (color: #e0e0e0)
991
+ style backup border: #d2904e line: #d2904e
992
+ node mirror "shared folder / mirror" style: backup
993
+ node nas "NAS" right of mirror
994
+ edge mirror -> nas style: backup
985
995
  ```
986
996
 
987
- Three attributes:
988
-
989
- - `theme` names the theme that supplies every color the file does not state. See "Themes" below.
990
- - `background` takes a color the same way `fill` does, and it colors the page behind everything, including the strip an edge text knocks out of whatever it crosses. Say nothing and the theme's own background stands.
991
- - `text: (color: …)` sets the color of every text, on nodes and edges alike. It takes a color and nothing else; a text size is about one kind of text and belongs on a `default`.
997
+ 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:`.
992
998
 
993
- An unknown key is refused by name — `diagram has no "backround" — it takes theme, background, text` — rather than quietly ignored, the same as every other attribute. See "Attributes".
999
+ - **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.
1000
+ - **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.
1001
+ - **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.
1002
+ - **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.
994
1003
 
995
1004
  ### Themes
996
1005
 
997
- ```
998
- diagram theme: solarized-dark
999
- ```
1006
+ `diagram theme: nord` names a theme, which supplies every color the file doesn't state.
1000
1007
 
1001
1008
  | theme | |
1002
1009
  |---|---|
1003
- | `dark` | the default: the palette of the benchmark drawing |
1010
+ | `dark` | the default |
1004
1011
  | `light` | the same roles on a white page |
1005
1012
  | `solarized-dark`, `solarized-light` | [Solarized](https://ethanschoonover.com/solarized/) |
1006
1013
  | `gruvbox-dark`, `gruvbox-light` | [Gruvbox](https://github.com/morhetz/gruvbox) |
@@ -1011,17 +1018,13 @@ diagram theme: solarized-dark
1011
1018
  | `high-contrast-dark`, `high-contrast-light` | no fills to lean on, every line at full strength |
1012
1019
  | `print` | a white page, black lines and no fills, for paper |
1013
1020
 
1014
- The borrowed palettes were made for code, where a color marks a keyword or a string. A diagram needs a page, a box, a border, text and a line, so each theme is its scheme read as a diagram rather than a copy of it. Their licenses are in `NOTICE`.
1021
+ 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.
1015
1022
 
1016
- A theme only ever supplies a color, so changing it never moves anything. A color you write yourself — `fill: #14532d`, or a style's — stays that color under every theme, which means a hand-picked color can clash with a theme it was not chosen against. To color something and have it follow the theme, name a theme color instead.
1017
-
1018
- An unknown name is an error that lists the themes.
1023
+ 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.
1019
1024
 
1020
1025
  ### Theme colors
1021
1026
 
1022
- Every theme defines the same set of colors, and any attribute that takes a color takes their names. All of them begin `theme-`, so a color that follows the theme says so, and no CSS color name is taken: `red` is still CSS red.
1023
-
1024
- The theme's own color for each part — what a box or a line gets when the file says nothing:
1027
+ 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-`:
1025
1028
 
1026
1029
  | color | is |
1027
1030
  |---|---|
@@ -1031,13 +1034,8 @@ The theme's own color for each part — what a box or a line gets when the file
1031
1034
  | `theme-fill` | a box's fill |
1032
1035
  | `theme-border` | a box's outline |
1033
1036
  | `theme-line` | a line |
1034
-
1035
- And its two accents, each at two strengths, with the text that reads on it:
1036
-
1037
- | color | is |
1038
- |---|---|
1039
- | `theme-primary` | the theme's signature color — Dracula's purple, Solarized's blue |
1040
- | `theme-primary-subtle` | the same mixed toward the page: dark on a dark theme, pale on a light one |
1037
+ | `theme-primary` | the theme's signature color |
1038
+ | `theme-primary-subtle` | the same, mixed toward the page |
1041
1039
  | `theme-on-primary` | text that reads on a solid `theme-primary` |
1042
1040
  | `theme-secondary`, `theme-secondary-subtle`, `theme-on-secondary` | the same for its second color |
1043
1041
 
@@ -1047,11 +1045,7 @@ style live fill: theme-primary
1047
1045
  style dim text: (color: theme-muted)
1048
1046
  ```
1049
1047
 
1050
- A subtle fill is how a box is set apart: its text is the theme's ordinary text, and it reads in every theme. A solid fill is a badge. A box with a theme fill and no text color of its own gets text that reads on that fill, so `style live` above needs no `text:` — its text is `theme-on-primary`. A hand-picked fill gets no such help; its text is the theme's, as before.
1051
-
1052
- A `theme-` name the theme does not define is refused with the list above.
1053
-
1054
- The command line can render a file in another theme without editing it — `reladraw arch.reladraw --theme light` — and the flag wins over the file's `theme:`. That is how one source becomes a light and a dark image for a README that shows whichever matches the reader's settings. The file's own `background:` and `text:` still win over the flag's theme, as a hand-written color wins over any theme.
1048
+ 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.
1055
1049
 
1056
1050
  ### Defaults
1057
1051
 
@@ -1062,83 +1056,236 @@ default container fill: #1a2620 border: #2a3a30
1062
1056
  default edge line: #7a8a99 text: (color: #9aa5b1, size: small)
1063
1057
  ```
1064
1058
 
1065
- A default is a style that every thing of one kind wears without naming it. `node` covers every node; `leaf` a node with no children, and `container` a node with some — the same "has children" the theme's quieter backdrop colors follow, so setting a leaf's fill leaves containers looking like containers. `edge` covers every edge.
1059
+ 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.
1060
+
1061
+ 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.
1062
+
1063
+ 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`.
1066
1064
 
1067
- The more specific word wins, all the way down:
1065
+ ### The diagram
1068
1066
 
1069
- 1. the theme
1070
- 2. `diagram`
1071
- 3. `default node`
1072
- 4. `default leaf` or `default container`, or `default edge`
1073
- 5. the thing's styles, a later one winning where two set the same key
1074
- 6. what the thing's own line says
1067
+ ```
1068
+ diagram theme: nord background: #1e2229 text: (color: #e0e0e0)
1069
+ ```
1075
1070
 
1076
- A default takes the words a style does, and may name a style of its own with `style:`, which it lays beneath its own words. Unlike a style, it is strict about kind: it names the kind it is for, so a word that kind has no use for can only be a mistake. `default edge fill: red` is refused and points at `line:`. `badge:` is refused on `default leaf` and `default node`, since a badge is a child and would make every leaf a container, and `icon:` is a leaf's alone, since a picture cannot hold children. A `url:` belongs to one thing, never to a kind.
1071
+ `diagram` holds what covers the whole drawing: `theme`, `background` (the page behind everything), `text: (color: …)` for every text, `guides: show`, and `checks: (…)` ([Findings](#errors-and-findings)). 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.
1077
1072
 
1078
- Each kind is written at most once. `default leaf` twice is an error naming both lines, and `default leaf node` is refused with `default leaf` — a leaf is already a node.
1073
+ ### Destinations
1079
1074
 
1080
- ## What the language refuses
1075
+ ```
1076
+ node docs "Documentation" url: "https://example.com/docs"
1077
+ node store "Store" right of docs (gap: 12)
1078
+ edge docs -> store "read first" url: "https://example.com/order"
1079
+ ```
1081
1080
 
1082
- Deliberate omissions. What they protect is that the renderer never *chooses* an arrangement — it computes the one you described. Working out coordinates from a stated arrangement is arithmetic and is not what is being refused here; picking between arrangements that all satisfy what you wrote is. There is exactly one narrow exception, and it is named as such under "Nodes do not overlap".
1081
+ `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.
1083
1082
 
1084
- - **Coordinates**, in any form, including as an escape hatch.
1085
- - **Guessing an axis nobody constrained.** When two placements bind one axis and nothing binds the other, the tool refuses rather than picking a target to center on. Choosing there would decide which row a node shares, not how far it sits from something.
1086
- - **Placements that run in a circle.** A loop where each placement demands more room than the last cannot be satisfied and is an error naming the placements involved. A target does *not* have to be positioned before the node naming it — the whole system is solved at once — so ordinary mutual references are fine.
1087
- - **Edge waypoints.** A point a line must pass through is a coordinate wearing a hat. Saying a line goes between two named things is not one — it names things the diagram already contains, and it survives those things moving.
1088
- - **Choosing where a line goes when you said.** The tool finds its own way round a box in a line's way, but only as the shortest way through the open space, so where a line goes follows from where the boxes are. When you want a particular way instead, `between` and `below <node>` say it, and the tool keeps to what they say.
1089
- - **Set-level placement.** Four siblings around a hub are four statements today. Whether a durable group that reflows when a member is added is worth the same-axis conflict it introduces is undecided.
1083
+ ## Errors and findings
1090
1084
 
1091
- Note what is *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, and the first version was short enough of them to render the benchmark wrong.
1085
+ 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.
1092
1086
 
1093
- ## Not built yet
1087
+ | kind | reports | default |
1088
+ |---|---|---|
1089
+ | `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 |
1090
+ | `text-overflow` | a text running out of a box whose stated size is too small for it | on |
1091
+ | `edge-node-overlap` | a line across a node that isn't one of its ends, or through its own ends | on |
1092
+ | `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 |
1093
+ | `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 |
1094
+ | `node-overlap` | two nodes on one spot that no layout, `inside`, `on` or `overlap: allow` accounts for | on |
1095
+ | `edge-no-length` | a line between two boxes that touch, drawn as its marks at the seam | on |
1096
+ | `edge-crossings` | two lines crossing | off |
1094
1097
 
1095
- Designed, decided, and absent from the code. Written down so the next version has somewhere to start.
1098
+ A finding is one line, in the form editors already read:
1096
1099
 
1097
- **Nothing keeps an edge clear of a node on its own.** Non-overlap applies to nodes only. A line may still cut across a node it has nothing to do with, and an edge text may still land on top of one. `between` and `below <node>` are how you say where a line goes when that matters, and nothing checks the ones where you have not said. A check belongs on the diagnostics list, but finding a route by itself does not — see "What the language refuses".
1100
+ ```
1101
+ 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
1102
+ ```
1098
1103
 
1099
- **A named channel cannot make room for itself.** Lines through a `between` gap too narrow for them crowd together silently, in exactly the way attachments on a too-short side do. An edge with text *does* now open the gap between its own two ends — see "A text makes room for itself" — and it does so by the measure-then-constrain route that region alignments already use, which is the route this wants too. What is missing is the harder case: several edges sharing a channel between two nodes neither of them is an end of, where the room needed is the whole stack of lanes rather than one text.
1104
+ 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.
1100
1105
 
1101
- ## Known to be wrong
1106
+ 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.
1102
1107
 
1103
- Not omissions — defects, left here so nobody rediscovers them. Most were found by rendering the benchmark diagram; the last was not, and that is the interesting one, because the benchmark could never have caught it.
1108
+ 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.
1104
1109
 
1105
- ~~A placement written after an attribute was an error.~~ Gone entirely in 0.3.0, along with the papercut about how it was reported. `node q "Q" gap: wide level with p` is now an ordinary statement. The rule existed only because a bare `gap:` written between two placements could not be told from the node-wide default; [bracketing a gap onto its placement](#a-gap-belongs-to-the-placement) removed that ambiguity and left the ordering rule with nothing to protect.
1110
+ ## The command line
1106
1111
 
1107
- **A bracketed node sits against one side of any slack.** When two opposing placements leave more room than the node needs — because something else forced the two targets further apart — the node sits against the side it was pushed from rather than centered between them. In practice the tightest arrangement usually leaves no slack, so this rarely shows. Whether it should center instead is not decided.
1112
+ ```
1113
+ reladraw render diagram.reladraw -o diagram.svg
1114
+ reladraw check diagram.reladraw
1115
+ ```
1108
1116
 
1109
- ~~A node placed only with `left of` or `above` drifted to the canvas edge.~~ Fixed. Every constraint reads "this one is at least so far right of that one", so the solve puts each node at the smallest position its constraints allow — right for anything with something behind it, but `left of X` bounds *X* rather than the node that wrote it, leaving such a node nothing to be pushed by. It settled at the edge of the drawing while its target was carried off by the rest of the diagram. A node with nothing behind it now travels until the first of its own placements binds, which is what "as close together as your placements allow" always said.
1117
+ `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.
1110
1118
 
1111
- ~~An annotation could not be put beside the rows it was about.~~ Fixed by letting a placement name several targets, which places the node against the region bounding them. The workaround before it was to wrap the targets in an invisible container so there was a single thing to name, which made the author declare a node to stand in for an idea the language could have expressed directly — and cost the container's padding on top.
1119
+ | option | |
1120
+ |---|---|
1121
+ | `-o`, `--out` | where `render` writes; defaults to the input with `.svg`; `-` for standard output |
1122
+ | `--theme <name>` | render in another theme, whatever the file says |
1123
+ | `--guides` | draw the guides |
1124
+ | `--checks "<kind>: on, …"` | turn kinds of finding on or off, over the file |
1125
+ | `--no-checks` | print no findings; errors still print |
1126
+ | `-v`, `--version` | print the version |
1112
1127
 
1113
- ~~One relation cannot say what a real arrangement needs.~~ Fixed by letting a node carry several placements: it takes its horizontal position from one target and its vertical from another, and two opposing placements put it between two more.
1128
+ 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.
1114
1129
 
1115
- ~~The unwritten axis is a silent guess.~~ Fixed. A lone placement's centering is now the documented meaning of the direction rather than a fallback, and the case where it would have to choose between two targets is an error.
1130
+ ## Attributes
1116
1131
 
1117
- ~~Two nodes can land on the same pixels in silence.~~ Fixed. Every pair of nodes must now clear the other, and where the file does not order them the tool says so instead of drawing one over the other. Edges are still unchecked.
1132
+ 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`).
1118
1133
 
1119
- ~~Gaps get used as a fixing hack.~~ Fixed by making every gap a minimum. Room for something is made by saying that something goes there, not by widening a number on an unrelated line.
1134
+ | attribute | box | picture | bare | line | says |
1135
+ |---|---|---|---|---|---|
1136
+ | `style` | ✓ | ✓ | ✓ | ✓ | the styles to wear, one or a list |
1137
+ | `shape` | ✓ | | ✓ | | the outline |
1138
+ | `icon` | | ✓ | | | the picture the node is drawn as |
1139
+ | `badge` | ✓ | | | | a picture joined to the text |
1140
+ | `cards` | ✓ | | | | a deck's cards, when they are only texts, front first |
1141
+ | `fill`, `border` | ✓ | | | | colors |
1142
+ | `corners` | ✓ | | | | `sharp` or `rounded`; not on a circle. A line's go in `line: (…)` |
1143
+ | `text` | ✓ | ✓ | ✓ | ✓ | in a style only; on a statement, the bracket after the text |
1144
+ | `layout` | ✓ | | ✓ | | how the children are arranged |
1145
+ | `gather-children` | ✓ | | ✓ | | where the children sit in spare room |
1146
+ | `heading` | ✓ | | ✓ | | the child that is the title |
1147
+ | `join-with` | ✓ | ✓ | ✓ | | the thing this moves with |
1148
+ | `padding` | ✓ | | ✓ | | room inside the edge |
1149
+ | `padding-top`, `-right`, `-bottom`, `-left` | ✓ | | ✓ | | one side's room, over `padding` |
1150
+ | `width`, `height`, `min-…`, `max-…` | ✓ | | ✓ | | a stated size |
1151
+ | `overlap` | ✓ | ✓ | ✓ | | `allow`: an overlap is meant |
1152
+ | `depth` | ✓ | ✓ | ✓ | ✓ | drawn behind or in front of something |
1153
+ | `url` | ✓ | ✓ | ✓ | ✓ | a destination |
1154
+ | `from`, `to` | | | | ✓ | where the line leaves and arrives |
1155
+ | `via` | | | | ✓ | the places it passes |
1156
+ | `from-mark`, `to-mark` | | | | ✓ | the marks at its ends |
1157
+ | `line` | | | | ✓ | its color, or everything about it in a bracket |
1158
+
1159
+ 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`.
1160
+
1161
+ **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:`.
1120
1162
 
1121
- ~~A text cannot contain a slash.~~ Fixed in two parts: the line-break marker now needs whitespace on both sides, so `TCP/IP`, `16/9`, `I/O` and every path and URL survive untouched, and `\/` escapes the marker for a text that wants a spaced slash and no break, such as `Before \/ After`.
1163
+ ## What the language refuses
1122
1164
 
1123
- That one was found by testing the lexer, not by rendering — and it could not have been found by rendering, because every text in the benchmark happens to use spaces around its separator. Worth knowing that the repository's own second test target is an OSI and **TCP/IP** diagram, so a picture the language was meant to be tested against could not have been written in it. A benchmark only exercises the cases it happens to contain.
1165
+ Deliberate omissions. What they protect is that the tool never *chooses* an arrangement: it computes the one you described.
1124
1166
 
1125
- ~~An edge with text between two nodes at the default gap drew its text across both of them.~~ Fixed. The default gap is sized for nodes to breathe and a text is wider than that, so `edge a -> b "statements"` on two adjacent nodes came out unreadable and nothing said so; the authoring workaround was to name a wider gap on a placement that had no reason to be wider. An edge with text now widens the corridor it crosses by what the text needs. Note what this is *not*: no coordinate, no repair of a solved layout, and nothing that finds a route — the corridor is derived from where the nodes landed and then becomes an ordinary minimum distance like any other.
1167
+ - **Coordinates**, in any form. Every position is said relative to things the diagram contains, so it survives those things moving.
1168
+ - **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.
1169
+ - **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.
1170
+ - **Synonyms.** One spelling for each thing, and the others refused by name.
1171
+ - **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.
1126
1172
 
1127
- ~~Two edges between the same pair of sides were drawn on top of each other.~~ Fixed. Each side was ordered on its own, by where the far ends sat, and for edges that share both ends that signal says nothing — so the two edges were ordered without reference to each other and the lines converged in the middle instead of nesting. The visible damage was to the texts: both landed at the same point and the second knocked a hole through the first, leaving one word of it. A group like this now takes one lane order used at both ends. See "Several edges between the same two sides".
1173
+ 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.
1128
1174
 
1129
- ~~Several edges between the same two nodes with no side named were drawn on top of each other.~~ Fixed. This is the same defect as the one above, one step out: a bundle is a statement about two named sides, and an end with no side named has not made one, so nothing saw the group. `edge a -> b` three times drew one visible line carrying one text. Each such edge now takes its own line, parallel to the one it would have drawn alone and a lane away from it. Note what did *not* change: an unnamed end still attaches where the center-to-center ray crosses the border, so no single edge anywhere moved. See "Several edges with no side named at all".
1175
+ ## Not built yet
1130
1176
 
1131
- ~~A text's properties sat among the node's.~~ Gone in 0.3.0. `size:`, `wrap:` and the text color `text:` were top-level attributes, sitting beside `fill:` and `shape:` as though how big a text is set were a fact about the node. They are in the brackets after the text now, where the reader can see what they modify, and an older file writing one at the top level gets an error naming the bracket. `at:` came with them and grew from `top | bottom` to the nine named positions, which is the same repair one level down: two of the nine had been handed out because those were the two somebody needed.
1177
+ Designed and absent from the code.
1132
1178
 
1133
- ~~An unknown attribute was ignored in silence.~~ Fixed. `wibble: red` on a node parsed, was stored, and was never read again — nothing drew and nothing was said. So did every real attribute written on a kind with no use for it: `align:` on a node that may have no children, `gap:` on an edge. This was the same defect the color parts had closed one level down a version earlier, and it is how that migration produced false results from the repository's own regression check, since the older build simply dropped every `border:` it had not heard of. See "Attributes".
1179
+ - **A line joining or leaving a bundle partway.**
1180
+ - **Several marks at one end of a line**, such as a database's crow's feet.
1181
+ - **Attributes, templates and functions**: definitions of your own that add placements, nodes and lines.
1134
1182
 
1135
- ~~An edge text ignored the line break.~~ Fixed. ` / ` split a node's text and was never applied to an edge's, so the marker came out as a literal slash on an arrow and the benchmark's two-line captions had to be flattened to one. The measurer had always returned the split lines; the renderer was handing it the raw string and drawing that instead. The block now centers on the point the text already occupied, so a one-line text sits exactly where it did.
1183
+ ## Undecided
1136
1184
 
1137
- ~~An edge whose ends faced away from each other was drawn through its own nodes.~~ Fixed. `from: left to: right` with the far node further right was one curve bending out at both ends, and with the two nodes in a row it flattened into a straight line through both of them and anything between, its text landing on whatever was in the middle. It was not a matter of exact alignment: a node stepped down a little drew a tilted version of the same line. Such an edge now turns back in the gap between its nodes if there is room, and goes over the top of the row if not. See "Going round boxes".
1185
+ - `widths: match` on a hand-placed container, so "as wide as the widest of them" doesn't go stale.
1186
+ - 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.
1187
+ - How a file sets its own unit.
1188
+ - Spreading one side's line ends over the whole side.
1189
+ - More layouts: `grid`, `star`, `circle`.
1190
+ - 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.
1138
1191
 
1139
1192
  ## Changelog
1140
1193
 
1141
- Pre-1.0, so the minor number is where a breaking change goes. Every removal below 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.
1194
+ 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.
1195
+
1196
+ **0.17.1**
1197
+
1198
+ - **`reladraw --version`**, or `-v`, prints the version.
1199
+ - **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.
1200
+
1201
+ **0.17.0**
1202
+
1203
+ 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.
1204
+
1205
+ Breaking, in how things are drawn:
1206
+
1207
+ - **Gaps are exact.** A gap used to be a minimum that other things could widen. Now it is the distance, and nothing widens it.
1208
+ - **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`.
1209
+ - **Nothing pushes overlapping boxes apart.** Two boxes placed on one spot are drawn there and reported as `node-overlap`.
1210
+ - **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`.
1211
+ - **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.
1212
+ - **A child placed against its parent's side no longer grows the parent.** It is placed against the finished box.
1213
+ - **Opposite placements no longer push their targets apart.** `right of a left of b` centers the box in the space already there.
1214
+ - **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.
1215
+ - **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.
1216
+ - **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.
1217
+ - **Lines are straight by default**, `path: straight`. They were curved. `default edge line: (path: curved)` keeps the old look.
1218
+ - **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.
1219
+ - **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.
1220
+ - **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.
1221
+ - **A container's title is centered along its top.** It was at the top left; `(inside top, align: left)` keeps that.
1222
+ - **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.
1223
+ - **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`.
1224
+ - **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`.
1225
+ - **The command takes a verb**: `reladraw render diagram.reladraw`. The bare form is refused.
1226
+
1227
+ Breaking, in spelling:
1228
+
1229
+ | 0.16.0 | now |
1230
+ |---|---|
1231
+ | `level with a`, `top level with a` | `even with a`, `top even with a` |
1232
+ | `gap: wide` on a node | in the placement's bracket: `right of a (gap: wide)`, or `8 right of a` |
1233
+ | `contents: (widths: match, align: center)` | `layout: column (widths: match)`; `gather-children: center` for where the group sits |
1234
+ | `align:` in a layout's bracket | `align-children:` in the bracket, or `gather-children:` on the box |
1235
+ | `below of a`, `left a` | `below a`, `left of a` |
1236
+ | `edge a -> b between c and d` | `via: between c and d` |
1237
+ | `edge a -> b between c and d vertically` | `via: between c and d`, or `via: through r horizontal` through a named region |
1238
+ | `edge a -> b below c` | `via: c bottom` |
1239
+ | `edge a -> b above c`, `left of c`, `right of c` | `via: c top`, `via: c left`, `via: c right` |
1240
+ | `"Title" (at: top-left)` on a node | `(inside top, align: left)` |
1241
+ | `(at: center)` on a container's text | `(center)` |
1242
+ | `(at: …)` on a leaf's text | nothing; a leaf's text is centered, or `(inside top)` and the other sides |
1243
+ | `reladraw diagram.reladraw` | `reladraw render diagram.reladraw` |
1244
+ | `size: 22` | `size: 22px` |
1245
+ | `thickness: 3` | `thickness: 3px` |
1246
+ | `gap: 12` | `gap: 12px`, or `1` |
1247
+ | `from:` or `to:` in a style, which did nothing | on each edge: `edge a -> b from: right to: left` |
1248
+ | `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 |
1249
+
1250
+ New:
1251
+
1252
+ - **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.
1253
+ - **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.
1254
+ - **Square and curved routes** read off a line's own two boxes, with `turn:`, `step-out:` and `curve:`.
1255
+ - **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`.
1256
+ - **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.
1257
+ - **Named lines** and points on them: `req start`, `req end`, `req at 1/3`.
1258
+ - **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.
1259
+ - **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`.
1260
+ - **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:`.
1261
+ - **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)`.
1262
+ - **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.
1263
+ - **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.
1264
+ - **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.
1265
+ - **`straight` from a spot on its side**: `from: right 1/4 down straight right` leaves from that spot rather than the side's middle.
1266
+ - **A curved line runs straight under its arrowhead**, so it goes into the head along its axis instead of bending out of its side.
1267
+ - **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.
1268
+ - **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.
1269
+ - **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.
1270
+ - **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.
1271
+ - **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.
1272
+ - **`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.
1273
+ - **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.
1274
+ - **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`.
1275
+ - **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.
1276
+ - **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`).
1277
+ - **Sizes**: `width:`, `height:` and the four limits, from numbers, measurements of other boxes, sums, spans and `larger of`.
1278
+ - **Layouts**: `layout: column` and `row`, with `gap:`, `widths:`, `align-children:` and `centers:`; `gather-children:` on any container.
1279
+ - **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.
1280
+ - **A point's and a region's text**: `point mid "halfway" (above-right)`, `region trust "trust boundary" (inside top)`.
1281
+ - **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.
1282
+ - **Invisible containers**: `shape: none` with children.
1283
+ - **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.
1284
+ - **`padding:`** on a box or region.
1285
+ - **Guides**: `guide` before any node, line, point or region, shown with `--guides`, the playground's Guides box, or `diagram guides: show`.
1286
+ - **Drawing order**: `depth: behind a`, `depth: in front of a`.
1287
+ - **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.
1288
+ - **Numbers**: `px` on any number, a number starting at its decimal point (`.5`), and `size:` and `thickness:` as multiples.
1142
1289
 
1143
1290
  **0.16.0**
1144
1291
 
@@ -1191,7 +1338,7 @@ No syntax changes, and no file that worked is refused.
1191
1338
  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.
1192
1339
 
1193
1340
  - A line can have no arrowhead: `edge a -- b`, or `edge a - b`.
1194
- - 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 "What is drawn at each end".
1341
+ - 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).
1195
1342
 
1196
1343
  **0.13.0**
1197
1344
 
@@ -1211,22 +1358,22 @@ No syntax changes, and no file that worked is refused. Lines move: every line th
1211
1358
 
1212
1359
  **0.11.0**
1213
1360
 
1214
- - 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. See "Going round boxes".
1361
+ - 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.
1215
1362
  - 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.
1216
1363
  - A curve finishes straight at an arrowhead, so the head points square into its side. Most diagrams with a named side look slightly different.
1217
- - 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. See "How the line is drawn".
1364
+ - 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.
1218
1365
  - 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.
1219
1366
  - 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.
1220
1367
  - **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.
1221
1368
 
1222
1369
  **0.10.0**
1223
1370
 
1224
- - A statement may continue onto the lines after it by indenting them. See "Shape of the file". An error in such a statement names the line the mistake is on.
1371
+ - 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.
1225
1372
  - **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.
1226
1373
 
1227
1374
  **0.9.0**
1228
1375
 
1229
- - 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. See "Theme colors".
1376
+ - 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.
1230
1377
  - A box with a theme fill and no text color of its own gets text that reads on that fill.
1231
1378
  - **Breaking:** `muted` is now `theme-muted`. `color: muted` is refused with the new name.
1232
1379
  - The examples use theme colors where they used to hand-pick colors for a dark page, so they read in every theme.
@@ -1268,6 +1415,7 @@ No syntax changes, and no file that worked is refused. Lines move: every line th
1268
1415
  - **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.
1269
1416
  - **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.
1270
1417
  - `style:` takes a list, `style: equipment, critical and alarm`, applied in order with a later style winning where two set the same key.
1418
+
1271
1419
  **0.3.0** — the vocabulary, reworked in one breaking version so there is one migration rather than five.
1272
1420
 
1273
1421
  - `box` is `node` and `link` is `edge`. The string on either is its *text*; "label" is not a word the language has.
@@ -1284,15 +1432,3 @@ No syntax changes, and no file that worked is refused. Lines move: every line th
1284
1432
  **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.
1285
1433
 
1286
1434
  **0.1.0** — first published version.
1287
-
1288
- ## Undecided
1289
-
1290
- Open questions the benchmark raised, recorded so a later session does not rediscover them.
1291
-
1292
- - ~~One edge passing some nodes above or below and others left or right — an L-shaped line, across under one node and then down past another — is refused today.~~ Answered in 0.6.0: such an edge turns between the two kinds as few times as it can, and where two orders both fit, a standing rule picks — a side named at an end says which way the line goes first, and with none named it goes across first. Since 0.13.0, such a line takes whichever way keeps its clauses with the fewest turns, and the across-first rule no longer applies. See "Passing on one side of a node" above.
1293
- - Named gaps are the first step toward numbers, but making them minimums took most of the pressure off: they now set how much a diagram breathes, never whether something fits. Whether four names is the right number is still open.
1294
- - Four machines each holding a `files` child with the same text means writing the same line four times. This is the strongest case for a set-level declaration, for terseness rather than for placement.
1295
- - The 2×2 arrangement around a hub is four independent statements, so a fifth machine has no slot to reflow into. There are only eight directions.
1296
- - ~~Two annotations anchored to the same side of one node will collide.~~ Answered by putting both in an invisible container and placing the container, so they stack instead of stacking on top of each other. Writing it the colliding way is now an error rather than a bad picture, since nothing in the file orders the two. Whether the container idiom is good enough or wants dedicated syntax is open.
1297
- - Nothing yet expresses one node spanning several rows of a parallel column, which the OSI reference render needs.
1298
- - ~~How contents sit inside a container that is wider than they are.~~ Answered by `contents: (widths: …, align: …)` — see "How the contents sit". Automatic centering stays ruled out: it would move every existing diagram whose container title is wider than its contents. Whether this is the same question as the bracketed node sitting against one side of its slack, under "Known to be wrong", is still open — they share the phrase and not the mechanism, since one distributes to a group at size time and the other is a single member in a constraint system.