Language Reference
The FractalForm diagramming language is small and intuitive. At its simplest, a diagram is just edges:
client -> api: GET /users
api -> client: 200 OK
Two lines, two components, two arrows. Components are created automatically from edge references. Everything else on this page builds on top of that.
Quick reference
| Construct | Syntax |
|---|---|
| Component | component key "Name" <shape:browser tier> <icon:icon badge> or <<shape>> for many |
| Edge | from -> to: label |
| Icon | <icon:name [modifier]> |
| State | key: text, ` key: {...} , or key: `json {...}` ` |
| Phase | # phase: Name |
| Group | group "Name" ... end |
| Note | # note: Text |
| Overlay | # overlay: <icon:name> text |
| Comment | # text |
| Artifact | artifact $name "Label" <icon:name> |
| Clear | --clear [entities] |
| Visibility | --dim [entities], --show [entities], --hide [entities] |
| Delay | --delay 100ms |
| Step duration | --step 500ms |
| Speed | --speed 5 |
| Repeat | repeat N ... end |
| Parallel | parallel ... end |
| Reference | &name: value, &name.field: value, &name.field++ |
Syntax families
| Family | Form | Purpose |
|---|---|---|
| Comment-promoted | # phase:, # note:, # overlay: | Plain comments promoted to visible elements. Free-form prose. |
| Visual directives | <icon:>, <shape:>, <edge:> | Typed, positional fields in angle brackets. |
| Playback directives | --delay, --step, --speed, --clear, --dim, --show, --hide | Engine instructions. Not rendered. |
Components
Components are the building blocks of your diagram. You don't need to declare them — edges like api -> db create both components automatically. The component declaration is only needed when you want to customize appearance: display name, shape, tier, or icon.
component api "API Gateway" <shape:hexagon primary> <icon:globe badge>
component db "PostgreSQL" <icon:postgresql badge>
component cache <shape:hexagon>
component workers "Workers" <<shape:hexagon>>
The full syntax is:
component <key> ["Display Name"] [<shape:browser [tier]>] [<icon:globe [badge]>]
Double angle brackets (<<shape:...>>) mark the component as "many" — it renders as a stack of overlapping shapes, indicating multiple instances. Use <<shape>> (without a value) to keep the default shape but show the stack effect.
- Key:
[a-zA-Z][a-zA-Z0-9._:-]*. The stable identity id. - Display name: Quoted string. What shows up in the viewer.
- Shape + tier: Angle bracket with
shape:prefix. Tier sets visual emphasis (see below). Double brackets for many. - Icon: See Icons.
When to declare components: Most edges create components automatically. Only use component declarations when you need display names, specific shapes, tiers, or icons. If you find yourself declaring every component before using it in edges, you're over-specifying. Let the implicit creation handle the common case and declare only what needs customization.
Shapes
Each component gets a shape that hints at what it represents: a database cylinder, a browser window, a cloud service, etc. The default is a rounded rectangle.
default, circle, database, hexagon, diamond, note, actor, cloud, browser, server, funnel, code
Choosing shapes: Match the shape to the conceptual role. Use database for persistent storage, hexagon for stateless services, browser for clients, server for backend processes, cloud for managed services, funnel for load balancers/gateways, diamond for decision points or routing logic, code for modules, handlers, or functions. The shape is a visual hint that doesn't change behavior, but it makes the diagram self-documenting at a glance.
Many (multiple instances)
Double angle brackets render the component as a stack of overlapping shapes, visually indicating multiple instances (worker pools, replicated databases, load-balanced servers).
component workers "Worker Pool" <<shape:hexagon>>
component replicas "Read Replicas" <<shape:database secondary>>
component pods "Pods" <<shape>>
<<shape>> without a value uses the default shape. The stack effect works with all shapes and tiers.
When to use many: Use double brackets when the component conceptually represents more than one running instance. Worker pools, replica sets, horizontally scaled services, and container pods are all good candidates. Don't use it for a single instance that happens to handle multiple requests.
Tiers
Visual prominence: primary, secondary, tertiary, background
component api <shape:hexagon primary>
component cache <shape:hexagon secondary>
component logger <shape:default background>
Using tiers to guide attention: Set primary on 1-3 components that define the critical path (the user, the main API, the system being explained). secondary for important infrastructure that supports the flow. tertiary for optional components. background for logging, metrics, or observability components that shouldn't compete for attention. Think of tiers as visual weight that tells the viewer where to look first.
Key hierarchy
Dots and colons in component keys create parent-child relationships:
| Pattern | Meaning | Renders as |
|---|---|---|
app | Standalone component | Rendered independently |
app.child | Child of app | Foldable section inside the parent |
app:context | Facet of app | Separate component, visually attached to parent with a connector |
component app "My App" <shape:server>
component app.cpu "CPU Usage"
component app.mem "Memory"
component app:logs "App Logs" <shape:database>
Dots vs colons: Use dots (.) for internal metrics or sub-components that are conceptually part of the parent (CPU, memory, queue depth). These render as collapsible sections inside the parent component. Use colons (:) for related but independent components like separate logs storage, sidecars, or related artifacts. These render as their own components with a visual connector to the parent. If you're unsure, start with dots for tighter grouping and switch to colons if the child needs its own shape or incoming edges from other parts of the system.
Edges
The arrow encodes what kind of connection it is:
| Arrow | Line | Arrowhead | Meaning |
|---|---|---|---|
-> | solid | filled | Standard call |
--> | dashed | filled | Optional/conditional |
..> | dotted | filled | Inferred/weak |
~> | animated | filled | Streaming |
->> | solid | open | Async |
-->> | dashed | open | Async conditional |
..>> | dotted | open | Async inferred |
<-> | solid | both ends | Bidirectional |
<--> | dashed | both ends | Bidirectional conditional |
<..> | dotted | both ends | Bidirectional weak |
<~> | animated | both ends | Bidirectional streaming |
Choosing the right arrow: Use solid -> for synchronous request/response (HTTP calls, database queries). Use dashed --> when the interaction is conditional or optional (fallback paths, cache hits that skip the database). Use dotted ..> for inferred or weak relationships (configuration reads, background telemetry). Use ~> for streaming data (WebSocket updates, event streams). Use double-head ->> for async fire-and-forget (message queues, background jobs, webhooks). Use <-> for bidirectional connections where data flows both ways through a single wire (peer-to-peer sync, full-duplex channels). The arrow style is semantic and tells the reader about timing and coupling at a glance.
Bidirectional edges
Bidirectional arrows draw a single edge with arrowheads on both ends. The direction of each line determines which way the particle travels:
service-a <-> service-b: sync request
service-b <-> service-a: sync response
Both lines share the same wire. a <-> b sends a particle from A to B. b <-> a sends a particle from B to A through the same edge. All edge features work: labels, icons, modifiers, fan-out.
primary <-> replica: replicate <icon:database>
replica <-> primary: acknowledge <icon:check success>
primary <-> replica: heartbeat <edge:drop>
When to use bidirectional edges: Use <-> when two components communicate as peers over a single logical connection — database replication, WebSocket channels, peer-to-peer sync, full-duplex protocols. The single wire with two arrowheads signals that the connection is symmetric. Use separate unidirectional edges (-> and <-) when the request and response are conceptually different connections.
Labels
api -> db: SELECT users
Left component is always the subject. A -> B means A sends to B.
Fan-out / Fan-in
Send to multiple targets in one line:
api -> [worker1, worker2, worker3]: dispatch job
[service-a, service-b] -> aggregator: results
api ->> [queue1, queue2]: publish event
Works with all arrow styles. Expands into individual edges. The animation shows particles leaving the source at the same time.
When to use fan-out: Use brackets when one component sends the same message to multiple targets simultaneously. Deployments to multiple regions, parallel health checks, broadcasting events all benefit from this pattern. Write separate edges when the messages differ or timing is sequential. Fan-out is about visualizing concurrency and showing that all targets receive identical treatment.
Edge modifiers
Control particle behavior on edges using <edge:modifier>:
| Modifier | Behavior |
|---|---|
drop | Particles drop at midpoint; target component does not highlight |
error | Edge color: red |
success | Edge color: green |
payments -> api: 503 Unavailable <icon:x error>
api -> upstream: timeout <edge:drop error>
breaker -> payments: blocked <edge:drop>
payments -> api: 200 OK <icon:check success>
Showing failure and success: Combine <edge:drop error> to show a call that fails partway. The particle drops at the edge midpoint and the target doesn't activate, signaling the request never arrived. Use <icon:check success> on response edges for happy paths and <icon:x error> for explicit failures. This visual vocabulary works across retry patterns, circuit breakers, and health checks. See circuit-breaker and retry-pattern for full examples.
Icons
The <icon:name> syntax works on components, edges, and artifacts. Any Lucide icon name works, plus brands: redis, postgresql, stripe, nginx, bun, docker, and more.
component api <shape:hexagon> <icon:globe badge>
component db <shape:database> <icon:postgresql badge>
artifact $jwt "JWT Token" <icon:key-round>
api -> db: SELECT users <icon:database>
payments -> api: 200 OK <icon:check success>
On components, add badge to render the icon as a small circle on the component. On edges, the icon appears at the midpoint during playback.
Icon modifiers
Color the icon based on intent:
| Modifier | Color |
|---|---|
success | Green |
error | Red |
warning | Yellow |
info | Blue |
| (none) | Default |
State
Set the content displayed inside a component's body. There are three formats: plain text, structured data, and raw code blocks.
Choosing a format: Use plain text for simple status updates (api: Processing request). Use single-backtick JSON when you want the viewer to render the data as a structured dashboard (metrics, key-value pairs, tables). Use triple-backtick code blocks when you're showing raw payloads or want exact formatting control (API responses, logs, SQL). Start simple with text and evolve to structured data as the interaction progresses. This creates a sense of the component "filling up" with detail.
Plain text
api: Processing request
api: Waiting for upstream...
Structured data
Wrap JSON in single backticks to render it as structured, formatted content. The viewer picks a layout based on the shape of the data (tables, lists, key-value pairs, etc. - see data shapes below).
api: `{status: ok, count: 42}`
Multi-line and relaxed JSON (unquoted keys/values, single quotes, trailing commas) are supported:
api: `{
status: ok,
count: 42
}`
Raw code blocks
Use triple backticks to display raw, syntax-highlighted code instead of structured data:
api: ```json {
"status": "ok",
"data": [1, 2, 3]
}```
The language tag is optional and defaults to json. Works inline too:
api: ```json {"raw": "display"}```
Code blocks can be mixed with text. Text before and after the fenced block renders as separate lines:
api: Request body: ```json {"user": "alice"}``` Accepted
This renders three parts: the text "Request body:", a highlighted JSON block, and the text "Accepted".
Code blocks work on child components, artifacts, and all other state targets:
api.response: ```json {
"status": 200,
"body": {"id": "usr_123"}
}```
$jwt: ```json {"sub": "user123", "exp": 3600}```
How data shapes render
The viewer picks a layout based on the structure of the data inside single backticks. See all of them in action in the content formats example.
Headline
# { text: string }
api: `{
text: "Processing"
}`

