@djangocfg/ui-tools 2.1.404 → 2.1.408

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 (336) hide show
  1. package/README.md +9 -11
  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 +13 -16
  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/lazy.tsx +13 -27
  67. package/src/tools/AudioPlayer/parts/Meta/TimeDisplay.tsx +2 -5
  68. package/src/tools/Chat/README.md +267 -39
  69. package/src/tools/Chat/composer/Composer.tsx +471 -0
  70. package/src/tools/Chat/composer/ComposerActionBar.tsx +65 -0
  71. package/src/tools/Chat/composer/ComposerBanner.tsx +128 -0
  72. package/src/tools/Chat/composer/ComposerButton.tsx +64 -0
  73. package/src/tools/Chat/composer/ComposerFooter.tsx +90 -0
  74. package/src/tools/Chat/composer/ComposerMenuButton.tsx +62 -0
  75. package/src/tools/Chat/composer/ComposerModelPicker.tsx +104 -0
  76. package/src/tools/Chat/composer/ComposerRichTextarea.tsx +88 -0
  77. package/src/tools/Chat/composer/ComposerToolPill.tsx +95 -0
  78. package/src/tools/Chat/composer/index.ts +45 -0
  79. package/src/tools/Chat/composer/size-context.tsx +26 -0
  80. package/src/tools/Chat/composer/types.ts +143 -0
  81. package/src/tools/Chat/composer/useComposerActions.tsx +164 -0
  82. package/src/tools/Chat/context/ChatProvider.tsx +54 -3
  83. package/src/tools/Chat/core/__tests__/metadata.test.ts +69 -0
  84. package/src/tools/Chat/core/index.ts +23 -1
  85. package/src/tools/Chat/core/markdown.ts +1 -1
  86. package/src/tools/Chat/core/metadata.ts +47 -0
  87. package/src/tools/Chat/core/payload-dispatch.ts +1 -1
  88. package/src/tools/Chat/core/transport/http.ts +71 -32
  89. package/src/tools/Chat/core/transport/sse.ts +18 -10
  90. package/src/tools/Chat/highlight/HighlightOverlay.tsx +101 -0
  91. package/src/tools/Chat/highlight/README.md +103 -0
  92. package/src/tools/Chat/highlight/SpotlightCanvas.tsx +153 -0
  93. package/src/tools/Chat/highlight/__tests__/HighlightOverlay.test.tsx +112 -0
  94. package/src/tools/Chat/highlight/__tests__/resolveRef.test.ts +55 -0
  95. package/src/tools/Chat/highlight/index.ts +21 -0
  96. package/src/tools/Chat/highlight/resolveRef.ts +42 -0
  97. package/src/tools/Chat/highlight/types.ts +49 -0
  98. package/src/tools/Chat/highlight/useHighlightTargets.ts +128 -0
  99. package/src/tools/Chat/hooks/index.ts +0 -5
  100. package/src/tools/Chat/hooks/useAutoFocusOnStreamEnd.ts +28 -47
  101. package/src/tools/Chat/hooks/useChat.ts +47 -14
  102. package/src/tools/Chat/hooks/useChatComposer.ts +2 -2
  103. package/src/tools/Chat/hooks/useChatLayout.ts +1 -1
  104. package/src/tools/Chat/hooks/useStreamEndFocus.ts +54 -0
  105. package/src/tools/Chat/index.ts +25 -219
  106. package/src/tools/Chat/launcher/ChatDock.tsx +1 -1
  107. package/src/tools/Chat/launcher/ChatLauncher.tsx +1 -1
  108. package/src/tools/Chat/launcher/{ChatHeader.tsx → header/ChatHeader.tsx} +24 -11
  109. package/src/tools/Chat/launcher/{ChatHeaderActionButton.tsx → header/ChatHeaderActionButton.tsx} +34 -3
  110. package/src/tools/Chat/launcher/{ChatHeaderLanguageButton.tsx → header/ChatHeaderLanguageButton.tsx} +2 -2
  111. package/src/tools/Chat/launcher/{ChatHeaderModeToggle.tsx → header/ChatHeaderModeToggle.tsx} +1 -1
  112. package/src/tools/Chat/launcher/{ChatHeaderResetButton.tsx → header/ChatHeaderResetButton.tsx} +2 -1
  113. package/src/tools/Chat/launcher/{HeaderSlots.tsx → header/HeaderSlots.tsx} +3 -3
  114. package/src/tools/Chat/launcher/header/index.ts +26 -0
  115. package/src/tools/Chat/launcher/index.ts +3 -10
  116. package/src/tools/Chat/lazy.tsx +38 -284
  117. package/src/tools/Chat/{components → messages}/MessageBubble.tsx +58 -5
  118. package/src/tools/Chat/{components → messages}/MessageList.tsx +8 -25
  119. package/src/tools/Chat/messages/blocks/MessageBlocks.tsx +131 -0
  120. package/src/tools/Chat/messages/blocks/builtin.tsx +91 -0
  121. package/src/tools/Chat/messages/blocks/index.ts +12 -0
  122. package/src/tools/Chat/messages/blocks/registry.tsx +42 -0
  123. package/src/tools/Chat/messages/blocks/renderers/AudioBlock.tsx +20 -0
  124. package/src/tools/Chat/messages/blocks/renderers/CodeBlock.tsx +19 -0
  125. package/src/tools/Chat/messages/blocks/renderers/GalleryBlock.tsx +26 -0
  126. package/src/tools/Chat/messages/blocks/renderers/ImageBlock.tsx +27 -0
  127. package/src/tools/Chat/messages/blocks/renderers/JsonBlock.tsx +12 -0
  128. package/src/tools/Chat/messages/blocks/renderers/LottieBlock.tsx +11 -0
  129. package/src/tools/Chat/messages/blocks/renderers/MapBlock.tsx +36 -0
  130. package/src/tools/Chat/messages/blocks/renderers/MermaidBlock.tsx +11 -0
  131. package/src/tools/Chat/messages/blocks/renderers/VideoBlock.tsx +24 -0
  132. package/src/tools/Chat/messages/blocks/renderers/types.ts +8 -0
  133. package/src/tools/Chat/{components → messages}/index.ts +11 -5
  134. package/src/tools/Chat/public.ts +212 -0
  135. package/src/tools/Chat/shell/ChatRoot.tsx +326 -0
  136. package/src/tools/Chat/{components → shell}/EmptyState.tsx +4 -2
  137. package/src/tools/Chat/shell/index.ts +15 -0
  138. package/src/tools/Chat/types/block.ts +120 -0
  139. package/src/tools/Chat/types/config.ts +0 -5
  140. package/src/tools/Chat/types/index.ts +17 -0
  141. package/src/tools/Chat/types/message.ts +3 -0
  142. package/src/tools/Chat/utils/index.ts +4 -0
  143. package/src/tools/CodeEditor/README.md +4 -6
  144. package/src/tools/CodeEditor/components/DiffEditor.tsx +48 -13
  145. package/src/tools/CodeEditor/components/Editor.tsx +96 -44
  146. package/src/tools/CodeEditor/context/EditorProvider.tsx +34 -17
  147. package/src/tools/CodeEditor/hooks/useEditorTheme.ts +92 -99
  148. package/src/tools/CodeEditor/hooks/useMonaco.ts +37 -22
  149. package/src/tools/CodeEditor/lazy.tsx +6 -0
  150. package/src/tools/CodeEditor/lib/index.ts +1 -1
  151. package/src/tools/CodeEditor/lib/themes.ts +3 -39
  152. package/src/tools/CronScheduler/CronScheduler.client.tsx +230 -61
  153. package/src/tools/CronScheduler/components/CustomInput.tsx +21 -4
  154. package/src/tools/CronScheduler/components/DayChips.tsx +13 -11
  155. package/src/tools/CronScheduler/components/MonthDayGrid.tsx +4 -4
  156. package/src/tools/CronScheduler/components/SchedulePreview.tsx +7 -3
  157. package/src/tools/CronScheduler/components/TimeSelector.tsx +1 -1
  158. package/src/tools/CronScheduler/index.tsx +1 -1
  159. package/src/tools/CronScheduler/types/index.ts +8 -3
  160. package/src/tools/CronScheduler/utils/cron-humanize.ts +61 -16
  161. package/src/tools/CronScheduler/utils/cron-parser.ts +13 -4
  162. package/src/tools/FileIcon/FileIcon.tsx +24 -39
  163. package/src/tools/FileIcon/get-file-icon.ts +73 -0
  164. package/src/tools/FileIcon/icons/icon-data.ts +399 -0
  165. package/src/tools/FileIcon/index.ts +4 -0
  166. package/src/tools/FileIcon/loader.ts +17 -35
  167. package/src/tools/FileIcon/specialFolders.ts +18 -0
  168. package/src/tools/Gallery/components/lightbox/GalleryLightbox.tsx +112 -35
  169. package/src/tools/Gallery/components/media/GalleryVideo.tsx +21 -2
  170. package/src/tools/Gallery/components/preview/GalleryCarousel.tsx +11 -1
  171. package/src/tools/Gallery/hooks/usePreloadImages.ts +54 -7
  172. package/src/tools/ImageViewer/components/ImageInfo.tsx +12 -1
  173. package/src/tools/ImageViewer/components/ImageToolbar.tsx +51 -43
  174. package/src/tools/ImageViewer/components/ImageViewer.tsx +106 -26
  175. package/src/tools/ImageViewer/hooks/useImageLoading.ts +13 -0
  176. package/src/tools/ImageViewer/utils/constants.ts +3 -0
  177. package/src/tools/ImageViewer/utils/index.ts +1 -0
  178. package/src/tools/JsonForm/JsonSchemaForm.tsx +4 -1
  179. package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +5 -3
  180. package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +7 -4
  181. package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +3 -1
  182. package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +23 -3
  183. package/src/tools/JsonForm/widgets/ColorWidget.tsx +20 -12
  184. package/src/tools/JsonForm/widgets/NumberWidget.tsx +14 -9
  185. package/src/tools/JsonForm/widgets/RadioWidget.tsx +78 -0
  186. package/src/tools/JsonForm/widgets/SelectWidget.tsx +1 -0
  187. package/src/tools/JsonForm/widgets/SliderWidget.tsx +7 -4
  188. package/src/tools/JsonForm/widgets/TextWidget.tsx +41 -17
  189. package/src/tools/JsonForm/widgets/index.ts +1 -0
  190. package/src/tools/JsonTree/components/JsonContent.tsx +115 -40
  191. package/src/tools/LottiePlayer/LottiePlayer.client.tsx +177 -72
  192. package/src/tools/LottiePlayer/index.tsx +14 -4
  193. package/src/tools/LottiePlayer/lazy.tsx +11 -3
  194. package/src/tools/LottiePlayer/types.ts +31 -1
  195. package/src/tools/LottiePlayer/useLottie.ts +32 -9
  196. package/src/tools/LottiePlayer/usePrefersReducedMotion.ts +46 -0
  197. package/src/tools/Map/components/LayerSwitcher.tsx +54 -21
  198. package/src/tools/Map/components/MapCluster.tsx +28 -21
  199. package/src/tools/Map/components/MapContainer.tsx +11 -4
  200. package/src/tools/Map/components/MapLegend.tsx +46 -15
  201. package/src/tools/Map/components/MapMarker.tsx +31 -2
  202. package/src/tools/Map/hooks/useMapEvents.ts +64 -105
  203. package/src/tools/MarkdownEditor/MarkdownEditor.tsx +61 -6
  204. package/src/tools/MarkdownEditor/MentionList.tsx +37 -4
  205. package/src/tools/MarkdownEditor/createMentionSuggestion.ts +11 -0
  206. package/src/tools/MarkdownEditor/lazy.tsx +32 -7
  207. package/src/tools/MarkdownEditor/styles.css +13 -0
  208. package/src/tools/MarkdownMessage/CodeBlock.tsx +40 -17
  209. package/src/tools/MarkdownMessage/MarkdownMessage.tsx +26 -6
  210. package/src/tools/MarkdownMessage/components.tsx +22 -9
  211. package/src/tools/MarkdownMessage/types.ts +24 -1
  212. package/src/tools/Mermaid/Mermaid.client.tsx +27 -5
  213. package/src/tools/Mermaid/components/MermaidErrorPanel.tsx +31 -0
  214. package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +14 -17
  215. package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +264 -168
  216. package/src/tools/Mermaid/hooks/useMermaidValidation.ts +76 -10
  217. package/src/tools/Mermaid/index.tsx +6 -0
  218. package/src/tools/Mermaid/utils/mermaid-helpers.ts +141 -18
  219. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/FieldRow.tsx +11 -1
  220. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts +49 -20
  221. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/index.tsx +7 -0
  222. package/src/tools/OpenapiViewer/components/DocsLayout/grouping.ts +7 -4
  223. package/src/tools/OpenapiViewer/constants.ts +3 -0
  224. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +73 -11
  225. package/src/tools/OpenapiViewer/utils/schemaExport.ts +26 -6
  226. package/src/tools/PrettyCode/PrettyCode.client.tsx +23 -16
  227. package/src/tools/PrettyCode/lazy.tsx +1 -1
  228. package/src/tools/SpeechRecognition/README.md +1 -1
  229. package/src/tools/SpeechRecognition/__tests__/language.test.ts +9 -3
  230. package/src/tools/SpeechRecognition/components/RecordingPulse.tsx +59 -0
  231. package/src/tools/SpeechRecognition/components/index.ts +2 -0
  232. package/src/tools/SpeechRecognition/core/engine/external.ts +24 -7
  233. package/src/tools/SpeechRecognition/core/language.ts +23 -6
  234. package/src/tools/SpeechRecognition/hooks/usePushToTalk.ts +36 -5
  235. package/src/tools/SpeechRecognition/hooks/useSpeechRecognition.ts +18 -11
  236. package/src/tools/SpeechRecognition/widgets/VoiceComposerSlot.tsx +94 -26
  237. package/src/tools/SpeechRecognition/widgets/index.ts +1 -1
  238. package/src/tools/Tree/README.md +4 -8
  239. package/src/tools/Tree/TreeRoot.tsx +22 -10
  240. package/src/tools/Tree/components/TreeContent.tsx +24 -4
  241. package/src/tools/Tree/components/TreeLabel.tsx +8 -2
  242. package/src/tools/Tree/components/TreeRow.tsx +16 -6
  243. package/src/tools/Tree/data/flatten.ts +10 -4
  244. package/src/tools/Tree/types.ts +4 -0
  245. package/src/tools/Uploader/components/UploadAddButton.tsx +29 -6
  246. package/src/tools/Uploader/components/UploadDropzone.tsx +63 -7
  247. package/src/tools/Uploader/components/UploadPageDropOverlay.tsx +19 -5
  248. package/src/tools/Uploader/components/UploadPreviewItem.tsx +47 -17
  249. package/src/tools/Uploader/components/UploadPreviewList.tsx +24 -12
  250. package/src/tools/Uploader/utils/formatters.ts +8 -3
  251. package/src/tools/VideoPlayer/README.md +87 -230
  252. package/src/tools/VideoPlayer/VideoPlayer.tsx +82 -0
  253. package/src/tools/VideoPlayer/canvas/canvas-dispatcher.tsx +34 -0
  254. package/src/tools/VideoPlayer/canvas/hls-canvas.tsx +39 -0
  255. package/src/tools/VideoPlayer/canvas/iframe-canvas.tsx +33 -0
  256. package/src/tools/VideoPlayer/canvas/index.ts +12 -0
  257. package/src/tools/VideoPlayer/canvas/jsx-augmentation.ts +47 -0
  258. package/src/tools/VideoPlayer/canvas/native-canvas.tsx +38 -0
  259. package/src/tools/VideoPlayer/canvas/vimeo-canvas.tsx +40 -0
  260. package/src/tools/VideoPlayer/canvas/youtube-canvas.tsx +78 -0
  261. package/src/tools/VideoPlayer/index.ts +51 -65
  262. package/src/tools/VideoPlayer/lazy.tsx +11 -54
  263. package/src/tools/VideoPlayer/parts/controls-bar.tsx +35 -0
  264. package/src/tools/VideoPlayer/parts/fullscreen.tsx +19 -0
  265. package/src/tools/VideoPlayer/parts/index.ts +15 -0
  266. package/src/tools/VideoPlayer/parts/pip.tsx +19 -0
  267. package/src/tools/VideoPlayer/parts/play-button.tsx +19 -0
  268. package/src/tools/VideoPlayer/parts/playback-rate.tsx +31 -0
  269. package/src/tools/VideoPlayer/parts/poster.tsx +3 -0
  270. package/src/tools/VideoPlayer/parts/seek-bar.tsx +26 -0
  271. package/src/tools/VideoPlayer/parts/volume.tsx +32 -0
  272. package/src/tools/VideoPlayer/styles/video-player.css +141 -0
  273. package/src/tools/VideoPlayer/types.ts +82 -0
  274. package/src/tools/VideoPlayer/utils/parse-embed-url.ts +70 -0
  275. package/src/tools/VideoPlayer/utils/vimeo-id.ts +24 -0
  276. package/src/tools/VideoPlayer/utils/youtube-id.ts +64 -0
  277. package/src/tools/index.ts +37 -29
  278. package/src/tools/Chat/components/AudioToggle.tsx +0 -78
  279. package/src/tools/Chat/components/ChatRoot.tsx +0 -305
  280. package/src/tools/Chat/components/Composer.tsx +0 -216
  281. package/src/tools/Chat/hooks/useChatScroll.ts +0 -145
  282. package/src/tools/Chat/types.ts +0 -9
  283. package/src/tools/JsonTree/components/JsonToolbar.tsx +0 -95
  284. package/src/tools/JsonTree/hooks/useElementCorner.ts +0 -84
  285. package/src/tools/JsonTree/hooks/useNavbarHeight.ts +0 -83
  286. package/src/tools/OpenapiViewer/components/DocsLayout/schemaFields.ts +0 -121
  287. package/src/tools/Tour/README.md +0 -373
  288. package/src/tools/Tour/components/Tour.tsx +0 -12
  289. package/src/tools/Tour/components/TourContent.tsx +0 -171
  290. package/src/tools/Tour/components/TourNavigation.tsx +0 -77
  291. package/src/tools/Tour/components/TourProgress.tsx +0 -88
  292. package/src/tools/Tour/components/TourSpotlight.tsx +0 -199
  293. package/src/tools/Tour/components/index.ts +0 -5
  294. package/src/tools/Tour/context/TourContext.ts +0 -19
  295. package/src/tools/Tour/context/TourProvider.tsx +0 -292
  296. package/src/tools/Tour/context/index.ts +0 -2
  297. package/src/tools/Tour/hooks/index.ts +0 -3
  298. package/src/tools/Tour/hooks/useKeyboardNavigation.ts +0 -59
  299. package/src/tools/Tour/hooks/useStepTarget.ts +0 -121
  300. package/src/tools/Tour/hooks/useTour.ts +0 -42
  301. package/src/tools/Tour/index.ts +0 -38
  302. package/src/tools/Tour/types/index.ts +0 -224
  303. package/src/tools/Tour/utils/dom.ts +0 -98
  304. package/src/tools/Tour/utils/index.ts +0 -3
  305. package/src/tools/Tour/utils/logger.ts +0 -3
  306. package/src/tools/Tour/utils/scrollIntoView.ts +0 -24
  307. package/src/tools/VideoPlayer/components/VideoControls.tsx +0 -138
  308. package/src/tools/VideoPlayer/components/VideoErrorFallback.tsx +0 -172
  309. package/src/tools/VideoPlayer/components/VideoPlayer.tsx +0 -201
  310. package/src/tools/VideoPlayer/components/index.ts +0 -14
  311. package/src/tools/VideoPlayer/context/VideoPlayerContext.tsx +0 -52
  312. package/src/tools/VideoPlayer/context/index.ts +0 -8
  313. package/src/tools/VideoPlayer/hooks/index.ts +0 -12
  314. package/src/tools/VideoPlayer/hooks/useVideoPlayerSettings.ts +0 -71
  315. package/src/tools/VideoPlayer/hooks/useVideoPositionCache.ts +0 -117
  316. package/src/tools/VideoPlayer/providers/NativeProvider.tsx +0 -284
  317. package/src/tools/VideoPlayer/providers/StreamProvider.tsx +0 -505
  318. package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +0 -397
  319. package/src/tools/VideoPlayer/providers/index.ts +0 -8
  320. package/src/tools/VideoPlayer/types/index.ts +0 -38
  321. package/src/tools/VideoPlayer/types/player.ts +0 -116
  322. package/src/tools/VideoPlayer/types/provider.ts +0 -93
  323. package/src/tools/VideoPlayer/types/sources.ts +0 -97
  324. package/src/tools/VideoPlayer/utils/debug.ts +0 -14
  325. package/src/tools/VideoPlayer/utils/fileSource.ts +0 -78
  326. package/src/tools/VideoPlayer/utils/index.ts +0 -12
  327. package/src/tools/VideoPlayer/utils/resolvers.ts +0 -75
  328. /package/src/tools/Chat/{config.ts → constants.ts} +0 -0
  329. /package/src/tools/Chat/launcher/{ChatHeaderAudioToggle.tsx → header/ChatHeaderAudioToggle.tsx} +0 -0
  330. /package/src/tools/Chat/{components → messages}/Attachments.tsx +0 -0
  331. /package/src/tools/Chat/{components → messages}/JumpToLatest.tsx +0 -0
  332. /package/src/tools/Chat/{components → messages}/MessageActions.tsx +0 -0
  333. /package/src/tools/Chat/{components → messages}/Sources.tsx +0 -0
  334. /package/src/tools/Chat/{components → messages}/StreamingIndicator.tsx +0 -0
  335. /package/src/tools/Chat/{components → messages}/ToolCalls.tsx +0 -0
  336. /package/src/tools/Chat/{components → shell}/ErrorBanner.tsx +0 -0
