Skip to content

Story view and lanes

The story is the version of a scenario to put in front of someone who has not seen the system before. It reads as swimlanes: one column per actor, the steps running down the page in order, and the phases as horizontal bands.

The whole platform. Hover a box or a line to follow it, click a box for details, or pick a scenario to watch a job travel.

Scenario
The story in one breath

An operator opens a list. The management backend works out who they are, which device they mean and whether they may see it. Then it forwards the same call through the hub and the tunnel to the device, which checks again, asks the service, and sends the answer back along the same road.

The request going outThe answer coming backA lookup in a data store
IThe click
IIWho, where, may they?
IIIAcross the tunnel
IVAt the device
VThe answerthe same road back
Operator browsermanagement UI (module federation)
Management proxyreverse proxy
Management backendmiddleware
Management storesredis · postgres · openfga
Networkhub ⇒ tunnel ⇒ spoke
Device backendnginx · API
Device storespostgres-device · openfga
The serviceruns in its own container
1 ·Click
GET /​api/​devices/{id}/​services
2 ·The front door
TLS · app.* → backend :9100
3 ·Who are you?
session cookie → session
tokens read from Redis
redis
session + tokens → found
4 ·Which device and org?
device → URL + organisation
postgres
device → URL, org → row
5 ·May they view it?
authorisation check in the org's own store
openfga
view · org store → allowed
6 ·Is it licensed?
feature + licence checked on the ledger
postgres
licence ledger → licensed
7 ·Cross the tunnel
same call, same token
8 ·Device front door
hub ⇒ tunnel ⇒ spoke VM ⇒ device LAN
9 ·Check the token again
nginx → dx-backend :8080
postgres-device
licence snapshot → valid
openfga
view · instance → allowed
10 ·Ask the service
docker API · proxy
11 ·Read the instance
the list of monitors
12 ·Shape the answer
device ⇒ hub ⇒ management
13 ·The list appears
rendered in the browser
IThe click
1
Operator browserClick
2
Operator browser→nginx · mgmt-proxy
IIWho, where, may they?
3
nginx · mgmt-proxy→management-backend
4
management-backend→redissession + tokens
5
redis→management-backendfound
6
management-backendWhich device and org?
7
management-backend→postgresdevice → URL, org
8
postgres→management-backendrow
9
management-backendMay they view it?
10
management-backend→openfgaview · org store
11
openfga→management-backendallowed
12
management-backendIs it licensed?
13
management-backend→postgreslicence ledger
14
postgres→management-backendlicensed
IIIAcross the tunnel
15
management-backend→Hub · gateway
16
Hub · gateway→nginx (device)via Gateway VM · spoke
IVAt the device
17
nginx (device)→dx-backend
18
dx-backend→postgres-devicelicence snapshot
19
postgres-device→dx-backendvalid
20
dx-backend→openfgaview · instance
21
openfga→dx-backendallowed
22
dx-backend→VMS
VThe answerthe same road back
23
VMS→dx-backend
24
dx-backend→management-backendvia Gateway VM · spoke, Hub · gateway
25
management-backend→Operator browservia nginx · mgmt-proxy
KeyFrom → ToKindAuthWhat travels
AOperator browser → nginx · mgmt-proxyhttpIdentity session (OIDC)management UI and API calls
Bnginx · mgmt-proxy → identityhttpnone (public login pages)OIDC redirects and token endpoint
Cnginx · mgmt-proxy → control-apihttpagent API keyagent websocket upgrade
Dnginx · mgmt-proxy → management-backendhttpbearer JWTUI host and REST API
Emanagement-backend → control-apihttpservice tokenjobs, tunnels, policies
Fmanagement-backend → vision-apihttp—inference requests
Gmanagement-backend → management-iothttp—device telemetry queries
Hmanagement-backend → Hub · gatewayhttpmTLScalls forwarded to a device
IHub · gateway → nginx · mgmt-proxychannelagent API keycontrol websocket
Jmanagement-backend ↔ Hub · gatewaychannelmTLSconfig and state sync
KHub · gateway ↔ Gateway VM · spoketunnelIPsec / WireGuard / OpenVPNall management traffic
LHub · gateway ↔ More spokes × Ntunnel——
MGateway VM · spoke → nginx (device)http—forwarded management calls
NLocal operator → nginx (device)httpdevice realm sessiondevice UI
Onginx (device) → dx-backendhttp—device app and API
Pdx-backend ↔ Gateway VM · spokechannel—job relay
Qdx-backend → VMShttp—container lifecycle

