@djangocfg/ui-tools 2.1.407 → 2.1.409

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 (297) hide show
  1. package/README.md +9 -10
  2. package/dist/file-icon/index.cjs +449 -61
  3. package/dist/file-icon/index.cjs.map +1 -1
  4. package/dist/file-icon/index.d.cts +56 -18
  5. package/dist/file-icon/index.d.ts +56 -18
  6. package/dist/file-icon/index.mjs +448 -62
  7. package/dist/file-icon/index.mjs.map +1 -1
  8. package/dist/tree/index.cjs +49 -22
  9. package/dist/tree/index.cjs.map +1 -1
  10. package/dist/tree/index.d.cts +9 -3
  11. package/dist/tree/index.d.ts +9 -3
  12. package/dist/tree/index.mjs +49 -22
  13. package/dist/tree/index.mjs.map +1 -1
  14. package/dist/{types-B_zhyAqR.d.cts → types-eEu8SeiQ.d.cts} +4 -0
  15. package/dist/{types-B_zhyAqR.d.ts → types-eEu8SeiQ.d.ts} +4 -0
  16. package/package.json +8 -13
  17. package/src/components/FloatingToolbar/index.tsx +37 -3
  18. package/src/lib/page-snapshot/__tests__/capture-integration.test.ts +85 -0
  19. package/src/lib/page-snapshot/__tests__/engine.test.ts +36 -0
  20. package/src/lib/page-snapshot/__tests__/redaction-integration.test.ts +99 -0
  21. package/src/lib/page-snapshot/__tests__/tokens.test.ts +17 -0
  22. package/src/lib/page-snapshot/capture/__tests__/budget.test.ts +49 -0
  23. package/src/lib/page-snapshot/capture/__tests__/chrome-filter.test.ts +47 -0
  24. package/src/lib/page-snapshot/capture/__tests__/fold.test.ts +66 -0
  25. package/src/lib/page-snapshot/capture/__tests__/scope.test.ts +74 -0
  26. package/src/lib/page-snapshot/capture/__tests__/walk.test.ts +129 -0
  27. package/src/lib/page-snapshot/capture/accessible-name.ts +73 -0
  28. package/src/lib/page-snapshot/capture/budget.ts +95 -0
  29. package/src/lib/page-snapshot/capture/chrome-filter.ts +81 -0
  30. package/src/lib/page-snapshot/capture/classify.ts +111 -0
  31. package/src/lib/page-snapshot/capture/dom-utils.ts +111 -0
  32. package/src/lib/page-snapshot/capture/fold.ts +96 -0
  33. package/src/lib/page-snapshot/capture/scope.ts +169 -0
  34. package/src/lib/page-snapshot/capture/walk.ts +250 -0
  35. package/src/lib/page-snapshot/cst/__tests__/serialize.test.ts +50 -0
  36. package/src/lib/page-snapshot/cst/directives.ts +47 -0
  37. package/src/lib/page-snapshot/cst/payload.ts +50 -0
  38. package/src/lib/page-snapshot/cst/serialize.ts +84 -0
  39. package/src/lib/page-snapshot/cst/types.ts +115 -0
  40. package/src/lib/page-snapshot/engine.ts +176 -0
  41. package/src/lib/page-snapshot/index.ts +93 -0
  42. package/src/lib/page-snapshot/react/PageSnapshotChip.tsx +72 -0
  43. package/src/lib/page-snapshot/react/PageSnapshotPreview.tsx +78 -0
  44. package/src/lib/page-snapshot/react/__tests__/PageSnapshotChip.test.tsx +54 -0
  45. package/src/lib/page-snapshot/react/__tests__/provider.test.tsx +103 -0
  46. package/src/lib/page-snapshot/react/__tests__/use-page-snapshot-toggle.test.tsx +62 -0
  47. package/src/lib/page-snapshot/react/provider.tsx +162 -0
  48. package/src/lib/page-snapshot/react/use-page-snapshot-toggle.ts +47 -0
  49. package/src/lib/page-snapshot/react/use-page-snapshot.ts +67 -0
  50. package/src/lib/page-snapshot/redaction/__tests__/audit.test.ts +25 -0
  51. package/src/lib/page-snapshot/redaction/__tests__/heuristics.test.ts +73 -0
  52. package/src/lib/page-snapshot/redaction/__tests__/luhn.test.ts +26 -0
  53. package/src/lib/page-snapshot/redaction/__tests__/patterns.test.ts +60 -0
  54. package/src/lib/page-snapshot/redaction/audit.ts +58 -0
  55. package/src/lib/page-snapshot/redaction/heuristics.ts +75 -0
  56. package/src/lib/page-snapshot/redaction/index.ts +75 -0
  57. package/src/lib/page-snapshot/redaction/luhn.ts +25 -0
  58. package/src/lib/page-snapshot/redaction/patterns.ts +111 -0
  59. package/src/lib/page-snapshot/refs/__tests__/registry.test.ts +24 -0
  60. package/src/lib/page-snapshot/refs/registry.ts +46 -0
  61. package/src/lib/page-snapshot/staleness/__tests__/hash.test.ts +34 -0
  62. package/src/lib/page-snapshot/staleness/hash.ts +20 -0
  63. package/src/lib/page-snapshot/tokens.ts +15 -0
  64. package/src/tools/AudioPlayer/context/PlayerProvider.tsx +13 -14
  65. package/src/tools/AudioPlayer/hooks/useAudioElementEvents.ts +55 -6
  66. package/src/tools/AudioPlayer/parts/Meta/TimeDisplay.tsx +2 -5
  67. package/src/tools/Chat/README.md +277 -39
  68. package/src/tools/Chat/composer/Composer.tsx +471 -0
  69. package/src/tools/Chat/composer/ComposerActionBar.tsx +65 -0
  70. package/src/tools/Chat/composer/ComposerBanner.tsx +128 -0
  71. package/src/tools/Chat/composer/ComposerButton.tsx +64 -0
  72. package/src/tools/Chat/composer/ComposerFooter.tsx +90 -0
  73. package/src/tools/Chat/composer/ComposerMenuButton.tsx +62 -0
  74. package/src/tools/Chat/composer/ComposerModelPicker.tsx +104 -0
  75. package/src/tools/Chat/composer/ComposerRichTextarea.tsx +88 -0
  76. package/src/tools/Chat/composer/ComposerToolPill.tsx +95 -0
  77. package/src/tools/Chat/composer/index.ts +45 -0
  78. package/src/tools/Chat/composer/size-context.tsx +26 -0
  79. package/src/tools/Chat/composer/types.ts +143 -0
  80. package/src/tools/Chat/composer/useComposerActions.tsx +164 -0
  81. package/src/tools/Chat/context/ChatProvider.tsx +54 -3
  82. package/src/tools/Chat/core/__tests__/metadata.test.ts +69 -0
  83. package/src/tools/Chat/core/index.ts +23 -1
  84. package/src/tools/Chat/core/markdown.ts +1 -1
  85. package/src/tools/Chat/core/metadata.ts +47 -0
  86. package/src/tools/Chat/core/payload-dispatch.ts +1 -1
  87. package/src/tools/Chat/core/transport/http.ts +71 -32
  88. package/src/tools/Chat/core/transport/sse.ts +18 -10
  89. package/src/tools/Chat/highlight/HighlightOverlay.tsx +101 -0
  90. package/src/tools/Chat/highlight/README.md +103 -0
  91. package/src/tools/Chat/highlight/SpotlightCanvas.tsx +153 -0
  92. package/src/tools/Chat/highlight/__tests__/HighlightOverlay.test.tsx +112 -0
  93. package/src/tools/Chat/highlight/__tests__/resolveRef.test.ts +55 -0
  94. package/src/tools/Chat/highlight/index.ts +21 -0
  95. package/src/tools/Chat/highlight/resolveRef.ts +42 -0
  96. package/src/tools/Chat/highlight/types.ts +49 -0
  97. package/src/tools/Chat/highlight/useHighlightTargets.ts +128 -0
  98. package/src/tools/Chat/hooks/index.ts +0 -5
  99. package/src/tools/Chat/hooks/useAutoFocusOnStreamEnd.ts +28 -47
  100. package/src/tools/Chat/hooks/useChat.ts +47 -14
  101. package/src/tools/Chat/hooks/useChatComposer.ts +2 -2
  102. package/src/tools/Chat/hooks/useChatLayout.ts +1 -1
  103. package/src/tools/Chat/hooks/useStreamEndFocus.ts +54 -0
  104. package/src/tools/Chat/index.ts +25 -219
  105. package/src/tools/Chat/launcher/ChatDock.tsx +1 -1
  106. package/src/tools/Chat/launcher/ChatLauncher.tsx +1 -1
  107. package/src/tools/Chat/launcher/{ChatHeader.tsx → header/ChatHeader.tsx} +24 -11
  108. package/src/tools/Chat/launcher/{ChatHeaderActionButton.tsx → header/ChatHeaderActionButton.tsx} +34 -3
  109. package/src/tools/Chat/launcher/{ChatHeaderLanguageButton.tsx → header/ChatHeaderLanguageButton.tsx} +2 -2
  110. package/src/tools/Chat/launcher/{ChatHeaderModeToggle.tsx → header/ChatHeaderModeToggle.tsx} +1 -1
  111. package/src/tools/Chat/launcher/{ChatHeaderResetButton.tsx → header/ChatHeaderResetButton.tsx} +2 -1
  112. package/src/tools/Chat/launcher/{HeaderSlots.tsx → header/HeaderSlots.tsx} +3 -3
  113. package/src/tools/Chat/launcher/header/index.ts +26 -0
  114. package/src/tools/Chat/launcher/index.ts +3 -10
  115. package/src/tools/Chat/lazy.tsx +38 -284
  116. package/src/tools/Chat/{components → messages}/MessageBubble.tsx +58 -5
  117. package/src/tools/Chat/{components → messages}/MessageList.tsx +8 -25
  118. package/src/tools/Chat/messages/blocks/MessageBlocks.tsx +131 -0
  119. package/src/tools/Chat/messages/blocks/builtin.tsx +91 -0
  120. package/src/tools/Chat/messages/blocks/index.ts +12 -0
  121. package/src/tools/Chat/messages/blocks/registry.tsx +42 -0
  122. package/src/tools/Chat/messages/blocks/renderers/AudioBlock.tsx +20 -0
  123. package/src/tools/Chat/messages/blocks/renderers/CodeBlock.tsx +19 -0
  124. package/src/tools/Chat/messages/blocks/renderers/GalleryBlock.tsx +26 -0
  125. package/src/tools/Chat/messages/blocks/renderers/ImageBlock.tsx +27 -0
  126. package/src/tools/Chat/messages/blocks/renderers/JsonBlock.tsx +12 -0
  127. package/src/tools/Chat/messages/blocks/renderers/LottieBlock.tsx +11 -0
  128. package/src/tools/Chat/messages/blocks/renderers/MapBlock.tsx +36 -0
  129. package/src/tools/Chat/messages/blocks/renderers/MermaidBlock.tsx +11 -0
  130. package/src/tools/Chat/messages/blocks/renderers/VideoBlock.tsx +24 -0
  131. package/src/tools/Chat/messages/blocks/renderers/types.ts +8 -0
  132. package/src/tools/Chat/{components → messages}/index.ts +11 -5
  133. package/src/tools/Chat/public.ts +212 -0
  134. package/src/tools/Chat/shell/ChatRoot.tsx +345 -0
  135. package/src/tools/Chat/{components → shell}/EmptyState.tsx +4 -2
  136. package/src/tools/Chat/shell/index.ts +15 -0
  137. package/src/tools/Chat/types/block.ts +120 -0
  138. package/src/tools/Chat/types/config.ts +0 -5
  139. package/src/tools/Chat/types/index.ts +17 -0
  140. package/src/tools/Chat/types/message.ts +3 -0
  141. package/src/tools/Chat/utils/index.ts +4 -0
  142. package/src/tools/CodeEditor/README.md +4 -6
  143. package/src/tools/CodeEditor/components/DiffEditor.tsx +48 -13
  144. package/src/tools/CodeEditor/components/Editor.tsx +96 -44
  145. package/src/tools/CodeEditor/context/EditorProvider.tsx +34 -17
  146. package/src/tools/CodeEditor/hooks/useEditorTheme.ts +92 -99
  147. package/src/tools/CodeEditor/hooks/useMonaco.ts +37 -22
  148. package/src/tools/CodeEditor/lazy.tsx +6 -0
  149. package/src/tools/CodeEditor/lib/index.ts +1 -1
  150. package/src/tools/CodeEditor/lib/themes.ts +3 -39
  151. package/src/tools/CronScheduler/CronScheduler.client.tsx +230 -61
  152. package/src/tools/CronScheduler/components/CustomInput.tsx +21 -4
  153. package/src/tools/CronScheduler/components/DayChips.tsx +13 -11
  154. package/src/tools/CronScheduler/components/MonthDayGrid.tsx +4 -4
  155. package/src/tools/CronScheduler/components/SchedulePreview.tsx +7 -3
  156. package/src/tools/CronScheduler/components/TimeSelector.tsx +1 -1
  157. package/src/tools/CronScheduler/index.tsx +1 -1
  158. package/src/tools/CronScheduler/types/index.ts +8 -3
  159. package/src/tools/CronScheduler/utils/cron-humanize.ts +61 -16
  160. package/src/tools/CronScheduler/utils/cron-parser.ts +13 -4
  161. package/src/tools/FileIcon/FileIcon.tsx +24 -39
  162. package/src/tools/FileIcon/get-file-icon.ts +73 -0
  163. package/src/tools/FileIcon/icons/icon-data.ts +399 -0
  164. package/src/tools/FileIcon/index.ts +4 -0
  165. package/src/tools/FileIcon/loader.ts +17 -35
  166. package/src/tools/FileIcon/specialFolders.ts +18 -0
  167. package/src/tools/Gallery/components/lightbox/GalleryLightbox.tsx +112 -35
  168. package/src/tools/Gallery/components/media/GalleryVideo.tsx +21 -2
  169. package/src/tools/Gallery/components/preview/GalleryCarousel.tsx +11 -1
  170. package/src/tools/Gallery/hooks/usePreloadImages.ts +54 -7
  171. package/src/tools/ImageViewer/components/ImageInfo.tsx +12 -1
  172. package/src/tools/ImageViewer/components/ImageToolbar.tsx +51 -43
  173. package/src/tools/ImageViewer/components/ImageViewer.tsx +96 -24
  174. package/src/tools/ImageViewer/hooks/useImageLoading.ts +13 -0
  175. package/src/tools/ImageViewer/utils/constants.ts +3 -0
  176. package/src/tools/ImageViewer/utils/index.ts +1 -0
  177. package/src/tools/JsonForm/JsonSchemaForm.tsx +4 -1
  178. package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +5 -3
  179. package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +7 -4
  180. package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +3 -1
  181. package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +23 -3
  182. package/src/tools/JsonForm/widgets/ColorWidget.tsx +20 -12
  183. package/src/tools/JsonForm/widgets/NumberWidget.tsx +14 -9
  184. package/src/tools/JsonForm/widgets/RadioWidget.tsx +78 -0
  185. package/src/tools/JsonForm/widgets/SelectWidget.tsx +1 -0
  186. package/src/tools/JsonForm/widgets/SliderWidget.tsx +7 -4
  187. package/src/tools/JsonForm/widgets/TextWidget.tsx +41 -17
  188. package/src/tools/JsonForm/widgets/index.ts +1 -0
  189. package/src/tools/JsonTree/components/JsonContent.tsx +115 -40
  190. package/src/tools/LottiePlayer/LottiePlayer.client.tsx +177 -72
  191. package/src/tools/LottiePlayer/index.tsx +14 -4
  192. package/src/tools/LottiePlayer/lazy.tsx +11 -3
  193. package/src/tools/LottiePlayer/types.ts +31 -1
  194. package/src/tools/LottiePlayer/useLottie.ts +32 -9
  195. package/src/tools/LottiePlayer/usePrefersReducedMotion.ts +46 -0
  196. package/src/tools/Map/components/LayerSwitcher.tsx +54 -21
  197. package/src/tools/Map/components/MapCluster.tsx +28 -21
  198. package/src/tools/Map/components/MapContainer.tsx +11 -4
  199. package/src/tools/Map/components/MapLegend.tsx +46 -15
  200. package/src/tools/Map/components/MapMarker.tsx +31 -2
  201. package/src/tools/Map/hooks/useMapEvents.ts +64 -105
  202. package/src/tools/MarkdownEditor/MarkdownEditor.tsx +61 -6
  203. package/src/tools/MarkdownEditor/MentionList.tsx +37 -4
  204. package/src/tools/MarkdownEditor/createMentionSuggestion.ts +11 -0
  205. package/src/tools/MarkdownEditor/lazy.tsx +32 -7
  206. package/src/tools/MarkdownEditor/styles.css +13 -0
  207. package/src/tools/MarkdownMessage/CodeBlock.tsx +40 -17
  208. package/src/tools/MarkdownMessage/MarkdownMessage.tsx +26 -6
  209. package/src/tools/MarkdownMessage/components.tsx +22 -9
  210. package/src/tools/MarkdownMessage/types.ts +24 -1
  211. package/src/tools/Mermaid/Mermaid.client.tsx +27 -5
  212. package/src/tools/Mermaid/components/MermaidErrorPanel.tsx +31 -0
  213. package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +14 -17
  214. package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +264 -168
  215. package/src/tools/Mermaid/hooks/useMermaidValidation.ts +76 -10
  216. package/src/tools/Mermaid/index.tsx +6 -0
  217. package/src/tools/Mermaid/utils/mermaid-helpers.ts +141 -18
  218. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/FieldRow.tsx +11 -1
  219. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts +49 -20
  220. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/index.tsx +7 -0
  221. package/src/tools/OpenapiViewer/components/DocsLayout/grouping.ts +7 -4
  222. package/src/tools/OpenapiViewer/constants.ts +3 -0
  223. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +73 -11
  224. package/src/tools/OpenapiViewer/utils/schemaExport.ts +26 -6
  225. package/src/tools/PrettyCode/PrettyCode.client.tsx +23 -16
  226. package/src/tools/PrettyCode/lazy.tsx +1 -1
  227. package/src/tools/SpeechRecognition/README.md +1 -1
  228. package/src/tools/SpeechRecognition/__tests__/language.test.ts +9 -3
  229. package/src/tools/SpeechRecognition/components/RecordingPulse.tsx +59 -0
  230. package/src/tools/SpeechRecognition/components/index.ts +2 -0
  231. package/src/tools/SpeechRecognition/core/engine/external.ts +24 -7
  232. package/src/tools/SpeechRecognition/core/language.ts +23 -6
  233. package/src/tools/SpeechRecognition/hooks/usePushToTalk.ts +36 -5
  234. package/src/tools/SpeechRecognition/hooks/useSpeechRecognition.ts +18 -11
  235. package/src/tools/SpeechRecognition/widgets/VoiceComposerSlot.tsx +94 -26
  236. package/src/tools/SpeechRecognition/widgets/index.ts +1 -1
  237. package/src/tools/Tree/README.md +4 -8
  238. package/src/tools/Tree/TreeRoot.tsx +22 -10
  239. package/src/tools/Tree/components/TreeContent.tsx +24 -4
  240. package/src/tools/Tree/components/TreeLabel.tsx +8 -2
  241. package/src/tools/Tree/components/TreeRow.tsx +16 -6
  242. package/src/tools/Tree/data/flatten.ts +10 -4
  243. package/src/tools/Tree/types.ts +4 -0
  244. package/src/tools/Uploader/components/UploadAddButton.tsx +29 -6
  245. package/src/tools/Uploader/components/UploadDropzone.tsx +63 -7
  246. package/src/tools/Uploader/components/UploadPageDropOverlay.tsx +19 -5
  247. package/src/tools/Uploader/components/UploadPreviewItem.tsx +47 -17
  248. package/src/tools/Uploader/components/UploadPreviewList.tsx +24 -12
  249. package/src/tools/Uploader/utils/formatters.ts +8 -3
  250. package/src/tools/VideoPlayer/canvas/hls-canvas.tsx +1 -0
  251. package/src/tools/VideoPlayer/canvas/{jsx.d.ts → jsx-augmentation.ts} +12 -19
  252. package/src/tools/VideoPlayer/canvas/vimeo-canvas.tsx +1 -0
  253. package/src/tools/VideoPlayer/canvas/youtube-canvas.tsx +1 -0
  254. package/src/tools/VideoPlayer/parts/fullscreen.tsx +1 -1
  255. package/src/tools/VideoPlayer/parts/pip.tsx +1 -1
  256. package/src/tools/VideoPlayer/parts/playback-rate.tsx +1 -1
  257. package/src/tools/VideoPlayer/parts/seek-bar.tsx +2 -2
  258. package/src/tools/VideoPlayer/parts/volume.tsx +2 -2
  259. package/src/tools/index.ts +2 -1
  260. package/src/tools/Chat/components/AudioToggle.tsx +0 -78
  261. package/src/tools/Chat/components/ChatRoot.tsx +0 -305
  262. package/src/tools/Chat/components/Composer.tsx +0 -216
  263. package/src/tools/Chat/hooks/useChatScroll.ts +0 -145
  264. package/src/tools/Chat/types.ts +0 -9
  265. package/src/tools/JsonTree/components/JsonToolbar.tsx +0 -95
  266. package/src/tools/JsonTree/hooks/useElementCorner.ts +0 -84
  267. package/src/tools/JsonTree/hooks/useNavbarHeight.ts +0 -83
  268. package/src/tools/OpenapiViewer/components/DocsLayout/schemaFields.ts +0 -121
  269. package/src/tools/Tour/README.md +0 -373
  270. package/src/tools/Tour/components/Tour.tsx +0 -12
  271. package/src/tools/Tour/components/TourContent.tsx +0 -171
  272. package/src/tools/Tour/components/TourNavigation.tsx +0 -77
  273. package/src/tools/Tour/components/TourProgress.tsx +0 -88
  274. package/src/tools/Tour/components/TourSpotlight.tsx +0 -199
  275. package/src/tools/Tour/components/index.ts +0 -5
  276. package/src/tools/Tour/context/TourContext.ts +0 -19
  277. package/src/tools/Tour/context/TourProvider.tsx +0 -292
  278. package/src/tools/Tour/context/index.ts +0 -2
  279. package/src/tools/Tour/hooks/index.ts +0 -3
  280. package/src/tools/Tour/hooks/useKeyboardNavigation.ts +0 -59
  281. package/src/tools/Tour/hooks/useStepTarget.ts +0 -121
  282. package/src/tools/Tour/hooks/useTour.ts +0 -42
  283. package/src/tools/Tour/index.ts +0 -38
  284. package/src/tools/Tour/types/index.ts +0 -224
  285. package/src/tools/Tour/utils/dom.ts +0 -98
  286. package/src/tools/Tour/utils/index.ts +0 -3
  287. package/src/tools/Tour/utils/logger.ts +0 -3
  288. package/src/tools/Tour/utils/scrollIntoView.ts +0 -24
  289. /package/src/tools/Chat/{config.ts → constants.ts} +0 -0
  290. /package/src/tools/Chat/launcher/{ChatHeaderAudioToggle.tsx → header/ChatHeaderAudioToggle.tsx} +0 -0
  291. /package/src/tools/Chat/{components → messages}/Attachments.tsx +0 -0
  292. /package/src/tools/Chat/{components → messages}/JumpToLatest.tsx +0 -0
  293. /package/src/tools/Chat/{components → messages}/MessageActions.tsx +0 -0
  294. /package/src/tools/Chat/{components → messages}/Sources.tsx +0 -0
  295. /package/src/tools/Chat/{components → messages}/StreamingIndicator.tsx +0 -0
  296. /package/src/tools/Chat/{components → messages}/ToolCalls.tsx +0 -0
  297. /package/src/tools/Chat/{components → shell}/ErrorBanner.tsx +0 -0
