@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
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Message blocks — typed, serializable rich-content slots.
3
+ *
4
+ * A `ChatMessage` may carry `blocks: MessageBlock[]`. Each block is a
5
+ * discriminated union member keyed by `kind`. Payloads are plain JSON
6
+ * (no Date, no Float32Array, no React nodes) so blocks survive history
7
+ * persistence and SSE transport. See `@docs/message-blocks.md`.
8
+ */
9
+
10
+ /** Spaciousness hint forwarded from the bubble to renderers. */
11
+ export type BlockAppearance = 'compact' | 'full';
12
+
13
+ interface BlockBase {
14
+ /** Stable id — used as React key and for memo diffing. */
15
+ id: string;
16
+ /** Optional caption rendered above the block. */
17
+ caption?: string;
18
+ }
19
+
20
+ export interface TextBlock extends BlockBase {
21
+ kind: 'text';
22
+ text: string;
23
+ }
24
+
25
+ export interface MarkdownBlock extends BlockBase {
26
+ kind: 'markdown';
27
+ markdown: string;
28
+ }
29
+
30
+ export interface AudioBlock extends BlockBase {
31
+ kind: 'audio';
32
+ src: string;
33
+ title?: string;
34
+ artist?: string;
35
+ cover?: string;
36
+ /** `compact` → AudioPlayer variant. */
37
+ variant?: 'default' | 'compact';
38
+ }
39
+
40
+ export interface VideoBlock extends BlockBase {
41
+ kind: 'video';
42
+ /** Raw URL string — VideoPlayer auto-classifies YouTube/Vimeo/HLS/MP4. */
43
+ src: string;
44
+ poster?: string;
45
+ title?: string;
46
+ aspectRatio?: number | 'auto' | 'fill';
47
+ }
48
+
49
+ export interface ImageBlock extends BlockBase {
50
+ kind: 'image';
51
+ src: string;
52
+ alt?: string;
53
+ /** When true → zoom/pan ImageViewer; else a plain `<img>`. */
54
+ interactive?: boolean;
55
+ }
56
+
57
+ export interface GalleryBlock extends BlockBase {
58
+ kind: 'gallery';
59
+ items: Array<{ id: string; src: string; thumbnail?: string; alt?: string }>;
60
+ }
61
+
62
+ export interface MapBlock extends BlockBase {
63
+ kind: 'map';
64
+ center: { lat: number; lng: number };
65
+ zoom?: number;
66
+ markers?: Array<{ id: string; lat: number; lng: number; label?: string }>;
67
+ }
68
+
69
+ export interface JsonBlock extends BlockBase {
70
+ kind: 'json';
71
+ data: unknown;
72
+ /** JsonTree display mode. */
73
+ mode?: 'full' | 'compact' | 'inline';
74
+ }
75
+
76
+ export interface MermaidBlock extends BlockBase {
77
+ kind: 'mermaid';
78
+ chart: string;
79
+ }
80
+
81
+ export interface CodeBlock extends BlockBase {
82
+ kind: 'code';
83
+ code: string;
84
+ language: string;
85
+ }
86
+
87
+ export interface LottieBlock extends BlockBase {
88
+ kind: 'lottie';
89
+ /** Animation JSON URL or inline animationData object. */
90
+ src: string;
91
+ }
92
+
93
+ /**
94
+ * Escape hatch for host-defined block types. The built-in registry
95
+ * renders it as the unknown-kind fallback unless the host registers a
96
+ * `custom` renderer that switches on `name`.
97
+ */
98
+ export interface CustomBlock extends BlockBase {
99
+ kind: 'custom';
100
+ /** Host-defined sub-type, dispatched by the host's own renderer. */
101
+ name: string;
102
+ payload: unknown;
103
+ }
104
+
105
+ export type MessageBlock =
106
+ | TextBlock
107
+ | MarkdownBlock
108
+ | AudioBlock
109
+ | VideoBlock
110
+ | ImageBlock
111
+ | GalleryBlock
112
+ | MapBlock
113
+ | JsonBlock
114
+ | MermaidBlock
115
+ | CodeBlock
116
+ | LottieBlock
117
+ | CustomBlock;
118
+
119
+ /** The discriminant literal — `'audio' | 'video' | …`. */
120
+ export type MessageBlockKind = MessageBlock['kind'];
@@ -29,13 +29,8 @@ export interface ChatConfig {
29
29
  description?: string;
30
30
  /** Suggested prompts shown on empty conversation. */
31
31
  suggestions?: Array<{ label: string; prompt: string }>;
32
- /** Locale forwarded to the transport via stream metadata. */
33
- locale?: string;
34
32
  /** Project / chat slug forwarded to the transport. */
35
33
  slug?: string;
36
- /** UI density. Use `prefs.density` for the same effect; this stays for
37
- * backwards compatibility. */
38
- density?: 'comfortable' | 'compact';
39
34
  /** Identity of the human author. Renders avatar / name on user bubbles
40
35
  * and gets stamped on outgoing messages as `message.sender`. */
41
36
  user?: ChatUserContext;
@@ -19,6 +19,23 @@
19
19
  export type { ChatRole, ChatPersona, ChatUserContext, ChatAssistantContext } from './persona';
20
20
  export type { ChatToolCall } from './tool-call';
21
21
  export type { ChatAttachment, ChatSource } from './attachment';
22
+ export type {
23
+ MessageBlock,
24
+ MessageBlockKind,
25
+ BlockAppearance,
26
+ TextBlock,
27
+ MarkdownBlock,
28
+ AudioBlock,
29
+ VideoBlock,
30
+ ImageBlock,
31
+ GalleryBlock,
32
+ MapBlock,
33
+ JsonBlock,
34
+ MermaidBlock,
35
+ CodeBlock,
36
+ LottieBlock,
37
+ CustomBlock,
38
+ } from './block';
22
39
  export type { ChatMessage } from './message';
23
40
  export { DEFAULT_LABELS } from './labels';
24
41
  export type { ChatLabels } from './labels';
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { ChatAttachment, ChatSource } from './attachment';
9
+ import type { MessageBlock } from './block';
9
10
  import type { ChatPersona, ChatRole } from './persona';
10
11
  import type { ChatToolCall } from './tool-call';
11
12
 
@@ -26,6 +27,8 @@ export interface ChatMessage {
26
27
  toolActivity?: string;
27
28
  toolCalls?: ChatToolCall[];
28
29
  attachments?: ChatAttachment[];
30
+ /** Rich tool-component blocks rendered after the message body. */
31
+ blocks?: MessageBlock[];
29
32
  sources?: ChatSource[];
30
33
  tokensIn?: number;
31
34
  tokensOut?: number;
@@ -0,0 +1,4 @@
1
+ /** Utils — pure chat helpers (draft sanitizer, attachment collector). */
2
+
3
+ export { sanitizeDraft, isSubmittableDraft } from './sanitizeDraft';
4
+ export { collectImageAttachments } from './collectImageAttachments';
@@ -102,14 +102,12 @@ const {
102
102
 
103
103
  ## Theme
104
104
 
105
- Editor automatically follows the app theme (dark/light) via `useResolvedTheme()` from `@djangocfg/ui-core`. No configuration needed just works with `next-themes` or any setup that toggles `.dark` class on `<html>`.
105
+ The editor is always dark (devtool-style), regardless of the app theme. The custom `app-dark` Monaco theme is registered automatically with colors derived from CSS variables (`--background`, `--foreground`, `--card`, `--border`).
106
106
 
107
- Custom Monaco themes (`app-dark`, `app-light`) are registered automatically with colors derived from CSS variables (`--background`, `--foreground`, `--card`, `--border`).
108
-
109
- Override with explicit theme:
107
+ Override with an explicit theme:
110
108
 
111
109
  ```tsx
112
- <Editor options={{ theme: 'vs-dark' }} /> {/* force dark */}
110
+ <Editor options={{ theme: 'vs-dark' }} /> {/* built-in dark */}
113
111
  <Editor options={{ theme: 'hc-black' }} /> {/* high contrast */}
114
112
  ```
115
113
 
@@ -120,7 +118,7 @@ Override with explicit theme:
120
118
  | `useMonaco()` | `{ monaco, isLoading, error }` | Load Monaco namespace |
121
119
  | `useEditor()` | `{ editor, isReady, setEditor }` | Editor instance management |
122
120
  | `useLanguage(filename)` | `string` | Detect language from file extension |
123
- | `useEditorTheme(monaco, override?)` | `string` | Resolved Monaco theme name (auto dark/light) |
121
+ | `useEditorTheme(monaco, override?)` | `string` | Resolved Monaco theme name (always dark) |
124
122
 
125
123
  ## Types
126
124
 
@@ -4,7 +4,7 @@ import { useRef, useEffect } from 'react';
4
4
  import type * as monaco from 'monaco-editor';
5
5
 
6
6
  import { useMonaco } from '../hooks/useMonaco';
7
- import { useEditorTheme } from '../hooks/useEditorTheme';
7
+ import { useEditorTheme, EDITOR_BACKGROUND } from '../hooks/useEditorTheme';
8
8
  import type { DiffEditorProps } from '../types';
9
9
 
10
10
  /**
@@ -31,10 +31,10 @@ export function DiffEditor({
31
31
  }: DiffEditorProps) {
32
32
  const containerRef = useRef<HTMLDivElement>(null);
33
33
  const editorRef = useRef<monaco.editor.IStandaloneDiffEditor | null>(null);
34
- const { monaco, isLoading } = useMonaco();
34
+ const { monaco, isLoading, error } = useMonaco();
35
35
  const resolvedTheme = useEditorTheme(monaco, options.theme);
36
36
 
37
- // Create diff editor
37
+ // Create diff editor — runs once Monaco is available.
38
38
  useEffect(() => {
39
39
  if (!monaco || !containerRef.current || editorRef.current) return;
40
40
 
@@ -60,24 +60,26 @@ export function DiffEditor({
60
60
  editorRef.current = editor;
61
61
 
62
62
  return () => {
63
+ // Dispose models before the editor so Monaco releases all references.
63
64
  originalModel.dispose();
64
65
  modifiedModel.dispose();
65
66
  editor.dispose();
66
67
  editorRef.current = null;
67
68
  };
69
+ // eslint-disable-next-line react-hooks/exhaustive-deps
68
70
  }, [monaco]);
69
71
 
70
- // Update models when content changes
72
+ // Update models when content changes — skip no-op writes to avoid
73
+ // resetting scroll/decorations on unrelated re-renders.
71
74
  useEffect(() => {
72
75
  const editor = editorRef.current;
73
- if (!editor || !monaco) return;
76
+ if (!editor) return;
74
77
 
75
78
  const model = editor.getModel();
76
- if (model) {
77
- model.original.setValue(original);
78
- model.modified.setValue(modified);
79
- }
80
- }, [original, modified, monaco]);
79
+ if (!model) return;
80
+ if (model.original.getValue() !== original) model.original.setValue(original);
81
+ if (model.modified.getValue() !== modified) model.modified.setValue(modified);
82
+ }, [original, modified]);
81
83
 
82
84
  // Update language
83
85
  useEffect(() => {
@@ -91,21 +93,53 @@ export function DiffEditor({
91
93
  }
92
94
  }, [language, monaco]);
93
95
 
96
+ // Apply theme — must go through setTheme(), updateOptions ignores `theme`.
97
+ useEffect(() => {
98
+ if (!monaco || !editorRef.current) return;
99
+ monaco.editor.setTheme(resolvedTheme);
100
+ }, [monaco, resolvedTheme]);
101
+
102
+ if (error) {
103
+ return (
104
+ <div
105
+ className={className}
106
+ role="alert"
107
+ style={{
108
+ width: '100%',
109
+ height,
110
+ display: 'flex',
111
+ alignItems: 'center',
112
+ justifyContent: 'center',
113
+ padding: 16,
114
+ textAlign: 'center',
115
+ backgroundColor: EDITOR_BACKGROUND,
116
+ color: 'hsl(var(--destructive))',
117
+ fontSize: 13,
118
+ }}
119
+ >
120
+ Failed to load diff editor: {error.message}
121
+ </div>
122
+ );
123
+ }
124
+
94
125
  if (isLoading) {
95
126
  return (
96
127
  <div
97
128
  className={className}
129
+ role="status"
130
+ aria-busy="true"
98
131
  style={{
99
132
  width: '100%',
100
133
  height,
101
134
  display: 'flex',
102
135
  alignItems: 'center',
103
136
  justifyContent: 'center',
104
- backgroundColor: '#1e1e1e',
105
- color: '#666',
137
+ backgroundColor: EDITOR_BACKGROUND,
138
+ color: 'hsl(var(--muted-foreground))',
139
+ fontSize: 13,
106
140
  }}
107
141
  >
108
- Loading diff editor...
142
+ Loading diff editor
109
143
  </div>
110
144
  );
111
145
  }
@@ -117,6 +151,7 @@ export function DiffEditor({
117
151
  style={{
118
152
  width: '100%',
119
153
  height,
154
+ backgroundColor: EDITOR_BACKGROUND,
120
155
  }}
121
156
  />
122
157
  );
@@ -4,7 +4,7 @@ import { useRef, useEffect, useState, useCallback, forwardRef, useImperativeHand
4
4
  import type * as monaco from 'monaco-editor';
5
5
 
6
6
  import { useMonaco } from '../hooks/useMonaco';
7
- import { useEditorTheme } from '../hooks/useEditorTheme';
7
+ import { useEditorTheme, EDITOR_BACKGROUND } from '../hooks/useEditorTheme';
8
8
  import type { EditorProps } from '../types';
9
9
 
10
10
  export interface EditorRef {
@@ -51,7 +51,7 @@ export const Editor = forwardRef<EditorRef, EditorProps>(function Editor(
51
51
  ) {
52
52
  const containerRef = useRef<HTMLDivElement>(null);
53
53
  const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
54
- const { monaco, isLoading } = useMonaco();
54
+ const { monaco, isLoading, error } = useMonaco();
55
55
  const resolvedTheme = useEditorTheme(monaco, options.theme);
56
56
 
57
57
  // Auto-height state
@@ -63,18 +63,24 @@ export const Editor = forwardRef<EditorRef, EditorProps>(function Editor(
63
63
  setContentHeight(Math.min(Math.max(h, minHeight), maxHeight));
64
64
  }, [autoHeight, minHeight, maxHeight]);
65
65
 
66
- // Track internal changes to prevent cursor reset on prop sync
67
- const isInternalChangeRef = useRef(false);
66
+ // Keep latest callbacks in refs so the create-effect can stay [monaco]-only
67
+ // without going stale when the parent passes new function identities.
68
+ const onChangeRef = useRef(onChange);
69
+ const onMountRef = useRef(onMount);
70
+ useEffect(() => {
71
+ onChangeRef.current = onChange;
72
+ onMountRef.current = onMount;
73
+ });
68
74
 
69
75
  // Expose editor methods via ref
70
76
  useImperativeHandle(ref, () => ({
71
77
  getEditor: () => editorRef.current,
72
- getValue: () => editorRef.current?.getValue() || '',
78
+ getValue: () => editorRef.current?.getValue() ?? '',
73
79
  setValue: (val: string) => editorRef.current?.setValue(val),
74
80
  focus: () => editorRef.current?.focus(),
75
- }));
81
+ }), []);
76
82
 
77
- // Create editor
83
+ // Create editor — runs once Monaco is available.
78
84
  useEffect(() => {
79
85
  if (!monaco || !containerRef.current || editorRef.current) return;
80
86
 
@@ -91,7 +97,7 @@ export const Editor = forwardRef<EditorRef, EditorProps>(function Editor(
91
97
  lineNumbers: options.lineNumbers || 'on',
92
98
  readOnly: options.readOnly || false,
93
99
  automaticLayout: true,
94
- scrollBeyondLastLine: autoHeight ? false : false,
100
+ scrollBeyondLastLine: !autoHeight,
95
101
  scrollbar: autoHeight ? { vertical: 'hidden', horizontal: 'auto' } : undefined,
96
102
  overviewRulerLanes: autoHeight ? 0 : undefined,
97
103
  padding: { top: 16, bottom: 16 },
@@ -108,57 +114,60 @@ export const Editor = forwardRef<EditorRef, EditorProps>(function Editor(
108
114
 
109
115
  editorRef.current = editor;
110
116
 
111
- // Setup change listener
112
- if (onChange) {
113
- editor.onDidChangeModelContent(() => {
114
- // Mark as internal change to prevent cursor reset on prop sync
115
- isInternalChangeRef.current = true;
116
- onChange(editor.getValue());
117
- });
118
- }
117
+ // Change listener — always attached; reads latest onChange via ref.
118
+ const changeSub = editor.onDidChangeModelContent(() => {
119
+ onChangeRef.current?.(editor.getValue());
120
+ });
119
121
 
120
122
  // Auto-height: resize container to fit content
123
+ let sizeSub: monaco.IDisposable | undefined;
121
124
  if (autoHeight) {
122
- editor.onDidContentSizeChange(() => updateContentHeight(editor));
125
+ sizeSub = editor.onDidContentSizeChange(() => updateContentHeight(editor));
123
126
  updateContentHeight(editor);
124
127
  }
125
128
 
126
129
  // Call onMount callback
127
- onMount?.(editor);
130
+ onMountRef.current?.(editor);
128
131
 
129
132
  return () => {
133
+ changeSub.dispose();
134
+ sizeSub?.dispose();
135
+ // Dispose the model explicitly — editor.dispose() does not always
136
+ // release models in older Monaco builds, and never the ones we own.
137
+ editor.getModel()?.dispose();
130
138
  editor.dispose();
131
139
  editorRef.current = null;
132
140
  };
141
+ // eslint-disable-next-line react-hooks/exhaustive-deps
133
142
  }, [monaco]);
134
143
 
135
- // Update value when prop changes (only for external changes)
144
+ // Sync value when the prop changes from outside (skip echoes of user typing).
136
145
  useEffect(() => {
137
146
  const editor = editorRef.current;
138
147
  if (!editor) return;
139
148
 
140
- // Skip if this is an internal change (user typing) - prevents cursor reset
141
- if (isInternalChangeRef.current) {
142
- isInternalChangeRef.current = false;
143
- return;
144
- }
145
-
146
149
  const currentValue = editor.getValue();
147
- if (value !== currentValue) {
148
- // Save cursor position
149
- const position = editor.getPosition();
150
- const selections = editor.getSelections();
150
+ // If the prop already matches the editor, nothing to do — this also
151
+ // covers the common controlled case where onChange echoes value back,
152
+ // so we never reset the cursor on the user's own keystrokes.
153
+ if (value === currentValue) return;
151
154
 
152
- editor.setValue(value);
155
+ const position = editor.getPosition();
156
+ const selections = editor.getSelections();
153
157
 
154
- // Restore cursor position if possible
155
- if (position) {
156
- editor.setPosition(position);
157
- }
158
- if (selections && selections.length > 0) {
159
- editor.setSelections(selections);
160
- }
158
+ // executeEdits preserves undo history (unlike setValue, which wipes it).
159
+ const model = editor.getModel();
160
+ if (model) {
161
+ editor.executeEdits('external-sync', [
162
+ { range: model.getFullModelRange(), text: value, forceMoveMarkers: true },
163
+ ]);
164
+ editor.pushUndoStop();
165
+ } else {
166
+ editor.setValue(value);
161
167
  }
168
+
169
+ if (position) editor.setPosition(position);
170
+ if (selections && selections.length > 0) editor.setSelections(selections);
162
171
  }, [value]);
163
172
 
164
173
  // Update language when prop changes
@@ -167,41 +176,83 @@ export const Editor = forwardRef<EditorRef, EditorProps>(function Editor(
167
176
  if (!editor || !monaco) return;
168
177
 
169
178
  const model = editor.getModel();
170
- if (model) {
179
+ if (model && model.getLanguageId() !== language) {
171
180
  monaco.editor.setModelLanguage(model, language);
172
181
  }
173
182
  }, [language, monaco]);
174
183
 
184
+ // Apply theme — Monaco ignores `theme` inside updateOptions, it must go
185
+ // through setTheme(). This is what makes light/dark switching actually work.
186
+ useEffect(() => {
187
+ if (!monaco || !editorRef.current) return;
188
+ monaco.editor.setTheme(resolvedTheme);
189
+ }, [monaco, resolvedTheme]);
190
+
175
191
  // Update options when props change
176
192
  useEffect(() => {
177
193
  const editor = editorRef.current;
178
194
  if (!editor) return;
179
195
 
180
196
  editor.updateOptions({
181
- theme: resolvedTheme,
182
197
  fontSize: options.fontSize,
183
198
  readOnly: options.readOnly,
184
199
  minimap: { enabled: options.minimap !== false },
185
200
  wordWrap: options.wordWrap,
186
201
  lineNumbers: options.lineNumbers,
202
+ tabSize: options.tabSize,
203
+ insertSpaces: options.insertSpaces,
187
204
  });
188
- }, [options, resolvedTheme]);
205
+ }, [
206
+ options.fontSize,
207
+ options.readOnly,
208
+ options.minimap,
209
+ options.wordWrap,
210
+ options.lineNumbers,
211
+ options.tabSize,
212
+ options.insertSpaces,
213
+ ]);
214
+
215
+ if (error) {
216
+ return (
217
+ <div
218
+ className={className}
219
+ role="alert"
220
+ style={{
221
+ width,
222
+ height,
223
+ display: 'flex',
224
+ alignItems: 'center',
225
+ justifyContent: 'center',
226
+ padding: 16,
227
+ textAlign: 'center',
228
+ backgroundColor: EDITOR_BACKGROUND,
229
+ color: 'hsl(var(--destructive))',
230
+ fontSize: 13,
231
+ }}
232
+ >
233
+ Failed to load editor: {error.message}
234
+ </div>
235
+ );
236
+ }
189
237
 
190
238
  if (isLoading) {
191
239
  return (
192
240
  <div
193
241
  className={className}
242
+ role="status"
243
+ aria-busy="true"
194
244
  style={{
195
245
  width,
196
246
  height,
197
247
  display: 'flex',
198
248
  alignItems: 'center',
199
249
  justifyContent: 'center',
200
- backgroundColor: '#1e1e1e',
201
- color: '#666',
250
+ backgroundColor: EDITOR_BACKGROUND,
251
+ color: 'hsl(var(--muted-foreground))',
252
+ fontSize: 13,
202
253
  }}
203
254
  >
204
- Loading editor...
255
+ Loading editor
205
256
  </div>
206
257
  );
207
258
  }
@@ -215,7 +266,8 @@ export const Editor = forwardRef<EditorRef, EditorProps>(function Editor(
215
266
  style={{
216
267
  width,
217
268
  height: resolvedHeight,
218
- ...(autoHeight && { minHeight: minHeight, maxHeight: maxHeight, overflow: 'hidden' }),
269
+ backgroundColor: EDITOR_BACKGROUND,
270
+ ...(autoHeight && { minHeight, maxHeight, overflow: 'hidden' }),
219
271
  }}
220
272
  />
221
273
  );
@@ -168,23 +168,40 @@ export function EditorProvider({ children, onSave }: EditorProviderProps) {
168
168
  [openFiles]
169
169
  );
170
170
 
171
- const value: EditorContextValue = {
172
- openFiles,
173
- activeFile,
174
- monaco,
175
- editor,
176
- isReady: monaco !== null && editor !== null,
177
-
178
- openFile,
179
- closeFile,
180
- setActiveFile,
181
- updateContent,
182
- saveFile,
183
-
184
- isDirty,
185
- getContent,
186
- getFile,
187
- };
171
+ // Memoize so consumers don't re-render on unrelated parent renders.
172
+ const value = useMemo<EditorContextValue>(
173
+ () => ({
174
+ openFiles,
175
+ activeFile,
176
+ monaco,
177
+ editor,
178
+ isReady: monaco !== null && editor !== null,
179
+
180
+ openFile,
181
+ closeFile,
182
+ setActiveFile,
183
+ updateContent,
184
+ saveFile,
185
+
186
+ isDirty,
187
+ getContent,
188
+ getFile,
189
+ }),
190
+ [
191
+ openFiles,
192
+ activeFile,
193
+ monaco,
194
+ editor,
195
+ openFile,
196
+ closeFile,
197
+ setActiveFile,
198
+ updateContent,
199
+ saveFile,
200
+ isDirty,
201
+ getContent,
202
+ getFile,
203
+ ],
204
+ );
188
205
 
189
206
  return (
190
207
  <EditorContext.Provider value={value}>