@conscience-technology/ctds 0.1.13 → 0.2.0

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.
Files changed (72) hide show
  1. package/assets/components/AppFrame/AppFrame.module.css +6 -0
  2. package/assets/components/AttachmentItem/AttachmentItem.module.css +112 -0
  3. package/assets/components/AttachmentItem/AttachmentItem.tsx +164 -0
  4. package/assets/components/AttachmentItem/index.ts +6 -0
  5. package/assets/components/Button/Button.module.css +11 -2
  6. package/assets/components/Button/Button.tsx +5 -5
  7. package/assets/components/ChatInput/ChatInput.tsx +2 -2
  8. package/assets/components/DatePicker/Calendar.tsx +19 -1
  9. package/assets/components/Drawer/Drawer.tsx +120 -39
  10. package/assets/components/Drawer/index.ts +9 -1
  11. package/assets/components/GeneratedImage/GeneratedImage.module.css +90 -0
  12. package/assets/components/GeneratedImage/GeneratedImage.tsx +70 -0
  13. package/assets/components/GeneratedImage/index.ts +5 -0
  14. package/assets/components/Icon/Icon.tsx +4 -1
  15. package/assets/components/Input/Input.module.css +3 -0
  16. package/assets/components/MessageState/MessageState.module.css +12 -6
  17. package/assets/components/MessageState/MessageState.tsx +23 -5
  18. package/assets/components/Modal/Modal.tsx +114 -33
  19. package/assets/components/Modal/index.ts +12 -2
  20. package/assets/components/Spinner/Spinner.module.css +1 -1
  21. package/assets/components/Spinner/Spinner.tsx +2 -2
  22. package/assets/components/Switch/Switch.tsx +6 -0
  23. package/assets/components/TableHeader/TableHeader.module.css +29 -20
  24. package/assets/components/TableRow/TableRow.module.css +7 -5
  25. package/assets/components/TableRowEdit/TableRowEdit.module.css +10 -3
  26. package/assets/components/TableRowEdit/TableRowEdit.tsx +7 -2
  27. package/assets/components/Toast/Toast.tsx +19 -0
  28. package/assets/components/_data.ts +39 -14
  29. package/assets/components/app-shell.css +17 -0
  30. package/assets/components/index.ts +77 -0
  31. package/assets/components/reset.css +63 -0
  32. package/assets/components/themeBootScript.ts +40 -0
  33. package/assets/components/tokens.css +70 -60
  34. package/assets/prompts/GUIDE.md +1 -1
  35. package/assets/prompts/PRINCIPLES.md +9 -0
  36. package/assets/prompts/axes/cascade.md +49 -0
  37. package/assets/prompts/axes/mode.md +3 -1
  38. package/assets/prompts/components/_variants.md +84 -0
  39. package/assets/prompts/components/attachment-item.md +80 -0
  40. package/assets/prompts/components/attachment-thumb.md +2 -2
  41. package/assets/prompts/components/avatar-upload.md +53 -0
  42. package/assets/prompts/components/bubble.md +2 -2
  43. package/assets/prompts/components/chat-input.md +7 -8
  44. package/assets/prompts/components/code-block.md +2 -2
  45. package/assets/prompts/components/disclosure.md +47 -0
  46. package/assets/prompts/components/file-icon.md +3 -3
  47. package/assets/prompts/components/generated-image.md +38 -0
  48. package/assets/prompts/components/message-quote.md +2 -2
  49. package/assets/prompts/components/message-sources.md +5 -4
  50. package/assets/prompts/components/message-state.md +15 -12
  51. package/assets/prompts/components/message-table.md +9 -6
  52. package/assets/prompts/components/onboarding.md +43 -0
  53. package/assets/prompts/components/quotation-bar.md +1 -1
  54. package/assets/prompts/components/sidebar-split.md +70 -0
  55. package/assets/prompts/components/sidebar.md +2 -0
  56. package/assets/prompts/components/spinner.md +4 -4
  57. package/assets/prompts/components/time-picker.md +69 -0
  58. package/assets/prompts/components/toolbar.md +69 -0
  59. package/assets/prompts/components/user-message.md +2 -2
  60. package/package.json +80 -3
  61. package/src/commands/init.js +53 -23
  62. package/src/utils/deps.js +23 -17
  63. package/src/utils/patch.js +155 -0
  64. package/assets/components/AttachmentChip/AttachmentChip.module.css +0 -73
  65. package/assets/components/AttachmentChip/AttachmentChip.tsx +0 -74
  66. package/assets/components/AttachmentChip/index.ts +0 -2
  67. package/assets/components/Calendar/Calendar.module.css +0 -163
  68. package/assets/components/Calendar/Calendar.tsx +0 -323
  69. package/assets/components/Calendar/index.ts +0 -2
  70. package/assets/prompts/components/attachment-chip.md +0 -46
  71. package/assets/prompts/components/calendar.md +0 -116
  72. package/assets/prompts/components/form-field.md +0 -74