Key-value pairs
# { [key]: string | number | boolean }
api: `{
status: processing,
queue_depth: 12
}`

Bullet list
# { [key]: string[] }
api: `{
tags: [auth, v2, beta]
}`

Vertical key/value entries
# { [key]: [string, string][] }
api: `{
headers: [
[Content-Type, application/json]
]
}`

Definition list
# { [key]: { name, value }[] }
api: `{
params: [
{name: timeout, value: 30}
]
}`

Metrics (definition list with unit)
# { [key]: { name, value, unit }[] }
api: `{
metrics: [
{name: latency, value: 42, unit: ms},
{name: throughput, value: 1200, unit: req/s}
]
}`

Table
# { [key]: { ...3+ scalar keys }[] }
api: `{
users: [
{name: alice, role: admin, active: true},
{name: bob, role: editor, active: true},
{name: carol, role: viewer, active: false}
]
}`

Error panel
The error key with code and message fields must be present for the viewer to recognize it as an error.
# { error: { code, message } }
api: `{
error: {
code: 404,
message: "Not found"
}
}`

Collapsible grouped section
A nested object with scalar values renders as a collapsible section. The key becomes the section header.
# { [key]: { scalar, scalar, ... } }
api: `{
cache: {
hits: 12,
misses: 3
}
}`

