Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Diagram syntax

A specification is a sequence of blocks separated by blank lines. A line beginning with % is a comment and is discarded before parsing.

The first line of a block decides what it is. Blocks are matched in this order, and the first match wins:

First line matchesBlock is
[styles]diagram styles
[styles.X]message styles
@ followed by space or end of linea time axis
>, = or ., a single-token label, then |a grid row
>an operator
anything elsea marble stream

Lines after the first in a block are configuration, written key = value.

Two modes

A diagram containing a time axis is in grid mode; one without it is in frame mode. The difference is what a horizontal position means: grid mode looks up a discrete labelled column shared by every row, frame mode scales a continuous frame number.

Marble rows are rejected in grid mode rather than half-parsed, so you cannot mix the two by accident.

Time axis

@ <label> | <column> | <column> | ...

t012

The segment before the first | is the gutter label and may be empty. Each segment after it is one column — one transaction.

One | declares one column, and the axis draws one dashed boundary per column with none after the last, so the line above is three pipes, three columns and three dashed lines. There is no special case for a trailing pipe: the count of pipes on a line is the count of columns it declares, and a grid row carries exactly the same number.

A final | with nothing after it therefore declares an unlabelled column, which is how a diagram asks for a closing boundary:

t01

A column label may be prefixed with one > per level of nesting, for diagrams that split a transaction:

@ t | [0] | >[0,0] | >[0,1] | [1]

t[0][0,0][0,1][1]

Nesting lightens the grid line that opens the column.

A diagram may declare at most one axis, and it need not come first.

Configuration: title overrides the gutter label.

Grid rows

All three kinds share a shape: a sigil, a single-token label, then one slot per column.

<sigil> <label> | <slot> | <slot> | ...

Slots are trimmed, so you may pad them to line up with the axis. A slot’s text is drawn verbatim — the quotes in 'a' are yours, not the notation’s.

Every row must have exactly as many slots as the axis has columns, which means exactly as many pipes. A mismatch stops the build.

A row whose last slot is empty ends in a bare | and needs no extra one, so an all-empty three-column row is > s | | |.

Stream rows — >

A line running the width of the axis, ending in an arrowhead. An empty slot means the stream did not fire in that transaction.

> s1 | 'a' |  | 'b'

t012‘a’‘b’s1

Cell rows — =

A box holding a value across an interval. A non-empty slot changes the held value; an empty one keeps it. Dividers are derived from that, not written.

= c | 'a' |  | 'b'

t012‘a’‘b’c

Configuration:

KeyMeaning
fromcolumn label whose opening boundary the box starts at. Default: before column 0
tocolumn label whose opening boundary the box closes at. Default: after the last column

Both take a column label, not an index. The box overhangs the boundary that bounds it rather than sitting flush against it, so a cell reads as holding its value through that transaction.

t01234‘a’‘b’c

Annotation rows — .

A label and values with no line of their own, for commenting on a transaction.

. a1 |  | 'a' |

t012‘a’‘b’c‘a’a1

References

A slot whose text matches another row’s label is a reference to that row rather than a literal — the switch case, where a cell holds another cell or a stream.

t0123‘a’‘b’‘c’‘d’s1‘W’‘X’‘Y’‘Z’s2s1s2c

Resolution happens after the whole diagram is parsed, so the row being named does not have to be declared first. A row naming itself stays a literal.

Marble streams

Frame mode uses RxJS marble-testing syntax- for a frame of time, a letter or digit for a value, | for completion, # for an error, and () to group events into one frame.

--a--b--|

Configuration:

KeyMeaning
titlethe label drawn to the left of the line
ghostscomma-separated value names to draw faded
X := valuedraw marble X with this text instead of X

Leading whitespace offsets the stream in time.

Named streams

A block of the form x = <marbles>, where x is one character, defines a stream instead of drawing one. Using that character in a later marble line nests the stream inside the event, which is how higher-order diagrams are drawn:

x = --a--b--|

-x----|

Operators

A line beginning with > that is not a grid row — that is, one without a pipe right after its first word — is an operator band:

> concatAll

Backticks inside the title embed a marble diagram in it:

> debounce(() => `--|`)

Configuration: X := value, as for marble streams.

Diagram styles

[styles]
axis_column_sizing = fixed
axis_column_width = 160

Applies to the diagram it appears in. See Themes and styles for the keys.

Message styles

[styles.a]
fill_color = red

Applies to one marble value — the single character after the dot — in frame mode.