Immutable. This exact content is served forever at /api/v1/blob/b3a4fcec3d7f13bd.
---
name: prep
description: Prepares a Figma design page before the real work — normalizing frame names to `[screen]-[state]`, creating and placing feature-level sections, and reading screen content to find missing cases (state variants, interaction results) and stub them as placeholder frames. Includes the absorb workflow that works around reparenting limits with local font files. It handles renumbering and handoff URLs; it does not verify or report violations — both status checks and completion checks belong to /fig:lint. Triggers - "/fig:prep", "tidy this page", "group these into sections", "stub the missing states", "프렙 해줘", "가이드대로 정리해줘", "빠진 케이스 채워줘".
allowed-tools: AskUserQuestion, Bash, mcp__plugin_figma_figma__use_figma, mcp__plugin_figma_figma__get_metadata, mcp__plugin_figma_figma__get_screenshot, mcp__claude_ai_Notion__notion-fetch
---
# fig:prep — 페이지 준비 (정리·섹션화·누락 케이스 placeholder)
요리의 *mise en place*처럼, 본 디자인·화살표 작업 전에 프레임이라는 재료를 전부 썰어 제자리에 갖춰두는 단계다.
디자인 파일의 한 페이지를 받아 (1) 프레임 이름을 규칙에 맞게 통일하고, (2) 기능 단위 Section으로 묶어 배치하고, (3) 각 화면의 실제 내용을 읽어 빠진 케이스를 placeholder 프레임으로 채운다. 정리가 끝난 페이지는 `/fig:arrows`로 흐름 화살표를 잇는 것까지가 한 세트다.
**전제**: `use_figma` 호출 전 반드시 `figma:figma-use` 스킬을 먼저 로드한다.
## When to invoke
- "이 페이지 가이드대로 정리해줘", "프레임 네이밍/섹션 정리"
- "빠진 상태 케이스 placeholder로 채워줘"
- 새 도메인 화면 설계 시작 전, 페이지 골격(섹션+placeholder)을 먼저 깔 때
## When NOT to invoke
- 흐름 화살표 생성·sync → `/fig:arrows`
- 규칙 위반 검증만(쓰기 0) → `/fig:lint`
- 작업분이 정본에 반영됐는지 감사·반영 → `/fig:sync`
- 화면 자체의 디자인 생성 → `figma:figma-generate-design`
- 파일 전체 구조 파악만 → `/fig:read`
## Inputs
- `figma_url` (필수): 정리할 페이지 URL. 페이지 prefix를 보고 적용 엄격도를 정한다
- `mode` (선택): 정리 실행. 생략하면 `/fig:lint` 로 현황을 먼저 파악해 정리안을 제안한다
## 규칙 원천 — 설정 파일 (fig:lint·fig:sync와 동일 출처)
규칙은 스킬 문서가 아니라 **`figma-conventions.yaml`** 이 정한다. 본문에 값을 적지 않는다.
```
python3 ${CLAUDE_PLUGIN_ROOT}/_common/scripts/lib/resolve-config.py --js <fileKey>
```
읽어 쓰는 절 — `naming`(프레임·섹션 패턴, 상태 접미사, 화면 유형별 필수 상태) · `layout`(간격 토큰·열 그리드·행 버킷) · `section_style` · `placeholder_style` · `pages`(엄격도·제외 섹션·보호 번호대).
- **대상 페이지의 이름으로 엄격도를 정한다.** `pages.readonly` 에 걸리면 작업을 거부하고 보고한다. `pages.free` 면 최소 규칙(프레임 네이밍)만 적용하고 섹션화·배치는 건드리지 않는다. `pages.strict` 여야 전 단계를 적용한다
- 어느 목록에도 안 걸리는 페이지는 **엄격으로 올리지 말고 사용자에게 묻는다** — 규약을 모르는 것과 규약이 없는 것은 다르다
- 설정이 없는 낯선 파일이면 `/fig:setup` 으로 관례를 역추출해 초안을 만든 뒤 시작한다
- 팀 가이드 문서가 따로 있으면 설정 `guide_source` 에 적는다. **매 실행 fetch 하지 않는다** — 최초 1회 흡수용 입력이다
## 핵심 컨벤션 (설정이 정하는 것과 이 스킬이 아는 것)
값은 설정에 있고, 아래는 **그 값을 어떻게 쓰는지**의 판단이다.
| 항목 | 규칙 |
|---|---|
| 프레임 이름 | `naming.frame` 형태 (예: `로그인-Default`, `로그인-ErrorModal`, `상품목록-Empty`) |
| Section 단위 | **기능 기준** — 도메인×기능으로 균일하게. UI 패턴(페이지냐 모달이냐)으로 가르지 않는다 |
| 분류 축 통일 | 한 Page 안에서 섹션 분리 기준은 하나로 — A 도메인은 기능별로 쪼개고 B 도메인은 통으로 두는 식의 혼용 금지 |
| Section 이름 | `naming.section` 형태. 번호 순서 = 사용자 흐름 순서 |
| Section 스타일 | `section_style` 그대로. **색을 구분 용도로 바꾸지 않는다** — 모든 섹션 동일 스타일 |
| 배치 (섹션 내) | **플로우 우선**: 1행 = 해피패스(시작 화면 → 진행 단계 → 완료 피드백, 좌→우, `layout.column_grid` 열 그리드). 분기 화면(모달·다이얼로그)과 그 후속은 출발 화면 아래 행에 좌→우로, 오버레이의 상태 변형은 그 오버레이 바로 아래. 풀페이지 화면의 상태 변형과 형제 타입 화면은 섹션 하단 **변형 존**으로(`layout.section_gap_same_row` 만큼 띄워 구분). 목록 화면의 상태 스택(단일 열)은 그 자체가 변형 존. **상태 변형 연속 스택(필수): 같은 `[화면명]`의 상태 변형은 한 열에 끊김 없이 연속으로 쌓고, 부모 화면과 그 상태 변형 사이에 다른 화면(전환 결과 모달·다이얼로그 등)을 끼우지 않는다.** 상태 변형 자리(부모 바로 아래)와 분기 후속 자리(출발 화면 아래 행)가 충돌하면 **상태 변형을 우선해 바로 아래 연속**으로 두고 분기 후속은 옆 열·다음 줄로 민다 — `[state]` 점선이 직선이라 사이에 프레임이 끼면 관통하기 때문(/fig:arrows 상태 체인과 연동). 화살표가 가로=진행·세로=분기로 짧게 흐르는 게 목적이다. 배치가 화살표와 싸우면 배치를 고친다 |
| 배치 (섹션 간) | **도메인 행**: 같은 도메인 섹션을 가로 한 행으로(좌→우 = 기능 순번), 도메인이 바뀌면 아래 새 행. 순번 = 행 우선. 행 상단 y 정렬. 간격은 `layout.section_gap_same_row`·`layout.domain_row_gap` |
| 제외 대상 | `pages.exclude_sections` 에 걸리는 섹션과 `pages.protected_numbers` 번호대는 **모든 단계에서 건드리지 않는다** |
## 공통 반복 요소 — 화면별 placeholder 대신 공통 페이지 + Default 주석
여러 화면에서 **같은 모습으로 반복되는 상태·요소**(범용 Empty·Error·Loading, 공통 빈 결과·공통 에러·공통 토스트/다이얼로그 등)는 화면마다 placeholder 프레임을 만들지 않는다. 같은 패턴을 화면 수만큼 복제하면 단일 출처가 사라지고, 한 번 바뀌면 전부 손봐야 한다. 대신 단일 출처로 모은다:
1. **전용 공통 페이지에 canonical 한 벌** — 설정 `naming.common_page_pattern` 이 가리키는 페이지에 그 상태를 1세트만 둔다(접두사는 `naming.common_frame_prefix`). 이게 반복 패턴의 단일 출처. **기존 재사용 자산을 먼저 찾아 쓴다**(디자인시스템의 spinner·feedback 컴포넌트 등) — 없을 때만 새로 만든다.
2. **각 화면은 placeholder 대신 Default에 주석** — 화면별로 빈/오류/로딩 프레임을 만들지 않고, 그 화면의 Default 프레임에 **Dev Mode 주석(annotation)**으로 공통 페이지를 참조한다. 주석은 클릭 점프되게 마크다운 링크로 단다(프리앰블 `commonRef`). 레이아웃을 침범하지 않으면서 "이 화면의 빈/오류/로딩 = 공통 참조"가 핸드오프에 남는다.
3. **이미 흩어진 placeholder가 있으면 제거 후 전환** — 화면별로 만들어둔 공통-성격 placeholder 프레임은 삭제하고, 거기 걸린 `[state]`·`-->` 화살표 중 삭제 대상을 가리키던 것도 함께 정리(끊긴 흐름은 `/fig:arrows`로 재연결). **삭제 전 그 화면에 별도 Default 원본이 있는지 확인**(유일본 삭제 방지) → 미리보기 → go.
**판단 — 공통이냐 화면 고유냐:** 그 상태가 *다수 화면에 같은 모습*으로 반복되면 공통 페이지로, *그 화면 고유*의 빈 문구·고유 에러·고유 인터랙션 결과면 종전대로 화면별 placeholder 유지(4단계). 애매하면 범위를 사용자에게 묻는다 — 화면 고유 상태까지 공통으로 몰지 않는다. **이미 설계가 채워진 실제 화면은 placeholder가 아니므로 삭제·주석 대상이 아니다**(이름에 `-Empty` 등이 붙어 있어도 내용이 설계돼 있으면 제외하고 보고).
공통 페이지의 canonical 상태 프레임은 placeholder와 같은 식별 스타일(점선 테두리·`Placeholder —`/`TBD`)로 두되, 디자인이 채워지면 그 한 곳만 갱신하면 모든 참조 화면에 반영된 것으로 본다.
## 제약: 로컬 폰트와 reparent — group+ungroup 우회 (2026-06-05 확인)
기존 프레임에 로컬 폰트(클라우드 미동기화) 텍스트가 있으면 `appendChild`/`insertChild`로 **섹션 안에 넣을 수 없다** — 폰트 로드 실패로 스크립트가 통째로 거부된다(원자적이라 파일 변경은 없음). `loadFontAsync`도 로컬 폰트는 실패한다.
**그러나 `figma.group([frame], section)` → `figma.ungroup(group)`은 폰트 검증을 타지 않는다.** 그룹을 섹션 자식으로 만들면서 프레임을 끌고 들어간 뒤 해제하면 프레임이 섹션 직속이 된다 — 수동 드래그가 필요 없다. 프리앰블 `absorb()` 가 이걸 한다. 주의:
1. 좌표 보정은 **group 호출 전의 부모를 확인**하고 한다 — 프레임이 이미 그 섹션의 자식이면 좌표가 이미 상대좌표라, 무조건 빼면 이중 차감으로 섹션 밖으로 튄다
2. group+ungroup마저 실패하는 환경이면 폴백: 프레임을 섹션 영역 위로 좌표만 이동 → `figma.currentPage.selection`에 담기 → 사용자에게 "선택된 프레임을 살짝 드래그하면 섹션이 흡수합니다" 1회 요청
새로 만드는 placeholder 는 설정된 클라우드 폰트만 쓰므로 이 제약이 없다 — **생성 시점에 올바른 섹션의 자식으로** 만든다.
## 현황 파악은 `/fig:lint`
정리 전에 무엇이 어긋나 있는지 보려면 `/fig:lint` 를 호출한다. **검사 항목·기준·감사 코드는 전부 거기 하나에 있다** — 검출과 수정을 분리하는 Design Lint 방식이고, 이 스킬은 고치는 쪽만 맡는다.
lint 리포트의 `[소속]`·`[경계]`·`[프레임겹침]`·`[섹션겹침]`·`[네이밍]` 이 곧 이 스킬의 작업 목록이다. 그대로 아래 Procedure 2단계 정리안으로 옮긴다.
검사 기준을 여기 다시 적지 않는다. 두 벌이 되면 개정할 때 갈라진다.
## Procedure
### 1. 가이드 + 인벤토리 수집
- **설정 해석** — `resolve-config.py --js <fileKey>` 로 이번 실행 기준을 확정한다
- 대상 페이지 이름으로 엄격도 결정 (`pages.readonly` 면 여기서 중단·보고)
- 페이지 `get_metadata`로 프레임·섹션 이름과 좌표·크기 수집
- 프레임 스크린샷으로 각 화면의 정체 파악 (5개씩 배치 병렬) — 이름만 보고 추측하지 않는다
- 기존 섹션이 있으면 fill·이름 패턴을 읽어 따른다
### 2. 정리안 제안 (미리보기 필수)
리네임·섹션 구성·배치를 표로 제시:
```
| 현재 이름 | → 새 이름 | 소속 섹션 |
```
- 섹션 구성이 여러 방식으로 가능하면(통합 vs 분리 등) 선택지로 제시
- 화면 정체가 불확실한 프레임은 추측 라벨 명시
- **사용자 go 전에 쓰지 않는다**
### 3. 실행 (단계 분할)
한 번에 다 하지 않는다 — 단계마다 검증 후 다음으로:
1. **리네임** — 폰트 무관, 일괄 가능
2. **섹션 생성** — 빈 섹션을 목표 위치·크기로. 생성 직후 `figma.currentPage.insertChild(0, s)`로 z순서 맨 아래로 내린다 (안 내리면 새 섹션이 기존 프레임을 흰 배경으로 덮음)
3. **프레임 배치** — 섹션 영역 위로 좌표 이동. 가이드 배치 규칙(폴백: 위 매트릭스·도메인 열·간격 토큰) 그리드로. **간격은 균일하게** — 가이드 토큰 값으로 통일 (Tidy Up과 같은 효과)
4. **흡수** — `figma.group([frame], section)` → `figma.ungroup()`으로 API 흡수 (로컬 폰트 무관, 위 "제약" 절). 흡수 후 메타데이터로 부모·상대좌표 확인
5. **섹션 리사이즈** — 섹션은 자동 리사이즈되지 않는다. 프리앰블 `resizeSection()` 이 `layout.section_resize_margin` 여백으로 처리한다. **리사이즈 직후 인접 섹션과의 겹침을 검사한다** — 늘린 섹션의 하단·우측이 아래·옆 행 섹션을 침범하면(특히 목록 상태 스택처럼 세로로 길어진 섹션) 침범당한 섹션을 `layout` 의 행 간격만큼 밀어 해소하고 재검사한다. placeholder 추가로 섹션을 키울 때 가장 흔한 사고
6. **순번 재부여** — 배열 확정 후 프리앰블 `renumber()` 로 일괄 재부여. `pages.protected_numbers` 는 제외하고 그대로 둔다
7. **레이어 순서 정렬 (옵션)** — 레이어 패널 순서를 캔버스 위치 순과 일치시킨다 (같은 부모 내 `insertChild` 재정렬이라 폰트 제약 없음). 가이드 요구사항은 아니므로 기본은 생략, 요청 시에만
### 4. 누락 케이스 검출 → placeholder (미리보기 필수)
**먼저 공통이냐 고유냐 가른다** — 검출한 누락 케이스가 다수 화면에 같은 모습으로 반복되는 공통 상태(범용 Empty·Error·Loading 등)면 화면별 placeholder를 만들지 말고 위 "공통 반복 요소" 절대로(공통 페이지 + Default 주석). 그 화면 고유 케이스만 아래대로 화면별 placeholder.
두 축으로 검사한다:
**(a) 상태 변형 체크리스트** — 설정 `naming.required_states` 가 기준이다. 화면 유형(목록·폼·검색)을 화면 내용으로 판정한 뒤 그 유형의 목록과 대조한다. 설정에 없으면 아래를 폴백으로 쓰고 그 사실을 보고에 적는다:
| 화면 유형 | 기대 상태 |
|---|---|
| 목록/조회 | Default, Empty(0건), Loading, Error(불러오기 실패) |
| 입력 폼 (추가/수정) | Default, Validation(필수·형식 오류), 저장 완료 피드백, 이탈 확인(작성 중 닫기) |
| 검색/필터 | 결과 없음 |
placeholder 이름의 상태 접미사는 설정 `naming.states` 에서 고른다 — 임의 접미사를 만들지 않는다.
**(b) 인터랙션 결과 화면** — 스크린샷에서 실제 UI 요소를 읽고, 그 요소를 눌렀을 때의 화면이 있는지 확인:
| 화면에 보이는 단서 | 있어야 할 케이스 |
|---|---|
| 삭제 버튼/링크 | 삭제 확인 다이얼로그 → 완료 피드백 |
| 항목 행/카드 (클릭 가능) | 상세 또는 수정 진입 화면 |
| 업로드 영역 | 업로드 실패/형식 오류 |
| 최대 개수 제한 문구 | 초과 시 상태 |
누락 목록을 표로 제시(어느 단서에서 추론했는지 근거 포함) → go 후 placeholder 생성. **정책이 미정인 케이스는 placeholder 설명에 `TBD(확인 필요): …`로 명시** — 임의로 확정처럼 그리지 않는다.
### 5. 검증·다음 단계
**이 단계의 필수 마지막 동작 — `/fig:lint` 호출(Skill 도구로).** 쓰기(생성·복제·이동·배치)가 끝나면 **항상** `/fig:lint`를 호출해 `STRUCT PASS`·`FLOW PASS`를 받는다(특히 clone/move가 끼면 무조건). 위반이 나오면 고치고 재호출하며, **PASS 없이는 완료로 보고하지 않는다.**
자가확인용 인라인 감사를 여기 두지 않는다. 같은 검사를 두 벌 들고 있으면 한쪽만 고쳐져 갈라진다 — 판정은 언제나 `/fig:lint` 하나가 한다.
- **격리 스크린샷만으로 PASS 판정 금지.** `node.screenshot()`·단일 노드 캡처는 프레임을 단독 렌더해서 **부모·캔버스 위치 오류를 못 잡는다** — 프레임만 보면 멀쩡해 보인다. 1차는 `/fig:lint` 의 수치 감사, 2차는 **섹션 노드 전체 스크린샷**(프레임 격리 X)이다. clone/move 가 끼면 거의 항상 여기서 사고가 난다
- **섹션별 핸드오프 URL 목록 출력** — `https://figma.com/design/{fileKey}/?node-id={섹션ID의 :를 -로}` 형식. 가이드의 "개발 전달은 Section URL 공유(Frame 개별 X)" 체크리스트에 대응
- 출력 마지막에: 추측 라벨 목록(사용자 확인 필요), TBD 정책 목록, 그리고 `/fig:arrows`로 흐름 연결 제안
## 구현 — 정리 헬퍼 프리앰블
쓰기 스크립트는 `${CLAUDE_PLUGIN_ROOT}/_common/scripts/prep-ops.js` 를 프리앰블로 쓴다. 설정 한 줄 + 이 파일 전문 + 실제 호출을 이어 붙여 `use_figma` 에 넣는다.
| 헬퍼 | 하는 일 · 주의 |
|---|---|
| `createSection(name, x, y, w, h)` | 섹션 생성. **z순서를 맨 아래로 내리는 게 필수** — 안 내리면 새 섹션이 기존 프레임을 흰 배경으로 덮는다 |
| `absorb(jobs)` | group+ungroup 흡수. 좌표 보정은 **호출 전 부모**로 판단한다(이중 차감 방지) |
| `resizeSection(section, w, h)` | 리사이즈 후 **침범한 이웃 섹션 이름을 반환**한다. 빈 배열이어야 안전 |
| `renumber()` | 캔버스 행 우선 순으로 `NN.` 재부여. 보호 번호대는 제외 |
| `placeholder(section, name, desc, w, h, x, y)` | 점선 테두리 placeholder. 정책 미정이면 desc 에 `TBD(확인 필요)` 병기 |
| `commonRef(frame, fileKey, pageId, label)` | Default 에 공통 페이지 참조 주석. 페이지 간 노드 하이퍼링크가 막혀 URL 딥링크를 쓴다 |
## Constraints
- **쓰기 전 미리보기 → go** — 리네임·섹션·배치(2단계)와 placeholder 목록(4단계)은 각각 별도 go
- `pages.exclude_sections` 에 걸리는 섹션은 리네임·이동·리사이즈·placeholder 어느 단계에서도 건드리지 않는다
- 한 호출에 10개 이내 작업, 단계마다 메타데이터/스크린샷 검증 후 진행
- **`clone()`·`createFrame`의 기본 부모는 currentPage(원래 노드의 섹션 아님).** 섹션 안에 두려면 흡수(group+ungroup) 후 좌표 설정해야 한다 — 안 하면 페이지 직속으로 남고, 거기에 섹션 상대좌표를 박으면 절대좌표로 해석돼 엉뚱한 곳으로 튄다. **복제·생성 직후 부모를 확인하고(`f.parent.type==="SECTION"`) 흡수 → 좌표 → 경계 감사**까지가 한 세트. 격리 스크린샷만으로 "됐다"로 끝내지 않는다(검증 5장)
- 빈 섹션의 스크린샷은 겹친 비자식 프레임을 렌더하지 않는다(흰 박스) — 흡수 검증은 프레임 쪽 부모 조회로
- placeholder에 출처 없는 정책을 확정처럼 쓰지 않는다 — 근거 없는 동작은 전부 TBD
## Notes
- 케이스 검출 표 (a)(b)는 시작점이지 전부가 아니다 — 화면을 실제로 읽고 "이 요소를 누르면 무슨 화면이 나와야 하나"를 화면마다 자문하는 게 본질
- 정리 직후 프레임 좌표가 바뀌므로, 기존 흐름 화살표가 있던 페이지면 `/fig:arrows` sync 를 같이 제안한다
- placeholder가 실제 디자인으로 채워지면 점선 테두리·"Placeholder —" 텍스트를 지우는 것까지가 완료 — 이름은 그대로 유지