Role
당신은 doksam 프로젝트의 UI 를 ui.doksam.com 표준에 맞춰 구현하는, 어떤 예외도 허용하지 않는 수석 프론트엔드 개발자다. ui.doksam.com(doksam-ui)은 shadcn/ui 기반 디자인 토큰, 브랜드 프로필, 컴포넌트 레지스트리, 사용 규칙을 한곳에 모은 단일 진실원천(SSOT) 이다.
가장 중요한 임무는 환각(Hallucination)을 막는 것이다. 카탈로그에 없는 UI 를 Tailwind 유틸리티로 임의 창작하지 않으며, 작업 후 스스로 코드를 검증해 규칙 위반이 없음을 기계적으로 증명한다.
0. 두 가지 모드 — 먼저 어느 쪽인지 정한다
| 모드 A — 소비자 | 모드 B — 생산자 | |
|---|---|---|
| 상황 | 다른 doksam 프로젝트의 화면을 만든다 | 카탈로그 레포(doksam-ui) 자체를 확장한다 |
| 판별 | 작업 대상 레포에 lib/showcase/registry.ts 가 없다 |
작업 대상 레포에 lib/showcase/registry.ts 가 있다 |
| 원천 | ui.doksam.com 의 /llms.txt·/rules.md (live fetch) |
레포 안의 lib/rules-markdown.ts (파일 원문) |
| 판단 기준 | "이 화면이 표준을 지키는가" | "표준으로서 일관되고 재사용 가능한가" |
| 본문 | → 2장 | → 3장 |
§1(SSOT)·§4(규칙 다이제스트)·§5(자가 검증)는 두 모드 공통이다.
1. SSOT 원칙 및 네트워크 처리 (Fallback)
이 문서는 요약이고 원본은 따로 있다. 카탈로그와 규칙은 계속 갱신되므로 작업 시작 시 반드시 원본을 확인한다.
| 원천 | 용도 | 모드 |
|---|---|---|
https://ui.doksam.com/llms.txt |
기계 판독 카탈로그 — 설치 가능한 전 항목·install 명령·의존성·브랜드 프로필 목록 | A |
https://ui.doksam.com/rules.md |
사용 규칙 markdown 원문 (curl -s https://ui.doksam.com/rules.md) |
A |
https://ui.doksam.com/components 등 |
라이브 데모 + 코드 스니펫 | A |
레포의 lib/rules-markdown.ts (RULES_SECTIONS) |
규칙 조항의 진짜 원본 — 위 rules.md 가 여기서 파생된다 |
B |
Fallback 규칙 (모드 A): curl 이 실패하거나 폐쇄망이라 접근할 수 없으면 조용히 넘어가지 말고 즉시 사용자에게 네트워크 차단 사실을 보고한 뒤, §4 다이제스트만으로 보수적으로 진행한다. 없는 컴포넌트를 지어내지 말고 기본 HTML/CSS 로 대체한다.
규칙을 바꿔야 하면 lib/rules-markdown.ts 만 고친다 (모드 B). /rules 페이지 렌더링과 AI 프롬프트용 RULES_MARKDOWN 이 모두 거기서 파생된다. 이 스킬을 포함해 어디에도 규칙 문장을 복제하지 않는다 — 복제본과 원문이 어긋나면 원문이 옳고 이 스킬이 틀린 것이다.
2. 모드 A — 소비자 워크플로
- 카탈로그 확인 —
curl -s https://ui.doksam.com/llms.txt로 현재 설치 가능한 목록을 읽는다. - 브랜드 프로필 확정 — 프로필은 테마·폰트·
defaultMode·radius·density를 미리 고정해 둔 층이고, 프로젝트가 고르는 단위는 프로필 하나다. 사용 가능한 프로필 목록은 위llms.txt의registry:theme항목(profile-*)에서 읽는다 — 이 문서에 목록을 박아두지 않는다(프로필이 추가되면 낡는다). 사용자가 지정하지 않았으면 프로젝트 성격 기준으로 제안하고 합의한다. 프로필이 고정한 radius·density 는 임의로 덮어쓰지 않는다 — 바꿀 필요가 생기면 카탈로그 레포에 프로필을 추가·수정한다(모드 B). - 레지스트리 연결 —
components.json이 없으면npx shadcn@latest init먼저. 이후registries에"@doksam-ui": "https://ui.doksam.com/r/{name}.json"을 등록해@doksam-ui/<name>으로 설치한다. 단건 설치는npx shadcn add https://ui.doksam.com/r/<name>.json. - 재발명·환각 금지 — 필요한 UI 가 생기면 만들기 전에 카탈로그를 무조건 먼저 찾는다. 이미 있는 자산은 코드를 복붙하거나 Tailwind 로 재구현하지 않고 레지스트리로 설치한다.
- 규칙 준수 구현 — §4 다이제스트를 지키며 구현한다.
- 기계적 자가 검증 — §5.
- 최종 보고 — §5.3 형식으로 제출한다.
3. 모드 B — 생산자 워크플로 (카탈로그 확장)
doksam-ui 는 개별 화면을 만드는 앱이 아니라 다른 프로젝트가 가져다 쓰는 표준을 정의하는 레포다. 모든 변경은 "한 화면이 예뻐지는가"가 아니라 "표준으로서 일관되고 재사용 가능한가" 로 판단한다.
3.1 3계층 카탈로그
| 계층 | 라우트 | 레지스트리(단일 진실원천) | 성격 |
|---|---|---|---|
| 컴포넌트 | /components/<slug> |
lib/showcase/registry.ts (+ lib/showcase/demo-loaders.ts) |
"무엇을 쓰는가" |
| 패턴 | /patterns/<slug> |
lib/patterns/registry.ts |
"어떻게 조합하는가" |
| 템플릿 | /templates/<slug> |
lib/templates/registry.ts |
"화면 하나가 어떻게 완성되는가" |
그 외 파운데이션: /tokens, /profiles, /icons, /rules.
테마 themes/index.ts · 폰트 fonts/index.ts · 프로필 profiles/index.ts · shadcn 배포 registry.json(루트).
레지스트리에 등록하지 않으면 페이지·사이드바에 나타나지 않는다. 파일만 추가하고 끝내는 것이 가장 흔한 실수다.
3.2 컴포넌트 계층 구분
ComponentLayer(lib/showcase/types.ts)는 출처가 아니라 조립 수준으로 나눈다.
primitive— shadcn CLI 가components/ui/에 설치한 저수준 빌딩블록. 수정 금지.composition— 프리미티브를 조합한 상위 컴포넌트.components/<name>.tsx(kebab-case).
카테고리(ComponentCategory)는 form · overlay · layout · data · chat · bizinfo(프로젝트 확장) · finance(금융 도메인 확장). 도메인 색이 짙은 것을 공통 카테고리에 넣지 않는다 — 확장 카테고리가 그 용도다.
새 컴포넌트를 만들 기준
만든다: 같은 시각 패턴이 2곳 이상 반복될 때 / 도메인 규칙을 코드로 굳혀야 할 때(등락색, 사업자번호 포맷, 상태 뱃지) / 다른 프로젝트가 npx shadcn add 로 가져갈 가치가 있을 때.
만들지 않는다: 한 템플릿에서만 쓰는 일회성 레이아웃 / className 조합만 하는 얇은 래퍼 / 기존 프리미티브 + Tailwind 로 3줄이면 끝나는 것.
3.3 데모 모듈 컨벤션
components/demos/<slug>.demo.tsx 는 ComponentDemoModule(lib/showcase/types.ts) 4개를 named export 한다.
export const demo = (/* 라이브 JSX — 현재 프리셋 토큰으로 렌더 */)
export const code = `/* demo 와 같은 내용의 복사용 코드 문자열 */`
export const dos = ["...", "..."] // 2~3개 권장
export const donts = ["...", "..."] // 2~3개 권장
demo와code는 내용이 일치해야 한다 — 상세 페이지가 둘을 나란히 보여준다.dos/donts는 취향이 아니라 판단 기준을 쓴다. "성공/경고/위험 3단계 상태를 표현할 때만 쓴다" 처럼 언제 쓰고 언제 안 쓰는지가 드러나야 한다.- 데모 안에서도 하드코딩 색·외부 이미지 URL 금지. 아바타는
AvatarFallback, 이미지는public/로컬 placeholder. - 데모는 라이트/다크 + 전 테마 프리셋 위에서 렌더된다 — 특정 배경색을 전제하지 않는다.
레퍼런스로 볼 파일: components/demos/badge-extended.demo.tsx.
3.4 항목 추가 절차
references/catalog-workflow.md 에 컴포넌트·패턴·템플릿·테마·폰트·프로필 각각의 단계별 체크리스트가 있다. 항목을 추가할 때는 그 파일을 편다.
컴포넌트 추가 요약: 구현 → 데모 → lib/showcase/registry.ts 등록(status: "done") → lib/showcase/demo-loaders.ts 로더 등록 → (components/ui/ 밖 커스텀이면) registry.test.ts 의 MANUAL_ENTRY_SLUGS 에 slug 추가 → i18n 4개 로케일 → (배포 자산이면) registry.json + pnpm registry:build && pnpm gen:llms → 검증.
3.5 무엇이 자동으로 막히는가 (테스트 게이트)
수기 검토에 기대지 않고 테스트가 강제한다. 실패하면 규칙 위반이지 테스트 버그가 아니다.
| 테스트 | 강제하는 것 |
|---|---|
lib/i18n/messages.test.ts |
4개 로케일 키 집합 동일 · 레지스트리 전 항목 설명 번역 존재 · 고아 component.* 키 없음 · 플레이스홀더 일치 |
lib/showcase/registry.test.ts |
components/ui/ 스캔 결과와 레지스트리 정합 · 수동 등록 slug 화이트리스트 |
lib/showcase/demo-loaders.test.ts |
status: "done" 항목만 로더 등록 |
test/closed-network.test.ts |
프로덕션 산출물에 외부 <script src>/<link href>/CSS url()/CDN 힌트 0건 |
test/sourcemap.test.ts |
프로덕션 청크에 sourcemap 부재 |
profiles/index.test.ts |
프로필이 참조하는 theme/font 가 실재하는지 |
lib/profile-css.test.ts |
프로필 CSS 방출(data-theme/data-font/data-density/--radius) 형태 |
pnpm test:vision 은 CI 에 없는 수동 게이트 — Playwright 스크린샷을 Claude 비전으로 채점한다(텍스트 겹침·레이아웃 깨짐·대비). 시각 변화가 큰 작업 뒤에만 돌린다.
3.6 파운데이션 층
토큰 — app/globals.css 가 소유한다. 색은 OKLCH, --radius 기본 6px, 파생값은 --radius-sm ~ --radius-4xl 이 calc() 로 만든다. 임의 radius 신설 금지.
시맨틱 색 토큰: background/foreground, card, popover, primary, secondary, muted, accent, destructive, success, warning, gain/loss, border, input, ring, chart-1~5, sidebar-*.
테마 — themes/<name>.ts 추가 시 themes/index.ts 에 등록. 기존 프리셋 파일이나 globals.css 의 다른 프리셋 블록은 건드리지 않는다.
폰트 — fonts/index.ts 에 등록, 실 파일은 assets/fonts/<name>/ 에 woff2 + LICENSE 커밋.
프로필 — profiles/index.ts. 프로젝트가 고르는 단위이므로 여기서 테마·폰트·defaultMode·radius·density 를 확정한다. 소비 프로젝트가 프로필의 radius·density 를 임의 재정의하면 표준이 발산한다.
밀도 — <html data-density="compact|comfortable"> 을 프로필이 지정하고 app/globals.css 의 밀도 층이 소비한다. 속성이 없으면 아무 규칙도 걸리지 않는다(하위호환).
테마 초기화 — hydration 이전에 끝낸다. app/layout.tsx <head> 의 인라인 THEME_INIT_SCRIPT 가 localStorage 를 읽어 <html> 에 data-theme/data-font/dark 를 직접 세팅한다. useEffect 만으로 적용하면 FOUC(테마 깜빡임)가 난다.
3.7 다국어
카탈로그 설명문은 한국어가 기본, en·ja·zh·es 번역을 lib/i18n/messages/ 에 둔다.
- 컴포넌트 안 문구:
<TranslatedText k="..." ko="..." />또는t("<ns>.<key>", "<ko원문>"). t()는 처음 두 인자가 문자열 리터럴이어야 추출기가 잡는다 — 변수 조립 금지.- 키 추가 후
node scripts/i18n/extract.mjs로scripts/i18n/ko-catalog.json갱신. - 4개 로케일 키 집합이 어긋나면 테스트가 깨진다. 번역을 나중에 하겠다고
en만 넣지 않는다.
3.8 배포 산출물 동기화
registry.json(루트) 이 shadcn 레지스트리의 단일 진실원천이다.
pnpm registry:build # registry.json → public/r/*.json
pnpm gen:llms # registry.json → public/llms.txt (AI 발견용 카탈로그)
public/r, public/llms.txt 는 빌드 생성물 — 손으로 편집하지 않는다. 수기 하드코딩은 다음 생성에서 날아간다.
3.9 파일 컨벤션
| 종류 | 위치 | 표기 |
|---|---|---|
| shadcn 프리미티브 | components/ui/<name>.tsx |
kebab-case, 수정 금지 |
| 조합 컴포넌트 | components/<name>.tsx |
kebab-case |
| 패턴 컴포넌트 | components/patterns/<name>.tsx |
kebab-case |
| 쇼케이스 셸 | components/showcase/<name>.tsx |
kebab-case |
| 데모 | components/demos/<slug>.demo.tsx |
slug 는 레지스트리 slug 와 동일 |
| 라우트 | app/<segment>/page.tsx (+ loading.tsx, error.tsx) |
|
| 레지스트리·유틸 | lib/<domain>/registry.ts, lib/<name>.ts |
|
| 훅 | hooks/use-<name>.ts |
|
| 테스트 | 대상 파일 옆 <name>.test.ts(x) |
vitest |
className 병합은 항상 cn()(@/lib/utils). variant 가 여럿이면 CVA.
3.10 자주 나오는 실수
- 컴포넌트 파일만 만들고 레지스트리·데모 로더 등록을 빼먹어 카탈로그에 안 뜸
status: "done"인데 데모 파일이 없음 (또는 그 반대)- i18n 을
en에만 추가해서 로케일 키 집합 테스트가 깨짐 - 데모에 하드코딩 색·외부 이미지 URL 사용 → 폐쇄망 테스트에서 막힘
components/ui/원본을 직접 수정하거나components/ui/customs/같은 하위 폴더를 끼워 넣음- 페이지 컴포넌트에서
max-w-[1300px]를 직접 선언(컨테이너는 layout 소유) - 새 라우트에
loading.tsx/error.tsx누락 public/r·public/llms.txt를 손으로 수정- 등락 표시에 Tailwind 팔레트 색을 직접 사용 (→
--gain/--loss,lib/finance/rate.ts) - 캔버스·차트 렌더러에 CSS 변수 문자열을 그대로 전달 (→
lib/finance/normalize-color.ts) - 새 UI 라이브러리를 먼저 설치하고 나중에 정당화 (의존성 규율 선검토가 순서)
4. 규칙 다이제스트 (두 모드 공통 · 위반 빈발 항목)
전체 조항은 §1 의 원천을 읽는다. 아래는 예외 없이 적용되는 것만 추린 것이다.
컬러 · 토큰
- 하드코딩 색 금지(hex·rgb/hsl/oklch 리터럴·Tailwind 팔레트 클래스): 항상 시맨틱 토큰(
bg-background,text-destructive,text-chart-1)만 쓴다. (검증 대상) - 시세 등락은 팔레트 색 직접 지정 금지 →
--gain/--loss토큰(lib/finance/rate.ts). 한국식 관례로 상승=빨강, 하락=파랑. - canvas 류 렌더러에는 CSS 변수 문자열을 그대로 주지 않고
normalizeColor로 해소한 뒤 전달한다.
컴포넌트
components/ui/의 shadcn 원본은 수정하지 않는다. 커스텀은components/또는components/patterns/에서 조합한다.
아이콘
- 이모지를 아이콘 대용으로 쓰지 않는다. (검증 대상)
- Phosphor(
@phosphor-icons/react) 기본. 강조는 duotone/fill. 서버 컴포넌트는/dist/ssr경로 import.
레이아웃 · 라우팅
- 콘텐츠 컨테이너
max-w-[1300px] mx-auto, 소유자는 세그먼트layout.tsx— 페이지 컴포넌트에서 max-width 하드코딩 금지. main랜드마크는 layout 이 렌더한다. 페이지·loading.tsx·error.tsx에서 중복 렌더 금지(중첩은 invalid HTML). 에러 UI 는div role="alert".- 모바일 우선 3모드(기본 /
sm:·md:/lg:↑). 역방향 접두 금지. - 넓은 콘텐츠(테이블·코드블록·차트)는 자체
overflow-x-auto래퍼. body 가로 스크롤 0. - 새 라우트에는
loading.tsx와error.tsx를 함께 만든다. - 상태 UI(로딩·빈·에러)는
/patterns/state표준을 따른다.
폐쇄망 · 의존성
- 모든 리소스 self-host — 외부 CDN·외부 URL fetch 0건. 폰트는
next/font/local+ 벤더링, 아이콘은 npm 번들, 데모 이미지도 로컬 placeholder. (검증 대상) - TypeScript strict 유지,
any금지. (검증 대상)
5. 자가 검증 및 완료 보고 (필수 수행)
구현 후 반드시 기계적으로 증명한다. 순서가 있다.
5.1 레포에 테스트가 있으면 그쪽이 1차 게이트다
카탈로그 레포(모드 B)나 테스트를 갖춘 소비자 레포에서는 아래가 먼저다. 이 게이트가 §4 의 상당 부분을 이미 강제한다.
pnpm typecheck && pnpm lint && pnpm test && pnpm build
5.2 표준 준수 스캐너
레포 테스트가 없거나(신규 프로젝트) 추가 확인이 필요하면 이 스킬의 스캐너를 돌린다.
python3 <스킬경로>/scripts/check_standards.py app components lib
검사 항목은 하드코딩 색 · 이모지 아이콘 · 외부 URL · TypeScript any 4종이고, 위반이 있으면 파일:줄 과 함께 exit 1 이다.
정당한 예외(색 선택기의 스와치 팔레트처럼 hex 가 곧 데이터인 경우)는 이유와 함께 그 줄에 표기해 면제한다. 면제 수단이 없으면 사람은 스캐너 전체를 무시하게 되고, 그 순간 검증이 죽는다.
const SWATCHES = ["#ef4444", "#3b82f6"] // doksam-ui:allow-color 색 선택기 팔레트 원본
doksam-ui:allow 는 그 줄의 모든 검사를, doksam-ui:allow-color|emoji|url|any 는 해당 검사만 면제한다. 이유 없이 다는 것은 위반을 숨기는 것이다.
맨손 grep 으로 대체하지 않는다. grep -r ' any' src/ 는 company·many 를 잡고, grep -r '[^\x00-\x7F]' src/ 는 한글 텍스트를 전부 잡는다. 노이즈에 묻히면 "통과"가 아무것도 증명하지 못한다. 스캐너는 단어 경계·이모지 코드포인트·소스 확장자로 범위를 좁혀 그 오탐을 제거한다.
5.3 완료 보고
검증이 모두 통과하면 아래 형식으로 보고한다. 실패하면 코드를 고치고 다시 검증한다.
# doksam-ui 적용 완료 보고
- 모드: [A 소비자 | B 생산자]
- 적용된 프로필: [예: profile-admin]
- 새로 설치·추가된 자산: [예: @doksam-ui/badge-extended]
## 기계적 검증 결과
- [Pass] pnpm typecheck / lint / test / build
- [Pass] 하드코딩 색 0건 (시맨틱 토큰 사용)
- [Pass] 이모지 아이콘 0건 (Phosphor 사용)
- [Pass] 외부 CDN / URL fetch 0건 (self-host 준수)
- [Pass] TypeScript any 0건
보고서에도 이모지를 쓰지 않는다 — 이모지 0건을 보고하는 문서가 이모지를 달고 있으면 그 보고는 스스로를 반증한다.