diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index b2bd921..0e28335 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -1,7 +1,7 @@ { "name": "diagram-design", "metadata": { - "description": "Editorial-quality technical and product diagrams — 26 types rendered as standalone HTML with inline SVG, skinnable to match your brand" + "description": "Editorial-quality technical and product diagrams — 27 types rendered as standalone HTML with inline SVG, skinnable to match your brand" }, "owner": { "name": "Cathryn Lavery" diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 71e67b2..cdc047a 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "diagram-design", - "description": "Editorial-quality technical and product diagrams — 26 types rendered as standalone HTML with inline SVG, skinnable to match your brand", + "description": "Editorial-quality technical and product diagrams — 27 types rendered as standalone HTML with inline SVG, skinnable to match your brand", "version": "2.0.0", "homepage": "https://github.com/cathrynlavery/diagram-design", "repository": "https://github.com/cathrynlavery/diagram-design", diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 74ca72f..40a0dc8 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "diagram-design", - "description": "Editorial-quality technical and product diagrams - 26 types rendered as standalone HTML with inline SVG, skinnable to match your brand", + "description": "Editorial-quality technical and product diagrams - 27 types rendered as standalone HTML with inline SVG, skinnable to match your brand", "version": "2.0.0", "author": { "name": "Cathryn Lavery", diff --git a/README.md b/README.md index 504114a..b6dda5f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,11 @@ ![Content site architecture](docs/screenshots/architecture.png) -26 types. One Claude Code skill. Your brand in 60 seconds — the skill reads your website and maps colors + fonts to every diagram. +![The self-improving loop](docs/screenshots/loop.png) + +*New in 2.0 — the Loop: flywheels with a shared-memory hub. The dashed lines are the write-backs.* + +27 types. One Claude Code skill. Your brand in 60 seconds — the skill reads your website and maps colors + fonts to every diagram. No Figma. No generic rounded boxes. No 30-minute color-picking sessions. @@ -14,7 +18,7 @@ No Figma. No generic rounded boxes. No 30-minute color-picking sessions. I write at [littlemight.com](https://littlemight.com?utm_source=diagram-design&utm_medium=readme&utm_campaign=github&utm_content=intro) (and run [BestSelf.co](https://bestself.co?utm_source=diagram-design&utm_medium=readme&utm_campaign=github&utm_content=intro) on the side). Every time I needed a diagram — an architecture sketch, a flowchart, a pyramid of what matters most — I'd ask Claude and get back a generic rounded-box thing that looked nothing like the rest of the site. I'd either fight with Figma for 30 minutes or just skip the diagram. -So I built a Claude Code skill for it. Twenty-six types, editorial quality, matches your brand in 60 seconds by reading your website. +So I built a Claude Code skill for it. Twenty-seven types, editorial quality, matches your brand in 60 seconds by reading your website. > *The highest-quality move is usually deletion.* Every node earns its place. The accent color is reserved for the 1–2 things the reader should look at first. Target density: 4/10. @@ -22,7 +26,7 @@ So I built a Claude Code skill for it. Twenty-six types, editorial quality, matc ## What it makes -All 26 diagrams ship in three variants: minimal light, minimal dark, and full-editorial. Open any of them directly in a browser — no build step, no JS, no external images. +All 27 diagrams ship in three variants: minimal light, minimal dark, and full-editorial. Open any of them directly in a browser — no build step, no JS, no external images. @@ -52,27 +56,32 @@ All 26 diagrams ship in three variants: minimal light, minimal dark, and full-ed + - + - + - + + + + +
Radar
Radar / Spider
Multi-axis comparison
Loop
Loop
Flywheel · stations around a hub
IT current-state
IT current-state
Legacy landscape · modernization
High-Level
High-Level
End-to-end stack on a cluster
High-Level
High-Level
End-to-end stack on a cluster
Bar chart
Bar chart
Categorical comparison
Line chart
Line chart
Trends over time
Gantt
Gantt
Tasks and phases on a timeline
Gantt
Gantt
Tasks and phases on a timeline
Scatter plot
Scatter plot
Distribution and correlation
Process
Process
Multi-actor sequential workflow
Medallion
Medallion
Multi-tier data storage
Medallion
Medallion
Multi-tier data storage
Data flow
Data flow
Role-scoped pipeline steps
DP integration
DP integration
Sources → core → consumers
DP security matrix
DP security matrix
Per-role access permissions
-**Browse the live gallery:** open [`skills/diagram-design/assets/index.html`](skills/diagram-design/assets/index.html) in your browser to flip through all 26 diagrams with light / dark / full-editorial tabs. +**Browse the live gallery:** open [`skills/diagram-design/assets/index.html`](skills/diagram-design/assets/index.html) in your browser to flip through all 27 diagrams with light / dark / full-editorial tabs. --- @@ -149,7 +158,7 @@ Before writing tokens, the skill verifies WCAG AA contrast on `ink` over `paper` ### Manual override -Prefer to set tokens by hand? Open [`skills/diagram-design/references/style-guide.md`](skills/diagram-design/references/style-guide.md) and edit the table. Everything downstream reads from there — all 26 diagrams, the annotation primitive, and the gallery all inherit semantic role names (`accent`, not `#eb6c36`). +Prefer to set tokens by hand? Open [`skills/diagram-design/references/style-guide.md`](skills/diagram-design/references/style-guide.md) and edit the table. Everything downstream reads from there — all 27 diagrams, the annotation primitive, and the gallery all inherit semantic role names (`accent`, not `#eb6c36`). ### First-run gate @@ -164,7 +173,7 @@ See [`skills/diagram-design/references/onboarding.md`](skills/diagram-design/ref ## Quickstart ```bash -# Open the gallery to see all 26 diagrams +# Open the gallery to see all 27 diagrams open ~/.claude/skills/diagram-design/assets/index.html # In Claude Code, just ask: @@ -235,16 +244,21 @@ diagram-design/ ├── assets/ │ ├── index.html — live gallery, tabbed │ ├── template*.html — scaffolds for new diagrams -│ ├── example-.html — 3 variants × 26 types +│ ├── example-.html — 3 variants × 27 types │ └── example-quadrant-consultant.html — consultant-special 2×2 scenario matrix └── docs/screenshots/ — the images in this README ``` -This keeps Claude's working context tight (only load what you need) and makes the skill easy to extend — drop a new `type-.md` and wire it into the selection guide. The skill ships with 32 reference files covering every diagram type, primitive, and utility. +This keeps Claude's working context tight (only load what you need) and makes the skill easy to extend — drop a new `type-.md` and wire it into the selection guide. The skill ships with 33 reference files covering every diagram type, primitive, and utility. + +### Contributing / skin lint + +Before submitting a new example, run `python3 scripts/lint-skin.py `. +The repository-wide check `python3 scripts/lint-skin.py --all --baseline` must stay green. ### What loads when -The top-level `SKILL.md` is always in context. Everything else is pulled in only when relevant — this is what keeps the skill fast even with 32 reference files. +The top-level `SKILL.md` is always in context. Everything else is pulled in only when relevant — this is what keeps the skill fast even with 33 reference files. | You ask for… | Claude loads | |---|---| @@ -253,7 +267,7 @@ The top-level `SKILL.md` is always in context. Everything else is pulled in only | "Onboard this skill to my site" | `SKILL.md` + `references/onboarding.md` + `references/style-guide.md` | | "Add an editorial callout to this diagram" | `SKILL.md` + `references/primitive-annotation.md` | | "Give me a hand-drawn version" | `SKILL.md` + `references/primitive-sketchy.md` | -| Routine diagram-making (any of the 26 diagrams) | Only `SKILL.md` + that one type's reference | +| Routine diagram-making (any of the 27 diagrams) | Only `SKILL.md` + that one type's reference | No matter how many types exist, Claude only reads the one you need. Add a new type tomorrow and nothing else changes. diff --git a/docs/screenshots/data-flow.png b/docs/screenshots/data-flow.png index 7699eee..76286f8 100644 Binary files a/docs/screenshots/data-flow.png and b/docs/screenshots/data-flow.png differ diff --git a/docs/screenshots/dp-integration.png b/docs/screenshots/dp-integration.png index d74ea10..7718872 100644 Binary files a/docs/screenshots/dp-integration.png and b/docs/screenshots/dp-integration.png differ diff --git a/docs/screenshots/it-state.png b/docs/screenshots/it-state.png index 69d70f6..f96dfeb 100644 Binary files a/docs/screenshots/it-state.png and b/docs/screenshots/it-state.png differ diff --git a/docs/screenshots/loop.png b/docs/screenshots/loop.png new file mode 100644 index 0000000..d6de8dc Binary files /dev/null and b/docs/screenshots/loop.png differ diff --git a/scripts/lint-skin-baseline.txt b/scripts/lint-skin-baseline.txt new file mode 100644 index 0000000..e6ece33 --- /dev/null +++ b/scripts/lint-skin-baseline.txt @@ -0,0 +1,20 @@ +example-architecture-dark.html +example-data-flow-dark.html +example-data-flow-full.html +example-data-flow.html +example-dp-integration-dark.html +example-dp-integration-full.html +example-dp-integration.html +example-high-level-dark.html +example-high-level-full.html +example-high-level-vertical-dark.html +example-high-level-vertical-full.html +example-high-level-vertical.html +example-high-level.html +example-it-state-dark.html +example-it-state-full.html +example-it-state.html +example-layers-dark.html +example-sequence-dark.html +example-timeline-dark.html +example-venn-dark.html diff --git a/scripts/lint-skin.py b/scripts/lint-skin.py new file mode 100644 index 0000000..f7fa95c --- /dev/null +++ b/scripts/lint-skin.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python3 +"""Lint NEW and CHANGED diagram examples against the current visual skin. + +Pre-2.0 examples may legitimately fail because they were built against an older +skin. Use ``--all --baseline`` to skip those documented legacy files. +""" + +import argparse +import re +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parent.parent +STYLE_GUIDE = ROOT / "skills/diagram-design/references/style-guide.md" +ASSET_DIR = ROOT / "skills/diagram-design/assets" +BASELINE = ROOT / "scripts/lint-skin-baseline.txt" + +HEX_RE = re.compile( + r"(?]*>", re.IGNORECASE | re.DOTALL) +HREF_RE = re.compile(r"\bhref\s*=\s*(['\"])(.*?)\1", re.IGNORECASE | re.DOTALL) +FONT_CSS_RE = re.compile(r"font-family\s*:\s*([^;}]+)", re.IGNORECASE) +FONT_ATTR_RE = re.compile( + r"\bfont-family\s*=\s*(['\"])(.*?)\1", re.IGNORECASE | re.DOTALL +) + +ALLOWED_FONTS = { + "instrument serif", + "geist", + "geist mono", + "system-ui", + "sans-serif", + "serif", + "monospace", + "ui-monospace", +} +CSS_FONT_KEYWORDS = {"inherit", "initial", "revert", "revert-layer", "unset"} + + +def normalize_hex(value): + value = value.lower() + if len(value) == 4: + return "#" + "".join(character * 2 for character in value[1:]) + return value + + +def table_hexes(markdown, heading): + """Return hex colors from the first Markdown table after *heading*.""" + start = markdown.find(heading) + if start < 0: + raise ValueError(f"missing {heading!r} in {STYLE_GUIDE}") + + table_started = False + colors = set() + for line in markdown[start:].splitlines()[1:]: + if line.startswith("|"): + table_started = True + colors.update(normalize_hex(match.group()) for match in HEX_RE.finditer(line)) + elif table_started: + break + return colors + + +def allowed_colors(): + markdown = STYLE_GUIDE.read_text(encoding="utf-8") + colors = table_hexes(markdown, "### Semantic roles") + colors.update(table_hexes(markdown, "### Series palette")) + colors.update({"#fff", "#ffffff"}) + + rgb_triplets = set() + for color in colors: + expanded = normalize_hex(color) + if len(expanded) == 7: + rgb_triplets.add( + tuple(int(expanded[offset : offset + 2], 16) for offset in (1, 3, 5)) + ) + return colors, rgb_triplets + + +def line_number(text, offset): + return text.count("\n", 0, offset) + 1 + + +def display_path(path): + try: + return path.resolve().relative_to(ROOT).as_posix() + except ValueError: + return str(path) + + +def named_families(value): + families = [] + for raw_family in value.split(","): + family = raw_family.strip().strip("'\"").strip() + lowered = family.casefold() + if not family or lowered in CSS_FONT_KEYWORDS or lowered.startswith("var("): + continue + if lowered not in ALLOWED_FONTS: + families.append(family) + return families + + +def lint_text(text, colors, rgb_triplets): + findings = [] + + def add(offset, category, message): + findings.append((line_number(text, offset), offset, category, message)) + + for match in HEX_RE.finditer(text): + value = match.group() + normalized = normalize_hex(value) + if normalized == "#000000": + add(match.start(), "pure-black", f"pure black {value} is not allowed") + elif normalized not in colors: + add(match.start(), "color", f"{value} is not in the style-guide palette") + + for match in RGBA_RE.finditer(text): + rgb = tuple(int(match.group(index)) for index in (1, 2, 3)) + if rgb not in rgb_triplets: + add( + match.start(), + "color", + f"{match.group()} is not derived from an allowed palette color", + ) + + for match in BLACK_RGB_RE.finditer(text): + add(match.start(), "pure-black", "pure black rgb(0,0,0) is not allowed") + + for match in SCRIPT_RE.finditer(text): + add(match.start(), "script", "