# `StatifierBlocks.Map`
[🔗](https://github.com/riddler/statifier_blocks/blob/v0.44.1/lib/statifier_blocks/map.ex#L1)

The Map: the block document drawn as boxes in boxes, laid out by ELK in
the browser. This module builds the graph a layout hook lays out and
draws; it computes no position itself and calls no layout engine. Its
input is the view model and the host's two options below; it never
reads the document itself.

## A reader of the view model, never a second model

The graph is derived from `StatifierBlocks.ViewModel` on every change
and is never stored: no coordinate, no connector and no hand-placed
position reaches the document or any table. What an author edits is the
block tree, and the map is one more way of reading it, beside the
indented list a host draws from the same
`StatifierBlocks.ViewModel.outline/1`. The two cannot disagree about what
is in the document, because `nodes/1` below is the outline's own block
list and the test beside this module holds them equal. It is not a
layout mode of the editor: it reads the struct the editor reads, and
changes nothing in it.

## Its input

`graph/2` takes the view model and two options, both the host's:

| Option | What it is | Default |
|---|---|---|
| `:selected` | the block id the host has selected, or `nil` | `nil` |
| `:phrase` | a function from an event name to the words a reader reads for it, answering `nil` for a name it has no words for | no words: every name as authored |

The selection marks one block's node `"selected" => true` and changes
nothing else in the graph, so a host whose selection moves hands the hook
the same boxes. A selection naming no block marks nothing.

## The shape

| Graph node | What it is | Where its children come from |
|---|---|---|
| a block | one `ViewModel.Node`, keyed by its block id | its slots, below |
| a slot | one of a block's slots, keyed `<block id>/<slot name>` | the slot's blocks, in slot order |
| an empty marker | a slot with nothing in it, keyed `<slot id>/empty`, carrying `parent` and `slot` | nothing |

Every block but the root carries `gap: true`: it sits in a slot, so there
is a place right after it an insert can target, the same place the list's
"+" under its row targets. The root sits in no slot. An empty marker is
the other kind of gap: the head of the slot it stands for.

A block whose only body slot stacks (`ViewModel.arrangement/1` is
`:stack`) holds that slot's blocks directly, because a sequence drawn
inside a box inside a box says nothing the one box does not. Every other
slot is a node of its own: a branch's arms side by side, a parallel's
lanes, a group's interrupt rules and a drafts shelf. A group is the one
exception the other way: its body stacks, but it is drawn as a slot node
of its own all the same (see "The Group" below). An empty slot is
drawn as a marker rather than left out - an outcome nobody has written a
step for yet is exactly what a reader of the map has to be able to see.

Consecutive blocks in one body slot are joined by a `sequence` edge, in
`ViewModel.flow_children/1` order (a `rejoin` edge where the first is a
branch; see "The Branch" below); besides the edges out of the start
dot and into the end marks (below), nothing else is an edge the layout
sees (a group's interrupt edges are drawn after it, below). A rail's
blocks are not joined - a group's interrupt rules are alternatives that
each watch the whole body, not steps that run one after another - and
neither is a tray's. Connectors are drawn, never authored.

A box's title is its type's name and the line under it is the block's
sentence, with the event names the host's `:phrase` has words for read
as words: a send reads "Send word that" and the words, a wait "Wait
until" and the words, a name anywhere else the words alone, and a name
the host has no words for stays as authored. Where the title and the
sentence are the same words ("Invoke", "Raise") the line is left off
rather than said twice.

## The Branch

A branch is one decision, and its box says so in three ways.

- **The header names every arm, in order.** Where every other container
  carries its sentence, a `core.branch` carries the package's own "one
  of" and then one numbered entry per arm, in slot order, each the arm's
  label and its condition as authored (`When "renew": copy.holds == 0
  AND loan.renewals < 2`), the arms with no condition ("Otherwise",
  "Cannot be decided") by their label alone. An entry wider than a line
  wraps, and its later lines are indented under its label, by the width
  of its number, so a wrapped condition does not read as an entry of its
  own.
  This header is the map's own: the branch type's sentence, which the
  list draws, is not changed by it.
- **The arms sit under one band.** The branch's node carries `band: true`
  and room above its arms; the hook draws one band spanning every arm,
  with a small fork mark at its left, from the boxes the layout placed.
  The band and the mark are drawn inside the branch's own box, so a click
  on either is a click on the branch.
- **The arms rejoin at its bottom edge.** The edge from a branch to the
  step after it carries `kind: "rejoin"`, and the hook draws it with a
  join dot where it leaves the branch. A branch that is the last step of
  the document's own flow rejoins into the document's end (see "The
  end"); a branch that ends a nested flow needs none, since the flow it
  ends is itself joined to what follows it.
- **The band carries a caption.** The branch's node carries `caption`,
  `caption/1` of its type (see "Captions"), and `caption_width`, the
  room the caption and the fork mark need; the hook writes the caption
  inside the band, after the fork mark, and never draws the band
  narrower than that room.

## The start

A document starts when its host starts an execution of it, and the map
says where that execution begins the way a state chart does: one filled
dot, the initial mark, carrying the word Start, and one edge from it into
the first step of the document's own flow. The dot is a node of
`kind: "start"`, keyed `<root id>/start`, and the graph's first node,
ahead of the root's box rather than inside it: the layout puts it above
that box, so the dot and its caption come first in reading order, above
the root's title and every box under it. The word is the dot's one ELK
label, which the node asks the layout to place outside it, on its
right and level with it, so the layout leaves the word its room and the
edge still leaves the dot itself; an end mark carries End the same way.
Its edge is the graph's own, carries
`kind: "start"` and one ELK label, `start_text/0`, so the layout leaves
the caption its room. The caption names who starts the document, "Starts
when the system is told to", and reads the same on every document: what
the document listens for while it runs is the description region's to
say, not the edge's.

A root whose flow is not drawn inside it has no first step there, so its
edge goes into the root's box itself. Like the end marks, the dot is not
a block: it is in no outline, selects nothing and arms no insert.

## The end

Where the document finishes is drawn the way a state chart draws it too:
a final mark, a dot inside a ring, at every end of the document's own
flow, one per way it finishes, with the outcome named on the edge into
it. The ends are the ones `StatifierBlocks.Describe.outline/3` answers,
read here off the same view model, and the test beside this module holds
the two equal for every fixture:

| End | Where it comes from | Mark | Edge into it |
|---|---|---|---|
| `done` | the `:exit` edge from the last step of the root's flow into the root's exit | a solid ring | from that step, captioned `done` |
| `abandon` | an `abandon` interrupt rule of a group in tail position - that last step, or the last step of a flow it runs, and so on down - which leaves the group with nothing after it | a dashed ring | from each such group, dashed, captioned `abandon` |

The root's last step is in tail position, and so is the last step of the
body of a sequence or a group in tail position, or of any arm of a
branch in tail position: the steps the outline's `:exit` edges chain
from, container by container, into the root's exit. However many such
groups abandon, the document has one `abandon` end, and each of them
has its own edge into it.

Each mark is a node of `kind: "end"` carrying its `outcome`, keyed
`<root id>/end/<outcome>`, drawn right after the last step, and carrying
the word End as its one ELK label, whichever way it finishes: the
outcome stays on the edge into it, not on the mark. Its edge
carries the same `outcome` and one ELK label, the outcome's name, so the
layout leaves the caption its room. The edge out of a branch stays a
rejoin (see "The Branch"); any other edge into a mark is of
`kind: "end"`. `done` is written here rather than read from the palette:
it is the outcome every block type declaring no outcomes of its own has,
the one a root that runs its steps finishes with, and the test holds it
equal to the outline's own outcome for every fixture's root.

Only the document's own flow ends. A step that is last in a nested flow
- a Send at the foot of an arm or of a group's body - hands back to the
block around it, which goes on, so nothing is drawn after it and it does
not read as an end. A root whose flow is not drawn inside it, or that has
no step yet, draws no end. Like the start dot, an end mark is not a
block: it is in no outline, selects nothing and arms no insert.

## The Group

A group's body is what it is for, and its interrupt rules watch that
body from beside it, so the map draws the body first and larger.

- **The body is a pane.** A `core.group` or `core.resumable_group`
  draws its body slot as a slot node with `style: "body"`, first among
  its children, rather than holding the body's blocks directly. The pane
  is held wider than the rules column and at least as tall, at the same
  per-character estimate as every other size here, so the body is the
  larger of the two whatever the rules say.
- **The rules are a side column.** The interrupt rules' slot node is
  laid out on its own, in one column, rules top to bottom in rail order,
  and ELK places it beside the pane. It is laid out apart from the rest
  of the graph (`hierarchyHandling: SEPARATE_CHILDREN`) because nothing
  joins a rule to anything ELK sees: the interrupt edges are the hook's
  (below), so no edge crosses into the column. It is laid out left to
  right, where rules nothing joins share one layer, and a layer runs top
  to bottom. Its minimum size is written width first, the documented
  way round: a layout of its own is not transposed (see "Sizes").
- **The column carries a caption.** The rules' slot node carries
  `caption`, the words "Rules: watch the steps while they run", which
  the hook draws under the column's label, and `explanation_lines`,
  `caption/1` of the group's type (see "Captions") wrapped to lines,
  which the hook draws under that, one line each.

## Captions

A structural container the map draws apart says what its type does: a
branch on its band, a group on its rules column, under the column's
label and its caption. Nothing else carries one, and a leaf never does.
The words are the type's own: `caption/1` is the first sentence of
`StatifierBlocks.BlockType.explain/1`, the paragraph the package's
optional `explain/0` callback answers, and the description region shows
the whole paragraph. The sentence is whole: never cut, never ended in
"...". A branch's band carries it as one line and is held wide enough
for it; a group's rules column carries it as `explanation_lines`, the
sentence wrapped to lines no wider than a box's (a word longer than a
line keeps a line of its own), the way the key carries a mark's `line`
as its `lines`, and the column is held wide and tall enough for every
one of them.

## Interrupt edges

A group's interrupt rules are not joined to each other, but each one does
lead somewhere: an `abandon` rule leaves the group by its exit and a
`resume` rule goes back to the head of its body. The group's node carries
those as `interrupts`, one per rule, in rail order, and the hook draws
each one dashed from the rule's box to where it leads; the head of the
body is the first node drawn in the body's pane. They are handed to
the hook beside the graph's `edges`, not among them, and drawn after the
layout from the boxes it placed, so an interrupt edge never moves a box.
They are the edges `StatifierBlocks.Describe.outline/3` answers with
`kind: :interrupt`, read here off the same view model - a rule's
`outcome`, on the `interrupts` rail of a `core.group` or a
`core.resumable_group` - because the map is built from the view model
alone; the test beside this module holds the two equal for every fixture.

## Timer edges

A send with a delay arms an event that arrives later, and what it arms
is the interrupt rule or the hourglass step that hears it. The graph's root
carries those as `timers`, one per delayed send and rule or await naming
its event, sends in reading order and each send's targets in reading
order, each with its `event` and its `delay` as the send's config holds
it; the hook draws each one dotted from the send's box to the rule's or
the await's, labelled with the delay. Like the interrupt edges they are
handed to the hook beside the graph's `edges`, not among them, and drawn
after the layout, so a timer edge never moves a box. They are the edges
`StatifierBlocks.Describe.outline/3` answers with `kind: :timer`, read
here off the same view model - only blocks the palette resolved, and
nothing on a drafts shelf - and the tests beside this module hold the two
equal for every fixture and for a delayed send that is the document's
root, from which neither draws one. A timer edge is not a transition: it
says which block hears the event a send arms, so it is dotted where an
interrupt edge is dashed.

## Timer marks

Blocks wait on a clock in different ways, and a box says which with a
small mark at its top right: a `core.await` carries the wait mark (it
waits, in its own step, for an event or its timeout), a `core.wait`
carries the clock mark (time passes: it holds its step for its
duration, which it always has, since the duration is required), and a
`core.send` with a delay carries the clock mark too (the event it arms
fires later, after the step has moved on). The clock says time passes;
the hourglass says a step waits for an event. The clock on a wait is a
mark only: a wait hears no timer edge. A delay counts when
`StatifierBlocks.Core.Duration.duration?/1` accepts it, the test the
send's own sentence and the timer edges use; a send with no delay
carries neither mark. A mark is drawn inside its block's box, so a click
on it is a click on the box.

## The happy path runs straight

A reader follows the steps that run when nothing goes wrong, so those
are drawn on one vertical line, and what can interrupt them sits to the
side. Inside a group's pane the body's steps already share a line: ELK
centres a chain of steps on one another, against the pane's left edge.
What would bend the line is the group itself, which is wider than its
body by the rules column: an edge attached to the middle of the group
lands between the body and the column, and the steps before and after
the group follow it there.

So a group attaches its edges where its body's line is. Its node
carries two `ports`, one on its top side for the edge in and one on its
bottom side for the edge out, at a fixed x (`portConstraints:
FIXED_POS`): the group's and the pane's left padding plus half the
widest step in the body. The steps before and after the group line up
under that point, and the rules column and its dashed abandon edges
stand to the right of it. The edges themselves still join block to
block: the hook moves an edge onto its node's port only for the layout,
and hands the edge back with its own ends.

The line is placed by estimate, from the widths this module gives its
leaves, so a group puts out its ports only when every step in its body
is a leaf or an empty marker. A body that holds a container has a width
only ELK can find, so this module gives that group no ports and places
nothing: the hook lays such a graph out twice. The first layout says
where the body's steps stand; the hook then puts the group's two ports
at the centre of the widest of them, in the same shape as the ones
above, and lays the graph out again. A graph whose every group carries
its ports is laid out once. The first layout is already a whole
drawing, its line bent at the group, so when the second layout fails
the hook draws the first rather than the error pane.

## The drafts shelf

A drafts shelf holds steps an author has put down but not wired into the
flow, so the path is read without it. Its block's node carries `drafts`,
`%{"count" => count, "label" => label}`: `count` is how many steps sit on
the shelf, the blocks in its slot, and `label` is the row its drawer
reads, "Drafts: 2 steps not wired in" ("1 step" for one). The node stays
where the outline visits it, so `nodes/1` is unchanged; the hook takes it
out of the drawing before the layout and lays it out on its own, so the
path is laid out as though the shelf were not there.

The hook draws the shelf in a drawer under the drawing and outside it: a
native disclosure element whose row reads `label`, holding the shelf as
its own drawing. On a page that cannot edit the drawer is closed, and a
click on its row opens it in place; on a page that can edit it is drawn
open, in the same place. Whether it is open lives in that element, not in
the hook. The shelf's entry in the key (below) is the same row.

## The key

The graph's root carries `legend`, the key the hook draws beside the
drawing: one entry per kind of mark the drawing uses, each
`%{"mark" => mark, "line" => line, "lines" => lines}`, in a fixed
order, and none for a mark the drawing does not use. `line` is what the
mark means, in words, and `lines` is the same words wrapped to a line
somewhat wider than a box's, which is what the hook writes beside the
mark's glyph.

| Mark | Drawn where the graph has |
|---|---|
| `start` | the start dot, which every graph has |
| `end` | a `done` end mark |
| `end-abandon` | an `abandon` end mark |
| `arrow` | a solid edge: the start edge, a connector, or an edge into the `done` end |
| `fork` | a branch's band, with its fork mark |
| `join` | a `rejoin` edge, with its join dot |
| `interrupt` | a dashed edge: an interrupt edge, or an edge into the `abandon` end |
| `wait` | a block carrying the `wait` mark |
| `clock` | a block carrying the `clock` mark |
| `timer` | a timer edge |
| `drafts` | a drafts shelf, its line the drawer's own row, `label` |

The key is data in the graph, read off the graph itself, so the Map stays
pure and the key cannot name a mark the drawing lacks. The map region is
hidden from assistive technology, so the same lines reach the description
region as text: `StatifierBlocks.Map.Info.idle/4` carries them as its
"Legend" fact.

## Order is semantic, so it is forced

A branch evaluates its arms in slot order and a sequence runs its steps
in order, so a layout that swapped two arms to save a crossing would draw
a different chart. Two ELK options keep the model order, and the two are
set differently on purpose:

- `crossingMinimization.forceNodeModelOrder` is set on EVERY container,
  because under `hierarchyHandling: INCLUDE_CHILDREN` a value set on the
  root does not reach a nested graph;
- `considerModelOrder.strategy` is set on the ROOT ONLY. The root value
  is all the order this map needs, and a per-container value has been
  seen to throw inside elkjs on a differently shaped graph, so it is kept
  off the containers rather than found out again.

The test beside this module pins both.

A group's rules column is laid out on its own, and there
`forceNodeModelOrder` alone does not hold the order: rules that nothing
joins are each a connected component, and ELK packs separate components
by size, not in model order. The column keeps its components together
(`separateConnectedComponents: false`), so its rules share one layer and
that layer keeps model order.

## Sizes

Text is measured by estimate, a fixed width per character, rather than
by the browser: the graph is built on the server, and a node sized after
the page measured it would be a layout that depends on which fonts
loaded. The estimate errs wide, and no box is narrower than the widest
line it carries, its title included: a single word longer than a line
widens its box rather than running out of it, and a container is held
at least that wide whatever its children need.

# `graph_node`

```elixir
@type graph_node() :: %{required(String.t()) =&gt; term()}
```

One graph node, in elkjs's JSON shape plus the fields the hook draws
from: `kind` (with `outcome`, all the start dot and an end mark carry
beside the ELK `labels` holding their word and the `layoutOptions`
placing it),
`title` and `lines`, on a slot `style`, on a timer block
`mark`, on a group with interrupt rules `interrupts`, on a branch `band`,
`caption` and `caption_width`, on a group's rules column `caption` and
`explanation_lines`, on
a group whose body is all leaves the `ports` its edges attach to, on a
drafts shelf `drafts`, and on the selected block `selected`.

# `option`

```elixir
@type option() ::
  {:selected, String.t() | nil} | {:phrase, (String.t() -&gt; String.t() | nil)}
```

The options `graph/2` takes; see the moduledoc's "Its input".

# `t`

```elixir
@type t() :: %{required(String.t()) =&gt; term()}
```

The whole graph: the ELK root, with the start dot and the document's root
block as its two children, the start edge as its one edge, `timers`, the
timer edges the hook draws after the layout, and `legend`, the key the
hook draws beside the drawing.

# `caption`

```elixir
@spec caption(String.t()) :: String.t() | nil
```

The caption the map draws for a block of `type`, or `nil`: the whole
first sentence of the type's explanation for a branch and the two group
types, never cut; see the moduledoc's "Captions".

# `empty_text`

```elixir
@spec empty_text() :: String.t()
```

The text an empty slot's marker carries.

# `graph`

```elixir
@spec graph(StatifierBlocks.ViewModel.t(), [option()]) :: t()
```

The graph for `view_model`, ready to be JSON-encoded onto the hook.

The root carries the layout options every container inherits and the two
it alone may carry; see the moduledoc on why `considerModelOrder` is one
of them. `opts` are the host's selection and its words for event names;
see the moduledoc's "Its input".

# `interrupts`

```elixir
@spec interrupts(t() | graph_node()) :: [%{required(String.t()) =&gt; String.t()}]
```

Every interrupt edge in `graph`, groups in the order a pre-order walk
meets them and each group's rules in rail order, as
`%{"from" => rule, "group" => group, "to" => "exit" | "body"}`.

# `nodes`

```elixir
@spec nodes(t() | graph_node()) :: [String.t()]
```

The block ids in `graph`, in the order a pre-order walk of it meets them.

The map's claim to be the document is that this list is the outline's:
every block exactly once, in reading order. Slot nodes and empty markers
are not blocks and are not in it.

# `start_text`

```elixir
@spec start_text() :: String.t()
```

What starts a document, in the words the map's start edge carries and the
description region says: the one place the sentence is written.

# `timers`

```elixir
@spec timers(t()) :: [%{required(String.t()) =&gt; String.t()}]
```

Every timer edge in `graph`, in the order it is drawn, as
`%{"from" => send, "to" => rule_or_await, "event" => event, "delay" => delay}`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
