CoUI Figma 라이브러리 소비 규약
cc-blueprint 가 Figma 에서 제품 화면을 저작할 때 쓰는 부품의 출처와 사용 방식을 고정한다.
cc-blueprint 가 Figma 에서 제품 화면을 저작할 때 쓰는 부품의 출처와 사용 방식을 고정한다.
라이브러리를 만드는 쪽이 아니라 쓰는 쪽의 규약이다 — 생성 파이프라인의 SoT 는
coco-de/coui 저장소의 tools/figma-token-sync 플러그인이다.
§1. 출처 — 무엇이 정본인가#
| 항목 | 값 |
|---|---|
| 라이브러리 생성기 | coco-de/coui tools/figma-token-sync — coui_core 컨트랙트의 투영 |
| 데이터 SSOT | components.json schema v3 (coui repo 산출물) |
| 페이지 규약 | 컴포넌트당 1페이지 + 섹션 헤더 페이지 — 이름·섹션·프레임 구성은 생성기 정본(coui tools/figma-token-sync README) 소유 |
| 축·프로퍼티 |
Variant=
·
Size=
·
State=
·
Enabled=
등 부품마다 다른 축 +
TEXT·BOOLEAN component property
(문구·플래그). 축 이름·값은 variant set 의 실제 property 에서 읽는다(추측 금지)
|
| 브랜드 축 |
색 variable 은 디자이너가 발행한 컬렉션
Semantic/ Color · {Brand}
로 나뉜다. 어떤 브랜드가 있는지는 실행 때 발견하고 하나를 골라 처음부터 바인딩한다 — 절차·금지는
figma-brand-selection.md
. 저작 중 컬렉션을 갈아끼우지 않는다. 반경 · 선 두께 · 그림자(와 라이브러리가 싣는 만큼의 표면 재질)는 라이브러리에
Semantic/ Surface
가 있으면 그 컬렉션의 모드(서페이스 스타일)로, 간격 · 컨트롤 행 높이는
Semantic/ Dimension
에 밀도 모드가 있으면 그 모드로 바뀐다 — 고르는 법은
figma-direction.md
§D2
|
| 버전 기준 |
둘이고, 묻는 것이 다르다.
① 스킬 기준 — 이 저장소 루트
coui.version.json
의
coui_skills_basis
(bare-widget 세대: 위젯
Co
접두 제거,
Core*
enum 체계 유지). 스킬 문서의 이름 · 예제가 그 세대로 쓰였는가를 답한다. ② 앱 기준 — 그 앱이 해석한 CoUI 릴리스(
.dart_tool/package_config.json
·
pubspec.lock
,
cc-pixel-loop:visual-verify
의
figma_compare.py coui
). 그 앱이 무엇을 그릴 수 있는가를 답한다 — 어떤 컴포넌트 · API 가 있는지, 캔버스와 렌더의 차이가 버전 차이인지는
앱 기준으로만
판단한다. ①을 앱의 판으로 읽으면 버전 차이를 앱 코드에서 "고친다".
라이브러리 구조·축은 마이너 단위로도 변한다
— 세대 내 변경의 감지·흡수는
skills/coui-library-update
|
§2. 사용 규칙#
- 라이브러리 인스턴스만 — 제품 파일에는 CoUI 라이브러리 컴포넌트의 instance 를 놓는다. detach·복제 후 수정·CoUI 에 있는 부품을 프리미티브로 흉내 전부 금지 (figma-workflow §4 L0). 인스턴스 안 값 조정은 복제가 아니다 — 허용 범위는 §7, 라이브러리가 표현 못 하는 필요는 §4·§8.
-
variant 는 property 로 전환 — 상태·크기·종류는 인스턴스의 component property
(
Variant=/Size=/State=/Enabled=/기타 축)로 바꾸고, 문구·플래그는 TEXT·BOOLEAN property 로 넣는다. 다른 variant 를 새로 그리거나 문구용 텍스트 노드를 덧대지 않는다. - 색·간격·타이포·표면은 제품 테마에 바인딩 — 라이브러리 및 제품 로컬 변수·텍스트/효과 스타일을 사용한다. 전체 테마와 컴포넌트/variant/state/슬롯/인스턴스 override를 구분한다. 제품 고유 값은 제품 토큰으로 정의할 수 있다. 등록한 base·brand·component 의존성은 혼입이 아니며, 의도하지 않은 기본 브랜드 잔존을 검사한다.
-
코드 대응 확인 — 인스턴스로 놓은 컴포넌트가 코드에서 무엇인지는 그 페이지의
Properties 표·Examples 프레임이 1차 소스다. Flutter 구현 시
cc-coui:*스킬 중 해당 컴포넌트의 것이 API 정본이다.
§3. 프리플라이트 (실행 단계의 정본은 design.md Step 0)#
그 단계가 기대는 규칙만 둔다:
-
변수·CoUI 컴포넌트 검색은
get_libraries로 얻은 CoUI 라이브러리 키로 한정한다(search_design_system의includeLibraryKeys=[CoUI 키]) — 변수는 컬렉션마다 같은 이름이 하나씩, 키만 다르게 나오므로 한정하지 않으면 엉뚱한 컬렉션의 것을 집을 수 있다. 아이콘 글리프는 별도 아이콘 라이브러리에 있어 이 검색에 안 나온다 — 그 라이브러리 키로 따로 찾는다. -
라이브러리가 '추가됨'이 아니라
libraries_available_to_add로만 보여도 키 import 는 동작한다 — 중단하지 말고 보고에 「라이브러리 미추가」를 적는다. -
세대(
coui_skills_basis)가 어긋나면 저작은 계속하고skills/coui-library-update로 라우팅한다(보고 문구는 design.md Step 0). 구현할 앱이 정해져 있으면 앱 기준도 본다 — 앱의 릴리스와 라이브러리 사이에서 달라진 컴포넌트는 그 화면의 캔버스와 렌더가 다를 자리다(design.md Step 0 항목 4). 저작은 막지 않는다. -
기본 브랜드 및 제품·컴포넌트 override 컬렉션의 이름·id·적용 범위를 DDR에 기록한다. 원격 import/로컬 조회 후 실제 소속과 해석값을 검증한다. 모드 고정·재바인딩은
figma-brand-selection.md§6을 따른다.
§4. 라이브러리에 없는 부품이 필요할 때#
디자이너 원본 규칙(figma-workflow §5 R5)의 판정 질문을 따른다 — 차단이 아니라 분류다:
- 기존 컴포넌트 조합으로 대체 가능한지 먼저 판정 (
cc-coui:coui-composition-and-extension참조) -
불가하면 판정 질문: 「이 제품이 아닌 다른 제품에서도 이게 필요한가?」
- 그렇다(범용) →
promote/{Component}로 만들어 쓴다 — 부채 표시이며 CoUI 승격 백로그로 보낸다(coui repo 에 승격 제안 + DDR 첨부 — §6 절차). 저작은 계속한다. - 아니다(도메인 전용) →
{domain}/{Component}(예: 제품 슬러그가petcare이면petcare/VaccinationCard) 도메인 컴포넌트로 만들어 900 components 페이지에 둔다.{domain}= 제품 슬러그(파일 이름·기존 도메인 접두에서) — 페이지 이름·레지스트리domain칸의 기능 도메인(store등)과는 다른 값이다. 여러 화면 재사용일 때만 컴포넌트화 — 한 화면 전용 요소는 해당 프레임 안에 그대로 둔다(둘 이상의 화면에 나오면 design.md Step 1.7).
- 그렇다(범용) →
-
두 경로 모두 CoUI 인스턴스의 detach·재제작으로 흉내 내지 않는다(§2-1).
/cc-blueprint:sync가promote/부채 목록을 추적한다.
§5. 라이브러리 부품이 잘못 그려질 때#
부품이 없는 것이 아니라 있는데 이상한 경우다. 가장 흔한 형태 하나를 이름 붙여 둔다 — 인스턴스를 늘렸는데 안쪽이 안 따라오는 것. 실제로 발견된 계열이며 한 부품의 문제가 아니라 여러 부품에 같은 모양으로 존재했다.
우회하지 않는다#
부품 안쪽 구조 결함(늘려도 겉면이 안 따라오는 것)은 소비 파일에서 고치지 않는다. 고치려고 detach·복제하면 §2-1 의 L0 다. 의미에 맞는 컴포넌트를 그대로 쓰고, 결함은 생성기에서 고친다. 결함 없는 다른 컴포넌트로 갈아타는 것도 답이 아니다 — 시안이 의미를 잃고, 재발행 후 다시 갈아타야 한다.
반대로 §7 의 허용 오버라이드는 우회가 아니라 부품을 쓰는 정상 방법이다 — 결함 이슈의 근거로도 세지 않는다.
예외는 하나다. 다른 컴포넌트가 그 자리에 원래 더 맞을 때는 갈아탄다. 그 판단의 근거는 "결함이 없어서"가 아니라 "형태가 맞아서"여야 하고, DDR 에 그렇게 적는다.
절차#
1. 재본다 — 자연 크기 인스턴스와 늘린 인스턴스를 나란히 두고 안쪽 노드를 대조한다.
재는 법은 `skills/coui-library-stretch-audit`.
2. 같은 계열이 더 있는지 훑는다 — 하나 나오면 대개 여럿이다. 세로 축과 비기본
variant 까지 본다.
3. coui 저장소로 라우팅 — §6 절차. 증거는 잰 숫자 그대로.
생성기 수정 PR 과 재발 방지 검사는 coui 저장소 작업이 요청 범위에 있을 때만 이어서 연다 — §6 처리는 범위와 무관하게 항상 한다.
4. 저작은 계속한다 — 차단하지 않는다. DDR 로 기록하고, 화면 레지스트리·설계 문서에
"재발행 대기" 와 §6-5 의 기록을 남긴다.
5. 해소 시점은 라이브러리 재발행이다 (`skills/coui-library-update`).
왜 소비 측이 이걸 발견하게 되는가#
생성기 쪽 검사는 빌더가 무엇을 썼는지를 본다. 이 결함은 그린 결과를 늘려 봐야 드러난다 — 소스에는 늘어난다고 적혀 있는데 늘어남을 담당하는 노드가 다른 노드였던 경우가 실제로 있었다. 그래서 제품 화면을 저작하는 쪽이 첫 목격자가 되는 일이 구조적으로 생긴다. 목격했으면 위 절차로 넘긴다 — 시안에서 조용히 피해 가면 다음 사람이 같은 것을 다시 발견한다.
§6. 라이브러리 결함 이슈 처리 — 모드별#
소비 측에서 라이브러리 결함을 목격하면 놓치지 않고 coco-de/coui 로 올린다. 사람에게 "이슈로 올릴까요?" 묻지 않는다. 외부 저장소에 쓰는 일은 요청자가 허용했을 때만 하므로 모드가 있다:
| 모드 | 조건 | 처리 |
|---|---|---|
| draft (기본 — 단독 실행 포함) | 요청문·세션 지시가 gh 사용을 명시적으로 허용하지 않음 | gh 를 쓰지 않는다(검색·댓글 포함). 아래 4번 본문을 초안 파일로 남기고 draft pending 으로 표시 |
| file | 요청자가 gh 로 이슈 등록을 명시적으로 허용 | 아래 절차 전부 — 이 경우 이 절이 상시 승인이다 |
등록 대상 — 무엇이 라이브러리 결함인가#
| 분류 | 판별 기준 (소비 측에서 잰 사실) |
|---|---|
| 크기 추종 | 인스턴스를 늘렸는데 부품 자신의 겉면(배경·외곽선·규칙선·트랙·본문 영역)이 따라오지 않음 — §5 |
| 속성 결선 누락 | TEXT·BOOLEAN component property 를 바꿔도 화면이 바뀌지 않음 (라벨·표시 여부가 노드에 연결 안 됨) |
| 속성 미노출·상한 | 화면마다 바꿔야 하는 문구·값·플래그가 property 로 노출되지 않아 직접 오버라이드만 가능하거나 표현 자체가 안 됨, 또는 스펙이 요구하는 개수가 부품 상한을 넘음(§8) |
| 미발행 | 원본 파일에 있는 컴포넌트가 소비 파일에서 key import 실패 (not found) |
| 렌더 붕괴 | 특정 셀·슬롯의 텍스트 폭이 0 이 되는 등 자연 크기에서도 깨짐 |
| 토큰 드리프트 | 코드 토큰과 라이브러리 variable 값이 다름 (coui-library-update 로 흡수할 skew 는 제외 — 재발행 필요 판정만 등록) |
| 접근성 기준 미달 | 부품 기본 토큰·크기가 접근성 기준(대비·터치 영역)에 못 미침 — 잰 값과 기준을 적는다(§C7 ③) |
등록하지 않는 것: 저작 실수, 정상 판정(skills/coui-library-stretch-audit "안 커지는
것이 맞는 것")에 해당하는 것, 제품 도메인에만 필요한 부품의 부재(§4 — 도메인 컴포넌트로 해결).
CoUI 에 없는 범용 부품의 부재는 결함이 아니라 승격 제안이다 — 같은 절차로 올리되 제목을
"승격 제안"으로 구분한다.
절차 (저작을 막지 않는다)#
1. 모은다 — 결함 1종 = 이슈 1건. 인스턴스마다 올리지 않는다. 같은 원인의 variant·축은 한 건에
묶고, 원인이 다르면 나눈다. 세션 중 발견분을 모아 저작 단계 끝(design.md Step 4 의 이슈 처리 단계)에 한 번에 처리.
2. 재본다 — 잰 숫자가 없으면 올리지 않는다. 자연 크기 vs 늘린 크기, property 전·후 값,
import 응답 원문. 가능하면 원본 라이브러리 파일에서 읽기 전용으로 다시 확인한다.
3. 중복 확인 (file 모드) — `gh issue list -R coco-de/coui --state all --search "<컴포넌트> <증상>"`.
열린 같은 이슈가 있으면 새로 만들지 않고 **새 증거를 댓글로** 단다. 닫힌 이슈에 같은 증상이
재발했으면 새 이슈를 만들고 닫힌 이슈를 링크한다(회귀 표시).
4. 본문 — coui 저장소의 이슈 템플릿·라벨을 따른다. 필수 항목:
현상 · 재현(원본 파일 key · 컴포넌트 node · 소비 파일 인스턴스 크기) · 측정값 ·
소비 측 대응(§7 오버라이드·§8 대안/생략) · 기대 동작 · 수정 위치 추정(생성기) · 재발 방지 검사 제안.
5. 되적는다 — 이슈 URL(또는 아래 draft pending 한 줄)을 self-check L1 사유 칸, DDR, 화면 레지스트리 note 에 넣는다.
L1 사유가 "라이브러리 결함"인데 둘 다 없으면 그 L1 은 사유 미기록으로 본다.
draft 모드 — 등록하지 않을 때#
gh 미인증·권한 없음·네트워크 차단이거나 draft 모드이면 저작은 계속하고, 위 4번 본문을 초안 파일로 남긴다. 위치: 요청이 정한 경로 → 없으면
.claude/docs/{scope}/coui-issue-{컴포넌트}-{증상}.md
→ 코드 scope 도 없거나 파일을 쓸 수 없으면 보고서 본문에 초안 전문을 싣는다(DDR 도 같은 곳). L1 사유에는 한 줄로 이슈 초안 {경로} — 미등록(draft pending)
을 적는다({경로} 는 파일 경로, 본문이면 보고서 본문 #{초안 번호}) — 이 문자열이 사유 미기록 판정의 기준이니 다른 문구를 쓰지 않는다.
3번 중복 확인은 gh 가 필요하므로 건너뛰고 초안에 "중복 미확인"으로 적는다. 최종 보고에 초안 대기 건수를 따로 싣는다. 등록한 척하지 않는다 — URL 이 없으면 등록되지 않은 것이다.
공개 범위#
이슈 본문에는 라이브러리 쪽 사실만 적는다. 소비 제품의 화면 문구·고객 데이터·내부 운영 경로는 넣지 않는다 — 재현에 필요한 것은 소비 파일의 인스턴스 크기와 노드 구조뿐이다.
§7. 인스턴스 오버라이드 허용 범위#
인스턴스를 놓은 뒤 안쪽 값을 바꿔도 되는 범위다. 어느 값을 쓰는지(행 인셋·슬롯 정렬의 선택)는
figma-composition-patterns.md
§C1~§C9 — 이 절은 연산이 허용되는지만 정한다.
인스턴스 오버라이드는 §2-1 이 금지한 '복제 후 수정'이 아니다 — 부품은 그대로 두고 값만 얹는 정상 사용이다. 전체 테마 위에 컴포넌트·variant/size/state·부모 슬롯·인스턴스 스타일을 적용할 수 있다. 로컬 토큰/효과 스타일도 허용한다. 지원하지 않는 내부 구조는 제품 컴포넌트 조합이나 API 확장으로 표현하고, 코드 지원 gap을 남긴다. 아래 표는 이미 검증한 연산의 예시이며 허용 속성의 상한이 아니다. 표 밖의 속성도 현재 API를 확인하고 작은 인스턴스에서 적용·읽기·렌더·swap 후 보존을 검증하여 사용할 수 있다. 지원 불가 속성은 제품 조합/스타일/API 확장으로 표현하고 코드 대응과 남은 gap을 기록한다.
| 허용 연산 | 방법 (실측) | 주의 |
|---|---|---|
| 글자 바꾸기 |
property 가 있는 글자는
setProperties
. 쓴 직후 그 글자 노드를 다시 읽어 안 바뀌었으면(결선 누락) 텍스트 노드
characters
를 직접 쓴다. 쓰기 전에 그 노드의
현재
폰트를 로드(
getStyledTextSegments(['fontName'])
→
loadFontAsync
)
|
로드 없이 쓰면 unloaded font 오류. 결선 누락·미노출은 §6 대상 |
| 채움·글자색·선·패딩 재바인딩 · 안쪽 선·반경 끄기 · 루트 clip·반경·gap · 효과 스타일 적용 |
인스턴스 루트와 안쪽 노드 모두 가능. 페인트는
setBoundVariableForPaint
가 돌려주는 새 페인트를 다시 대입. 효과는 라이브러리 효과 스타일(
shadow/*
·inset)을 키로 import 해 프레임·인스턴스 루트에
setEffectStyleIdAsync
— 크기 변화 없고 Variant 교체 뒤에도 유지
|
색·간격 출처는 §2-3. 높이 = 본문 + 2×패딩 + 2×선 두께. 선이 없던 안쪽 노드에 선을 새로 주면 루트가 커진다 — 기존 선 재바인딩이 먼저. 효과 스타일은 원격 스타일이라 로컬 스타일 생성이 아니고, 효과 컬렉션의
shadow/*
변수는 문자열이라 바인딩 대상이 아니다
|
| 정렬 |
인스턴스 루트
counterAxisAlignItems='CENTER'
. 대안: leading
layoutSizingVertical='FILL'
+
counterAxisAlignItems='CENTER'
— trailing 은 위에 남는다
|
leading 의
counterAxisAlignItems
단독
은 오류 없이 아무 일도 안 한다 — 쓰지 않는다. 루트 CENTER 는 trailing 도 가운데로 옮긴다
|
| 슬롯 부품 교체 | INSTANCE_SWAP property 를 setProperties 로 — 값은 variant 컴포넌트 id |
교체할 variant 는 이름으로 골라 Size 를 명시. 교체해도 슬롯의 기존 덮어쓰기(칠·선)가 새 내용에 남아 칠이 사라진다 — 교체 전 의도된 override를 기록하고 잔존값만 정리한다.
resetOverrides()
가 필요하면 이후 제품 override를 재적용·검증한다
|
순서 — Variant 를 바꾸면 fill 오버라이드가 사라지고, State 를 바꾸면 일부(루트 fill)가 사라진다. Size·TEXT 변경은 유지된다. 그래서 Variant·State·Size·글자를 먼저, 패딩·정렬·반경· 브랜드 재바인딩은 마지막에 한다.
지켜졌는지 — 인스턴스 안에 원본(main component)에 없는 노드 0(이름 경로로 대조 — 제품 컴포넌트의 SLOT 안은 제외) · 값·색은 design-self-check 3번.
§8. 사양과 라이브러리가 충돌할 때#
스펙이 요구하는 값·개수·구성·구분 단서를 정상인 부품이 표현하지 못하는 경우다 — 부품이 잘못 그려지는 §5, 부품이 없는 §4 와 다르다(예: Progress 값 · Tabs 7번째 탭).
규칙: 눈에 보이는 틀린 값을 시안에 남기지 않는다. 아래 순서로 처리한다.
1. 대안 표현 — 같은 의미를 다른 CoUI 부품·배치로 정확히 표현할 수 있으면 그것으로. 근거는 "결함이 없어서"가
아니라 "형태가 맞아서"여야 하고(§5 예외와 같은 기준) DDR 에 그렇게 적는다. 부품이 없어서(예: Badge·Button 을
담는 표) 못 그리는 것이면 §4 로 간다.
2. 대안이 없으면 생략 + known-gap 표시 — 틀린 값을 그리느니 그 값·항목을 빼고(요소 전체 또는 넘치는
항목만), 같은 섹션 안 프레임 행 아래에 두 줄짜리 캡션 텍스트 노트(첫 줄 `known-gap {요소}`, 둘째 줄 `스펙: {스펙이 요구한 값}`)를 눈에 보이게
둔다. 노트는 프레임이 아닌 텍스트 노드이고 제품 화면 안에는 넣지 않으며, 색·글꼴은 §2-3 그대로다.
3. 어느 쪽이든 기록 — DDR(스펙 문장 · 충돌 · 고른 처리 · 만료 조건) + 상류 목록(§6 — 표의 '속성 미노출·상한'
또는 승격 제안).
만료 — 대안·known-gap 마다 "coui 가 지원해 재발행하면 걷고 원래 요소로 되돌린다" 식으로 DDR 에 적는다. 만료 조건 없는 대응은 영구 우회가 된다.
지켜졌는지 — design-self-check 9번에서 표시값 ≠ 스펙값이 0 이고, known-gap 노트마다 대응하는 DDR 이 있으며 그 DDR 이 상류 목록 항목(§6-5)에 이어져 있다(같은 원인은 한 이슈로 묶이므로 노트 수와 이슈 수는 달라도 된다 — §6-1).