@@ -0,0 +1,49 @@
1
+ # Cascade
2
+
3
+ ### 원칙
4
+ 축은 `html` 또는 중간 컨테이너에 `data-*` 어트리뷰트로 선언되고, CSS 변수가
5
+ 서브트리로 상속되는 방식으로 동작한다. 축 하나(예: Mode)만 뒤집으면 다른 축
6
+ (Palette·Shape·Density·Motion·Icon·Lang·Focus·Voice)은 **부모 값을 그대로 상속**한다.
7
+
8
+ ### Do
9
+ - 국소 스코프 바꾸기는 `data-mode="dark"` 처럼 **바꾸는 축 하나만** 자식 컨테이너에 선언한다. 나머지는 자동 상속.
10
+ - 자식 영역의 축 변경이 손주까지 전파되길 원할 때는 **아무 것도 안 한다** — CSS 변수 상속이 공짜로 처리한다.
11
+ - 변경한 축에 영향받는 토큰 셋만 바뀐다는 사실을 명시적으로 활용 (예: 다크 카드 안에서도 Shape는 그대로).
12
+ - 디버깅할 땐 DevTools에서 `html` 또는 중간 컨테이너의 `data-*` 값을 눈으로 확인하는 걸 1순위로.
13
+
14
+ ### Don't
15
+ - 자식 컨테이너에 쓸모없는 전체 축 셋을 다시 선언하지 않는다. 예: `<div data-mode="dark" data-palette="standard" data-shape="standard" ...>` → `<div data-mode="dark">` 로 충분.
16
+ - 자식 컨테이너에서 CSS 변수를 **직접** 재선언하지 않는다 (예: `style={{ "--bg": "..." }}`). 토큰 whitelist 바깥의 값을 섞으면 축 스왑이 깨짐. 축으로 못 표현하는 색은 축에 값을 추가하거나 테마 프리셋 레벨에서 해결.
17
+ - 축 중 하나가 "값이 없을 때 자동 폴백" 되지 않는다고 가정하지 않는다 — 모든 축은 `:root`에 기본값이 선언돼 있고 자식은 명시적으로 override 할 때만 바뀜.
18
+
19
+ ### 선언이 두 번이면 가까운 쪽이 이긴다
20
+ ```tsx
21
+ <html data-mode="light"> {/* L1 */}
22
+ <main data-mode="dark"> {/* L2: light 뒤집음 */}
23
+ <aside data-mode="light"> {/* L3: 다시 light */}
24
+ {/* 여기서 --bg, --fg는 L3 light. --shape는 L1 standard 그대로. */}
25
+ </aside>
26
+ </main>
27
+ </html>
28
+ ```
29
+
30
+ ### 축별 "국소 스코프" 적용 가능성
31
+ | 축 | 국소 override | 대표 쓰임 |
32
+ |---|---|---|
33
+ | Mode | ✅ 흔함 | 이미지 오버레이, 어두운 카드, 다크 툴바 |
34
+ | Palette | ⚠️ 드물게 | 브랜드 섹션에 vivid 강조 (주의: 전체 톤 깨짐) |
35
+ | Shape | ⚠️ 드물게 | 이벤트 배너에만 soft 둥글기 |
36
+ | Density | ✅ 자주 | 대시보드 안 compact 테이블 vs 외곽 default 패널 |
37
+ | Motion | ⚠️ 거의 안 함 | 접근성용 `none` 국소 적용 외엔 금지 |
38
+ | Icon | ⚠️ 거의 안 함 | A/B 테스트용 |
39
+ | Lang | ✅ 자주 | 다국어 혼재 페이지 (한국어 본문 + 영문 코드 블록) |
40
+ | Focus | ✅ 가끔 | 키보드 네비게이션 섹션만 ring on |
41
+ | Voice | ⚠️ 드물게 | 공식·캐주얼 영역이 섞인 UI |
42
+
43
+ ### 디버깅
44
+ - `html` 요소에 전체 축 셋이 다 보이는지 DevTools로 체크. ThemeController가 마운트되지 않으면 축 어트리뷰트가 비어있을 수 있음 — boot script(themeBootScript)가 `<head>`에 inline으로 들어가 있어야 FOUC 없이 뜬다.
45
+ - 자식 영역이 축을 상속받지 못한 것 같으면: (1) 중간에 `data-*` 재선언이 있는지, (2) iframe·portal로 넘어가 조상 체인이 끊긴 건 아닌지 확인.
46
+
47
+ ### 관련
48
+ - 데모 앱 wiring: [web/app/layout.tsx](/web/app/layout.tsx) + [web/components/ThemeController.tsx](/web/components/ThemeController.tsx) + [web/components/themeBootScript.ts](/web/components/themeBootScript.ts)
49
+ - 각 축의 값·기본값·국소 스코프 디테일: [mode.md](mode.md) · [palette.md](palette.md) · [shape.md](shape.md) · [density.md](density.md) · [motion.md](motion.md) · [icon.md](icon.md)
@@ -18,7 +18,9 @@
18
18
  ### 값
19
19
  - `light` — 밝은 배경, 어두운 텍스트. 일반 웹 UI 기본.
20
20
  - `dark` — 어두운 배경, 밝은 텍스트. 야간·장시간 응시·대시보드.
21
- - `high-contrast` — 접근성 강화 대비. KWCAG/WCAG AAA 대응. (미구현, 로드맵)
21
+
22
+ ### 접근성 — 하이콘트라스트
23
+ OS 하이콘트라스트 모드(macOS Increase Contrast · Windows High Contrast)를 켠 사용자는 `@media (prefers-contrast: more)`로 자동 처리 — border alpha + muted fg를 상향해 WCAG AA 선을 넘긴다. 축 값이 아니라 토큰 override라, 컴포넌트는 변경 없이 자동 적용. 테스트: Chrome DevTools "Emulate CSS prefers-contrast: more" 또는 OS 설정 토글.
22
24
 
23
25
  ### 기본값
24
26
  `light`