@@ -51,11 +51,13 @@ export function MyChat() {
51
51
  - **Rich attachments.** `AttachmentsGrid` for thumbnails, `AttachmentsList` for custom renderers; `onAttachmentOpen` for lightbox.
52
52
  - **Tool-payload dispatcher.** `dispatchToolPayload(matchers, fallback)` — pluggable predicates render `<LazyJsonTree>` / `<LazyMap>` / etc.
53
53
  - **Persisted dock prefs.** `headerSlots.modeToggle.persistAs: 'my.key'` stores `mode` / `side` / `width` in localStorage; the toggle lets users flip popover ↔ side and survives reloads. (`useChatDockPrefs()` is now owned by the launcher internally — no longer a consumer-facing hook.)
54
- - **UX guards.** Auto-focus composer on dock open, two-step Escape (textarea blur → close), click-to-focus on empty message area (Slack / Linear style), `useHotkey('mod+/')` toggle.
54
+ - **UX guards.** Auto-focus composer on dock open, two-step Escape (textarea blur → close), click-to-focus on empty message area **and** on the composer surface padding (Slack / Linear / ChatGPT style), `useHotkey('mod+/')` toggle.
55
55
  - **ChatGPT-style autoscroll.** `MessageList` follows the bottom while the user is within `atBottomThreshold` px (default 120). Every user-sent message bumps `scrollAnchorId` and re-anchors the viewport with `behavior: 'smooth'` — sending no longer leaves your own bubble stuck above the fold. Scrolling up by hand breaks the lock; `<JumpToLatest>` brings it back.
56
- - **Voice composer slot.** `<VoiceComposerSlot />` drops into `composerToolbarEnd` with **zero props** — reads/writes the composer through the `ComposerHandle` registered in chat context. The built-in `<Composer>` and TipTap-backed `MarkdownEditor` register themselves automatically; custom composers wire it via `useRegisterComposer({ focus, moveCursorToEnd, getValue, setValue })`. Auto-gates on Firefox / in-app WebViews / missing `getUserMedia`, preserves typed prefix, 90-second countdown, silence auto-stop, Esc / Enter hotkeys, start / stop earcons. See [`SpeechRecognition`](../SpeechRecognition/README.md).
56
+ - **Voice composer slot.** `<VoiceComposerSlot />` drops into `composer.slots.blockStart` with **zero props** — reads/writes the composer through the `ComposerHandle` registered in chat context. The built-in `<Composer>` and TipTap-backed `MarkdownEditor` register themselves automatically; custom composers wire it via `useRegisterComposer({ focus, moveCursorToEnd, getValue, setValue })`. Auto-gates on Firefox / in-app WebViews / missing `getUserMedia`, preserves typed prefix, 90-second countdown, silence auto-stop, Esc / Enter hotkeys, start / stop earcons. See [`SpeechRecognition`](../SpeechRecognition/README.md).
57
57
  - **Language flag button.** `headerSlots.languagePicker: true` slots a 28×28 country flag into the dock header — opens a searchable `<Combobox>` with 66 BCP-47 tags from the Chrome Web Speech catalogue. Selection persists via `useSpeechPrefs`, picked up by every `useSpeechRecognition` downstream. (Raw `<ChatHeaderLanguageButton>` still exported for custom shells.)
58
- - **Auto-focus on stream end.** `useAutoFocusOnStreamEnd()` (active inside `ChatRoot` by default) re-focuses the composer on the streaming → idle edge — type → send → read → keep typing without reaching for the mouse.
58
+ - **Auto-focus on stream end.** `<ChatProvider>` re-focuses the registered composer on the streaming → idle edge — type → send → read → keep typing without reaching for the mouse. Works for **every** usage pattern (`ChatRoot`, hand-rolled `ChatProvider` + `Composer`, headless), not just `ChatRoot`. Opt out with `<ChatProvider autoFocusOnStreamEnd={false}>`. The standalone `useAutoFocusOnStreamEnd()` hook is still exported for advanced cases (focus a non-composer target, drive `isStreaming` from your own store).
59
+ - **Page-context snapshot.** Optional `getDynamicMetadata` contributor on `<ChatProvider>` / `<ChatRoot>` — called fresh at send time, merged into transport `metadata`. Pairs with the `page-snapshot` engine (`src/lib/page-snapshot`) to attach a token-efficient, redacted snapshot of the page the user is looking at, so the assistant can answer in context. The snapshot rides a separate `metadata` field, never the message text.
60
+ - **AI highlight directives.** `highlight/` — when the assistant returns `point` directives, `<HighlightOverlay>` resolves each CST ref to a live element and draws an SVG-mask spotlight (optionally moves focus). Read-only: it points at the UI, never changes data. See [`highlight/README.md`](./highlight/README.md).
59
61
  - **Centralized colors.** Role-aware className tokens (`BUBBLE_SURFACE` / `ANCHOR` / `TOGGLE` / `DESTRUCTIVE_SURFACE`) + hooks (`useChatBubbleStyles`, `useChatRoleStyles`, `useChatDestructiveStyles`).
60
62
  - **Responsive.** FAB `size='responsive'` (default): phone → `sm`, tablet → `md`, desktop → `lg`. Side mode is desktop-only and falls back to popover below `lg`.
61
63
  - **Mobile fullscreen.** Dock auto-fills viewport below 768px via `useIsMobile`. Heights use `dvh/svh/lvh` so iOS Safari URL bar doesn't clip the chat.
@@ -68,7 +70,7 @@ Transport (interface) ← Pydantic-AI / HTTP+SSE / Wails / mock
68
70
 
69
71
  Reducer (pure state machine)
70
72
 
71
- Hooks (useChat / useChatComposer / useChatScroll / useChatHistory / useChatLayout)
73
+ Hooks (useChat / useChatComposer / useChatHistory / useChatLayout)
72
74
 
73
75
  ChatProvider (context)
74
76
 
@@ -79,18 +81,25 @@ ChatRoot (one-line preset) ◄── optionally wrapped by ──► ChatLau
79
81
 
80
82
  `ChatLauncher` mounts the `ChatProvider` itself — pass `transport` / `config` / `audio` / `initialSessionId` / `autoCreateSession` / `streaming` / `debug` to the launcher and use `<ChatRoot />` without props as the child. `ChatRoot` detects the ambient provider and reuses it; standalone `<ChatRoot transport={…}>` still works for non-launcher embeds. This is what makes declarative `headerSlots` (which render in the dock header) able to call `useChatContext()` and read `sessionId` / `clearMessages` / etc.
81
83
 
84
+ `ChatProvider` also owns chat-wide UX behaviour that must hold regardless of which preset wraps it — notably **stream-end composer re-focus** (`autoFocusOnStreamEnd`, default `true`). Putting it in the provider means a hand-rolled `ChatProvider` + `MessageList` + `Composer` layout behaves identically to `ChatRoot` with no extra wiring.
85
+
82
86
  Module boundaries:
83
87
 
84
88
  | Layer | May import | May NOT import |
85
89
  | ---------------- | -------------------------------- | ------------------------ |
86
90
  | `types/` | — | anything |
87
- | `core/transport` | `types/` | React, hooks, components |
91
+ | `constants.ts` | | anything |
92
+ | `core/transport` | `types/` | React, hooks, UI |
88
93
  | `core/reducer` | `types/` | React, transport |
89
- | `hooks` | `core/*`, `ui-core` hooks | components |
90
- | `context` | `hooks` | components |
91
- | `styles/` | `types/` | hooks, components |
92
- | `components` | `hooks`, `context`, `styles`, `ui-core` UI | transport implementations |
93
- | `launcher/` | `components`, `styles`, `ui-core` UI | transport implementations |
94
+ | `hooks` | `core/*`, `ui-core` hooks | UI folders |
95
+ | `context` | `hooks` | UI folders |
96
+ | `styles/` | `types/` | hooks, UI folders |
97
+ | `utils/` | `types/` | React, UI folders |
98
+ | `messages/` | `hooks`, `context`, `styles`, `ui-core` UI | transport implementations |
99
+ | `composer/` | `hooks`, `context`, `styles`, `messages`, `ui-core` UI | transport implementations |
100
+ | `shell/` | `messages`, `composer`, `hooks`, `context`, `ui-core` UI | transport implementations |
101
+ | `launcher/` | `shell`, `messages`, `composer`, `styles`, `ui-core` UI | transport implementations |
102
+ | `launcher/header/` | `context`, `hooks`, `launcher` siblings | transport implementations |
94
103
 
95
104
  ## Types
96
105
 
@@ -107,6 +116,7 @@ Single source of truth in [`types/`](./types/index.ts). Split by domain — impo
107
116
  | `events` | `ChatStreamEvent` SSE union |
108
117
  | `session` | `SessionInfo`, `HistoryPage`, `Stream/Send/CreateSessionOptions` |
109
118
  | `transport` | `ChatTransport` |
119
+ | `block` | `MessageBlock` union + `BlockAppearance` (see Message blocks) |
110
120
 
111
121
  ## Launcher (FAB + Dock + Greeting)
112
122
 
@@ -525,6 +535,58 @@ function MyError() {
525
535
  }
526
536
  ```
527
537
 
538
+ ## Message blocks
539
+
540
+ A message can carry typed, serializable rich content beyond its markdown
541
+ `content`: an optional `blocks: MessageBlock[]`. `MessageBlock` is a
542
+ discriminated union on `kind` — `text`, `markdown`, `audio`, `video`,
543
+ `image`, `gallery`, `map`, `json`, `mermaid`, `code`, `lottie`, plus a
544
+ `custom` escape hatch. Payloads are plain JSON, so blocks survive history
545
+ persistence and SSE transport.
546
+
547
+ `<MessageBubble>` renders blocks **after** the markdown bubble, before
548
+ `toolCalls`. `text`/`markdown` blocks keep the bubble surface; media
549
+ blocks render full-width below it. Legacy messages (no `blocks`) render
550
+ byte-for-byte unchanged.
551
+
552
+ ```ts
553
+ const msg: ChatMessage = {
554
+ id: 'm-42', role: 'assistant', createdAt: Date.now(), content: '',
555
+ blocks: [
556
+ { kind: 'markdown', id: 'b1', markdown: 'Recording from **vps-audi**:' },
557
+ { kind: 'audio', id: 'b2', src: 'https://cdn…/call.mp3', title: 'Call · 04:12' },
558
+ { kind: 'map', id: 'b3', caption: 'Server', center: { lat: 52.52, lng: 13.405 }, zoom: 11 },
559
+ ],
560
+ };
561
+ ```
562
+
563
+ ### The registry
564
+
565
+ Each `kind` maps to a `BlockRenderer` in a `BlockRegistry` — same idea as
566
+ `AttachmentRendererMap`, but keyed by the discriminant. Built-in renderers
567
+ cover the in-house tools (`BUILTIN_BLOCK_REGISTRY`); each is reached
568
+ through a `React.lazy` chunk, so a chat with no map block never downloads
569
+ MapLibre.
570
+
571
+ Merge host overrides with `createBlockRegistry`:
572
+
573
+ ```ts
574
+ const registry = createBlockRegistry({
575
+ json: (block) => <MyJsonCard data={block.data} />, // override one kind
576
+ });
577
+ ```
578
+
579
+ Pass it via `<ChatRoot messages={{ blockRegistry }}>` — it is threaded
580
+ through context to every `<MessageBubble>`. `<MessageBubble>` also accepts
581
+ a direct `blockRegistry` prop (prop beats context) for standalone /
582
+ Storybook use. An unknown `kind` is skipped silently (lenient default);
583
+ `<MessageBlocks strict>` surfaces a dev notice. Each block is wrapped in
584
+ an error boundary so one malformed block can't blank the transcript.
585
+
586
+ Exports (`@djangocfg/ui-tools/chat`): `MessageBlocks`,
587
+ `createBlockRegistry`, `BUILTIN_BLOCK_REGISTRY`, and the `MessageBlock` /
588
+ `BlockRegistry` / `BlockRenderer` / `BlockRenderContext` types.
589
+
528
590
  ## Transport contract
529
591
 
530
592
  A single I/O seam.
@@ -564,11 +626,169 @@ for await (const event of parseSSE(res, { map: createPydanticAISSEMap() })) {
564
626
  }
565
627
  ```
566
628
 
567
- ## Slots
629
+ ## `ChatRoot` props
630
+
631
+ `<ChatRootProps>` groups ~13 keys by concern — no flat `composer*` namespace:
632
+
633
+ ```tsx
634
+ <ChatRoot
635
+ // core wiring
636
+ transport={transport}
637
+ config={{ greeting: 'Hi!' }}
638
+ session={{ initialId, autoCreate: true }}
639
+ streaming
640
+ audio={{}}
641
+ debug
642
+
643
+ // presentation
644
+ appearance="compact" // 'compact' | 'full' — scales the whole chat
645
+ className="…"
646
+ listClassName="px-6 pt-6" // padding inside the scroll viewport
647
+
648
+ // composition (grouped)
649
+ slots={{
650
+ banner: <QuotaWarning />,
651
+ header: (ctx) => <MyHeader sessionId={ctx.sessionId} />, // node or fn
652
+ empty: ({ setValue, focus }) => <MyEmpty seed={setValue} />, // node or fn
653
+ jumpToLatest: <MyPill />,
654
+ }}
655
+ composer={{
656
+ size: 'lg', // 'sm' | 'md' | 'lg'
657
+ layout: 'stacked', // 'stacked' | 'inline'
658
+ className: '…',
659
+ hidden: false, // unmount the composer (human-in-the-loop pause)
660
+ showAttachmentButton: true,
661
+ onPickFiles: () => openPicker(),
662
+ slots: { actionsStart, actionsEnd, blockStart }, // ComposerSlots
663
+ footer: { showCounter: true } /* | false to hide */,
664
+ render: ({ composer, config }) => <MyComposer composer={composer} />,
665
+ }}
666
+ messages={{
667
+ render: (m, i) => <MyBubble message={m} />,
668
+ renderAfter: (m) => <SideChannelWidget messageId={m.id} />,
669
+ toolCallsProps: { defaultExpanded: true },
670
+ attachmentRenderers: { image: MyImageTile },
671
+ onAttachmentOpen: (a) => openLightbox(a),
672
+ }}
673
+
674
+ // behavior
675
+ focusOnEmptyClick // default true
676
+
677
+ // page context — extra metadata computed fresh per send
678
+ getDynamicMetadata={() => ({ pageContext })}
679
+ />
680
+ ```
681
+
682
+ `getDynamicMetadata` is forwarded to the `<ChatProvider>` that `ChatRoot`
683
+ creates; it is **ignored** when `ChatRoot` mounts under an ambient
684
+ provider — set it on that provider instead. Typically wired from
685
+ `usePageSnapshot().getChatMetadata` (see `src/lib/page-snapshot`).
686
+
687
+ The `slots.header` / `slots.empty` accept either a `ReactNode` or a render
688
+ function — there is no separate `renderHeader` / `renderEmpty` prop and no
689
+ "which one wins" precedence. `composer.render` fully replaces the built-in
690
+ `<Composer>`; it receives `{ composer, config }` — the live composer hook
691
+ plus the same config object. See **[Slots inventory](#slots-inventory)** below.
692
+
693
+ ## Composer slot system
694
+
695
+ `<Composer>` is a single bordered input surface — textarea on top, an
696
+ action bar pinned to the bottom inside the same frame (attach
697
+ bottom-left, mic + send bottom-right). Two layouts:
568
698
 
569
- `<ChatRoot>` exposes named-prop slots — `header`, `footer`, `banner`, `empty`, `composerToolbarStart/End`, `composerAttachmentTray`, `jumpToLatest`. None are required.
699
+ - `layout="stacked"` (default) Telegram-style two-tier surface.
700
+ - `layout="inline"` — compact single row. `size="sm"` defaults to this.
570
701
 
571
- See **[Slots inventory](#slots-inventory)** below for the full list, plus `hideComposer` / `hideToolCalls` / `renderToolCall` / `renderAfterCalls` patterns.
702
+ ### Tier A declarative actions
703
+
704
+ Pass `ComposerAction[]` arrays; the composer renders consistent,
705
+ correctly-aligned, fully-labelled buttons. Built-in send/stop/attach are
706
+ themselves descriptors — host extras land between attach and mic/send.
707
+
708
+ ```tsx
709
+ <Composer
710
+ composer={composer}
711
+ composerSlots={{
712
+ actionsStart: [{ id: 'emoji', icon: <Smile />, label: 'Emoji', onClick }],
713
+ actionsEnd: [{ id: 'model', icon: <Sparkles />, label: 'Model', onClick }],
714
+ blockStart: <ReplyBanner />, // full-width row above the textarea
715
+ }}
716
+ />
717
+ ```
718
+
719
+ `ComposerAction` fields: `id`, `icon`, `label` (required — `aria-label` +
720
+ tooltip), `onClick`, `disabled?`, `pressed?` (→ `aria-pressed`),
721
+ `variant?`, `hideWhen?` (`streaming` | `empty` | `hasText` | `disabled`),
722
+ `order?`. Memoize the arrays so the action bar does not churn.
723
+
724
+ ### Tier B — slot replacement
725
+
726
+ Replace a primitive entirely via `slots` / tweak its props via
727
+ `slotProps`:
728
+
729
+ ```tsx
730
+ <Composer composer={composer}
731
+ slots={{ SendButton: MyBrandedSend, Textarea: MyEditor }}
732
+ slotProps={{ textarea: { className: 'font-mono' } }}
733
+ />
734
+ ```
735
+
736
+ Swappable: `SendButton`, `AttachButton`, `Textarea`, `ActionBar`.
737
+
738
+ ### mic ↔ send swap
739
+
740
+ `micSendSwap` (default `true`) — Telegram behaviour: an action with
741
+ `id: 'mic'` (or `hideWhen: 'hasText'`) shows only while the draft is
742
+ empty; once there is text, send takes its place. Set `false` to keep
743
+ both visible (voice-message hosts).
744
+
745
+ ### Footer toolbar
746
+
747
+ `<ComposerFooter>` is the quiet strip below the surface — three zones:
748
+ `start` (auto keyboard hint), `center` (host extras), `end` (auto char
749
+ counter that only appears near `maxLength`). Configure via the
750
+ `footer` prop on `<Composer>` or `composer.footer` on `<ChatRoot>`;
751
+ pass `false` to hide.
752
+
753
+ ### Composer kit — ready-made slot widgets
754
+
755
+ ChatGPT / Gemini-style controls that drop straight into `composerSlots`.
756
+ All are exported from `@djangocfg/ui-tools/chat` and size-aware (they
757
+ inherit the composer `size` via context, no prop needed).
758
+
759
+ | Component | Slot | What it is |
760
+ |---|---|---|
761
+ | `<ComposerMenuButton items={MenuItem[]}>` | `inlineStart` | The ChatGPT `+` button — opens a declarative `MenuBuilder` dropdown (sections, submenus, shortcuts). |
762
+ | `<ComposerToolPill icon label active onRemove>` | `inlineStart` | Gemini-style capsule for a selected tool/mode with an optional `×` to clear it. |
763
+ | `<ComposerModelPicker value options onChange>` | `inlineEnd` | "Flash-Lite ▾" pill — opens a radio-group model picker. |
764
+ | `<ComposerBanner variant title description actions onDismiss>` | `blockStart` | Standalone notice bubble above the composer (upsell / quota / info). |
765
+ | `<ComposerRichTextarea mentions>` | `slots.Textarea` (Tier B) | Ready-made TipTap chat editor — unstyled, no toolbar, size-matched height, Enter-to-send, `@`-mention support. |
766
+
767
+ ```tsx
768
+ <Composer
769
+ composer={composer}
770
+ composerSlots={{
771
+ blockStart: <ComposerBanner variant="upgrade" title="Free plan limit" … />,
772
+ inlineStart: <ComposerMenuButton items={MENU_ITEMS} />,
773
+ inlineEnd: <ComposerModelPicker value={model} options={MODELS} onChange={setModel} />,
774
+ }}
775
+ />
776
+ ```
777
+
778
+ ### `appearance` — compact vs full
779
+
780
+ `<Composer appearance="full">` layers extra spaciousness (radius,
781
+ padding, taller textarea, larger text) over the chosen `size` — for a
782
+ full-page ChatGPT/Gemini surface. `compact` (default) keeps the embedded
783
+ docked-chat geometry. Orthogonal to `size`; `<ChatRoot appearance="full">`
784
+ threads it to both the composer and the message bubbles.
785
+
786
+ ### Click-to-focus surface
787
+
788
+ The whole input panel reads as a text field — the padding around the
789
+ editor shows a text caret and a `mousedown` on the bare surface (not a
790
+ button) focuses the editor. Works for both the plain `<textarea>` and the
791
+ TipTap (`contenteditable`) backend.
572
792
 
573
793
  ## Three usage patterns
574
794
 
@@ -627,7 +847,7 @@ type HttpTransportConfig, type MockTransportOptions, type ParseSSEOptions,
627
847
  type PydanticAIChatTransportOpts, type PydanticAIEvent, type ToolIdQueue
628
848
 
629
849
  // Hooks
630
- useChat, useChatComposer, useChatScroll, useChatHistory, useChatLayout,
850
+ useChat, useChatComposer, useChatHistory, useChatLayout,
631
851
  useChatLightbox, useAutoFocusOnStreamEnd, useRegisterComposer,
632
852
  useChatReset, useVisitorFingerprint, useChatUnread, useChatUnreadNotifier,
633
853
  createBrowserNotifier, createCrossTabNotifier, createTitleRotator, createFaviconBadge,
@@ -682,22 +902,41 @@ JumpToLatest, StreamingIndicator
682
902
  // `MessageList` exposes `atBottomThreshold` (default 120 px) and
683
903
  // `scrollAnchorId` props for ChatGPT-style autoscroll — see "What you get".
684
904
 
905
+ // Composer kit — slot widgets + types
906
+ ComposerButton, ComposerActionBar, ComposerFooter,
907
+ ComposerMenuButton, ComposerToolPill, ComposerModelPicker,
908
+ ComposerBanner, ComposerRichTextarea, useComposerActions, useResolvedComposerSize
909
+ type ComposerProps, type ComposerSize, type ComposerAppearance,
910
+ type ComposerLayout, type ComposerAction, type ComposerActionVisibility,
911
+ type ComposerSlots, type ComposerSlotComponents, type ComposerSlotProps,
912
+ type ComposerFooterProps, type ComposerMenuButtonProps, type ComposerToolPillProps,
913
+ type ComposerModelPickerProps, type ComposerModelOption,
914
+ type ComposerBannerProps, type ComposerBannerAction, type ComposerRichTextareaProps,
915
+ type ComposerTextareaProps, type SendButtonProps, type AttachButtonProps
916
+
917
+ // Message blocks
918
+ MessageBlocks, createBlockRegistry, BUILTIN_BLOCK_REGISTRY
919
+ type MessageBlock, type BlockAppearance, type BlockRegistry,
920
+ type BlockRenderer, type BlockRenderContext
921
+
685
922
  // Lazy preset
686
923
  LazyChat
687
924
  ```
688
925
 
689
926
  ## Storybook
690
927
 
691
- Stories live next to components open `@djangocfg/playground`:
928
+ Stories are grouped into five feature sub-groups under `UI Tools/Chat`,
929
+ plus `Overview` (MDX) and `Showcase` (a one-screen living demo):
692
930
 
693
- - `Tools/Chat/Basic`Default + Composition patterns.
694
- - `Tools/Chat/Bubbles` every bubble state in one screen.
695
- - `Tools/Chat/ToolCalls`streaming panels + JsonTree payload dispatch.
696
- - `Tools/Chat/Personas` per-message persona overrides, multi-user transcripts.
697
- - `Tools/Chat/Launcher`Default / Playground / LiveChatStyle / MobileFullscreen / WithLivePush / VariantsAndSizes.
698
- - `Tools/Chat/Header` `ChatHeader` standalone, persistent dock prefs, side mode.
699
- - `Tools/Chat/Audio & Actions` audio toggle auto-inject, reset with `window.dialog.confirm`.
700
- - `Tools/Chat/Voice composer` — `<VoiceComposerSlot>` wired into the composer toolbar with a mock STT engine.
931
+ - **Getting Started** `ChatRoot`, `Composition` (bring-your-own-layout),
932
+ `Appearance` (compact / full / huge).
933
+ - **Messages**`Bubbles`, `Markdown`, `Message Blocks`,
934
+ `Tool Calls & Sources`, `Streaming`, `Personas`.
935
+ - **Composer**`Playground`, `Layout & Actions`, `Menu & Tools`,
936
+ `Banner`, `Mentions`, `Speech & Attachments`.
937
+ - **Launcher** `Playground`, `Parts`, `Header`,
938
+ `Unread & Notifications`.
939
+ - **Transports** — the three shipped transports with a stubbed `fetchImpl`.
701
940
 
702
941
  ---
703
942
 
@@ -705,37 +944,36 @@ Stories live next to components — open `@djangocfg/playground`:
705
944
 
706
945
  ## Slots inventory
707
946
 
708
- | Slot | Position | Render-prop variant |
947
+ | Key | Position | Type |
709
948
  |---|---|---|
710
- | `header` | Above messages | `renderHeader({ messages })` |
711
- | `footer` | Below composer | `renderFooter({ canSend })` |
712
- | `banner` | Top of message list | `renderBanner({ error, dismiss, retry })` |
713
- | `empty` | Empty state | `renderEmpty({ config, send })` |
714
- | `composerToolbarStart` / `composerToolbarEnd` | Composer toolbar | |
715
- | `composerAttachmentTray` | Above composer textarea | `renderAttachmentTray({ attachments })` |
716
- | `jumpToLatest` | Sticky overlay | `renderJumpToLatest({ unread, scrollToBottom })` |
717
- | `renderToolCall` | Per tool-call panel | `(call) => ReactNode` |
718
- | `renderAfterCalls` | After all tool panels | `(calls) => ReactNode` — **only renders when the message has tool calls** |
719
- | `renderAfterMessage` | Below every assistant bubble | `(message) => ReactNode` — fires for every message, independent of `toolCalls` |
949
+ | `slots.banner` | Top of message list | `ReactNode` |
950
+ | `slots.header` | Above messages | `ReactNode \| (ctx) => ReactNode` |
951
+ | `slots.empty` | Empty state | `ReactNode \| ({ setValue, focus }) => ReactNode` |
952
+ | `slots.jumpToLatest` | Sticky overlay | `ReactNode` |
953
+ | `composer.slots.actionsStart` / `actionsEnd` | Composer action clusters | `ComposerAction[]` |
954
+ | `composer.slots.blockStart` | Above composer textarea | `ReactNode` |
955
+ | `composer.footer` | Below composer | `ComposerFooterProps \| false` |
956
+ | `composer.render` | Replace `<Composer>` | `({ composer, config }) => ReactNode` |
957
+ | `messages.render` | Replace each bubble | `(m, i) => ReactNode` |
958
+ | `messages.renderAfter` | Below every assistant bubble | `(m) => ReactNode` — fires for every message, independent of `toolCalls` |
720
959
 
721
960
  Flags:
722
961
 
723
- - `hideComposer` — agent-pause / human-in-the-loop pause; composer is unmounted.
724
- - `hideToolCalls` — show only `renderAfterCalls` rich UI without raw tool panels.
962
+ - `composer.hidden` — agent-pause / human-in-the-loop pause; composer is unmounted.
725
963
 
726
- ### Which slot for product widgets — `renderAfterCalls` vs `renderAfterMessage`?
964
+ ### Which slot for product widgets — `renderAfterCalls` vs `messages.renderAfter`?
727
965
 
728
966
  Both put custom content under the assistant bubble; the difference is **what triggers them**.
729
967
 
730
968
  - `renderAfterCalls` is gated on `message.toolCalls?.length > 0`. Use it when the widget is **derived from raw tool output** (read `call.output`) and you can rely on the host streaming the `tool_call` / `tool_result` SSE frames. Admin / dev flows typically work this way.
731
- - `renderAfterMessage` fires **for every message**, regardless of `toolCalls`. Use it when the widget is driven by a side channel — e.g. typed `ui_payload` SSE frames the host emits independently of the raw tool surface. This is the correct slot when the public-prod stream **hides** `tool_call` events for security: the message lands with `toolCalls === undefined`, so `renderAfterCalls` would never mount, but `renderAfterMessage` still runs and the widget renders from the side channel.
969
+ - `messages.renderAfter` fires **for every message**, regardless of `toolCalls`. Use it when the widget is driven by a side channel — e.g. typed `ui_payload` SSE frames the host emits independently of the raw tool surface. This is the correct slot when the public-prod stream **hides** `tool_call` events for security: the message lands with `toolCalls === undefined`, so `renderAfterCalls` would never mount, but `messages.renderAfter` still runs and the widget renders from the side channel.
732
970
 
733
971
  Recommended pairing for a "vehicle cards" / "tax breakdown" / "chart" widget on a public chat:
734
972
 
735
973
  ```tsx
736
974
  <ChatRoot
737
975
  transport={transport}
738
- renderAfterMessage={(m) => <VehicleCardsForMessage messageId={m.id} />}
976
+ messages={{ renderAfter: (m) => <VehicleCardsForMessage messageId={m.id} /> }}
739
977
  />
740
978
  ```
741
979