@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
@@ -1,9 +1,34 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
+ import {createElement, createRef, Fragment, type ReactNode} from 'react';
3
4
  import {describe, it, expect} from 'vitest';
4
5
  import {render, screen} from '@testing-library/react';
5
6
  import {ChatMessage} from './ChatMessage';
6
7
  import {ChatMessageBubble} from './ChatMessageBubble';
8
+ import {ChatMessageList} from './ChatMessageList';
9
+
10
+ // Content that renders nothing but is not a skipped scalar: its wrapper
11
+ // renders and collapses through CSS instead of the content being inspected.
12
+ const contentThatRendersNothing: [string, () => ReactNode][] = [
13
+ ['an empty array', () => []],
14
+ ['an array of non-rendering values', () => [null, false, '']],
15
+ ['an empty Fragment', () => createElement(Fragment)],
16
+ ['a component that returns null', () => <RendersNothing />],
17
+ ];
18
+
19
+ function RendersNothing() {
20
+ return null;
21
+ }
22
+
23
+ function OpaqueName() {
24
+ return <span>Navi</span>;
25
+ }
26
+
27
+ /** The name wrapper the article's aria-labelledby points at. */
28
+ function labelTarget(article: HTMLElement): HTMLElement | null {
29
+ const id = article.getAttribute('aria-labelledby');
30
+ return id == null ? null : article.ownerDocument.getElementById(id);
31
+ }
7
32
 
