@astryxdesign/core 0.7.1-canary.ff40b78 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/dist/Button/Button.d.ts +1 -1
  3. package/dist/Button/Button.d.ts.map +1 -1
  4. package/dist/Button/Button.js +5 -2
  5. package/dist/Calendar/Calendar.d.ts.map +1 -1
  6. package/dist/Calendar/Calendar.js +191 -32
  7. package/dist/Chat/ChatComposerInput.d.ts +9 -0
  8. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  9. package/dist/Chat/ChatComposerInput.js +5 -0
  10. package/dist/Chat/ChatMessage.d.ts +11 -0
  11. package/dist/Chat/ChatMessage.d.ts.map +1 -1
  12. package/dist/Chat/ChatMessage.js +18 -9
  13. package/dist/Chat/useTriggerMenu.d.ts +9 -2
  14. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  15. package/dist/Chat/useTriggerMenu.js +222 -50
  16. package/dist/CodeBlock/CodeBlock.d.ts +10 -1
  17. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  18. package/dist/CodeBlock/CodeBlock.js +158 -38
  19. package/dist/CodeBlock/tokenizer.d.ts +39 -0
  20. package/dist/CodeBlock/tokenizer.d.ts.map +1 -1
  21. package/dist/CodeBlock/tokenizer.js +108 -0
  22. package/dist/CommandPalette/CommandPalette.d.ts +9 -5
  23. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  24. package/dist/CommandPalette/CommandPalette.js +38 -20
  25. package/dist/CommandPalette/CommandPaletteEmpty.d.ts +4 -3
  26. package/dist/CommandPalette/CommandPaletteEmpty.d.ts.map +1 -1
  27. package/dist/CommandPalette/CommandPaletteEmpty.js +4 -3
  28. package/dist/CommandPalette/CommandPaletteInput.d.ts +5 -4
  29. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  30. package/dist/CommandPalette/CommandPaletteInput.js +35 -18
  31. package/dist/CommandPalette/CommandPaletteItem.d.ts +11 -6
  32. package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
  33. package/dist/CommandPalette/CommandPaletteItem.js +8 -9
  34. package/dist/CommandPalette/CommandPaletteList.d.ts +4 -2
  35. package/dist/CommandPalette/CommandPaletteList.d.ts.map +1 -1
  36. package/dist/CommandPalette/CommandPaletteList.js +10 -5
  37. package/dist/ComplexSelector/ComplexSelector.d.ts +8 -5
  38. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  39. package/dist/ComplexSelector/ComplexSelector.js +12 -3
  40. package/dist/ContextMenu/ContextMenu.d.ts +3 -3
  41. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  42. package/dist/ContextMenu/ContextMenu.js +62 -48
  43. package/dist/DateInput/DateInput.js +1 -1
  44. package/dist/DateInput/MonthScroller.d.ts.map +1 -1
  45. package/dist/DateInput/MonthScroller.js +12 -4
  46. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  47. package/dist/DateRangeInput/DateRangeInput.js +12 -2
  48. package/dist/Dialog/Dialog.d.ts +1 -1
  49. package/dist/Dialog/Dialog.d.ts.map +1 -1
  50. package/dist/Dialog/Dialog.js +16 -5
  51. package/dist/Dialog/DialogHeader.d.ts +1 -1
  52. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  53. package/dist/Dialog/DialogHeader.js +7 -7
  54. package/dist/Divider/Divider.d.ts +1 -0
  55. package/dist/Divider/Divider.d.ts.map +1 -1
  56. package/dist/Divider/Divider.js +5 -4
  57. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  58. package/dist/DropdownMenu/DropdownMenu.js +161 -65
  59. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +7 -5
  60. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  61. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +12 -5
  62. package/dist/Field/InputClearButton.d.ts +8 -4
  63. package/dist/Field/InputClearButton.d.ts.map +1 -1
  64. package/dist/Field/InputClearButton.js +23 -7
  65. package/dist/FormLayout/FormLayout.d.ts.map +1 -1
  66. package/dist/FormLayout/FormLayout.js +1 -0
  67. package/dist/Grid/Grid.d.ts +13 -4
  68. package/dist/Grid/Grid.d.ts.map +1 -1
  69. package/dist/Grid/Grid.js +7 -1
  70. package/dist/HoverCard/useHoverCard.d.ts +1 -1
  71. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  72. package/dist/HoverCard/useHoverCard.js +67 -49
  73. package/dist/InputGroup/InputGroup.d.ts +2 -4
  74. package/dist/InputGroup/InputGroup.d.ts.map +1 -1
  75. package/dist/InputGroup/InputGroup.js +2 -4
  76. package/dist/InputGroup/groupStyles.d.ts +10 -2
  77. package/dist/InputGroup/groupStyles.d.ts.map +1 -1
  78. package/dist/InputGroup/groupStyles.js +19 -2
  79. package/dist/Layer/useLayerDismissal.d.ts +9 -9
  80. package/dist/Layer/useLayerDismissal.js +9 -9
  81. package/dist/Markdown/Markdown.d.ts.map +1 -1
  82. package/dist/Markdown/Markdown.js +25 -4
  83. package/dist/MobileNav/MobileNav.d.ts +7 -0
  84. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  85. package/dist/MobileNav/MobileNav.js +45 -2
  86. package/dist/MultiSelector/MultiSelector.d.ts +7 -4
  87. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  88. package/dist/MultiSelector/MultiSelector.js +24 -3
  89. package/dist/NumberInput/NumberInput.js +1 -1
  90. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  91. package/dist/PowerSearch/PowerSearchEditPopover.js +6 -0
  92. package/dist/Resizable/ResizeHandle.d.ts +1 -1
  93. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  94. package/dist/Resizable/ResizeHandle.js +83 -15
  95. package/dist/Resizable/useResizable.d.ts +23 -0
  96. package/dist/Resizable/useResizable.d.ts.map +1 -1
  97. package/dist/Resizable/useResizable.js +78 -0
  98. package/dist/Selector/Selector.d.ts +6 -3
  99. package/dist/Selector/Selector.d.ts.map +1 -1
  100. package/dist/Selector/Selector.js +28 -6
  101. package/dist/SideNav/SideNav.d.ts.map +1 -1
  102. package/dist/SideNav/SideNav.js +10 -2
  103. package/dist/Slider/Slider.d.ts.map +1 -1
  104. package/dist/Slider/Slider.js +18 -7
  105. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  106. package/dist/Tokenizer/Tokenizer.js +16 -4
  107. package/dist/Toolbar/Toolbar.d.ts +2 -1
  108. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  109. package/dist/Toolbar/Toolbar.js +3 -1
  110. package/dist/TreeList/TreeList.d.ts.map +1 -1
  111. package/dist/TreeList/TreeList.js +6 -3
  112. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  113. package/dist/TreeList/TreeListItem.js +7 -1
  114. package/dist/astryx.css +22 -0
  115. package/dist/hooks/useScrollOverflow.d.ts.map +1 -1
  116. package/dist/hooks/useScrollOverflow.js +55 -12
  117. package/dist/hooks/useTreeFocus.d.ts +21 -0
  118. package/dist/hooks/useTreeFocus.d.ts.map +1 -1
  119. package/dist/hooks/useTreeFocus.js +40 -2
  120. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  121. package/dist/i18n/generated-locales/en.generated.js +3 -0
  122. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  123. package/dist/i18n/generated-locales/pseudo.generated.js +3 -0
  124. package/dist/reset.a11y.chromium.spec.js +6 -1
  125. package/dist/reset.until-found.a11y.browser.spec.d.ts +2 -0
  126. package/dist/reset.until-found.a11y.browser.spec.d.ts.map +1 -0
  127. package/dist/reset.until-found.a11y.browser.spec.js +71 -0
  128. package/dist/theme/syntax/SyntaxTheme.d.ts.map +1 -1
  129. package/dist/theme/syntax/SyntaxTheme.js +43 -2
  130. package/dist/theme/syntax/defineSyntaxTheme.d.ts +18 -5
  131. package/dist/theme/syntax/defineSyntaxTheme.d.ts.map +1 -1
  132. package/dist/theme/syntax/defineSyntaxTheme.js +38 -9
  133. package/dist/theme/syntax/index.d.ts +1 -1
  134. package/dist/theme/syntax/index.d.ts.map +1 -1
  135. package/dist/theme/syntax/tokens.d.ts +12 -2
  136. package/dist/theme/syntax/tokens.d.ts.map +1 -1
  137. package/dist/theme/syntax/tokens.js +25 -4
  138. package/dist/utils/plainDate.d.ts +12 -0
  139. package/dist/utils/plainDate.d.ts.map +1 -1
  140. package/dist/utils/plainDate.js +63 -10
  141. package/locales/en.json +13 -1
  142. package/locales/pseudo.json +9 -0
  143. package/package.json +4 -4
  144. package/src/AlertDialog/AlertDialog.doc.mjs +6 -0
  145. package/src/AlertDialog/AlertDialog.spec.md +205 -0
  146. package/src/AlertDialog/AlertDialog.test.tsx +3 -14
  147. package/src/AlertDialog/__tests__/AlertDialog.a11y.chromium.spec.ts +115 -0
  148. package/src/AlertDialog/__tests__/AlertDialog.a11y.renders.tsx +52 -0
  149. package/src/AlertDialog/__tests__/AlertDialog.a11y.states.ts +68 -0
  150. package/src/AlertDialog/__tests__/AlertDialog.a11y.test.tsx +144 -0
  151. package/src/Button/Button.test.tsx +37 -0
  152. package/src/Button/Button.tsx +5 -1
  153. package/src/Calendar/Calendar.doc.mjs +234 -60
  154. package/src/Calendar/Calendar.spec.md +209 -0
  155. package/src/Calendar/Calendar.test.tsx +559 -20
  156. package/src/Calendar/Calendar.tsx +258 -41
  157. package/src/Calendar/__tests__/Calendar.a11y.chromium.spec.ts +126 -0
  158. package/src/Calendar/__tests__/Calendar.a11y.known-failures.ts +12 -0
  159. package/src/Calendar/__tests__/Calendar.a11y.renders.tsx +57 -0
  160. package/src/Calendar/__tests__/Calendar.a11y.states.ts +139 -0
  161. package/src/Calendar/__tests__/Calendar.a11y.test.tsx +137 -0
  162. package/src/Carousel/Carousel.test.tsx +54 -71
  163. package/src/Carousel/__tests__/Carousel.a11y.chromium.spec.ts +192 -0
  164. package/src/Carousel/__tests__/Carousel.a11y.known-failures.ts +27 -0
  165. package/src/Carousel/__tests__/Carousel.a11y.renders.tsx +39 -0
  166. package/src/Carousel/__tests__/Carousel.a11y.states.ts +139 -0
  167. package/src/Carousel/__tests__/Carousel.a11y.test.tsx +127 -0
  168. package/src/Chat/ChatComposerInput.doc.mjs +1 -1
  169. package/src/Chat/ChatComposerInput.spec.md +94 -55
  170. package/src/Chat/ChatComposerInput.test.tsx +202 -1
  171. package/src/Chat/ChatComposerInput.tsx +13 -0
  172. package/src/Chat/ChatMessage.doc.mjs +31 -16
  173. package/src/Chat/ChatMessage.spec.md +265 -0
  174. package/src/Chat/ChatMessage.test.tsx +246 -0
  175. package/src/Chat/ChatMessage.tsx +30 -7
  176. package/src/Chat/ChatSystemMessage.spec.md +36 -37
  177. package/src/Chat/ChatSystemMessage.test.tsx +4 -4
  178. package/src/Chat/useTriggerMenu.test.tsx +994 -0
  179. package/src/Chat/useTriggerMenu.tsx +278 -46
  180. package/src/CodeBlock/CodeBlock.doc.mjs +7 -0
  181. package/src/CodeBlock/CodeBlock.spec.md +40 -41
  182. package/src/CodeBlock/CodeBlock.test.tsx +89 -9
  183. package/src/CodeBlock/CodeBlock.tsx +205 -25
  184. package/src/CodeBlock/__tests__/CodeBlockOverflowStop.a11y.chromium.spec.ts +125 -0
  185. package/src/CodeBlock/__tests__/DiffRowPaint.a11y.chromium.spec.ts +93 -0
  186. package/src/CodeBlock/tokenizer.test.ts +112 -0
  187. package/src/CodeBlock/tokenizer.ts +106 -0
  188. package/src/Collapsible/CollapsibleGroup.spec.md +17 -8
  189. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.renders.tsx +45 -0
  190. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.states.ts +139 -0
  191. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.test.tsx +135 -0
  192. package/src/Collapsible/__tests__/CollapsibleGroupAccordion.a11y.chromium.spec.ts +128 -0
  193. package/src/CommandPalette/CommandPalette.doc.mjs +126 -23
  194. package/src/CommandPalette/CommandPalette.spec.md +100 -49
  195. package/src/CommandPalette/CommandPalette.test.tsx +241 -2
  196. package/src/CommandPalette/CommandPalette.tsx +47 -21
  197. package/src/CommandPalette/CommandPaletteEmpty.tsx +4 -3
  198. package/src/CommandPalette/CommandPaletteInput.doc.mjs +61 -4
  199. package/src/CommandPalette/CommandPaletteInput.spec.md +215 -0
  200. package/src/CommandPalette/CommandPaletteInput.test.tsx +375 -51
  201. package/src/CommandPalette/CommandPaletteInput.tsx +74 -21
  202. package/src/CommandPalette/CommandPaletteItem.doc.mjs +73 -22
  203. package/src/CommandPalette/CommandPaletteItem.spec.md +223 -0
  204. package/src/CommandPalette/CommandPaletteItem.test.tsx +200 -5
  205. package/src/CommandPalette/CommandPaletteItem.tsx +16 -11
  206. package/src/CommandPalette/CommandPaletteList.doc.mjs +3 -3
  207. package/src/CommandPalette/CommandPaletteList.spec.md +41 -36
  208. package/src/CommandPalette/CommandPaletteList.test.tsx +80 -0
  209. package/src/CommandPalette/CommandPaletteList.tsx +10 -5
  210. package/src/CommandPalette/__tests__/CommandPalette.a11y.chromium.spec.ts +599 -0
  211. package/src/ComplexSelector/ComplexSelector.doc.mjs +10 -8
  212. package/src/ComplexSelector/ComplexSelector.spec.md +14 -9
  213. package/src/ComplexSelector/ComplexSelector.test.tsx +64 -16
  214. package/src/ComplexSelector/ComplexSelector.tsx +37 -8
  215. package/src/ContextMenu/ContextMenu.doc.mjs +6 -0
  216. package/src/ContextMenu/ContextMenu.spec.md +51 -53
  217. package/src/ContextMenu/ContextMenu.test.tsx +132 -1
  218. package/src/ContextMenu/ContextMenu.tsx +78 -48
  219. package/src/DateInput/DateInput.doc.mjs +52 -0
  220. package/src/DateInput/DateInput.spec.md +27 -15
  221. package/src/DateInput/DateInput.test.tsx +38 -0
  222. package/src/DateInput/DateInput.tsx +1 -1
  223. package/src/DateInput/DateInputTouch.test.tsx +22 -0
  224. package/src/DateInput/MonthScroller.tsx +12 -4
  225. package/src/DateRangeInput/DateRangeInput.test.tsx +49 -0
  226. package/src/DateRangeInput/DateRangeInput.tsx +20 -2
  227. package/src/Dialog/Dialog.test.tsx +27 -1
  228. package/src/Dialog/Dialog.tsx +16 -5
  229. package/src/Dialog/DialogHeader.test.tsx +27 -2
  230. package/src/Dialog/DialogHeader.tsx +7 -8
  231. package/src/Dialog/__tests__/Dialog.a11y.chromium.spec.ts +21 -14
  232. package/src/Divider/Divider.doc.mjs +5 -3
  233. package/src/Divider/Divider.spec.md +140 -54
  234. package/src/Divider/Divider.test.tsx +15 -0
  235. package/src/Divider/Divider.tsx +6 -4
  236. package/src/DropdownMenu/DropdownMenu.doc.mjs +2 -2
  237. package/src/DropdownMenu/DropdownMenu.spec.md +121 -45
  238. package/src/DropdownMenu/DropdownMenu.test.tsx +288 -23
  239. package/src/DropdownMenu/DropdownMenu.tsx +212 -81
  240. package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +38 -2
  241. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +32 -18
  242. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +146 -1
  243. package/src/DropdownMenu/__tests__/MenuButton.a11y.chromium.spec.ts +136 -0
  244. package/src/DropdownMenu/__tests__/MenuButton.a11y.known-failures.ts +12 -0
  245. package/src/DropdownMenu/__tests__/MenuButton.a11y.renders.tsx +67 -0
  246. package/src/DropdownMenu/__tests__/MenuButton.a11y.states.ts +141 -0
  247. package/src/DropdownMenu/__tests__/MenuButton.a11y.test.tsx +138 -0
  248. package/src/DropdownMenu/__tests__/MenuHeaderFooter.a11y.chromium.spec.ts +40 -5
  249. package/src/Field/InputClearButton.test.tsx +29 -4
  250. package/src/Field/InputClearButton.tsx +26 -8
  251. package/src/FieldStatus/__tests__/StatusMessage.a11y.chromium.spec.ts +21 -14
  252. package/src/FormLayout/FormLayout.tsx +1 -0
  253. package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +1 -1
  254. package/src/Grid/Grid.doc.mjs +14 -11
  255. package/src/Grid/Grid.spec.md +50 -32
  256. package/src/Grid/Grid.tsx +13 -4
  257. package/src/HoverCard/HoverCard.test.tsx +46 -1
  258. package/src/HoverCard/useHoverCard.tsx +68 -48
  259. package/src/InputGroup/InputGroup.doc.mjs +11 -1
  260. package/src/InputGroup/InputGroup.tsx +2 -4
  261. package/src/InputGroup/groupStyles.ts +40 -2
  262. package/src/Layer/useLayerDismissal.ts +9 -9
  263. package/src/Link/__tests__/Link.a11y.chromium.spec.ts +142 -0
  264. package/src/Link/__tests__/Link.a11y.renders.tsx +97 -0
  265. package/src/Link/__tests__/Link.a11y.states.ts +104 -0
  266. package/src/Link/__tests__/Link.a11y.test.tsx +127 -0
  267. package/src/Markdown/Markdown.doc.mjs +1 -1
  268. package/src/Markdown/Markdown.fr23.perf.test.ts +161 -40
  269. package/src/Markdown/Markdown.spec.md +45 -23
  270. package/src/Markdown/Markdown.test.tsx +38 -0
  271. package/src/Markdown/Markdown.tsx +24 -2
  272. package/src/Markdown/plugins/semanticFence.test.tsx +3 -2
  273. package/src/MobileNav/MobileNav.tsx +50 -2
  274. package/src/MobileNav/MobileNavFocus.test.tsx +553 -0
  275. package/src/MoreMenu/MoreMenu.spec.md +18 -7
  276. package/src/MoreMenu/MoreMenu.test.tsx +38 -0
  277. package/src/MultiSelector/MultiSelector.doc.mjs +8 -8
  278. package/src/MultiSelector/MultiSelector.spec.md +1 -1
  279. package/src/MultiSelector/MultiSelector.test.tsx +188 -21
  280. package/src/MultiSelector/MultiSelector.tsx +50 -11
  281. package/src/NumberInput/NumberInput.test.tsx +24 -0
  282. package/src/NumberInput/NumberInput.tsx +1 -1
  283. package/src/NumberInput/__tests__/NumberInput.a11y.chromium.spec.ts +22 -15
  284. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +78 -0
  285. package/src/PowerSearch/PowerSearchEditPopover.tsx +6 -0
  286. package/src/Resizable/Resizable.spec.md +41 -9
  287. package/src/Resizable/ResizeHandle.test.tsx +565 -2
  288. package/src/Resizable/ResizeHandle.tsx +126 -21
  289. package/src/Resizable/__tests__/ResizeHandle.a11y.chromium.spec.ts +119 -0
  290. package/src/Resizable/__tests__/ResizeHandle.a11y.known-failures.ts +39 -0
  291. package/src/Resizable/__tests__/ResizeHandle.a11y.renders.tsx +80 -0
  292. package/src/Resizable/__tests__/ResizeHandle.a11y.states.ts +197 -0
  293. package/src/Resizable/__tests__/ResizeHandle.a11y.test.tsx +123 -0
  294. package/src/Resizable/useResizable.ts +95 -0
  295. package/src/Selector/Selector.doc.mjs +7 -6
  296. package/src/Selector/Selector.spec.md +37 -24
  297. package/src/Selector/Selector.test.tsx +176 -10
  298. package/src/Selector/Selector.tsx +59 -14
  299. package/src/Selector/__tests__/Combobox.a11y.known-failures.ts +27 -0
  300. package/src/Selector/__tests__/Combobox.a11y.states.ts +145 -0
  301. package/src/Selector/__tests__/Selector.combobox.a11y.chromium.spec.ts +108 -0
  302. package/src/Selector/__tests__/Selector.combobox.a11y.test.tsx +126 -0
  303. package/src/SideNav/SideNav.test.tsx +35 -0
  304. package/src/SideNav/SideNav.tsx +16 -2
  305. package/src/Slider/Slider.spec.md +43 -17
  306. package/src/Slider/Slider.test.tsx +134 -0
  307. package/src/Slider/Slider.tsx +29 -6
  308. package/src/Slider/__tests__/Slider.a11y.chromium.spec.ts +112 -0
  309. package/src/Slider/__tests__/Slider.a11y.known-failures.ts +12 -0
  310. package/src/Slider/__tests__/Slider.a11y.renders.tsx +42 -0
  311. package/src/Slider/__tests__/Slider.a11y.states.ts +106 -0
  312. package/src/Slider/__tests__/Slider.a11y.test.tsx +105 -0
  313. package/src/Switch/__tests__/Switch.a11y.chromium.spec.ts +25 -18
  314. package/src/TabList/__tests__/Tabs.a11y.chromium.spec.ts +19 -12
  315. package/src/TextInput/__tests__/TextInput.a11y.chromium.spec.ts +21 -14
  316. package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +18 -11
  317. package/src/Tokenizer/Tokenizer.test.tsx +87 -0
  318. package/src/Tokenizer/Tokenizer.tsx +31 -3
  319. package/src/Toolbar/Toolbar.test.tsx +131 -4
  320. package/src/Toolbar/Toolbar.tsx +3 -1
  321. package/src/TreeList/TreeList.spec.md +33 -8
  322. package/src/TreeList/TreeList.test.tsx +72 -0
  323. package/src/TreeList/TreeList.tsx +10 -6
  324. package/src/TreeList/TreeListItem.tsx +5 -0
  325. package/src/TreeList/__tests__/TreeList.a11y.chromium.spec.ts +125 -0
  326. package/src/TreeList/__tests__/TreeList.a11y.renders.tsx +44 -0
  327. package/src/TreeList/__tests__/TreeList.a11y.states.ts +164 -0
  328. package/src/TreeList/__tests__/TreeList.a11y.test.tsx +138 -0
  329. package/src/TreeList/__tests__/TreeListRowNames.a11y.chromium.spec.ts +82 -0
  330. package/src/hooks/useScrollOverflow.test.tsx +222 -0
  331. package/src/hooks/useScrollOverflow.ts +55 -13
  332. package/src/hooks/useTreeFocus.test.tsx +37 -0
  333. package/src/hooks/useTreeFocus.ts +55 -2
  334. package/src/i18n/generated-locales/en.generated.ts +3 -0
  335. package/src/i18n/generated-locales/pseudo.generated.ts +3 -0
  336. package/src/reset.a11y.chromium.spec.ts +9 -1
  337. package/src/reset.css +8 -3
  338. package/src/reset.until-found.a11y.browser.spec.ts +75 -0
  339. package/src/reset.until-found.test.ts +32 -0
  340. package/src/stylexValidation.source-build.test.mjs +42 -0
  341. package/src/theme/syntax/SyntaxTheme.tsx +46 -5
  342. package/src/theme/syntax/defineSyntaxTheme.diff.test.ts +91 -0
  343. package/src/theme/syntax/defineSyntaxTheme.ts +75 -15
  344. package/src/theme/syntax/index.ts +5 -1
  345. package/src/theme/syntax/tokens.ts +32 -2
  346. package/src/theme/syntax/useSyntaxTheme.diff.test.tsx +40 -0
  347. package/src/utils/plainDate.test.ts +147 -0
  348. package/src/utils/plainDate.ts +75 -10