@@ -1,10 +1,21 @@
1
1
  /**
2
- * Hook for rendering Mermaid diagrams with debounce and validation
2
+ * Hook for rendering Mermaid diagrams with debounced, race-safe rendering.
3
+ *
4
+ * Mermaid (~800KB) is imported eagerly here because the whole component
5
+ * tree is already lazy-loaded behind `Mermaid.client` — splitting again
6
+ * would just add a second waterfall for no win.
3
7
  */
4
8
 
5
9
  import mermaid from 'mermaid';
6
10
  import { useEffect, useRef, useState } from 'react';
7
11
 
12
+ import {
13
+ applyMermaidErRowColors,
14
+ applyMermaidTextColors,
15
+ getThemeColor,
16
+ getTextColor,
17
+ isVerticalDiagram,
18
+ } from '../utils/mermaid-helpers';
8
19
  import { useMermaidCleanup } from './useMermaidCleanup';
9
20
  import { useMermaidValidation } from './useMermaidValidation';
10
21
 
@@ -12,6 +23,8 @@ interface UseMermaidRendererProps {
12
23
  chart: string;
13
24
  theme: string;
14
25
  isCompact?: boolean;
26
+ /** Debounce window in ms before (re)rendering. Default 300. */
27
+ debounceMs?: number;
15
28
  }
