---
name: excalidraw-diagram
description: |
  트리거: "excalidraw", "다이어그램 그려줘", "플로우 시각화", "excalidraw json", "화이트보드",
  "시스템 구조도", "플로우차트", "시퀀스 다이어그램", "ER 다이어그램"
  시스템 구조, 플로우, 관계를 Excalidraw JSON 형식으로 시각화한다.
  다이어그램 유형별 레이아웃 템플릿을 사용하고, JSON 유효성을 자체 검증한 후 출력한다.
  출력: Excalidraw JSON (excalidraw.com에서 바로 붙여넣기 가능) + 요소 요약 + 불러오기 안내
---
# Excalidraw Diagram — 시스템/플로우 시각화

## 목적
텍스트로 설명된 시스템 구조, 플로우, 관계도를 Excalidraw JSON으로 변환하여
excalidraw.com에서 즉시 시각화할 수 있게 한다.
다이어그램 유형별 고정 레이아웃 템플릿을 활용해 좌표 오류를 최소화한다.

---

## 실행 절차

### 1단계: 다이어그램 유형 파악 및 템플릿 선택

| 유형 | 레이아웃 방향 | 노드 간격 | 사용 사례 |
|------|-------------|---------|---------|
| **플로우차트** | 상→하 (top-down) | x: 200, y: 120 | 프로세스, 분기, 반복 |
| **시스템 구조도** | 좌→우 (left-right) | x: 260, y: 100 | 컴포넌트, 서비스 의존성 |
| **시퀀스 다이어그램** | 좌→우 액터, 상→하 메시지 | actor: 200, message: 80 | API 호출, 이벤트 흐름 |
| **ER 다이어그램** | 격자 (grid) | x: 280, y: 160 | 테이블 관계, 데이터 모델 |
| **마인드맵** | 중심→방사형 | radius: 200 | 개념 확장, 기능 트리 |

### 2단계: 요소 목록 추출
입력에서 다음을 구조화한다.

```
노드 목록:
  - id(짧고 유일), label, type(actor/service/db/external/decision)

엣지 목록:
  - from_id → to_id, label(선택), style(solid/dashed/arrow)

그룹 목록:
  - group_id, label, members[]
```

### 3단계: 좌표 계산 규칙

**플로우차트 (상→하):**
```
레벨 0 (시작): x=400, y=100
레벨 N: y = 100 + N × 120
같은 레벨의 N번째 노드: x = 400 + (i - center_offset) × 200
center_offset = (노드수 - 1) / 2 × 200
```

**시스템 구조도 (좌→우):**
```
레이어 0 (클라이언트): x=100,  y=시작y
레이어 1 (게이트웨이):  x=360,  y=시작y
레이어 2 (서비스):      x=620,  y=시작y (여러 서비스: y 간격 100)
레이어 3 (DB/캐시):     x=880,  y=시작y
```

**시퀀스 다이어그램:**
```
액터 y=80, x=100 + i × 200  (간격 200)
메시지 y=160 + j × 80       (간격 80)
액터 수직선: x=액터x, y=80부터 y=마지막메시지y+60
```

**ER 다이어그램:**
```
테이블 배치: 격자(3열) 또는 관계 기반 배치
테이블 너비: 200, 높이: 30 + 필드수 × 25
열 간격: 280, 행 간격: (테이블 높이 + 60)
```

### 4단계: JSON 생성 전 유효성 체크리스트

아래 항목을 생성 전에 반드시 확인한다.

- [ ] 모든 `id` 값이 고유한가?
- [ ] 모든 화살표의 `startBinding.elementId`가 실제 존재하는 노드 id인가?
- [ ] 모든 화살표의 `endBinding.elementId`가 실제 존재하는 노드 id인가?
- [ ] 화살표의 `points` 배열이 최소 2개 이상인가?
- [ ] 모든 사각형/다이아몬드에 `width`와 `height`가 있는가?
- [ ] 그룹(`groupIds`)을 사용한 경우 groupId가 연결된 모든 요소에 동일하게 지정됐는가?
- [ ] `type` 필드가 허용값(rectangle/ellipse/diamond/arrow/text/line/freedraw)인가?