@@ -11,7 +11,7 @@ import type {
11
11
  ChatComposerInputHandle,
12
12
  } from './ChatComposerInput';
13
13
  import {createStaticSource} from '../Typeahead/createStaticSource';
14
- import type {SearchableItem} from '../Typeahead/types';
14
+ import type {SearchableItem, SearchSource} from '../Typeahead/types';
15
15
 
16
16
  // =============================================================================
17
17
  // Helpers
@@ -1370,6 +1370,207 @@ describe('ChatComposerInput', () => {
1370
1370
  });
1371
1371
  });
1372
1372
 
1373
+ describe('trigger query (component:ChatComposerInput FR13, AR5)', () => {
1374
+ function typeAtEnd(editable: HTMLElement, text: string) {
1375
+ editable.textContent = text;
1376
+ const node = editable.firstChild!;
1377
+ const range = document.createRange();
1378
+ range.setStart(node, text.length);
1379
+ range.collapse(true);
1380
+ const sel = window.getSelection()!;
1381
+ sel.removeAllRanges();
1382
+ sel.addRange(range);
1383
+ fireEvent.input(editable);
1384
+ }
1385
+
1386
+ it('Enter while a newer trigger search is pending sends the typed text, picks no row, and no menu reopens after the send', async () => {
1387
+ const onSubmit = vi.fn();
1388
+ let resolveSlow: (items: SearchableItem[]) => void = () => {};
1389
+ const source: SearchSource = {
1390
+ bootstrap: async () => [],
1391
+ async search(query: string) {
1392
+ if (query === 'ci') {
1393
+ return new Promise(resolve => {
1394
+ resolveSlow = resolve;
1395
+ });
1396
+ }
1397
+ return Promise.resolve(
1398
+ USERS.filter(u => u.label.toLowerCase().includes(query)),
1399
+ );
1400
+ },
1401
+ };
1402
+ render(
1403
+ <ChatComposerInput
1404
+ triggers={[createMentionTrigger({searchSource: source})]}
1405
+ debounceMs={0}
1406
+ onSubmit={onSubmit}
1407
+ />,
1408
+ );
1409
+ const editable = screen.getByRole('combobox');
1410
+ editable.focus();
1411
+
1412
+ typeAtEnd(editable, '@c');
1413
+ await waitFor(() => {
1414
+ expect(editable.getAttribute('aria-activedescendant')).toBeTruthy();
1415
+ });
1416
+
1417
+ // A newer search is pending: Enter sends the draft exactly as typed.
1418
+ typeAtEnd(editable, '@ci');
1419
+ fireEvent.keyDown(editable, {key: 'Enter'});
1420
+ expect(onSubmit).toHaveBeenCalledWith('@ci');
1421
+ expect(editable.querySelector('[data-astryx-token]')).toBeNull();
1422
+ expect(editable.textContent).toBe('');
1423
+
1424
+ // The late result for the sent query must not reopen the menu.
1425
+ await act(async () => {
1426
+ resolveSlow([{id: 'cindy', label: 'Cindy Zhang'}]);
1427
+ });
1428
+ expect(editable).toHaveAttribute('aria-expanded', 'false');
1429
+ expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
1430
+ expect(screen.queryByRole('option')).not.toBeInTheDocument();
1431
+ });
1432
+
1433
+ it('forgets an Escape-dismissed mention when the message is sent, so the next message can mention', async () => {
1434
+ const onSubmit = vi.fn();
1435
+ render(
1436
+ <ChatComposerInput
1437
+ triggers={[createMentionTrigger()]}
1438
+ onSubmit={onSubmit}
1439
+ />,
1440
+ );
1441
+ const editable = screen.getByRole('combobox');
1442
+ editable.focus();
1443
+
1444
+ typeAtEnd(editable, '@');
1445
+ await waitFor(() => {
1446
+ expect(editable).toHaveAttribute('aria-expanded', 'true');
1447
+ });
1448
+ fireEvent.keyDown(editable, {key: 'Escape'});
1449
+ await waitFor(() => {
1450
+ expect(editable).toHaveAttribute('aria-expanded', 'false');
1451
+ });
1452
+
1453
+ fireEvent.keyDown(editable, {key: 'Enter'});
1454
+ expect(onSubmit).toHaveBeenCalledWith('@');
1455
+
1456
+ typeAtEnd(editable, '@ci');
1457
+ await waitFor(() => {
1458
+ expect(editable).toHaveAttribute('aria-expanded', 'true');
1459
+ });
1460
+ expect(editable.getAttribute('aria-activedescendant')).toBeTruthy();
1461
+ });
1462
+
1463
+ it('forgets an Escape-dismissed trigger when the send button sends the draft', async () => {
1464
+ const onSubmit = vi.fn();
1465
+ render(
1466
+ <ChatComposer
1467
+ onSubmit={onSubmit}
1468
+ input={<ChatComposerInput triggers={[createCommandTrigger()]} />}
1469
+ />,
1470
+ );
1471
+ const editable = screen.getByRole('combobox');
1472
+ editable.focus();
1473
+
1474
+ typeAtEnd(editable, '/');
1475
+ await waitFor(() => {
1476
+ expect(editable).toHaveAttribute('aria-expanded', 'true');
1477
+ });
1478
+ fireEvent.keyDown(editable, {key: 'Escape'});
1479
+ await waitFor(() => {
1480
+ expect(editable).toHaveAttribute('aria-expanded', 'false');
1481
+ });
1482
+
1483
+ fireEvent.click(screen.getByRole('button', {name: /send/i}));
1484
+ expect(onSubmit).toHaveBeenCalledWith('/');
1485
+ await waitFor(() => {
1486
+ expect(editable.textContent).toBe('');
1487
+ });
1488
+
1489
+ typeAtEnd(editable, '/s');
1490
+ await waitFor(() => {
1491
+ expect(editable).toHaveAttribute('aria-expanded', 'true');
1492
+ });
1493
+ });
1494
+
1495
+ it('drops a late trigger result after the send button sends the draft', async () => {
1496
+ const onSubmit = vi.fn();
1497
+ let resolveSlow: (items: SearchableItem[]) => void = () => {};
1498
+ const source: SearchSource = {
1499
+ bootstrap: async () => [],
1500
+ async search(query: string) {
1501
+ if (query === 'ci') {
1502
+ return new Promise(resolve => {
1503
+ resolveSlow = resolve;
1504
+ });
1505
+ }
1506
+ return Promise.resolve(
1507
+ USERS.filter(u => u.label.toLowerCase().includes(query)),
1508
+ );
1509
+ },
1510
+ };
1511
+ render(
1512
+ <ChatComposer
1513
+ onSubmit={onSubmit}
1514
+ input={
1515
+ <ChatComposerInput
1516
+ triggers={[createMentionTrigger({searchSource: source})]}
1517
+ debounceMs={0}
1518
+ />
1519
+ }
1520
+ />,
1521
+ );
1522
+ const editable = screen.getByRole('combobox');
1523
+ editable.focus();
1524
+
1525
+ typeAtEnd(editable, '@c');
1526
+ await waitFor(() => {
1527
+ expect(editable.getAttribute('aria-activedescendant')).toBeTruthy();
1528
+ });
1529
+ typeAtEnd(editable, '@ci');
1530
+
1531
+ fireEvent.click(screen.getByRole('button', {name: /send/i}));
1532
+ expect(onSubmit).toHaveBeenCalledWith('@ci');
1533
+ await waitFor(() => {
1534
+ expect(editable.textContent).toBe('');
1535
+ });
1536
+
1537
+ await act(async () => {
1538
+ resolveSlow([{id: 'cindy', label: 'Cindy Zhang'}]);
1539
+ });
1540
+ expect(editable).toHaveAttribute('aria-expanded', 'false');
1541
+ expect(screen.queryByRole('option')).not.toBeInTheDocument();
1542
+ });
1543
+
1544
+ it('lets Escape reach the consumer while an unmatched multi-word query hides the menu', async () => {
1545
+ const onKeyDown = vi.fn();
1546
+ render(
1547
+ <ChatComposerInput
1548
+ triggers={[createMentionTrigger({hasMultiWordQuery: true})]}
1549
+ onKeyDown={onKeyDown}
1550
+ />,
1551
+ );
1552
+ const editable = screen.getByRole('combobox');
1553
+ editable.focus();
1554
+
1555
+ typeAtEnd(editable, '@team please');
1556
+ await waitFor(() => {
1557
+ expect(editable).toHaveAttribute('aria-expanded', 'false');
1558
+ });
1559
+ // The hidden menu's results commit outside act, so the popover's Escape
1560
+ // registration is released by a passive effect that can still be pending
1561
+ // when the attribute changes. Flush it before pressing Escape.
1562
+ await act(async () => {});
1563
+
1564
+ // Nothing cancels the key: the closed menu holds no Escape handling,
1565
+ // so a surrounding dialog or drawer still receives it.
1566
+ const notCancelled = fireEvent.keyDown(editable, {key: 'Escape'});
1567
+ expect(notCancelled).toBe(true);
1568
+ expect(onKeyDown).toHaveBeenCalledWith(
1569
+ expect.objectContaining({key: 'Escape'}),
1570
+ );
1571
+ });
1572
+ });
1573
+
1373
1574
  describe('trigger menu cursor anchor', () => {
1374
1575
  // The trigger menu anchors its popover to the cursor position, not the
1375
1576
  // entire input element. In real browsers this creates a fixed-position
@@ -170,6 +170,15 @@ export type ChatComposerTrigger = {
170
170
  loadingText?: string;
171
171
  /** Accessible label for the menu. @default 'Suggestions' */
172
172
  menuLabel?: string;
173
+ /**
174
+ * Whether the query continues past spaces, for suggestions whose names have
175
+ * spaces (e.g. "@María José García", "@Design Review: Q4"). The query then
176
+ * runs until a line break or about 64 characters, and when nothing matches
177
+ * the menu hides unless `emptySearchText` is set. When false, the query ends
178
+ * at the first space.
179
+ * @default false
180
+ */
181
+ hasMultiWordQuery?: boolean;
173
182
  };
174
183
 
175
184
  export interface ChatComposerInputProps extends Omit<
@@ -685,6 +694,10 @@ export function ChatComposerInput(props: ChatComposerInputProps) {
685
694
  currentDraftRef.current = '';
686
695
  }
687
696
 
697
+ // Submitting ends any trigger query, including one dismissed with
698
+ // Escape: no menu stays open, and no dismissal carries into the next
699
+ // message.
700
+ triggerMenu.reset();
688
701
  onSubmit?.(text);
689
702
  editableRef.current.textContent = '';
690
703
  setIsEmpty(true);
@@ -6,7 +6,8 @@ export const docs = {
6
6
  name: 'ChatMessage',
7
7
  subComponentOf: 'Chat',
8
8
  displayName: 'Chat Message',
9
- description: 'Sender context wrapper: handles avatar, name, metadata, and alignment based on sender role.',
9
+ description:
10
+ 'Sender context wrapper: handles avatar, name, metadata, and alignment based on sender role.',
10
11
  playground: {
11
12
  defaults: {
12
13
  sender: 'assistant',
@@ -28,7 +29,8 @@ export const docs = {
28
29
  {
29
30
  name: 'children',
30
31
  type: 'ReactNode',
31
- description: 'Free-form content: bubbles, asset lists, tool calls, images. Custom (non-bubble) children render flush with the message edge; wrap them in a ghost ChatMessageBubble to align them with the bubble text column.',
32
+ description:
33
+ 'Free-form content: bubbles, asset lists, tool calls, images. Custom (non-bubble) children render flush with the message edge; wrap them in a ghost ChatMessageBubble to align them with the bubble text column.',
32
34
  required: true,
33
35
  slotElements: [
34
36
  {
@@ -40,7 +42,8 @@ export const docs = {
40
42
  {
41
43
  name: 'avatar',
42
44
  type: 'ReactNode',
43
- description: 'Avatar element rendered beside the message. Typically Avatar.',
45
+ description:
46
+ 'Avatar element rendered beside the message. Typically Avatar. `null`, booleans, and empty strings create no slot; any other content that renders nothing collapses its slot.',
44
47
  slotElements: [
45
48
  {
46
49
  __element: 'Avatar',
@@ -54,12 +57,14 @@ export const docs = {
54
57
  {
55
58
  name: 'name',
56
59
  type: 'ReactNode',
57
- description: "Sender name rendered above the message body. Use when the first child is raw content (not a bubble). If the first child is a bubble, put the name on the bubble's `name` prop instead.",
60
+ description:
61
+ "Sender name rendered above the message body; it labels the message. `null`, booleans, and empty strings create no slot, and any other content that renders nothing collapses its slot; either way the message is labelled by the translated sender fallback. Use when the first child is raw content (not a bubble). If the first child is a bubble, put the name on the bubble's `name` prop instead.",
58
62
  },
59
63
  {
60
64
  name: 'metadata',
61
65
  type: 'ReactNode',
62
- description: "Metadata rendered below the message body. Use when the last child is raw content (not a bubble). If the last child is a bubble, put metadata on the bubble's `metadata` prop instead.",
66
+ description:
67
+ "Metadata rendered below the message body. `null`, booleans, and empty strings create no slot; any other content that renders nothing collapses its slot. Use when the last child is raw content (not a bubble). If the last child is a bubble, put metadata on the bubble's `metadata` prop instead.",
63
68
  slotElements: [
64
69
  {
65
70
  __element: 'Text',
@@ -74,6 +79,7 @@ export const docs = {
74
79
  name: 'density',
75
80
  type: "'compact' | 'balanced' | 'spacious'",
76
81
  description: 'Visual density. Inherited from list context if not set.',
82
+ default: "'balanced'",
77
83
  },
78
84
  ],
79
85
  };
@@ -81,13 +87,17 @@ export const docs = {
81
87
  export const docsZh = {
82
88
  name: 'ChatMessage',
83
89
  displayName: 'Chat Message',
84
- description: '发送者上下文包装器,根据发送者角色处理头像、名称、元数据和对齐方式。',
90
+ description:
91
+ '发送者上下文包装器,根据发送者角色处理头像、名称、元数据和对齐方式。',
85
92
  propDescriptions: {
86
93
  sender: '消息发送者,控制对齐和布局。',
87
- children: '自由内容:气泡、资源列表、工具调用、图片。非气泡的自定义子内容默认与消息边缘齐平,可用 ghost 变体的 ChatMessageBubble 包裹使其与气泡文本列对齐。',
88
- avatar: '消息旁边渲染的头像元素。通常是 Avatar。',
89
- name: '消息正文上方渲染的发送者名称。当第一个子元素不是气泡时使用。',
90
- metadata: '消息正文下方渲染的元数据。当最后一个子元素不是气泡时使用。',
94
+ children:
95
+ '自由内容:气泡、资源列表、工具调用、图片。非气泡的自定义子内容默认与消息边缘齐平,可用 ghost 变体的 ChatMessageBubble 包裹使其与气泡文本列对齐。',
96
+ avatar:
97
+ '消息旁边渲染的头像元素。通常是 Avatar。`null`、布尔值和空字符串不会创建槽位;其他不渲染任何内容的值会让槽位折叠。',
98
+ name: '消息正文上方渲染的发送者名称,并作为消息的标签。`null`、布尔值和空字符串不会创建槽位,其他不渲染任何内容的值会让槽位折叠;两种情况都改用本地化发送者标签。当第一个子元素不是气泡时使用。',
99
+ metadata:
100
+ '消息正文下方渲染的元数据。`null`、布尔值和空字符串不会创建槽位;其他不渲染任何内容的值会让槽位折叠。当最后一个子元素不是气泡时使用。',
91
101
  density: '视觉密度。未设置时从列表上下文继承。',
92
102
  },
93
103
  };
@@ -95,14 +105,19 @@ export const docsZh = {
95
105
  export const docsDense = {
96
106
  name: 'ChatMessage',
97
107
  displayName: 'Chat Message',
98
- description: 'sender context wrapper; handles avatar+name+metadata+alignment by sender role',
108
+ description:
109
+ 'sender context wrapper; handles avatar+name+metadata+alignment by sender role',
99
110
  propDescriptions: {
100
111
  sender: 'who sent; controls alignment+layout',
101
- children: 'free-form: bubbles, assets, tool calls, images; wrap non-bubble content in a ghost bubble to align with bubble text column',
102
- avatar: 'avatar element beside msg; typically Avatar',
103
- name: 'sender name above body; use when first child is raw (not bubble)',
104
- displayName: 'sender name above body; use when first child is raw (not bubble)',
105
- metadata: 'metadata below body; use when last child is raw (not bubble)',
112
+ children:
113
+ 'free-form: bubbles, assets, tool calls, images; wrap non-bubble content in a ghost bubble to align with bubble text column',
114
+ avatar:
115
+ 'avatar beside msg; null/boolean/"" omit slot; content rendering nothing collapses slot',
116
+ name: 'sender name above body; labels msg; null/boolean/"" omit slot, content rendering nothing collapses it; both fall back to translated sender label; use when first child is raw',
117
+ displayName:
118
+ 'sender name above body; use when first child is raw (not bubble)',
119
+ metadata:
120
+ 'metadata below body; null/boolean/"" omit slot; content rendering nothing collapses slot; use when last child is raw',
106
121
  density: 'visual density; inherited from list context if unset',
107
122
  },
108
123
  };
@@ -0,0 +1,265 @@
1
+ ---
2
+ schema_version: 3
3
+ template_version: 6
4
+ kind: component
5
+ id: component:ChatMessage
6
+ authority: draft
7
+ archive_reason: null
8
+ superseded_by: null
9
+ approved_by: null
10
+ approved_at: null
11
+ owners: [cixzhang]
12
+ review_triggers: [public-api, behavior, layout, theming, accessibility, testing]
13
+ verified_by:
14
+ [
15
+ packages/core/src/Chat/ChatMessage.test.tsx,
16
+ packages/core/src/theme/themingTargets.test.ts,
17
+ apps/storybook/stories/ChatMessage.stories.tsx,
18
+ ]
19
+ modules: []
20
+ families: []
21
+ design_specs: []
22
+ architecture:
23
+ [
24
+ architecture:public-component-api,
25
+ architecture:component-style-authoring,
26
+ architecture:component-theming-surface,
27
+ architecture:theme-tokens,
28
+ architecture:react-component-runtime,
29
+ architecture:component-test-sufficiency,
30
+ architecture:knowledge-contracts,
31
+ ]
32
+ contributing: []
33
+ system_specs: [spec:AST-020, spec:AST-029]
34
+ ---
35
+
36
+ # ChatMessage component contract
37
+
38
+ This record is an **observational backfill** written during the 2026-09-24
39
+ component audit under `spec:AST-029/FR3–FR4`. Rows marked `verify` describe the
40
+ audit base's released contract. Rows marked `proposed` describe the empty-slot
41
+ behavior (FR3, FR6, ORD2, AR1–AR2, DEC-1) and await owner approval: `null`,
42
+ booleans, and empty strings create no slot; any other slot content that renders
43
+ nothing collapses its wrapper through CSS; and a name that renders no text falls
44
+ back to the translated sender label. Slot content is never inspected to decide
45
+ any of this. Unresolved questions remain open rather than becoming policy.
46
+
47
+ ## Contract at a glance
48
+
49
+ | Area | Contract |
50
+ | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
51
+ | Public contract | `sender`, `children`, `avatar`, `name`, `metadata`, `density`, plus `BaseProps<HTMLElement>` and `ref` on the message article. |
52
+ | Behavior | Sender controls logical alignment and system-message suppression; density resolves explicit prop → message-list context → `balanced`; descendants receive the resolved context. |
53
+ | End-user impact | People reading a conversation can distinguish user, assistant, and system messages while names and translated fallback labels preserve each article's sender identity. |
54
+ | Builder impact | No new caller choice. Slot content that renders nothing leaves no empty space and no unnamed article. |
55
+ | Compatibility/readiness | Draft. No public API, default, or theming target changes; the empty-slot rows are a patch fix. Authority remains `draft` until exact-head owner approval. |
56
+ | Review checks | Reject a row that states unshipped behavior, changes sender or density meaning, treats caller content as component-owned, or turns the unresolved theming question into new API. |
57
+ | Governing rules | `architecture:public-component-api/INV1, INV3, INV5–INV6, INV8–INV9`; `architecture:component-theming-surface/INV3–INV6`; `spec:AST-029/FR3–FR6`. |
58
+
59
+ This table is a review projection; the body below is authoritative.
60
+
61
+ ## Intent
62
+
63
+ ChatMessage is the sender-aware article wrapper for one message in a
64
+ conversation. It owns logical alignment, optional sender identity slots, the
65
+ resolved density context supplied to message descendants, and the article's
66
+ accessible name. Message content and its own interactions remain caller-owned.
67
+
68
+ Consumer syntax and examples belong in `ChatMessage.doc.mjs` and the Chat family
69
+ document.
70
+
71
+ ## Compatibility and migration
72
+
73
+ - Released default preserved: yes — the published `./Chat` subpath exports
74
+ `ChatMessage`, `ChatMessageProps`, `ChatMessageSender`, and `ChatDensity`.
75
+ - Compatibility class: patch fix. Slot content that renders nothing creates no
76
+ visible slot, and a name that renders no text leaves the translated fallback
77
+ label; a name that renders text labels the article.
78
+ - Controlled/uncontrolled behavior: not applicable; the component owns no
79
+ controllable value.
80
+ - Migration decision: none.
81
+
82
+ Consumer migration instructions belong in consumer docs and release notes.
83
+
84
+ ## Ownership boundary
85
+
86
+ **Owns**
87
+
88
+ - One semantic `<article>` root and its public ref, DOM, data, ARIA, class, and
89
+ style passthrough.
90
+ - Logical start, end, or centered alignment selected by `sender`.
91
+ - Optional avatar, name, and metadata placement for non-system messages,
92
+ including collapsing a slot whose content renders nothing (DEC-1).
93
+ - Density resolution and the `ChatMessageContext` value supplied to descendants.
94
+ - The `chat-message` target with its `sender` and resolved `density` axes.
95
+ - The article's visible-name or translated fallback accessible label.
96
+
97
+ **Does not own / non-goals**
98
+
99
+ - Bubble presentation, grouping, width, name, or metadata — owned by
100
+ `component:ChatMessageBubble` when a bubble supplies them.
101
+ - Avatar appearance — owned by the caller's supplied node, commonly
102
+ `component:Avatar`.
103
+ - Metadata content, timestamp formatting, status, or footer actions — owned by
104
+ the supplied node and its components.
105
+ - Message-list ordering, spacing between messages, streaming announcements, or
106
+ scroll behavior — owned by `component:ChatMessageList` and
107
+ `component:ChatLayout`.
108
+ - Interactions inside `children`; descendants retain their own semantic and
109
+ accessibility contracts.
110
+
111
+ ## Public concepts
112
+
113
+ | Concept | Closed values or states | Meaning | Availability by variant/orientation/state | Default | Owner | Stability | Invalid-value behavior |
114
+ | ------------ | --------------------------------------------------------- | --------------------------------------------------------------------------------------- | ----------------------------------------- | --------- | ---------------------- | --------- | ------------------------------- |
115
+ | `sender` | `assistant`; `user`; `system` | Selects logical alignment, article identity, and whether auxiliary identity slots show. | required in every message | required | component | stable | closed union; rejected by types |
116
+ | `children` | any `ReactNode` | Message body passed through inside the aligned content column. | every sender | required | caller | stable | n/a — required |
117
+ | `avatar` | renders content; renders nothing; skipped scalar; omitted | Optional identity node beside a non-system message. | assistant and user; suppressed for system | omitted | caller | stable | renders nothing: slot collapses |
118
+ | `name` | renders text; renders nothing; skipped scalar; omitted | Optional visible sender name and article label source. | assistant and user; suppressed for system | omitted | caller/component | stable | renders no text: fallback label |
119
+ | `metadata` | renders content; renders nothing; skipped scalar; omitted | Optional content below the message body. | assistant and user; suppressed for system | omitted | caller | stable | renders nothing: slot collapses |
120
+ | `density` | `compact`; `balanced`; `spacious`; omitted | Selects gaps and becomes descendant message context. | every sender | inherited | component/list context | stable | closed union; rejected by types |
121
+ | root surface | `ref`; DOM/data/ARIA props; `className`/`style`/`xstyle` | Extends the message article. | every sender | omitted | public component API | stable | n/a |
122
+
123
+ ## Behavioral and layout contract
124
+
125
+ Draft requirements identify their basis so observed code is not mistaken for an
126
+ intentional decision.
127
+
128
+ | ID | Candidate invariant | Basis | Draft review state |
129
+ | --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ------------------ |
130
+ | FR1 | `assistant` MUST align to inline start, `user` to inline end, and `system` to the centre; the root MUST reflect the sender on the `chat-message` target. | shipped implementation; consumer docs; focused tests | verify |
131
+ | FR2 | `system` MUST suppress the supplied avatar, name, and metadata while continuing to render `children`. | shipped implementation; Chat family anatomy; focused tests | verify |
132
+ | FR3 | A non-system message MUST render supplied avatar, name, children, and metadata in that visual order. `null`, boolean, and empty-string auxiliary values MUST NOT create wrappers; a wrapper whose content renders nothing MUST collapse (DEC-1). | owner direction; focused tests | proposed |
133
+ | FR4 | Density MUST resolve explicit `density` → nearest `ChatMessageList` density → `balanced`, and the resolved value MUST reflect on the root target. | shipped implementation; consumer docs; focused tests; theming metadata | verify |
134
+ | FR5 | Descendants MUST receive the resolved `{sender, density}` value through `ChatMessageContext`. | shipped implementation; descendant composition | verify |
135
+ | FR6 | The article MUST carry the translated sender fallback as `aria-label`, and a non-system `name` that is not a skipped scalar MUST also label it through `aria-labelledby`. A name that renders text names the article; one that renders none leaves the fallback (accessible name computation 1.2, step 2B). | owner direction; accessible name computation 1.2; focused tests | proposed |
136
+ | FR7 | Accepted `ref`, DOM/data/ARIA props, `className`, `style`, and `xstyle` MUST reach and compose on the same article that carries the `chat-message` target. | `architecture:public-component-api/INV5–INV6, INV8`; focused tests | settled |
137
+
138
+ ### Allowed variation
139
+
140
+ - **AV1 — Message content.** `children` may be bubbles, rich content, tool output,
141
+ media, or any other caller-owned React node.
142
+ - **AV2 — Identity content.** Avatar, name, and metadata are caller-owned nodes
143
+ passed to React unchanged; the wrapper controls placement, system suppression,
144
+ and collapse of a slot whose content renders nothing.
145
+ - **AV3 — Density geometry.** Resolved gaps follow the active theme's spacing
146
+ scale; the compact → balanced → spacious ordering is fixed.
147
+
148
+ ### Representative states
149
+
150
+ | State | Required invariant | Allowed variation |
151
+ | ------------------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------- |
152
+ | Assistant with avatar, name, and metadata | Inline-start layout; visible name labels the article | Any caller-owned node may fill the slots |
153
+ | Skipped scalar auxiliary values | No slot or naming reference is created | `null`, booleans, empty strings |
154
+ | Auxiliary content that renders nothing | The slot collapses; a name of this kind leaves the fallback label | Empty array or Fragment, component returning `null` |
155
+ | Numeric auxiliary values | `0` remains visible and a numeric name labels the article | Any non-system sender |
156
+ | User without a name | Inline-end layout; translated sender fallback labels the article | Avatar and metadata may be present or omitted |
157
+ | System with auxiliary props supplied | Centre layout; avatar, name, and metadata remain suppressed | Any children continue to render |
158
+ | Standalone message | Resolved density is `balanced` | Any sender and content |
159
+ | Message inside a density-providing list | Inherits the list density | Any sender and content |
160
+ | Message with explicit density inside a list | Explicit density wins | Any supported density |
161
+ | Narrow container with long content | Root stays within the container and content remains available | Descendant content chooses its own wrap policy |
162
+ | RTL container | Logical user/assistant alignment mirrors with document direction | System remains centred |
163
+
164
+ ### Transformation and precedence order
165
+
166
+ - **ORD1 — Density.** Resolve the explicit message prop first, then the nearest
167
+ message-list context, then `balanced`; use that single result for layout,
168
+ target reflection, and descendant context.
169
+ - **ORD2 — Accessible name.** A system `name` or a skipped scalar creates no
170
+ name wrapper. Otherwise the article references the name wrapper's generated
171
+ id; the rendered text there names the article, and empty text yields to the
172
+ translated fallback carried as `aria-label`.
173
+
174
+ ### Performance and resources
175
+
176
+ - **PR1 — No external resources.** ChatMessage installs no listener, timer,
177
+ observer, request, or Effect.
178
+ - **PR2 — Stable broadcast.** The descendant context value changes only when the
179
+ resolved sender or density changes.
180
+
181
+ ## Accessibility contract
182
+
183
+ - **AR1 — Article naming mechanism.** Every article carries the translated
184
+ sender fallback as `aria-label`. A name wrapper, when present, is referenced
185
+ through `aria-labelledby`; its rendered text names the article, and when it
186
+ renders no text the accessible name falls back to `aria-label`.
187
+ - **AR2 — No inspection.** Slot content is never inspected, cloned, or
188
+ executed to decide whether it renders; collapse and the naming fallback follow
189
+ the rendered DOM.
190
+ - **AR3 — Descendant ownership.** ChatMessage is non-interactive and introduces no
191
+ keyboard model. Interactive descendants remain responsible for their own roles,
192
+ names, states, focus, and activation.
193
+
194
+ ## Design relationships
195
+
196
+ | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
197
+ | ---------------- | ------------------------------------------------------- | ------------------------ | -------------- | ------------------ |
198
+ | Assistant | Align the message column to logical start | observed | content | FR1 |
199
+ | User | Align the message column to logical end | observed | content | FR1 |
200
+ | System | Centre the content column and suppress identity slots | observed | supporting | FR1–FR2 |
201
+ | Name | Use supporting type and secondary text colour tokens | observed | supporting | FR3 |
202
+ | Density | Increase root and child gaps across the three-step axis | observed | structure | FR4 |
203
+
204
+ No component-local design decision is settled by this draft.
205
+
206
+ ### Theming reachability (observed)
207
+
208
+ | Anatomy part | Disposition |
209
+ | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
210
+ | Message root | `target` — the `chat-message` target sits on the article and reflects `sender` and resolved `density`; the Chat family document owns its consumer metadata. |
211
+ | Avatar | `delegatesTo` the supplied node, commonly the `avatar` target owned by `component:Avatar`. |
212
+ | Name | `none` — reachability-gap: the built-in name paints explicit supporting type and secondary colour on an untargeted descendant; see OQ1. |
213
+ | Content | `delegatesTo` caller-owned children and their targets; custom content has no component-owned target. |
214
+ | Metadata | `none` — intentional current boundary: the caller supplies the complete rendered node, while the wrapper contributes placement only. |
215
+
216
+ ## Family and system relationships
217
+
218
+ - There is no current `family:chat` record. `Chat.doc.mjs` is the consumer-facing
219
+ family document and owns the shared target inventory; it is not product authority.
220
+ - `component:ChatMessageList` supplies optional density context and owns list
221
+ spacing, log semantics, streaming state, and message ordering.
222
+ - `component:ChatMessageBubble` consumes sender and density context when present
223
+ and owns bubble presentation.
224
+ - There is no current direct owner for ChatMessage's
225
+ `compact | balanced | spacious` density vocabulary or ordering. FR4–FR5 record
226
+ the shipped axis and precedence observationally; routed global layout claims do
227
+ not settle that component-specific vocabulary.
228
+ - `spec:AST-029` permits this observational draft and keeps the closed audit
229
+ inventory outside the component contract.
230
+
231
+ ## Verification map
232
+
233
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
234
+ | ------------ | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- | ---------------------- |
235
+ | FR1–FR3 | `ChatMessage.test.tsx`; `ChatMessage.stories.tsx` | all senders; skipped scalar, renders-nothing, numeric, content, and suppressed slot states | Removing the scalar skip or the CSS collapse leaves an empty slot or a gap | `audit:ChatMessage/§4` |
236
+ | FR4–FR5 | `ChatMessage.test.tsx`; `themingTargets.test.ts` | standalone; inherited density; explicit override | Changing precedence or reflection gives descendants and themes a different density | `audit:ChatMessage/§2` |
237
+ | FR6, AR1–AR2 | `ChatMessage.test.tsx`; Chromium accessibility tree | visible, omitted, skipped-scalar, renders-nothing, and system names | Dropping the fallback `aria-label` or the name reference leaves an article unnamed | `audit:ChatMessage/§1` |
238
+ | FR7 | `ChatMessage.test.tsx`; public export and theming-target checks | ref; data prop; class; style | Moving passthrough or ref off the target article breaks the public root contract | `audit:ChatMessage/§3` |
239
+ | AR3 | Source review plus component-scoped accessibility audit | rich and interactive descendants | The wrapper introduces an interactive role or takes ownership away from descendants | `audit:ChatMessage/§1` |
240
+ | RTL | `ChatMessage.stories.tsx`; component-scoped RTL audit | all senders; narrow long content | Physical direction keeps user or assistant on the same side in both directions | `audit:ChatMessage/§9` |
241
+
242
+ ## Decision log
243
+
244
+ - **DEC-1 — Slot emptiness follows the rendered DOM.** (`proposed`, awaiting
245
+ owner approval) ChatMessage skips only `null`, boolean, and empty-string
246
+ slot values. Any other content is passed to React unchanged; its wrapper
247
+ collapses with `:empty` when it renders nothing, and the article's naming
248
+ falls back to `aria-label` when the referenced name renders no text.
249
+ Rejected: inspecting arrays, Fragments, or iterables, which cannot see
250
+ component output and consumes one-shot iterators.
251
+
252
+ ## Open questions
253
+
254
+ - **OQ1 — Should the built-in name become a separately themeable anatomy part?**
255
+ (`human-api`) The released family inventory exposes only the aggregate
256
+ `chat-message` target, while the name's explicit type and colour prevent a root
257
+ override from reaching it by inheritance. Adding or splitting a target changes
258
+ public theming API, so this draft records the reachability gap without choosing a
259
+ remedy.
260
+
261
+ ## Content boundary
262
+
263
+ This file does not duplicate consumer prop tables or examples, current audit
264
+ scores, screenshot matrices, or shared-system rules. The audit PR owns the closed
265
+ run evidence required by `spec:AST-029/FR5`.