@conscience-technology/ctds 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,102 @@
1
+ # Recipes
2
+
3
+ **자리별 조립 레시피 모음.** 벤더에 없는 자리를 조립할 때, 토큰·vendor 컴포넌트의 **정식 조합 규격**이다.
4
+
5
+ ## 왜 레시피인가
6
+
7
+ 벤더 컴포넌트 문서(`components/*.md`)는 "**있는 컴포넌트를 어떻게 쓰는가**"만 다룬다. 하지만 실제 페이지는 벤더가 없는 자리가 더 많다 — 페이지 헤더, 섹션 그룹, 메타 라인, 빈 상태 등. 이 자리들은 **여러 토큰과 vendor 컴포넌트의 조합**으로만 성립하고, 조합의 리듬·위계·여백이 매번 달라지면 CTDS의 결과물이 흩어진다.
8
+
9
+ 레시피는 그 **조합을 못 박는다** — 벤더 컴포넌트를 추가하지 않고도, 조립 시점에 "이 자리는 이 토큰 조합"이라는 표준을 제공.
10
+
11
+ ## 레시피 작성 규칙
12
+
13
+ 레시피 파일은 [[GUIDE]]의 **공통 골격**을 따르되, 아래 추가 섹션을 포함한다.
14
+
15
+ ```markdown
16
+ # <레시피 이름>
17
+
18
+ ### 원칙
19
+ <1~2줄. 이 자리의 역할·언제 쓰는가>
20
+
21
+ ### Anatomy
22
+ <이 자리를 구성하는 요소. 뼈대 구조. 수치가 아니라 역할 중심.>
23
+
24
+ ### Do
25
+ - <토큰·vendor 조합 규칙 (명령형)>
26
+
27
+ ### Don't
28
+ - <금지 조합. [HARD]/[SOFT] 명시 — PRINCIPLES §4a>
29
+
30
+ ### Fallback
31
+ <규칙 밖 자리에서의 기본 선택>
32
+
33
+ ### 조립 예시
34
+ <규칙을 종합한 최소 코드 예시 1개. 수치 하드코딩 금지, 토큰·vendor만.>
35
+
36
+ ### 관련
37
+ - [[다른 레시피]] / [[components/X]] / [[tokens/Y]]
38
+ ```
39
+
40
+ ## 등재된 레시피
41
+
42
+ - [[recipes/page-header]] — 페이지 상단: 큰 타이틀 + 서브텍스트 + (옵션) 액션
43
+ - [[recipes/section-header]] — 섹션 상단: 중간 타이틀 + (옵션) 설명/액션
44
+ - [[recipes/meta-line]] — 메타 라벨 가로 나열 (구분자 포함)
45
+ - ~~[[recipes/card-row]]~~ — **폐기**. 리스트 자리는 [[components/list-item]] 벤더 사용 ([[PRINCIPLES]] §5a HARD)
46
+ - [[recipes/form-footer]] — 폼·모달 하단 액션 묶음
47
+ - [[recipes/page-banner]] — 페이지 폭 안내 배너
48
+
49
+ > "빈 상태"는 vendor [[components/empty-state]]로 해결 — 조립 레시피 아님.
50
+
51
+ ## 신설 기준
52
+
53
+ 새 자리가 **3번 반복**되면 레시피로 올린다. 1~2번은 vendor anatomy 복제로 처리하되, 3번째가 되면 조합 규칙이 자리 잡은 것 — [[ASSEMBLY]] §2 목록에 추가하고 이 디렉토리에 파일 신설.
54
+
55
+ ## 조립 테스트 결과 (레시피 품질 보증)
56
+
57
+ 레시피는 **반드시 실제 조립으로 검증**된다. 테스트 페이지는 `web/app/assembly-test/`. 지금까지 검증 중 발견된 결함:
58
+
59
+ - **meta-line separator** — 초기 레시피의 `width:1px / height:0.75em`가 check_tokens hook에서 리터럴로 차단됨 → `border-left: 1px solid var(--border)` + `align-self: stretch`로 수정
60
+ - **투명 테두리 가드 금지** — `border: 1px solid transparent` 가드 패턴이 check_bg_stroke_combo hook에서 차단됨 → border 안 쓸 땐 선언 자체 생략. page-banner, card-row 레시피 수정
61
+ - **날짜 포맷** — 초기 예시들의 "10. 13." 숫자 날짜가 [[feedback-date-writing]] 룰 위반 → "10월 13일" 표기로 수정
62
+ - **Callout 유령 참조** — `[[components/banner]]`가 유령 → `[[recipes/page-banner]]`로 교체
63
+ - **아이콘 이름 misname** — 레시피 예시의 `name="chevron-right" / "refresh" / "share" / "code" / "shield" / "checklist" / "terminal"` 중 어느 것도 CTDS Icon 세트에 **등재되지 않음**. 각각 `caret-right / arrow-clockwise / share-network / brackets-square / shield-check / clipboard-text / brackets-square`로 교체. **레시피에 "아이콘 이름은 `web/components/Icon/Icon.tsx`의 phosphor 이름 레지스트리에서 찾을 것" 가이드 추가 필요**
64
+ - **Badge API prop 오류** — 레시피가 `tone="warning"`을 썼지만 실제 API는 `status="warning"` + `variant="soft"` 조합. `tone`은 Callout prop. 레시피 수정 필요
65
+ - **Vendor prop 선언 확인 공백** — ASSEMBLY §1 2단계 "가장 가까운 벤더 anatomy 베끼기"는 API까지 확인하지 않으면 복제가 깨짐. ASSEMBLY에 **"vendor prop·타입 선언을 반드시 확인"** 단계 추가 필요
66
+ - **Compound component의 Server Component 경계 — "use client" 전염** — Stepper처럼 `"use client"` 벤더를 Server Component page에서 compound property(`Stepper.Item`)로 접근하면 **런타임에 `Element type is invalid` + undefined**. Next 15/React 19 경계 이슈. 레시피 조립 코드는 vendor 하나라도 client면 **page 자체도 `"use client"`로 선언** — ASSEMBLY에 "조립 page가 client 벤더를 포함하면 'use client' 강제" 가이드 추가
67
+
68
+ **브라우저 렌더 1차 피드백 (유저 육안 검증, 2026-10-14)**:
69
+ - **섹션 간 gap `space-6` 과잉** — 콘솔 리듬에서 섹션이 border/배경으로 이미 분리되면 `space-4`가 적정. page 레벨 gap을 `space-4`로 조정 + ASSEMBLY 체크리스트에 "섹션 간 space-6 과잉" 항목 추가
70
+ - **섹션 타이틀 `text-xl` + medium → "크고 얇음"** — [[recipes/section-header]] 기본값을 `text-lg`로 다운. text-xl은 랜딩 리듬에만
71
+ - **멀티라인 banner padding 타이트** — `padding: space-2 space-4` + `gap: space-0.5`가 멀티라인에서 숨 안 쉼. 단일/멀티라인 2 모드로 분리 + 멀티라인은 상하 `space-3`
72
+ - **아이콘 + 다줄 텍스트 center 정렬 함정** — 긴 inline hint에서 `align-items: center`면 아이콘이 중앙쯤 뜸. `flex-start` + `margin-top: space-0.5`로 **첫 줄 baseline** 맞춤 필요
73
+ - **"라벨 혼자 위에 뜨는" 함정** — Stepper 카드에서 "작업 전 점검" 라벨을 Stepper 위에 flex-end로 두니 라벨 영역만 공백 → 라벨은 header 자리로 명시적 묶음
74
+
75
+ **브라우저 렌더 2차 피드백 (유저 육안 검증, 2026-10-14)**:
76
+ - **"page gap 하나로 다 담당" = 통짜 느낌의 근원** — 섹션 간 gap을 `space-4`로 줄여도 모든 블록 사이가 같은 공간이면 역할 분리가 안 됨
77
+ - **"Stepper 카드" 자체 폐기** — 카드로 감싸면 상하좌우 균일 padding이 공식인데 "라벨 혼자" 있는 조합은 균일 못 지킴. 카드를 포기하고 페이지 흐름에 그대로 놓는 쪽이 정답. → ASSEMBLY §2b "카드 포기 시그널" 섹션 신설
78
+ - **섹션 header가 "통짜 라벨"만 있으면 어색** — h2만 떠있고 count·설명·eyebrow 없으면 섹션끼리 구별 안 됨. count badge 또는 설명 1줄 **의미 있는 자리면** 넣기
79
+
80
+ **브라우저 렌더 3차 피드백 (유저 육안 검증, 2026-10-14)** — ★ 레이아웃 전면 교체:
81
+ - **"margin-top differential rhythm"은 틀린 결론이었음** — 2차에서 시도한 `A + B { margin-top: ... }`도 그룹 설계가 틀렸다는 신호였음. 정답은 **wrapper 추가 + 그룹별 flex column + gap**. 레이아웃 1원칙은 "**관련있는 것끼리 묶기**" — 이게 **padding/margin으로 간격 조정보다 선행**해야 함. → ASSEMBLY §2a 전면 재작성, [[feedback-group-first-layout]] 메모리 교체 (기존 differential rhythm 폐기)
82
+ - **유령 토큰 `--font-medium` → 모든 타이틀 얇음의 숨은 원인** — 실제 토큰은 `--font-weight-medium`. 레시피들이 전부 `var(--font-medium)` 썼고 **check_tokens hook이 유령 토큰은 못 잡음** (리터럴만 잡음). 조용히 상속된 400으로 렌더되어 h1·h2·cardTitle 전부 얇음. → ASSEMBLY §1 체크리스트 "font-weight 토큰 이름 정확성" 추가, 전수 교체
83
+ - **섹션 header의 `margin-bottom`은 틀림** — section 자체를 flex column + gap으로 묶으면 header와 body가 자연스럽게 간격을 가짐. margin-bottom은 그룹 설계 실패의 흔적
84
+ - **3-tier 리듬 확립**: 그룹 안 `space-2~3` / 섹션 반복 `space-6` / 그룹 사이 `space-10`. 콘솔 리듬 기준
85
+ - **`<section>` 태그 금지의 재확인** — AppShell의 `.content section`이 `padding-top: var(--space-20)` = **80px**을 모든 `<section>`에 꽂음. 메모리 [[feedback-no-semantic-tags-in-components]]가 이미 못 박은 함정인데 레시피 예시가 `<section>`을 그대로 썼음. 전수 `<div>`로 교체 + section-header 레시피 Don't에 명시 + ASSEMBLY §1 체크리스트 추가
86
+ - **그룹 안에서도 "미니 그룹" 필요** — introGroup 안 banner↔header↔hint 셋을 `space-3` 하나로 통일했더니 "같은 거리로 뭉쳐" 성격 구분 안 됨. **banner(메타)와 headerBlock(타이틀+설명)**을 분리하는 중간 wrapper(headerBlock) 추가 → introGroup gap을 `space-6`로 벌리고 headerBlock 안은 `space-2` 좁게. 그룹 설계는 **재귀적**이어야 함.
87
+ - **3-tier 리듬 수치 상향** — 초기 "섹션 반복 space-6 / 그룹 space-10"은 실측에서 좁았음. "섹션 ↔ 섹션 반복"과 "그룹 ↔ 그룹"은 둘 다 `space-20` (80px) 수준이 자연스러움. space-6은 "섹션 안 블록 단위" 또는 "같은 그룹 안 성격 다른 요소"에만.
88
+ - **"가까운 관계일수록 더 좁게" 원칙 확립 (ASSEMBLY §2a 재작성)** — 유저 지적: section header↔body `space-2` + cardList 아이템 간 `space-2`가 **같은 값**이라 "타이틀과 아이템이 같은 레벨"로 뭉쳐 보임. **인접한 두 레벨은 반드시 한 단계 이상 차이**가 나야 위계 성립. 수정: 아이템 간 `space-1`, 섹션 header↔body `space-3`. 같은 원칙으로 headerBlock gap도 `space-2`→`space-3` 상향 (pageHeader 안 title↔description이 `space-1`이므로)
89
+ - **"아이콘 + 다줄 muted 텍스트" 자리 → 벤더 `Hint`로 확정** — assembly-test의 inlineHint 수제 조립이 flex align/margin-top 보정으로도 반복 어긋남. 벤더 신설 결정, 이름 `Hint`. Layer 2 스펙은 [[components/hint]], Layer 1 코드는 유저 작성. 안내 자리 3층 구분 확립:
90
+ - 흐름 속 각주 (톤·액션 없음) = Hint
91
+ - 액션 유도 안내 블록 = Callout
92
+ - 페이지 폭 상태 바 = page-banner 레시피
93
+ - **벤더 신설이 레시피보다 나은 자리 식별 — 반복 함정 임계점** — 레시피 수정 2회 이상 반복해도 특정 조립 실패가 재발하면 "레시피로 담기 어려운 자리". 벤더로 승격 신호. "line-box 안 아이콘 정렬"처럼 **구현 디테일이 결과를 좌우**하는 자리는 특히.
94
+ - **card-row 레시피 폐기 — "리스트 자리는 벤더 필수" 룰 PRINCIPLES에 승격** — 유저 지적: 레시피에서 수제 조립 가이드를 올린 것 자체가 메모리 [[feedback-no-hand-rolled-components]]를 레시피가 어긴 셈. 메모리 수준이 아니라 **PRINCIPLES §5a HARD로 승격**해 "리스트는 무조건 Table/ListItem/EventListItem/MenuItem" 못 박음. "카드처럼 보이는 리스트"가 필요하면 레시피/수제 조립 금지, ListItem 벤더에 새 variant 신설. ASSEMBLY §1 1단계와 체크리스트에도 반영.
95
+
96
+ 이 섹션은 **살아있는 체인지로그** — 새 결함이 나오면 추가하고 레시피를 수정한다.
97
+
98
+ ## 관련
99
+
100
+ - [[ASSEMBLY]] — 조립 알고리즘. 레시피는 그 3단계에서 참조된다
101
+ - [[GUIDE]] — 공통 골격·N/A 규칙. 레시피 파일도 이를 따른다
102
+ - [[components/_variants]] — 역할 × variant 매핑. 레시피 내 vendor 선택 시 참조
@@ -0,0 +1,58 @@
1
+ # ~~Card Row~~ (폐기 — ListItem 벤더로 대체)
2
+
3
+ > ⚠ **이 레시피는 폐기됨.** 반복 리스트 자리는 **반드시 벤더 컴포넌트**를 쓴다 ([[PRINCIPLES]] §5a HARD). 레시피 조립 금지.
4
+
5
+ ## 왜 폐기됐나
6
+
7
+ 이 레시피는 "border + bg + radius 있는 카드형 row"를 벤더 variant로 커버 못한다는 전제로 작성됐지만, 실제로는 **설계 판단 자체가 틀렸음**:
8
+
9
+ 1. [[components/list-item]]은 **chrome 없음이 설계 공식** — 블록 신호가 필요하면 Callout으로 승격이 정답
10
+ 2. 수제 조립 레시피는 메모리 [[feedback-no-hand-rolled-components]]와 PRINCIPLES §5a를 직접 위반
11
+ 3. "카드처럼 보이는 리스트"가 꼭 필요하면 → ListItem 벤더에 `surface="card"` 등 **새 variant를 신설** 후 사용
12
+
13
+ ## 지금 쓸 자리는 어디?
14
+
15
+ 반복 아이템 N개를 수직 나열하는 자리는 전부 아래 중 하나:
16
+
17
+ | 자리 성격 | 쓸 벤더 |
18
+ |---|---|
19
+ | **표 역할** (열 공통 축이 있는 격자) | [[components/table-row]] + [[components/table-header]] |
20
+ | **단건 반복** (알림·활동·작업·자원 목록) | [[components/list-item]] |
21
+ | **날짜 중심 이벤트** | [[components/event-list-item]] |
22
+ | **메뉴 아이템** (팝오버·사이드바) | [[components/menu-item]] / [[components/sidebar-item]] |
23
+
24
+ ## ListItem 조립 예시 (card-row가 받으려던 자리)
25
+
26
+ 작업 리스트 (상태 Badge + chevron):
27
+
28
+ ```tsx
29
+ <ListItem
30
+ href={item.href}
31
+ align="top"
32
+ leading={<Icon name="list-dashes" />}
33
+ trailing={
34
+ <>
35
+ <Badge variant="soft" status={item.status.kind}>{item.status.label}</Badge>
36
+ <IconButton size="sm" variant="ghost" icon={<Icon name="caret-right" />} aria-label="열기" />
37
+ </>
38
+ }
39
+ >
40
+ <ListItem.Title>{item.title}</ListItem.Title>
41
+ <ListItem.Meta>
42
+ <time>요청 {item.requestedAt}</time>
43
+ {item.note && <>
44
+ <ListItem.MetaDivider />
45
+ <span>{item.note}</span>
46
+ </>}
47
+ </ListItem.Meta>
48
+ </ListItem>
49
+ ```
50
+
51
+ - `size`·`align`·`leading`·`trailing` 선택은 [[components/list-item]] "결정 가이드" 참고
52
+ - chrome(border/bg)이 필요하면 ListItem 설계 재검토 — 블록 신호면 Callout, "카드형 리스트"면 ListItem 벤더에 새 variant 신설
53
+
54
+ ## 관련
55
+
56
+ - [[PRINCIPLES]] §5a — 리스트 자리 수제 조립 영구 금지 (HARD)
57
+ - [[components/list-item]] — 범용 한 줄 anatomy
58
+ - [[ASSEMBLY]] §1 — 1단계에서 리스트 자리는 반드시 벤더로 확정
@@ -0,0 +1,76 @@
1
+ # Form Footer
2
+
3
+ ### 원칙
4
+ 폼·모달·drawer 하단의 **액션 묶음** 자리. 주 액션(primary)과 보조 액션(outline)이 **반드시 쌍으로** 등장하는 자리라 레시피로 못 박는다. 유저·AI가 자주 틀리는 지점: variant 선택, 좌/우 정렬, 액션 순서.
5
+
6
+ ### Anatomy
7
+ 한 줄 row, 액션은 **오른쪽 정렬**이 기본.
8
+
9
+ ```
10
+ [.............................spacer............................] [secondary] [primary]
11
+ ```
12
+
13
+ - spacer: `flex: 1 1 auto` (액션들을 오른쪽으로 밀어냄)
14
+ - secondary (취소·닫기): [[components/button]] `variant="outline"`
15
+ - primary (저장·제출·확인): [[components/button]] `variant="primary"`
16
+ - 액션 간 gap: `space-1` 고정 (버튼↔버튼 묶음, [[tokens/spacing]])
17
+ - footer 자체 상단: `space-4` 또는 `space-6` 공간 (본문과의 분리) — 선 없이 공간으로
18
+
19
+ ### Do
20
+ - 액션 순서: **왼쪽 secondary → 오른쪽 primary** ([[components/_variants]] "중요=primary, 서브=outline")
21
+ - destructive 액션이 primary 자리로 올 때: `variant="destructive"` 사용. destructive가 두 번째 시선이면 `destructive-ghost`
22
+ - 모달의 경우 primary는 항상 하나 — ESC/닫기 외에 두 개 이상의 "주 액션"이 있으면 모달 설계 재검토
23
+ - footer 상단에 **선이 필요하면** `border-top: 1px solid var(--border)`만, 배경 변화 없이 — [[PRINCIPLES]] §5b
24
+ - 좌측에 **보조 정보**(예: "변경사항 저장 안 됨")가 필요하면 spacer 자리에 muted 텍스트 1줄. 더는 금지
25
+
26
+ ### Don't
27
+ - [HARD] primary 2개 금지 — [[PRINCIPLES]] §4a HARD 예시. 주 액션은 한 화면에 하나
28
+ - [HARD] 액션을 세로로 쌓지 않는다 (모바일에서도 가로 유지가 기본). 세로가 필요하면 drawer/sheet가 맞는지 재검토
29
+ - [HARD] secondary로 `variant="secondary"`(레거시) 쓰지 않는다 — `outline`을 쓴다 ([[components/_variants]] 액션 선택 규칙)
30
+ - [HARD] 액션 사이 gap을 `space-2` 이상으로 벌리지 않는다 — 버튼 묶음은 `space-1` 고정 ([[tokens/spacing]])
31
+ - [SOFT] 왼쪽 정렬·중앙 정렬 금지 — 우측 정렬이 디폴트. 좌측 정렬은 오직 "폼 흐름이 좌에서 우로 흐르지 않는" 특수 자리에서만
32
+
33
+ ### Fallback
34
+ primary 하나만 있으면 그 단독으로 (여전히 오른쪽 정렬). destructive가 primary 자리면 secondary도 반드시 함께 — 확인 가능한 escape를 제공.
35
+
36
+ ### 조립 예시
37
+ ```tsx
38
+ <footer className="form-footer">
39
+ <Button variant="outline" size="md" onClick={onCancel}>
40
+ 취소
41
+ </Button>
42
+ <Button variant="primary" size="md" type="submit">
43
+ 저장
44
+ </Button>
45
+ </footer>
46
+ ```
47
+
48
+ ```css
49
+ .form-footer {
50
+ display: flex;
51
+ justify-content: flex-end;
52
+ align-items: center;
53
+ gap: var(--space-1);
54
+ margin-top: var(--space-6);
55
+ }
56
+ /* 좌측 보조 정보가 필요한 경우 */
57
+ .form-footer .status {
58
+ margin-right: auto;
59
+ font-size: var(--text-sm);
60
+ color: var(--fg-muted);
61
+ }
62
+ ```
63
+
64
+ destructive 예시:
65
+ ```tsx
66
+ <footer className="form-footer">
67
+ <Button variant="outline" size="md">취소</Button>
68
+ <Button variant="destructive" size="md">삭제</Button>
69
+ </footer>
70
+ ```
71
+
72
+ ### 관련
73
+ - [[components/button]] — variant 선택
74
+ - [[components/_variants]] — 역할 × variant 매핑
75
+ - [[components/modal]] — footer 자리의 vendor host
76
+ - [[tokens/spacing]] — space-1 버튼 묶음 고정
@@ -0,0 +1,61 @@
1
+ # Meta Line
2
+
3
+ ### 원칙
4
+ 한 줄 안에 **여러 메타 라벨**(요청 시각, 작성자, 상태 주석, 카테고리 등)을 가로로 나열하는 자리. 카드 서브텍스트, 리스트 row의 두번째 줄, 테이블 셀 아래 보조 라인에 반복 등장. 구분자가 틀리면 UI가 즉시 싸구려로 뜬다.
5
+
6
+ ### Anatomy
7
+ ```
8
+ [label1] [separator] [label2] [separator] [label3]
9
+ ```
10
+
11
+ - label은 모두 같은 톤·크기 (한 줄 안에서 위계 X)
12
+ - separator는 **짧고 연한 세로 hairline** 1개만 — 구간 사이에 들어감 (처음/끝 바깥 X)
13
+ - label과 separator 사이: `space-2` (양쪽 균등)
14
+
15
+ ### Do
16
+ - 각 label: `text-xs` 또는 `text-sm`, `color: var(--fg-muted)`
17
+ - separator는 `<span>` + `border-left: 1px solid var(--border)` + `align-self: stretch` — 텍스트 하이트에 자동 맞춤
18
+ - separator는 `aria-hidden="true"` — 스크린리더에서 읽히지 않게
19
+ - label 중 하나가 **값**(사람 이름·시각)이고 나머지가 **고정 라벨**이면, 값만 `color: var(--fg)` (살짝 올림) — 단 톤 차이는 1단계 이내
20
+ - 전체 wrapper: `display: flex; align-items: center; gap: var(--space-2);`
21
+ - 날짜·시간은 [[feedback-date-writing]] 전략 중 하나로 (상대어 "12분 전" / "10월 13일") — 숫자 패턴("10. 13") 금지
22
+
23
+ ### Don't
24
+ - [HARD] **가운뎃점(`·` `•` `∙`)·점(`.`)·pipe(`|`)·슬래시(`/`)** 등 **문자 구분자 금지** — 메모리 [no-bullet-separator]. hairline으로만 분리
25
+ - [HARD] separator를 label 앞뒤에도 넣지 않는다. **"사이에만"**. 끝에 또는 처음에 두면 끊긴 느낌
26
+ - [HARD] label 하나하나를 Badge로 쓰지 않는다 — 메타 라인은 톤이 조용해야 한다. Badge는 상태·카테고리 **표시**가 필요할 때만, 메타 라인에 섞이지 않게 분리
27
+ - [SOFT] 3개 넘으면 메타 라인이 아니라 **별도 블록**이 맞는지 재검토. 4개 이상은 사용자가 다 못 읽음
28
+
29
+ ### Fallback
30
+ 항목이 1개뿐이면 separator 없음. 2개 이상부터 hairline 사용.
31
+
32
+ ### 조립 예시
33
+ ```tsx
34
+ <div className="meta-line">
35
+ <span>요청 10월 13일 오후 10시 25분</span>
36
+ <span className="sep" aria-hidden="true" />
37
+ <span>근거 변경 확인 필요</span>
38
+ </div>
39
+ ```
40
+
41
+ ```css
42
+ .meta-line {
43
+ display: flex;
44
+ align-items: center;
45
+ gap: var(--space-2);
46
+ font-size: var(--text-sm);
47
+ color: var(--fg-muted);
48
+ }
49
+ /* separator는 border-left로 — width/height 리터럴 금지 (check_tokens hook) */
50
+ .meta-line .sep {
51
+ border-left: 1px solid var(--border);
52
+ align-self: stretch;
53
+ margin: var(--space-0\.5) 0;
54
+ flex: 0 0 auto;
55
+ }
56
+ ```
57
+
58
+ ### 관련
59
+ - [[components/badge]] — 상태·카테고리 표시가 필요하면 (메타 라인 바깥에 분리)
60
+ - [[tokens/color]] — fg-muted, border
61
+ - [[tokens/typography]] — text-sm/text-xs
@@ -0,0 +1,121 @@
1
+ # Page Banner
2
+
3
+ ### 원칙
4
+ 페이지 폭에 걸치는 **안내 바**. 공유 상태·만료 안내·시스템 공지·권한 경고 등 "이 페이지 전체에 적용되는 메타 상태"를 알린다. Callout과 다름 — Callout은 사이드바·리스트 끝의 **국소** 블록, Banner는 페이지 **상단/하단**의 전면 바.
5
+
6
+ 관찰: 유저 페이지에서 ①상단 "공유 열람 10.20 만료" ②중간 "`</>` 자동 처리 안내" ③하단 "열람 권한 접속" 셋 다 Banner 성격인데 서로 다른 스타일로 조립됐다 — 이 레시피가 그 셋을 하나의 리듬으로 묶는다.
7
+
8
+ ### Anatomy
9
+ 한 줄 row.
10
+
11
+ ```
12
+ [leading icon] [text (1~2줄)] [...spacer...] [action/dismiss]
13
+ ```
14
+
15
+ - leading icon: 20px, [[components/icon]]
16
+ - text: `flex: 1 1 auto`, 1~2줄, `line-height: 1.4`
17
+ - action: [[components/button]] `variant="ghost"` 또는 간단한 link. **banner에서 primary 금지** — primary 액션이 필요하면 banner가 아니라 modal/dialog
18
+ - dismiss: [[components/icon-button]] `ghost` + close 아이콘 (옵션)
19
+
20
+ **padding:**
21
+ - 단일라인 (title만, 또는 1줄 메시지): 상하 `space-2`, 좌우 `space-3`
22
+ - 멀티라인 (title + sub, 또는 메타 라인 포함): 상하 `space-3`, 좌우 `space-4`
23
+ - 하단 조용한 안내: `data-size="sm"`로 다운스케일 — 상하 `space-2`, 좌우 `space-3`
24
+
25
+ **radius:** `var(--radius-md)` (단, 페이지 full-bleed로 쓰면 radius 0).
26
+
27
+ **gap:**
28
+ - leading↔text: 단일라인 `space-2`, 멀티라인 `space-3`
29
+ - text↔action: `space-3`
30
+ - banner.text 내부 title↔sub: `space-1` (space-0.5는 너무 타이트 — 두 줄이 녹아보임)
31
+
32
+ **leading icon 수직 정렬:** 멀티라인일 때 `align-self: flex-start` + `margin-top: space-0.5` — 센터 정렬이면 title보다 아이콘이 내려가 보임.
33
+
34
+ ### Tone 체계
35
+ Banner는 톤 3종만 지원 — Callout과 통일.
36
+
37
+ | Tone | 자리 | 색 조합 |
38
+ |---|---|---|
39
+ | `info` | 상태 공지 (만료, 공유 모드) | `background: var(--bg-subtle)` + border 없음 |
40
+ | `warning` | 주의 (권한 제한, 임시 상태) | `background: var(--bg-warning-subtle)` + border 없음 |
41
+ | `critical` | 위험 (만료 임박, 오류) | `background: var(--bg-critical-subtle)` + border 없음 |
42
+
43
+ ### Do
44
+ - leading icon은 tone에 맞춤 — info=`info`/`share-network`, warning=`warning-circle`, critical=`warning`. **아이콘 이름은 `web/components/Icon/Icon.tsx` 레지스트리에서 확인**
45
+ - 텍스트는 **1문장 명제 + (옵션) 부가 1문장** — 길어지면 banner가 아니라 Callout이나 dedicated page
46
+ - 액션 라벨은 **동사**로 짧게 ("공유 열람 종료", "자세히")
47
+ - 상단 banner면 Page Header 위에 둠. 블록 아래 gap: `space-4`
48
+ - 하단 banner면 섹션들 다 끝난 뒤, `space-6` 간격
49
+
50
+ ### Don't
51
+ - [HARD] **배경 + 테두리 동시 사용 금지** ([[PRINCIPLES]] §5b). Banner는 **배경 톤만**, border는 transparent
52
+ - [HARD] primary 버튼 금지 — banner는 "알림" 자리, 주 액션은 다른 곳
53
+ - [HARD] banner 안에 chip·Badge 섞지 않는다 — 톤이 겹쳐 시끄러워짐. 상태 분류가 필요하면 Banner tone을 바꾼다
54
+ - [HARD] 아이콘 인라인 `<svg>` 금지 — [[components/icon]] 경유
55
+ - [SOFT] banner 3개 이상 동시에 쌓지 않는다. 2개 이상이 쌓이면 "뭐가 중요한지" 판독 불가. 정보가 많으면 하나의 banner + "자세히" 액션으로
56
+
57
+ ### Fallback
58
+ - action 없으면 그 자리 생략, text가 끝까지 참
59
+ - dismiss만 있고 action 없으면 dismiss는 trailing 우측 끝
60
+
61
+ ### 조립 예시
62
+ ```tsx
63
+ {/* info: 공유 열람 상태 */}
64
+ <aside className="page-banner" data-tone="info">
65
+ <Icon name="share-network" size={20} className="leading" />
66
+ <div className="text">
67
+ <strong>Kailey Kim님 프로젝트 공유 열람</strong>
68
+ <span className="sub">
69
+ 조회 전용
70
+ <span className="sep" aria-hidden="true" />
71
+ 10월 20일 오후 9시 53분 만료
72
+ <span className="sep" aria-hidden="true" />
73
+ Kailey 프로젝트 전체 공유
74
+ </span>
75
+ </div>
76
+ <Button variant="ghost" size="sm">공유 열람 종료</Button>
77
+ </aside>
78
+ ```
79
+
80
+ ```css
81
+ /* 테두리 선언 없이 배경만 — bg+stroke 조합 금지 ([[PRINCIPLES]] §5b) */
82
+ .page-banner {
83
+ display: flex;
84
+ align-items: center;
85
+ gap: var(--space-3);
86
+ padding: var(--space-3) var(--space-4);
87
+ border-radius: var(--radius-md);
88
+ }
89
+ .page-banner[data-size="sm"] {
90
+ padding: var(--space-2) var(--space-3);
91
+ gap: var(--space-2);
92
+ }
93
+ .page-banner[data-tone="info"] { background: var(--bg-subtle); }
94
+ .page-banner[data-tone="warning"] { background: var(--bg-warning-subtle); }
95
+ .page-banner[data-tone="critical"] { background: var(--bg-critical-subtle); }
96
+
97
+ .page-banner .leading {
98
+ color: var(--fg-muted);
99
+ flex: 0 0 auto;
100
+ align-self: flex-start;
101
+ margin-top: var(--space-0\.5);
102
+ }
103
+ .page-banner .text {
104
+ flex: 1 1 auto;
105
+ display: flex;
106
+ flex-direction: column;
107
+ gap: var(--space-1);
108
+ line-height: 1.4;
109
+ }
110
+ .page-banner .text .sub {
111
+ font-size: var(--text-sm);
112
+ color: var(--fg-muted);
113
+ }
114
+ ```
115
+
116
+ ### 관련
117
+ - [[components/callout]] — 국소 안내 블록 (사이드바·리스트 끝). Banner와 자리가 다름
118
+ - [[components/button]] — ghost 액션
119
+ - [[components/icon-button]] — dismiss
120
+ - [[tokens/color]] — bg-subtle, bg-warning-subtle, bg-critical-subtle
121
+ - [[PRINCIPLES]] §5b — 배경 또는 테두리
@@ -0,0 +1,93 @@
1
+ # Page Header
2
+
3
+ ### 원칙
4
+ 페이지 바디 최상단에 쓰는 **"이 페이지가 뭘 하는 곳인가"** 블록. Topbar가 shell chrome(ID·전역 action)을 담는다면, Page Header는 **콘텐츠 레이어의 첫 블록**이다. [[components/topbar]]가 명시한 "큰 타이틀은 body 첫 블록 자리"를 받는 자리.
5
+
6
+ ### Anatomy
7
+ 3개 요소가 수직으로 쌓인다. (옵션 요소는 생략 가능)
8
+
9
+ ```
10
+ [eyebrow] ← 옵션: 상위 맥락 (breadcrumb·카테고리) — xs/muted
11
+ [h1 title] [actions] ← 필수: 큰 타이틀 + 우측 액션 (옵션)
12
+ [description] ← 옵션: 1~2줄 서브텍스트, muted
13
+ ```
14
+
15
+ - eyebrow와 title 사이: `space-1`
16
+ - title과 description 사이: `space-2`
17
+ - 블록 전체 아래 (다음 섹션까지): `space-6` (앱 리듬) / `space-10` (랜딩 리듬)
18
+ - actions는 title과 같은 row — `align-items: center`, `flex: 1 1 auto` title / `flex: 0 0 auto` actions
19
+
20
+ ### Do
21
+ - h1: `text-4xl` (랜딩) / `text-3xl` (앱), **`font-medium` 또는 그 이하** ([[tokens/typography]] font weight cap)
22
+ - h1에 **반드시 `line-height: var(--lh-tight)`** 명시 ([[tokens/spacing]] "텍스트 gap·line-height")
23
+ - description: `text-base` / `color: var(--fg-muted)` / `max-width: 60ch`
24
+ - actions는 [[components/_variants]] 액션 매핑에 따름 — 중요=`primary`, 서브=`outline`
25
+ - 액션 2개 이상이면 서로 간 gap은 `space-1` 고정 ([[tokens/spacing]])
26
+
27
+ ### Don't
28
+ - [HARD] h1 아래 `margin-bottom`을 자식(h1)이 걸고 **동시에** 부모 wrapper도 gap을 걸지 않는다 — 이중 여백 ([[tokens/spacing]] Don't)
29
+ - [HARD] h1을 `font-semibold`/`font-bold`로 쓰지 않는다 — [[tokens/typography]] font weight cap
30
+ - [HARD] uppercase + letter-spacing 조합 금지 — 메모리 [no-uppercase]
31
+ - [SOFT] eyebrow 없이 breadcrumb만 Topbar leading에 두는 패턴은 피한다 — 페이지 식별이 shell로 샌다. Topbar leading에는 workspace/section만, 페이지 이름은 body로
32
+
33
+ ### Fallback
34
+ 서브텍스트가 애매하면 생략. 설명이 1줄 미만이면 쓰지 않는다. actions도 없으면 h1 단독으로.
35
+
36
+ ### 조립 예시
37
+ ```tsx
38
+ <header className="page-header">
39
+ {/* eyebrow 옵션 */}
40
+ <div className="eyebrow">프로젝트</div>
41
+ <div className="title-row">
42
+ <h1>개발 계획·실행</h1>
43
+ <div className="actions">
44
+ <Button variant="outline" size="md">가져오기</Button>
45
+ <Button variant="primary" size="md">새 계획</Button>
46
+ </div>
47
+ </div>
48
+ <p className="description">
49
+ 확정본을 선택해 개발 계획을 요청하고, 계획과 작업 범위를 승인한 뒤 실행하세요.
50
+ </p>
51
+ </header>
52
+ ```
53
+
54
+ ```css
55
+ .page-header {
56
+ display: flex;
57
+ flex-direction: column;
58
+ gap: var(--space-1); /* eyebrow→title */
59
+ margin-bottom: var(--space-6);
60
+ }
61
+ .page-header .title-row {
62
+ display: flex;
63
+ align-items: center;
64
+ gap: var(--space-3);
65
+ }
66
+ .page-header h1 {
67
+ flex: 1 1 auto;
68
+ font-size: var(--text-3xl);
69
+ font-weight: var(--font-weight-medium);
70
+ line-height: var(--lh-tight);
71
+ }
72
+ .page-header .actions {
73
+ display: flex;
74
+ gap: var(--space-1);
75
+ flex: 0 0 auto;
76
+ }
77
+ .page-header .description {
78
+ color: var(--fg-muted);
79
+ margin-top: var(--space-1); /* title→description은 더 가깝게 */
80
+ max-width: 60ch;
81
+ }
82
+ .page-header .eyebrow {
83
+ font-size: var(--text-xs);
84
+ color: var(--fg-muted);
85
+ }
86
+ ```
87
+
88
+ ### 관련
89
+ - [[recipes/section-header]] — 하위 섹션의 유사 패턴
90
+ - [[components/topbar]] — shell 쪽. "큰 타이틀은 body 첫 블록 자리" 지침의 수신처
91
+ - [[components/breadcrumb]] — eyebrow 자리에 쓸 수 있는 vendor
92
+ - [[tokens/spacing]] — 블록 아래 리듬 선택
93
+ - [[tokens/typography]] — font weight cap, lh-tight
@@ -0,0 +1,90 @@
1
+ # Section Header
2
+
3
+ ### 원칙
4
+ 페이지 바디 안의 **섹션 경계**를 알리는 중간 블록. Page Header보다 작고 조용하다. 섹션이 반복될 때 **같은 리듬·같은 위계**로 등장해야 페이지가 흩어지지 않는다.
5
+
6
+ ### Anatomy
7
+ 한 줄 row + (옵션) 설명.
8
+
9
+ ```
10
+ [h2 title] [count] [actions] ← 필수: h2, 옵션: count badge / 우측 액션
11
+ [description] ← 옵션: 1줄 설명 (muted)
12
+ ```
13
+
14
+ - title과 count: `space-2` (한 줄 안)
15
+ - title row와 description: `space-1`
16
+ - 섹션 header와 body(리스트/카드 등) 사이: `space-3`. **body 내부 아이템끼리 gap(space-1)보다 반드시 한 단계 더 넓어야** 위계 성립. 둘이 같으면 "타이틀과 아이템이 같은 레벨"로 뭉쳐 보임
17
+ - 섹션끼리는 **반복되는 작업 단위**면 `space-20`(80px), 섹션 내부 블록 묶음이면 `space-6` ([[ASSEMBLY]] §2a 갭 가이드)
18
+
19
+ ### Do
20
+ - h2: **`text-lg` 기본**, 랜딩 리듬이면 `text-xl` 까지. **`font-medium`** ([[tokens/typography]] "섹션 h2/h3는 semibold 금지"). text-xl 이상은 "크고 얇은" 함정에 빠지기 쉬움 — 콘솔 리듬에서는 피함
21
+ - count badge: [[components/badge]] `soft` variant, title 바로 오른쪽
22
+ - actions는 title row 끝에 — `flex: 0 0 auto`
23
+ - actions 중 가장 흔한 "새로고침·더보기"는 [[components/icon-button]] `ghost` 또는 `outline`
24
+ - 액션이 2개 이상이면 서로 간 gap `space-1`
25
+
26
+ ### Don't
27
+ - [HARD] `<section>` 태그를 쓰지 않는다 — AppShell의 `.content section`이 `padding-top: space-20`(80px)을 꽂아 "섹션 머리에 거대한 여백" 함정 발생. `<div>`로 짠다 ([[feedback-no-semantic-tags-in-components]])
28
+ - [HARD] h2를 `font-semibold`/`font-bold`로 쓰지 않는다 — [[tokens/typography]] font weight cap
29
+ - [HARD] h2 아래에 `border-bottom`을 깔지 않는다. 섹션 분리는 **gap**이 담당. 선 + 공간은 이중 분리
30
+ - [HARD] 섹션끼리 `space-6` 이상 gap으로 분리하지 않는다 — 각 섹션이 border/배경으로 분리돼 있으면 과한 공간은 "구조가 끊긴" 결과 ([[tokens/spacing]] Don't)
31
+ - [SOFT] title row에 액션이 **3개 이상**이면 Toolbar를 쓴다. 액션이 많으면 섹션이 아니라 toolbar가 맞음
32
+
33
+ ### Fallback
34
+ 설명이 애매하면 생략. actions도 없으면 h2 단독으로. count badge는 숫자가 **의미 있을 때만**(총 몇 개인지 알아야 하는 리스트 등) — 장식용 금지.
35
+
36
+ ### 조립 예시
37
+ ```tsx
38
+ <div>
39
+ <header className="section-header">
40
+ <div className="title-row">
41
+ <h2>작업 계획</h2>
42
+ <Badge variant="soft">4</Badge>
43
+ <div className="actions">
44
+ <IconButton icon={<Icon name="arrow-clockwise" />} variant="ghost" aria-label="새로고침" />
45
+ </div>
46
+ </div>
47
+ <p className="description">
48
+ 요청한 계획을 검토하고 승인하세요.
49
+ </p>
50
+ </header>
51
+ <div className="section-body">
52
+ {/* 섹션 바디 */}
53
+ </div>
54
+ </div>
55
+ ```
56
+
57
+ ```css
58
+ .section-header {
59
+ display: flex;
60
+ flex-direction: column;
61
+ gap: var(--space-1);
62
+ margin-bottom: var(--space-3);
63
+ }
64
+ .section-header .title-row {
65
+ display: flex;
66
+ align-items: center;
67
+ gap: var(--space-2);
68
+ }
69
+ .section-header h2 {
70
+ font-size: var(--text-lg);
71
+ font-weight: var(--font-weight-medium);
72
+ line-height: var(--lh-tight);
73
+ }
74
+ .section-header .actions {
75
+ display: flex;
76
+ gap: var(--space-1);
77
+ margin-left: auto;
78
+ }
79
+ .section-header .description {
80
+ color: var(--fg-muted);
81
+ font-size: var(--text-sm);
82
+ }
83
+ ```
84
+
85
+ ### 관련
86
+ - [[recipes/page-header]] — 상위 블록. 위계상 h1 → h2
87
+ - [[components/badge]] — count badge
88
+ - [[components/icon-button]] — 섹션 액션
89
+ - [[tokens/spacing]] — 섹션 간 gap
90
+ - [[tokens/typography]] — font weight cap
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conscience-technology/ctds",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "CTDS — 하이브리드 디자인 시스템 설치 · 온보딩 CLI (shadcn식 코드 생성기)",
5
5
  "type": "module",
6
6
  "sideEffects": [