@godxjp/ui 20.2.1 → 22.0.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 (141) hide show
  1. package/dist/app/app-provider.js +2 -1
  2. package/dist/components/data-display/badge.d.ts +28 -1
  3. package/dist/components/data-display/badge.js +2 -0
  4. package/dist/components/data-display/chat-bubble.d.ts +30 -0
  5. package/dist/components/data-display/chat-bubble.js +229 -0
  6. package/dist/components/data-display/data-table.d.ts +18 -2
  7. package/dist/components/data-display/data-table.js +4 -0
  8. package/dist/components/data-display/descriptions.js +4 -1
  9. package/dist/components/data-display/index.d.ts +5 -3
  10. package/dist/components/data-display/index.js +5 -2
  11. package/dist/components/data-display/popover.d.ts +19 -1
  12. package/dist/components/data-display/popover.js +12 -3
  13. package/dist/components/data-display/progress.d.ts +37 -0
  14. package/dist/components/data-display/progress.js +57 -5
  15. package/dist/components/data-display/scroll-area.js +26 -1
  16. package/dist/components/data-display/tree.d.ts +8 -0
  17. package/dist/components/data-display/tree.js +426 -0
  18. package/dist/components/data-entry/chat-composer.d.ts +50 -0
  19. package/dist/components/data-entry/chat-composer.js +163 -0
  20. package/dist/components/data-entry/chat-suggestion.d.ts +28 -0
  21. package/dist/components/data-entry/chat-suggestion.js +285 -0
  22. package/dist/components/data-entry/date-picker.d.ts +10 -3
  23. package/dist/components/data-entry/date-picker.js +503 -238
  24. package/dist/components/data-entry/form-field.d.ts +1 -1
  25. package/dist/components/data-entry/form-field.js +4 -1
  26. package/dist/components/data-entry/form.js +10 -1
  27. package/dist/components/data-entry/index.d.ts +4 -6
  28. package/dist/components/data-entry/index.js +4 -6
  29. package/dist/components/data-entry/label.d.ts +14 -1
  30. package/dist/components/data-entry/label.js +6 -1
  31. package/dist/components/data-entry/radio.d.ts +27 -6
  32. package/dist/components/data-entry/radio.js +88 -43
  33. package/dist/components/data-entry/search-select.js +8 -1
  34. package/dist/components/data-entry/select.js +2 -0
  35. package/dist/components/data-entry/switch.d.ts +6 -3
  36. package/dist/components/data-entry/switch.js +34 -11
  37. package/dist/components/data-entry/textarea.js +3 -1
  38. package/dist/components/data-entry/tree-utils.d.ts +10 -48
  39. package/dist/components/data-entry/tree-utils.js +1 -154
  40. package/dist/components/layout/mobile-shell.d.ts +1 -1
  41. package/dist/components/layout/mobile-shell.js +2 -0
  42. package/dist/components/navigation/menubar.d.ts +10 -3
  43. package/dist/components/navigation/steps.d.ts +1 -1
  44. package/dist/components/navigation/steps.js +3 -1
  45. package/dist/components/navigation/tabs-scroll.d.ts +27 -0
  46. package/dist/components/navigation/tabs-scroll.js +52 -1
  47. package/dist/components/navigation/tabs.d.ts +2 -2
  48. package/dist/components/navigation/tabs.js +88 -23
  49. package/dist/components/ui/index.d.ts +0 -1
  50. package/dist/components/ui/index.js +0 -1
  51. package/dist/components/ui/segmented.d.ts +15 -1
  52. package/dist/components/ui/segmented.js +13 -6
  53. package/dist/i18n/messages/en.json +48 -2
  54. package/dist/i18n/messages/ja.json +46 -2
  55. package/dist/i18n/messages/vi.json +47 -2
  56. package/dist/lib/datetime/picker-format.d.ts +18 -0
  57. package/dist/lib/datetime/picker-format.js +35 -1
  58. package/dist/lib/field-a11y.d.ts +12 -0
  59. package/dist/lib/field-a11y.js +14 -1
  60. package/dist/lib/tree.d.ts +53 -0
  61. package/dist/lib/tree.js +155 -0
  62. package/dist/props/components/data-display.prop.d.ts +194 -1
  63. package/dist/props/components/data-entry.prop.d.ts +307 -99
  64. package/dist/props/components/index.d.ts +1 -1
  65. package/dist/props/components/layout.prop.d.ts +12 -1
  66. package/dist/props/components/navigation.prop.d.ts +76 -1
  67. package/dist/props/registry.d.ts +156 -19
  68. package/dist/props/registry.js +218 -18
  69. package/dist/props/vocabulary/data.prop.d.ts +12 -0
  70. package/dist/props/vocabulary/index.d.ts +2 -2
  71. package/dist/props/vocabulary/layout.prop.d.ts +11 -0
  72. package/dist/styles/badge-layout.css +4 -0
  73. package/dist/styles/card-layout.css +6 -6
  74. package/dist/styles/control.css +22 -6
  75. package/dist/styles/data-display-layout.css +385 -68
  76. package/dist/styles/data-entry-layout.css +64 -0
  77. package/dist/styles/focus-ring.css +4 -2
  78. package/dist/styles/navigation-layout.css +25 -0
  79. package/dist/styles/shell-layout.css +7 -1
  80. package/dist/styles/table-layout.css +34 -0
  81. package/dist/tokens/base.css +3 -0
  82. package/dist/tokens/components/chat-bubble.css +36 -0
  83. package/dist/tokens/components/chat-composer.css +19 -0
  84. package/dist/tokens/components/data-display.css +9 -6
  85. package/dist/tokens/components/descriptions.css +4 -0
  86. package/dist/tokens/components/navigation.css +6 -0
  87. package/dist/tokens/components/segmented.css +0 -2
  88. package/dist/tokens/components/shell.css +3 -0
  89. package/dist/tokens/components/table.css +3 -0
  90. package/dist/tokens/components/tree.css +27 -0
  91. package/dist/tokens/foundation.css +7 -0
  92. package/docs/COMPONENTS.md +14 -14
  93. package/docs/CONSUMER-RULES.md +24 -1
  94. package/docs/FRAME-COVERAGE-LEDGER.md +1 -1
  95. package/docs/FRAME-COVERAGE-REPORT.md +9 -7
  96. package/docs/TOKENS.md +65 -0
  97. package/docs/data-display/badge.tsx +37 -0
  98. package/docs/data-display/chat-bubble.tsx +397 -0
  99. package/docs/data-display/data-table/index.tsx +18 -0
  100. package/docs/data-display/popover.tsx +41 -1
  101. package/docs/data-display/progress.tsx +28 -0
  102. package/docs/data-display/timeline.tsx +46 -0
  103. package/docs/data-display/tree.tsx +394 -0
  104. package/docs/data-entry/calendar.tsx +2 -2
  105. package/docs/data-entry/chat-composer.tsx +464 -0
  106. package/docs/data-entry/chat-suggestion.tsx +301 -0
  107. package/docs/data-entry/date-picker.tsx +143 -3
  108. package/docs/data-entry/form/examples/invoice-form.tsx +2 -2
  109. package/docs/data-entry/form-field/examples/a11y-contract.tsx +3 -5
  110. package/docs/data-entry/form.tsx +4 -4
  111. package/docs/data-entry/number-input.tsx +31 -0
  112. package/docs/data-entry/segmented.tsx +103 -1
  113. package/docs/layout/mobile-shell.tsx +5 -0
  114. package/docs/navigation/tabs.tsx +149 -0
  115. package/docs/roadmap/ai-chat-components.md +207 -0
  116. package/docs/roadmap/antd-parity.md +155 -0
  117. package/docs/roadmap/badge-tag-chip-count.md +172 -0
  118. package/docs/roadmap/list-masonry.md +159 -0
  119. package/docs/roadmap/parity-audit-data-display-feedback.md +567 -0
  120. package/docs/roadmap/parity-audit-data-entry.md +357 -0
  121. package/docs/roadmap/parity-audit-layout-navigation-general.md +464 -0
  122. package/docs/roadmap/parity-backlog.md +78 -0
  123. package/docs/roadmap/tree-components.md +151 -0
  124. package/docs/showcase/acme-portal.tsx +7 -0
  125. package/docs/showcase/table-tree-rows.tsx +4 -4
  126. package/package.json +13 -23
  127. package/scripts/ui-audit.mjs +17 -4
  128. package/dist/components/data-display/tree-list.d.ts +0 -13
  129. package/dist/components/data-display/tree-list.js +0 -26
  130. package/dist/components/data-entry/date-range-picker.d.ts +0 -11
  131. package/dist/components/data-entry/date-range-picker.js +0 -349
  132. package/dist/components/data-entry/month-picker.d.ts +0 -10
  133. package/dist/components/data-entry/month-picker.js +0 -241
  134. package/dist/components/data-entry/month-range-picker.d.ts +0 -10
  135. package/dist/components/data-entry/month-range-picker.js +0 -301
  136. package/dist/components/ui/date-range-picker.d.ts +0 -1
  137. package/dist/components/ui/date-range-picker.js +0 -2
  138. package/docs/data-display/tree-list.tsx +0 -107
  139. package/docs/data-entry/date-range-picker.tsx +0 -175
  140. package/docs/data-entry/month-picker.tsx +0 -101
  141. package/docs/data-entry/month-range-picker.tsx +0 -115
