13. Sequence
A sequence reads a diagram on a time axis: layout: sequence places named
participants across the top, drops a lifeline from each, and lays messages —
ordinary links — top-to-bottom in source order, so the order you write the wires is
the order they happen. It adds no grammar: participants are nodes, messages are links
(SPEC 9), frames and notes are nodes — only the engine, six type names, and two
properties (place, activation) are new, and it lowers to primitives like any layout-owning engine
(SPEC 11, seam 3).
The container & its children
layout: sequence on the root ({ layout: sequence }) or any node makes a sequence; the
|sequence| template is the preset over |block|. Its children split by role,
recognised by type — every other box is a participant (an open fallback, unlike a
chart’s closed series set):
| Child | Is a | Drawn |
|---|---|---|
a box (|box|, |cyl|, |icon|, …) | participant | a header at the top + a lifeline down |
a link (a -> b) | message | a time-row arrow between two lifelines |
|loop| / |opt| / |alt| | frame | a labelled rectangle around a span of messages |
|else| | separator | a guarded compartment divider inside an |alt| |
|note| | note | a callout over / beside lifelines |
Nodes and links interleave in source order (SPEC 9), so a frame (a node) sits among the messages (links) around it.
One scope. Every message resolves its endpoints against the sequence’s
participants, whatever frame it sits in: a frame’s [ ] groups messages for layout but
opens no new scope — it declares no participants and auto-creates none, overriding the
sealed-body and body-auto-create rules (SPEC 3, SPEC 9)
inside a sequence. So |alt| [ db --> api … ] wires the outer db / api lifelines,
never frame-local boxes.
gap sets spacing: its column part the space between participants, its row part
the gap between message rows (gap: row col; default 32 32). A label wider than its span
widens it — adjacent lifelines sit max(gap-column, widest message label between them + margin) apart, text measured at compile time. width / height size the whole frame and
distribute any surplus; unset, it sizes to its content.
Participants & lifelines
A participant is an ordinary node; its smart label is its header, placed by its
type (SPEC 3) — centred text for a box, the symbol for an |icon|.
Participants sit across the top in declaration order (left to right), each dropping a
lifeline and sharing a common foot at the last row. An undeclared endpoint
auto-creates a participant — a -> b with neither declared draws two — appended in
first-use order, so a quick sequence needs no header:
{ layout: sequence }
user -> server "login" // two auto-created participants, one message
server --> user "token"
Declare a participant (with an #id, so messages can name it) to fix its order, type, or
paint: |cyl#db| "Store", or |icon#user| "user" for an actor glyph. A participant
lends its paint to its apparatus: its lifeline and activation bars take its own
fill / stroke / stroke-width, so colouring or weighting a participant carries down
its whole timeline. Being an ordinary box, it also honours the box model on its own
content (SPEC 11).
Messages
A message is a link (SPEC 9) read on the time axis: its operator picks the look, its label rides above the arrow, its order is its row.
| Write | Means |
|---|---|
a -> b "x" | a call — solid arrow, a to b |
a --> b "x" | a return — dashed arrow |
a ~> b "x" | an async message — wavy arrow |
a -> a "x" | a self-message — a hook on a’s own lifeline, label to the right |
Every operator, marker, class, and { } is the link’s own; only the placement differs,
so a message’s label sits centred above its arrow and along: has no role. A chain
a -> b -> c is two messages on two rows; a fan a -> b & c likewise expands to two, in
expansion order. A forced side (a:left) and routing have no meaning on a time-row arrow
and are ignored.
Call vs. return is read from the operator (-> vs -->), not a stroke-style:
override.
Activations
A participant is active while it handles a call. By default (activation: auto) a
call (->) opens an activation bar on its target’s lifeline; the next return
(-->) from that target closes its most recent open bar; nested calls stack (each
bar offset outward), and an unclosed bar runs to that participant’s last row. The bar
stack is sequence-global — a call inside a frame may close outside it. A self-message
(a -> a) and an async (~>) open none, and a return with no open bar just draws its
arrow. activation: none on the sequence draws no bars. (Explicit per-message control is
deferred — SPEC 24.)
Frames & notes
A frame is a node whose [ ] holds its messages, drawn as a dashed rectangle spanning
the lifelines those messages touch (plus a small inset) over the rows they occupy. A
top-left tab names the operator; the frame’s smart label is its guard, drawn
as the first compartment’s condition. Frames nest and draw behind the lifelines (a
fill tints the region without hiding the wires):
| Frame | Means |
|---|---|
|loop| "guard" | the messages repeat (drawn once, not unrolled) |
|opt| "guard" | the messages happen only if the guard holds (an if) |
|alt| "guard" | one of several alternatives (an if/else) |
An |alt| holds two or more compartments split by |else| "guard" — a separator
valid only inside an |alt|, its label that branch’s guard; the first compartment’s guard
is the |alt|’s own label:
api -> db "query"
|alt| "found" [
db --> api "row"
|else| "missing"
db --> api "404"
]
api --> user "done"
A |note| is a callout placed at its time row (source order), bound to lifelines by
place: — a mode, then its lifeline id(s): { place: over api } a box over one
lifeline, { place: over api db } a box spanning those (and any between),
{ place: left api } / { place: right api } a box beside one. One mode per
note. Its smart label is the text; a multi-line or styled note rides the [ ]
like any box. place: is valid only in a sequence. par and other fragments are
deferred (SPEC 24).
Defaults
The five sequence types are bundles over |block|, tuned to read with no styling; the
cascade overrides any of it, and they reuse the scene’s role variables — no new ones.
(|note| is the core template, compacted here by its built-in scoped rule —
SPEC 8.)
| Type | Defaults over |block| |
|---|---|
|sequence| | layout: sequence; gap: 32 32 (a root { layout: sequence } gets the same gap) |
|loop| / |opt| / |alt| | fill: none; stroke: --group-stroke; stroke-style: dashed; stroke-width: 1; radius: 4; padding: 24; font-size: 12 |
|else| | fill: none; stroke: --group-stroke; stroke-style: dashed; stroke-width: 1; font-size: 12 |
The engine resolves in the layout phase — a message’s x-ends are the lifelines’ positions
(fixed once participants are placed) and its y is its row — placing participants, walking
messages/frames/notes in source order, and lowering headers → |block| + text, lifelines
and arrows → |line|, activations/frames/notes → |block| (SPEC 19).
The orthogonal router never sees these links.