@astryxdesign/core 0.6.2 → 0.6.3-canary.0899f21

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 (549) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/dist/BottomSheet/BottomSheet.d.ts +1 -1
  3. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  4. package/dist/BottomSheet/BottomSheet.js +3 -1
  5. package/dist/BottomSheet/BottomSheetPanel.d.ts +7 -5
  6. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  7. package/dist/BottomSheet/BottomSheetPanel.js +56 -19
  8. package/dist/Button/Button.d.ts.map +1 -1
  9. package/dist/Button/Button.js +13 -28
  10. package/dist/Chat/ChatComposer.d.ts.map +1 -1
  11. package/dist/Chat/ChatComposer.js +10 -5
  12. package/dist/Chat/ChatComposerDrawer.d.ts +19 -5
  13. package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
  14. package/dist/Chat/ChatComposerDrawer.js +48 -23
  15. package/dist/Chat/ChatComposerInput.d.ts +11 -3
  16. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  17. package/dist/Chat/ChatComposerInput.js +63 -16
  18. package/dist/Chat/ChatDictationButton.d.ts +3 -2
  19. package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
  20. package/dist/Chat/ChatDictationButton.js +6 -4
  21. package/dist/Chat/ChatLayout.d.ts +19 -0
  22. package/dist/Chat/ChatLayout.d.ts.map +1 -1
  23. package/dist/Chat/ChatLayout.js +19 -0
  24. package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
  25. package/dist/Chat/ChatLayoutScrollButton.js +44 -34
  26. package/dist/Chat/ChatMessageBubble.d.ts +2 -0
  27. package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
  28. package/dist/Chat/ChatMessageBubble.js +5 -3
  29. package/dist/Chat/ChatMessageList.d.ts +2 -1
  30. package/dist/Chat/ChatMessageList.d.ts.map +1 -1
  31. package/dist/Chat/ChatMessageList.js +4 -3
  32. package/dist/Chat/ChatMessageMetadata.d.ts +5 -4
  33. package/dist/Chat/ChatMessageMetadata.d.ts.map +1 -1
  34. package/dist/Chat/ChatMessageMetadata.js +12 -9
  35. package/dist/Chat/ChatSendButton.js +1 -1
  36. package/dist/Chat/index.d.ts +1 -1
  37. package/dist/Chat/index.d.ts.map +1 -1
  38. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  39. package/dist/CheckboxInput/CheckboxInput.js +12 -2
  40. package/dist/Citation/Citation.d.ts +1 -1
  41. package/dist/Citation/Citation.d.ts.map +1 -1
  42. package/dist/Citation/Citation.js +3 -2
  43. package/dist/CodeBlock/highlightRanges.d.ts.map +1 -1
  44. package/dist/CodeBlock/highlightRanges.js +14 -3
  45. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  46. package/dist/Collapsible/Collapsible.js +6 -1
  47. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  48. package/dist/CommandPalette/CommandPaletteInput.js +1 -1
  49. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  50. package/dist/ComplexSelector/ComplexSelector.js +1 -1
  51. package/dist/DateInput/DateInput.d.ts.map +1 -1
  52. package/dist/DateInput/DateInput.js +4 -4
  53. package/dist/DateInput/NativeDateField.d.ts.map +1 -1
  54. package/dist/DateInput/NativeDateField.js +16 -16
  55. package/dist/DateInput/TouchDateField.d.ts.map +1 -1
  56. package/dist/DateInput/TouchDateField.js +2 -2
  57. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  58. package/dist/DateRangeInput/DateRangeInput.js +4 -4
  59. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  60. package/dist/DateTimeInput/DateTimeInput.js +8 -8
  61. package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
  62. package/dist/DateTimeInput/TouchDateTimeField.js +4 -4
  63. package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts +9 -3
  64. package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts.map +1 -1
  65. package/dist/DateTimeInput/nativePickerSegmentStyles.js +3 -3
  66. package/dist/Dialog/DialogHeader.d.ts +8 -1
  67. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  68. package/dist/Dialog/DialogHeader.js +13 -4
  69. package/dist/Field/InputClearButton.d.ts +4 -2
  70. package/dist/Field/InputClearButton.d.ts.map +1 -1
  71. package/dist/Field/InputClearButton.js +4 -2
  72. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  73. package/dist/Field/PanelSearchInput.js +1 -1
  74. package/dist/FileInput/FileInput.d.ts.map +1 -1
  75. package/dist/FileInput/FileInput.js +7 -7
  76. package/dist/Icon/Icon.d.ts +6 -31
  77. package/dist/Icon/Icon.d.ts.map +1 -1
  78. package/dist/Icon/Icon.js +8 -71
  79. package/dist/Icon/IconDefaultSizeContext.d.ts +4 -0
  80. package/dist/Icon/IconDefaultSizeContext.d.ts.map +1 -0
  81. package/dist/Icon/IconDefaultSizeContext.js +18 -0
  82. package/dist/Icon/IconSize.stylex.d.ts +65 -0
  83. package/dist/Icon/IconSize.stylex.d.ts.map +1 -0
  84. package/dist/Icon/IconSize.stylex.js +75 -0
  85. package/dist/Item/Item.d.ts +1 -1
  86. package/dist/Item/Item.d.ts.map +1 -1
  87. package/dist/Item/Item.js +4 -3
  88. package/dist/Kbd/Kbd.d.ts +5 -3
  89. package/dist/Kbd/Kbd.d.ts.map +1 -1
  90. package/dist/Kbd/Kbd.js +36 -42
  91. package/dist/Link/Link.d.ts.map +1 -1
  92. package/dist/Link/Link.js +6 -2
  93. package/dist/Link/useLinkComponent.d.ts +5 -15
  94. package/dist/Link/useLinkComponent.d.ts.map +1 -1
  95. package/dist/Link/useLinkComponent.js +34 -51
  96. package/dist/List/List.d.ts +19 -1
  97. package/dist/List/List.d.ts.map +1 -1
  98. package/dist/List/List.js +9 -2
  99. package/dist/List/ListContext.d.ts +1 -0
  100. package/dist/List/ListContext.d.ts.map +1 -1
  101. package/dist/List/ListContext.js +2 -1
  102. package/dist/List/ListItem.d.ts +1 -1
  103. package/dist/List/ListItem.d.ts.map +1 -1
  104. package/dist/List/ListItem.js +9 -2
  105. package/dist/Markdown/Markdown.d.ts +8 -2
  106. package/dist/Markdown/Markdown.d.ts.map +1 -1
  107. package/dist/Markdown/Markdown.fr23.bench.d.ts +2 -0
  108. package/dist/Markdown/Markdown.fr23.bench.d.ts.map +1 -0
  109. package/dist/Markdown/Markdown.fr23.bench.js +150 -0
  110. package/dist/Markdown/Markdown.fr23.sampling.d.ts +2 -0
  111. package/dist/Markdown/Markdown.fr23.sampling.d.ts.map +1 -0
  112. package/dist/Markdown/Markdown.fr23.sampling.js +43 -0
  113. package/dist/Markdown/Markdown.js +262 -109
  114. package/dist/Markdown/ast.d.ts +162 -0
  115. package/dist/Markdown/ast.d.ts.map +1 -0
  116. package/dist/Markdown/ast.js +74 -0
  117. package/dist/Markdown/index.d.ts +5 -3
  118. package/dist/Markdown/index.d.ts.map +1 -1
  119. package/dist/Markdown/index.js +3 -2
  120. package/dist/Markdown/parser/index.d.ts +10 -0
  121. package/dist/Markdown/parser/index.d.ts.map +1 -0
  122. package/dist/Markdown/parser/index.js +10 -0
  123. package/dist/Markdown/parser.d.ts +126 -58
  124. package/dist/Markdown/parser.d.ts.map +1 -1
  125. package/dist/Markdown/parser.js +716 -167
  126. package/dist/Markdown/plugins/frontmatter.d.ts +32 -0
  127. package/dist/Markdown/plugins/frontmatter.d.ts.map +1 -0
  128. package/dist/Markdown/plugins/frontmatter.js +132 -0
  129. package/dist/Markdown/plugins/index.d.ts +17 -0
  130. package/dist/Markdown/plugins/index.d.ts.map +1 -0
  131. package/dist/Markdown/plugins/index.js +14 -0
  132. package/dist/Markdown/plugins/protocol.d.ts +191 -0
  133. package/dist/Markdown/plugins/protocol.d.ts.map +1 -0
  134. package/dist/Markdown/plugins/protocol.js +957 -0
  135. package/dist/Markdown/plugins/semanticFence.d.ts +36 -0
  136. package/dist/Markdown/plugins/semanticFence.d.ts.map +1 -0
  137. package/dist/Markdown/plugins/semanticFence.js +213 -0
  138. package/dist/Markdown/plugins/softBreaks.d.ts +3 -0
  139. package/dist/Markdown/plugins/softBreaks.d.ts.map +1 -0
  140. package/dist/Markdown/plugins/softBreaks.js +172 -0
  141. package/dist/Markdown/plugins/sourceDecoration.d.ts +76 -0
  142. package/dist/Markdown/plugins/sourceDecoration.d.ts.map +1 -0
  143. package/dist/Markdown/plugins/sourceDecoration.js +393 -0
  144. package/dist/Markdown/plugins/textTransform.d.ts +30 -0
  145. package/dist/Markdown/plugins/textTransform.d.ts.map +1 -0
  146. package/dist/Markdown/plugins/textTransform.js +374 -0
  147. package/dist/Markdown/remark.d.ts +196 -0
  148. package/dist/Markdown/remark.d.ts.map +1 -0
  149. package/dist/Markdown/remark.js +1062 -0
  150. package/dist/Markdown/url.d.ts +6 -0
  151. package/dist/Markdown/url.d.ts.map +1 -0
  152. package/dist/Markdown/url.js +31 -0
  153. package/dist/Markdown/utils.d.ts +4 -2
  154. package/dist/Markdown/utils.d.ts.map +1 -1
  155. package/dist/Markdown/utils.js +2 -1
  156. package/dist/MetadataList/MetadataList.js +3 -3
  157. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  158. package/dist/NumberInput/NumberInput.js +4 -4
  159. package/dist/Outline/index.d.ts +1 -0
  160. package/dist/Outline/index.d.ts.map +1 -1
  161. package/dist/Outline/parseOutlineFromMarkdown.d.ts +7 -1
  162. package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
  163. package/dist/Outline/parseOutlineFromMarkdown.js +14 -8
  164. package/dist/Outline/useOutlineFromMarkdown.d.ts +3 -1
  165. package/dist/Outline/useOutlineFromMarkdown.d.ts.map +1 -1
  166. package/dist/Outline/useOutlineFromMarkdown.js +8 -3
  167. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  168. package/dist/PowerSearch/PowerSearchEditPopover.js +46 -30
  169. package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
  170. package/dist/PowerSearch/PowerSearchValueEditor.js +5 -0
  171. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  172. package/dist/ProgressBar/ProgressBar.js +36 -31
  173. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  174. package/dist/RadioList/RadioListItem.js +13 -1
  175. package/dist/ScrollableArea/ScrollableArea.d.ts +1 -0
  176. package/dist/ScrollableArea/ScrollableArea.d.ts.map +1 -1
  177. package/dist/ScrollableArea/ScrollableArea.js +1 -0
  178. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  179. package/dist/SegmentedControl/SegmentedControlItem.js +5 -5
  180. package/dist/Selector/Selector.d.ts +9 -7
  181. package/dist/Selector/Selector.d.ts.map +1 -1
  182. package/dist/Selector/Selector.js +16 -7
  183. package/dist/SideNav/SideNav.d.ts +2 -1
  184. package/dist/SideNav/SideNav.d.ts.map +1 -1
  185. package/dist/SideNav/SideNav.js +9 -3
  186. package/dist/Slider/Slider.d.ts.map +1 -1
  187. package/dist/Slider/Slider.js +28 -12
  188. package/dist/Spinner/Spinner.d.ts.map +1 -1
  189. package/dist/Spinner/Spinner.js +23 -7
  190. package/dist/Switch/Switch.d.ts.map +1 -1
  191. package/dist/Switch/Switch.js +11 -0
  192. package/dist/TabList/Tab.d.ts +1 -1
  193. package/dist/TabList/Tab.d.ts.map +1 -1
  194. package/dist/TabList/Tab.js +20 -7
  195. package/dist/Table/Table.d.ts +1 -0
  196. package/dist/Table/Table.d.ts.map +1 -1
  197. package/dist/Table/Table.js +29 -13
  198. package/dist/TextArea/TextArea.d.ts.map +1 -1
  199. package/dist/TextArea/TextArea.js +1 -1
  200. package/dist/TextInput/TextInput.d.ts.map +1 -1
  201. package/dist/TextInput/TextInput.js +2 -2
  202. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  203. package/dist/TimeInput/TimeInput.js +4 -4
  204. package/dist/Timer/Timer.d.ts +49 -0
  205. package/dist/Timer/Timer.d.ts.map +1 -0
  206. package/dist/Timer/Timer.js +158 -0
  207. package/dist/Timer/index.d.ts +9 -0
  208. package/dist/Timer/index.d.ts.map +1 -0
  209. package/dist/Timer/index.js +10 -0
  210. package/dist/ToggleButton/ToggleButton.d.ts +9 -6
  211. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  212. package/dist/ToggleButton/ToggleButton.js +15 -27
  213. package/dist/TopNav/TopNavMenu.d.ts +1 -1
  214. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  215. package/dist/TopNav/TopNavMenu.js +3 -2
  216. package/dist/TreeList/TreeList.d.ts.map +1 -1
  217. package/dist/TreeList/TreeList.js +3 -0
  218. package/dist/TreeList/TreeListItem.d.ts +6 -1
  219. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  220. package/dist/TreeList/TreeListItem.js +12 -2
  221. package/dist/TreeList/TreeListTypes.d.ts +8 -1
  222. package/dist/TreeList/TreeListTypes.d.ts.map +1 -1
  223. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  224. package/dist/Typeahead/BaseTypeahead.js +16 -7
  225. package/dist/astryx.css +27 -11
  226. package/dist/hooks/scrollKeyboardDelegation.d.ts +3 -0
  227. package/dist/hooks/scrollKeyboardDelegation.d.ts.map +1 -0
  228. package/dist/hooks/scrollKeyboardDelegation.js +146 -0
  229. package/dist/hooks/useClickableContainer.d.ts +10 -1
  230. package/dist/hooks/useClickableContainer.d.ts.map +1 -1
  231. package/dist/hooks/useClickableContainer.js +15 -4
  232. package/dist/hooks/useScrollableArea.d.ts +6 -2
  233. package/dist/hooks/useScrollableArea.d.ts.map +1 -1
  234. package/dist/hooks/useScrollableArea.js +17 -5
  235. package/dist/index.d.ts +1 -0
  236. package/dist/index.d.ts.map +1 -1
  237. package/dist/index.js +3 -0
  238. package/dist/theme/declarationBoundary.d.ts +17 -0
  239. package/dist/theme/declarationBoundary.d.ts.map +1 -0
  240. package/dist/theme/declarationBoundary.js +343 -0
  241. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  242. package/dist/theme/derivedVarRegistry.js +4 -0
  243. package/dist/theme/generateThemeRules.d.ts +13 -7
  244. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  245. package/dist/theme/generateThemeRules.js +140 -34
  246. package/dist/utils/interactionOverlay.stylex.d.ts +8 -0
  247. package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
  248. package/dist/utils/interactionOverlay.stylex.js +9 -0
  249. package/dist/utils/safeUrl.d.ts +11 -0
  250. package/dist/utils/safeUrl.d.ts.map +1 -0
  251. package/dist/utils/safeUrl.js +78 -0
  252. package/locales/af-ZA.json +530 -1
  253. package/locales/ar-SA.json +490 -1
  254. package/locales/ca-ES.json +514 -1
  255. package/locales/cs-CZ.json +498 -1
  256. package/locales/da-DK.json +522 -1
  257. package/locales/de-DE.json +522 -1
  258. package/locales/el-GR.json +498 -1
  259. package/locales/es-ES.json +510 -1
  260. package/locales/fi-FI.json +498 -1
  261. package/locales/fr-FR.json +534 -1
  262. package/locales/he-IL.json +498 -1
  263. package/locales/hu-HU.json +498 -1
  264. package/locales/it-IT.json +510 -1
  265. package/locales/ja-JP.json +490 -1
  266. package/locales/ko-KR.json +498 -1
  267. package/locales/nl-NL.json +534 -1
  268. package/locales/no-NO.json +510 -1
  269. package/locales/pl-PL.json +506 -1
  270. package/locales/pt-BR.json +506 -1
  271. package/locales/pt-PT.json +506 -1
  272. package/locales/ro-RO.json +506 -1
  273. package/locales/ru-RU.json +498 -1
  274. package/locales/sr-SP.json +498 -1
  275. package/locales/sv-SE.json +502 -1
  276. package/locales/tr-TR.json +498 -1
  277. package/locales/uk-UA.json +498 -1
  278. package/locales/vi-VN.json +502 -1
  279. package/locales/zh-CN.json +490 -1
  280. package/locales/zh-TW.json +490 -1
  281. package/package.json +24 -4
  282. package/src/BottomSheet/BottomSheet.doc.mjs +8 -1
  283. package/src/BottomSheet/BottomSheet.spec.md +46 -20
  284. package/src/BottomSheet/BottomSheet.test.tsx +6 -3
  285. package/src/BottomSheet/BottomSheet.tsx +3 -1
  286. package/src/BottomSheet/BottomSheetKeyboard.test.tsx +195 -0
  287. package/src/BottomSheet/BottomSheetPanel.test.tsx +11 -1
  288. package/src/BottomSheet/BottomSheetPanel.tsx +49 -16
  289. package/src/BottomSheet/__tests__/BottomSheetKeyboard.a11y.browser.spec.ts +344 -0
  290. package/src/Button/Button.doc.mjs +5 -5
  291. package/src/Button/Button.tsx +15 -13
  292. package/src/Button/__tests__/Button.a11y.chromium.spec.ts +17 -1
  293. package/src/Button/__tests__/Button.a11y.known-failures.ts +0 -29
  294. package/src/Button/__tests__/Button.a11y.renders.tsx +9 -2
  295. package/src/Button/__tests__/Button.a11y.states.ts +9 -0
  296. package/src/Chat/ChatComposer.doc.mjs +14 -0
  297. package/src/Chat/ChatComposer.spec.md +230 -0
  298. package/src/Chat/ChatComposer.test.tsx +57 -0
  299. package/src/Chat/ChatComposer.tsx +21 -9
  300. package/src/Chat/ChatComposerDrawer.doc.mjs +63 -5
  301. package/src/Chat/ChatComposerDrawer.spec.md +267 -0
  302. package/src/Chat/ChatComposerDrawer.test.tsx +81 -12
  303. package/src/Chat/ChatComposerDrawer.tsx +38 -9
  304. package/src/Chat/ChatComposerInput.doc.mjs +114 -26
  305. package/src/Chat/ChatComposerInput.spec.md +185 -0
  306. package/src/Chat/ChatComposerInput.test.tsx +113 -3
  307. package/src/Chat/ChatComposerInput.tsx +78 -13
  308. package/src/Chat/ChatComposerTokenElement.spec.md +152 -0
  309. package/src/Chat/ChatComposerTokenElement.test.tsx +85 -0
  310. package/src/Chat/ChatDictationButton.doc.mjs +3 -3
  311. package/src/Chat/ChatDictationButton.spec.md +160 -0
  312. package/src/Chat/ChatDictationButton.test.tsx +92 -27
  313. package/src/Chat/ChatDictationButton.tsx +8 -6
  314. package/src/Chat/ChatLayout.doc.mjs +12 -4
  315. package/src/Chat/ChatLayout.spec.md +271 -0
  316. package/src/Chat/ChatLayout.test.tsx +28 -0
  317. package/src/Chat/ChatLayout.tsx +19 -0
  318. package/src/Chat/ChatLayoutScrollButton.spec.md +338 -0
  319. package/src/Chat/ChatLayoutScrollButton.test.tsx +31 -0
  320. package/src/Chat/ChatLayoutScrollButton.tsx +31 -14
  321. package/src/Chat/ChatMessageBubble.doc.mjs +28 -15
  322. package/src/Chat/ChatMessageBubble.spec.md +263 -0
  323. package/src/Chat/ChatMessageBubble.test.tsx +93 -0
  324. package/src/Chat/ChatMessageBubble.tsx +8 -3
  325. package/src/Chat/ChatMessageList.spec.md +222 -0
  326. package/src/Chat/ChatMessageList.test.tsx +64 -2
  327. package/src/Chat/ChatMessageList.tsx +4 -3
  328. package/src/Chat/ChatMessageMetadata.doc.mjs +57 -3
  329. package/src/Chat/ChatMessageMetadata.spec.md +220 -0
  330. package/src/Chat/ChatMessageMetadata.test.tsx +34 -0
  331. package/src/Chat/ChatMessageMetadata.tsx +13 -13
  332. package/src/Chat/ChatSendButton.test.tsx +1 -4
  333. package/src/Chat/ChatSendButton.tsx +1 -1
  334. package/src/Chat/__tests__/ChatLayoutScrollButton.a11y.chromium.spec.ts +514 -0
  335. package/src/Chat/__tests__/ChatLayoutScrollButtonSurface.a11y.chromium.spec.ts +740 -0
  336. package/src/Chat/__tests__/ChatMessageBubble.a11y.chromium.spec.ts +457 -0
  337. package/src/Chat/__tests__/ChatMessageBubble.auditProvenance.test.ts +91 -0
  338. package/src/Chat/__tests__/ChatMessageBubble.auditProvenance.ts +91 -0
  339. package/src/Chat/__tests__/ChatMessageList.a11y.chromium.spec.ts +456 -0
  340. package/src/Chat/__tests__/ChatMessageMetadata.a11y.chromium.spec.ts +413 -0
  341. package/src/Chat/index.ts +1 -0
  342. package/src/CheckboxInput/CheckboxInput.doc.mjs +11 -0
  343. package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
  344. package/src/CheckboxInput/CheckboxInput.tsx +21 -1
  345. package/src/Citation/Citation.doc.mjs +2 -2
  346. package/src/Citation/Citation.test.tsx +17 -0
  347. package/src/Citation/Citation.tsx +4 -2
  348. package/src/ClickableCard/ClickableCard.doc.mjs +2 -2
  349. package/src/ClickableCard/ClickableCard.test.tsx +102 -5
  350. package/src/CodeBlock/highlightRanges.test.ts +143 -1
  351. package/src/CodeBlock/highlightRanges.ts +15 -5
  352. package/src/Collapsible/Collapsible.doc.mjs +11 -0
  353. package/src/Collapsible/Collapsible.test.tsx +21 -0
  354. package/src/Collapsible/Collapsible.tsx +5 -0
  355. package/src/CommandPalette/CommandPaletteInput.tsx +6 -1
  356. package/src/ComplexSelector/ComplexSelector.tsx +6 -1
  357. package/src/DateInput/DateInput.tsx +6 -1
  358. package/src/DateInput/NativeDateField.tsx +24 -8
  359. package/src/DateInput/TouchDateField.tsx +6 -2
  360. package/src/DateRangeInput/DateRangeInput.tsx +6 -1
  361. package/src/DateTimeInput/DateTimeInput.tsx +6 -1
  362. package/src/DateTimeInput/TouchDateTimeField.tsx +6 -1
  363. package/src/DateTimeInput/nativePickerSegmentStyles.ts +24 -8
  364. package/src/Dialog/Dialog.doc.mjs +2 -0
  365. package/src/Dialog/DialogHeader.doc.mjs +22 -0
  366. package/src/Dialog/DialogHeader.test.tsx +167 -3
  367. package/src/Dialog/DialogHeader.tsx +37 -6
  368. package/src/Dialog/modules/DialogHeader.spec.md +101 -42
  369. package/src/DropdownMenu/DropdownMenu.test.tsx +5 -2
  370. package/src/Field/InputClearButton.test.tsx +17 -3
  371. package/src/Field/InputClearButton.tsx +5 -2
  372. package/src/Field/PanelSearchInput.tsx +5 -2
  373. package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +1 -1
  374. package/src/FileInput/FileInput.tsx +11 -2
  375. package/src/Icon/Icon.doc.mjs +7 -5
  376. package/src/Icon/Icon.spec.md +81 -40
  377. package/src/Icon/Icon.test.tsx +6 -3
  378. package/src/Icon/Icon.tsx +20 -68
  379. package/src/Icon/IconDefaultSizeContext.ts +23 -0
  380. package/src/Icon/IconSize.stylex.ts +70 -0
  381. package/src/IconButton/IconButton.doc.mjs +3 -3
  382. package/src/IconButton/IconButton.test.tsx +40 -0
  383. package/src/Item/Item.doc.mjs +6 -2
  384. package/src/Item/Item.tsx +9 -3
  385. package/src/Kbd/Kbd.doc.mjs +3 -3
  386. package/src/Kbd/Kbd.test.tsx +57 -1
  387. package/src/Kbd/Kbd.tsx +57 -37
  388. package/src/Layout/LayoutFooter.doc.mjs +17 -0
  389. package/src/Link/Link.doc.mjs +17 -4
  390. package/src/Link/Link.test.tsx +24 -0
  391. package/src/Link/Link.tsx +5 -0
  392. package/src/Link/LinkProvider.doc.mjs +2 -2
  393. package/src/Link/__tests__/Link.navigation.a11y.chromium.spec.ts +261 -0
  394. package/src/Link/useLinkComponent.test.tsx +333 -0
  395. package/src/Link/useLinkComponent.ts +29 -57
  396. package/src/List/List.doc.mjs +6 -0
  397. package/src/List/List.spec.md +200 -0
  398. package/src/List/List.test.tsx +117 -1
  399. package/src/List/List.tsx +22 -2
  400. package/src/List/ListContext.tsx +3 -1
  401. package/src/List/ListItem.doc.mjs +2 -2
  402. package/src/List/ListItem.source-build.test.mjs +41 -0
  403. package/src/List/ListItem.tsx +26 -2
  404. package/src/Markdown/Markdown.doc.mjs +300 -2
  405. package/src/Markdown/Markdown.fr23.bench.ts +174 -0
  406. package/src/Markdown/Markdown.fr23.perf.test.ts +249 -0
  407. package/src/Markdown/Markdown.fr23.sampling.ts +49 -0
  408. package/src/Markdown/Markdown.helpers.perf.test.ts +489 -0
  409. package/src/Markdown/Markdown.public.test.ts +130 -3
  410. package/src/Markdown/Markdown.renderBoundary.test.tsx +72 -0
  411. package/src/Markdown/Markdown.spec.md +203 -45
  412. package/src/Markdown/Markdown.test.tsx +63 -0
  413. package/src/Markdown/Markdown.tsx +414 -135
  414. package/src/Markdown/ast.test.ts +307 -0
  415. package/src/Markdown/ast.ts +337 -0
  416. package/src/Markdown/incremental.test.ts +12 -0
  417. package/src/Markdown/index.ts +37 -1
  418. package/src/Markdown/modules/remark.spec.md +269 -0
  419. package/src/Markdown/modules/softBreaks.spec.md +137 -0
  420. package/src/Markdown/parser/index.ts +64 -0
  421. package/src/Markdown/parser.public.test.ts +103 -0
  422. package/src/Markdown/parser.test.ts +20 -0
  423. package/src/Markdown/parser.ts +1180 -235
  424. package/src/Markdown/plugins/frontmatter.test.tsx +125 -0
  425. package/src/Markdown/plugins/frontmatter.ts +198 -0
  426. package/src/Markdown/plugins/index.ts +54 -0
  427. package/src/Markdown/plugins/protocol.test.tsx +1022 -0
  428. package/src/Markdown/plugins/protocol.ts +1618 -0
  429. package/src/Markdown/plugins/semanticFence.test.tsx +546 -0
  430. package/src/Markdown/plugins/semanticFence.ts +424 -0
  431. package/src/Markdown/plugins/softBreaks.test.tsx +172 -0
  432. package/src/Markdown/plugins/softBreaks.ts +169 -0
  433. package/src/Markdown/plugins/sourceDecoration.test.tsx +799 -0
  434. package/src/Markdown/plugins/sourceDecoration.ts +539 -0
  435. package/src/Markdown/plugins/textTransform.test.ts +467 -0
  436. package/src/Markdown/plugins/textTransform.ts +554 -0
  437. package/src/Markdown/remark.test.tsx +1877 -0
  438. package/src/Markdown/remark.ts +1574 -0
  439. package/src/Markdown/url.ts +36 -0
  440. package/src/Markdown/utils.ts +13 -1
  441. package/src/MetadataList/MetadataList.test.tsx +4 -2
  442. package/src/MetadataList/MetadataList.tsx +3 -3
  443. package/src/MultiSelector/MultiSelector.test.tsx +1 -35
  444. package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +128 -0
  445. package/src/NumberInput/NumberInput.tsx +6 -1
  446. package/src/Outline/Outline.doc.mjs +7 -5
  447. package/src/Outline/Outline.spec.md +1 -1
  448. package/src/Outline/index.ts +1 -0
  449. package/src/Outline/modules/parseOutlineFromMarkdown.spec.md +142 -0
  450. package/src/Outline/parseOutlineFromMarkdown.ts +40 -10
  451. package/src/Outline/useOutlineFromMarkdown.ts +15 -3
  452. package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
  453. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +150 -1
  454. package/src/PowerSearch/PowerSearchEditPopover.tsx +51 -28
  455. package/src/PowerSearch/PowerSearchValueEditor.test.tsx +51 -0
  456. package/src/PowerSearch/PowerSearchValueEditor.tsx +4 -0
  457. package/src/ProgressBar/ProgressBar.test.tsx +29 -11
  458. package/src/ProgressBar/ProgressBar.tsx +59 -72
  459. package/src/RadioList/RadioList.doc.mjs +11 -0
  460. package/src/RadioList/RadioList.test.tsx +32 -0
  461. package/src/RadioList/RadioListItem.tsx +25 -1
  462. package/src/ScrollableArea/ScrollableArea.tsx +1 -0
  463. package/src/ScrollableArea/modules/useScrollableArea.spec.md +50 -22
  464. package/src/SegmentedControl/SegmentedControl.doc.mjs +2 -2
  465. package/src/SegmentedControl/SegmentedControl.test.tsx +31 -0
  466. package/src/SegmentedControl/SegmentedControlItem.tsx +6 -9
  467. package/src/Selector/Selector.doc.mjs +2 -2
  468. package/src/Selector/Selector.spec.md +51 -49
  469. package/src/Selector/Selector.test.tsx +61 -55
  470. package/src/Selector/Selector.tsx +34 -15
  471. package/src/Selector/__tests__/Listbox.a11y.chromium.spec.ts +130 -0
  472. package/src/Selector/__tests__/Listbox.a11y.dom.ts +38 -0
  473. package/src/Selector/__tests__/Listbox.a11y.inventory.test.ts +63 -0
  474. package/src/Selector/__tests__/Listbox.a11y.known-failures.ts +37 -0
  475. package/src/Selector/__tests__/Listbox.a11y.states.ts +232 -0
  476. package/src/Selector/__tests__/Selector.a11y.chromium.spec.ts +45 -0
  477. package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +121 -0
  478. package/src/SideNav/SideNav.doc.mjs +1 -1
  479. package/src/SideNav/SideNav.test.tsx +10 -0
  480. package/src/SideNav/SideNav.tsx +14 -2
  481. package/src/Slider/Slider.doc.mjs +17 -3
  482. package/src/Slider/Slider.test.tsx +221 -0
  483. package/src/Slider/Slider.tsx +40 -9
  484. package/src/Spinner/Spinner.test.tsx +4 -0
  485. package/src/Spinner/Spinner.tsx +31 -18
  486. package/src/Switch/Switch.doc.mjs +11 -0
  487. package/src/Switch/Switch.test.tsx +16 -0
  488. package/src/Switch/Switch.tsx +28 -0
  489. package/src/TabList/Tab.tsx +35 -7
  490. package/src/TabList/TabList.doc.mjs +11 -0
  491. package/src/TabList/TabList.spec.md +204 -0
  492. package/src/TabList/TabList.test.tsx +66 -0
  493. package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +1 -34
  494. package/src/Table/Table.doc.mjs +1 -1
  495. package/src/Table/Table.spec.md +54 -40
  496. package/src/Table/Table.test.tsx +83 -10
  497. package/src/Table/Table.tsx +30 -22
  498. package/src/Table/__tests__/TableScroll.a11y.chromium.spec.ts +137 -0
  499. package/src/TextArea/TextArea.tsx +6 -1
  500. package/src/TextInput/TextInput.test.tsx +20 -1
  501. package/src/TextInput/TextInput.tsx +6 -1
  502. package/src/TimeInput/TimeInput.tsx +6 -1
  503. package/src/Timer/Timer.doc.mjs +183 -0
  504. package/src/Timer/Timer.spec.md +215 -0
  505. package/src/Timer/Timer.test.tsx +302 -0
  506. package/src/Timer/Timer.tsx +258 -0
  507. package/src/Timer/index.ts +11 -0
  508. package/src/ToggleButton/ToggleButton.doc.mjs +3 -3
  509. package/src/ToggleButton/ToggleButton.test.tsx +299 -4
  510. package/src/ToggleButton/ToggleButton.tsx +26 -33
  511. package/src/ToggleButton/ToggleButtonGroup.doc.mjs +3 -3
  512. package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +209 -0
  513. package/src/Token/Token.doc.mjs +2 -2
  514. package/src/Tokenizer/Tokenizer.spec.md +142 -75
  515. package/src/TopNav/TopNavMenu.doc.mjs +2 -2
  516. package/src/TopNav/TopNavMenu.test.tsx +30 -0
  517. package/src/TopNav/TopNavMenu.tsx +7 -2
  518. package/src/TreeList/TreeList.doc.mjs +3 -3
  519. package/src/TreeList/TreeList.spec.md +123 -75
  520. package/src/TreeList/TreeList.test.tsx +29 -0
  521. package/src/TreeList/TreeList.tsx +3 -0
  522. package/src/TreeList/TreeListItem.tsx +15 -1
  523. package/src/TreeList/TreeListTypes.ts +11 -1
  524. package/src/Typeahead/BaseTypeahead.spec.md +4 -3
  525. package/src/Typeahead/BaseTypeahead.tsx +21 -7
  526. package/src/Typeahead/Typeahead.test.tsx +53 -0
  527. package/src/__tests__/PressedState.a11y.chromium.spec.ts +813 -0
  528. package/src/__tests__/pressState.ts +93 -0
  529. package/src/hooks/scrollKeyboardDelegation.test.ts +155 -0
  530. package/src/hooks/scrollKeyboardDelegation.ts +233 -0
  531. package/src/hooks/useClickableContainer.test.tsx +236 -0
  532. package/src/hooks/useClickableContainer.ts +20 -4
  533. package/src/hooks/useScrollableArea.doc.mjs +15 -3
  534. package/src/hooks/useScrollableArea.test.tsx +59 -1
  535. package/src/hooks/useScrollableArea.ts +34 -10
  536. package/src/index.ts +3 -0
  537. package/src/inputFontFloor.test.ts +106 -0
  538. package/src/tailwind-theme.css +0 -6
  539. package/src/theme/Theme.test.tsx +32 -0
  540. package/src/theme/declarationBoundary.test.ts +126 -0
  541. package/src/theme/declarationBoundary.ts +347 -0
  542. package/src/theme/derivedVarRegistry.test.ts +7 -0
  543. package/src/theme/derivedVarRegistry.ts +1 -0
  544. package/src/theme/generateThemeRules.test.ts +526 -1
  545. package/src/theme/generateThemeRules.ts +228 -50
  546. package/src/theme/tokenValueCompat.test.ts +4 -1
  547. package/src/utils/interactionOverlay.stylex.ts +20 -0
  548. package/src/utils/safeUrl.test.ts +88 -0
  549. package/src/utils/safeUrl.ts +85 -0