@@ -0,0 +1,84 @@
1
+ # Variants
2
+
3
+ ### 원칙
4
+ 컴포넌트마다 variant 이름이 역사적으로 조금씩 다르다 (Button `primary` ≈ Badge `solid` ≈ Input `filled`). 이 문서는 **역할 축**과 **컴포넌트 축**을 교차해, 지금 자리에 들어가야 할 variant 이름이 뭔지 매번 확인하지 않고 결정할 수 있게 하는 매핑표다.
5
+
6
+ 이 표는 **기존 이름을 바꾸지 않는다**. 각 컴포넌트의 variant 이름은 그대로 유지된다 (breaking change 금지). 대신 "같은 역할"이 컴포넌트마다 뭐라 불리는지 보여줘, AI 에이전트·사람이 일관되게 매핑하도록 돕는다.
7
+
8
+ ### 역할 축
9
+ - **Primary** — 화면의 주 액션. 한 화면에 1개 원칙.
10
+ - **Secondary** — 보조 액션. 폼의 "취소", 모달의 "닫기".
11
+ - **Tertiary / Quiet** — 조용한 액션. 테이블 행 action, 아이콘 버튼 default.
12
+ - **Destructive** — 되돌릴 수 없는 액션. 삭제·해제.
13
+ - **Inverse** — 반전된 배경(어두운 섹션의 라이트 버튼) 위.
14
+
15
+ 표면(surface) 축은 별도로 둔다 — Badge·Chip·Callout·Input 같이 액션이 아닌 "표시" 컴포넌트에만 쓴다:
16
+ - **Solid** — 꽉 찬 배경.
17
+ - **Soft** — 연한 배경 + 어두운 텍스트.
18
+ - **Outline** — 투명 배경 + 테두리.
19
+ - **Line** — 아래선 하나 (입력류).
20
+ - **Fill** — 꽉 찬 배경 (알림류).
21
+ - **Plain / Ghost** — 배경·테두리 둘 다 없음.
22
+
23
+ ### 액션 컴포넌트 — 역할 × variant 매핑
24
+
25
+ | 컴포넌트 | Primary | Secondary | Tertiary | Destructive | Inverse |
26
+ |---|---|---|---|---|---|
27
+ | [Button](button.md) | `primary` | `outline` *(또는 `secondary`)* | `ghost` | `destructive` + `destructive-ghost` | `inverse` |
28
+ | [IconButton](icon-button.md) | — *(강조는 Button 쪽)* | `outline` | `ghost` | `destructive` | — |
29
+ | [SplitButton](split-button.md) | `primary` | `outline` | `ghost` | `destructive` | — |
30
+ | [IdentityButton](identity-button.md) | — | `muted` | `ghost` | — | — |
31
+
32
+ **선택 규칙 (액션)**
33
+ - 중요 액션=`primary`, 서브 액션=`outline`. 폼·모달·페이지 액션의 기본 쌍. **`secondary`는 레거시, 신규 코드에선 `outline`을 쓴다.** (관련: feedback 메모리 [Button variant defaults](../../memory/feedback_button_variant_defaults.md))
34
+ - 테이블 행·툴바 안 액션은 `ghost`.
35
+ - 삭제·해제는 `destructive`, 삭제가 두 번째 시선이면 `destructive-ghost`.
36
+ - IconButton엔 `primary`가 없다 — "꽉 찬 색의 아이콘 버튼"은 CTDS에서 금지. 강조가 필요하면 label 붙은 Button을 써라.
37
+
38
+ ### 표시 컴포넌트 — 역할 × variant 매핑
39
+
40
+ | 컴포넌트 | Solid | Soft | Outline | Line | 기타 |
41
+ |---|---|---|---|---|---|
42
+ | [Badge](badge.md) | `solid` | `soft` | — | — | `inverse` |
43
+ | [Chip](chip.md) *(필터 선택값 전용)* | — | `soft` | `outline` | — | — |
44
+ | [Callout](callout.md) *(알림 블록)* | — | — | `outline` | — | `fill` |
45
+ | [MetricCard](metric-card.md) | — | `soft` | — | `line` | `bare` |
46
+
47
+ **선택 규칙 (표시)**
48
+ - 상태·카테고리 라벨=`Badge solid`(기본) 또는 `soft`(순한 톤). 선택된 필터 값=`Chip`.
49
+ - Chip은 "선택된 값(X로 제거 가능)" 전용. 카테고리·태그·메타 라벨은 **반드시 Badge** (관련: feedback 메모리 [Chip filter-only](../../memory/feedback_chip_filter_only.md)).
50
+ - 알림·안내 박스는 `Callout fill` *(색 테두리+색 배경 금지 — 메모리 [No outlined Callout](../../memory/feedback_no_outlined_callout.md))*.
51
+
52
+ ### 입력 컴포넌트 — 매핑
53
+
54
+ | 컴포넌트 | Default | 폼 블록 | 밀도 승부처 |
55
+ |---|---|---|---|
56
+ | [Input](input.md) | `line` | `outline` | `filled` |
57
+ | [Textarea](textarea.md) | `line` | `outline` | `filled` |
58
+ | [Select](select.md) | `line` | `outline` | `filled` |
59
+ | [TimePicker](time-picker.md) | `line` | `outline` | `filled` |
60
+
61
+ **선택 규칙 (입력)**
62
+ - `line`을 먼저 고려. 독립 폼 블록이면 `outline`, 테이블 셀처럼 밀도가 극한이면 `filled` (관련: feedback 메모리 [Input line default](../../memory/feedback_input_line_default.md)).
63
+
64
+ ### 레이아웃·구조 컴포넌트
65
+ "역할" 매핑과 맞지 않는 축을 쓰는 것들. 이름 그대로 사용:
66
+ - [AppFrame](app-frame.md): `outlined` · `floating` · `flat` — 외곽 쉘 톤
67
+ - [Toolbar](toolbar.md): `floating` · `attached` — 위치
68
+ - [Tabs](tabs.md): `underline` · `segmented` · `text` — 시각 패턴
69
+ - [TableHeader](table-header.md): `line` · `plate` — 헤더 톤
70
+ - [Pagination](pagination.md): `numbered` · `compact` · `jump` — 밀도
71
+ - [MessageQuote](message-quote.md): `plain` · `blockquote` — 인용 톤
72
+ - [ListItem](list-item.md): `activity` · `notification` · `mention` · `system` — 내용 종류
73
+ - [Spinner](spinner.md): `ring` · `dots-bounce` · `dots-fade` · `pulse` · `lottie-*` — 애니메이션 종류
74
+
75
+ ### Don't
76
+ - 신규 컴포넌트에 "역할 축"의 네이밍을 쓸 수 있으면 **primary/outline/ghost/destructive**를 그대로 재사용한다. "solid"는 표면 축, "primary"는 역할 축이라는 구분을 유지.
77
+ - 기존 컴포넌트의 variant 이름을 바꾸지 않는다 — 소비자 breaking.
78
+ - 매핑표에 없는 역할을 임의로 추가하지 않는다. 신규 역할이 필요하면 이 문서를 먼저 업데이트.
79
+
80
+ ### 신규 컴포넌트 체크리스트
81
+ 1. 액션? → 역할 축 네이밍(`primary`/`outline`/`ghost`/`destructive`)
82
+ 2. 표시? → 표면 축 네이밍(`solid`/`soft`/`outline`/`line`/`fill`)
83
+ 3. 입력? → `line` 기본 + `outline`/`filled` 선택
84
+ 4. 레이아웃? → 자유. 다만 "variant"가 아니라 더 의미 있는 prop 이름(예: `position`, `tone`)을 먼저 검토
@@ -0,0 +1,80 @@
1
+ # Attachment Item
2
+
3
+ ### 원칙
4
+ 파일 첨부 1건 — `[FileIcon] filename (meta) [actions]`. 폭 400px 고정, 레이아웃은 `size` 하나로 결정. 파일명에서 확장자를 뽑아 [[components/file-icon]] 색/라벨을 자동 결정. `state="loading"`이면 좌측 뱃지를 Spinner(lottie-dots)로 교체. trailing은 콜백 조합(`onRemove`/`onDownload`/`onMore`)으로 노출. 긴 파일명은 **중간 생략 + 끝 확장자 유지**. "Chip"이 아님에 주의 — [[components/chip]]은 필터 선택값 전용.
5
+
6
+ ### Do
7
+ - 파일명 통째로 넘기면 끝 — `filename="report.pdf"` 하나로 아이콘·라벨·식별까지.
8
+ - size로 레이아웃 선택:
9
+ - `sm` — ChatInput 밀도 자리 (1줄)
10
+ - `md` — 일반 입력·목록 자리 (1줄, 기본값)
11
+ - `lg` — 첨부 상세·메시지 본문 (2줄, 파일명 위 / meta 아래)
12
+ - 업로드 중은 `state="loading"` + `meta` 문구 — 좌측이 Spinner(lottie-dots)로 교체. 상태 문구는 소비처 책임.
13
+ - 즉시 액션(미리보기·업로드 재시도 등)은 `meta`에 Button `variant="ghost"` `size="xs"`로 넣는다 — 전역 Button 체계와 합치.
14
+ - trailing은 콜백을 지정한 만큼만 노출. 복수 지정 시 download → more → remove 순으로 배치.
15
+ - Thumb(이미지)·Item(파일) 섞여 흐를 자리는 `flex-wrap: wrap` + `gap: space-2`로 조립.
16
+
17
+ ### Don't
18
+ - 폭을 임의로 바꾸지 않는다 — 전부 `--input-w-lg` (400px) 고정. 소비처가 폭 걱정 없이 세로/wrap만 결정.
19
+ - 파일명·확장자를 **따로** 받는 props 추가하지 않는다 — 소비처가 자르면 FileIcon 매핑이 어긋남.
20
+ - trailing에 임의 ReactNode 슬롯을 뚫지 않는다 — 접근성·간격·톤 통일이 깨짐. 정의된 콜백 조합만.
21
+ - trailing을 수제 버튼으로 그리지 않는다 — 반드시 IconButton 재사용 (sm/md → IconButton sm 20, lg → IconButton md 24). IconButton에 없는 치수(28·36 등)는 금지.
22
+ - loading 좌측에 베이스 Spinner variant(pulse·dots-bounce 등)를 쓰지 않는다 — CTDS 로더 톤 통일 위해 `lottie-dots` 고정. [[components/spinner]].
23
+ - 배경을 과한 톤으로 덮지 않는다 — bg는 `var(--bg)`, 테두리는 hairline, drop shadow는 가장 연한 `--shadow-xs`만.
24
+ - meta 라인에 가운뎃점 "·"는 OK — 하지만 `··•·|` 같은 꾸밈 구분자는 금지. [[feedback-no-bullet-separator]].
25
+ - `AttachmentChip`으로 부르지 않는다 — [[components/chip]]은 필터 선택값(X 제거) 전용. 파일 첨부는 Item.
26
+
27
+ ### Fallback
28
+ size 애매하면 `md`. 모르는 확장자는 FileIcon이 `others`로 폴백하니 그대로 넘겨도 됨.
29
+
30
+ ### 관련
31
+ - [[components/file-icon]] — 좌측 뱃지
32
+ - [[components/spinner]] — loading 상태 좌측 (lottie-dots variant)
33
+ - [[components/icon-button]] — trailing 액션 버튼
34
+ - [[components/button]] — meta 안 즉시 액션 (ghost xs)
35
+ - [[components/attachment-thumb]] — 이미지 첨부 (사촌)
36
+ - [[components/list-item]] — data 그룹 이웃
37
+ - [[components/chat-input]] — 상위 소비처 (size=sm/md)
38
+ - [[components/user-message]] — 메시지 내 파일 인용 (size=lg)
39
+
40
+ ### Props 시그니처
41
+ ```tsx
42
+ <AttachmentItem
43
+ filename={string} // 필수. 확장자 자동 추출
44
+ size="sm | md | lg" // default: md. lg는 2줄 큰 카드
45
+ state="idle | loading" // default: idle. loading이면 좌측이 Spinner
46
+ meta={ReactNode} // 문자열(muted 라벨) 또는 Button ghost xs 등
47
+ onRemove={(e) => void} // 지정 시 우측 X
48
+ onDownload={(e) => void} // 지정 시 우측 download
49
+ onMore={(e) => void} // 지정 시 우측 더보기 (dots)
50
+ />
51
+ ```
52
+
53
+ ### 비율·레이아웃
54
+ - 폭: 전부 `--input-w-lg` (400px) 고정.
55
+ - 외곽: `--hairline` 1px border, `var(--border)` 색. drop shadow는 `--shadow-xs` (가장 연함).
56
+ - radius: sm/md=`--radius-md`, lg=`--radius-lg`.
57
+ - 내부 gap (leading ↔ text): sm=`--space-1_5`, md=`--space-2`, lg=`--space-2_5`. actions 간은 전부 `--space-1`.
58
+ - padding:
59
+ - sm: `space-1` 상하, `space-1` 우, `space-2` 좌
60
+ - md: `space-1_5` 상하, `space-1_5` 우, `space-2_5` 좌
61
+ - lg: `space-2_5` 상하, `space-1_5` 우, `space-2_5` 좌 (세로 숨 쉬게 + 좌우는 아이콘 버튼 자리 고려해 타이트)
62
+ - leading FileIcon size: sm→sm(16) · md→md(24) · lg→lg(32).
63
+ - lg 안쪽 두 줄: label=`--text-sm`(14.5) · meta=`--text-xs`(13). 한 단계 차이. 무게/색은 label=medium/fg-strong, meta=regular/fg-subtle. (meta는 모든 size 공통 `--text-xs` + `--fg-subtle`.)
64
+ - leading loading slot: FileIcon과 동일 치수(sm=`space-4`, md=`space-6`, lg=`space-8`), 내부 Spinner는 100%.
65
+ - trailing IconButton: sm/md→IconButton `sm`(20), lg→IconButton `md`(24). variant는 전부 ghost.
66
+
67
+ ### Middle ellipsis 규칙
68
+ `filename.length > max`이면 `name... + ext`로 치환. 확장자는 항상 끝에 보존.
69
+ - sm: max=36
70
+ - md: max=40
71
+ - lg: max=44
72
+ - 확장자 없거나(dot<=0) 비정상적으로 긴 확장자(>8자) / 확장자가 max를 거의 다 먹는 경우만 일반 `...` 꼬리로 폴백.
73
+ - 사용 문자는 ASCII `...` (유니코드 `…` 금지, [[feedback-no-em-dash]]).
74
+ - CSS `text-overflow: ellipsis`는 안전망으로 유지하되, 글자 수 cap을 400px 폭보다 보수적으로 잡아 CSS가 확장자를 다시 잘라내지 않게 한다.
75
+
76
+ ### Loading 좌측 뱃지
77
+ `state="loading"`일 때 FileIcon 자리에 Spinner — `variant="lottie-dots"`. 색은 `--fg-subtle` currentColor 바인딩. 크기는 FileIcon의 각 size와 동일 치수를 가지는 정사각 슬롯이 감싸고 내부 Spinner는 100%.
78
+
79
+ ### meta 폰트
80
+ meta는 size와 무관하게 항상 `--text-xs`(13) — 파일명보다 한 단계 작은 보조 톤 유지. label(파일명)만 size별로 변동(sm=`--text-xs`, md=`--text-sm`, lg=`--text-sm`).
@@ -4,7 +4,7 @@
4
4
  Chat Input·Message의 **이미지 첨부 썸네일**. 정사각 radius ~22%(size × 0.22) 고정, 세 가지 상태 — `src` 있음(이미지)/없음(블루톤 image glyph)/`loading`(pulse). 전부 inner hairline으로 edge definition. hover 시 코너에 X 삭제 버튼 노출(`onRemove` 지정 시).
