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:
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -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())
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user