16
29
 
17
30
  interface MermaidRenderResult {
@@ -19,215 +32,298 @@ interface MermaidRenderResult {
19
32
  svgContent: string;
20
33
  isVertical: boolean;
21
34
  isRendering: boolean;
35
+ /** Set when the last render failed with a syntax / parse error. */
36
+ error: string | null;
22
37
  }
23
38
 
24
- // Utility function to apply text colors to Mermaid SVG
25
- const applyMermaidTextColors = (container: HTMLElement, textColor: string) => {
26
- const svgElement = container.querySelector('svg');
27
- if (svgElement) {
28
- // SVG text elements use 'fill'
29
- svgElement.querySelectorAll('text').forEach((el) => {
30
- (el as SVGElement).style.fill = textColor;
31
- });
32
-
33
- // HTML elements inside foreignObject use 'color'
34
- svgElement.querySelectorAll('.nodeLabel, .edgeLabel').forEach((el) => {
35
- (el as HTMLElement).style.color = textColor;
36
- });
37
- }
38
- };
39
-
40
- // Detect if diagram is vertical (tall and narrow)
41
- const isVerticalDiagram = (svgElement: SVGSVGElement): boolean => {
42
- const viewBox = svgElement.getAttribute('viewBox');
43
- if (viewBox) {
44
- const [, , width, height] = viewBox.split(' ').map(Number);
45
- if (width === undefined || height === undefined) return false;
46
- return height > width * 1.5;
47
- }
48
- const bbox = svgElement.getBBox?.();
49
- if (bbox) {
50
- return bbox.height > bbox.width * 1.5;
51
- }
52
- return false;
53
- };
54
-
55
- export function useMermaidRenderer({ chart, theme, isCompact = false }: UseMermaidRendererProps): MermaidRenderResult {
39
+ /**
40
+ * Section color scales for timeline / journey / mindmap / pie diagrams.
41
+ *
42
+ * These diagram types do NOT use `mainBkg` for their boxes — they cycle
43
+ * through `cScale0..N` (and `pie1..N`) instead. Mermaid's `base` theme
44
+ * derives those scales from `primaryColor`, which lands far too dark in
45
+ * light mode (dark box) while the section label still inherits the
46
+ * default dark `textColor` — i.e. dark text on a dark box.
47
+ *
48
+ * We pin an explicit, theme-aware palette: mid-saturation backgrounds
49
+ * that read on either page background, each paired with an explicit
50
+ * contrasting label color via `cScaleLabel*`. `cScalePeer*` colors the
51
+ * sub-task boxes that sit under a section.
52
+ */
53
+ const SECTION_SCALES = {
54
+ light: [
55
+ { bg: '#1f6f8b', label: '#ffffff', peer: '#3a8ba6' },
56
+ { bg: '#7b5ea7', label: '#ffffff', peer: '#977dc0' },
57
+ { bg: '#2e8b57', label: '#ffffff', peer: '#4caf7d' },
58
+ { bg: '#c25a3a', label: '#ffffff', peer: '#d6805f' },
59
+ { bg: '#3a6ea5', label: '#ffffff', peer: '#5d8cc0' },
60
+ { bg: '#a8456b', label: '#ffffff', peer: '#c06b8b' },
61
+ { bg: '#5f8f3a', label: '#ffffff', peer: '#80aa5d' },
62
+ { bg: '#9a7d2e', label: '#ffffff', peer: '#b89c50' },
63
+ ],
64
+ dark: [
65
+ { bg: '#3aa6c9', label: '#0b1620', peer: '#2b7d99' },
66
+ { bg: '#b39ddb', label: '#1a142b', peer: '#8a72b5' },
67
+ { bg: '#66c990', label: '#0c1f15', peer: '#479a6b' },
68
+ { bg: '#e8956f', label: '#2a1409', peer: '#bd6f4c' },
69
+ { bg: '#79a8d9', label: '#0d1726', peer: '#577fad' },
70
+ { bg: '#d987a8', label: '#2a0f1b', peer: '#ad6082' },
71
+ { bg: '#a4cf7a', label: '#142008', peer: '#7da352' },
72
+ { bg: '#d4bb6e', label: '#241c08', peer: '#a8924c' },
73
+ ],
74
+ } as const;
75
+
76
+ /**
77
+ * Build Mermaid `themeVariables` from our semantic tokens.
78
+ *
79
+ * Tokens are read live from the DOM so the diagram tracks light/dark
80
+ * without a hard-coded palette. Fallbacks only fire during SSR or before
81
+ * stylesheets load.
82
+ */
83
+ function buildThemeVariables(theme: string, fontSize: string) {
84
+ const isDark = theme === 'dark';
85
+ const fg = getThemeColor('--foreground', isDark ? 'hsl(0 0% 98%)' : 'hsl(0 0% 9%)');
86
+ const card = getThemeColor('--card', isDark ? 'hsl(0 0% 8%)' : 'hsl(0 0% 100%)');
87
+ const muted = getThemeColor('--muted', isDark ? 'hsl(0 0% 15%)' : 'hsl(0 0% 96%)');
88
+ const border = getThemeColor('--border', isDark ? 'hsl(0 0% 15%)' : 'hsl(0 0% 90%)');
89
+ const primary = getThemeColor('--primary', isDark ? 'hsl(189 100% 50%)' : 'hsl(192 90% 35%)');
90
+ const accent = getThemeColor('--accent', muted);
91
+ const secondary = getThemeColor('--secondary', muted);
92
+ const background = getThemeColor('--background', isDark ? 'hsl(0 0% 4%)' : 'hsl(0 0% 94%)');
93
+ const destructive = getThemeColor('--destructive', 'hsl(0 84% 60%)');
94
+ const destructiveFg = getThemeColor('--destructive-foreground', 'hsl(0 0% 98%)');
95
+
96
+ const scales = SECTION_SCALES[isDark ? 'dark' : 'light'];
97
+ // `cScale*` / `pie*` / `fillType*` are flat keys (cScale0, pie1, ...).
98
+ //
99
+ // `fillType*` is what the **journey** diagram actually paints its
100
+ // section / task rects with. Left unset, Mermaid derives it by
101
+ // rotating the hue of `cScale*` and forcing odd indexes to
102
+ // `hsl(H, 0%, 9%)` — a near-black box. Pinning `fillType*` to our
103
+ // explicit palette kills the dark-on-dark sections.
104
+ const sectionVars: Record<string, string> = {};
105
+ scales.forEach((s, i) => {
106
+ sectionVars[`cScale${i}`] = s.bg;
107
+ sectionVars[`cScaleLabel${i}`] = s.label;
108
+ sectionVars[`cScaleInv${i}`] = s.label;
109
+ sectionVars[`cScalePeer${i}`] = s.peer;
110
+ sectionVars[`fillType${i}`] = s.bg;
111
+ sectionVars[`surface${i}`] = s.bg;
112
+ // Pie slices are 1-indexed and have no separate label var — the
113
+ // slice text color is global (`pieSectionTextColor`).
114
+ sectionVars[`pie${i + 1}`] = s.bg;
115
+ });
116
+
117
+ return {
118
+ primaryColor: primary,
119
+ primaryTextColor: fg,
120
+ primaryBorderColor: primary,
121
+ secondaryColor: secondary,
122
+ secondaryTextColor: fg,
123
+ secondaryBorderColor: border,
124
+ tertiaryColor: accent,
125
+ tertiaryTextColor: fg,
126
+ tertiaryBorderColor: border,
127
+ mainBkg: card,
128
+ textColor: fg,
129
+ nodeBorder: border,
130
+ nodeTextColor: fg,
131
+ secondBkg: muted,
132
+ lineColor: primary,
133
+ edgeLabelBackground: card,
134
+ clusterBkg: muted,
135
+ clusterBorder: primary,
136
+ background,
137
+ labelBackground: card,
138
+ labelTextColor: fg,
139
+ errorBkgColor: destructive,
140
+ errorTextColor: destructiveFg,
141
+
142
+ // --- ER diagram ---
143
+ // ER attribute-row zebra fills are derived from `mainBkg` by
144
+ // Mermaid and ignore `themeVariables` — they are re-asserted
145
+ // post-render via `applyMermaidErRowColors`.
146
+
147
+ // --- Section-colored diagrams (timeline / journey / mindmap) ---
148
+ ...sectionVars,
149
+ // Timeline / journey section title bars + the global label fallback.
150
+ cScaleLabel0: scales[0]?.label ?? fg,
151
+ // Journey actor faces / labels in the legend.
152
+ actorBkg: card,
153
+ actorBorder: border,
154
+ actorTextColor: fg,
155
+ actorLineColor: border,
156
+ // Mindmap nodes inherit `cScale*`; their text uses `nodeTextColor`
157
+ // on the outer ring — keep it readable on the page background.
158
+
159
+ // --- Pie chart ---
160
+ pieTitleTextColor: fg,
161
+ pieSectionTextColor: isDark ? '#0b1620' : '#ffffff',
162
+ pieSectionTextSize: '13px',
163
+ pieLegendTextColor: fg,
164
+ pieLegendTextSize: '13px',
165
+ pieStrokeColor: background,
166
+ pieStrokeWidth: '2px',
167
+ pieOuterStrokeColor: border,
168
+ pieOuterStrokeWidth: '1px',
169
+ pieOpacity: '1',
170
+
171
+ // --- Git graph ---
172
+ git0: scales[0]?.bg ?? primary,
173
+ git1: scales[1]?.bg ?? secondary,
174
+ git2: scales[2]?.bg ?? accent,
175
+ git3: scales[3]?.bg ?? primary,
176
+ git4: scales[4]?.bg ?? secondary,
177
+ git5: scales[5]?.bg ?? accent,
178
+ git6: scales[6]?.bg ?? primary,
179
+ git7: scales[7]?.bg ?? secondary,
180
+ gitBranchLabel0: scales[0]?.label ?? fg,
181
+ gitBranchLabel1: scales[1]?.label ?? fg,
182
+ gitBranchLabel2: scales[2]?.label ?? fg,
183
+ gitBranchLabel3: scales[3]?.label ?? fg,
184
+ gitBranchLabel4: scales[4]?.label ?? fg,
185
+ gitBranchLabel5: scales[5]?.label ?? fg,
186
+ gitBranchLabel6: scales[6]?.label ?? fg,
187
+ gitBranchLabel7: scales[7]?.label ?? fg,
188
+ gitInv0: scales[0]?.label ?? fg,
189
+ commitLabelColor: fg,
190
+ commitLabelBackground: card,
191
+ tagLabelColor: fg,
192
+ tagLabelBackground: muted,
193
+ tagLabelBorder: border,
194
+
195
+ fontSize,
196
+ fontFamily: 'Inter, system-ui, sans-serif',
197
+ };
198
+ }
199
+
200
+ export function useMermaidRenderer({
201
+ chart,
202
+ theme,
203
+ isCompact = false,
204
+ debounceMs = 300,
205
+ }: UseMermaidRendererProps): MermaidRenderResult {
56
206
  const mermaidRef = useRef<HTMLDivElement>(null);
57
- const renderTimerRef = useRef<NodeJS.Timeout | null>(null);
207
+ const renderTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
208
+ // Monotonic token: every effect run bumps it, and an in-flight async
209
+ // render checks it before touching the DOM. Guards against a stale
210
+ // chart's `mermaid.render` resolving after a newer one started.
211
+ const renderSeqRef = useRef(0);
212
+
58
213
  const [svgContent, setSvgContent] = useState<string>('');
59
214
  const [isVertical, setIsVertical] = useState(false);
60
215
  const [isRendering, setIsRendering] = useState(false);
216
+ const [error, setError] = useState<string | null>(null);
61
217
 
62
218
  const { isMermaidCodeComplete } = useMermaidValidation();
63
219
  const { cleanupMermaidErrors } = useMermaidCleanup();
64
220
 
65
221
  useEffect(() => {
66
- // Get CSS variables for semantic colors
67
- const getCSSVariable = (variable: string) => {
68
- if (typeof document === 'undefined') return '';
69
- const value = getComputedStyle(document.documentElement).getPropertyValue(variable).trim();
70
- if (!value) return '';
71
- // If value is already a complete color (hex, rgb, hsl with parentheses), return as-is
72
- if (value.startsWith('#') || value.startsWith('rgb') || value.startsWith('hsl(')) {
73
- return value;
74
- }
75
- // Otherwise assume it's HSL components and wrap in hsl()
76
- return `hsl(${value})`;
77
- };
78
-
79
- const diagramFontSize = isCompact ? '12px' : '14px';
80
-
81
- const themeVariables = theme === 'dark' ? {
82
- primaryColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
83
- primaryTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',
84
- primaryBorderColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
85
- secondaryColor: getCSSVariable('--muted') || 'hsl(217.2 32.6% 17.5%)',
86
- secondaryTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',
87
- secondaryBorderColor: getCSSVariable('--border') || 'hsl(217.2 32.6% 27.5%)',
88
- tertiaryColor: getCSSVariable('--accent') || 'hsl(217.2 32.6% 20%)',
89
- tertiaryTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',
90
- tertiaryBorderColor: getCSSVariable('--border') || 'hsl(217.2 32.6% 27.5%)',
91
- mainBkg: getCSSVariable('--card') || 'hsl(222.2 84% 8%)',
92
- textColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',
93
- nodeBorder: getCSSVariable('--border') || 'hsl(217.2 32.6% 27.5%)',
94
- nodeTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',
95
- secondBkg: getCSSVariable('--muted') || 'hsl(217.2 32.6% 17.5%)',
96
- lineColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
97
- edgeLabelBackground: getCSSVariable('--card') || 'hsl(222.2 84% 8%)',
98
- clusterBkg: getCSSVariable('--muted') || 'hsl(217.2 32.6% 12%)',
99
- clusterBorder: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
100
- background: getCSSVariable('--background') || 'hsl(222.2 84% 4.9%)',
101
- labelBackground: getCSSVariable('--card') || 'hsl(222.2 84% 8%)',
102
- labelTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',
103
- errorBkgColor: getCSSVariable('--destructive') || 'hsl(0 62.8% 30.6%)',
104
- errorTextColor: 'hsl(210 40% 98%)',
105
- fontSize: diagramFontSize,
106
- fontFamily: 'Inter, system-ui, sans-serif',
107
- } : {
108
- primaryColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
109
- primaryTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',
110
- primaryBorderColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
111
- secondaryColor: getCSSVariable('--secondary') || 'hsl(210 40% 96.1%)',
112
- secondaryTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',
113
- secondaryBorderColor: getCSSVariable('--border') || 'hsl(214.3 31.8% 91.4%)',
114
- tertiaryColor: getCSSVariable('--muted') || 'hsl(210 40% 96.1%)',
115
- tertiaryTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',
116
- tertiaryBorderColor: getCSSVariable('--border') || 'hsl(214.3 31.8% 91.4%)',
117
- mainBkg: getCSSVariable('--card') || 'hsl(0 0% 100%)',
118
- textColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',
119
- nodeBorder: getCSSVariable('--border') || 'hsl(214.3 31.8% 91.4%)',
120
- nodeTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',
121
- secondBkg: getCSSVariable('--muted') || 'hsl(210 40% 96.1%)',
122
- lineColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
123
- edgeLabelBackground: getCSSVariable('--card') || 'hsl(0 0% 100%)',
124
- clusterBkg: getCSSVariable('--accent') || 'hsl(210 40% 98%)',
125
- clusterBorder: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
126
- background: getCSSVariable('--background') || 'hsl(0 0% 100%)',
127
- labelBackground: getCSSVariable('--card') || 'hsl(0 0% 100%)',
128
- labelTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',
129
- errorBkgColor: getCSSVariable('--destructive') || 'hsl(0 84.2% 60.2%)',
130
- errorTextColor: 'hsl(210 40% 98%)',
131
- fontSize: diagramFontSize,
132
- fontFamily: 'Inter, system-ui, sans-serif',
133
- };
222
+ const seq = ++renderSeqRef.current;
223
+ const isStale = () => seq !== renderSeqRef.current;
134
224
 
135
- mermaid.initialize({
136
- startOnLoad: false,
137
- theme: 'base',
138
- securityLevel: 'loose',
139
- suppressErrorRendering: true, // Prevent mermaid from appending errors to body
140
- fontFamily: 'Inter, system-ui, sans-serif',
141
- flowchart: {
142
- useMaxWidth: true,
143
- htmlLabels: true,
144
- curve: 'basis',
145
- },
146
- themeVariables,
147
- });
225
+ const fontSize = isCompact ? '12px' : '14px';
148
226
 
149
227
  const renderChart = async () => {
150
- if (!mermaidRef.current || !chart) return;
228
+ const host = mermaidRef.current;
229
+ if (!host || !chart) {
230
+ setIsRendering(false);
231
+ return;
232
+ }
151
233
 
152
- // Validate code completeness
234
+ // Streaming guard: an incomplete diagram (still being typed /
235
+ // streamed) would throw a parse error. Keep the previous SVG
236
+ // visible and just show the spinner — don't flash an error.
153
237
  if (!isMermaidCodeComplete(chart)) {
154
238
  setIsRendering(true);
155
239
  return;
156
240
  }
157
241
 
158
- try {
159
- setIsRendering(true);
242
+ setIsRendering(true);
243
+ setError(null);
160
244
 
161
- // Clear container
162
- if (mermaidRef.current) {
163
- mermaidRef.current.innerHTML = '';
164
- }
245
+ // `mermaid.initialize` is global + idempotent; re-running it
246
+ // per render keeps `themeVariables` in sync with the live
247
+ // light/dark tokens (cheap, no library reload).
248
+ mermaid.initialize({
249
+ startOnLoad: false,
250
+ theme: 'base',
251
+ securityLevel: 'loose',
252
+ suppressErrorRendering: true,
253
+ fontFamily: 'Inter, system-ui, sans-serif',
254
+ flowchart: { useMaxWidth: true, htmlLabels: true, curve: 'basis' },
255
+ themeVariables: buildThemeVariables(theme, fontSize),
256
+ });
165
257
 
166
- const id = `mermaid-${Math.random().toString(36).substring(2, 9)}`;
258
+ const id = `mermaid-${Math.random().toString(36).slice(2, 9)}`;
259
+
260
+ try {
261
+ // `mermaid.parse` validates without mutating the DOM —
262
+ // catches syntax errors before `render` appends anything.
263
+ await mermaid.parse(chart);
167
264
  const { svg } = await mermaid.render(id, chart);
168
265
 
169
- if (mermaidRef.current) {
170
- const textColor = theme === 'dark'
171
- ? getCSSVariable('--foreground') || 'hsl(0 0% 90%)'
172
- : getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)';
173
-
174
- const processedSvg = svg.replace(
175
- /<svg /,
176
- `<svg style="--mermaid-text-color: ${textColor};" `
177
- );
178
-
179
- mermaidRef.current.innerHTML = processedSvg;
180
- setSvgContent(processedSvg);
181
-
182
- applyMermaidTextColors(mermaidRef.current, textColor);
183
-
184
- const svgElement = mermaidRef.current.querySelector('svg');
185
- if (svgElement) {
186
- svgElement.style.maxWidth = '100%';
187
- svgElement.style.height = 'auto';
188
- svgElement.style.display = 'block';
189
- setIsVertical(isVerticalDiagram(svgElement));
190
- }
266
+ if (isStale() || !mermaidRef.current) return;
267
+
268
+ const textColor = getTextColor(theme);
269
+ const processedSvg = svg.replace(
270
+ /<svg /,
271
+ `<svg style="--mermaid-text-color: ${textColor};" `,
272
+ );
273
+
274
+ mermaidRef.current.innerHTML = processedSvg;
275
+ applyMermaidTextColors(mermaidRef.current, textColor);
276
+ // ER attribute rows zebra-stripe off `mainBkg` and ignore
277
+ // `themeVariables` — re-assert themed, contrasting fills.
278
+ applyMermaidErRowColors(
279
+ mermaidRef.current,
280
+ getThemeColor('--card', theme === 'dark' ? 'hsl(0 0% 8%)' : 'hsl(0 0% 100%)'),
281
+ getThemeColor('--muted', theme === 'dark' ? 'hsl(0 0% 15%)' : 'hsl(0 0% 96%)'),
282
+ );
283
+
284
+ const svgElement = mermaidRef.current.querySelector('svg');
285
+ if (svgElement) {
286
+ svgElement.style.maxWidth = '100%';
287
+ svgElement.style.height = 'auto';
288
+ svgElement.style.display = 'block';
289
+ setIsVertical(isVerticalDiagram(svgElement));
191
290
  }
192
291
 
292
+ setSvgContent(processedSvg);
293
+ setError(null);
193
294
  setIsRendering(false);
194
- } catch (error) {
195
- console.error('Mermaid rendering error:', error);
196
- setIsRendering(false);
295
+ } catch (err) {
296
+ // `render` may still have appended an orphan node to
297
+ // <body> despite `suppressErrorRendering` — sweep it.
197
298
  cleanupMermaidErrors();
299
+ if (isStale()) return;
198
300
 
301
+ const message =
302
+ err instanceof Error ? err.message : 'Failed to render diagram';
303
+ setError(message);
304
+ setSvgContent('');
305
+ setIsVertical(false);
306
+ setIsRendering(false);
199
307
  if (mermaidRef.current) {
200
- mermaidRef.current.innerHTML = `
201
- <div class="p-4 text-destructive bg-destructive/10 border border-destructive/20 rounded-sm">
202
- <p class="font-semibold">Mermaid Diagram Error</p>
203
- <p class="text-sm">${error instanceof Error ? error.message : 'Unknown error'}</p>
204
- </div>
205
- `;
308
+ mermaidRef.current.innerHTML = '';
206
309
  }
207
310
  }
208
311
  };
209
312
 
210
- // Clear previous timer
211
313
  if (renderTimerRef.current) {
212
314
  clearTimeout(renderTimerRef.current);
213
315
  }
214
-
215
- // Debounce: wait 500ms after last update
216
- renderTimerRef.current = setTimeout(() => {
217
- renderChart();
218
- }, 500);
316
+ renderTimerRef.current = setTimeout(renderChart, debounceMs);
219
317
 
220
318
  return () => {
319
+ // Invalidate any in-flight async render from this effect run.
320
+ renderSeqRef.current++;
221
321
  if (renderTimerRef.current) {
222
322
  clearTimeout(renderTimerRef.current);
323
+ renderTimerRef.current = null;
223
324
  }
224
325
  };
225
- }, [chart, theme, isCompact, isMermaidCodeComplete, cleanupMermaidErrors]);
326
+ }, [chart, theme, isCompact, debounceMs, isMermaidCodeComplete, cleanupMermaidErrors]);
226
327
 
227
- return {
228
- mermaidRef,
229
- svgContent,
230
- isVertical,
231
- isRendering,
232
- };
328
+ return { mermaidRef, svgContent, isVertical, isRendering, error };
233
329
  }
@@ -1,28 +1,94 @@
1
1
  /**
2
- * Hook for validating Mermaid code completeness
2
+ * Hook for validating Mermaid code completeness.
3
+ *
4
+ * Purpose: while a diagram is being *streamed* (token by token from an
5
+ * LLM, or typed) the source is briefly invalid. Rendering it would throw
6
+ * a parse error and flash an error panel on every keystroke. This
7
+ * heuristic detects "obviously still-being-written" source so the
8
+ * renderer can wait instead. It is intentionally conservative — false
9
+ * "complete" is fine (the real parser catches it), false "incomplete"
10
+ * just delays a render.
3
11
  */
4
12
 
5
13
  import { useCallback } from 'react';
6
14
 
15
+ /** Diagram keywords that, alone, are a valid first line. */
16
+ const DIAGRAM_KEYWORDS = [
17
+ 'graph',
18
+ 'flowchart',
19
+ 'sequenceDiagram',
20
+ 'classDiagram',
21
+ 'stateDiagram',
22
+ 'stateDiagram-v2',
23
+ 'erDiagram',
24
+ 'journey',
25
+ 'gantt',
26
+ 'pie',
27
+ 'mindmap',
28
+ 'timeline',
29
+ 'gitGraph',
30
+ 'quadrantChart',
31
+ 'requirementDiagram',
32
+ 'C4Context',
33
+ 'sankey-beta',
34
+ 'xychart-beta',
35
+ 'block-beta',
36
+ ];
37
+
38
+ /** Count occurrences of a character that are not inside a quoted string. */
39
+ function countUnquoted(text: string, open: string, close: string): number {
40
+ let depth = 0;
41
+ let inQuote = false;
42
+ for (let i = 0; i < text.length; i++) {
43
+ const ch = text[i];
44
+ if (ch === '"') {
45
+ inQuote = !inQuote;
46
+ continue;
47
+ }
48
+ if (inQuote) continue;
49
+ if (ch === open) depth++;
50
+ else if (ch === close) depth--;
51
+ }
52
+ return depth;
53
+ }
54
+
7
55
  export function useMermaidValidation() {
8
56
  const isMermaidCodeComplete = useCallback((code: string): boolean => {
9
57
  if (!code || code.trim().length === 0) return false;
10
58
 
11
59
  const trimmed = code.trim();
12
60
 
13
- // Check if code has basic structure
14
- const lines = trimmed.split('\n');
15
- if (lines.length < 2) return false; // Need at least diagram type + one element
61
+ // Must start with a recognised diagram keyword. During streaming
62
+ // the very first tokens may be a partial keyword — wait for it.
63
+ const firstToken = trimmed.split(/[\s\n;]/)[0] ?? '';
64
+ const hasKnownType = DIAGRAM_KEYWORDS.some(
65
+ (kw) => firstToken === kw || trimmed.startsWith(kw),
66
+ );
67
+ if (!hasKnownType) return false;
16
68
 
17
- // Check for common incomplete patterns
69
+ const lines = trimmed.split('\n');
18
70
  const lastLine = (lines[lines.length - 1] ?? '').trim();
19
71
 
20
- // Incomplete if last line ends with arrow without destination
21
- if (lastLine.match(/-->?\s*$/)) return false;
22
- if (lastLine.match(/-->\|[^|]*\|\s*$/)) return false;
72
+ // Trailing edge with no destination: `A -->` / `A --` / `A -.->`.
73
+ if (/(-{1,3}>?|={1,3}>?|-\.->?|\.\.>?|--[ox])\s*$/.test(lastLine)) {
74
+ return false;
75
+ }
76
+ // Trailing edge label still open: `A -->|label` (no closing `|`).
77
+ if (/-{1,3}>?\s*\|[^|]*$/.test(lastLine)) return false;
78
+
79
+ // Unbalanced shape brackets across the whole source (open > close).
80
+ if (countUnquoted(trimmed, '[', ']') > 0) return false;
81
+ if (countUnquoted(trimmed, '(', ')') > 0) return false;
82
+ // ER diagrams use `{` / `}` for crow's-foot cardinality
83
+ // (`||--o{`, `}|--||`) — those braces are deliberately unbalanced
84
+ // and must not be counted, or the diagram never renders.
85
+ if (!trimmed.startsWith('erDiagram') && countUnquoted(trimmed, '{', '}') > 0) {
86
+ return false;
87
+ }
23
88
 
24
- // Incomplete if last line ends with opening bracket/parenthesis
25
- if (lastLine.match(/[\[({]\s*$/)) return false;
89
+ // Odd number of double quotes an unterminated string literal.
90
+ const quoteCount = (trimmed.match(/"/g) ?? []).length;
91
+ if (quoteCount % 2 !== 0) return false;
26
92
 
27
93
  return true;
28
94
  }, []);
@@ -32,6 +32,12 @@ export interface MermaidProps {
32
32
  * Mermaid.client implementation for the full rationale.
33
33
  */
34
34
  scrollIsolation?: boolean;
35
+ /**
36
+ * Debounce window (ms) before (re)rendering after `chart` changes.
37
+ * Lower feels snappier for static charts; higher reduces churn while
38
+ * a diagram is streamed in. Default 300.
39
+ */
40
+ debounceMs?: number;
35
41
  }
36
42
 
37
43
  const Mermaid: React.FC<MermaidProps> = (props) => {