Keys are automatically humanized for display: queue_depth becomes "Queue Depth", requestHeaders becomes "Request Headers".
Phases
Label the stages of a flow. Phases become clickable elements in the viewer for quick navigation. A regular comment that starts with phase: is promoted to a phase.
# phase: Authentication
# phase: Data retrieval
# phase: Response
Phases with descriptions - trailing comment lines after # phase: become the phase note:
# phase: Authentication
# Validates credentials and issues a short-lived token.
# The token expires after 15 minutes.
When to use phases: Use phases when your flow has 3+ distinct stages (initialization, processing, error handling). Each phase should represent a conceptual shift in what's happening. Single-phase diagrams don't need them. Phases become navigation targets in the viewer, so they're most useful for longer sequences where a reader might want to jump to "the retry logic" or "token revocation" directly. Adding description lines (continuation comments) documents the phase's purpose without cluttering the main flow.
Groups
Draws a visual border around components.
group "Infrastructure"
component nginx <shape:funnel>
component app <shape:server>
component db <shape:database>
end
When to use groups: Use groups to show organizational boundaries like infrastructure layers (frontend / backend / data), cloud regions (US / EU / AP), or team ownership (payments team services). Groups add visual structure but also cognitive overhead. Not every cluster of related components needs a group. If components naturally cluster by their edges and positions, the group border may be redundant. Reserve groups for cases where the boundary itself conveys important information.
Notes
Floating annotations that appear near components, edges, or phases. A regular comment that starts with note: is promoted to a note.
# note: Redis avoids a full Postgres round-trip here.
Multi-line notes with continuation:
# note: JWTs are stateless - the server can't "delete" one.
# A short-lived denylist is the workaround.
With a duration before the colon they auto-dismiss:
# note 3s: Stripe responds after fraud checks.
# note 500ms: Quick hint
When to use notes: Use notes to explain design decisions, call out non-obvious behavior, or document constraints that aren't visible in the flow itself (like "JWTs can't be deleted server-side"). Don't use notes to label obvious interactions. The edge labels already do that. Notes attach to the next topology command, so place them directly before the line they explain. Use timed notes (# note 2s:) for ephemeral callouts and persistent notes for key architectural insights that should stay visible.
Overlay
Canvas-level visual markers that render above the entire diagram. Use these to signify temporal breaks ("5 hours later"), transitions between scenarios, or narrative moments that aren't tied to a specific component or edge.
Overlays are visual elements, not playback instructions. They render when playback reaches their position in the sequence, and dismiss when the next event fires.
# overlay: <icon:clock> 5 hours later
# overlay: <icon:coffee> Taking a break
# overlay: <icon:alert-triangle error> System degraded
The icon uses the standard <icon:name [modifier]> syntax. The optional modifier colors the icon: success (green), error (red), warning (yellow), info (blue).
Interactive pause
Add pause before the colon to halt playback until the user clicks or presses any key:
# overlay pause: <icon:activity> Real-time prices flow into the trading desk
# overlay pause: <icon:clock> 5 hours later
When a paused overlay appears, a subtle "Press any key to continue" hint displays at the bottom. The overlay remains visible until the user interacts with it.
Without an icon
The icon is optional. Text-only overlays work:
# overlay: Intermission
# overlay pause: Intermission
Composed with delay
Overlays are purely visual. If you want the animation to pause while the overlay is visible, combine with --delay:
api -> db: start replication
# overlay: <icon:clock> 5 hours later
--delay 5s
db -> api: sync complete
The overlay appears when playback reaches that line, pauses for 5 seconds, then dismisses as the next event fires.
Composed with phases
Overlays work well before phase transitions to set the scene:
api -> user: 200 OK
# overlay: <icon:clock> Token expires
--delay 3s
# phase: Token Expiry
user -> api: GET /profile
api -> user: 401 Unauthorized
When to use overlays: Use overlays for narrative breaks like time skips ("5 hours later"), scenario transitions, or dramatic moments that affect the entire system (not just one component). Overlays are visual punctuation. Compose with --delay to pause the animation for a fixed duration, or use pause for interactive control where users advance at their own pace. Overlays work well before phase transitions to set context for what's about to change. See circuit-breaker for overlay-driven scenario shifts.
Comments
Plain comments are ignored by the parser:
# This is a comment
# Comments can document the flow
Artifacts
Artifacts are data objects that move through the topology. JWTs, orders, messages. Different from components, which are structural and static.
$name in an edge label is regular text by default. It becomes an artifact reference only when the name has been declared with artifact or given state with $name: ....
artifact $<name> ["Label"] [<icon:name>]
artifact $jwt "JWT Token" <icon:key-round>
artifact $order "Order" <icon:shopping-cart>
Setting artifact state
Prefix with $:
$jwt: sub = user123
$jwt: `{"sub": "user123", "exp": 3600}`
Instances
# creates distinct copies of the same artifact type:
When the same artifact type appears in multiple places at once, instances let you tell them apart. Each #suffix tracks its own state independently in the diagram. They inherit the parent's color with a slight tint variation.
$jwt#session1: sub = alice
$jwt#session2: sub = bob
In edge labels
Use $name in an edge label to attach the artifact to that interaction:
auth -> user: issue token $jwt
api -> db: store $order
Artifacts vs plain labels: Use artifacts when a data object has identity and moves through multiple stages. JWTs that get issued, validated, and revoked; orders that get created, processed, and completed; messages that route through queues are all good candidates. Plain labels like "GET /users" describe transient actions. Artifacts become visual elements in the timeline. Use instances ($jwt#session1, $order#7291) when the same artifact type appears in multiple simultaneous contexts like multiple active sessions, concurrent orders, or parallel message streams. See jwt-auth for artifact lifecycle and event-driven for instances.
Playback directives
-- directives are instructions to the playback engine. They control animation timing, canvas state, and component visibility without rendering visible elements in the diagram.
Clear
Resets visual state: component content, edge labels, edge modifiers, activation glow, and artifact snapshots. The full event history is preserved for rewind/seek. --clear only affects what's visible during forward playback.
By default, --clear resets every component. Add comma-separated component keys to clear specific ones:
# reset everything
--clear
# reset api and cache only
--clear api, cache
Use it to separate independent scenarios in the same diagram:
# phase: Happy path
user -> api: GET /users
api -> db: SELECT *
db -> api: rows
api -> user: 200 OK
--clear
# phase: Error path
user -> api: GET /users
api -> db: SELECT *
db -> api: connection timeout
api: 503 Service Unavailable
api -> user: 503 Error
When to use clear: Use --clear to separate independent scenarios in the same diagram. Compare happy path against error path, show before-and-after for a migration, or demonstrate multiple usage patterns of the same system. Don't use --clear within a single continuous narrative. Each --clear is a conceptual reset that gives the viewer a fresh canvas. Pair with phases to give each scenario a label. See health-check for a clear-driven multi-scenario example.
Visibility
Control component visibility mid-flow with --dim, --show, and --hide.
# dim specific components
--dim ca, crl
# show specific components
--show user, api
# hide specific components
--hide logger
# no targets = apply to all components
--dim
--show
--hide
No targets = apply to all components. Comma-separated component keys = apply to specific ones.
Use --dim to de-emphasize infrastructure components that aren't involved in the current phase. Use --show to reveal components progressively as the flow reaches them. Use --hide to remove components entirely (they don't render at all). Combine with phases for narrative control:
component client "Service A" <shape:hexagon primary>
component server "Service B" <shape:hexagon primary>
component ca "Certificate Authority" <shape:cloud secondary>
component crl "CRL / OCSP" <shape:database>
# phase: Certificate provisioning
--dim client, server
ca: Validating CSRs
client -> ca: CSR request
ca -> client: Signed certificate
--show client, server
--dim ca, crl
# phase: mTLS handshake
client -> server: ClientHello
server -> client: ServerHello + Certificate
See mtls-setup for visibility-driven phasing.
Delay
--delay 100ms
--delay 2s
--delay 0.5s
Step duration
Default animation speed for all events:
--step 500ms
Delay vs step: Use --step to set the global animation pace for the entire diagram. Use --delay for a one-off pause at a specific moment (waiting for an external system, showing elapsed time). --step 200ms makes everything fast; --step 1s slows it down for presentations. --delay is local punctuation.
Speed
Speed up or slow down sections of the diagram:
--speed 5
--speed with no argument resets to normal (1x).
--speed 5
repeat 5
device -> cdn: GET next chunk
cdn -> device: 200 OK
end
--speed
Use --speed with repeat to show high-throughput activity like streaming, polling, or bulk transfers. The rapid-fire visual conveys continuous flow without the viewer waiting through each exchange. Reset with --speed before returning to step-by-step narration. See netflix-streaming for an example.
Repeat blocks
repeat 3
client -> api: retry
--delay 100ms
end
Use repeat for polling loops, retry attempts, or heartbeats. Combine with --delay to show the interval between iterations. Repeat is syntactic sugar for duplicating the block N times. See retry-pattern for retry composition.
Parallel blocks
Execute multiple actions simultaneously. Everything inside a parallel block fires at the same instant during playback. Edges animate, components activate, and state updates all happen together.
parallel
api -> worker-a: task A
api -> worker-b: task B
cache: invalidated
end
Use parallel blocks when multiple things genuinely happen at the same instant. Deployments to three regions, fan-out health checks, concurrent database writes. The animation shows all edges firing simultaneously. Don't use parallel for events that happen quickly in sequence but aren't truly concurrent.
References
References (&name) let you define reusable state that evolves over time. Declare once, update fields incrementally, and assign to any component to show the current snapshot.
&state: `{text: "CLOSED", failures: 0, threshold: 3}`
breaker: &state
&state.failures++
&state.text: "OPEN"
breaker: &state
Operations: &name.field: value sets a field, &name.field++ and &name.field-- increment/decrement numbers. References also interpolate in text: api: Status is &status.
Use references when state evolves incrementally across phases (failure counters, connection status). For simple one-off state, inline structured data is clearer. See circuit-breaker for an example.
Full examples
JWT token revocation
End to end. Covers components, artifacts, edges, state, phases, and notes. Open it in the Editor.
artifact $jwt "JWT Token" <icon:key-round>
component user "User" <shape:browser primary> <icon:user badge>
component admin "Admin" <shape:browser> <icon:shield badge>
component auth "Auth Service" <shape:hexagon> <icon:shield badge>
component api "API Gateway" <shape:server> <icon:server badge>
component cache "Revocation Cache" <shape:database secondary> <icon:database badge>
component db "User DB" <shape:database> <icon:database badge>
# phase: Normal login
# JWTs are stateless - the server can't "delete" one.
# A short-lived denylist is the workaround.
user -> auth: POST /login
auth -> db: verify credentials
db -> auth: valid
$jwt: `{
jti: "tok_8f3a",
sub: "user123",
exp: 1710003600,
scope: "read write"
}`
auth -> user: 200 OK $jwt
# phase: Authenticated requests (happy path)
user -> api: GET /profile $jwt
api -> cache: CHECK jti tok_8f3a
cache -> api: not revoked
api: JWT Token valid
api -> db: SELECT user WHERE id = user123
db -> api: user profile
api -> user: 200 profile data
# phase: Admin revokes the token
admin -> auth: POST /revoke tok_8f3a
auth -> cache: SET tok_8f3a revoked (TTL = token exp)
# note: TTL = token expiration, so the denylist self-cleans
# and never grows unbounded.
cache: tok_8f3a → revoked
auth -> admin: 200 token revoked
# phase: Revoked token rejected
user -> api: GET /orders $jwt
api -> cache: CHECK jti tok_8f3a
cache -> api: REVOKED
api: token revoked!
api -> user: 401 Unauthorized
Health check with clear
Uses --clear to reset the canvas between independent scenarios, and icons on edges to show pass/fail at a glance. Open it in the Editor.
component lb "Load Balancer" <shape:hexagon primary> <icon:network badge>
component api "API" <shape:server> <icon:server badge>
group "Dependencies"
component db "Database" <shape:database> <icon:postgresql badge>
component cache "Cache" <shape:database secondary> <icon:redis badge>
end
# phase: All healthy
lb -> api: GET /health
api -> db: SELECT 1
db -> api: OK <icon:check success>
api -> cache: PING
cache -> api: PONG <icon:check success>
api: `{
status: healthy,
db: connected,
cache: connected,
latency_ms: 4
}`
api -> lb: 200 OK <icon:check success>
--clear
# phase: Database down
lb -> api: GET /health
api -> db: SELECT 1
db -> api: connection refused <icon:x error xl> <edge:error>
api -> cache: PING
cache -> api: PONG <icon:check success>
api: `{
status: degraded,
error: {
code: "DB_UNAVAILABLE",
message: "Database connection refused"
}
}`
api -> lb: 503 Degraded <icon:alert-triangle warning> <edge:error>
Parallel deployment
Deploys to three regions simultaneously using parallel blocks. Open it in the Editor.
component dev "Developer" <shape:browser primary> <icon:user badge>
component ci "CI Server" <shape:hexagon primary> <icon:github badge>
component registry "Container Registry" <shape:database> <icon:docker badge>
group "Regions"
component us "US Region" <shape:server> <icon:server badge>
component eu "EU Region" <shape:server> <icon:server badge>
component ap "AP Region" <shape:server> <icon:server badge>
end
component monitor "Monitor" <shape:hexagon secondary> <icon:activity badge>
# phase: Build
dev -> ci: git push main
ci -> registry: push app:v2.41 <icon:package>
registry -> ci: pushed <icon:check success>
# phase: Deploy to all regions
parallel
ci -> us: deploy app:v2.41
ci -> eu: deploy app:v2.41
ci -> ap: deploy app:v2.41
end
parallel
us: `{ status: running, version: "v2.41", replicas: "3/3" }`
eu: `{ status: running, version: "v2.41", replicas: "3/3" }`
ap: `{ status: running, version: "v2.41", replicas: "3/3" }`
end
# phase: Smoke tests
parallel
us -> monitor: health OK <icon:check success>
eu -> monitor: health OK <icon:check success>
ap -> monitor: health OK <icon:check success>
end
monitor -> ci: all regions healthy <icon:check success>
ci -> dev: deploy complete <icon:check success>