Figma 퍼블리싱 파이프라인 05. 제품 관리 탭 재퍼블리싱 실행 사례
운영 관리 상세 페이지의 제품 관리 탭을 다시 퍼블리싱했다. 이미 구현돼 있는 화면이라 새로 만드는 게 아니라 시안과 코드의 차이를 찾아 맞추는 작업이었다. --report 옵션을 붙여 단계마다 토큰이 얼마나 드는지도 같이 쟀다.
퍼블리싱해줘 https://www.figma.com/design/MiI74Bwt1BQ89xxzaMQ81u/...?node-id=19838-69031 --report3,285개 노드에서 두 섹션만 골라냈다
Phase 0에서 Python 스크립트가 Figma를 분석한다. 여기까지는 LLM을 안 쓰기 때문에 토큰이 거의 안 든다.
python3 .claude/scripts/figma_analyze.py "<FIGMA_URL>" \
--app admin --out /tmp/figma_spec.json| 항목 | 개수 |
|---|---|
| 추출된 노드 | 3,285개 |
| 이미지 에셋 | 15개 |
| 테이블 헤더 | 8개 |
| 테이블 바디 | 122개 |
| 토큰 매핑 | 112개 |
감지된 섹션은 LNB와 Main Container 둘이었고, 이번에 볼 건 Main Container라 그것만 골랐다. 여기까지 누적 400 토큰이다.
대상 코드는 세 파일이었다.
.../product-management/
├── product-management.tsx (메인)
├── product-management-control.tsx (필터)
└── product-management-table.tsx (테이블) ← 주요 변경필터는 손대지 않았다
탭 프레임과 필터 영역부터 대조했다. 활성 탭은 border-b-2에 grey.10, 비활성 탭 5개는 grey.5, 폰트는 SemiBold 14px, 좌우 패딩 40px에 간격 20px이었다.
필터는 광고 상태와 1차 카테고리 셀렉트, 시작일~종료일 날짜 범위, 상품명 검색, 초기화 버튼으로 구성돼 있었다. 여기는 건드리지 않았다. 기존 구현이 이미 시안과 일치했기 때문이다.
테이블은 컬럼 구조부터 달랐다
문제는 테이블이었다. 기존 코드는 7컬럼인데 시안은 8컬럼이었다.
| # | 기존 코드 | 너비 | Figma 시안 | 너비 |
|---|---|---|---|---|
| 1 | 광고 상태 | 120px | 상태 | 120px |
| 2 | 상품명 | 440px | 상품 정보 | 272px |
| 3 | 1차 카테고리 | flex-1 | 카테고리 | flex-1 |
| 4 | 상품 가격 | flex-1 | 상품 가격 | flex-1 |
| 5 | 판매 수수료율 | flex-1 | 구매 확정 수 | flex-1 |
| 6 | 노출 시작일 | flex-1 | 전환율 | flex-1 |
| 7 | 상세 | 90px | 광고 집행 기간 | flex-1 |
| 8 | — | — | 상세 | 90px |
컬럼 이름만 바뀐 게 아니라 지표 자체가 교체됐다. 판매 수수료율이 빠지고 구매 확정 수와 전환율이 들어왔다. 크기도 전반적으로 줄었다.
| 항목 | 기존 코드 | Figma 시안 |
|---|---|---|
| 상품 이미지 | 62×62px | 40×40px |
| 행 높이 | 78px | 56px |
| 행 배경색 | white | rgba(234,255,233,0.3) |
Phase 2에서 이 내용을 Chakra UI 기준 설계서로 정리했다. 컬럼마다 어떤 컴포넌트로 렌더링할지까지 확정해둔다.
{
"tableColumns": [
{ "name": "상태", "width": "fixed(120px)", "component": "StatusBadge" },
{ "name": "상품 정보", "width": "fixed(272px)", "structure": "ImageAsNext(40x40px) + Text(lineClamp=2)" },
{ "name": "카테고리", "width": "flex-1", "component": "Text(pre-caption-2)" },
{ "name": "상품 가격", "width": "flex-1", "component": "Text(pre-caption-1, localeString)" },
{ "name": "구매 확정 수", "width": "flex-1", "component": "Text(pre-caption-1, localeString)" },
{ "name": "전환율", "width": "flex-1", "component": "Text(pre-caption-1) + % suffix" },
{ "name": "광고 집행 기간", "width": "flex-1", "component": "Text(pre-caption-2, yyyy. MM. dd. ~)" },
{ "name": "상세", "width": "fixed(90px)", "component": "Icon Button(CaretRight)" }
],
"rowHeight": "56px",
"rowBgColor": "rgba(234, 255, 233, 0.3)"
}설계서를 먼저 확정하고 코드를 고치는 순서라, 이 JSON이 그대로 수정 지시서가 된다.
컬럼 정의만 바꾸면 되는 구조라 30줄로 끝났다
테이블이 컬럼 배열을 받아 렌더링하는 구조여서, 실제 수정은 그 배열을 갈아끼우는 일이었다.
// BEFORE
const columns: TableColumn<MockProductManagementItem>[] = [
{
key: 'status',
label: '광고 상태',
width: { type: 'fixed', value: 120 },
},
{
key: 'productName',
label: '상품명',
width: { type: 'fixed', value: 440 },
render: (item) => <ImageAsNext w="62px" h="62px" />,
},
{
key: 'commissionRate',
label: '판매 수수료율',
},
]
// AFTER
const columns: TableColumn<MockProductManagementItem>[] = [
{
key: 'status',
label: '상태',
width: { type: 'fixed', value: 120 },
},
{
key: 'productName',
label: '상품 정보',
width: { type: 'fixed', value: 272 },
render: (item) => <ImageAsNext w="40px" h="40px" />,
},
{
key: 'purchaseConfirmCount',
label: '구매 확정 수',
render: (item) => <Text>{item.purchaseConfirmCount.toLocaleString()}</Text>,
},
{
key: 'conversionRate',
label: '전환율',
render: (item) => {
const conversionRate = item.clickCount > 0
? Math.round((item.purchaseConfirmCount / item.clickCount) * 100)
: 0
return <Text>{conversionRate}%</Text>
},
},
{
key: 'startedAt',
label: '광고 집행 기간',
},
]전환율은 시안에 값이 그려져 있지만 저장된 필드가 아니다. 구매 확정 수를 클릭 수로 나눠 렌더링 시점에 계산했다. 분모가 0일 때를 막아두지 않으면 Infinity나 NaN이 화면에 찍힌다.
행 높이와 배경색은 한 곳에서 바뀐다.
// BEFORE
getRowProps={() => ({ className: 'group', h: '78px' })}
// AFTER
getRowProps={() => ({
className: 'group',
h: '56px',
bg: 'rgba(234, 255, 233, 0.3)'
})}Mock 데이터 인터페이스에는 conversionRate를 선택 필드로 추가했다. 렌더링 시점에 계산하는 값이라 데이터에 반드시 있어야 하는 건 아니다.
export interface MockProductManagementItem {
conversionRate?: number
}pnpm type-check는 통과했다. 다른 파일에 있던 기존 오류는 이번 작업과 무관한 것들이었다.
3,850 토큰으로 끝났다
| Phase | 작업 | 누적 토큰 |
|---|---|---|
| 0 | Figma 분석 | 약 300 |
| 0.5 | 섹션 선택 | 약 400 |
| 0.6 | 상세 분석 | 약 500 |
| 1 | 에셋 추출 | 약 600 |
| 2 | 설계서 작성 | 약 1,000 |
| 2.5 | 코드 비교 | 약 1,200 |
| 3 | 코드 생성 | 약 2,000 |
| 4 | 검증 | 약 3,850 |
같은 작업을 손으로 하면 15,000 토큰이 넘게 든다. 76% 정도 줄었다.
실제 변경량은 파일 2개에 30줄 남짓이다. 컬럼 2개 추가, 1개 삭제, 이름 3개 변경, 크기 관련 3개 조정.
막힌 지점과 원인
| 문제 | 원인 | 상태 |
|---|---|---|
| 새 패턴이 오면 분기를 통째로 고쳐야 함 | Phase 6~7이 TABLE 같은 패턴 이름에 if-elif로 묶여 있음 | 미해결 |
| Figma와 코드 불일치를 눈으로 대조 | 자동 검증 단계 없음 | 미해결 |
| 비교 기준이 되는 코드 정보를 매번 손으로 읽음 | Figma 쪽만 Python으로 분석하고 코드 분석 스크립트가 없음 | 미해결 |
파이프라인이 시안 쪽은 자동으로 읽는데 코드 쪽은 못 읽는 게 이번에 제일 걸렸다. 불일치 6건을 찾아낸 건 스크립트가 아니라 내 눈이었고, 페이지가 늘어나면 이 방식으로는 놓치는 게 생긴다.