Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions parsa/.codex/skills/excalidraw-pr-diagrams/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.venv/
*.png
uv.lock
__pycache__/
63 changes: 63 additions & 0 deletions parsa/.codex/skills/excalidraw-pr-diagrams/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Excalidraw Diagram Skill

A coding agent skill that generates beautiful and practical Excalidraw diagrams from natural language descriptions. Not just boxes-and-arrows - diagrams that **argue visually**.

Compatible with any coding agent that supports skills. For agents that read from `.claude/skills/` (like [Claude Code](https://docs.anthropic.com/en/docs/claude-code) and [OpenCode](https://github.com/nicepkg/OpenCode)), just drop it in and go.

## What Makes This Different

- **Diagrams that argue, not display.** Every shape/group of shapes mirrors the concept it represents — fan-outs for one-to-many, timelines for sequences, convergence for aggregation. No uniform card grids.
- **Evidence artifacts.** As an example, technical diagrams include real code snippets and actual JSON payloads.
- **Built-in visual validation.** A Playwright-based render pipeline lets the agent see its own output, catch layout issues (overlapping text, misaligned arrows, unbalanced spacing), and fix them in a loop before delivering.
- **Brand-customizable.** All colors and brand styles live in a single file (`references/color-palette.md`). Swap it out and every diagram follows your palette.

## Installation

Clone or download this repo, then copy it into your project's `.claude/skills/` directory:

```bash
git clone https://github.com/coleam00/excalidraw-diagram-skill.git
cp -r excalidraw-diagram-skill .claude/skills/excalidraw-diagram
```

## Setup

The skill includes a render pipeline that lets the agent visually validate its diagrams. There are two ways to set it up:

**Option A: Ask your coding agent (easiest)**

Just tell your agent: *"Set up the Excalidraw diagram skill renderer by following the instructions in SKILL.md."* It will run the commands for you.

**Option B: Manual**

```bash
cd .claude/skills/excalidraw-diagram/references
uv sync
uv run playwright install chromium
```

## Usage

Ask your coding agent to create a diagram:

> "Create an Excalidraw diagram showing how the AG-UI protocol streams events from an AI agent to a frontend UI"

The skill handles the rest — concept mapping, layout, JSON generation, rendering, and visual validation.

## Customize Colors

Edit `references/color-palette.md` to match your brand. Everything else in the skill is universal design methodology.

## File Structure

```
excalidraw-diagram/
SKILL.md # Design methodology + workflow
references/
color-palette.md # Brand colors (edit this to customize)
element-templates.md # JSON templates for each element type
json-schema.md # Excalidraw JSON format reference
render_excalidraw.py # Render .excalidraw to PNG
render_template.html # Browser template for rendering
pyproject.toml # Python dependencies (playwright)
```
663 changes: 663 additions & 0 deletions parsa/.codex/skills/excalidraw-pr-diagrams/SKILL.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Color Palette & Brand Style

**This is the single source of truth for all colors and brand-specific styles.** To customize diagrams for your own brand, edit this file — everything else in the skill is universal.

---

## Shape Colors (Semantic)

Colors encode meaning, not decoration. Each semantic purpose has a fill/stroke pair.

| Semantic Purpose | Fill | Stroke |
|------------------|------|--------|
| Primary/Neutral | `#3b82f6` | `#1e3a5f` |
| Secondary | `#60a5fa` | `#1e3a5f` |
| Tertiary | `#93c5fd` | `#1e3a5f` |
| Start/Trigger | `#fed7aa` | `#c2410c` |
| End/Success | `#a7f3d0` | `#047857` |
| Warning/Reset | `#fee2e2` | `#dc2626` |
| Decision | `#fef3c7` | `#b45309` |
| AI/LLM | `#ddd6fe` | `#6d28d9` |
| Inactive/Disabled | `#dbeafe` | `#1e40af` (use dashed stroke) |
| Error | `#fecaca` | `#b91c1c` |

**Rule**: Always pair a darker stroke with a lighter fill for contrast.

---

## Text Colors (Hierarchy)

Use color on free-floating text to create visual hierarchy without containers.

| Level | Color | Use For |
|-------|-------|---------|
| Title | `#1e40af` | Section headings, major labels |
| Subtitle | `#3b82f6` | Subheadings, secondary labels |
| Body/Detail | `#64748b` | Descriptions, annotations, metadata |
| On light fills | `#374151` | Text inside light-colored shapes |
| On dark fills | `#ffffff` | Text inside dark-colored shapes |

---

## Evidence Artifact Colors

Used for code snippets, data examples, and other concrete evidence inside technical diagrams.

| Artifact | Background | Text Color |
|----------|-----------|------------|
| Code snippet | `#1e293b` | Syntax-colored (language-appropriate) |
| JSON/data example | `#1e293b` | `#22c55e` (green) |

---

## Default Stroke & Line Colors

| Element | Color |
|---------|-------|
| Arrows | Use the stroke color of the source element's semantic purpose |
| Structural lines (dividers, trees, timelines) | Primary stroke (`#1e3a5f`) or Slate (`#64748b`) |
| Marker dots (fill + stroke) | Primary fill (`#3b82f6`) |

---

## Background

| Property | Value |
|----------|-------|
| Canvas background | `#ffffff` |
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# Element Templates

Copy-paste JSON templates for each Excalidraw element type. The `strokeColor` and `backgroundColor` values are placeholders — always pull actual colors from `color-palette.md` based on the element's semantic purpose.

## Free-Floating Text (no container)
```json
{
"type": "text",
"id": "label1",
"x": 100, "y": 100,
"width": 200, "height": 25,
"text": "Section Title",
"originalText": "Section Title",
"fontSize": 20,
"fontFamily": 3,
"textAlign": "left",
"verticalAlign": "top",
"strokeColor": "<title color from palette>",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"angle": 0,
"seed": 11111,
"version": 1,
"versionNonce": 22222,
"isDeleted": false,
"groupIds": [],
"boundElements": null,
"link": null,
"locked": false,
"containerId": null,
"lineHeight": 1.25
}
```

## Line (structural, not arrow)
```json
{
"type": "line",
"id": "line1",
"x": 100, "y": 100,
"width": 0, "height": 200,
"strokeColor": "<structural line color from palette>",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"angle": 0,
"seed": 44444,
"version": 1,
"versionNonce": 55555,
"isDeleted": false,
"groupIds": [],
"boundElements": null,
"link": null,
"locked": false,
"points": [[0, 0], [0, 200]]
}
```

## Small Marker Dot
```json
{
"type": "ellipse",
"id": "dot1",
"x": 94, "y": 94,
"width": 12, "height": 12,
"strokeColor": "<marker dot color from palette>",
"backgroundColor": "<marker dot color from palette>",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"angle": 0,
"seed": 66666,
"version": 1,
"versionNonce": 77777,
"isDeleted": false,
"groupIds": [],
"boundElements": null,
"link": null,
"locked": false
}
```

## Rectangle
```json
{
"type": "rectangle",
"id": "elem1",
"x": 100, "y": 100, "width": 180, "height": 90,
"strokeColor": "<stroke from palette based on semantic purpose>",
"backgroundColor": "<fill from palette based on semantic purpose>",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"angle": 0,
"seed": 12345,
"version": 1,
"versionNonce": 67890,
"isDeleted": false,
"groupIds": [],
"boundElements": [{"id": "text1", "type": "text"}],
"link": null,
"locked": false,
"roundness": {"type": 3}
}
```

## Text (centered in shape)
```json
{
"type": "text",
"id": "text1",
"x": 130, "y": 132,
"width": 120, "height": 25,
"text": "Process",
"originalText": "Process",
"fontSize": 16,
"fontFamily": 3,
"textAlign": "center",
"verticalAlign": "middle",
"strokeColor": "<text color — match parent shape's stroke or use 'on light/dark fills' from palette>",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"angle": 0,
"seed": 11111,
"version": 1,
"versionNonce": 22222,
"isDeleted": false,
"groupIds": [],
"boundElements": null,
"link": null,
"locked": false,
"containerId": "elem1",
"lineHeight": 1.25
}
```

## Arrow
```json
{
"type": "arrow",
"id": "arrow1",
"x": 282, "y": 145, "width": 118, "height": 0,
"strokeColor": "<arrow color — typically matches source element's stroke from palette>",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"angle": 0,
"seed": 33333,
"version": 1,
"versionNonce": 44444,
"isDeleted": false,
"groupIds": [],
"boundElements": null,
"link": null,
"locked": false,
"points": [[0, 0], [118, 0]],
"startBinding": {"elementId": "elem1", "focus": 0, "gap": 2},
"endBinding": {"elementId": "elem2", "focus": 0, "gap": 2},
"startArrowhead": null,
"endArrowhead": "arrow"
}
```

For curves: use 3+ points in `points` array.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Excalidraw JSON Schema

## Element Types

| Type | Use For |
|------|---------|
| `rectangle` | Processes, actions, components |
| `ellipse` | Entry/exit points, external systems |
| `diamond` | Decisions, conditionals |
| `arrow` | Connections between shapes |
| `text` | Labels inside shapes |
| `line` | Non-arrow connections |
| `frame` | Grouping containers |

## Common Properties

All elements share these:

| Property | Type | Description |
|----------|------|-------------|
| `id` | string | Unique identifier |
| `type` | string | Element type |
| `x`, `y` | number | Position in pixels |
| `width`, `height` | number | Size in pixels |
| `strokeColor` | string | Border color (hex) |
| `backgroundColor` | string | Fill color (hex or "transparent") |
| `fillStyle` | string | "solid", "hachure", "cross-hatch" |
| `strokeWidth` | number | 1, 2, or 4 |
| `strokeStyle` | string | "solid", "dashed", "dotted" |
| `roughness` | number | 0 (smooth), 1 (default), 2 (rough) |
| `opacity` | number | 0-100 |
| `seed` | number | Random seed for roughness |

## Text-Specific Properties

| Property | Description |
|----------|-------------|
| `text` | The display text |
| `originalText` | Same as text |
| `fontSize` | Size in pixels (16-20 recommended) |
| `fontFamily` | 3 for monospace (use this) |
| `textAlign` | "left", "center", "right" |
| `verticalAlign` | "top", "middle", "bottom" |
| `containerId` | ID of parent shape |

## Arrow-Specific Properties

| Property | Description |
|----------|-------------|
| `points` | Array of [x, y] coordinates |
| `startBinding` | Connection to start shape |
| `endBinding` | Connection to end shape |
| `startArrowhead` | null, "arrow", "bar", "dot", "triangle" |
| `endArrowhead` | null, "arrow", "bar", "dot", "triangle" |

## Binding Format

```json
{
"elementId": "shapeId",
"focus": 0,
"gap": 2
}
```

## Rectangle Roundness

Add for rounded corners:
```json
"roundness": { "type": 3 }
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[project]
name = "excalidraw-render"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"playwright>=1.40.0",
]
Loading