Product design skills

references/type-sequence.md

A supporting file of the diagram-design skill.

Sequence

Best for: request/response flows, protocol exchanges, multi-actor interactions over time, API call traces, incident reconstructions, auth/token refresh paths with branching.

Layout conventions

  • Actors as boxes in a horizontal row at the top.
  • Lifelines: dashed vertical lines descending from each actor to the bottom.
  • Messages: horizontal arrows between lifelines; time flows top→down.
  • Activation bar: narrow rectangle (w=8, muted fill, 0.8 hairline stroke) on a lifeline spanning the interval that actor holds control. Stack for nested calls.
  • Self-messages: short U-shaped loop returning to the same lifeline; label right of the loop.
  • Return messages: dashed stroke + filled marker (never open). Prefer muted; optionally match the originating call color when pairing multi-hop stacks. Headline success may use solid coral (see Message kinds).
  • Coral on the primary success response or headline message — one, maybe two. Actor focal strokes do not count toward the coral message budget.
  • When the flow branches (valid vs invalid token, retry, optional step), draw a combined fragment frame — do not invent free-floating if/else arrow clusters.

Message kinds

KindStrokeMarkerWhen
Call (sync)solid muted or link-bluefilledRequest that expects a reply
Returndashed muted (or match call color)filledReply to a sync call — never solid
Async / fire-and-forgetdashed mutedopen arrowheadBeacons, events, one-way notify
Headline successsolid accent (≤1–2 messages)accent filledPrimary happy-path response only

Open arrowhead (async)

Define once in <defs> and use for fire-and-forget only:

<marker id="arrow-open" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
  <polyline points="0 0, 8 3, 0 6" fill="none" stroke="#4f5d75" stroke-width="1.2"/>
</marker>

Dark mode: stroke #bfc0c0 (muted on dark paper). Do not fill the open marker — the hollow head is the async signal. Return messages keep the filled marker even when dashed.

Combined fragments (alt / opt / loop)

Use a rectangular frame that spans only the lifelines participating in the branch. Operator label is Geist Mono, uppercase, in a small tab at the top-left of the frame. Time still flows top→down inside the frame.

Frame primitive (shared)

<!-- Frame: light ink wash + hairline. Label tab top-left. -->
<rect x="X" y="Y" width="W" height="H" rx="4"
      fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.22)" stroke-width="1"/>
<!-- Operator tab -->
<rect x="X" y="Y" width="40" height="16" rx="2"
      fill="#f5f5f5" stroke="rgba(45,49,66,0.22)" stroke-width="1"/>
<text x="X+20" y="Y+12" fill="#4f5d75" font-size="8"
      font-family="'Geist Mono', monospace" text-anchor="middle"
      letter-spacing="0.12em">ALT</text>

Dark mode: frame fill rgba(245,245,245,0.04), stroke rgba(245,245,245,0.22), tab fill = dark paper (#2d3142), tab text = dark muted (#bfc0c0).

Operators

OperatorRegionsDividerGuard label
opt1none[if condition] under the tab (Geist Mono 8px)
alt2 maxdashed horizontal hairline across the frame[guard] on region 1; [else] (or a second guard) on region 2
loop1none[for each item] or [retry ≤ 3] under the tab

Guard + divider primitives

<!-- Guard: left-aligned inside the frame, mono -->
<text x="X+12" y="GUARD_Y" fill="#4f5d75" font-size="8"
      font-family="'Geist Mono', monospace" letter-spacing="0.04em">[token valid]</text>

<!-- alt region divider -->
<line x1="X+8" y1="DIV_Y" x2="X+W-8" y2="DIV_Y"
      stroke="rgba(45,49,66,0.20)" stroke-width="1" stroke-dasharray="4,3"/>

Fragment layout rules

  • Frame left/right inset ≥12px from the outermost participating lifeline centers (so activation bars stay inside the frame).
  • ≥24px between consecutive message y-levels inside a region (4px grid).
  • Guard sits in the first ~20px under the tab; first message in that region is ≥24px below the guard baseline.
  • Divider y on the 4px grid; ≥16px clear of messages above and below.
  • Nested fragments: max 1 level. Prefer two separate diagrams over deep nesting.
  • Default: one fragment per diagram. A second only if both stay under the complexity budget.
  • Coral stays on one headline success message across the whole diagram (usually the happy-path return inside the first alt region, or the final success outside a loop). Do not coral both alt branches.

Out of scope (do not invent)

  • par, critical, break, ref, and other UML operators — second PR if needed.
  • Participant create/destroy, found/lost messages, duration timing bars.

Complexity budget (sequence-specific)

  • Max lifelines: 5 (same as SKILL.md §7).
  • Max messages (arrows): 12.
  • Max combined fragments: 1 (hard default); 2 only if each is a single-region opt/loop.
  • Max alt regions: 2.
  • Max fragment nesting depth: 1.
  • Max coral elements: 2 (prefer 1 for fragment diagrams).

If you exceed, split: overview (happy path) + detail (failure / refresh path).

Lifeline primitive

<line x1="CX" y1="TOP" x2="CX" y2="BOTTOM"
      stroke="rgba(45,49,66,0.20)" stroke-width="1" stroke-dasharray="3,3"/>

Activation bar primitive

<rect x="CX-4" y="TOP" width="8" height="H"
      fill="rgba(45,49,66,0.06)" stroke="#4f5d75" stroke-width="0.8"/>

Anti-patterns

  • Message arrow pointing upward (reverses time — never).
  • Activation bars that never close.
  • Labels sitting over another lifeline — shorten or shift y into a gap.
  • Swimlane-style lanes instead of lifelines (different grammar).
  • Drawing if/else as two free-floating arrow clusters with no fragment frame.
  • Nested alt inside alt (split into two diagrams).
  • Fragment operator label in Geist sans — must be mono: ALT / OPT / LOOP.
  • Coral on both alt branches.
  • Frame that covers actors with no messages inside the fragment.
  • Filled arrowhead on async fire-and-forget (use open marker).
  • Open arrowhead on return messages (returns stay filled + dashed).

Examples

On this page