feat(sequence): combined fragments (alt/opt/loop) + OAuth example (#13)

Expand type-sequence.md with fragment frames, async open arrowheads,
complexity budget, and anti-patterns. Add bearer+refresh special examples
(light/dark/full) without changing the cold-cache trio. Wire README/SKILL
budget and a verify script that drives lint-skin.py.

Co-authored-by: wolf10drc <alaa@golead.io>
This commit is contained in:
ActArtech
2026-08-10 11:44:04 -07:00
committed by GitHub
co-authored by wolf10drc
parent a157f76164
commit 8827b27739
7 changed files with 996 additions and 8 deletions
+4 -1
View File
@@ -180,6 +180,8 @@ open ~/.claude/skills/diagram-design/assets/index.html
# "Make me an architecture diagram of my app: frontend, backend, database, Redis cache."
# "I need a quadrant showing Q2 projects by impact vs effort."
# "Give me a sequence diagram of the OAuth handshake."
# (branching token refresh uses the ALT combined-fragment grammar in type-sequence.md;
# see assets/example-sequence-oauth.html)
```
Claude will pick the right type, build the HTML, and save it. You can also start from a template directly:
@@ -247,7 +249,8 @@ diagram-design/
│ ├── template*.html — scaffolds for new diagrams
│ ├── example-<type>.html — 3 variants × 27 types
│ ├── example-loop-terminal.html — terminal-variant flagship
│ └── example-quadrant-consultant.html — consultant-special 2×2 scenario matrix
│ ├── example-quadrant-consultant.html — consultant-special 2×2 scenario matrix
│ └── example-sequence-oauth*.html — sequence special: bearer + ALT refresh
└── docs/screenshots/ — the images in this README
```
+129
View File
@@ -0,0 +1,129 @@
#!/usr/bin/env python3
"""Structural + skin-lint verification for sequence combined-fragment work.
Drives the real shipped artifacts (type-sequence.md, oauth examples, lint-skin.py).
Exit 0 only when all gates pass. Intended for local/PR checks alongside:
python scripts/lint-skin.py --all --baseline
"""
from __future__ import annotations
import re
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
TYPE_SEQ = ROOT / "skills/diagram-design/references/type-sequence.md"
ASSETS = ROOT / "skills/diagram-design/assets"
COLD = [
ASSETS / "example-sequence.html",
ASSETS / "example-sequence-dark.html",
ASSETS / "example-sequence-full.html",
]
OAUTH = [
ASSETS / "example-sequence-oauth.html",
ASSETS / "example-sequence-oauth-dark.html",
ASSETS / "example-sequence-oauth-full.html",
]
LINT = ROOT / "scripts/lint-skin.py"
def fail(msg: str) -> None:
print(f"FAIL: {msg}", file=sys.stderr)
raise SystemExit(1)
def ok(msg: str) -> None:
print(f"OK: {msg}")
def main() -> int:
if not TYPE_SEQ.is_file():
fail(f"missing {TYPE_SEQ}")
text = TYPE_SEQ.read_text(encoding="utf-8")
required_sections = [
"## Combined fragments",
"### Open arrowhead (async)",
"## Complexity budget",
"## Anti-patterns",
"arrow-open",
]
for needle in required_sections:
if needle not in text:
fail(f"type-sequence.md missing {needle!r}")
for op in ("ALT", "OPT", "LOOP"):
if op not in text:
fail(f"type-sequence.md missing operator {op}")
if "[token valid]" not in text and "guard" not in text.lower():
fail("type-sequence.md missing guard documentation")
ok("type-sequence.md fragments + async + budget present")
for path in COLD:
if not path.is_file():
fail(f"cold-cache example missing: {path.name}")
body = path.read_text(encoding="utf-8")
if "cold cache" not in body.lower() and "Article request" not in body:
fail(f"{path.name} no longer looks like the cold-cache example")
if "ALT" in body and "example-sequence-oauth" not in path.name:
# cold-cache must not grow an ALT frame (special example only)
# allow the word only in unrelated comments; require no ALT operator tab content
if re.search(r">ALT<", body):
fail(f"{path.name} unexpectedly contains ALT fragment tab")
ok("cold-cache sequence trio intact (no ALT tab)")
for path in OAUTH:
if not path.is_file():
fail(f"oauth example missing: {path.name}")
body = path.read_text(encoding="utf-8")
if ">ALT<" not in body and ">ALT</text>" not in body:
# tab text is ALT
if not re.search(r">\s*ALT\s*<", body):
fail(f"{path.name} missing ALT operator label")
if "[token valid]" not in body:
fail(f"{path.name} missing [token valid] guard")
if "[else" not in body and "[else · 401]" not in body:
fail(f"{path.name} missing else-region guard")
if "arrow-open" not in body:
fail(f"{path.name} missing arrow-open async marker")
if "Bearer" not in body and "BEARER" not in body:
fail(f"{path.name} missing bearer call")
ok("oauth trio has ALT two-region structure + async open")
# Drive real skin linter entry point on new files
cmd = [
sys.executable,
str(LINT),
*[str(p) for p in OAUTH],
]
proc = subprocess.run(cmd, cwd=ROOT, capture_output=True, text=True)
print(proc.stdout, end="")
if proc.stderr:
print(proc.stderr, end="", file=sys.stderr)
if proc.returncode != 0:
fail(f"lint-skin.py failed on oauth examples (exit {proc.returncode})")
if "0 finding(s)" not in proc.stdout:
fail("lint-skin.py did not report 0 findings on oauth examples")
ok("lint-skin.py clean on oauth examples")
# Full repo gate
proc2 = subprocess.run(
[sys.executable, str(LINT), "--all", "--baseline"],
cwd=ROOT,
capture_output=True,
text=True,
)
print(proc2.stdout, end="")
if proc2.returncode != 0:
fail(f"lint-skin.py --all --baseline failed (exit {proc2.returncode})")
if "0 finding(s)" not in proc2.stdout:
fail("lint-skin.py --all --baseline did not report 0 findings")
ok("lint-skin.py --all --baseline green")
print("ALL GATES PASSED")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+3
View File
@@ -344,6 +344,9 @@ Quick check: if a coordinate ends in 1, 2, 3, 5, 6, 7, 9 — fix it.
| Max arrows / transitions | 12 |
| Max coral elements | 2 |
| Max lifelines (sequence) | 5 |
| Max combined fragments (sequence) | 1 (default); 2 only if each is single-region `opt`/`loop` |
| Max `alt` regions (sequence) | 2 |
| Max fragment nesting (sequence) | 1 |
| Max lanes (swimlane) | 5 |
| Max items (quadrant) | 12 |
| Max entities (ER) | 8 |
@@ -0,0 +1,185 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bearer call with refresh · Sequence</title>
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--color-paper: #2d3142;
--color-ink: #f5f5f5;
--color-muted: #bfc0c0;
--color-accent: #f08a59;
--font-sans: 'Geist', system-ui, sans-serif;
--font-serif: 'Instrument Serif', serif;
--font-mono: 'Geist Mono', ui-monospace, monospace;
}
body {
font-family: var(--font-sans);
background: var(--color-paper);
color: var(--color-ink);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 3rem 2rem;
}
.frame { max-width: 1100px; width: 100%; }
.eyebrow {
font-family: var(--font-mono);
font-size: 0.66rem;
font-weight: 500;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--color-muted);
margin-bottom: 0.5rem;
}
h1 {
font-family: var(--font-serif);
font-size: clamp(1.5rem, 2.4vw + 0.75rem, 2rem);
font-weight: 400;
letter-spacing: -0.02em;
line-height: 1.15;
color: var(--color-ink);
margin-bottom: 1.5rem;
}
svg { width: 100%; min-width: 840px; display: block; }
</style>
</head>
<body>
<div class="frame">
<p class="eyebrow">Sequence · Diagram Design · Special</p>
<h1>Bearer call with refresh</h1>
<svg viewBox="0 0 960 720" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="seq-title seq-desc">
<title id="seq-title">Bearer call with refresh</title>
<desc id="seq-desc">Client calls API with a bearer token. An ALT fragment shows either a valid 200 response or a 401 path that refreshes via Auth and retries.</desc>
<defs>
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
<circle cx="1" cy="1" r="0.9" fill="rgba(245,245,245,0.10)"/>
</pattern>
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#bfc0c0"/>
</marker>
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#f08a59"/>
</marker>
<marker id="arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#6a95d8"/>
</marker>
<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="#bfc0c0" stroke-width="1.2"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#2d3142"/>
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
<line x1="160" y1="128" x2="160" y2="624" stroke="rgba(245,245,245,0.22)" stroke-width="1" stroke-dasharray="3,3"/>
<line x1="480" y1="128" x2="480" y2="624" stroke="rgba(245,245,245,0.22)" stroke-width="1" stroke-dasharray="3,3"/>
<line x1="800" y1="128" x2="800" y2="624" stroke="rgba(245,245,245,0.22)" stroke-width="1" stroke-dasharray="3,3"/>
<rect x="476" y="180" width="8" height="88" fill="rgba(245,245,245,0.06)" stroke="#bfc0c0" stroke-width="0.8"/>
<rect x="156" y="360" width="8" height="200" fill="rgba(245,245,245,0.06)" stroke="#bfc0c0" stroke-width="0.8"/>
<rect x="796" y="400" width="8" height="64" fill="rgba(245,245,245,0.06)" stroke="#bfc0c0" stroke-width="0.8"/>
<rect x="476" y="496" width="8" height="64" fill="rgba(245,245,245,0.06)" stroke="#bfc0c0" stroke-width="0.8"/>
<line x1="160" y1="176" x2="476" y2="176" stroke="#6a95d8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
<rect x="248" y="160" width="128" height="12" rx="2" fill="#2d3142"/>
<text x="312" y="170" fill="#6a95d8" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">GET /RESOURCE + BEARER</text>
<rect x="88" y="208" width="752" height="360" rx="4"
fill="rgba(245,245,245,0.04)" stroke="rgba(245,245,245,0.22)" stroke-width="1"/>
<rect x="88" y="208" width="40" height="16" rx="2"
fill="#2d3142" stroke="rgba(245,245,245,0.22)" stroke-width="1"/>
<text x="108" y="220" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.12em">ALT</text>
<text x="100" y="244" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.04em">[token valid]</text>
<line x1="476" y1="272" x2="168" y2="272" stroke="#f08a59" stroke-width="1.4" marker-end="url(#arrow-accent)"/>
<rect x="256" y="256" width="88" height="12" rx="2" fill="#2d3142"/>
<text x="300" y="266" fill="#f08a59" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">200 · BODY</text>
<line x1="96" y1="312" x2="832" y2="312"
stroke="rgba(245,245,245,0.20)" stroke-width="1" stroke-dasharray="4,3"/>
<text x="100" y="336" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.04em">[else · 401]</text>
<line x1="476" y1="368" x2="168" y2="368" stroke="#bfc0c0" stroke-width="1.2" marker-end="url(#arrow)"/>
<rect x="272" y="352" width="56" height="12" rx="2" fill="#2d3142"/>
<text x="300" y="362" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">401</text>
<line x1="164" y1="416" x2="796" y2="416" stroke="#6a95d8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
<rect x="408" y="400" width="128" height="12" rx="2" fill="#2d3142"/>
<text x="472" y="410" fill="#6a95d8" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">POST /TOKEN · REFRESH</text>
<line x1="796" y1="456" x2="168" y2="456" stroke="#bfc0c0" stroke-width="1.2" stroke-dasharray="5,4" marker-end="url(#arrow)"/>
<rect x="424" y="440" width="96" height="12" rx="2" fill="#2d3142"/>
<text x="472" y="450" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">200 · NEW ACCESS</text>
<line x1="164" y1="504" x2="476" y2="504" stroke="#6a95d8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
<rect x="248" y="488" width="112" height="12" rx="2" fill="#2d3142"/>
<text x="304" y="498" fill="#6a95d8" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">GET /RESOURCE · RETRY</text>
<line x1="476" y1="544" x2="168" y2="544" stroke="#bfc0c0" stroke-width="1.2" marker-end="url(#arrow)"/>
<rect x="256" y="528" width="88" height="12" rx="2" fill="#2d3142"/>
<text x="300" y="538" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">200 · BODY</text>
<line x1="164" y1="600" x2="796" y2="600" stroke="#bfc0c0" stroke-width="1.2" stroke-dasharray="5,4" marker-end="url(#arrow-open)"/>
<rect x="424" y="584" width="96" height="12" rx="2" fill="#2d3142"/>
<text x="472" y="594" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">AUDIT · ASYNC</text>
<rect x="88" y="72" width="144" height="56" rx="6" fill="#2d3142"/>
<rect x="88" y="72" width="144" height="56" rx="6" fill="rgba(191,192,192,0.10)" stroke="#8e98ac" stroke-width="1"/>
<rect x="96" y="80" width="32" height="12" rx="2" fill="transparent" stroke="rgba(142,152,172,0.40)" stroke-width="0.8"/>
<text x="112" y="89" fill="#8e98ac" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">APP</text>
<text x="160" y="104" fill="#f5f5f5" font-size="12" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Client</text>
<text x="160" y="119" fill="#bfc0c0" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">SPA · SDK</text>
<rect x="408" y="72" width="144" height="56" rx="6" fill="#2d3142"/>
<rect x="408" y="72" width="144" height="56" rx="6" fill="rgba(240,138,89,0.10)" stroke="#f08a59" stroke-width="1"/>
<rect x="416" y="80" width="32" height="12" rx="2" fill="transparent" stroke="rgba(240,138,89,0.50)" stroke-width="0.8"/>
<text x="432" y="89" fill="#f08a59" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">API</text>
<text x="480" y="104" fill="#f5f5f5" font-size="12" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Resource API</text>
<text x="480" y="119" fill="#bfc0c0" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">Bearer gate</text>
<rect x="728" y="72" width="144" height="56" rx="6" fill="#2d3142"/>
<rect x="728" y="72" width="144" height="56" rx="6" fill="rgba(245,245,245,0.04)" stroke="#f5f5f5" stroke-width="1"/>
<rect x="736" y="80" width="36" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.30)" stroke-width="0.8"/>
<text x="754" y="89" fill="#bfc0c0" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">AUTH</text>
<text x="800" y="104" fill="#f5f5f5" font-size="12" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Auth</text>
<text x="800" y="119" fill="#bfc0c0" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">token · refresh</text>
<line x1="56" y1="648" x2="904" y2="648" stroke="rgba(245,245,245,0.12)" stroke-width="0.8"/>
<text x="56" y="664" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
<rect x="56" y="680" width="14" height="10" rx="2" fill="rgba(240,138,89,0.10)" stroke="#f08a59" stroke-width="1"/>
<text x="76" y="688" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Focal / success</text>
<rect x="200" y="676" width="28" height="18" rx="2" fill="rgba(245,245,245,0.04)" stroke="rgba(245,245,245,0.22)" stroke-width="1"/>
<text x="208" y="688" fill="#bfc0c0" font-size="7" font-family="'Geist Mono', monospace">ALT</text>
<text x="236" y="688" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Fragment</text>
<line x1="320" y1="684" x2="348" y2="684" stroke="#6a95d8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
<text x="356" y="688" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">HTTP call</text>
<line x1="448" y1="684" x2="476" y2="684" stroke="#bfc0c0" stroke-width="1.2" stroke-dasharray="5,4" marker-end="url(#arrow)"/>
<text x="484" y="688" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Return</text>
<line x1="560" y1="684" x2="588" y2="684" stroke="#bfc0c0" stroke-width="1.2" stroke-dasharray="5,4" marker-end="url(#arrow-open)"/>
<text x="596" y="688" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Async open</text>
<line x1="696" y1="684" x2="724" y2="684" stroke="#f08a59" stroke-width="1.4" marker-end="url(#arrow-accent)"/>
<text x="732" y="688" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Headline</text>
</svg>
</div>
</body>
</html>
@@ -0,0 +1,347 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bearer Call with Refresh · Sequence</title>
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--color-paper: #f5f5f5;
--color-paper-2: #ececec;
--color-ink: #2d3142;
--color-muted: #4f5d75;
--color-soft: #7a8399;
--color-rule: rgba(45,49,66,0.12);
--color-rule-solid: rgba(79,93,117,0.25);
--color-accent: #eb6c36;
--color-accent-tint: rgba(235,108,54,0.08);
--color-link: #2e5aa8;
--font-sans: 'Geist', system-ui, sans-serif;
--font-serif: 'Instrument Serif', 'Times New Roman', serif;
--font-mono: 'Geist Mono', ui-monospace, Menlo, monospace;
}
body {
font-family: var(--font-sans);
background: var(--color-paper);
min-height: 100vh;
padding: 3rem 2rem;
color: var(--color-ink);
}
.container { max-width: 1100px; margin: 0 auto; }
.header { margin-bottom: 2.5rem; }
.header-eyebrow {
font-family: var(--font-mono);
font-size: 0.66rem;
font-weight: 500;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--color-muted);
margin-bottom: 0.75rem;
}
h1 {
font-family: var(--font-serif);
font-size: clamp(1.75rem, 3vw + 1rem, 2.5rem);
font-weight: 400;
letter-spacing: -0.02em;
line-height: 1.1;
color: var(--color-ink);
margin-bottom: 0.5rem;
}
.subtitle {
font-family: var(--font-sans);
font-size: 1rem;
line-height: 1.55;
color: var(--color-muted);
max-width: 58ch;
}
.diagram-container {
background: var(--color-paper-2);
border-radius: 8px;
border: 1px solid var(--color-rule);
padding: 1.5rem;
overflow-x: auto;
}
svg { width: 100%; min-width: 840px; display: block; }
.cards {
display: grid;
grid-template-columns: 1.1fr 1fr 0.9fr;
gap: 1rem;
margin-top: 1.5rem;
}
@media (max-width: 820px) {
.cards { grid-template-columns: 1fr; }
}
.card {
background: #ffffff;
border-radius: 6px;
border: 1px solid var(--color-rule);
padding: 1.25rem;
}
.card .eyebrow {
font-family: var(--font-mono);
font-size: 0.5rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--color-muted);
margin-bottom: 0.5rem;
}
.card-header {
display: flex;
align-items: center;
gap: 0.6rem;
margin-bottom: 0.875rem;
padding-bottom: 0.875rem;
border-bottom: 1px solid rgba(45,49,66,0.08);
}
.card-dot {
width: 7px; height: 7px;
border-radius: 50%;
flex-shrink: 0;
}
.card-dot.ink { background: var(--color-ink); }
.card-dot.muted { background: var(--color-muted); }
.card-dot.coral { background: var(--color-accent); }
.card-dot.link { background: var(--color-link); }
.card h3 {
font-family: var(--font-sans);
font-size: 0.875rem;
font-weight: 600;
color: var(--color-ink);
letter-spacing: -0.005em;
}
.card p {
color: var(--color-muted);
font-size: 0.8125rem;
line-height: 1.55;
}
.card ul {
list-style: none;
color: var(--color-muted);
font-size: 0.8125rem;
line-height: 1.55;
}
.card li {
margin-bottom: 0.3rem;
padding-left: 0.875rem;
position: relative;
}
.card li::before {
content: '-';
position: absolute;
left: 0;
color: rgba(45,49,66,0.25);
font-size: 0.75rem;
}
.footer {
margin-top: 2rem;
padding-top: 1.5rem;
border-top: 1px solid rgba(45,49,66,0.10);
font-family: var(--font-mono);
font-size: 0.72rem;
letter-spacing: 0.06em;
color: var(--color-soft);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 0.5rem;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<p class="header-eyebrow">Sequence · Diagram Design · Special</p>
<h1>Bearer call with refresh</h1>
<p class="subtitle">A resource call under a bearer token, with an ALT fragment for the happy path versus a 401 that refreshes via Auth and retries. Async audit is fire-and-forget with an open arrowhead.</p>
</div>
<div class="diagram-container">
<svg viewBox="0 0 960 720" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="seq-title seq-desc">
<title id="seq-title">Bearer call with refresh</title>
<desc id="seq-desc">Client calls API with a bearer token. An ALT fragment shows either a valid 200 response or a 401 path that refreshes via Auth and retries.</desc>
<defs>
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
<circle cx="1" cy="1" r="0.9" fill="rgba(45,49,66,0.10)"/>
</pattern>
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#4f5d75"/>
</marker>
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#eb6c36"/>
</marker>
<marker id="arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#2e5aa8"/>
</marker>
<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>
</defs>
<rect width="100%" height="100%" fill="#f5f5f5"/>
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
<line x1="160" y1="128" x2="160" y2="624" stroke="rgba(45,49,66,0.22)" stroke-width="1" stroke-dasharray="3,3"/>
<line x1="480" y1="128" x2="480" y2="624" stroke="rgba(45,49,66,0.22)" stroke-width="1" stroke-dasharray="3,3"/>
<line x1="800" y1="128" x2="800" y2="624" stroke="rgba(45,49,66,0.22)" stroke-width="1" stroke-dasharray="3,3"/>
<rect x="476" y="180" width="8" height="88" fill="rgba(45,49,66,0.06)" stroke="#4f5d75" stroke-width="0.8"/>
<rect x="156" y="360" width="8" height="200" fill="rgba(45,49,66,0.06)" stroke="#4f5d75" stroke-width="0.8"/>
<rect x="796" y="400" width="8" height="64" fill="rgba(45,49,66,0.06)" stroke="#4f5d75" stroke-width="0.8"/>
<rect x="476" y="496" width="8" height="64" fill="rgba(45,49,66,0.06)" stroke="#4f5d75" stroke-width="0.8"/>
<line x1="160" y1="176" x2="476" y2="176" stroke="#2e5aa8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
<rect x="248" y="160" width="128" height="12" rx="2" fill="#f5f5f5"/>
<text x="312" y="170" fill="#2e5aa8" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">GET /RESOURCE + BEARER</text>
<rect x="88" y="208" width="752" height="360" rx="4"
fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.22)" stroke-width="1"/>
<rect x="88" y="208" width="40" height="16" rx="2"
fill="#f5f5f5" stroke="rgba(45,49,66,0.22)" stroke-width="1"/>
<text x="108" y="220" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.12em">ALT</text>
<text x="100" y="244" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.04em">[token valid]</text>
<line x1="476" y1="272" x2="168" y2="272" stroke="#eb6c36" stroke-width="1.4" marker-end="url(#arrow-accent)"/>
<rect x="256" y="256" width="88" height="12" rx="2" fill="#f5f5f5"/>
<text x="300" y="266" fill="#eb6c36" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">200 · BODY</text>
<line x1="96" y1="312" x2="832" y2="312"
stroke="rgba(45,49,66,0.20)" stroke-width="1" stroke-dasharray="4,3"/>
<text x="100" y="336" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.04em">[else · 401]</text>
<line x1="476" y1="368" x2="168" y2="368" stroke="#4f5d75" stroke-width="1.2" marker-end="url(#arrow)"/>
<rect x="272" y="352" width="56" height="12" rx="2" fill="#f5f5f5"/>
<text x="300" y="362" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">401</text>
<line x1="164" y1="416" x2="796" y2="416" stroke="#2e5aa8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
<rect x="408" y="400" width="128" height="12" rx="2" fill="#f5f5f5"/>
<text x="472" y="410" fill="#2e5aa8" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">POST /TOKEN · REFRESH</text>
<line x1="796" y1="456" x2="168" y2="456" stroke="#4f5d75" stroke-width="1.2" stroke-dasharray="5,4" marker-end="url(#arrow)"/>
<rect x="424" y="440" width="96" height="12" rx="2" fill="#f5f5f5"/>
<text x="472" y="450" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">200 · NEW ACCESS</text>
<line x1="164" y1="504" x2="476" y2="504" stroke="#2e5aa8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
<rect x="248" y="488" width="112" height="12" rx="2" fill="#f5f5f5"/>
<text x="304" y="498" fill="#2e5aa8" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">GET /RESOURCE · RETRY</text>
<line x1="476" y1="544" x2="168" y2="544" stroke="#4f5d75" stroke-width="1.2" marker-end="url(#arrow)"/>
<rect x="256" y="528" width="88" height="12" rx="2" fill="#f5f5f5"/>
<text x="300" y="538" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">200 · BODY</text>
<line x1="164" y1="600" x2="796" y2="600" stroke="#4f5d75" stroke-width="1.2" stroke-dasharray="5,4" marker-end="url(#arrow-open)"/>
<rect x="424" y="584" width="96" height="12" rx="2" fill="#f5f5f5"/>
<text x="472" y="594" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">AUDIT · ASYNC</text>
<rect x="88" y="72" width="144" height="56" rx="6" fill="#f5f5f5"/>
<rect x="88" y="72" width="144" height="56" rx="6" fill="rgba(79,93,117,0.10)" stroke="#7a8399" stroke-width="1"/>
<rect x="96" y="80" width="32" height="12" rx="2" fill="transparent" stroke="rgba(122,131,153,0.40)" stroke-width="0.8"/>
<text x="112" y="89" fill="#7a8399" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">APP</text>
<text x="160" y="104" fill="#2d3142" font-size="12" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Client</text>
<text x="160" y="119" fill="#4f5d75" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">SPA · SDK</text>
<rect x="408" y="72" width="144" height="56" rx="6" fill="#f5f5f5"/>
<rect x="408" y="72" width="144" height="56" rx="6" fill="rgba(235,108,54,0.08)" stroke="#eb6c36" stroke-width="1"/>
<rect x="416" y="80" width="32" height="12" rx="2" fill="transparent" stroke="rgba(235,108,54,0.50)" stroke-width="0.8"/>
<text x="432" y="89" fill="#eb6c36" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">API</text>
<text x="480" y="104" fill="#2d3142" font-size="12" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Resource API</text>
<text x="480" y="119" fill="#4f5d75" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">Bearer gate</text>
<rect x="728" y="72" width="144" height="56" rx="6" fill="#f5f5f5"/>
<rect x="728" y="72" width="144" height="56" rx="6" fill="#ffffff" stroke="#2d3142" stroke-width="1"/>
<rect x="736" y="80" width="36" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.30)" stroke-width="0.8"/>
<text x="754" y="89" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">AUTH</text>
<text x="800" y="104" fill="#2d3142" font-size="12" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Auth</text>
<text x="800" y="119" fill="#4f5d75" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">token · refresh</text>
<line x1="56" y1="648" x2="904" y2="648" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
<text x="56" y="664" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
<rect x="56" y="680" width="14" height="10" rx="2" fill="rgba(235,108,54,0.08)" stroke="#eb6c36" stroke-width="1"/>
<text x="76" y="688" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Focal / success</text>
<rect x="200" y="676" width="28" height="18" rx="2" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.22)" stroke-width="1"/>
<text x="208" y="688" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace">ALT</text>
<text x="236" y="688" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Fragment</text>
<line x1="320" y1="684" x2="348" y2="684" stroke="#2e5aa8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
<text x="356" y="688" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">HTTP call</text>
<line x1="448" y1="684" x2="476" y2="684" stroke="#4f5d75" stroke-width="1.2" stroke-dasharray="5,4" marker-end="url(#arrow)"/>
<text x="484" y="688" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Return</text>
<line x1="560" y1="684" x2="588" y2="684" stroke="#4f5d75" stroke-width="1.2" stroke-dasharray="5,4" marker-end="url(#arrow-open)"/>
<text x="596" y="688" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Async open</text>
<line x1="696" y1="684" x2="724" y2="684" stroke="#eb6c36" stroke-width="1.4" marker-end="url(#arrow-accent)"/>
<text x="732" y="688" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Headline</text>
</svg>
</div>
<div class="cards">
<div class="card">
<p class="eyebrow">THE HEADLINE</p>
<div class="card-header">
<span class="card-dot coral"></span>
<h3>Valid token is one hop</h3>
</div>
<p>When the access token is still good, the ALT frame collapses to a single coral return. That is the path most readers should see first.</p>
</div>
<div class="card">
<div class="card-header">
<span class="card-dot link"></span>
<h3>401 drives refresh</h3>
</div>
<ul>
<li>API answers 401, not a hard fail</li>
<li>Client posts refresh to Auth</li>
<li>New access returns dashed</li>
<li>Retry re-enters the resource API</li>
</ul>
</div>
<div class="card">
<div class="card-header">
<span class="card-dot muted"></span>
<h3>Async uses open arrows</h3>
</div>
<p>The audit event is dashed with an open arrowhead so it never reads as a blocking return. Returns stay filled even when dashed.</p>
</div>
</div>
<div class="footer">
<span>oauth · bearer + alt refresh sequence</span>
<span>example · schematic skill</span>
</div>
</div>
</body>
</html>
@@ -0,0 +1,226 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bearer call with refresh · Sequence</title>
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--color-paper: #f5f5f5;
--color-ink: #2d3142;
--color-muted: #4f5d75;
--color-accent: #eb6c36;
--font-sans: 'Geist', system-ui, sans-serif;
--font-serif: 'Instrument Serif', serif;
--font-mono: 'Geist Mono', ui-monospace, monospace;
}
body {
font-family: var(--font-sans);
background: var(--color-paper);
color: var(--color-ink);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 3rem 2rem;
}
.frame { max-width: 1100px; width: 100%; }
.eyebrow {
font-family: var(--font-mono);
font-size: 0.66rem;
font-weight: 500;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--color-muted);
margin-bottom: 0.5rem;
}
h1 {
font-family: var(--font-serif);
font-size: clamp(1.5rem, 2.4vw + 0.75rem, 2rem);
font-weight: 400;
letter-spacing: -0.02em;
line-height: 1.15;
color: var(--color-ink);
margin-bottom: 1.5rem;
}
svg { width: 100%; min-width: 840px; display: block; }
</style>
</head>
<body>
<div class="frame">
<p class="eyebrow">Sequence · Diagram Design · Special</p>
<h1>Bearer call with refresh</h1>
<svg viewBox="0 0 960 720" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="seq-title seq-desc">
<title id="seq-title">Bearer call with refresh</title>
<desc id="seq-desc">Client calls API with a bearer token. An ALT fragment shows either a valid 200 response or a 401 path that refreshes via Auth and retries.</desc>
<defs>
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
<circle cx="1" cy="1" r="0.9" fill="rgba(45,49,66,0.10)"/>
</pattern>
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#4f5d75"/>
</marker>
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#eb6c36"/>
</marker>
<marker id="arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#2e5aa8"/>
</marker>
<!-- Open arrowhead for async / fire-and-forget -->
<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>
</defs>
<!-- Background: paper + optional dots -->
<rect width="100%" height="100%" fill="#f5f5f5"/>
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
<!-- =================================================================
LIFELINES — Client 160 · API 480 · Auth 800
================================================================= -->
<line x1="160" y1="128" x2="160" y2="624" stroke="rgba(45,49,66,0.22)" stroke-width="1" stroke-dasharray="3,3"/>
<line x1="480" y1="128" x2="480" y2="624" stroke="rgba(45,49,66,0.22)" stroke-width="1" stroke-dasharray="3,3"/>
<line x1="800" y1="128" x2="800" y2="624" stroke="rgba(45,49,66,0.22)" stroke-width="1" stroke-dasharray="3,3"/>
<!-- =================================================================
ACTIVATION BARS
================================================================= -->
<!-- API holds control for the initial call -->
<rect x="476" y="180" width="8" height="88" fill="rgba(45,49,66,0.06)" stroke="#4f5d75" stroke-width="0.8"/>
<!-- Client activation during refresh region -->
<rect x="156" y="360" width="8" height="200" fill="rgba(45,49,66,0.06)" stroke="#4f5d75" stroke-width="0.8"/>
<!-- Auth activation for refresh -->
<rect x="796" y="400" width="8" height="64" fill="rgba(45,49,66,0.06)" stroke="#4f5d75" stroke-width="0.8"/>
<!-- API activation for retry -->
<rect x="476" y="496" width="8" height="64" fill="rgba(45,49,66,0.06)" stroke="#4f5d75" stroke-width="0.8"/>
<!-- =================================================================
M1: Client → API GET /resource (before ALT frame)
================================================================= -->
<line x1="160" y1="176" x2="476" y2="176" stroke="#2e5aa8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
<rect x="248" y="160" width="128" height="12" rx="2" fill="#f5f5f5"/>
<text x="312" y="170" fill="#2e5aa8" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">GET /RESOURCE + BEARER</text>
<!-- =================================================================
ALT COMBINED FRAGMENT — spans Client · API · Auth
Frame: x=88 y=208 w=752 h=360
================================================================= -->
<rect x="88" y="208" width="752" height="360" rx="4"
fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.22)" stroke-width="1"/>
<!-- Operator tab: ALT -->
<rect x="88" y="208" width="40" height="16" rx="2"
fill="#f5f5f5" stroke="rgba(45,49,66,0.22)" stroke-width="1"/>
<text x="108" y="220" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.12em">ALT</text>
<!-- Region 1 guard -->
<text x="100" y="244" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.04em">[token valid]</text>
<!-- M2: API → Client 200 (coral headline success) -->
<line x1="476" y1="272" x2="168" y2="272" stroke="#eb6c36" stroke-width="1.4" marker-end="url(#arrow-accent)"/>
<rect x="256" y="256" width="88" height="12" rx="2" fill="#f5f5f5"/>
<text x="300" y="266" fill="#eb6c36" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">200 · BODY</text>
<!-- alt region divider -->
<line x1="96" y1="312" x2="832" y2="312"
stroke="rgba(45,49,66,0.20)" stroke-width="1" stroke-dasharray="4,3"/>
<!-- Region 2 guard -->
<text x="100" y="336" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.04em">[else · 401]</text>
<!-- M3: API → Client 401 -->
<line x1="476" y1="368" x2="168" y2="368" stroke="#4f5d75" stroke-width="1.2" marker-end="url(#arrow)"/>
<rect x="272" y="352" width="56" height="12" rx="2" fill="#f5f5f5"/>
<text x="300" y="362" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">401</text>
<!-- M4: Client → Auth refresh (link-blue) -->
<line x1="164" y1="416" x2="796" y2="416" stroke="#2e5aa8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
<rect x="408" y="400" width="128" height="12" rx="2" fill="#f5f5f5"/>
<text x="472" y="410" fill="#2e5aa8" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">POST /TOKEN · REFRESH</text>
<!-- M5: Auth → Client access (return dashed, filled marker) -->
<line x1="796" y1="456" x2="168" y2="456" stroke="#4f5d75" stroke-width="1.2" stroke-dasharray="5,4" marker-end="url(#arrow)"/>
<rect x="424" y="440" width="96" height="12" rx="2" fill="#f5f5f5"/>
<text x="472" y="450" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">200 · NEW ACCESS</text>
<!-- M6: Client → API retry -->
<line x1="164" y1="504" x2="476" y2="504" stroke="#2e5aa8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
<rect x="248" y="488" width="112" height="12" rx="2" fill="#f5f5f5"/>
<text x="304" y="498" fill="#2e5aa8" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">GET /RESOURCE · RETRY</text>
<!-- M7: API → Client 200 retry success (muted — coral already used) -->
<line x1="476" y1="544" x2="168" y2="544" stroke="#4f5d75" stroke-width="1.2" marker-end="url(#arrow)"/>
<rect x="256" y="528" width="88" height="12" rx="2" fill="#f5f5f5"/>
<text x="300" y="538" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">200 · BODY</text>
<!-- =================================================================
M8: Client → Auth async audit (open arrow, outside fragment)
================================================================= -->
<line x1="164" y1="600" x2="796" y2="600" stroke="#4f5d75" stroke-width="1.2" stroke-dasharray="5,4" marker-end="url(#arrow-open)"/>
<rect x="424" y="584" width="96" height="12" rx="2" fill="#f5f5f5"/>
<text x="472" y="594" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">AUDIT · ASYNC</text>
<!-- =================================================================
ACTOR BOXES — drawn after arrows
================================================================= -->
<!-- Client (input / soft) -->
<rect x="88" y="72" width="144" height="56" rx="6" fill="#f5f5f5"/>
<rect x="88" y="72" width="144" height="56" rx="6" fill="rgba(79,93,117,0.10)" stroke="#7a8399" stroke-width="1"/>
<rect x="96" y="80" width="32" height="12" rx="2" fill="transparent" stroke="rgba(122,131,153,0.40)" stroke-width="0.8"/>
<text x="112" y="89" fill="#7a8399" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">APP</text>
<text x="160" y="104" fill="#2d3142" font-size="12" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Client</text>
<text x="160" y="119" fill="#4f5d75" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">SPA · SDK</text>
<!-- API (focal) -->
<rect x="408" y="72" width="144" height="56" rx="6" fill="#f5f5f5"/>
<rect x="408" y="72" width="144" height="56" rx="6" fill="rgba(235,108,54,0.08)" stroke="#eb6c36" stroke-width="1"/>
<rect x="416" y="80" width="32" height="12" rx="2" fill="transparent" stroke="rgba(235,108,54,0.50)" stroke-width="0.8"/>
<text x="432" y="89" fill="#eb6c36" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">API</text>
<text x="480" y="104" fill="#2d3142" font-size="12" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Resource API</text>
<text x="480" y="119" fill="#4f5d75" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">Bearer gate</text>
<!-- Auth (backend) -->
<rect x="728" y="72" width="144" height="56" rx="6" fill="#f5f5f5"/>
<rect x="728" y="72" width="144" height="56" rx="6" fill="#ffffff" stroke="#2d3142" stroke-width="1"/>
<rect x="736" y="80" width="36" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.30)" stroke-width="0.8"/>
<text x="754" y="89" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">AUTH</text>
<text x="800" y="104" fill="#2d3142" font-size="12" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Auth</text>
<text x="800" y="119" fill="#4f5d75" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">token · refresh</text>
<!-- =================================================================
LEGEND
================================================================= -->
<line x1="56" y1="648" x2="904" y2="648" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
<text x="56" y="664" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
<rect x="56" y="680" width="14" height="10" rx="2" fill="rgba(235,108,54,0.08)" stroke="#eb6c36" stroke-width="1"/>
<text x="76" y="688" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Focal / success</text>
<rect x="200" y="676" width="28" height="18" rx="2" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.22)" stroke-width="1"/>
<text x="208" y="688" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace">ALT</text>
<text x="236" y="688" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Fragment</text>
<line x1="320" y1="684" x2="348" y2="684" stroke="#2e5aa8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
<text x="356" y="688" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">HTTP call</text>
<line x1="448" y1="684" x2="476" y2="684" stroke="#4f5d75" stroke-width="1.2" stroke-dasharray="5,4" marker-end="url(#arrow)"/>
<text x="484" y="688" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Return</text>
<line x1="560" y1="684" x2="588" y2="684" stroke="#4f5d75" stroke-width="1.2" stroke-dasharray="5,4" marker-end="url(#arrow-open)"/>
<text x="596" y="688" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Async open</text>
<line x1="696" y1="684" x2="724" y2="684" stroke="#eb6c36" stroke-width="1.4" marker-end="url(#arrow-accent)"/>
<text x="732" y="688" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Headline</text>
</svg>
</div>
</body>
</html>
@@ -1,6 +1,6 @@
# Sequence
**Best for:** request/response flows, protocol exchanges, multi-actor interactions over time, API call traces, incident reconstructions.
**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.
@@ -10,12 +10,91 @@
- Self-messages: short U-shaped loop returning to the same lifeline; label right of the loop.
- Return messages: dashed line in the same color as the originating call.
- Coral on the primary success response or headline message — one, maybe two.
- 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.
## 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).
## Message kinds
| Kind | Stroke | Marker | When |
|---|---|---|---|
| Call (sync) | solid muted or link-blue | filled | Request that expects a reply |
| Return | dashed, same color as the call | filled | Reply to a sync call |
| Async / fire-and-forget | dashed muted | **open** arrowhead | Beacons, events, one-way notify |
| Headline success | solid accent (≤1–2) | accent filled | Primary happy-path response |
### Open arrowhead (async)
Define once in `<defs>` and use for fire-and-forget only:
```svg
<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)
```svg
<!-- 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
| Operator | Regions | Divider | Guard label |
|---|---|---|---|
| `opt` | 1 | none | `[if condition]` under the tab (Geist Mono 8px) |
| `alt` | **2 max** | dashed horizontal hairline across the frame | `[guard]` on region 1; `[else]` (or a second guard) on region 2 |
| `loop` | 1 | none | `[for each item]` or `[retry ≤ 3]` under the tab |
### Guard + divider primitives
```svg
<!-- 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
```svg
@@ -29,7 +108,23 @@
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
- `assets/example-sequence.html` — minimal light
- `assets/example-sequence.html` — minimal light (cold-cache happy path)
- `assets/example-sequence-dark.html` — minimal dark
- `assets/example-sequence-full.html` — full editorial
- `assets/example-sequence-oauth.html` — special: bearer call + `alt` refresh (light)
- `assets/example-sequence-oauth-dark.html` — same special, dark
- `assets/example-sequence-oauth-full.html` — same special, full editorial