5
5
 
6
6
  ### Do
7
- - 이미지 첨부(png/jpg/gif/webp)엔 썸네일, 그 외 포맷은 [[components/file-icon]] 또는 [[components/attachment-chip]].
7
+ - 이미지 첨부(png/jpg/gif/webp)엔 썸네일, 그 외 포맷은 [[components/file-icon]] 또는 [[components/attachment-item]].
8
8
  - 업로드 처리 중/디코딩 전엔 `loading` → pulse. 끝나면 `src` 세팅.
9
9
  - 메타만 받고 썸네일 URL이 늦게 오면 `src` 비워두면 image glyph placeholder — 로딩이 길어 답답한 자리를 메움.
10
10
  - 삭제 UI가 필요하면 `onRemove`를 넘김 — hover/focus-visible에서 코너 X 활성.
@@ -19,7 +19,7 @@ Chat Input·Message의 **이미지 첨부 썸네일**. 정사각 radius ~22%(siz
19
19
  size 애매하면 `md`(40). `src`·`loading` 둘 다 없으면 자동 placeholder.
20
20
 
21
21
  ### 관련
22
- - [[components/attachment-chip]] — 파일형 첨부
22
+ - [[components/attachment-item]] — 파일형 첨부
23
23
  - [[components/file-icon]] — 브랜드 사각 뱃지
24
24
  - [[components/chat-input]] — 상위 소비처
25
25
 
@@ -0,0 +1,53 @@
1
+ # AvatarUpload
2
+
3
+ ### 원칙
4
+ Avatar + 파일 피커. 프로필·조직 로고 설정의 "현재 사진 보여주고 바꾸기" 자리. 평소엔 [[components/avatar]]와 동일하게 보이고, hover·focus에서 카메라 오버레이가 뜨며, src가 있으면 우측 상단에 작은 X로 제거.
5
+
6
+ ### Do
7
+ - `size="xl"` 기본 — 프로필 설정 폼의 가장 흔한 자리.
8
+ - `name`은 반드시 넘긴다 — src가 없을 때 Avatar가 이니셜로 폴백. alt는 생략 시 name으로 폴백.
9
+ - `onRemove`를 넘길 때만 X 버튼이 뜬다. 초기 상태(src 없음)에서 X가 보이지 않도록 조건부로.
10
+ - 허용 포맷은 `accept="image/*"` 유지. 특정 포맷만 받을 땐 `accept="image/png,image/jpeg"`.
11
+
12
+ ### Don't
13
+ - 업로드 중 상태를 자체 prop으로 두지 않는다 — 상위에서 Avatar 자리 Spinner로 교체하거나 `disabled`로 잠근다.
14
+ - 로컬 파일 preview를 컴포넌트가 책임지지 않는다 — `onChange(file)` 받고 호출자가 FileReader·blob URL로 src 세팅.
15
+ - `size="xs"`에 X 버튼이 보이게 두지 않는다 — 버튼이 아바타보다 커진다. 작은 자리에선 remove 비활성.
16
+
17
+ ### Props 시그니처
18
+ ```tsx
19
+ <AvatarUpload
20
+ size?="xs | sm | md | lg | xl" // default: xl
21
+ shape?="circle | rounded" // default: circle
22
+ src?={string}
23
+ name?={string} // 이니셜·접근성 폴백
24
+ alt?={string} // name보다 우선
25
+ onChange?={(file: File) => void}
26
+ onRemove?={() => void} // 있을 때만 X 노출
27
+ accept?={string} // default: image/*
28
+ disabled?={boolean}
29
+ editLabel?={string} // default: "사진 변경"
30
+ removeLabel?={string} // default: "사진 제거"
31
+ />
32
+ ```
33
+
34
+ ### 조합 예시
35
+ ```tsx
36
+ const [src, setSrc] = useState<string | undefined>(user.avatarUrl);
37
+
38
+ <AvatarUpload
39
+ size="xl"
40
+ src={src}
41
+ name={user.name}
42
+ onChange={async (file) => {
43
+ const url = await uploadToStorage(file);
44
+ setSrc(url);
45
+ }}
46
+ onRemove={() => setSrc(undefined)}
47
+ />
48
+ ```
49
+
50
+ ### 관련
51
+ - [[components/avatar]] — 베이스 Avatar (업로드 없는 표시용)
52
+ - [[components/icon-button]] — remove X 버튼의 패턴
53
+ - [[tokens/color]] — overlay 색은 scrim 톤
@@ -1,12 +1,12 @@
1
1
  # Bubble
2
2
 
3
3
  ### 원칙
4
- 메시지 말풍선 **primitive**. Shape 4종 × Fill 4종 조합. [[components/user-message]]·[[components/ai-message]]의 Quote 블록·AttachmentChip 등 "담는 그릇" 역할 전부에 재사용.
4
+ 메시지 말풍선 **primitive**. Shape 4종 × Fill 4종 조합. [[components/user-message]]·[[components/ai-message]]의 Quote 블록·AttachmentItem 등 "담는 그릇" 역할 전부에 재사용.
5
5
 
6
6
  ### Do
7
7
  - UserMessage는 `shape=circular-drop fill=translucent tail=end` 조합이 기본.
8
8
  - AI Quote 블록은 `shape=rounded fill=outline` 또는 `fill=none`.
9
- - Shape 축 따라 radius 변화 — rounded는 `radius-xl` 토큰 사용.
9
+ - Shape 축 따라 radius 변화 — rounded는 `radius-md` 토큰 사용 (soft/standard/crisp 반응 명확).
10
10
  - circular-drop의 `tail` prop으로 꼬리 방향 지정 — user는 end, assistant/quote는 start.
11
11
 
12
12
  ### Don't
@@ -1,11 +1,11 @@
1
1
  # Chat Input
2
2
 
3
3
  ### 원칙
4
- AI 채팅의 입력 영역. **첨부물 바 → 인용 바 → 입력 본문**(textarea 또는 input)의 세 층 구성. 모델 선택·전송·중지·파일첨부를 전부 담당. 하위 레이어 [[components/file-icon]]·[[components/attachment-thumb]]·[[components/attachment-chip]]·[[components/quotation-bar]]를 **조립**하는 상위 블록 — 자체 primitive 추가 금지.
4
+ AI 채팅의 입력 영역. **첨부물 바 → 인용 바 → 입력 본문**(textarea 또는 input)의 세 층 구성. 모델 선택·전송·중지·파일첨부를 전부 담당. 하위 레이어 [[components/file-icon]]·[[components/attachment-thumb]]·[[components/attachment-item]]·[[components/quotation-bar]]를 **조립**하는 상위 블록 — 자체 primitive 추가 금지.
5
5
 
6
6
  ### Do
7
7
  - Shape은 두 가지 — `multi`(기본, 긴 입력·오버레이 UI), `single`(토픽바·검색봉 느낌).
8
- - Radius는 세 단계: `square`(--radius-xs) / `rounded`(--radius-sm) / `circular`(--radius-md). Shape 축 반응.
8
+ - 외곽 radius는 Shape 축 토큰이 직접 결정 — prop으로 받지 않음.
9
9
  - `models` + `selectedModel` + `onSelectModel` 셋이 다 있을 때만 모델 selector 노출.
10
10
  - 전송은 `Enter`로 — multi에선 `Shift+Enter`가 줄바꿈. 스트리밍 중엔 `Enter`나 send 클릭이 `onStop`을 호출.
11
11
  - 첨부·인용은 전부 **외부 상태** — ChatInput은 표시·삭제 콜백만 중계. 업로드 로직·중복 체크는 소비처 책임.
@@ -13,16 +13,15 @@ AI 채팅의 입력 영역. **첨부물 바 → 인용 바 → 입력 본문**(t
13
13
 
14
14
  ### Don't
15
15
  - 첨부·인용·메시지 상태를 ChatInput 안에서 관리하지 않는다 — controlled 전용.
16
- - radius prop을 수치로 받지 않는다 — `square | rounded | circular` 세 단계만. 실제 값은 Shape 축 토큰이 결정.
17
16
  - 모델 선택 메뉴를 내부에서 열지 않는다 — 현재는 라벨 클릭 시 다음 모델로 토글만. 실제 ContextMenu는 소비처에서 조립 (ChatInput의 책임 과잉 방지).
18
17
  - SingleLine에서 모델 라벨을 끝까지 노출하지 않는다 — 10자 넘으면 자동 compact(아이콘만).
19
18
  - Send 버튼 색을 변경하지 않는다 — 비활성=`bg-subtle` / 활성·스트리밍=`primary`로 고정.
20
19
 
21
20
  ### Fallback
22
- shape 애매하면 `multi`, radius 애매하면 `rounded`. maxRows 애매하면 `8`.
21
+ shape 애매하면 `multi`. maxRows 애매하면 `8`.
23
22
 
24
23
  ### 관련
25
- - [[components/file-icon]] / [[components/attachment-thumb]] / [[components/attachment-chip]] / [[components/quotation-bar]] — 조립 요소
24
+ - [[components/file-icon]] / [[components/attachment-thumb]] / [[components/attachment-item]] / [[components/quotation-bar]] — 조립 요소
26
25
  - [[components/message-bubble]] — 짝 컴포넌트 (후속)
27
26
  - [[components/context-menu]] — 모델 선택 메뉴를 소비처에서 조립할 때
28
27
  - [[primitives/scroll]] — "Textarea / 단일 콘텐츠 스크롤" 섹션. 긴 글 스크롤 시 글이 root border에서 clip되도록 wrapper padding-top + textarea padding-bottom 패턴 사용. 레이아웃 변경 시 반드시 그 규약 유지.
@@ -31,7 +30,6 @@ shape 애매하면 `multi`, radius 애매하면 `rounded`. maxRows 애매하면
31
30
  ```tsx
32
31
  <ChatInput
33
32
  shape="multi | single" // default: multi
34
- radius="square | rounded | circular" // default: rounded
35
33
 
36
34
  value={string}
37
35
  onChange={(v) => void}
@@ -63,10 +61,11 @@ type ChatAttachment =
63
61
  | { id, kind: "file", filename }
64
62
  ```
65
63
  - `kind: "image"`에 `src` 없거나 `loading: true`면 AttachmentThumb이 placeholder/pulse 자동.
66
- - `kind: "file"`은 AttachmentChip이 filename 확장자로 FileIcon을 자동 결정.
64
+ - `kind: "file"`은 AttachmentItem이 filename 확장자로 FileIcon을 자동 결정.
67
65
 
68
66
  ### 레이아웃
69
- - 외곽: hairline border, `var(--bg)`, radius=props
67
+ - 외곽: hairline border, `var(--bg)`, radius=Shape 축 (`--radius-sm`)
68
+ - **스크롤 룰**: root `padding: 0; overflow: hidden`. textarea에 상·하·좌·우 padding 전부 → 긴 글 스크롤 시 글이 root border까지 흘러 자연스레 clip. (wrapper에 padding 두면 scroll 시 "빈 띠"가 남아 어색 — [[primitives/scroll]])
70
69
  - focus-within에서 ring
71
70
  - 내부 세로 gap: `--space-2`, padding: `--space-3`
72
71
  - **MultiLine**: `attachments → quotation → [textarea / footer]` 세로
@@ -5,7 +5,7 @@
5
5
 
6
6
  ### Do
7
7
  - 언어 아는 경우 `language="html"` 처럼 라벨 노출 — 작게 fg-muted로.
8
- - `onCopy`를 안 주면 clipboard API로 자동 복사. 성공 시 Check 아이콘으로 1.5s 피드백.
8
+ - `onCopyCode`를 안 주면 clipboard API로 자동 복사. 성공 시 Check 아이콘으로 1.5s 피드백.
9
9
  - 가로가 넘치면 pre 안에서 가로 스크롤 — body wrapping 대신.
10
10
 
11
11
  ### Don't
@@ -31,5 +31,5 @@ language 애매하면 생략. onCopy 애매하면 clipboard API 기본.
31
31
  ### 레이아웃
32
32
  - root: `primary-subtle` bg, `radius-md`, overflow hidden
33
33
  - header: `space-2 space-3` padding, 언어 라벨(mono text-xs fg-muted) + copy 버튼
34
- - copy 버튼: `space-6` 정사각, `bg-muted` 기본
34
+ - copy 버튼: `space-6` 정사각, **ghost (transparent → hover `--hover` 틴트)**
35
35
  - 본문 `<pre>`: `space-2 space-4 space-4` padding, mono text-sm, line-height 1.6, overflow-x auto
@@ -0,0 +1,47 @@
1
+ # Disclosure
2
+
3
+ ### 원칙
4
+ 토글로 열고 닫는 섹션. caret·triggerLabel·body의 3단, 애니메이션은 `grid-template-rows: 0fr → 1fr` 트릭으로 자연 높이 측정 없이 CSS만으로. FAQ·설정 섹션·"적용 방법"처럼 기본은 접혀 있고 필요한 때만 펴는 자리.
5
+
6
+ ### Do
7
+ - compound(`.Trigger` + `.Body`) 조합을 쓴다. flat API 없음 — 명시적으로 Trigger·Body 2단 조립.
8
+ - `defaultOpen`으로 초기 상태만 지정하고 uncontrolled로 두는 게 가장 흔함. 외부에서 열림 추적이 필요할 때만 `open` + `onOpenChange`.
9
+ - 하나의 섹션 안에 **여러 Disclosure**를 쌓을 때 각각 독립 — accordion처럼 "한 번에 하나만 열림"이 필요하면 상위에서 상태로 조율.
10
+ - `aria-expanded`·`aria-controls`가 자동 세팅됨. 소비자가 수동 추가할 필요 없음.
11
+
12
+ ### Don't
13
+ - Modal·Drawer 안의 "숨겨진 설정" 자리에 Disclosure를 과용하지 않는다 — 모달 안 모달 느낌. 그 자리엔 섹션 구분(hairline + heading)이 먼저.
14
+ - Trigger 안에 `<button>`·`<a>` 중첩하지 않는다 — Trigger 자체가 button.
15
+ - Body의 콘텐츠 높이가 매번 뒤바뀌는 경우(실시간 스트림) grid-rows 애니메이션이 뚝뚝 끊김. 그 경우엔 `open` 토글 애니메이션을 쓰지 말거나 Disclosure 밖으로.
16
+
17
+ ### Props 시그니처
18
+ ```tsx
19
+ <Disclosure defaultOpen?={boolean} open?={boolean} onOpenChange?={(open: boolean) => void}>
20
+ <Disclosure.Trigger>…라벨…</Disclosure.Trigger>
21
+ <Disclosure.Body>…접히는 콘텐츠…</Disclosure.Body>
22
+ </Disclosure>
23
+ ```
24
+
25
+ ### 조합 예시
26
+ ```tsx
27
+ <Disclosure defaultOpen>
28
+ <Disclosure.Trigger>적용 방법</Disclosure.Trigger>
29
+ <Disclosure.Body>
30
+ 1) ctds.config.json 커밋
31
+ 2) layout.tsx에 ThemeController 추가
32
+ 3) /ctds 라우트에서 축 설정
33
+ </Disclosure.Body>
34
+ </Disclosure>
35
+
36
+ // 섹션 리스트 — 각자 독립 open
37
+ {faqs.map((f) => (
38
+ <Disclosure key={f.id}>
39
+ <Disclosure.Trigger>{f.q}</Disclosure.Trigger>
40
+ <Disclosure.Body>{f.a}</Disclosure.Body>
41
+ </Disclosure>
42
+ ))}
43
+ ```
44
+
45
+ ### 관련
46
+ - [[components/sidebar]] — 섹션 접기 UI는 Sidebar.Section의 collapsible 사용
47
+ - [[axes/motion]] — Disclosure 애니메이션은 `--duration-base` × Motion 축에 반응
@@ -20,7 +20,7 @@
20
20
  size 애매하면 `md`, radius 애매하면 `auto`(Shape 축 따라감). 확장자 모르면 `txt`도 OK — 어차피 `others` 폴백.
21
21
 
22
22
  ### 관련
23
- - [[components/attachment-chip]] — FileIcon + 파일명 + X. 가장 흔한 소비처
23
+ - [[components/attachment-item]] — FileIcon + 파일명 + X. 가장 흔한 소비처
24
24
  - [[components/attachment-thumb]] — 이미지 첨부(FileIcon 대신)
25
25
  - [[components/chat-input]] / [[components/message-bubble]] — 상위 소비처
26
26
  - [[axes/shape]] — radius=auto일 때 Shape 축 따라감
@@ -59,8 +59,8 @@ size 애매하면 `md`, radius 애매하면 `auto`(Shape 축 따라감). 확장
59
59
 
60
60
  ### 조합 예시
61
61
  ```tsx
62
- // 가장 흔한 — AttachmentChip 안
63
- <AttachmentChip filename="report.pdf" /> // 내부에서 <FileIcon ext="pdf" size="md" />
62
+ // 가장 흔한 — AttachmentItem 안
63
+ <AttachmentItem filename="report.pdf" /> // 내부에서 <FileIcon ext="pdf" size="md" />
64
64
 
65
65
  // 리스트 셀 (sm)
66
66
  <FileIcon ext=".psd" size="sm" />
@@ -0,0 +1,38 @@
1
+ # Generated Image
2
+
3
+ ### 원칙
4
+ AI가 생성한 이미지를 메시지 안에 **크게** 보여주는 블록. 작은 썸네일은 [[components/attachment-thumb]].
5
+
6
+ ### Do
7
+ - 기본은 세로형 포스터(`aspect="portrait"` = 3:4).
8
+ - 생성 중엔 `loading`만 넘기고 src 생략 — 완료되면 src 세팅.
9
+ - 다운로드가 필요하면 `onDownload` 전달 — hover/focus-within에서만 우상단에 노출.
10
+
11
+ ### Don't
12
+ - loading과 src 동시 지정 — loading이 우선이라 의미 없음.
13
+ - 다운로드 버튼 상시 노출.
14
+
15
+ ### Fallback
16
+ aspect 애매하면 `portrait`. src 없으면 placeholder (bg 이미지 + spinner lottie-image-gen).
17
+
18
+ ### 관련
19
+ - [[components/attachment-thumb]]
20
+ - [[components/ai-message]] — 상위 소비처
21
+
22
+ ### Props 시그니처
23
+ ```tsx
24
+ <GeneratedImage
25
+ src={string}
26
+ alt={string}
27
+ aspect="portrait | square | landscape" // default: portrait
28
+ loading={boolean}
29
+ onDownload={(e) => void}
30
+ />
31
+ ```
32
+
33
+ ### 레이아웃
34
+ - 외곽: `radius-md`, `max-width: input-w-lg × 1.1`, overflow hidden
35
+ - aspect-ratio로 비율 고정 (`3/4`·`1/1`·`4/3`)
36
+ - loading/placeholder bg: `/illustrations/image-gen-placeholder.png`
37
+ - 로딩 중 중앙: [[components/spinner]] `variant="lottie-image-gen"` `size="lg"`, color `--bg`
38
+ - Download 버튼: `space-8` 정사각, 우상단 inset `space-2`, hover 시 fade-in + scale
@@ -22,5 +22,5 @@
22
22
  ```
23
23
 
24
24
  ### 레이아웃
25
- - plain: 외곽 장식 없음
26
- - blockquote: `padding-left: space-4`, `border-left: space-0_5 solid border-strong`, color `fg-muted`
25
+ - plain: 외곽 장식 없음, 색 `--fg`
26
+ - blockquote: `padding-left: space-4`, 왼쪽 bar는 `::before` pseudo로 상·하 `space-1` inset — border-left 아님 (**짧고 radius-full로 캡슐**). 색 `--fg-muted`, bar는 `--border-strong`.
@@ -26,8 +26,9 @@ AIMessage의 **하단 액션바 옆에 inline으로 붙는 chip**. 겹친 favico
26
26
 
27
27
  ### 레이아웃
28
28
  - 외곽: ghost pill (transparent → hover bg-subtle)
29
- - padding: `space-0_5 space-2 space-0_5 space-1`
30
- - 폰트: `text-xs`, `fg-muted` → hover `fg-strong`
29
+ - padding: `space-1_5 space-3 space-1_5 space-2` (hover 영역 넉넉)
30
+ - gap: `space-1_5`
31
+ - 폰트: `text-sm`, `fg-muted` → hover `fg-strong`
31
32
  - favicon stack: 각 원 `space-4`(16px), 서로 `space-1` negative margin으로 겹침
32
- - 각 favicon에 box-shadow로 bg 색 ring (cutout 효과)
33
- - `+N`은 `fg-muted`, tabular-nums
33
+ - 각 favicon에 box-shadow로 bg 색 ring (cutout 효과) — [[feedback-bg-or-stroke-only]] 예외: cutout은 "연결 모양 분리"가 목적이라 bg+stroke 금지 룰과 다른 범주
34
+ - 숫자 노출 없음 — 라벨만 (전체 리스트는 popover로)
@@ -26,22 +26,25 @@ kind 애매하면 `generating`, message 애매하면 kind별 기본.
26
26
  ```tsx
27
27
  <MessageState
28
28
  kind="generating | searching | memory | image | video | file | custom"
29
- message={ReactNode} // kind별 기본 라벨 override
30
- done={boolean} // true → ✓ + 애니 정지
29
+ message={ReactNode} // 활성 라벨 override (기본 "…ing…")
30
+ doneMessage={ReactNode} // 완료 라벨 override (기본 "…ed")
31
+ done={boolean} // true → ✓ + 애니 정지 + doneMessage로 전환
31
32
  icon={ReactNode} // kind="custom" 또는 override
32
33
  />
33
34
  ```
34
35
 
35
- ### Kind 매트릭스
36
- | kind | 아이콘 | 기본 라벨 |
37
- |---|---|---|
38
- | generating | Spinner (lottie-dots) | 응답 생성 중… |
39
- | searching | MagnifyingGlass | Searching the web… |
40
- | memory | PencilSimple | Updated saved memory |
41
- | image | Image | Looking at the image… |
42
- | video | VideoCamera | Reviewing the video… |
43
- | file | File | Processing file… |
44
- | custom | `icon` prop | `message` prop |
36
+ ### Kind 매트릭스 (active / done 라벨 pair)
37
+ | kind | 아이콘 | active | done |
38
+ |---|---|---|---|
39
+ | generating | Spinner (lottie-dots) | 응답 생성 중… | 응답 완료 |
40
+ | searching | MagnifyingGlass | Searching the web… | Searched the web |
41
+ | memory | PencilSimple | Updating memory… | Updated saved memory |
42
+ | image | Image | Looking at the image… | Looked at the image |
43
+ | video | VideoCamera | Reviewing the video… | Reviewed the video |
44
+ | file | File | Processing file… | Processed file |
45
+ | custom | `icon` prop | `message` prop | `doneMessage` prop |
46
+
47
+ 사용처가 커스텀 pair를 넘기면 (예: `message="Reading 'report.pdf'…" doneMessage="Read 'report.pdf'"`) 활성·완료 모두 자연스러운 영문 과거형으로.
45
48
 
46
49
  ### 애니메이션
47
50
  - 라벨 각 글자 wave bounce — `duration-slower × 5` 주기, `duration-base` 간격으로 index 지연
@@ -1,7 +1,7 @@
1
1
  # Message Table
2
2
 
3
3
  ### 원칙
4
- [[components/ai-message]] 안의 markdown table 톤 wrapper. 소비처가 `<table><thead><tr><th>…` 생짜 그대로 전달하면 CTDS 톤(hairline border, radius-md, header bg)으로 자동 스타일. 가로 overflow 시 wrapper가 scroll.
4
+ [[components/ai-message]] 안의 markdown table 톤 wrapper. 소비처가 `<table><thead><tr><th>…` 생짜 그대로 전달하면 CTDS 톤(**TableHeader·TableRow line variant와 동일 — 외곽 border 없음, bg 없음, 하단 hairline만**)으로 자동 스타일. 가로 overflow 시 wrapper가 scroll.
5
5
 
6
6
  ### Do
7
7
  - 생짜 `<table>` 전달 — markdown parser(remark 등)가 뱉은 걸 그대로 wrap.
@@ -19,9 +19,12 @@
19
19
  </MessageTable>
20
20
  ```
21
21
 
22
- ### 레이아웃
23
- - wrapper: hairline border, radius-md, overflow-x auto
24
- - thead: `bg-subtle`
25
- - cell padding: `space-2 space-3`
26
- - cell border-bottom: hairline (마지막 행 제외)
22
+ ### 레이아웃 (TableHeader/TableRow `line` variant와 짝)
23
+ - wrapper: 외곽 border 없음, overflow-x: auto
24
+ - thead: bg 없음, 하단 hairline
25
+ - tbody row: 하단 hairline (마지막 행 제외)
26
+ - th/td padding: `space-2 space-2`. 첫 셀 좌측 padding 0, 마지막 셀 우측 padding 0 (셀이 경계에서 떨어지지 않음)
27
+ - th: `fg-strong`, font-weight medium, `label-sm` 사이즈
28
+ - td: `text-sm`, line-height 1.5
27
29
  - 숫자: `font-variant-numeric: tabular-nums`
30
+ - 숫자 셀도 **좌측 정렬 유지** ([[feedback-no-right-align-numbers]])
@@ -0,0 +1,43 @@
1
+ # Onboarding
2
+
3
+ ### 원칙
4
+ CTDS 설치 직후 `/ctds` 라우트에 뜨는 위자드. 스타일·분위기·밀도·컬러·레이아웃을 6단계로 결정해 `ctds.config.json` + `DESIGN.md`를 생성한다. 소비자 프로젝트의 "설정 전 단계" — 테마·축 기본값은 보류하고 중립 라이트로 뜬다 ([themeBootScript](../../web/components/themeBootScript.ts)가 `/onboarding` 경로를 감지해 다크 선호도 무시).
5
+
6
+ ### Do
7
+ - `OnboardingPage`를 그대로 소비자 프로젝트의 `/ctds` 라우트에 심는다 (`ctds-cli init`이 자동 복사).
8
+ - 사용자가 "적용"을 누르면 `actions.applyOnboarding`이 `ctds.config.json` + `DESIGN.md`를 소비자 레포 루트에 쓰고, 그 커밋 이후 ThemeController가 config를 읽어 축 기본값으로 사용.
9
+ - 소비자가 자체 설정 플로우를 쓰고 싶으면 OnboardingPage를 교체하지 말고 **wizardData의 choices만 override** 하거나, 자체 라우트에서 `applyOnboarding`을 직접 호출.
10
+
11
+ ### Don't
12
+ - OnboardingPage를 메인 앱 안으로 포팅하지 않는다 — 설정 전 단계 전용. 메인 앱의 테마 변경은 ThemeController가 담당.
13
+ - `OnboardingAxisPanel`을 다른 페이지에서 재사용하지 않는다 — 축 디버거는 docs 전용([Docs axis controller replacement TODO](../../memory/project_docs_axis_todo.md)).
14
+ - 위자드 단계 수를 늘리지 않는다 — 6단계는 심리적 상한. 신규 설정은 "적용" 이후 메인 앱의 설정 페이지에서.
15
+
16
+ ### Props 시그니처
17
+ ```tsx
18
+ <OnboardingPage /> // props 없음. 내부에서 useTheme + wizardData로 조립.
19
+ ```
20
+
21
+ 하위 블록은 OnboardingPage 안에서만 조립:
22
+ - `PresetsGallery` — 테마 프리셋 그리드 (step 1)
23
+ - `OnboardingAxisPanel` — 축 디버거 패널 (docs 전용 재사용 금지)
24
+ - `LayoutBuilder` — 레이아웃 선택 (step 5)
25
+ - `DocsIndex` — 설치 후 문서 지도
26
+
27
+ ### Flow
28
+ 1. **theme** — "디자인 스타일" 프리셋 선택 (standard · spare · expressive 등)
29
+ 2. **vibe** — "프로덕트 분위기" (일반 · 생산성 · 소셜) → 축 세트 결정
30
+ 3. **density** — 정보 밀도 (default · dense)
31
+ 4. **color** — Palette + brand hex
32
+ 5. **layout** — 사이드바·탑바 조합
33
+ 6. **summary** — 선택 확인 + 적용
34
+
35
+ ### 적용 결과
36
+ - `ctds.config.json` — 선택한 축 값 + palette hue
37
+ - `DESIGN.md` — 소비자 프로젝트의 "현재 디자인 결정" 기록
38
+ - 두 파일 커밋 후 ThemeController가 config를 initialConfig로 받아 축 기본값으로 사용
39
+
40
+ ### 관련
41
+ - [[components/stepper]] — 단계 네비
42
+ - [[components/selectable-card]] — 각 step의 선택 카드
43
+ - [[axes/*]] — 선택 결과가 세팅하는 축