### 5단계: 색상 체계 (일관성 유지)

| 역할 | strokeColor | backgroundColor | 사용처 |
|------|-------------|----------------|--------|
| 사용자/클라이언트 | `#1971c2` | `#dbe4ff` | 브라우저, 앱 |
| 내부 서비스 | `#6741d9` | `#f3d9fa` | API, 마이크로서비스 |
| 데이터 저장소 | `#2f9e44` | `#d3f9d8` | DB, 캐시, S3 |
| 외부 API/서드파티 | `#e67700` | `#fff3bf` | 결제, OAuth, SMS |
| 결정/분기 | `#c92a2a` | `#ffe3e3` | if/else, 조건 |
| 인프라/배포 | `#495057` | `#f1f3f5` | Nginx, Docker, K8s |

---

## 다이어그램 유형별 템플릿

### 플로우차트 템플릿

```json
{
  "type": "excalidraw",
  "version": 2,
  "source": "claude-skill",
  "elements": [
    {
      "id": "start",
      "type": "ellipse",
      "x": 320, "y": 80,
      "width": 160, "height": 50,
      "strokeColor": "#2f9e44", "backgroundColor": "#d3f9d8",
      "fillStyle": "solid", "strokeWidth": 2, "roughness": 1,
      "label": { "text": "시작" }
    },
    {
      "id": "step1",
      "type": "rectangle",
      "x": 280, "y": 180,
      "width": 240, "height": 60,
      "strokeColor": "#1971c2", "backgroundColor": "#dbe4ff",
      "fillStyle": "solid", "strokeWidth": 2, "roughness": 1,
      "label": { "text": "단계 1" }
    },
    {
      "id": "decision1",
      "type": "diamond",
      "x": 280, "y": 300,
      "width": 240, "height": 80,
      "strokeColor": "#c92a2a", "backgroundColor": "#ffe3e3",
      "fillStyle": "solid", "strokeWidth": 2, "roughness": 1,
      "label": { "text": "조건?" }
    },
    {
      "id": "end",
      "type": "ellipse",
      "x": 320, "y": 460,
      "width": 160, "height": 50,
      "strokeColor": "#c92a2a", "backgroundColor": "#ffe3e3",
      "fillStyle": "solid", "strokeWidth": 2, "roughness": 1,
      "label": { "text": "종료" }
    },
    {
      "id": "arrow-start-step1",
      "type": "arrow",
      "x": 400, "y": 130,
      "width": 0, "height": 50,
      "points": [[0, 0], [0, 50]],
      "startBinding": { "elementId": "start", "focus": 0, "gap": 4 },
      "endBinding": { "elementId": "step1", "focus": 0, "gap": 4 },
      "strokeColor": "#343a40", "strokeWidth": 2, "roughness": 1
    },
    {
      "id": "arrow-step1-decision1",
      "type": "arrow",
      "x": 400, "y": 240,
      "width": 0, "height": 60,
      "points": [[0, 0], [0, 60]],
      "startBinding": { "elementId": "step1", "focus": 0, "gap": 4 },
      "endBinding": { "elementId": "decision1", "focus": 0, "gap": 4 },
      "strokeColor": "#343a40", "strokeWidth": 2, "roughness": 1
    }
  ],
  "appState": { "viewBackgroundColor": "#ffffff", "gridSize": 20 }
}
```

### 시스템 구조도 템플릿 (좌→우 레이어)

노드를 레이어별로 배치하고, 화살표로 의존성을 연결한다.