You do not write a second file. The story is drawn from the same steps as the packets and the sequence diagram. Use the Flow, Sequence and Story buttons beside the player to switch.

Every step becomes a box in the lane where it happens.

Step In the story
at a: ... A box in a’s lane.
a -> b: ... A box in b’s lane, joined to the box before it by an arrow. If the story is not already at a, a small box shows where it starts.
a -> b: { kind: lookup } A dashed box beside the step that asked, in b’s lane, with a double-headed dotted arrow. The answer that follows (b -> a: ...) is folded into the same box.

Boxes are numbered 1, 2, 3 along the story. Lookups are not numbered. Click a box to play that step. The numbers match the step list under the player.

With no extra YAML you get a lane per node, in the order the nodes first appear in the scenario. To shape it, add up to three optional things.

scenarios:
request-story:
title: One read request, end to end
mode: story # open as a story; flow and sequence stay one click away
lanes: # columns, left to right
- { title: Operator browser, nodes: [operator] }
- { title: Management stores, sub: Redis · Postgres, nodes: [redis, postgres] }
- { title: Network, sub: hub ⇒ tunnel ⇒ spoke, nodes: [hub, spoke] }
phases:
- title: Who, where, may they?
caption: on the management side # shown under the title in the gutter
steps:
- proxy -> backend:
title: Who are you? # the box heading (defaults to the label)
detail: | # one to three short lines under it
session cookie → session
tokens read from Redis
- backend -> redis: { label: session + tokens, kind: lookup }
- redis -> backend: found

mode: story opens the scenario as a story. Without it, a scenario opens as packets over the diagram (flow). Set mode: sequence for a sequence diagram. The reader can always switch.

To show a story on a page without the scenario picker, pass mode to the component:

<Diagram name="platform" scenario="request-story" mode="story" />

A lane has a title, an optional sub (which defaults to the node’s sub, or the node titles joined) and the nodes it holds. A lane can hold several nodes, which is how you show “the data stores” or “the network” as one column.

List the lanes in the order you want. Nodes you leave out get a lane of their own at the end, and docspp check tells you which, so a forgotten node is never silent. A node can be in only one lane.

On a step, title is the heading of the box and detail is one to three short lines under it. Write them for a person who does not know the system:

Without With
management-backend -> redis Who are you? session cookie → session
Hub-gateway -> nginx (device) Cross the tunnel same call, same token

A step without a title uses its label, and without a detail shows from → to.

  • Phases carry the narrative. Name them as questions or stages: “Who, where, may they?”, “Across the tunnel”, “The answer”.
  • Use lookups for reads of stores. They fold into side boxes, so the main line stays about what the request does, not about every query.
  • Write a summary. In the story view it appears as “The story in one breath” above the lanes.
  • Keep lanes to about eight. More than that and the story scrolls sideways.
Scenario
The story in one breath

Stripe refuses the card. The payment stays pending and the shopper is told why.

The request going outSomething goes wrongA lookup in a data store
I
Web app
Payments API:8080
Ledger database
Stripecard processor
Web app
1 ·POST /​payments
Web app → Payments API
Ledger database
INSERT payment (pending)
2 ·create payment intent
Payments API → Stripe
3 ·card_​declined
Stripe → Payments API
4 ·mark payment failed
5 ·402 Payment Required
Payments API → Web app
1
Web app→Payments APIPOST /payments
2
Payments API→Ledger databaseINSERT payment (pending)
3
Payments API→Stripecreate payment intent
4
Stripe→Payments APIcard_declined
5
Payments APImark payment failed
6
Payments API→Web app402 Payment Required

The body carries the decline code so the UI can say something useful.