From 8827b277395988877ba997b714b43513f764b569 Mon Sep 17 00:00:00 2001 From: ActArtech <123718991+ActArtech@users.noreply.github.com> Date: Mon, 10 Aug 2026 22:44:04 +0400 Subject: [PATCH] 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 --- README.md | 5 +- scripts/verify-sequence-oauth.py | 129 +++++++ skills/diagram-design/SKILL.md | 3 + .../assets/example-sequence-oauth-dark.html | 185 ++++++++++ .../assets/example-sequence-oauth-full.html | 347 ++++++++++++++++++ .../assets/example-sequence-oauth.html | 226 ++++++++++++ .../references/type-sequence.md | 109 +++++- 7 files changed, 996 insertions(+), 8 deletions(-) create mode 100644 scripts/verify-sequence-oauth.py create mode 100644 skills/diagram-design/assets/example-sequence-oauth-dark.html create mode 100644 skills/diagram-design/assets/example-sequence-oauth-full.html create mode 100644 skills/diagram-design/assets/example-sequence-oauth.html diff --git a/README.md b/README.md index 30e675e..7079949 100644 --- a/README.md +++ b/README.md @@ -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-.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 ``` diff --git a/scripts/verify-sequence-oauth.py b/scripts/verify-sequence-oauth.py new file mode 100644 index 0000000..e70a093 --- /dev/null +++ b/scripts/verify-sequence-oauth.py @@ -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" 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()) diff --git a/skills/diagram-design/SKILL.md b/skills/diagram-design/SKILL.md index a26faf0..dd05090 100644 --- a/skills/diagram-design/SKILL.md +++ b/skills/diagram-design/SKILL.md @@ -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 | diff --git a/skills/diagram-design/assets/example-sequence-oauth-dark.html b/skills/diagram-design/assets/example-sequence-oauth-dark.html new file mode 100644 index 0000000..e8de6b9 --- /dev/null +++ b/skills/diagram-design/assets/example-sequence-oauth-dark.html @@ -0,0 +1,185 @@ + + + + + + Bearer call with refresh · Sequence + + + + +
+

Sequence · Diagram Design · Special

+

Bearer call with refresh

+ + + Bearer call with refresh + 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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GET /RESOURCE + BEARER + + + + ALT + + [token valid] + + + + 200 · BODY + + + + [else · 401] + + + + 401 + + + + POST /TOKEN · REFRESH + + + + 200 · NEW ACCESS + + + + GET /RESOURCE · RETRY + + + + 200 · BODY + + + + AUDIT · ASYNC + + + + + APP + Client + SPA · SDK + + + + + API + Resource API + Bearer gate + + + + + AUTH + Auth + token · refresh + + + LEGEND + + + Focal / success + + + ALT + Fragment + + + HTTP call + + + Return + + + Async open + + + Headline + +
+ + diff --git a/skills/diagram-design/assets/example-sequence-oauth-full.html b/skills/diagram-design/assets/example-sequence-oauth-full.html new file mode 100644 index 0000000..7e4163a --- /dev/null +++ b/skills/diagram-design/assets/example-sequence-oauth-full.html @@ -0,0 +1,347 @@ + + + + + + Bearer Call with Refresh · Sequence + + + + +
+ +
+

Sequence · Diagram Design · Special

+

Bearer call with refresh

+

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.

+
+ +
+ + Bearer call with refresh + 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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GET /RESOURCE + BEARER + + + + ALT + + [token valid] + + + + 200 · BODY + + + + [else · 401] + + + + 401 + + + + POST /TOKEN · REFRESH + + + + 200 · NEW ACCESS + + + + GET /RESOURCE · RETRY + + + + 200 · BODY + + + + AUDIT · ASYNC + + + + + APP + Client + SPA · SDK + + + + + API + Resource API + Bearer gate + + + + + AUTH + Auth + token · refresh + + + LEGEND + + + Focal / success + + + ALT + Fragment + + + HTTP call + + + Return + + + Async open + + + Headline + +
+ +
+
+

THE HEADLINE

+
+ +

Valid token is one hop

+
+

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.

+
+ +
+
+ +

401 drives refresh

+
+
    +
  • API answers 401, not a hard fail
  • +
  • Client posts refresh to Auth
  • +
  • New access returns dashed
  • +
  • Retry re-enters the resource API
  • +
+
+ +
+
+ +

Async uses open arrows

+
+

The audit event is dashed with an open arrowhead so it never reads as a blocking return. Returns stay filled even when dashed.

+
+
+ + + +
+ + diff --git a/skills/diagram-design/assets/example-sequence-oauth.html b/skills/diagram-design/assets/example-sequence-oauth.html new file mode 100644 index 0000000..db741f6 --- /dev/null +++ b/skills/diagram-design/assets/example-sequence-oauth.html @@ -0,0 +1,226 @@ + + + + + + Bearer call with refresh · Sequence + + + + +
+

Sequence · Diagram Design · Special

+

Bearer call with refresh

+ + + Bearer call with refresh + 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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GET /RESOURCE + BEARER + + + + + + ALT + + + [token valid] + + + + + 200 · BODY + + + + + + [else · 401] + + + + + 401 + + + + + POST /TOKEN · REFRESH + + + + + 200 · NEW ACCESS + + + + + GET /RESOURCE · RETRY + + + + + 200 · BODY + + + + + AUDIT · ASYNC + + + + + + + APP + Client + SPA · SDK + + + + + + API + Resource API + Bearer gate + + + + + + AUTH + Auth + token · refresh + + + + LEGEND + + + Focal / success + + + ALT + Fragment + + + HTTP call + + + Return + + + Async open + + + Headline + +
+ + diff --git a/skills/diagram-design/references/type-sequence.md b/skills/diagram-design/references/type-sequence.md index b70f734..441b1c3 100644 --- a/skills/diagram-design/references/type-sequence.md +++ b/skills/diagram-design/references/type-sequence.md @@ -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 `` and use for fire-and-forget only: + +```svg + + + +``` + +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 + + + + +ALT +``` + +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 + +[token valid] + + + +``` + +### 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