```json
{
  "type": "excalidraw",
  "version": 2,
  "source": "claude-skill",
  "elements": [
    {
      "id": "client",
      "type": "rectangle",
      "x": 60, "y": 180,
      "width": 160, "height": 60,
      "strokeColor": "#1971c2", "backgroundColor": "#dbe4ff",
      "fillStyle": "solid", "strokeWidth": 2, "roughness": 1,
      "label": { "text": "Client\n(Browser)" }
    },
    {
      "id": "gateway",
      "type": "rectangle",
      "x": 300, "y": 180,
      "width": 160, "height": 60,
      "strokeColor": "#6741d9", "backgroundColor": "#f3d9fa",
      "fillStyle": "solid", "strokeWidth": 2, "roughness": 1,
      "label": { "text": "API Gateway" }
    },
    {
      "id": "service-a",
      "type": "rectangle",
      "x": 540, "y": 120,
      "width": 160, "height": 60,
      "strokeColor": "#6741d9", "backgroundColor": "#f3d9fa",
      "fillStyle": "solid", "strokeWidth": 2, "roughness": 1,
      "label": { "text": "Service A" }
    },
    {
      "id": "service-b",
      "type": "rectangle",
      "x": 540, "y": 240,
      "width": 160, "height": 60,
      "strokeColor": "#6741d9", "backgroundColor": "#f3d9fa",
      "fillStyle": "solid", "strokeWidth": 2, "roughness": 1,
      "label": { "text": "Service B" }
    },
    {
      "id": "db",
      "type": "rectangle",
      "x": 780, "y": 180,
      "width": 160, "height": 60,
      "strokeColor": "#2f9e44", "backgroundColor": "#d3f9d8",
      "fillStyle": "solid", "strokeWidth": 2, "roughness": 1,
      "label": { "text": "PostgreSQL" }
    },
    {
      "id": "arrow-client-gw",
      "type": "arrow",
      "x": 220, "y": 210,
      "width": 80, "height": 0,
      "points": [[0, 0], [80, 0]],
      "startBinding": { "elementId": "client", "focus": 0, "gap": 4 },
      "endBinding": { "elementId": "gateway", "focus": 0, "gap": 4 },
      "strokeColor": "#343a40", "strokeWidth": 2, "roughness": 1,
      "label": { "text": "HTTPS" }
    },
    {
      "id": "arrow-gw-svcA",
      "type": "arrow",
      "x": 460, "y": 200,
      "width": 80, "height": -50,
      "points": [[0, 0], [80, -50]],
      "startBinding": { "elementId": "gateway", "focus": 0, "gap": 4 },
      "endBinding": { "elementId": "service-a", "focus": 0, "gap": 4 },
      "strokeColor": "#343a40", "strokeWidth": 2, "roughness": 1
    },
    {
      "id": "arrow-gw-svcB",
      "type": "arrow",
      "x": 460, "y": 210,
      "width": 80, "height": 50,
      "points": [[0, 0], [80, 50]],
      "startBinding": { "elementId": "gateway", "focus": 0, "gap": 4 },
      "endBinding": { "elementId": "service-b", "focus": 0, "gap": 4 },
      "strokeColor": "#343a40", "strokeWidth": 2, "roughness": 1
    },
    {
      "id": "arrow-svcA-db",
      "type": "arrow",
      "x": 700, "y": 150,
      "width": 80, "height": 50,
      "points": [[0, 0], [80, 50]],
      "startBinding": { "elementId": "service-a", "focus": 0, "gap": 4 },
      "endBinding": { "elementId": "db", "focus": 0, "gap": 4 },
      "strokeColor": "#343a40", "strokeWidth": 2, "roughness": 1
    }
  ],
  "appState": { "viewBackgroundColor": "#ffffff", "gridSize": 20 }
}
```

### 시퀀스 다이어그램 템플릿

액터 헤더 + 수직 생명선 + 메시지 화살표 구조.