8
33
  describe('ChatMessage', () => {
9
34
  it('renders children', () => {
@@ -33,6 +58,60 @@ describe('ChatMessage', () => {
33
58
  expect(screen.queryByText('System')).toBeNull();
34
59
  });
35
60
 
61
+ it.each([false, true, ''])('does not render a name wrapper for %j', name => {
62
+ render(
63
+ <ChatMessage sender="assistant" name={name}>
64
+ <span>Summary</span>
65
+ </ChatMessage>,
66
+ );
67
+ const article = screen.getByRole('article');
68
+ expect(article).toHaveAccessibleName('Message from assistant');
69
+ expect(article).not.toHaveAttribute('aria-labelledby');
70
+ expect(article.querySelector('div:empty')).toBeNull();
71
+ });
72
+
73
+ it('preserves a numeric name', () => {
74
+ render(
75
+ <ChatMessage sender="assistant" name={0}>
76
+ <span>Summary</span>
77
+ </ChatMessage>,
78
+ );
79
+ expect(screen.getByRole('article')).toHaveAccessibleName('0');
80
+ expect(screen.getByText('0')).toBeInTheDocument();
81
+ });
82
+
83
+ it.each(contentThatRendersNothing)(
84
+ 'collapses the name wrapper and keeps the fallback label for %s',
85
+ (_label, createName) => {
86
+ render(
87
+ <ChatMessage sender="assistant" name={createName()}>
88
+ <span>Summary</span>
89
+ </ChatMessage>,
90
+ );
91
+ const article = screen.getByRole('article');
92
+ const target = labelTarget(article);
93
+ // The label reference resolves to no text, so the accessible name falls
94
+ // back to aria-label; real Chromium computes "Message from assistant".
95
+ expect(target).not.toBeNull();
96
+ expect(target).toBeEmptyDOMElement();
97
+ expect(getComputedStyle(target as HTMLElement).display).toBe('none');
98
+ expect(article).toHaveAttribute('aria-label', 'Message from assistant');
99
+ },
100
+ );
101
+
102
+ it('labels the article by a name that renders text', () => {
103
+ render(
104
+ <ChatMessage sender="assistant" name={<OpaqueName />}>
105
+ <span>Summary</span>
106
+ </ChatMessage>,
107
+ );
108
+ const article = screen.getByRole('article');
109
+ expect(article).toHaveAccessibleName('Navi');
110
+ expect(
111
+ getComputedStyle(labelTarget(article) as HTMLElement).display,
112
+ ).not.toBe('none');
113
+ });
114
+
36
115
  it('renders avatar for assistant', () => {
37
116
  render(
38
117
  <ChatMessage
@@ -53,6 +132,53 @@ describe('ChatMessage', () => {
53
132
  expect(screen.queryByTestId('avatar')).toBeNull();
54
133
  });
55
134
 
135
+ it.each([false, true, ''])(
136
+ 'does not render an avatar wrapper for %j',
137
+ avatar => {
138
+ render(
139
+ <ChatMessage sender="assistant" avatar={avatar}>
140
+ <span>Summary</span>
141
+ </ChatMessage>,
142
+ );
143
+ expect(screen.getByRole('article').querySelector('div:empty')).toBeNull();
144
+ },
145
+ );
146
+
147
+ it('preserves a numeric avatar', () => {
148
+ render(
149
+ <ChatMessage sender="assistant" avatar={0}>
150
+ <span>Summary</span>
151
+ </ChatMessage>,
152
+ );
153
+ expect(screen.getByText('0')).toBeInTheDocument();
154
+ });
155
+
156
+ it.each(contentThatRendersNothing)(
157
+ 'collapses the avatar wrapper for %s',
158
+ (_label, createAvatar) => {
159
+ render(
160
+ <ChatMessage sender="assistant" avatar={createAvatar()}>
161
+ <span>Summary</span>
162
+ </ChatMessage>,
163
+ );
164
+ const wrapper = screen.getByRole('article').firstElementChild;
165
+ expect(wrapper).toBeEmptyDOMElement();
166
+ expect(getComputedStyle(wrapper as HTMLElement).display).toBe('none');
167
+ },
168
+ );
169
+
170
+ it('keeps an avatar wrapper that renders content visible', () => {
171
+ render(
172
+ <ChatMessage
173
+ sender="assistant"
174
+ avatar={<span data-testid="avatar">N</span>}>
175
+ <span>Summary</span>
176
+ </ChatMessage>,
177
+ );
178
+ const wrapper = screen.getByTestId('avatar').parentElement;
179
+ expect(getComputedStyle(wrapper as HTMLElement).display).not.toBe('none');
180
+ });
181
+
56
182
  it('applies sender class', () => {
57
183
  render(
58
184
  <ChatMessage sender="user" data-testid="msg">
@@ -95,4 +221,124 @@ describe('ChatMessage', () => {
95
221
  );
96
222
  expect(screen.getByTestId('custom-content')).toBeTruthy();
97
223
  });
224
+
225
+ it('renders metadata for a non-system sender', () => {
226
+ render(
227
+ <ChatMessage sender="assistant" metadata={<span>Updated just now</span>}>
228
+ <span>Summary</span>
229
+ </ChatMessage>,
230
+ );
231
+ expect(screen.getByText('Updated just now')).toBeInTheDocument();
232
+ });
233
+
234
+ it('hides metadata for a system sender', () => {
235
+ render(
236
+ <ChatMessage sender="system" metadata={<span>Hidden metadata</span>}>
237
+ <span>Maintenance complete</span>
238
+ </ChatMessage>,
239
+ );
240
+ expect(screen.queryByText('Hidden metadata')).not.toBeInTheDocument();
241
+ });
242
+
243
+ it.each([false, true, ''])(
244
+ 'does not render an empty metadata wrapper for %j',
245
+ metadata => {
246
+ render(
247
+ <ChatMessage sender="assistant" metadata={metadata}>
248
+ <span>Summary</span>
249
+ </ChatMessage>,
250
+ );
251
+ expect(screen.getByRole('article').querySelector('div:empty')).toBeNull();
252
+ },
253
+ );
254
+
255
+ it('preserves numeric metadata', () => {
256
+ render(
257
+ <ChatMessage sender="assistant" metadata={0}>
258
+ <span>Summary</span>
259
+ </ChatMessage>,
260
+ );
261
+ expect(screen.getByText('0')).toBeInTheDocument();
262
+ });
263
+
264
+ it.each(contentThatRendersNothing)(
265
+ 'collapses the metadata wrapper for %s',
266
+ (_label, createMetadata) => {
267
+ render(
268
+ <ChatMessage sender="assistant" metadata={createMetadata()}>
269
+ <span>Summary</span>
270
+ </ChatMessage>,
271
+ );
272
+ const column = screen.getByText('Summary').parentElement?.parentElement;
273
+ const wrapper = column?.lastElementChild;
274
+ expect(wrapper).toBeEmptyDOMElement();
275
+ expect(getComputedStyle(wrapper as HTMLElement).display).toBe('none');
276
+ },
277
+ );
278
+
279
+ it('keeps metadata that renders content visible', () => {
280
+ render(
281
+ <ChatMessage sender="assistant" metadata={<span>Updated</span>}>
282
+ <span>Summary</span>
283
+ </ChatMessage>,
284
+ );
285
+ const wrapper = screen.getByText('Updated').parentElement;
286
+ expect(getComputedStyle(wrapper as HTMLElement).display).not.toBe('none');
287
+ });
288
+
289
+ it('uses balanced density without list context', () => {
290
+ render(
291
+ <ChatMessage sender="assistant" data-testid="message">
292
+ <span>Summary</span>
293
+ </ChatMessage>,
294
+ );
295
+ expect(screen.getByTestId('message')).toHaveAttribute(
296
+ 'data-density',
297
+ 'balanced',
298
+ );
299
+ });
300
+
301
+ it('inherits list density and lets an explicit prop override it', () => {
302
+ render(
303
+ <ChatMessageList density="spacious">
304
+ <ChatMessage sender="assistant" data-testid="inherited">
305
+ <span>Inherited</span>
306
+ </ChatMessage>
307
+ <ChatMessage
308
+ sender="assistant"
309
+ density="compact"
310
+ data-testid="explicit">
311
+ <span>Explicit</span>
312
+ </ChatMessage>
313
+ </ChatMessageList>,
314
+ );
315
+ expect(screen.getByTestId('inherited')).toHaveAttribute(
316
+ 'data-density',
317
+ 'spacious',
318
+ );
319
+ expect(screen.getByTestId('explicit')).toHaveAttribute(
320
+ 'data-density',
321
+ 'compact',
322
+ );
323
+ });
324
+
325
+ it('forwards root props and ref while preserving the theme target', () => {
326
+ const ref = createRef<HTMLElement>();
327
+ render(
328
+ <ChatMessage
329
+ ref={ref}
330
+ sender="assistant"
331
+ data-testid="message"
332
+ data-purpose="summary"
333
+ className="consumer-message"
334
+ style={{order: 2}}>
335
+ <span>Summary</span>
336
+ </ChatMessage>,
337
+ );
338
+ const message = screen.getByTestId('message');
339
+ expect(ref.current).toBe(message);
340
+ expect(message).toHaveAttribute('data-purpose', 'summary');
341
+ expect(message).toHaveClass('astryx-chat-message', 'consumer-message');
342
+ expect(message.style.order).toBe('2');
343
+ });
98
344
  });
@@ -16,6 +16,7 @@
16
16
  * SYNC: When modified, update these files to stay in sync:
17
17
  * - /packages/core/src/Chat/index.ts (exports)
18
18
  * - /apps/storybook/stories/Chat.stories.tsx
19
+ * - /apps/storybook/stories/ChatMessage.stories.tsx
19
20
  * - /packages/cli/assets/templates/blocks/components/ChatMessage/ (block examples)
20
21
  */
21
22
 
@@ -33,7 +34,7 @@ import {
33
34
  type ChatMessageSender,
34
35
  type ChatDensity,
35
36
  } from './ChatContext';
36
- import {mergeProps} from '../utils';
37
+ import {isRenderable, mergeProps} from '../utils';
37
38
  import type {BaseProps} from '../BaseProps';
38
39
  import {themeProps} from '../utils/themeProps';
39
40
  import {useTranslator} from '../i18n';
@@ -49,9 +50,17 @@ export interface ChatMessageProps extends BaseProps<HTMLElement> {
49
50
  * should span the full message column.
50
51
  */
51
52
  children: ReactNode;
53
+ /**
54
+ * Avatar content rendered beside a non-system message.
55
+ * `null`, booleans, and empty strings create no slot; any other content that
56
+ * renders nothing collapses its slot.
57
+ */
52
58
  avatar?: ReactNode;
53
59
  /**
54
60
  * Sender name rendered above the message body.
61
+ * `null`, booleans, and empty strings create no slot, and the message is
62
+ * labelled by the translated sender fallback; any other content that renders
63
+ * nothing collapses its slot and falls back to the same label.
55
64
  * Use when the first child is raw content (not a bubble).
56
65
  * If the first child is a ChatMessageBubble, put the name on the
57
66
  * bubble's `name` prop instead — it aligns with the bubble's padding.
@@ -59,6 +68,8 @@ export interface ChatMessageProps extends BaseProps<HTMLElement> {
59
68
  name?: ReactNode;
60
69
  /**
61
70
  * Metadata rendered below the message body.
71
+ * `null`, booleans, and empty strings create no slot; any other content that
72
+ * renders nothing collapses its slot.
62
73
  * Use when the last child is raw content (not a bubble).
63
74
  * If the last child is a ChatMessageBubble, put metadata on the
64
75
  * bubble's `metadata` prop instead — it aligns with the bubble's padding.
@@ -94,7 +105,11 @@ const styles = stylex.create({
94
105
  flexDirection: 'row',
95
106
  justifyContent: 'center',
96
107
  },
108
+ // Slot wrappers collapse when the caller's content renders nothing, so an
109
+ // empty Fragment or a component that returns null leaves no gap. Content is
110
+ // never inspected to decide this.
97
111
  avatarWrap: {
112
+ display: {default: null, ':empty': 'none'},
98
113
  flexShrink: 0,
99
114
  ':has(~ * [data-chat-name])': {
100
115
  marginBlockStart: spacingVars['--spacing-5'],
@@ -117,12 +132,16 @@ const styles = stylex.create({
117
132
  alignItems: 'flex-end',
118
133
  },
119
134
  name: {
135
+ display: {default: null, ':empty': 'none'},
120
136
  fontSize: typeScaleVars['--text-supporting-size'],
121
137
  fontWeight: fontWeightVars['--font-weight-semibold'],
122
138
  color: colorVars['--color-text-secondary'],
123
139
  lineHeight: typeScaleVars['--text-supporting-leading'],
124
140
  marginBlockEnd: spacingVars['--spacing-1'],
125
141
  },
142
+ metadata: {
143
+ display: {default: null, ':empty': 'none'},
144
+ },
126
145
  childrenWrap: {
127
146
  display: 'flex',
128
147
  flexDirection: 'column',
@@ -219,8 +238,9 @@ export function ChatMessage({
219
238
  : styles.childrenAssistant;
220
239
 
221
240
  const isSystem = sender === 'system';
222
- const hasAvatar = avatar != null && !isSystem;
223
- const hasName = name != null && !isSystem;
241
+ const hasAvatar = !isSystem && isRenderable(avatar);
242
+ const hasName = !isSystem && isRenderable(name);
243
+ const hasMetadata = !isSystem && isRenderable(metadata);
224
244
  const nameId = useId();
225
245
 
226
246
  return (
@@ -229,9 +249,10 @@ export function ChatMessage({
229
249
  {...rest}
230
250
  ref={ref}
231
251
  data-testid={testId}
232
- aria-label={
233
- !hasName ? t('@astryx.chatMessage.messageFrom', {sender}) : undefined
234
- }
252
+ // The visible name labels the article. When it renders no text, the
253
+ // label reference resolves to nothing and the accessible name falls
254
+ // back to aria-label (accessible name computation 1.2, step 2B).
255
+ aria-label={t('@astryx.chatMessage.messageFrom', {sender})}
235
256
  aria-labelledby={hasName ? nameId : undefined}
236
257
  {...mergeProps(
237
258
  themeProps('chat-message', {sender, density}),
@@ -262,7 +283,9 @@ export function ChatMessage({
262
283
  {children}
263
284
  </div>
264
285
 
265
- {metadata != null && !isSystem && <div>{metadata}</div>}
286
+ {hasMetadata && (
287
+ <div {...stylex.props(styles.metadata)}>{metadata}</div>
288
+ )}
266
289
  </div>
267
290
  </article>
268
291
  </ChatMessageContext>
@@ -80,7 +80,7 @@ Consumer migration instructions belong in consumer docs and release notes.
80
80
  - Sender identity, avatar, bubble, transcript ordering, or scrolling.
81
81
  - Divider rule paint, label placement, separator role, or separator naming, which are delegated to `component:Divider`.
82
82
  - Long Divider-label overflow and forced-colors rule paint, which remain shared `component:Divider` advisories rather than ChatSystemMessage-owned defects.
83
- - Divider's falsey-label rendering. Empty string and empty Fragment content leave the separator without a visible label or accessible name; numeric zero renders stray `00` text with no accessible name. This is a shared `spec:AST-002/FR15` advisory routed to `component:Divider`.
83
+ - Divider's skipped-label rendering. Empty strings and empty Fragments leave the separator without a visible label or accessible name; numeric zero renders one visible `0` and names the separator `0`. These partitions remain owned by `component:Divider`.
84
84
  - The semantic or directional meaning of caller-provided content or icon artwork.
85
85
  - Announcement timing or spoken output; claims at that layer require `spec:AST-009` evidence.
86
86
 
@@ -99,14 +99,14 @@ observable public surface without deciding the unresolved divider/icon contract.
99
99
 
100
100
  ## Behavioral and layout contract
101
101
 
102
- | ID | Candidate invariant | Basis | Draft review state |
103
- | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
104
- | FR1 | The default branch MUST render the required content in a centered, noninteractive status row, allow long content to wrap within the available inline space, and preserve optional icon geometry. | Current source, released docs, tests, and examples | Verified current behavior |
105
- | FR2 | For truthy content, the divider branch MUST render the content through Divider as a labelled horizontal separator inside the component's status root. Falsey content follows the shared Divider advisory recorded in AV1. | Current source, released docs, Divider implementation, tests, and examples | Verified current behavior; shared Divider advisory |
106
- | FR3 | The divider branch currently ignores `icon`; this draft MUST NOT convert that observation into a stable restriction, a requirement to render the icon, or a warning policy. | `architecture:public-component-api/INV3`, `architecture:public-component-api/INV5`, and `spec:AST-002/FR15` | Human API decision required; see OQ1 |
107
- | FR4 | Supported root DOM, data, ARIA, className, style, xstyle, and ref inputs MUST reach or compose on the root while component-owned `role="status"` and reflected `variant` remain intact. | Current source plus `architecture:public-component-api/INV5-INV8` | Verified current behavior and existing focused evidence |
108
- | FR5 | The component MUST use semantic color, spacing, and typography tokens and logical layout; the `variant` state remains reflected on the single `chat-system-message` target. | Current source, `architecture:theme-tokens`, and theming architecture | Verified current behavior |
109
- | FR6 | The component remains render-only: props determine output without Effects, state mirrors, observers, listeners, timers, or owned async resources. | Current source and `architecture:react-component-runtime/INV1-INV8` | Verified current behavior |
102
+ | ID | Candidate invariant | Basis | Draft review state |
103
+ | --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
104
+ | FR1 | The default branch MUST render the required content in a centered, noninteractive status row, allow long content to wrap within the available inline space, and preserve optional icon geometry. | Current source, released docs, tests, and examples | Verified current behavior |
105
+ | FR2 | The divider branch MUST render renderable content through Divider as a labelled horizontal separator inside the component's status root. Skipped content follows Divider's delegated label contract. | Current source, released docs, Divider implementation, tests, and examples | Verified current behavior; delegated Divider partitions |
106
+ | FR3 | The divider branch currently ignores `icon`; this draft MUST NOT convert that observation into a stable restriction, a requirement to render the icon, or a warning policy. | `architecture:public-component-api/INV3`, `architecture:public-component-api/INV5`, and `spec:AST-002/FR15` | Human API decision required; see OQ1 |
107
+ | FR4 | Supported root DOM, data, ARIA, className, style, xstyle, and ref inputs MUST reach or compose on the root while component-owned `role="status"` and reflected `variant` remain intact. | Current source plus `architecture:public-component-api/INV5-INV8` | Verified current behavior and existing focused evidence |
108
+ | FR5 | The component MUST use semantic color, spacing, and typography tokens and logical layout; the `variant` state remains reflected on the single `chat-system-message` target. | Current source, `architecture:theme-tokens`, and theming architecture | Verified current behavior |
109
+ | FR6 | The component remains render-only: props determine output without Effects, state mirrors, observers, listeners, timers, or owned async resources. | Current source and `architecture:react-component-runtime/INV1-INV8` | Verified current behavior |
110
110
 
111
111
  ### Allowed variation
112
112
 
@@ -116,9 +116,8 @@ observable public surface without deciding the unresolved divider/icon contract.
116
116
  content unchanged: empty string and empty Fragment content remain empty, while
117
117
  numeric zero renders one `0`. In the divider variant, the status root and separator
118
118
  remain; empty string and empty Fragment content produce no visible label or
119
- accessible name, while numeric zero paints stray `00` text with no accessible name.
120
- The divider's falsey-label behavior is a shared `spec:AST-002/FR15` advisory routed
121
- to `component:Divider`.
119
+ accessible name, while numeric zero renders one visible `0` and names the
120
+ separator `0`. Divider owns those label partitions.
122
121
  - **AV2 - Caller styling.** Supported root styling inputs may extend the component
123
122
  without replacing owned status semantics or variant reflection.
124
123
  - **AV3 - Delegated Divider paint.** Divider owns its rule and label paint and may
@@ -126,15 +125,15 @@ observable public surface without deciding the unresolved divider/icon contract.
126
125
 
127
126
  ### Representative states
128
127
 
129
- | State | Required invariant | Allowed variation |
130
- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
131
- | Default | Centered status content is present. | Content, theme, and root styling. |
132
- | Default with icon | Optional icon content and visible content render together. | Caller-provided artwork and content. |
133
- | Default long content | Content wraps without crossing either inline edge or creating page overflow. | Theme and localized content. |
134
- | Divider | A labelled horizontal separator presents the supplied content. | Label content, theme, and root styling. |
135
- | Divider with icon | Current output is measured without deciding the ignored-input contract. | No behavior change until OQ1 is resolved. |
136
- | Empty ReactNode | The default variant keeps `role="status"` and renders content unchanged (empty or one `0`). In the divider variant, the status root and separator remain; empty content has no label/name, while numeric zero paints stray `00` without a name. | Empty string, numeric zero, empty Fragment. |
137
- | Narrow/coarse pointer | The noninteractive row remains visible without horizontal page overflow. | Theme and supported content. |
128
+ | State | Required invariant | Allowed variation |
129
+ | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
130
+ | Default | Centered status content is present. | Content, theme, and root styling. |
131
+ | Default with icon | Optional icon content and visible content render together. | Caller-provided artwork and content. |
132
+ | Default long content | Content wraps without crossing either inline edge or creating page overflow. | Theme and localized content. |
133
+ | Divider | A labelled horizontal separator presents the supplied content. | Label content, theme, and root styling. |
134
+ | Divider with icon | Current output is measured without deciding the ignored-input contract. | No behavior change until OQ1 is resolved. |
135
+ | Empty ReactNode | The default variant keeps `role="status"` and renders content unchanged (empty or one `0`). In the divider variant, the status root and separator remain; empty content has no label/name, while numeric zero renders and names one `0`. | Empty string, numeric zero, empty Fragment. |
136
+ | Narrow/coarse pointer | The noninteractive row remains visible without horizontal page overflow. | Theme and supported content. |
138
137
 
139
138
  ### Transformation and precedence order
140
139
 
@@ -156,10 +155,10 @@ observable public surface without deciding the unresolved divider/icon contract.
156
155
  - **AR1 - Status exposure.** The root currently exposes `role="status"` for both
157
156
  variants. Browser evidence may prove role exposure; spoken announcement timing is
158
157
  outside this draft and requires `spec:AST-009` evidence.
159
- - **AR2 - Divider relationship.** For truthy content, the divider branch delegates a
158
+ - **AR2 - Divider relationship.** Renderable content in the divider branch delegates a
160
159
  horizontal `separator` whose labelled target contains the visible label; focused DOM
161
- tests verify the computed accessible name. Falsey content follows the shared Divider
162
- advisory in AV1.
160
+ tests verify the computed accessible name. Skipped content follows Divider's
161
+ delegated label contract.
163
162
  - **AR3 - Caller content.** Visible content remains the self-contained message.
164
163
  Caller-provided icon content does not replace that text.
165
164
  - **AR4 - Noninteractive surface.** The component introduces no focus target,
@@ -180,8 +179,8 @@ icon artwork, live-region timing, or divider/icon behavior.
180
179
  ## Family and system relationships
181
180
 
182
181
  - `component:Divider` owns the delegated separator structure, role, name,
183
- paint, long-label overflow, and falsey-label behavior. The numeric-zero `00`
184
- output with no accessible name is a shared `spec:AST-002/FR15` advisory.
182
+ paint, long-label overflow, and label partitions. Numeric zero renders as one
183
+ visible label and names the separator `0`; skipped content remains unlabelled.
185
184
  - `architecture:public-component-api` owns released exports, BaseProps reachability,
186
185
  styling composition, and the ref contract.
187
186
  - `architecture:component-theming-surface` owns the target/anatomy mapping and
@@ -197,17 +196,17 @@ icon artwork, live-region timing, or divider/icon behavior.
197
196
 
198
197
  ## Verification map
199
198
 
200
- | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
201
- | ------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
202
- | FR1, AR1, AR3 | `ChatSystemMessage.test.tsx`, scoped axe, and exact-head Chromium receipts | Default, default with icon, long content, nested live log, neutral light/dark | Missing content, status role, icon content, wrapping, or browser-visible subject fails focused or browser evidence. | `audit:ChatSystemMessage/accessibility` |
203
- | FR2, AR2 | `ChatSystemMessage.test.tsx`, Divider tests, scoped axe, and exact-head Chromium receipts | Truthy divider labels plus empty string, numeric zero, and empty Fragment observations | Lost separator role, truthy label association, orientation, content, or paint fails focused or browser evidence; falsey anomalies stay routed to Divider. | `audit:ChatSystemMessage/behavior` |
204
- | FR3 | Closed audit inventory and exact-head observation of the divider/icon partition | Divider with supplied icon | The audit must expose, not silently omit, the unresolved branch; implementation remains unchanged until owner decision. | `audit:ChatSystemMessage/public-api` |
205
- | FR4 | Mutation-sensitive root-passthrough tests, source inspection, type checks, and target reflection | Default and divider roots | Removing ref, className, style, xstyle, ARIA forwarding, or owned role precedence fails focused tests. | `audit:ChatSystemMessage/public-api` |
206
- | FR5 | `themingTargets.test.ts`, source inspection, generated build, and exact-head computed paint | Both variants and color modes | Target/state metadata drift, raw styling, physical layout, or unresolved mode paint fails checks or browser evidence. | `audit:ChatSystemMessage/theming` |
207
- | FR6 | Source inspection, strict lint, and component test suite | Every render | Adding hidden state or an owned external resource triggers runtime review and corresponding evidence. | `audit:ChatSystemMessage/code-health` |
208
- | RTL relation | Source-hashed verified-N/A record | Both variants and caller-owned icon content | Directional component-owned content, physical positioning, ordering, scroll/drag, overlay, or horizontal keyboard behavior invalidates the record. | `audit:ChatSystemMessage/i18n-rtl` |
209
- | Browser matrix | `ChatSystemMessage.a11y.chromium.spec.ts` exact-head 10-sensor receipts | Default, icon, wrapped long content, divider, unresolved divider/icon, nested live log, 320px/coarse pointer, light/dark, D7 pairs | Stale build, wrong story, incorrect mode, missing semantics, default overflow, failed contrast, blank crop, or page error fails closed. | `audit:ChatSystemMessage/visual` |
210
- | Documentation and surface | Consumer docs, published declarations, block metadata, explicit story ownership, exports, and `check:knowledge` | Props, `./Chat` subpath, four blocks, draft record | Missing or stale public docs, export, owner route, target map, or knowledge shape fails validation or review. | `audit:ChatSystemMessage/docs` |
199
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
200
+ | ------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
201
+ | FR1, AR1, AR3 | `ChatSystemMessage.test.tsx`, scoped axe, and exact-head Chromium receipts | Default, default with icon, long content, nested live log, neutral light/dark | Missing content, status role, icon content, wrapping, or browser-visible subject fails focused or browser evidence. | `audit:ChatSystemMessage/accessibility` |
202
+ | FR2, AR2 | `ChatSystemMessage.test.tsx`, Divider tests, scoped axe, and exact-head Chromium receipts | Labelled divider content plus empty string, numeric zero, and empty Fragment observations | Lost separator role, label association, orientation, content, or paint fails focused or browser evidence; skipped partitions stay delegated to Divider. | `audit:ChatSystemMessage/behavior` |
203
+ | FR3 | Closed audit inventory and exact-head observation of the divider/icon partition | Divider with supplied icon | The audit must expose, not silently omit, the unresolved branch; implementation remains unchanged until owner decision. | `audit:ChatSystemMessage/public-api` |
204
+ | FR4 | Mutation-sensitive root-passthrough tests, source inspection, type checks, and target reflection | Default and divider roots | Removing ref, className, style, xstyle, ARIA forwarding, or owned role precedence fails focused tests. | `audit:ChatSystemMessage/public-api` |
205
+ | FR5 | `themingTargets.test.ts`, source inspection, generated build, and exact-head computed paint | Both variants and color modes | Target/state metadata drift, raw styling, physical layout, or unresolved mode paint fails checks or browser evidence. | `audit:ChatSystemMessage/theming` |
206
+ | FR6 | Source inspection, strict lint, and component test suite | Every render | Adding hidden state or an owned external resource triggers runtime review and corresponding evidence. | `audit:ChatSystemMessage/code-health` |
207
+ | RTL relation | Source-hashed verified-N/A record | Both variants and caller-owned icon content | Directional component-owned content, physical positioning, ordering, scroll/drag, overlay, or horizontal keyboard behavior invalidates the record. | `audit:ChatSystemMessage/i18n-rtl` |
208
+ | Browser matrix | `ChatSystemMessage.a11y.chromium.spec.ts` exact-head 10-sensor receipts | Default, icon, wrapped long content, divider, unresolved divider/icon, nested live log, 320px/coarse pointer, light/dark, D7 pairs | Stale build, wrong story, incorrect mode, missing semantics, default overflow, failed contrast, blank crop, or page error fails closed. | `audit:ChatSystemMessage/visual` |
209
+ | Documentation and surface | Consumer docs, published declarations, block metadata, explicit story ownership, exports, and `check:knowledge` | Props, `./Chat` subpath, four blocks, draft record | Missing or stale public docs, export, owner route, target map, or knowledge shape fails validation or review. | `audit:ChatSystemMessage/docs` |
211
210
 
212
211
  ## Decision log
213
212
 
@@ -152,7 +152,7 @@ describe('ChatSystemMessage', () => {
152
152
  expect(root).toContainElement(screen.getByRole('separator'));
153
153
  });
154
154
 
155
- it('records Divider numeric-zero output for the shared FR15 advisory', () => {
155
+ it('keeps the numeric-zero label visible and names the shared divider', () => {
156
156
  render(
157
157
  <ChatSystemMessage variant="divider" data-testid="divider-zero">
158
158
  {0}
@@ -160,8 +160,8 @@ describe('ChatSystemMessage', () => {
160
160
  );
161
161
  const root = screen.getByTestId('divider-zero');
162
162
  const separator = screen.getByRole('separator');
163
- expect(root).toHaveTextContent('00');
164
- expect(separator).not.toHaveAccessibleName();
165
- expect(separator).not.toHaveAttribute('aria-labelledby');
163
+ expect(root).toHaveTextContent(/^0$/);
164
+ expect(separator).toHaveAccessibleName('0');
165
+ expect(separator).toHaveAttribute('aria-labelledby');
166
166
  });
167
167
  });