Nodes and transitions
In the portal: Journeys.
What it is
A node is one step of a journey. A transition is a one-way arrow from one node to another.
A participant is always sitting on exactly one node of each journey they hold. The node they are on decides what has already been given to them. The arrows out of it decide where they can go next.
Nodes carry no content. They carry a list of things to do on arrival, and a list of ways out.
What it looks like
start standard end
+---------+ always +-------------+ score>=10 +---------+
| welcome |-------------->| sleep-check |------------->| support |
+---------+ +-------------+ priority 0 +---------+
|
| submitted priority 1
v
+----------+
| week-one |
+----------+
A journey is built from three shapes, and that is all there is: a single entry, interior steps, and one or more exits.
When you would add one
Add a node when the participant needs to be somewhere different, meaning the answer to "what happens next" has changed.
A worked case. A four week course delivers reading every week. That is four nodes, because after week two's reading the participant is in a different position from after week one's. It is not four nodes because there are four pieces of content: if all four went out at once with different timings on their deliveries, one node would do.
Do not add a node to hold content. Content lives in a sequence, and a node just hands it over.
The controls
On a node
| Setting | What it accepts | Default | What it does |
|---|---|---|---|
id | text, up to 128 characters | required | The name arrows point at. Unique inside the journey. Not a catalog code, so a plain slug is fine. It is the wiring: changing it breaks every arrow into that node. |
type | start, standard, end | required | The node's role. See below. |
name | text, up to 200 characters | none | The label shown on the participant's journey progress card and in the builder. Arrows point at the id, not at this, so a node can be renamed at any time. |
code | text, up to 128 characters | none | A readable label for the node, derived from the name. Cosmetic. |
actions | a list | empty | What runs the moment a participant arrives. See Actions. |
transitions | a list | empty | The ways out. |
The three types, and what each one actually changes.
start is the entry point. A journey starts a participant here when it is assigned.
standard is an interior step with no special behaviour at all.
end is terminal. When a participant reaches an end node the journey is marked
complete and the pass stops there. An end node's own arrows are never evaluated,
even if somebody authored some. Give an end node no arrows, so the file says what
it means.
Beyond that, the types are labels rather than fences. An arrow may run into a start node, and an arrow may run out of an end node. Neither is rejected, and the second one does nothing.
On a transition
| Setting | What it accepts | Default | What it does |
|---|---|---|---|
toNodeId | the id of a node in this journey | required | Where the arrow goes. A target that is not a node is refused with transition_target_missing. |
condition | a condition, or nothing | nothing | Whether the arrow is open. No condition means always open. |
priority | a whole number from 0 up | none | The order arrows are checked in. Lower is checked first. |
How it runs
Arrows are checked in a fixed order, and the first open one wins. Arrows with an
explicit priority go first, lowest number first. Everything with no priority goes
after them, in the order you wrote them. Ties keep their written order.
That order is the whole branching mechanism, so put the specific case above the general one. An unconditional arrow written first swallows every branch below it.
If no arrow is open, the participant stays where they are until something changes that opens one. That is the mechanism a wait is built from: a timed step is a node whose only arrow opens once enough seconds have passed.
So give every branching node a way out that can always fire. An arrow with no
condition, written last, is the "otherwise" branch, and it is enough on its own. Where
the participant should have finished something first, an .at exists catch-all does
the same job. Write one whenever every other arrow out of the node tests something
that could stay false: a score nobody reaches, a tag nobody carries, a body measure
the phone has not sent yet.
Once a participant moves, the new node's actions run straight away, and then the new node's arrows are checked as well. So a chain of unconditional arrows is walked in one go, and every node along it runs its actions.
Arrows read the answer in hand, actions read the answer on file. An action gated
on the submission that opened the arrow into its own node does not run: the
submission is spent by the time the node's actions start. Gate it on
latestResponse.<sequence> instead: same number, held on file. The full rule is on
Conditions.
A node in the middle of a live journey is a place people are standing. Editing its actions changes what future arrivals get, and does nothing for anybody already past it. If you need to reach people who have already been through, that is a new node or a new journey, not an edit.
Loops are allowed. A node may point back at itself or at an earlier node, and that is how a repeating check-in is built. Put a condition on the arrow that goes back, so each lap needs something from the participant. A safeguard stops any single pass after as many steps as the journey has nodes and writes a warning, so a loop carrying no conditions at all can never spin the app.
What has to be true before you can publish
Some rules apply to every save, including a draft. The rest apply only when the journey is published, so a work in progress is not blocked.
Always checked, draft or published:
- Node
idvalues are unique. Duplicates are refused withduplicate_node_id. - Every arrow points at a node that exists.
- Every waiting period is at least two seconds. See Schedules and recurrence.
Checked only when you publish:
- Exactly one start node. None is refused with
start_node_missing, more than one withmultiple_start_nodes. A draft saves either way, so this is the check that settles it before anybody is assigned. - No dead ends. Every node that is not an
endnode must have at least one arrow out. A node with none is refused withdead_end_node.
A node with no actions passes both. A pure routing node is a normal thing to write.
The publish check asks whether each node has a way out, not whether anything leads in. So trace your graph forward from the start node before you publish anything with more than a handful of branches, and satisfy yourself that every node can be reached.
Publishing a journey with a node that nothing can reach is refused, and the refusal names the node.
*Today: the builder draws a warning on a node nothing leads to, and lets you publish anyway. Read the warnings before you publish.Worked shapes
A straight line. Start, three standard nodes, one end. Every arrow conditional on the step before being finished. This is a guided course, and it is the most common journey there is.
A fork that rejoins. One node with two arrows, priority 0 for the specific case and priority 1 for everything else, both eventually pointing at the same node downstream. The specific branch usually does a little more work on the way through: a tag, a task, a message.
A self-loop. One node that delivers a check-in and has one arrow pointing back at itself, conditional on the check-in being submitted. Every submission re-runs the node's actions and hands out a fresh delivery. Pair it with a second arrow, higher priority, that leaves the loop when the participant has done enough.
Next
Conditions is the grammar for every rule on every arrow, and it is where the real power and the real traps live.