한 줄로
규칙은 문서에 잘 쓰는 것보다, 코드가 강제하게 만드는 편이 확실하다.
이 블로그 도식 182개로 확인한 결과다.
| 층 | 강제 방식 | 위반 |
|---|---|---|
| 색 | CSS 가 강제 | 0건 |
| 좌표 | 코드가 계산 | 0건 |
| 래퍼 | 문서에만 적음 | 3건 |
새어 나간 3개가 전부 “문서에만 적어둔” 항목이었다.
도식 163개 중 3개가 안 보였다
사이트를 훑다가 도커 글에서 도식이 있어야 할 자리에 점 하나가 찍혀 있는 걸 봤다.
개발자 도구로 재보니 18×18 픽셀이었다.
원인은 래퍼 한 겹이었다. 이 블로그의 도식은 인라인 SVG(HTML 에 직접 심는 벡터 그림)이고,
색과 크기 규칙은 전부 _sass/diagram.scss 의 .diagram 하위 선택자로 걸려 있다.
{% include %} 만 쓰고 감싸지 않으면 그 규칙이 하나도 안 붙는다.
대신 _sass/base.scss 51 번 줄의 인라인 아이콘용 규칙이 그대로 적용된다.
svg { width:18px; height:18px; fill:currentColor; display:block; }
_sass/base.scss:51 — 아이콘용 규칙인데, 감싸지 않은 도식까지 여기에 걸린다
아이콘 크기로 그려진 것이다. 전수 조사 결과는 이랬다.
대상 (도식 163개 중 아래 3개만 빠져 있었다)
- _pages/Docker/Text_Book_4.md docker5--tag-not-copy
- _pages/Docker/Text_Book_5.md docker6--mount-shadow
- _pages/Kubernetes/non-majors-Kubernetes-2.md k8s--deploy-service
실측 (고치기 전 -> 고친 뒤)
- 크기 18x18 -> 736x307
- fill #1f2328(검정) -> 테마 토큰 (라이트 #ffffff / 다크 #0d1117)
커밋 30f4ae4 본문
163 개 중 3 개, 1.8%. 낮아 보이지만 문제는 비율이 아니라 성질이다.
이 오류는 빌드를 깨뜨리지 않는다. Jekyll 은 정상 종료하고, HTML 도 유효하고,
CI 도 초록불이다. 눈으로 보기 전까지 아무도 모른다.
첫 설계 — 색을 파일에서 빼 CSS 로 옮긴다
이 문제의 뿌리는 3 주 전에 있었다. 처음엔 도식을 <img> 태그로 넣었는데,
테마를 어둡게 바꿔도 도식만 흰 배경 그대로였다.
<img> 로 넣은 SVG 는 페이지 안의 상태를 알 수 없다. SVG 가 볼 수 있는 건
prefers-color-scheme(OS 설정)뿐인데, 이 블로그의 테마 버튼은
body.dark-theme 클래스로 동작한다. 둘이 어긋나 OS 가 라이트면
페이지가 어두워도 도식은 계속 흰색이었다.
대안은 세 갈래였다.
| 방식 | 장점 | 버린 이유 |
|---|---|---|
SVG 안에 @media (prefers-color-scheme)
|
파일 하나로 끝 | OS 설정만 보므로 테마 버튼과 계속 어긋난다. 원인이 안 없어진다 |
| 라이트·다크 SVG 를 2 장씩 | 확실함 | 도식이 곧 100 개를 넘을 참이었다. 두 장의 동기화가 새 문제가 된다 |
| 인라인 SVG + CSS 토큰 | 페이지 CSS 를 상속받아 테마와 항상 같이 움직인다 | 채택 |
인라인으로 옮기면서 SVG 안의 <style> 을 전부 걷어내고 클래스만 남겼다.
색은 여기서 준다.
// 글 안에 들어가는 설명 도식(SVG).
//
// <img> 로 넣으면 SVG 안에서 페이지의 다크 여부를 알 수 없다. SVG 는
// prefers-color-scheme(OS 설정)만 볼 수 있는데, 이 블로그의 테마 버튼은
// body.dark-theme 클래스로 동작하므로 둘이 어긋난다 — 어두운 페이지에
// 흰 도식이 뜨는 문제가 실제로 있었다.
//
// 그래서 SVG 를 페이지에 그대로 심고(인라인), 색은 여기서 준다.
.diagram {
margin: 1.75rem 0;
overflow-x: auto; // 좁은 화면에서 도식만 가로 스크롤
svg {
display: block;
width: 100%;
height: auto;
max-width: 46rem;
margin: 0 auto;
}
.dg-row, .dg-item { fill: var(--bg); stroke: var(--border); }
.dg-t, .dg-m { fill: var(--fg); }
.dg-s { fill: var(--fg-subtle); }
.dg-n { fill: var(--accent); }
}
_sass/diagram.scss — 전체 정의는 파일 참조
전환 비용은 작지 않았다. 477 줄을 지우고 344 줄을 새로 썼다. 사실상 전량 폐기다.
이때 클래스에 dg- 접두사를 붙이고, <marker> id 에 파일명을 붙였다 —
한 페이지에 도식이 여러 개 들어가면 같은 id 가 겹쳐 화살표가 엉킨다.
여기까지가 1 층: CSS 가 계약을 강제하는 층이다. 색을 파일에 박을 수 없게 만들었다.
이후 이 규칙을 어긴 도식은 0 건이다.
그런데 “감싸는 것”은 CSS 가 강제하지 못한다
감싸지 않으면 규칙이 안 붙을 뿐, 아무도 오류를 내지 않는다.
18 일 뒤 도커·쿠버네티스 글에서 정확히 그 3 개가 새어 나갔다.
그래서 2 층: 규칙을 스킬 파일에 적는 층을 만들었다.
.claude/skills/blog-diagram/SKILL.md 의 맨 앞은 이렇게 시작한다.
## 두 가지 필수 조건
이 둘 중 하나만 빠져도 도식이 깨진다. 먼저 확인한다.
1. **`.diagram` 으로 감싼다.** `_sass/diagram.scss` 의 색 규칙이 전부
`.diagram` 하위 선택자다. `{% include %}` 만 쓰면 스타일이 안 붙어
검은 도형이 된다.
2. **SVG 안에 색을 쓰지 않는다.** `fill="#fff"`, `stroke="#333"` 를 쓰면
다크모드에서 그대로 남는다.
.claude/skills/blog-diagram/SKILL.md
핵심은 “먼저 확인한다”가 문서 최상단에 있다는 점이다.
규칙 목록의 27 번째 항목이 아니라 첫 줄이다.
규칙은 한 번에 완성되지 않았다
스킬을 만든 날, 백엔드 로드맵 글의 도식 7 개를 생성했다. 결과물에서 두 가지가 어긋났다.
첫째, 항목이 합쳐졌다. 원본에 MySQL·MariaDB·SQLite 세 상자가 있는데
도식에는 MySQL · MariaDB · SQLite 한 상자로 들어갔다. 폭은 줄지만
읽는 사람은 그게 세 갈래 선택지인지 알 수 없다.
둘째, 표시가 화면 절반을 덮었다. 노드 132 개 중 72 개가 ‘추천’인데
전부 달아놓으니 같은 글자가 반복돼 아무것도 구분되지 않았다.
개별 결과물만 고치면 다음 도식에서 다시 나온다. 그래서 결과물과 함께 규칙을 고쳤다.
+### 원본의 항목을 합치지 않는다
+
+원본에 상자가 3개면 도식에도 상자가 3개다. `MySQL · MariaDB · SQLite` 처럼
+가운뎃점으로 묶으면 폭은 줄지만 **원본에 없는 "한 덩어리"를 만들어낸다.**
+
+`wrap_rows` 가 폭을 넘으면 알아서 다음 줄로 접으므로 합칠 이유가 없다.
+세로로 길어지는 것은 문제가 아니다 — 도식 하나가 720×780 까지는 잘 읽힌다.
+
+### 다수인 표시는 달지 않는다
+
+표시(`s`)는 **예외를 가리킬 때만** 정보가 된다. 로드맵 132개 중 72개가 '추천'인데
+그걸 전부 달면 화면의 절반이 같은 글자로 덮여 아무것도 구분되지 않는다.
+소수인 '대안'·'순서 무관'만 달고, 다수는 표시 없음이 기본값이라고 본문에서 밝힌다.
SKILL.md, 커밋 5fbe636 → dd6d3cd (+15 줄)
주목할 점은 금지만 적지 않았다는 것이다.
“합치지 마라” 뒤에 “wrap_rows 가 알아서 접으니 합칠 이유가 없다”를 붙였다.
합치는 행동은 폭이 넘칠까 봐 나오는 것이므로,
그 걱정이 근거 없음을 같이 적어야 규칙이 지켜진다.
결과가 같은 커밋의 나머지다. 생성 스크립트 217 줄 재작성, 도식 7 개 재생성,
diff 합계 +537 / −310.
3 층 — 판단과 계산을 분리한다
세 번째 설계는 좌표를 아예 맡기지 않는 것이다.
도식이 여러 개면 여백과 노드 높이가 미세하게 어긋난다.
그래서 gen.py 가 존·노드 목록을 받아 좌표를 계산한다.
def wrap_rows(items, avail_w):
"""노드를 가로로 채우다 폭이 넘치면 다음 줄로 접는다."""
def node_width(label, sub):
"""글자 수로 대략의 폭을 잡는다. 한글은 넓게 친다."""
def vis(s):
return sum(2 if ord(c) > 0x2000 else 1 for c in s)
n = max(vis(label), vis(sub or "") * 0.82)
return max(96, min(232, int(n * 5.4) + 40))
.claude/skills/blog-diagram/gen.py
gen.py 에는 LLM 호출이 없다. 결정론적 계산기다.
여기서 정해지는 것은 무엇을 그릴지가 아니라 어디에 놓을지다.
판단(존을 어떻게 나눌지, 무엇을 강조할지)만 남기고 계산은 코드가 가져간다.
같은 원리가 순서에도 적용된다.
# 같은 줄의 노드는 기본적으로 나열(병렬)이다. 순서가 있는 존만
# seq=True 를 줘서 화살표로 잇는다 — 선택지끼리 화살표로 이으면
# "PostgreSQL 다음에 MySQL" 처럼 없는 순서를 만들어낸다.
기본값이 “잇지 않음”이다. 화살표를 그리려면 명시적으로 seq=True 를 줘야 한다.
틀린 쪽이 더 수고롭게 만들어 둔 것이다.
전후 측정
| 지표 | 값 | 출처 |
|---|---|---|
첫 설계(<img>) 폐기 규모 |
−477 줄 / +344 줄 | 커밋 9cc03de
|
.diagram 계약 위반율 |
163 개 중 3 개 (1.8%) | 커밋 30f4ae4
|
| 위반 시 렌더 크기 | 18×18px → 736×307px | 동일 |
| 스킬 개정 후 재생성 | +537 / −310 줄 | 커밋 dd6d3cd
|
| 현재 도식 총계 | 182 개 | ls _includes/diagrams/*.svg |
| 최다 생성일 | 하루 128 개 (커밋 14 개) | 2026-08-12 |
검증은 빌드가 아니라 눈으로 했다. 이 오류가 빌드를 안 깨뜨리기 때문이다.
검증
- diff 는 래퍼 6줄 추가뿐 — 본문·도식 파일 변경 없음
- 세 도식 라이트/다크 6개 조합 전부 통과
- 사이트 전체 47개 페이지 순회: 안 감싼 도식 0, 찌그러진 도식 0
- 로드맵 글 회귀 없음 (도식 9개, 상자 133개 유지)
커밋 30f4ae4 본문
안 된 것
1. 문서화가 위반을 0 으로 만들지는 않았다.
SKILL.md 의 첫 조건이 .diagram 래퍼인데도 3 개가 새어 나갔다.
규칙을 읽는 것과 지키는 것은 다르다. 진짜 해법은 빌드를 실패시키는 것인데 아직 안 했다.
Jekyll 플러그인에서 _includes/diagrams/ 를 참조하는 페이지가 감쌌는지 검사해
빌드를 깨뜨리는 방식이 가능하다. 이미 _plugins/tag_pages.rb 가 있으므로 자리는 있다.
2. “규칙 도입으로 수정이 줄었다”를 수치로 못 쓴다.
계산은 해봤다. 스킬 도입 전 fix 비율 29/95(30.5%), 후 9/35(25.7%).
하지만 쓸 수 없다. 앞 구간은 테마 전면 개편이고 뒤 구간은 글 추가다.
작업 성격이 달라 비교가 성립하지 않는다.
줄어든 것처럼 보이는 5%p 는 규칙의 효과가 아니라 작업이 쉬워진 결과일 수 있다.
3. 검증이 여전히 사람 눈에 의존한다.
라이트·다크 양쪽을 브라우저로 본다. 도식 182 개 × 2 테마 = 364 회.
전수 확인은 실제로 못 하고 바뀐 글만 본다. 스크린샷 비교 자동화가 다음 과제다.
4. Mermaid 한계는 실측했지만 대안을 못 찾았다.
노드 16 개를 flowchart LR 로 그리면 2543px 한 줄로 펴져
본문 폭(52rem)에 맞춰 축소되면서 글자가 안 읽힌다.
subgraph 를 화살표로 이으면 dagre 가 내부 direction 을 무시하고 전부 세로로 세운다.
elk 레이아웃도 결과는 같았다. 그래서 긴 선형 흐름은 인라인 SVG 로 간다 —
문제를 푼 게 아니라 경계를 그어 피한 것이다.
남는 결론
가운데 층만 강제력이 없고, 실제로 새어 나간 3 개가 전부 그 층의 항목이었다.
규칙을 잘 쓰는 것보다 규칙을 코드로 내리는 편이 확실하다는 게 이 작업에서 남은 것이다.
문서로 남겨야 할 것은 “무엇을 하지 마라”가 아니라,
코드로 내릴 수 없어서 사람이 기억해야 하는 것뿐이다.