```json
{
  "type": "excalidraw",
  "version": 2,
  "source": "claude-skill",
  "elements": [
    {
      "id": "actor-client",
      "type": "rectangle",
      "x": 60, "y": 60,
      "width": 140, "height": 50,
      "strokeColor": "#1971c2", "backgroundColor": "#dbe4ff",
      "fillStyle": "solid", "strokeWidth": 2, "roughness": 1,
      "label": { "text": "Client" }
    },
    {
      "id": "actor-server",
      "type": "rectangle",
      "x": 360, "y": 60,
      "width": 140, "height": 50,
      "strokeColor": "#6741d9", "backgroundColor": "#f3d9fa",
      "fillStyle": "solid", "strokeWidth": 2, "roughness": 1,
      "label": { "text": "Server" }
    },
    {
      "id": "lifeline-client",
      "type": "line",
      "x": 130, "y": 110,
      "width": 0, "height": 260,
      "points": [[0, 0], [0, 260]],
      "strokeColor": "#adb5bd", "strokeWidth": 1,
      "strokeStyle": "dashed", "roughness": 0
    },
    {
      "id": "lifeline-server",
      "type": "line",
      "x": 430, "y": 110,
      "width": 0, "height": 260,
      "points": [[0, 0], [0, 260]],
      "strokeColor": "#adb5bd", "strokeWidth": 1,
      "strokeStyle": "dashed", "roughness": 0
    },
    {
      "id": "msg1",
      "type": "arrow",
      "x": 130, "y": 160,
      "width": 300, "height": 0,
      "points": [[0, 0], [300, 0]],
      "startBinding": null,
      "endBinding": null,
      "strokeColor": "#343a40", "strokeWidth": 2, "roughness": 1,
      "label": { "text": "1. POST /login" }
    },
    {
      "id": "msg2",
      "type": "arrow",
      "x": 430, "y": 240,
      "width": -300, "height": 0,
      "points": [[0, 0], [-300, 0]],
      "startBinding": null,
      "endBinding": null,
      "strokeColor": "#2f9e44", "strokeWidth": 2, "roughness": 1,
      "strokeStyle": "dashed",
      "label": { "text": "2. 200 OK + JWT" }
    }
  ],
  "appState": { "viewBackgroundColor": "#ffffff", "gridSize": 20 }
}
```

---

## 출력 형식

```
## Excalidraw 다이어그램

> excalidraw.com → Open → Paste JSON 으로 불러오세요.

### 요소 요약
- 노드: N개 (박스: N, 다이아몬드: N, 원: N)
- 화살표: N개
- 레이아웃: [유형] — [방향]

### 유효성 확인
- [x] 모든 id 고유
- [x] 화살표 binding 참조 정상
- [x] 모든 필수 필드 존재

```json
{ ... (전체 Excalidraw JSON) ... }
```
```

---

## 복잡한 다이어그램 처리 전략

**요소 20개 초과 시:**
- 논리 그룹으로 묶어 `groupIds` 배열로 시각화
- 그룹 경계 표시를 위한 반투명 배경 사각형 추가

**여러 레이어/도메인이 있는 경우:**
- 구역별로 구분 텍스트 레이블을 배경에 배치
- 구역 내 서비스들은 같은 y 좌표 범위 내 배치

**복잡한 경우 단계별 분할:**
1. 전체 구조도 (컴포넌트 관계, 고수준)
2. 시퀀스 다이어그램 (특정 플로우 상세)
3. ER 다이어그램 (데이터 모델 상세)

---

## 주의사항
- 생성된 JSON은 excalidraw.com의 Open → Paste JSON으로 불러온다.
- 화살표 `points`는 시작점 기준 **상대 좌표** 배열이다: `[[0,0], [dx, dy]]`
- `startBinding`/`endBinding`이 없는 화살표는 `null`로 명시한다 (부유 화살표).
- `id` 값은 의미 있는 이름으로 짓는다 (`box-client`, `arrow-client-gw` 등).
- 화살표가 역방향(오른쪽←왼쪽)인 경우 `points`의 dx를 음수로 지정한다.
- 응답 메시지(점선 화살표)는 `strokeStyle: "dashed"`를 추가한다.
