Figma 라이브러리
CoUI 컴포넌트를 생성/관리하는 정본 Figma 라이브러리가 어디인지
목차
정본 파일 — CoUI — Cocode Design System v1.0 을 엽니다. 값은
coui_core가 정본이고, 이 파일은 그 투영입니다.
tools/figma-token-sync 플러그인(Generate Components)은 실행하는 순간
열려 있는 Figma 문서를 대상으로 컴포넌트를 만들거나 갱신합니다
(figma.root) — manifest.json 에는 특정 파일에 대한 바인딩이 없습니다.
즉 누가 다른 문서에서 플러그인을 실행하면 거기에 병렬 컴포넌트 세트가
생깁니다 — 정체성 보존은 "항상 같은 문서에서 재실행한다"는
전제 위에 있고, 그 전제가 사실인지 확인하는 곳이 바로 여기입니다.
정본#
| 필드 | 값 |
|---|---|
| 식별자 | coui-component-library |
| 표시 이름 | CoUI Component Library |
| Figma URL | CoUI — Cocode Design System v1.0 |
값의 단일 출처는 docs_core/content/_data/figma_library.json 한 파일입니다.
정본 문서가 바뀌면 이 파일의 값만 갱신합니다 — 플러그인이나 exporter
코드는 건드리지 않습니다. 이 파일은 packages/coui_core/bin/export_figma_tokens.dart
가 빌드 시점에 읽어 build/tokens.figma.json 의 meta.canonicalLibrary
에
실어 보내고, esbuild --bundle 이 그 JSON을 code.js 에 인라인합니다
(components.json 과 같은 경로 — 플러그인은 런타임에 아무것도 조회하지
않습니다. manifest.json 의 networkAccess: { "allowedDomains": ["none"] }
는 그대로 유지됩니다).
Foundation ↔ 문서#
캔버스의 Foundation 페이지와 소비자 가이드를 같은 축으로 맞춥니다.
노드 id 는 구조 스냅샷 기준이며, 페이지를 새로 만들면 바뀔 수 있습니다 —
그럴 때는 파일에서 페이지 이름으로 찾으세요. 값이 갈리면 coui_core 가
이깁니다.
| Figma Foundation | 문서 |
|---|---|
| 00. Overview | 디자인 토큰 |
| 01. Color | 색상 |
| 02. Typography | 타이포그래피 |
| 03. Layout & Space | 레이아웃 |
| 04. Shape | 형태 |
| 05. Effect | 효과 |
| 06. Motion | 애니메이션 |
| 07. Layering | 레이어링 |
| 08. Icon | 아이콘 |
컴포넌트 페이지(❖ Button 등)의 slug → node-id 는 같은 JSON 의 pages 맵입니다. 문서의 구조 (Anatomy)
섹션이 그 링크로 페이지를 엽니다.
매핑의 근거와 낡음 검사#
Anatomy는 코드 슬롯을 설명하고, Figma 링크는 캔버스의 해당 페이지를 찾는
진입점입니다. 두 문서가 같은 node-id를 쓴다는 것만으로 그 id의 실재가 증명되지는
않습니다. docs_core/test/markdown/figma_library_snapshot_test.dart는 기존 플러그인
구조 캡처의 파일 식별자와 페이지 이름·id를 맵과 대조합니다. 페이지가 없어졌거나,
그 id가 다른 컴포넌트의 것이거나, 캡처가 다른 라이브러리 파일에서 왔으면 실패합니다.
# docs_core 디렉터리에서
dart test test/markdown/figma_library_test.dart test/markdown/figma_library_snapshot_test.dart
이 검사는 커밋된 캡처를 기준으로 합니다. 라이브 Figma의 최신 상태를 조회하는 검사는 아닙니다. 캔버스를 변경한 뒤에는 구조 스냅샷 경로로 캡처를 갱신하고 다시 대조합니다. 캡처로 확인할 수 없는 페이지는 파일 URL과 페이지 이름까지만 링크하며, 숫자 id나 별도의 손 관리 매핑 목록을 새로 만들어 채우지 않습니다.
테마 주입·서페이스 프리셋·밀도는 코드 축입니다 — 테마, 서페이스 스타일. 그중 서페이스 스타일의
기하(반경·선 두께)와 밀도(간격·컨트롤 행 높이·행을 따르는 모서리)는 캔버스에서도 볼 수 있습니다 — 프레임에 Semantic/ Surface
컬렉션의 모드(neoBrutalism · claymorphism 등)를 고정하면 그 스타일의 모서리와 선이, Semantic/ Dimension
컬렉션의 모드(compact · spacious)를 고정하면 그 밀도의 간격과 행이 보입니다. 둘을 함께 고정하면 코드와 같은 순서(밀도 먼저, 스타일 나중)로 합성됩니다. 스타일이 그림자에 하는 일(단단한 오프셋 그림자 · 부풀린 그림자 · 거울 쌍)과 재질(반투명 채움 · 블러 · 굴절 · 림, 스타일이 더하는 보더와 그림자)도 그 모드를 따라 보입니다 — 표면의 채움이 그 뒤의
material 레이어에 그려지기 때문입니다. 다크 모드의 몫 · 다크의 그림자 세기(알파 ×4) · 채도 · 가장자리 광택은 캔버스에 없습니다 — 그림자 행의 알파는 라이트 세기입니다. 결정 문서의 우선순위는
디자인 소스.
타이포는 스타일로 적용한다#
화면의 텍스트에는 텍스트 스타일(Headline, Title, Body, Label
…)을 붙인다. 같은 값을 만드는 변수(font-size/*, letter-spacing/*)를 텍스트 노드에 직접 걸지 않는다
— 변수 직접 바인딩은 색과 간격에만.
이유는 두 가지다.
-
변수는 크기와 자간만 나르고 굵기와 행간은 노드에 남는다. 그래서 같은 역할의 제목이 화면마다 다른 굵기로 그려져도 아무것도 경고하지 않는다. 실제로 화면 제목 150여 개가 당시 시스템
Headline(SemiBold · 140%)과 다른 Bold · 160% 로 그려진 채 발견됐다. - 스타일 위에 변수를 겹치면 변수가 이긴다. 시스템에서 스타일을 고쳐도 그 노드만 안 따라온다.
점검법 — 텍스트 노드를 골라 오른쪽 패널을 본다.
| 보이는 것 | 판정 |
|---|---|
Text 섹션에 스타일 이름 (Body / Medium 등) | ✅ |
| 스타일 없이 크기 옆에 변수 칩 | ❌ 스타일을 붙이고 변수 칩을 뗀다 |
| 스타일 이름 과 변수 칩이 같이 | ❌ 변수 칩을 뗀다 — 스타일이 다시 이기게 |
컴포넌트 페이지(❖ …)는 플러그인이 생성하고, 같은 규약을 플러그인이 지킨다 — 라벨이 자기 역할 그대로면 그 역할의 스타일을 붙인다. 라벨이 그리는 크기·굵기·행간·자간·글꼴 중 하나라도 역할과 다르면(크기 사다리의 한 칸, Core 가 역할 위에 굵기나 행간을 얹은 자리) 스타일 없이 변수 칩으로 남는다. 스타일은 다섯을 한 묶음으로 덮어쓰기 때문에, 붙이면 그 라벨의 굵기·행간이 역할의 것으로 바뀐다. 그래서 컴포넌트 페이지에서 위 표의 두 번째 줄 모양은 결함이 아니다.
스타일 자체는 코드에서 온다 — 손으로 고치지 않는다#
텍스트 스타일 22개(Display/Large … Code/MD — Label/XL·Label/XL2·Body/XS·Label/XS·Code/XS·Code/SM
포함)는 Import Tokens 가 변수와 함께 이름으로 upsert 한다. 크기·자간·굵기·글꼴은 시맨틱 변수(font-size/*·letter-spacing/*·font-weight/<그룹>)에 묶이고, 행간은 export 가 준 % 리터럴로 쓰인다(Figma 는 행간에 묶인 숫자 변수를 단위와 무관하게 px 로 읽어 비율을 변수로 나를 수 없다).
Code/* 셋만 다르다 — 줄 높이가 비율이 아니라 21px 고정이라 PIXELS 로 쓰고 px 변수 line-height/code 에 묶는다. 이 값은 px 로 읽히는 것이 맞다.
컴포넌트 페이지의 라벨은 두 단으로 자간 변수를 찾는다 — 역할이 살아 있으면 letter-spacing/<role>(붙은 스타일이 나른다), 역할이 빠진 자리(Checkbox·RadioGroup 의 lg 라벨처럼 크기가 다른 곳, 역할 위에 굵기·행간을 얹은 곳)면 크기 키
letter-spacing/size-<n>. 두 번째 단은 변수 값이 노드의 자간 리터럴과 같을 때만 묶인다 — 다르면 리터럴로 남고, 그 리터럴은 그 자리의 자간이 아직 정해지지 않았다는 정직한 표시다(묶으면 변수가 리터럴을 덮어 그림이 바뀐다).
그래서 스타일의 굵기·행간이 틀려 보이면 스타일을 편집하지 말고 코드의 역할(CoreTypography)을 고친 뒤 export → Import 를 다시 돌린다. 스타일에 직접 쓴 값은 다음 Import 가 덮어쓴다. 플러그인의
드리프트 점검이 스타일 절을 따로 보고한다 — 없는 스타일, 풀린 바인딩, export 와 다른 굵기·행간.
고정폭 서체 — Geist Mono 를 설치한다#
Code/* 스타일과 컴포넌트 페이지의 코드 라벨은 Geist Mono 로 그려진다. 플러그인을 돌리는 컴퓨터에 Geist Mono 정적 굵기 넷(Regular · Medium · SemiBold · Bold)
을 설치한다 — 저장소의 docs_site/fonts/GeistMono-*.otf
가 코드가 번들한 것과 같은 판이다. 빠진 굵기가 있어도 Generate 는 멈추지 않는다. 그 굵기의 코드 라벨이 본문 서체로 그려지고, 실행 로그에 한 줄 남는다 — 캔버스의 코드가 본문 서체로 보이면 먼저 이것을 확인한다.
Geist Mono 에는 한글이 없다. 코드와 웹은 한글 구간을 Pretendard 로 그리지만, Figma 는 대체 서체를 고르지 못하므로 플러그인이 고정폭 글 안의 한글 구간을 Pretendard 로 칠한다. 컴포넌트 페이지는 Generate 가 알아서 칠한다. 디자이너가 화면에 직접 적은 고정폭 글은 라이브러리를 다시 Generate 해도 바뀌지 않으므로, 그 페이지에서 플러그인의 한글 구간 칠하기(「고정폭 글의 한글을 본문 서체로」)를 돌린다 — 프레임을 고르면 그 안만, 아무것도 고르지 않으면 페이지 전체를 칠한다. 글을 고쳐 쓰면 새 글자가 옆 글자의 서체를 따르므로 다시 돌린다.
구조 스냅샷 — 캔버스가 무엇을 바꿨는지 코드가 알게 하는 법#
플러그인의 구조 → .json 을 누르면 파일 전체(모든 페이지)의 노드 구조와
기하(id · 이름 · 타입 · x/y/w/h)가 파일 하나로 내려온다. 그 파일을 레포의
tools/figma-token-sync/reverse/incoming/ 에 커밋하면, 구조 드리프트 대조가
지난 베이스라인과 비교해 "디자이너가 무엇을 추가·삭제·이동·이름 변경했는지" 를
이슈로 남긴다.
이 버튼이 있는 이유: 자동화가 MCP 로 이 파일을 걷으면 페이지 목록 버그와 호출 예산 때문에 Cover 한 장밖에 못 본다. 플러그인은 파일 안에서 돌아 그 한계가 없다. 색 · 토큰 바인딩 · 타이포는 이 스냅샷에 없다 — 그건 충실도 → .json 이 답한다.
대조는 경고이지 차단이 아니다#
플러그인은 실행 시 현재 문서의 root pluginData에 스탬프가 있는지 봅니다.
없거나 다르면 "이 문서는 기록된 CoUI 라이브러리가 아닙니다 — 계속하면
별도 세트가 생깁니다" 확인을 요구하되, 계속 진행은 항상 허용합니다.
승인자가 명시한 대로 정본은 언제든 바뀔 수 있어야 하고, 차단형 게이트는
정본 교체 시점에 플러그인을 못 쓰게 만들어 오히려 사람이 우회하게
만듭니다.
A-5 (미결) — private org plugin 배포#
figma.fileKey 로 정확한 문서 대조(같은 파일인지 실제로 확인)를 하려면
이 플러그인이 private organization plugin 으로 배포돼야 합니다
(plugin-api.d.ts: fileKey 는 enablePrivatePluginApi
가 있는 private
플러그인에서만 접근 가능 — 현행 개발모드 로드에서는 undefined). 배포
전까지는 root pluginData 스탬프만으로 "처음 보는 문서인가"는 구분되지만
"이게 바로 그 정본 문서인가"까지는 확인할 수 없습니다 — 그 판단은 사람이
이 페이지의 URL 을 보고 합니다.
Organization 이상 플랜 확인이 필요해 별도로 결정되지 않았고, 나머지 작업과는 병렬로 진행 가능해 착수를 막지 않습니다.