@@ -2,7 +2,7 @@ import { useState } from "react";
2
2
 
3
3
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display";
4
4
  import { FormField, Segmented } from "@godxjp/ui/data-entry";
5
- import { Text } from "@godxjp/ui/general";
5
+ import { Text, VisuallyHidden } from "@godxjp/ui/general";
6
6
  import { Flex, PageContainer } from "@godxjp/ui/layout";
7
7
  import { CalendarDays, Columns3, LayoutList, Monitor, Moon, Sun } from "lucide-react";
8
8
 
@@ -84,6 +84,108 @@ export default function Demo() {
84
84
  </CardContent>
85
85
  </Card>
86
86
 
87
+ <Card>
88
+ <CardHeader>
89
+ <CardTitle level={2}>size · vertical · block</CardTitle>
90
+ <CardDescription>
91
+ size は 3 段(sm / md / lg)。トラックの高さは --control-height そのもので、
92
+ MobileShell のようにその変数をスコープする領域では自動的にタッチ段(44px)になります。
93
+ vertical では 1 行ぶんが 1 コントロールの高さになります — 横 1 行のときだけトラック
94
+ 内側の余白を引くので、積んだときは引きません。block は幅いっぱいに広げます。
95
+ </CardDescription>
96
+ </CardHeader>
97
+ <CardContent>
98
+ <Flex direction="col" gap="md" align="start">
99
+ <Segmented
100
+ aria-label="サイズ sm"
101
+ size="sm"
102
+ defaultValue="a"
103
+ options={[
104
+ { value: "a", label: "小" },
105
+ { value: "b", label: "中" },
106
+ { value: "c", label: "大" },
107
+ ]}
108
+ />
109
+ <Segmented
110
+ aria-label="サイズ md"
111
+ defaultValue="b"
112
+ options={[
113
+ { value: "a", label: "小" },
114
+ { value: "b", label: "中" },
115
+ { value: "c", label: "大" },
116
+ ]}
117
+ />
118
+ <Segmented
119
+ aria-label="サイズ lg"
120
+ size="lg"
121
+ defaultValue="c"
122
+ options={[
123
+ { value: "a", label: "小" },
124
+ { value: "b", label: "中" },
125
+ { value: "c", label: "大" },
126
+ ]}
127
+ />
128
+ <Segmented
129
+ aria-label="縦積み"
130
+ vertical
131
+ block
132
+ defaultValue="ok"
133
+ options={[
134
+ { value: "ok", label: "実施" },
135
+ { value: "warn", label: "要改善" },
136
+ { value: "none", label: "未実施" },
137
+ ]}
138
+ />
139
+ </Flex>
140
+ </CardContent>
141
+ </Card>
142
+
143
+ <Card>
144
+ <CardHeader>
145
+ <CardTitle level={2}>短い記号を出して、長い名前を読ませる</CardTitle>
146
+ <CardDescription>
147
+ `label` は ReactNode なので、見える記号を aria-hidden にして VisuallyHidden に
148
+ 読み上げ名を置けます。専用の prop は要りません。○ と読み上げられても意味が伝わらない
149
+ ため、色や記号だけに意味を持たせないという WCAG 1.4.1 の要求そのものです。
150
+ </CardDescription>
151
+ </CardHeader>
152
+ <CardContent>
153
+ <Segmented
154
+ aria-label="監査結果"
155
+ defaultValue="ok"
156
+ options={[
157
+ {
158
+ value: "ok",
159
+ label: (
160
+ <>
161
+ <span aria-hidden="true">○</span>
162
+ <VisuallyHidden>実施</VisuallyHidden>
163
+ </>
164
+ ),
165
+ },
166
+ {
167
+ value: "warn",
168
+ label: (
169
+ <>
170
+ <span aria-hidden="true">△</span>
171
+ <VisuallyHidden>要改善</VisuallyHidden>
172
+ </>
173
+ ),
174
+ },
175
+ {
176
+ value: "none",
177
+ label: (
178
+ <>
179
+ <span aria-hidden="true">×</span>
180
+ <VisuallyHidden>未実施</VisuallyHidden>
181
+ </>
182
+ ),
183
+ },
184
+ ]}
185
+ />
186
+ </CardContent>
187
+ </Card>
188
+
87
189
  <Card>
88
190
  <CardHeader>
89
191
  <CardTitle level={2}>フォームの中で</CardTitle>
@@ -32,6 +32,11 @@ export default function Demo() {
32
32
 
33
33
  return (
34
34
  <MobileShell
35
+ // `width="phone"` because this frame is READ ON A DESKTOP. The shell's own docstring already
36
+ // named this case on the block axis (`height="fill"` for "a phone view embedded in a wider
37
+ // page"); the inline axis had no answer until now, so this page drew a 1232px-wide handheld
38
+ // app with its four tab-bar destinations spread across the screen.
39
+ width="phone"
35
40
  statusBar={
36
41
  <>
37
42
  <Text size="sm" weight="medium" tabular>
@@ -1,5 +1,7 @@
1
1
  import { useState } from "react";
2
2
 
3
+ import { Trash2 } from "lucide-react";
4
+
3
5
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display";
4
6
  import { Button, Text } from "@godxjp/ui/general";
5
7
  import { Flex, PageContainer } from "@godxjp/ui/layout";
@@ -44,8 +46,45 @@ const journalItems = [
44
46
  },
45
47
  ];
46
48
 
49
+ /**
50
+ * 20 saved views with real Japanese labels — a strip that is wider than 1920px, so it overflows at
51
+ * EVERY width the browser gates sweep. `scripts/check-tabs-overflow-menu.mjs` measures this frame;
52
+ * it fails if the strip ever stops overflowing, because a gate over a bar that fits proves nothing.
53
+ */
54
+ const savedViewItems = [
55
+ { value: "all", label: "すべての仕訳", content: <Text as="p">全 1,284 件</Text> },
56
+ { value: "pending", label: "未承認の仕訳", content: <Text as="p">未承認 2 件</Text> },
57
+ { value: "posted", label: "承認済の仕訳", content: <Text as="p">承認済 48 件</Text> },
58
+ { value: "voided", label: "取消済の仕訳", content: <Text as="p">取消済 3 件</Text> },
59
+ { value: "returned", label: "差戻しの仕訳", content: <Text as="p">差戻し 5 件</Text> },
60
+ { value: "draft", label: "下書きの仕訳", content: <Text as="p">下書き 12 件</Text> },
61
+ { value: "recurring", label: "定期仕訳のひな形", content: <Text as="p">ひな形 7 件</Text> },
62
+ { value: "accrual", label: "未払費用の計上", content: <Text as="p">未払費用 9 件</Text> },
63
+ { value: "prepaid", label: "前払費用の振替", content: <Text as="p">前払費用 4 件</Text> },
64
+ { value: "payroll", label: "給与支払の仕訳", content: <Text as="p">給与 3 件</Text> },
65
+ { value: "tax", label: "消費税の集計", content: <Text as="p">消費税 6 件</Text> },
66
+ { value: "closing", label: "決算整理の仕訳", content: <Text as="p">決算整理 11 件</Text> },
67
+ { value: "audit", label: "監査対象の仕訳", content: <Text as="p">監査対象 8 件</Text> },
68
+ {
69
+ value: "archived",
70
+ label: "アーカイブ済の仕訳",
71
+ content: <Text as="p">アーカイブ 214 件</Text>,
72
+ },
73
+ { value: "fx", label: "外貨建の換算差額", content: <Text as="p">換算差額 2 件</Text> },
74
+ { value: "intercompany", label: "関係会社間の取引", content: <Text as="p">関係会社 15 件</Text> },
75
+ {
76
+ value: "fixed-asset",
77
+ label: "固定資産の減価償却",
78
+ content: <Text as="p">減価償却 22 件</Text>,
79
+ },
80
+ { value: "inventory", label: "棚卸資産の評価替", content: <Text as="p">評価替 6 件</Text> },
81
+ { value: "bank", label: "銀行勘定の照合", content: <Text as="p">未照合 4 件</Text> },
82
+ { value: "reversal", label: "翌期首の振戻し", content: <Text as="p">振戻し 9 件</Text> },
83
+ ];
84
+
47
85
  export default function Demo() {
48
86
  const [activeTab, setActiveTab] = useState("pending");
87
+ const [reopened, setReopened] = useState(0);
49
88
  const [editableTabs, setEditableTabs] = useState([
50
89
  { value: "je-0042", label: "JE-0042", content: <Text as="p">売上計上 ¥480,000</Text> },
51
90
  { value: "je-0043", label: "JE-0043", content: <Text as="p">仕入計上 ¥120,000</Text> },
@@ -367,6 +406,116 @@ export default function Demo() {
367
406
  </CardContent>
368
407
  </Card>
369
408
 
409
+ {/* antd `more` — overflow="menu" beside the default overflow="scroll" */}
410
+ <Card>
411
+ <CardHeader>
412
+ <CardTitle level={2}>overflow · scroll(既定) / menu</CardTitle>
413
+ <CardDescription>
414
+ antd の more をこのライブラリの overflow 語彙に写したもの。既定の scroll
415
+ は今までどおり、帯が自分の水平オーバーフローをスクロールする。menu
416
+ はそれに加えて、帯の外に「他のタブ」ボタンを出し、いま見えていないタブだけを並べる ·
417
+ antd と違いタブは帯から取り除かれない(tablist はタブ以外を持てず、display:none
418
+ のタブはロービングフォーカスを受け取れないため · WAI-ARIA APG)。
419
+ </CardDescription>
420
+ </CardHeader>
421
+ <CardContent>
422
+ <Flex direction="col" gap="lg">
423
+ <Flex direction="col" gap="sm">
424
+ <Text as="p" size="sm" tone="muted">
425
+ overflow=&quot;scroll&quot;(既定)
426
+ </Text>
427
+ <Tabs
428
+ id="antd-overflow-scroll"
429
+ defaultValue="all"
430
+ variant="line"
431
+ items={savedViewItems}
432
+ />
433
+ </Flex>
434
+ <Flex direction="col" gap="sm">
435
+ <Text as="p" size="sm" tone="muted">
436
+ overflow=&quot;menu&quot;
437
+ </Text>
438
+ <Tabs
439
+ id="antd-overflow-menu"
440
+ defaultValue="all"
441
+ variant="line"
442
+ overflow="menu"
443
+ items={savedViewItems}
444
+ />
445
+ </Flex>
446
+ </Flex>
447
+ </CardContent>
448
+ </Card>
449
+
450
+ {/* antd onTabClick / removeIcon / Tab.forceRender */}
451
+ <Card>
452
+ <CardHeader>
453
+ <CardTitle level={2}>onTabClick · closeIcon · forceRender</CardTitle>
454
+ <CardDescription>
455
+ onTabClick は antd の onTabClick で、ポインタで押されたときだけ発火し DOM の
456
+ MouseEvent を渡す(キーボードは activationMode=&quot;manual&quot;
457
+ でフォーカス移動と選択が分かれるため発火しない · 選択は onValueChange
458
+ が担当)。closeIcon は antd の removeIcon で、タブ全体の既定グリフを差し替える(item
459
+ 側の closeIcon が優先)。item の forceRender は antd の Tab.forceRender
460
+ で、そのパネルだけを先にマウントし、他のタブに 切り替えても保持する。
461
+ </CardDescription>
462
+ </CardHeader>
463
+ <CardContent>
464
+ <Flex direction="col" gap="md">
465
+ <Text as="p" size="sm" tone="muted">
466
+ 同じタブを押し直した回数: {reopened}
467
+ </Text>
468
+ <Tabs
469
+ id="antd-tab-click"
470
+ defaultValue="pending"
471
+ variant="line"
472
+ onTabClick={(value) => {
473
+ if (value === activeTab) setReopened((count) => count + 1);
474
+ setActiveTab(value);
475
+ }}
476
+ items={[
477
+ {
478
+ value: "pending",
479
+ label: "未承認",
480
+ content: <Text as="p">未承認の仕訳が 2 件あります。</Text>,
481
+ },
482
+ {
483
+ value: "chart",
484
+ label: "推移グラフ",
485
+ // The one panel that must survive a tab switch — antd Tab.forceRender.
486
+ forceRender: true,
487
+ content: <Text as="p">月次推移: 4 月 ¥3,120,000 · 5 月 ¥4,820,000</Text>,
488
+ },
489
+ {
490
+ value: "posted",
491
+ label: "承認済",
492
+ content: <Text as="p">当期承認済: 48 件</Text>,
493
+ },
494
+ ]}
495
+ />
496
+ <Tabs
497
+ id="antd-remove-icon"
498
+ variant="editable-card"
499
+ defaultValue="je-0042"
500
+ closeIcon={<Trash2 aria-hidden="true" />}
501
+ onEdit={() => undefined}
502
+ items={[
503
+ {
504
+ value: "je-0042",
505
+ label: "JE-0042",
506
+ content: <Text as="p">売上計上 ¥480,000</Text>,
507
+ },
508
+ {
509
+ value: "je-0043",
510
+ label: "JE-0043",
511
+ content: <Text as="p">仕入計上 ¥120,000</Text>,
512
+ },
513
+ ]}
514
+ />
515
+ </Flex>
516
+ </CardContent>
517
+ </Card>
518
+
370
519
  <Card>
371
520
  <CardHeader>
372
521
  <CardTitle level={2}>tabPlacement · size · centered</CardTitle>
@@ -0,0 +1,207 @@
1
+ # AI / chatbot components — normalized spec
2
+
3
+ > **Reference:** Ant Design X (<https://x.ant.design/components/overview>) for *capabilities*.
4
+ > **Naming:** when a component's identity is unclear, look it up at
5
+ > <https://namethatui.com/?platform=web> before inventing a name.
6
+ > **Contract:** `.claude/skills/godxjp-ui-component/SKILL.md` is the hard gate — MCP-first, real
7
+ > primitives only, `t()` + `Intl`, WAI-ARIA APG + WCAG 2.2 AA + vitest-axe, logical CSS, controlled
8
+ > vocabulary, semantic tokens, MCP catalog entry, real-screen docs page.
9
+
10
+ The library has **zero** chat/AI components today (verified against `mcp/src/data/components.ts` —
11
+ 127 entries, none of them conversational). This document decides which of Ant Design X's surface
12
+ becomes a framework component and which is a composition, so no agent has to re-litigate it.
13
+
14
+ ## 1. Scope decision — GATE 0 applied to every Ant Design X component
15
+
16
+ `docs/COMPOSITION-VS-COMPONENT.md` C1–C7. **Any FAIL ⇒ composition, never `src/components/`.**
17
+
18
+ | Ant Design X | godx name | Group | C1 | C2 | C3 | C4 | C5 | C6 | C7 | Verdict |
19
+ | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
20
+ | `Bubble` | **`ChatBubble`** | `data-display` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | **Framework component** |
21
+ | `Bubble.List` | **`ChatBubbleList`** | `data-display` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | **Framework component** |
22
+ | `Sender` | **`ChatComposer`** | `data-entry` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | **Framework component** |
23
+ | `Suggestion` | **`ChatSuggestion`** | `data-entry` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | **Framework component** |
24
+ | `Conversations` | **`ConversationList`** | `navigation` | ✅ | ✅ | ⚠️ | ✅ | ✅ | ✅ | ✅ | **Verify C3 first** — see §6 |
25
+ | `ThoughtChain` / `Think` | **`ThoughtChain`** | `data-display` | ✅ | ⚠️ | ⚠️ | ✅ | ✅ | ✅ | ⚠️ | **Verify C2/C3 first** — see §6 |
26
+ | `Prompts` | — | — | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | **Composition** — `ResponsiveGrid` + `Card` + `Button` |
27
+ | `Welcome` | — | — | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | **Composition** — static block, `EmptyState`-shaped |
28
+ | `Actions` | — | — | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | **Composition** — `Button` + `DropdownMenu` |
29
+ | `Attachments` / `FileCard` | — | — | ✅ | ✅ | ❌ | — | — | — | — | **Extend `Upload`** — duplicating it is an instant reject |
30
+ | `CodeHighlighter` | — | — | — | — | ❌ | — | — | — | — | **Use `CodeBlock`** |
31
+ | `Sources` | — | — | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | **Composition** — `ListRow` + `Badge` |
32
+ | `XProvider` | — | — | — | — | ❌ | — | — | — | — | **Use `AppProvider`** |
33
+ | `Mermaid` | — | — | ❌ | — | — | — | — | — | ❌ | **Out of scope** |
34
+ | `Folder/File Tree` | — | — | — | — | ❌ | — | — | — | — | **Covered by `Tree`** — see `docs/roadmap/tree-components.md` |
35
+
36
+ **v1 build set: `ChatBubble`, `ChatBubbleList`, `ChatComposer`, `ChatSuggestion`** (4 components,
37
+ all 7/7 PASS). `ConversationList` and `ThoughtChain` are conditional — §6.
38
+ Everything else ships as a `docs/` showcase composition, not library code.
39
+
40
+ ---
41
+
42
+ ## 2. `ChatBubble` — one message in a conversation
43
+
44
+ **Group:** `data-display` · **File:** `src/components/data-display/chat-bubble.tsx`
45
+
46
+ | Ant Design X | **godx `ChatBubble`** |
47
+ | --- | --- |
48
+ | `content` | `children` (a `ReactNode`; do not take an HTML string) |
49
+ | `placement: 'start' \| 'end'` | `placement: 'start' \| 'end'` (logical — flips under RTL) |
50
+ | `variant: 'filled' \| 'borderless' \| 'outlined' \| 'shadow'` | `variant: 'filled' \| 'borderless' \| 'outlined'` (drop `shadow`; the design system is 1px-border, no drop shadows) |
51
+ | `avatar` | `avatar` (a real `Avatar` node, never a styled div) |
52
+ | `header` / `footer` | `header` / `footer` |
53
+ | `loading` | `loading` (renders `Skeleton`, `aria-busy="true"`) |
54
+ | `typing` | `typing: boolean \| { step?: number; interval?: number }` |
55
+ | `messageRender` | *dropped* — the caller passes rendered `children` |
56
+ | `shape` | *dropped* — corner radius is a token |
57
+ | — | `size: 'xs' \| 'sm' \| 'lg'` (`md` default) |
58
+ | — | `tone` for status-tinted bubbles (error/warning), never colour-only |
59
+
60
+ **Semantics.** The bubble is an `<article>` inside the list's feed. Give it an accessible name from
61
+ its `header` (the author). The typing animation MUST respect `prefers-reduced-motion: reduce` —
62
+ render the full text immediately, no animation. Streaming text goes in an `aria-live="polite"`
63
+ region owned by `ChatBubbleList`, **not** per-bubble (a live region per bubble floods a screen
64
+ reader). Decorative avatars `aria-hidden`.
65
+
66
+ **Tokens** (`src/tokens/components/chat-bubble.css`): `--chat-bubble-background`,
67
+ `--chat-bubble-foreground`, `--chat-bubble-border-color`, `--chat-bubble-radius`,
68
+ `--chat-bubble-gap`, `--chat-bubble-max-inline-size`, `--chat-bubble-avatar-size`.
69
+ Role-mirror knobs default to `initial` (`docs/TOKENS.md`).
70
+
71
+ **i18n:** `chat.bubble.typing`, `chat.bubble.loading`, `chat.bubble.you`, `chat.bubble.assistant`.
72
+ Timestamps via `Intl.DateTimeFormat`; "2 minutes ago" via `Intl.RelativeTimeFormat`; counted nouns
73
+ via `Intl.PluralRules` — never a template string.
74
+
75
+ ---
76
+
77
+ ## 3. `ChatBubbleList` — the message feed
78
+
79
+ **Group:** `data-display` · **File:** same module as `ChatBubble`
80
+
81
+ | Ant Design X `Bubble.List` | **godx `ChatBubbleList`** |
82
+ | --- | --- |
83
+ | `items` | `items: ChatMessageProp[]` |
84
+ | `roles` | `roles: Record<string, Partial<ChatBubbleProp>>` — per-role defaults (`user`, `assistant`, `system`) |
85
+ | `autoScroll` | `autoScroll` (default `true`) |
86
+
87
+ **The behavior that earns C2/C3** — none of it is composable from `ScrollArea` + `map`:
88
+
89
+ 1. **Stick-to-bottom.** Auto-scroll while the user is at the bottom; the moment they scroll up,
90
+ **stop** and surface a "jump to latest" affordance. Silently yanking a reader back to the bottom
91
+ mid-read is the classic chat-UI defect.
92
+ 2. **Streaming without layout thrash** — appended text must not re-scroll the whole feed.
93
+ 3. **One `aria-live="polite"` region** for the whole feed, announcing only the newest message.
94
+ 4. `role="log"` on the scroll container, `aria-label` via `t()`.
95
+ 5. Keyboard: the feed is focusable and scrollable with `PageUp`/`PageDown`/`Home`/`End`; bubbles
96
+ are not a roving-tabindex widget (interactive controls inside them keep normal tab order).
97
+
98
+ ---
99
+
100
+ ## 4. `ChatComposer` — the message input (Ant's `Sender`)
101
+
102
+ **Group:** `data-entry` · **File:** `src/components/data-entry/chat-composer.tsx`
103
+
104
+ Named for what the industry calls it (Slack/Discord "composer"); `Sender` describes the person,
105
+ not the control.
106
+
107
+ | Ant Design X `Sender` | **godx `ChatComposer`** |
108
+ | --- | --- |
109
+ | `value` / `onChange` | `value` / `defaultValue` / `onValueChange` (**controlled triad — mandatory**) |
110
+ | `onSubmit` | `onSubmit(value: string)` |
111
+ | `loading` + `onCancel` | `loading` + `onCancel` |
112
+ | `submitType: 'enter' \| 'shiftEnter'` | `submitType: 'enter' \| 'shiftEnter'` |
113
+ | `placeholder` | `placeholder` (through `t()` at the call site) |
114
+ | `disabled` / `readOnly` | `disabled` / `readOnly` |
115
+ | `header` (`Sender.Header`) | `header` slot |
116
+ | `prefix` / `footer` / `actions` | `prefix` / `footer` / `actions` slots |
117
+ | `allowSpeech` | **out of scope v1** — Web Speech API is not cross-browser; file a follow-up |
118
+ | `autoSize` | always auto-sizing; not a prop |
119
+ | — | `size: 'xs' \| 'sm' \| 'lg'` · `maxLength` · `status` |
120
+
121
+ **Rules.**
122
+ - Built on the real `Textarea` primitive and real `Button`s — **no raw `<textarea>`/`<button>`**.
123
+ - Auto-grow between a min and max height expressed in `--control-height` tiers, then scroll
124
+ inside. **No literal `height` and no `calc(var(--control-height) ± …)`** (`check:control-sizing`).
125
+ - `submitType="enter"`: `Enter` submits, `Shift+Enter` newline. `submitType="shiftEnter"`: the
126
+ inverse. IME composition must never submit — guard on `compositionstart`/`compositionend`
127
+ (this is the bug that breaks Japanese and Vietnamese input; it is not optional).
128
+ - Empty or whitespace-only input does not submit; the submit button is `disabled` with an
129
+ accessible name, not merely greyed out.
130
+ - While `loading`, the submit button becomes a cancel button — **one trailing action at a time**,
131
+ the same discipline as the picker trailing-action rule.
132
+ - The textarea is the semantic focus target; forward the `FormField` label/helper/error contract
133
+ onto it via `pickFieldA11y` / `useFieldIdentity` — do not reinvent it (`src/lib/field-a11y.ts`).
134
+
135
+ ---
136
+
137
+ ## 5. `ChatSuggestion` — trigger-character autocomplete over the composer
138
+
139
+ **Group:** `data-entry` · **File:** `src/components/data-entry/chat-suggestion.tsx`
140
+
141
+ | Ant Design X `Suggestion` | **godx `ChatSuggestion`** |
142
+ | --- | --- |
143
+ | `items` | `items: SuggestionItemProp[]` (supports one level of `children`) |
144
+ | `onSelect` | `onValueChange(value: string)` |
145
+ | `block` | *dropped* — width follows the anchor |
146
+ | render-prop `children({ onTrigger, onKeyDown })` | same shape — it wraps `ChatComposer` |
147
+ | — | `triggerCharacter` (default `'/'`) · `open` / `defaultOpen` / `onOpenChange` |
148
+
149
+ **Rules.** Compose the existing `Command` (cmdk) inside a `Popover` anchored to the composer — do
150
+ **not** hand-roll a listbox; `Command` already ships correct ARIA. What this component owns is the
151
+ part `Command` does not: detecting the trigger character in a textarea, tracking the query as the
152
+ caret moves, and closing on `Escape`/blur/word-break. `Escape` returns focus to the textarea and
153
+ leaves the typed text intact.
154
+
155
+ ---
156
+
157
+ ## 6. Conditional components — prove C3 before writing code
158
+
159
+ Do **not** start these until the ledger is recorded in the PR description.
160
+
161
+ **`ConversationList`** (Ant's `Conversations`). `NavList` already renders selectable rows with
162
+ `aria-current="page"`, an icon column and a badge; `ListRow` covers short entity lists. Before
163
+ building, answer in writing: what does a conversation list own that `NavList` does not?
164
+ The honest candidates are date **grouping** (Today / Yesterday / Last 7 days — via
165
+ `Intl.RelativeTimeFormat` + `Intl.DateTimeFormat`, never hand-rolled buckets), a **per-item
166
+ overflow menu**, and **inline rename**. If those land as `NavList` props instead, that is the
167
+ better outcome — extend `NavList` and close this out.
168
+
169
+ **`ThoughtChain`** (Ant's `ThoughtChain` / `Think`). `Timeline` already has a 3-state per-item
170
+ `status` (`done`/`current`/`pending`), an icon rail and ordinal/status variants; `Collapsible`
171
+ owns disclosure. `ThoughtChain` = `Timeline` + per-item collapsible body + a streaming/pending
172
+ state. Strongly prefer adding a `collapsible` affordance to `Timeline` over a new component.
173
+ Whichever way it goes, the reasoning body must be collapsible, and the running step must be
174
+ announced once via `aria-live="polite"` — not on every token.
175
+
176
+ ---
177
+
178
+ ## 7. Per-component definition of done
179
+
180
+ Every component in the build set ships **all** of:
181
+
182
+ 1. `src/components/<group>/<name>.tsx` — real primitives only, logical CSS, `t()` for every string
183
+ and `aria-label`, `ref` forwarded, `...props` spread, `className` + `id` accepted.
184
+ 2. `XProp` + `XProp as XProps` in `src/props/components/<group>.prop.ts`, **registered in
185
+ `src/props/registry.ts`**.
186
+ 3. `src/tokens/components/<name>.css` + an `@import` in `src/tokens/base.css`; names pass
187
+ `check:token-tiers`; control boxes come from the `--control-height` tier.
188
+ 4. Keys in `src/i18n/messages/en.json`, `vi.json`, `ja.json` — all three, no exceptions.
189
+ 5. Tests in `src/components/<group>/__tests__/`: a behavior test using `@testing-library/user-event`
190
+ **and** a `<name>.a11y.test.tsx` with `expectNoA11yViolations` at **0 violations**.
191
+ 6. An `mcp/src/data/components.ts` entry (props / usage / useCases / related / example / rules) —
192
+ `check:mcp-sync` and `check:mcp-orphans` must pass.
193
+ 7. A real-screen docs page under `docs/<group>/` (AppShell + PageContainer + real primitives), not
194
+ a bare `Card` snippet.
195
+ 8. Export from the group's `index.ts`.
196
+
197
+ Gates, then **only** the touched group's tests:
198
+
199
+ ```
200
+ pnpm typecheck && pnpm lint && pnpm run audit \
201
+ && pnpm check:prop-vocabulary && pnpm check:mcp-sync && pnpm check:mcp-orphans \
202
+ && pnpm check:token-tiers && pnpm check:control-sizing && pnpm check:example-imports
203
+ pnpm vitest run src/components/<group>/__tests__ --maxWorkers=2
204
+ ```
205
+
206
+ `pnpm test` and a bare `pnpm vitest run` are **forbidden** — 506 files / 3700+ tests, and several
207
+ agents on one machine takes the load past 90. The full suite is CI's job on the PR.