@@ -0,0 +1,263 @@
1
+ ---
2
+ schema_version: 3
3
+ template_version: 6
4
+ kind: component
5
+ id: component:ChatMessageBubble
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/ChatMessageBubble.test.tsx,
16
+ packages/core/src/Chat/__tests__/ChatMessageBubble.a11y.chromium.spec.ts,
17
+ apps/storybook/stories/ChatMessageBubble.stories.tsx,
18
+ apps/storybook/rtl-audit/targets.json,
19
+ packages/core/src/theme/themingTargets.test.ts,
20
+ ]
21
+ modules: []
22
+ families: []
23
+ design_specs: []
24
+ architecture:
25
+ [
26
+ architecture:public-component-api,
27
+ architecture:component-style-authoring,
28
+ architecture:component-theming-surface,
29
+ architecture:theme-tokens,
30
+ architecture:component-test-sufficiency,
31
+ architecture:knowledge-contracts,
32
+ ]
33
+ contributing: []
34
+ system_specs: [spec:AST-029]
35
+ ---
36
+
37
+ # ChatMessageBubble component contract
38
+
39
+ ## Contract at a glance
40
+
41
+ | Area | Contract |
42
+ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
43
+ | Public contract | Required `children`; optional `variant`, `name`, `metadata`, `group`, and `width`; root `div` passthrough, styles, and ref. This audit changes no public API shape or default. |
44
+ | Behavior | The bubble derives sender and density from Chat message context, paints filled or ghost content, groups logical sender-side corners, and keeps renderable scalar name/metadata content inside aligned wrappers. |
45
+ | End-user impact | Numeric zero stays aligned above or below the bubble instead of escaping its wrapper, while non-rendering scalars no longer create empty spacing. Direct light/dark, a11y, RTL, and narrow-layout evidence protects the result. |
46
+ | Builder impact | Existing props and values remain compatible. Builders passing `0` now get the same aligned placement as other visible scalars; nullish, boolean, and empty-string slot values emit no wrapper. |
47
+ | Compatibility/readiness | Patch-compatible behavior correction. `@astryxdesign/core@0.6.3` already ships this surface through `./Chat`. The draft records the scalar correction and leaves composite/asynchronous content ownership and local visual representation unsettled. |
48
+ | Review checks | Reject a dropped root passthrough/ref, missing target metadata, physical grouped-corner logic, an explicit width that retains the default cap, scalar `0` outside its wrapper, empty scalar wrappers, or a claim that settles the open composite boundary. |
49
+ | Governing rules | `architecture:public-component-api/INV1, INV5, INV6, INV8–INV9`; `architecture:component-theming-surface/INV4, INV6–INV8`; `architecture:component-test-sufficiency/INV1–INV7, INV10–INV11`; WCAG 2.2 SC 1.4.10. |
50
+
51
+ This table is a review projection; the body below is authoritative.
52
+
53
+ ## Intent
54
+
55
+ ChatMessageBubble is the optional sender-aware painted content surface inside a
56
+ ChatMessage. It gives conversation content a bubble boundary, text inset,
57
+ grouped-corner treatment, and aligned name/metadata placement that raw message
58
+ content does not have.
59
+
60
+ This record is an observational backfill written during the 2026-09-24
61
+ whole-component audit. It describes verified shipped behavior. It does not
62
+ decide new visual direction, expand the variant axis, or decide how the
63
+ component should inspect composite or asynchronous React content.
64
+
65
+ ## Compatibility and migration
66
+
67
+ - Released default preserved: yes — `@astryxdesign/core@0.6.3` ships the `./Chat`
68
+ subpath, all six component props, the assistant/balanced fallback, and the
69
+ filled default.
70
+ - Compatibility class: patch. Visible numeric zero is moved into the existing
71
+ aligned wrapper; non-rendering scalar slots no longer create wrappers.
72
+ - Controlled/uncontrolled behavior: unchanged — the component owns no state.
73
+ - Migration decision: none. Existing consumers need no code change.
74
+
75
+ Consumer migration instructions belong in consumer docs and release notes.
76
+
77
+ ## Ownership boundary
78
+
79
+ **Owns**
80
+
81
+ - The bubble root's fill, text color, typography, radius, padding, wrapping, and
82
+ width cap.
83
+ - Mapping message sender and density context to bubble paint and geometry.
84
+ - Grouped corner geometry on the sender side, using logical corners.
85
+ - The aligned wrappers around optional `name` and `metadata` content.
86
+ - Root DOM passthrough, consumer style composition, and the root ref.
87
+
88
+ **Does not own / non-goals**
89
+
90
+ - Message semantics, sender layout, avatar placement, or message-level names and
91
+ metadata — owned by ChatMessage or the shared Chat composition surface.
92
+ - Timestamp, delivery status, and footer semantics — owned by
93
+ ChatMessageMetadata.
94
+ - The meaning, accessibility, lifetime, or eventual rendered output of
95
+ caller-supplied React nodes.
96
+ - List scrolling, transcript semantics, composer behavior, or system notices —
97
+ owned by their Chat components.
98
+
99
+ ## Public concepts
100
+
101
+ | Concept | Closed values or states | Meaning | Availability by variant/orientation/state | Default | Owner | Stability | Invalid-value behavior |
102
+ | ---------------------------- | ----------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ----------------------------------------- | ---------------------- | ---------------------------------------- | ------------- | -------------------------------------------------------------------------------- |
103
+ | `children` | any `ReactNode` | Content rendered inside the bubble root | always | required | caller | stable | n/a — required |
104
+ | `variant` | `filled`, `ghost` | Selects a filled sender-aware boundary or transparent aligned content | always | `filled` | component | stable | closed union; rejected by types |
105
+ | `name` | omitted; any `ReactNode` | Optional content above the bubble in an aligned wrapper | always | omitted | caller/component wrapper | stable | non-rendering scalars omit; visible scalars including `0` align; see FR7 and OQ1 |
106
+ | `metadata` | omitted; any `ReactNode` | Optional content below the bubble in an aligned wrapper | always | omitted | caller/component wrapper | stable | non-rendering scalars omit; visible scalars including `0` align; see FR7 and OQ1 |
107
+ | `group` | omitted, `first`, `middle`, `last` | Tightens sender-side corners for consecutive bubbles | filled, ghost | omitted | component | stable | omitted keeps full radius; other values rejected by types |
108
+ | `width` | omitted; any `SizeValue` | Replaces the default width cap with an explicit width | filled, ghost | omitted | caller | stable | numbers become CSS pixels; strings pass through |
109
+ | message context | sender `user`, `assistant`, `system`; density `compact`, `balanced`, `spacious`; absent | Selects reflected sender/density and corresponding paint and geometry | when nested or standalone | assistant and balanced | Chat context / this projection | observational | absent context uses component fallbacks |
110
+ | root `div` | `BaseProps<HTMLDivElement>` and `ref` | Extends and references the element carrying children, paint, target, and bubble geometry | always | none | `architecture:public-component-api` | stable | component target data and consumer styling compose |
111
+ | `chat-message-bubble` target | sender, variant, and density reflected as `data-*`; exact guaranteed-property set not separately declared in docs | Public selector for the painting bubble root | always | one target per bubble | `architecture:component-theming-surface` | stable | undeclared generic properties remain best effort |
112
+
113
+ Consumer syntax and prop defaults remain in `ChatMessageBubble.doc.mjs`.
114
+
115
+ ## Behavioral and layout contract
116
+
117
+ Draft requirements identify their basis so observed code is not mistaken for an
118
+ intentional decision. A `current` contract contains no unresolved rows.
119
+
120
+ | ID | Candidate invariant | Basis | Draft review state |
121
+ | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------- |
122
+ | FR1 | The component MUST always render one root `div` for `children`, including when the caller supplies non-rendering child content. | shipped implementation and required `children` public seam | verified observation |
123
+ | FR2 | The root MUST reflect the resolved sender. `user` selects user paint; `assistant`, `system`, and absent context use the non-user paint path; absent context reflects `assistant`. | shipped implementation and browser/unit evidence | verified observation |
124
+ | FR3 | The root MUST reflect the resolved density. `compact` uses the compact radius and inset; `balanced` is the fallback; `spacious` uses its wider block and inline inset. | shipped implementation and browser/unit evidence | verified observation |
125
+ | FR4 | `filled` MUST paint sender-aware bubble styles; `ghost` MUST remove the fill and block-axis padding while preserving inline alignment. | shipped implementation, consumer docs, and rendered evidence | verified observation |
126
+ | FR5 | `first`, `middle`, and `last` MUST tighten the logical sender-side end, both, and start corner respectively. User uses inline-end; every non-user sender uses inline-start. | shipped implementation plus real-browser LTR/RTL evidence | verified observation |
127
+ | FR6 | Omitted `width` MUST keep `max(80%, 280px)`. An explicit number or string MUST set width and replace the default cap with `none`. | shipped consumer docs, implementation, and unit evidence | verified observation |
128
+ | FR7 | `name` and `metadata` MUST omit their aligned wrappers for null, undefined, booleans, and the empty string. Visible scalar values, including numeric zero, MUST remain inside their aligned wrappers. Non-scalar React nodes remain opaque. | shared `isRenderable` scalar contract, public `ReactNode` seam, and red-before-green evidence | settled scalar obligation |
129
+ | FR8 | Root `ref`, neutral DOM/data/ARIA props, class, style, and `xstyle` MUST reach or combine on the painting root without losing the component target or owned styles. | `architecture:public-component-api/INV5, INV6, INV8` | settled obligation |
130
+ | FR9 | Exactly one `chat-message-bubble` target MUST ride the painting root and reflect sender, variant, and density. | `architecture:component-theming-surface/INV4, INV6–INV8`; public Chat docs | settled obligation |
131
+
132
+ FR7 corrects two reachable scalar mismatches. JavaScript truthiness previously
133
+ returned numeric `0` as an unwrapped Fragment sibling, so React painted it
134
+ outside the promised alignment, while `true` created an empty wrapper and gap.
135
+ The shared scalar predicate now keeps visible scalars in their wrappers and
136
+ omits scalar values that React does not paint.
137
+
138
+ The public type also admits arrays, Fragments, elements, one-shot iterables,
139
+ lazy content, and promises. The scalar correction does not traverse, clone,
140
+ consume, await, or materialize those values: each remains caller-owned opaque
141
+ content. Whether any composite or asynchronous category should be normalized is
142
+ OQ1 rather than an implicit extension of this patch.
143
+
144
+ ### Allowed variation
145
+
146
+ - **AV1 — Theme tokens.** Resolved colors, radii, spacing, and typography may vary
147
+ by theme while the semantic token roles remain.
148
+ - **AV2 — Caller content.** Child, name, and metadata node structure and semantics
149
+ remain caller-owned.
150
+ - **AV3 — Absolute geometry.** Token-resolved padding and radius may vary; logical
151
+ sender-side grouping and width precedence do not.
152
+
153
+ ### Representative states
154
+
155
+ | State | Required invariant | Allowed variation |
156
+ | ------------------------- | -------------------------------------------------------------------- | ---------------------------------- |
157
+ | standalone | assistant sender and balanced density fallback; one painting target | caller content |
158
+ | assistant/system, filled | non-user paint; inline-start grouped corners | token-resolved paint |
159
+ | user, filled | user paint; inline-end grouped corners | token-resolved paint |
160
+ | any sender, ghost | transparent surface; inline inset retained; block inset removed | themed text and padding |
161
+ | compact/balanced/spacious | reflected density and corresponding padding/radius path | resolved token scale |
162
+ | group first/middle/last | logical sender-side corner sequence in LTR and RTL | token-resolved radii |
163
+ | numeric slot `0` | visible zero remains inside the aligned wrapper | none |
164
+ | non-rendering scalar slot | null, undefined, booleans, and empty string emit no aligned wrapper | none |
165
+ | opaque name/metadata | non-scalar inputs emit wrappers without traversal or materialization | eventual caller-owned React output |
166
+ | width omitted/explicit | default cap retained / explicit width replaces cap | valid CSS length |
167
+ | narrow/long text | text wraps with no horizontal overflow at 320 CSS px | line breaks and resulting height |
168
+
169
+ ### Transformation and precedence order
170
+
171
+ - **ORD1 — Context to paint.** Resolve sender/density from the nearest message
172
+ context or local fallback, select paint and geometry, then merge component
173
+ theme props, StyleX output, and consumer styling inputs through the shared
174
+ `mergeProps` contract.
175
+ - **ORD2 — Width.** Apply the default maximum width when omitted; when explicit,
176
+ set width and replace that cap before consumer styling composition.
177
+ - **ORD3 — Grouping.** Resolve sender side first, then group position, then use
178
+ logical corner properties so CSS direction mirrors the physical edge.
179
+
180
+ ### Performance and resources
181
+
182
+ - **PR1 — Pure render derivation.** The component owns no state, Effect, event
183
+ listener, observer, timer, or animation frame. Every output derives from props
184
+ and context during render.
185
+ - **PR2 — Caller content is not inspected.** The current implementation does not
186
+ traverse, clone, consume, await, or materialize caller-supplied React nodes.
187
+ Whether to preserve that boundary or normalize selected inputs is OQ1.
188
+
189
+ ## Accessibility contract
190
+
191
+ - **AR1 — Display surface.** ChatMessageBubble adds no widget role, tab stop,
192
+ keyboard behavior, focus management, announcement, or accessible name of its
193
+ own. Semantics belong to the enclosing ChatMessage and caller content.
194
+ - **AR2 — Content preservation.** Caller DOM and ARIA passthrough reach the bubble
195
+ root under FR8; component target metadata MUST NOT overwrite neutral caller ARIA.
196
+ - **AR3 — Reflow.** At a 320 CSS-px parent width, long content and the documented
197
+ full-width case MUST wrap without horizontal page overflow (WCAG 2.2 SC 1.4.10).
198
+ - **AR4 — Color.** Bubble text remains meaningful content and requires WCAG 2.2
199
+ AA text contrast against the final rendered bubble/backdrop in light and dark.
200
+ The component uses semantic token pairs; rendered measurement remains the audit
201
+ oracle.
202
+
203
+ ## Design relationships
204
+
205
+ | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
206
+ | ---------------- | ----------------------------------------------------- | ------------------------ | -------------- | ------------------ |
207
+ | bubble content | sender-aware filled boundary or aligned ghost surface | unsettled | primary | FR2–FR4 |
208
+ | grouped corners | visually connect consecutive bubbles on sender side | unsettled | supporting | FR5 |
209
+ | name | align auxiliary content with bubble text inset | unsettled | supporting | FR7 |
210
+ | metadata | align auxiliary content with bubble text inset | unsettled | supporting | FR7 |
211
+
212
+ No `authority: current` design record owns ChatMessageBubble's local
213
+ representation today. These rows record shipped appearance for review; they do
214
+ not authorize a redesign or convert rendered judgment into policy.
215
+
216
+ ## Family and system relationships
217
+
218
+ - The shared Chat consumer docs own composition guidance across message, bubble,
219
+ metadata, list, layout, and composer components.
220
+ - `architecture:public-component-api` owns export, passthrough, styling-input,
221
+ and ref behavior.
222
+ - `architecture:component-theming-surface` owns the painting target and reflected
223
+ axes. It does not independently require this closed variant axis to become
224
+ extensible; that would need a component-level public API decision.
225
+ - `architecture:component-test-sufficiency` owns the closed, risk-based evidence
226
+ inventory and the red-before-green requirement for any future slot repair.
227
+ - `spec:AST-029` owns the Night Watch observational backfill and remediation flow.
228
+
229
+ ## Verification map
230
+
231
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
232
+ | -------- | -------------------------------------------------------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- | --------------------------------------- |
233
+ | FR1–FR3 | `ChatMessageBubble.test.tsx`; dedicated Storybook fixture | standalone; user/assistant; compact/balanced/spacious | removing fallback/reflection or a density path fails DOM or browser state evidence | `audit:ChatMessageBubble/behavior` |
234
+ | FR4, FR9 | `themingTargets.test.ts`; scoped axe report; Chromium evidence | filled; ghost; user/assistant/density | dropping reflection or moving the target fails DOM, generated, or rendered evidence | `audit:ChatMessageBubble/theming` |
235
+ | FR5 | dedicated RTL target plus Chromium LTR/RTL frames | user/assistant × first/middle/last | physical corner logic or wrong sender side fails mirrored geometry and pixels | `audit:ChatMessageBubble/rtl` |
236
+ | FR6 | `ChatMessageBubble.test.tsx`; narrow browser fixture | omitted; string `100%`; number `420` | retaining the cap or changing numeric CSS semantics fails the width assertions | `audit:ChatMessageBubble/behavior` |
237
+ | FR7 | `ChatMessageBubble.test.tsx`; pre-fix Chromium receipts and pixels | zero; true; false; empty string; nullish; opaque non-scalars | reverting to truthiness loses wrapper alignment or restores an empty scalar wrapper | `audit:ChatMessageBubble/behavior` |
238
+ | FR8 | public-API checks and existing `data-testid`/style composition tests | ref; DOM/data/ARIA; class; style | removing rest/ref forwarding or replacing consumer styling fails shared or focused checks | `audit:ChatMessageBubble/public-api` |
239
+ | AR1–AR4 | scoped axe report; Chromium receipts and pixels; narrow fixture | all visible states, light/dark, RTL, 320px | a semantic violation, low-contrast pair, or horizontal overflow fails its owning evidence layer | `audit:ChatMessageBubble/accessibility` |
240
+
241
+ ## Decision log
242
+
243
+ No component-local decision has been approved. This draft records only verified
244
+ shipped behavior and the evidence routes added by the audit.
245
+
246
+ ## Open questions
247
+
248
+ - **OQ1 — Should ChatMessageBubble keep every non-scalar `ReactNode`
249
+ name/metadata value as caller-owned opaque content, or normalize selected
250
+ arrays, Fragments, one-shot iterables, lazy values, or promises into a stable,
251
+ rerender-safe empty/non-empty contract?** (`human-api`)
252
+ - **OQ2 — Should the aligned name and metadata wrappers become independently
253
+ themeable, inherit a current owner, or remain intentionally unreachable?**
254
+ (`human-api`)
255
+ - **OQ3 — Which current design owner, if any, should make filled/ghost usage and
256
+ grouped-corner representation durable rather than observational?**
257
+ (`human-design`)
258
+
259
+ ## Content boundary
260
+
261
+ This file does not duplicate the consumer prop table/examples, current audit
262
+ scores, implementation steps, or shared architecture rules. Those remain in
263
+ `ChatMessageBubble.doc.mjs`, the audit report, and their canonical owners.
@@ -1,5 +1,6 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
+ import {createRef, 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';
@@ -66,6 +67,98 @@ describe('ChatMessageBubble', () => {
66
67
  expect(screen.getByTestId('my-bubble')).toBeTruthy();
67
68
  });
68
69
 
70
+ it('renders aligned name and metadata slots around the bubble root', () => {
71
+ render(
72
+ <ChatMessage sender="assistant">
73
+ <ChatMessageBubble
74
+ data-testid="bubble"
75
+ name="Navi"
76
+ metadata={<span>10:32 AM</span>}>
77
+ Hi
78
+ </ChatMessageBubble>
79
+ </ChatMessage>,
80
+ );
81
+ const bubble = screen.getByTestId('bubble');
82
+
83
+ expect(bubble.previousElementSibling).toHaveTextContent('Navi');
84
+ expect(bubble.nextElementSibling).toHaveTextContent('10:32 AM');
85
+ });
86
+
87
+ it('keeps numeric zero inside the aligned name and metadata slots', () => {
88
+ render(
89
+ <ChatMessageBubble data-testid="bubble" name={0} metadata={0}>
90
+ Zero slots
91
+ </ChatMessageBubble>,
92
+ );
93
+ const bubble = screen.getByTestId('bubble');
94
+
95
+ expect(bubble.previousElementSibling).toHaveAttribute('data-chat-name');
96
+ expect(bubble.previousElementSibling).toHaveTextContent('0');
97
+ expect(bubble.nextElementSibling).toHaveTextContent('0');
98
+ });
99
+
100
+ it.each([
101
+ ['null', null],
102
+ ['undefined', undefined],
103
+ ['false', false],
104
+ ['true', true],
105
+ ['empty string', ''],
106
+ ] satisfies [string, ReactNode][])(
107
+ 'omits aligned wrappers for %s',
108
+ (_, value) => {
109
+ const {container} = render(
110
+ <ChatMessageBubble data-testid="bubble" name={value} metadata={value}>
111
+ Empty scalar slots
112
+ </ChatMessageBubble>,
113
+ );
114
+ const bubble = screen.getByTestId('bubble');
115
+
116
+ expect(container.querySelector('[data-chat-name]')).toBeNull();
117
+ expect(bubble.previousElementSibling).toBeNull();
118
+ expect(bubble.nextElementSibling).toBeNull();
119
+ },
120
+ );
121
+
122
+ it('forwards the ref and neutral DOM props to the bubble root', () => {
123
+ const ref = createRef<HTMLDivElement>();
124
+ render(
125
+ <ChatMessageBubble
126
+ ref={ref}
127
+ id="bubble-id"
128
+ aria-label="Message content"
129
+ data-consumer="preserved"
130
+ data-testid="bubble">
131
+ Standalone
132
+ </ChatMessageBubble>,
133
+ );
134
+ const bubble = screen.getByTestId('bubble');
135
+
136
+ expect(ref.current).toBe(bubble);
137
+ expect(bubble).toHaveAttribute('id', 'bubble-id');
138
+ expect(bubble).toHaveAttribute('aria-label', 'Message content');
139
+ expect(bubble).toHaveAttribute('data-consumer', 'preserved');
140
+ });
141
+
142
+ it('reflects both built-in visual variants on the painting root', () => {
143
+ render(
144
+ <>
145
+ <ChatMessageBubble data-testid="filled">Filled</ChatMessageBubble>
146
+ <ChatMessageBubble data-testid="ghost" variant="ghost">
147
+ Ghost
148
+ </ChatMessageBubble>
149
+ </>,
150
+ );
151
+
152
+ expect(screen.getByTestId('filled')).toHaveAttribute(
153
+ 'data-variant',
154
+ 'filled',
155
+ );
156
+ expect(screen.getByTestId('ghost')).toHaveAttribute(
157
+ 'data-variant',
158
+ 'ghost',
159
+ );
160
+ });
161
+
69
162
  it('ghost variant aligns custom content with the bubble text column (#2574)', () => {
70
163
  // Repro from the issue: a raw child renders flush with the message
71
164
  // edge — it carries none of the inset the bubble's name slot gets.
@@ -38,7 +38,7 @@ import {
38
38
  typographyVars,
39
39
  } from '../theme/tokens.stylex';
40
40
  import {useChatMessageContext} from './ChatContext';
41
- import {mergeProps} from '../utils';
41
+ import {isRenderable, mergeProps} from '../utils';
42
42
  import type {SizeValue} from '../utils/types';
43
43
  import type {BaseProps} from '../BaseProps';
44
44
  import {themeProps} from '../utils/themeProps';
@@ -64,6 +64,7 @@ export interface ChatMessageBubbleProps extends BaseProps<HTMLDivElement> {
64
64
 
65
65
  /**
66
66
  * Sender name rendered above the bubble, aligned with bubble text padding.
67
+ * Non-rendering scalar values omit the aligned wrapper; numeric zero remains.
67
68
  * Use when the first content in a message is a bubble.
68
69
  * If the first content is raw (no bubble), use ChatMessage's `name`
69
70
  * prop instead.
@@ -72,6 +73,7 @@ export interface ChatMessageBubbleProps extends BaseProps<HTMLDivElement> {
72
73
 
73
74
  /**
74
75
  * Metadata content rendered below the bubble, aligned with bubble text padding.
76
+ * Non-rendering scalar values omit the aligned wrapper; numeric zero remains.
75
77
  * Use when the last content in a message is a bubble.
76
78
  * If the last content is raw (no bubble), use ChatMessage's `metadata`
77
79
  * prop instead.
@@ -289,9 +291,12 @@ export function ChatMessageBubble({
289
291
  ? styles.metadataPaddingSpacious
290
292
  : styles.metadataPaddingBalanced;
291
293
 
294
+ const hasName = isRenderable(name);
295
+ const hasMetadata = isRenderable(metadata);
296
+
292
297
  return (
293
298
  <>
294
- {name && (
299
+ {hasName && (
295
300
  <div
296
301
  data-chat-name
297
302
  {...stylex.props(
@@ -324,7 +329,7 @@ export function ChatMessageBubble({
324
329
  )}>
325
330
  {children}
326
331
  </div>
327
- {metadata && (
332
+ {hasMetadata && (
328
333
  <div
329
334
  {...stylex.props(
330
335
  metadataPaddingStyle,
@@ -0,0 +1,222 @@
1
+ ---
2
+ schema_version: 3
3
+ template_version: 6
4
+ kind: component
5
+ id: component:ChatMessageList
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:
13
+ [public-api, behavior, layout, scrolling, accessibility, theming, testing]
14
+ verified_by:
15
+ [
16
+ packages/core/src/Chat/ChatMessageList.test.tsx,
17
+ packages/core/src/Chat/__tests__/ChatMessageList.a11y.chromium.spec.ts,
18
+ apps/storybook/stories/ChatMessageList.stories.tsx,
19
+ packages/core/src/theme/themingTargets.test.ts,
20
+ ]
21
+ modules: []
22
+ families: []
23
+ design_specs: []
24
+ architecture:
25
+ [
26
+ architecture:public-component-api,
27
+ architecture:component-test-sufficiency,
28
+ architecture:component-style-authoring,
29
+ architecture:component-theming-surface,
30
+ architecture:react-component-runtime,
31
+ architecture:knowledge-contracts,
32
+ ]
33
+ contributing: []
34
+ system_specs: [spec:AST-029]
35
+ ---
36
+
37
+ # ChatMessageList component contract
38
+
39
+ This draft is an observational account of shipped `core/ChatMessageList`. Its
40
+ candidate invariants name their source and evidence; they do not decide any
41
+ new API, default, loading protocol, or visual direction. The current shared
42
+ records linked above continue to govern independently.
43
+
44
+ ## Contract at a glance
45
+
46
+ | Area | Contract |
47
+ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
48
+ | Public contract | `children`, `emptyState`, `scrollToTopAction`, `density`, `gap`, `align`, `isStreaming`, the root's `BaseProps<HTMLDivElement>`, and `ref`. |
49
+ | Behavior | A polite log holds caller messages. Density selects row spacing and passes through context. A top sentinel can request older messages. A spacer moves short lists down by default. |
50
+ | End-user impact | A reader with a numeric empty-state value sees `0`; other transcript arrangements remain unchanged. |
51
+ | Builder impact | None. No new caller choice or migration is introduced. |
52
+ | Compatibility/readiness | The published `./Chat` subpath and its defaults remain unchanged. This is a draft pending verification and exact-head owner review. |
53
+ | Review checks | Reject a claim that the list owns ChatLayout auto-scroll, that a draft settles an unobserved loading policy, or that DOM `aria-busy` proves spoken output. |
54
+ | Governing rules | `architecture:public-component-api/INV1, INV5–INV8`; `architecture:react-component-runtime/INV3, INV5–INV6`; `spec:AST-029/FR2–FR5`. |
55
+
56
+ This table is a review projection; the body below describes the observed
57
+ surface rather than creating new authority.
58
+
59
+ ## Intent
60
+
61
+ ChatMessageList is the presentational message container within the Chat
62
+ composition. It owns a flex-column transcript, spacing/density context, an
63
+ optional empty state and older-message sentinel, and the log's rendered ARIA
64
+ attributes. `ChatLayout` owns auto-follow scrolling and the dock; message and
65
+ bubble components own sender presentation and content.
66
+
67
+ Consumer prop syntax and examples remain in `ChatMessageList.doc.mjs`.
68
+
69
+ ## Compatibility and migration
70
+
71
+ - Released default preserved: yes. The published Core package exposes `./Chat`
72
+ with `ChatMessageList` and `ChatMessageListProps`.
73
+ - Compatibility class: no new API, default, or migration. Visible numeric
74
+ empty-state content follows the existing `ReactNode` type.
75
+ - Controlled/uncontrolled behavior: not applicable; there is no controlled
76
+ value on this component.
77
+ - Migration decision: none.
78
+
79
+ ## Ownership boundary
80
+
81
+ **Owns**
82
+
83
+ - The outer log element, its public DOM/ref/style passthrough, and the
84
+ `chat-message-list` target with its `density` selector.
85
+ - The inner flex-column message box and density-based padding and gap.
86
+ - The short-list block spacer when `align` is `bottom`.
87
+ - The optional `emptyState` substitution and older-message sentinel/loading
88
+ indicator.
89
+ - Passing the inner content element to `ChatLayoutContext.contentRef` while a
90
+ layout is present.
91
+
92
+ **Does not own / non-goals**
93
+
94
+ - Auto-scroll, scroll-to-bottom, dock, and scroll-owner selection, which belong
95
+ to `component:ChatLayout` and its scroll hooks.
96
+ - Sender identity, message alignment, bubbles, metadata, and text semantics,
97
+ which belong to their child components.
98
+ - The caller's region landmark or an accessible name for arbitrary transcript
99
+ context; no new naming policy is made by this draft.
100
+
101
+ ## Public concepts
102
+
103
+ | Concept | Closed values or states | Meaning observed | Availability | Default | Owner | Stability | Invalid-value behavior |
104
+ | ------------------- | ----------------------------------------------- | --------------------------------------------------------------- | ------------- | ---------- | --------- | --------- | -------------------------------------------------- |
105
+ | `children` | `ReactNode` | Top-level transcript content | every render | required | caller | released | null, false, or `[]` counts empty at the top level |
106
+ | `emptyState` | `ReactNode`; omitted | Substitutes for top-level empty children | empty state | omitted | caller | released | absent renders no substitute |
107
+ | `scrollToTopAction` | async function; omitted | Invoked when the top sentinel intersects | when supplied | omitted | caller | released | absent creates no sentinel or observer |
108
+ | `density` | `compact`; `balanced`; `spacious` | Chooses message spacing and child density context | every render | `balanced` | component | released | closed type union |
109
+ | `gap` | published `SpacingStep`; omitted | Overrides the list's top-level row gap independently of density | every render | omitted | caller | released | closed type union |
110
+ | `align` | `top`; `bottom` | Positions short content at the start or block end | every render | `bottom` | component | released | closed type union |
111
+ | `isStreaming` | `true`; `false` | Exposes `aria-busy` on the polite log while streaming | every render | `false` | caller | released | boolean |
112
+ | root passthrough | `ref`, accepted DOM/data/ARIA and styling props | Extends the outer log element | every render | omitted | caller | released | owned role/live/busy attributes retain precedence |
113
+
114
+ The table records the implementation's top-level emptiness test; it does not
115
+ promise recursive child inspection or resolve the meaning of arbitrary
116
+ non-rendering React nodes.
117
+
118
+ ## Behavioral and layout contract
119
+
120
+ These candidate invariants record observable shipped behavior. A `verify`
121
+ state means the audit has not upgraded implementation into policy.
122
+
123
+ | ID | Candidate invariant | Basis | Draft review state |
124
+ | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------ | ------------------ |
125
+ | FR1 | The outer root renders `role="log"`, `aria-live="polite"`, and `tabIndex=0`; `isStreaming=true` adds `aria-busy="true"`, which is absent otherwise. | Shipped source, colocated DOM tests and consumer docs | verify |
126
+ | FR2 | The chosen density is reflected as `data-density` on the root and flows to child messages through `ChatListContext`; it selects three spacing groups. | Shipped source, colocated density test, family docs | verify |
127
+ | FR3 | The optional `gap` replaces only the top-level row gap; density still selects padding and context. | Shipped source and consumer docs | verify |
128
+ | FR4 | `emptyState` renders only when `children` is nullish, `false`, or an empty top-level array. Otherwise the passed children render unchanged. | Shipped source, empty-array and numeric-zero tests | verify |
129
+ | FR5 | `align="bottom"` includes a flex spacer before messages; `align="top"` omits it. Overflowing content is not given a different rendering path. | Shipped source, colocated tests, existing family story | verify |
130
+ | FR6 | Supplying `scrollToTopAction` creates an observed top sentinel. An intersection starts its async transition, and a top-positioned spinner renders while it is pending. | Shipped source, consumer docs, focused async test | verify |
131
+ | FR7 | Inside ChatLayout, the inner content element is passed to the layout's `contentRef` and removed from that registration on cleanup; standalone lists have no layout ref. | Shipped source and context definition | verify |
132
+ | FR8 | The root receives `ref`, accepted DOM/data/ARIA props, and composed `className`, `style`, and `xstyle`; component-owned log semantics are applied after forwarded props. | Shipped source and shared public API authority | verify |
133
+
134
+ For the `emptyState` slot, numeric `0` is visible content. Nullish values,
135
+ booleans, and an empty string do not create an empty wrapper. This describes
136
+ scalar output only; it does not decide how to inspect nested children.
137
+
138
+ ### Allowed variation
139
+
140
+ - **AV1 - Content.** Any caller-owned ReactNode may be placed in the list; the
141
+ list does not infer sender metadata from children.
142
+ - **AV2 - Spacing.** Density selects token-based defaults; the optional `gap`
143
+ selects a published spacing step without changing the child density.
144
+ - **AV3 - Scroll ownership.** The list may be used alone or inside ChatLayout;
145
+ auto-follow behavior remains outside this component.
146
+
147
+ ### Representative states
148
+
149
+ | State | Observed result | Allowed variation |
150
+ | ----------------------------- | ----------------------------------------------------- | ------------------------------------------ |
151
+ | Two messages and a system row | Polite log with messages in source order | Caller controls row content |
152
+ | Empty top-level children | Optional centered empty state | An omitted empty state leaves an empty log |
153
+ | Compact/balanced/spacious | Different token-derived row spacing and child density | `gap` may override only row spacing |
154
+ | Short top/bottom list | Bottom includes a spacer, top omits it | Container height is caller-owned |
155
+ | Streaming | Log has `aria-busy=true` | Message content remains caller-owned |
156
+ | Top action pending | Spinner above content while transition is pending | Async action result is caller-owned |
157
+
158
+ ### Performance and resources
159
+
160
+ - An `IntersectionObserver` is installed only when `scrollToTopAction` exists,
161
+ and disconnected on cleanup. Its root is ChatLayout's scroll container when
162
+ available, otherwise the document viewport.
163
+ - An Effect registers the inner content element with ChatLayout, then clears
164
+ that registration on cleanup. This draft does not prescribe an internal hook
165
+ or listener implementation.
166
+
167
+ ## Accessibility contract
168
+
169
+ - The shipped root is a focusable polite log with `aria-busy` reflecting
170
+ `isStreaming`; this describes browser-exposed semantics, not an observed
171
+ screen-reader announcement transcript.
172
+ - The loading spinner is a separate child of the log. The list adds no
173
+ interactive button or directional keyboard behavior of its own.
174
+ - Caller-supplied content remains responsible for names and semantics inside
175
+ messages. This record does not claim one browser/AT pairing proves another.
176
+
177
+ ## Design relationships
178
+
179
+ No current component-local design decision is added by this observational
180
+ backfill. Spacing uses portable semantic token steps. The family consumer doc
181
+ owns the published `chat-message-list` theme target and its `density` axis.
182
+ This record does not choose a new visual representation or a new theming target.
183
+
184
+ ## Family and system relationships
185
+
186
+ - No `family:chat` authority record currently exists. `Chat.doc.mjs` is the
187
+ consumer family document, not a product-authority family contract.
188
+ - `ChatLayout` owns the scroll container and provides `contentRef`; the list
189
+ registers its content element without taking over ChatLayout's scrolling.
190
+ - `spec:AST-029` governs observational audit backfills and keeps their
191
+ evidence inventory separate from component policy.
192
+
193
+ ## Verification map
194
+
195
+ | Contract | Evidence | Representative states | Mutation or failure expectation | Audit section |
196
+ | -------- | ------------------------------------------------------------------- | ---------------------------------- | -------------------------------------------------------- | -------------------------- |
197
+ | FR1 | `ChatMessageList.test.tsx` ARIA assertions; Chromium audit receipts | rest, streaming | Dropping busy or the log role changes the exposed state | `audit:ChatMessageList/§1` |
198
+ | FR2–FR3 | density test, theming-target guard, rendered comparison | three densities and gap override | Losing density reflection or row spacing becomes visible | `audit:ChatMessageList/§2` |
199
+ | FR4–FR5 | empty/align tests, component-owned Storybook and Chromium receipts | empty, top and bottom | Empty substitute or short-list placement changes | `audit:ChatMessageList/§4` |
200
+ | FR6 | `ChatMessageList.test.tsx` async action/status test | sentinel absent, pending, settled | Action loss or stuck spinner fails the focused test | `audit:ChatMessageList/§6` |
201
+ | FR7 | ChatLayout integration and source review | standalone, inside layout, unmount | Content observation can lose registration | `audit:ChatMessageList/§7` |
202
+ | FR8 | `themingTargets.test.ts`, focused root passthrough inspection | ref, DOM/ARIA, styling | Consumer inputs fail to reach or compose on the root | `audit:ChatMessageList/§3` |
203
+
204
+ ## Decision log
205
+
206
+ None. This draft settles no component-local product decision.
207
+
208
+ ## Open questions
209
+
210
+ - **OQ1 - What is the intended overlap policy for repeated top-sentinel
211
+ intersections while an async load remains pending?** (`human-api`) The current
212
+ source invokes the action on intersection; this record does not add a
213
+ single-flight, initial-load, or exhaustion guarantee.
214
+ - **OQ2 - Should emptiness inspect nested non-rendering ReactNode values?**
215
+ (`human-api`) The shipped test is limited to the top-level `children` value.
216
+ Do not reinterpret that test as a policy about generators, fragments,
217
+ promised children, or arrays of non-rendering values.
218
+
219
+ ## Content boundary
220
+
221
+ This file contains no audit score, screenshot matrix, run transcript, consumer
222
+ prop example, new public API, or cross-component visual policy.