@conscience-technology/ctds 0.1.14 → 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 (64) hide show
  1. package/assets/components/AppFrame/AppFrame.module.css +6 -0
  2. package/assets/components/{AttachmentChip/AttachmentChip.module.css → AttachmentItem/AttachmentItem.module.css} +5 -8
  3. package/assets/components/{AttachmentChip/AttachmentChip.tsx → AttachmentItem/AttachmentItem.tsx} +21 -15
  4. package/assets/components/AttachmentItem/index.ts +6 -0
  5. package/assets/components/Button/Button.module.css +11 -2
  6. package/assets/components/ChatInput/ChatInput.tsx +2 -2
  7. package/assets/components/DatePicker/Calendar.tsx +19 -1
  8. package/assets/components/Drawer/Drawer.tsx +120 -39
  9. package/assets/components/Drawer/index.ts +9 -1
  10. package/assets/components/{MessageAttachment/MessageAttachment.module.css → GeneratedImage/GeneratedImage.module.css} +3 -2
  11. package/assets/components/{MessageAttachment/MessageAttachment.tsx → GeneratedImage/GeneratedImage.tsx} +8 -8
  12. package/assets/components/GeneratedImage/index.ts +5 -0
  13. package/assets/components/Icon/Icon.tsx +4 -1
  14. package/assets/components/Modal/Modal.tsx +114 -33
  15. package/assets/components/Modal/index.ts +12 -2
  16. package/assets/components/Spinner/lottieManifest.ts +8 -8
  17. package/assets/components/Switch/Switch.tsx +6 -0
  18. package/assets/components/TableHeader/TableHeader.module.css +29 -20
  19. package/assets/components/TableRow/TableRow.module.css +7 -5
  20. package/assets/components/TableRowEdit/TableRowEdit.module.css +10 -3
  21. package/assets/components/TableRowEdit/TableRowEdit.tsx +7 -2
  22. package/assets/components/Toast/Toast.tsx +19 -0
  23. package/assets/components/_data.ts +39 -16
  24. package/assets/components/app-shell.css +17 -0
  25. package/assets/components/index.ts +77 -0
  26. package/assets/components/reset.css +63 -0
  27. package/assets/components/themeBootScript.ts +40 -0
  28. package/assets/components/tokens.css +68 -57
  29. package/assets/prompts/GUIDE.md +1 -1
  30. package/assets/prompts/PRINCIPLES.md +9 -0
  31. package/assets/prompts/axes/cascade.md +49 -0
  32. package/assets/prompts/axes/mode.md +3 -1
  33. package/assets/prompts/components/_variants.md +84 -0
  34. package/assets/prompts/components/{attachment-chip.md → attachment-item.md} +12 -10
  35. package/assets/prompts/components/attachment-thumb.md +2 -2
  36. package/assets/prompts/components/avatar-upload.md +53 -0
  37. package/assets/prompts/components/bubble.md +2 -2
  38. package/assets/prompts/components/chat-input.md +7 -8
  39. package/assets/prompts/components/code-block.md +2 -2
  40. package/assets/prompts/components/disclosure.md +47 -0
  41. package/assets/prompts/components/file-icon.md +3 -3
  42. package/assets/prompts/components/generated-image.md +38 -0
  43. package/assets/prompts/components/message-quote.md +2 -2
  44. package/assets/prompts/components/message-sources.md +5 -4
  45. package/assets/prompts/components/message-table.md +9 -6
  46. package/assets/prompts/components/onboarding.md +43 -0
  47. package/assets/prompts/components/quotation-bar.md +1 -1
  48. package/assets/prompts/components/sidebar-split.md +70 -0
  49. package/assets/prompts/components/sidebar.md +2 -0
  50. package/assets/prompts/components/time-picker.md +69 -0
  51. package/assets/prompts/components/toolbar.md +69 -0
  52. package/assets/prompts/components/user-message.md +2 -2
  53. package/package.json +80 -3
  54. package/src/commands/init.js +53 -23
  55. package/src/utils/deps.js +23 -17
  56. package/src/utils/patch.js +155 -0
  57. package/assets/components/AttachmentChip/index.ts +0 -6
  58. package/assets/components/Calendar/Calendar.module.css +0 -163
  59. package/assets/components/Calendar/Calendar.tsx +0 -323
  60. package/assets/components/Calendar/index.ts +0 -2
  61. package/assets/components/MessageAttachment/index.ts +0 -5
  62. package/assets/prompts/components/calendar.md +0 -116
  63. package/assets/prompts/components/form-field.md +0 -74
  64. package/assets/prompts/components/message-attachment.md +0 -40
