Skip to main content

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​

The two gestures the whole builder rests on, shown one step at a time. Drag the plus handle onto empty canvas to make a node and join it in one go, or onto a node that already exists to join to that instead.
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​

SettingWhat it acceptsDefaultWhat it does
idtext, up to 128 charactersrequiredThe 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.
typestart, standard, endrequiredThe node's role. See below.
nametext, up to 200 charactersnoneThe 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.
codetext, up to 128 charactersnoneA readable label for the node, derived from the name. Cosmetic.
actionsa listemptyWhat runs the moment a participant arrives. See Actions.
transitionsa listemptyThe 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​

SettingWhat it acceptsDefaultWhat it does
toNodeIdthe id of a node in this journeyrequiredWhere the arrow goes. A target that is not a node is refused with transition_target_missing.
conditiona condition, or nothingnothingWhether the arrow is open. No condition means always open.
prioritya whole number from 0 upnoneThe 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 id values are unique. Duplicates are refused with duplicate_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 with multiple_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 end node must have at least one arrow out. A node with none is refused with dead_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.