@@ -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로)
@@ -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/*]] — 선택 결과가 세팅하는 축
@@ -21,7 +21,7 @@ ChatInput 상단·Message 안에서 "**다른 글을 인용**"을 1줄로 접어
21
21
  ### 관련
22
22
  - [[components/chat-input]] — 상위 소비처 (상단 bar 자리)
23
23
  - [[components/message-bubble]] — 메시지 내 인용 표시에서도 재사용
24
- - [[components/attachment-chip]] — 비슷한 레이아웃 (lead icon + content + X)
24
+ - [[components/attachment-item]] — 비슷한 레이아웃 (lead icon + content + X)
25
25
 
26
26
  ### Props 시그니처
27
27
  ```tsx
@@ -0,0 +1,70 @@
1
+ # SidebarSplit
2
+
3
+ ### 원칙
4
+ 2단 사이드바 — 좌측 좁은 **Rail**(아이콘 only 탭) + 우측 **Panel**(활성 탭의 상세 메뉴). VS Code·Linear 스타일 "활동 바 + 사이드 패널" 조합. 단일 폭 Sidebar가 메뉴 수를 다 담지 못할 때, 또는 완전히 다른 영역을 전환하는 네비게이션에서.
5
+
6
+ 메모리 주의: **"2단 쉘은 SidebarSplit 쓰지 말고 바깥 flex로 Rail+Sidebar 직접 조립"** ([Sidebar scroll trap](../../memory/feedback_sidebar_scroll.md)). 그 함정은 CtdsSidebar.Body 안 섹션이 뭉개지는 상황. SidebarSplit 자체는 panel 안 콘텐츠가 **menu/section 구조**일 때 안전하게 쓴다 — 복잡한 폼·검색 결과를 panel에 넣을 거면 Rail + Sidebar를 바깥 flex로 직접 조립.
7
+
8
+ ### Do
9
+ - 각 section의 `icon`은 Icon 인스턴스로. `label`은 rail 버튼의 `aria-label` + panel 헤더 타이틀로 재사용.
10
+ - panel 안은 `<SidebarSplit.Section title="…">` + `<SidebarSplit.Item icon label>` 조합을 기본으로. 섹션 하나에 Item 5~10개 수준이 적정.
11
+ - 좁은 공간이 필요하면 `panelOpen={false}` + onPanelOpenChange로 접기 토글. Rail만 남고 Panel은 숨음.
12
+ - 섹션이 **active일 때 Rail 아이콘은 `secondary` variant**로 자동 전환 — 사용자가 override하지 말 것.
13
+
14
+ ### Don't
15
+ - panel 안에 긴 폼·에디터를 넣지 않는다 — SidebarSplit은 "메뉴 전환" 자리. 콘텐츠는 메인 영역에.
16
+ - Rail 섹션을 **6개 이상** 쌓지 않는다. 세로 공간상 7~8개부터 하단이 잘림. 그 이상이면 메뉴 구조 재검토.
17
+ - `defaultActiveId`를 지정하지 않고 섹션 리스트를 조건부 렌더링하지 않는다 — 초기값이 비면 panel이 비어 보임.
18
+ - 2단 쉘 바깥(앱 전체) 레이아웃에 SidebarSplit을 **직접** 쓰지 않는다. 바깥 쉘은 flex로 Rail + CtdsSidebar를 수동 조립 — 메모리 참조.
19
+
20
+ ### Props 시그니처
21
+ ```tsx
22
+ <SidebarSplit
23
+ sections={SidebarSplitSection[]} // { id, icon, label, panel }[]
24
+ brand?={ReactNode} // rail 상단 로고
25
+ railFooter?={ReactNode}
26
+ panelFooter?={ReactNode}
27
+ activeId?={string} // controlled
28
+ defaultActiveId?={string}
29
+ onActiveChange?={(id: string) => void}
30
+ panelOpen?={boolean} // controlled
31
+ defaultPanelOpen?={boolean} // default: true
32
+ onPanelOpenChange?={(open: boolean) => void}
33
+ >
34
+ ```
35
+
36
+ Compound sub:
37
+ - `<SidebarSplit.Section title>` — panel 안 그룹 (role="group")
38
+ - `<SidebarSplit.Item icon label active trailing shortcut hasSubmenu onClick>` — MenuItem 래퍼
39
+
40
+ ### 조합 예시
41
+ ```tsx
42
+ const sections: SidebarSplitSection[] = [
43
+ {
44
+ id: "files",
45
+ icon: <Icon name="folder" />,
46
+ label: "파일",
47
+ panel: (
48
+ <>
49
+ <SidebarSplit.Section title="최근">
50
+ <SidebarSplit.Item icon={<Icon name="file" />} label="README.md" />
51
+ <SidebarSplit.Item icon={<Icon name="file" />} label="plan.md" />
52
+ </SidebarSplit.Section>
53
+ </>
54
+ ),
55
+ },
56
+ {
57
+ id: "search",
58
+ icon: <Icon name="search" />,
59
+ label: "검색",
60
+ panel: <Input placeholder="검색…" />,
61
+ },
62
+ ];
63
+
64
+ <SidebarSplit sections={sections} defaultActiveId="files" />
65
+ ```
66
+
67
+ ### 관련
68
+ - [[components/sidebar]] — 단일 폭 사이드바
69
+ - [[components/menu-item]] — panel item의 베이스
70
+ - [[components/icon-button]] — rail 버튼
@@ -33,6 +33,8 @@ Header는 **가로 행**이기 때문에 Section을 넣으면 nav 전체가 가
33
33
  - Sidebar 자체에 padding/width를 CSS로 덮지 않는다. `--sidebar-w*` 토큰 경유.
34
34
  - `.root`에 padding을 꽂지 않는다 — Body가 스크롤 컨테이너라 스크롤바가 안쪽으로 밀려 가장자리에 안 붙는다. 좌우 여백은 Header/Section/Footer가 각자 담당([[project_scroll_rule]]).
35
35
  - Brand symbol·wordmark 크기 override 금지. `--sidebar-brand-symbol-max`·`--sidebar-brand-wordmark-max` 상한 안에서만.
36
+ - **Body 안 섹션 리스트가 길어서 스크롤이 필요할 때**, 섹션들에 `flex-shrink`를 그대로 두지 않는다. Body는 `display:flex column`이라 자식(Section)이 기본 `flex-shrink:1`로 수축되어 header가 몇 px로 뭉개지고 아이템이 사라져 보인다. 해결: Body 직속 자식에 `flex: 0 0 auto`를 걸어 각 섹션이 자기 콘텐츠 전체 높이를 유지하게 하고, 스크롤은 Body의 `overflow-y:auto`가 담당하게 한다.
37
+ - **2단 쉘(아이콘 Rail + Sidebar) 조립 시 `SidebarSplit`를 쓰지 않는다** — SidebarSplit는 panel 영역을 자체 flex 체인으로 제한해서 안쪽 콘텐츠가 "영역 안에 갇힌" 느낌이 된다. 그냥 바깥 `display:flex`로 Rail(IconButton 세로 스택)과 `<Sidebar>`를 가로로 조립하고, 스크롤은 CtdsSidebar.Body에게 맡겨라. 기존 사이드바가 이미 자연 스크롤로 설계됐으니 그 구조를 그대로 차용하는 게 정답.
36
38
 
37
39
  ### Fallback
38
40
  collapsed 결정 애매하면 `false`. Section title 애매하면 생략. 경계선 애매하면 AppFrame `variant="outlined"`.
@@ -0,0 +1,69 @@
1
+ # TimePicker
2
+
3
+ ### 원칙
4
+ 시·분 선택 입력. [[components/date-picker]]의 시간 짝. 트리거는 세그먼트 입력(HH:MM 2자리씩) + popover 안에 시/분 리스트 또는 전체 슬롯 그리드. "약속 시간·회의 시작·마감 시각" 자리.
5
+
6
+ ### Do
7
+ - `variant="line"` 기본. 폼 블록이면 `outline`, 밀도 극한이면 `filled` ([Input line default](../../memory/feedback_input_line_default.md) 메모리와 동일 규칙).
8
+ - `step={30}` 기본. 회의·일정은 30분 단위가 흔함. 5·10·15·60도 선택 가능하지만 그 외 값은 자동 clamp.
9
+ - 24h 지역이면 `format="24h"`(기본), 12h 지역이면 `format="12h"` + meridiem 세그먼트.
10
+ - `quickOptions`는 "지금부터 상대 시간"이 흔한 자리(알람·예약 리마인더)에서만 켠다. 일반 폼엔 노이즈.
11
+ - 예약 가능 슬롯을 그리드로 보여줘야 하는 자리(병원·식당 예약)엔 `inline + panelLayout="grid"`. gridCols 4~8 중 레이아웃 폭에 맞춰.
12
+
13
+ ### Don't
14
+ - step에 7·13·45 같은 비표준 분수치를 넣지 않는다 — 리스트 가독성이 깨짐. 5·10·15·30·60만.
15
+ - `minTime`/`maxTime`은 "HH:MM" 문자열만. Date 객체·epoch 금지.
16
+ - `panelLayout="grid"`를 popover 모드(`inline=false`)에서 쓰지 않는다 — 그리드는 inline 전용.
17
+ - Input처럼 생겼다고 `<Input type="time">`을 섞어쓰지 않는다 — 네이티브 time input은 축·토큰이 적용 안 됨.
18
+
19
+ ### Props 시그니처
20
+ ```tsx
21
+ <TimePicker
22
+ value?={TimeValue | null} // { hours, minutes }
23
+ onChange?={(v: TimeValue | null) => void}
24
+ step?={5 | 10 | 15 | 30 | 60} // default: 30
25
+ format?="24h | 12h" // default: 24h
26
+ minTime?={string} // "HH:MM" (24h, inclusive)
27
+ maxTime?={string} // "HH:MM" (24h, inclusive)
28
+ disabled?={boolean}
29
+ size?="sm | md | lg" // default: md
30
+ variant?="outline | filled | line" // default: outline
31
+ inline?={boolean} // popover 없이 패널만
32
+ panelLayout?="columns | grid" // default: columns
33
+ gridCols?={4 | 6 | 8} // grid 모드 열 수
34
+ splitMeridiem?={boolean} // grid를 AM/PM 2섹션
35
+ quickOptions?={QuickOption[] | boolean}
36
+ aria-label?={string}
37
+ />
38
+ ```
39
+
40
+ ### 조합 예시
41
+ ```tsx
42
+ // 폼 — 시작·종료 시간
43
+ <TimePicker variant="outline" step={15} value={start} onChange={setStart} />
44
+ <TimePicker variant="outline" step={15} value={end} onChange={setEnd} minTime="09:00" />
45
+
46
+ // 예약 슬롯 그리드 (오전·오후 분리)
47
+ <TimePicker
48
+ inline
49
+ panelLayout="grid"
50
+ step={30}
51
+ splitMeridiem
52
+ gridCols={6}
53
+ value={slot}
54
+ onChange={setSlot}
55
+ />
56
+
57
+ // 알람 — 지금부터 15/30/60분 후
58
+ <TimePicker
59
+ variant="line"
60
+ quickOptions
61
+ value={remind}
62
+ onChange={setRemind}
63
+ />
64
+ ```
65
+
66
+ ### 관련
67
+ - [[components/date-picker]] — 날짜 짝. 날짜+시간이 함께면 둘을 나란히.
68
+ - [[components/input]] — variant 규칙 동일
69
+ - [[components/select]] — 밀도 극한에서 Select로 대체 가능
@@ -0,0 +1,69 @@
1
+ # Toolbar
2
+
3
+ ### 원칙
4
+ IconButton 중심의 action 줄. 에디터·캔버스·갤러리 상단, 또는 "뜨 있는" 알약으로 콘텐츠 위에 떠서 액션을 모으는 자리. 단일 size — toolbar 안의 버튼은 전부 같은 사이즈여야 한다.
5
+
6
+ ### Do
7
+ - `variant="floating"` 기본 — bg-elevated + shadow-md + radius-xl, 콘텐츠 위에 떠있는 알약. 캔버스·에디터·이미지 뷰어의 상단 중앙.
8
+ - `variant="attached"`는 콘텐츠에 붙은 bar — transparent + 하단 hairline. 페이지 상단에 "탭 아래 액션 바"로 쓸 때.
9
+ - 관련 액션은 `<Toolbar.Group>`으로 묶는다 — 내부는 좁은 gap, 그룹 간은 `<Toolbar.Divider>`.
10
+ - 좌·우 정렬이 필요하면 `<Toolbar.Spacer>`로 공간 밀기 — flex:1.
11
+ - toolbar 안엔 IconButton size="sm"이 기본. floating variant는 size="md"까지도 OK.
12
+
13
+ ### Don't
14
+ - toolbar 안에 label 긴 Button을 섞지 않는다 — IconButton + Tooltip이 toolbar 패턴. 긴 label이 필요하면 그 자리는 toolbar가 아니라 액션 영역.
15
+ - 두 Toolbar를 세로로 쌓지 않는다 — 하나의 toolbar에 Group·Divider로 구분. 쌓으면 밀도·시선 둘 다 깨짐.
16
+ - `attached` variant에 shadow·radius-xl를 수동으로 추가하지 않는다 — floating을 쓰면 됨.
17
+ - Group 안에 또 Group을 중첩하지 않는다 — flat 2단(Toolbar > Group > IconButton).
18
+
19
+ ### Props 시그니처
20
+ ```tsx
21
+ <Toolbar
22
+ variant?="floating | attached" // default: floating
23
+ stretch?={boolean} // default: variant로 자동 (attached=true, floating=false)
24
+ role?={string} // default: "toolbar"
25
+ >
26
+ {children}
27
+ </Toolbar>
28
+ ```
29
+
30
+ Compound sub:
31
+ - `<Toolbar.Group>` — 관련 액션 묶음 (role="group")
32
+ - `<Toolbar.Divider>` — 세로 hairline
33
+ - `<Toolbar.Spacer>` — flex:1 공간 밀기
34
+
35
+ ### 조합 예시
36
+ ```tsx
37
+ // 에디터 — floating 알약, 좌/중/우 섹션
38
+ <Toolbar variant="floating">
39
+ <Toolbar.Group>
40
+ <IconButton size="sm" variant="ghost" aria-label="실행 취소" icon={<Icon name="arrow-u-up-left" />} />
41
+ <IconButton size="sm" variant="ghost" aria-label="다시 실행" icon={<Icon name="arrow-u-up-right" />} />
42
+ </Toolbar.Group>
43
+ <Toolbar.Divider />
44
+ <Toolbar.Group>
45
+ <IconButton size="sm" variant="ghost" aria-label="굵게" icon={<Icon name="text-b" />} />
46
+ <IconButton size="sm" variant="ghost" aria-label="기울임" icon={<Icon name="text-italic" />} />
47
+ </Toolbar.Group>
48
+ <Toolbar.Spacer />
49
+ <Toolbar.Group>
50
+ <IconButton size="sm" variant="ghost" aria-label="공유" icon={<Icon name="share" />} />
51
+ </Toolbar.Group>
52
+ </Toolbar>
53
+
54
+ // 페이지 상단 — attached, 콘텐츠에 붙음
55
+ <Toolbar variant="attached">
56
+ <Toolbar.Group>
57
+ <Button size="sm" variant="ghost" leftIcon={<Icon name="plus" />}>새로 만들기</Button>
58
+ <Button size="sm" variant="ghost" leftIcon={<Icon name="funnel" />}>필터</Button>
59
+ </Toolbar.Group>
60
+ <Toolbar.Spacer />
61
+ <IconButton size="sm" variant="ghost" aria-label="보기 전환" icon={<Icon name="list" />} />
62
+ </Toolbar>
63
+ ```
64
+
65
+ ### 관련
66
+ - [[components/icon-button]] — toolbar 안 주 버튼
67
+ - [[components/button]] — label 짧은 액션
68
+ - [[components/tooltip]] — IconButton엔 Tooltip 필수
69
+ - [[components/breadcrumb]] — 페이지 상단에선 Breadcrumb + Toolbar 조합이 흔함
@@ -6,7 +6,7 @@
6
6
  ### Do
7
7
  - 기본 구성: `shape=circular-drop, fill=translucent, tail=end` — 레퍼런스 톤.
8
8
  - 액션은 `[copy, edit]` 기본. `actionsVisibility="show" | "hover"`로 상시/호버.
9
- - 첨부 있으면 `before` slot에 [[components/attachment-thumb]] / [[components/attachment-chip]] 자유 조합. 상위에서 레이아웃 책임.
9
+ - 첨부 있으면 `before` slot에 [[components/attachment-thumb]] / [[components/attachment-item]] 자유 조합. 상위에서 레이아웃 책임.
10
10
  - 매우 긴 글은 bubble이 max-width 안에서 wrap, 그보다 넘으면 자연스레 아래로.
11
11
 
12
12
  ### Don't
@@ -22,7 +22,7 @@ shape 애매하면 `circular-drop`, fill 애매하면 `translucent`, visibility
22
22
  - [[components/bubble]] — 담는 그릇
23
23
  - [[components/message-actions]] — 액션 바
24
24
  - [[components/ai-message]] — 짝 컴포넌트
25
- - [[components/attachment-thumb]] / [[components/attachment-chip]] — before slot 조합
25
+ - [[components/attachment-thumb]] / [[components/attachment-item]] — before slot 조합
26
26
 
27
27
  ### Props 시그니처
28
28
  ```tsx