@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
@@ -2,33 +2,156 @@
2
2
  * Helper utilities for Mermaid diagram rendering
3
3
  */
4
4
 
5
- // Utility function to apply text colors to Mermaid SVG
6
- export const applyMermaidTextColors = (container: HTMLElement, textColor: string) => {
5
+ /**
6
+ * Read a semantic color token from CSS custom properties.
7
+ *
8
+ * Theme tokens (`light.css` / `dark.css`) ship fully-wrapped CSS colors
9
+ * (`hsl(0 0% 94%)`) — see `ui-core/src/styles/theme/tokens.css`. Older
10
+ * tokens stored bare HSL components (`0 0% 94%`), so this helper handles
11
+ * both: complete colors pass through untouched, bare components get
12
+ * wrapped once. Never double-wrap an already-complete color.
13
+ *
14
+ * @param variable CSS custom property name, e.g. `--foreground`.
15
+ * @param fallback Returned when the variable is empty / unavailable.
16
+ */
17
+ export const getThemeColor = (variable: string, fallback = ''): string => {
18
+ if (typeof document === 'undefined') return fallback;
19
+ const value = getComputedStyle(document.documentElement)
20
+ .getPropertyValue(variable)
21
+ .trim();
22
+ if (!value) return fallback;
23
+ // Already a complete color (hex / rgb / hsl / oklch / named).
24
+ if (
25
+ value.startsWith('#') ||
26
+ value.startsWith('rgb') ||
27
+ value.startsWith('hsl(') ||
28
+ value.startsWith('oklch') ||
29
+ value.startsWith('oklab') ||
30
+ value.startsWith('color(') ||
31
+ value.startsWith('var(')
32
+ ) {
33
+ return value;
34
+ }
35
+ // Bare HSL components — wrap once.
36
+ return `hsl(${value})`;
37
+ };
38
+
39
+ /** Resolve the diagram text color for the given theme. */
40
+ export const getTextColor = (theme: string): string =>
41
+ theme === 'dark'
42
+ ? getThemeColor('--foreground', 'hsl(0 0% 98%)')
43
+ : getThemeColor('--foreground', 'hsl(222.2 84% 4.9%)');
44
+
45
+ /**
46
+ * Diagram types whose labels sit on per-section colored backgrounds
47
+ * (timeline sections, journey tasks, pie slices, mindmap nodes, gitgraph
48
+ * commits). For these, Mermaid already picks a contrasting label color
49
+ * from the `cScaleLabel*` / `pie*` theme variables — re-asserting a single
50
+ * `--foreground` would put dark text on dark boxes (and vice versa).
51
+ *
52
+ * We detect them via the wrapper class Mermaid puts on the root `<g>` /
53
+ * `<svg>` and skip the blanket text override for those SVGs.
54
+ */
55
+ const SECTION_COLORED_SELECTORS = [
56
+ '.timeline',
57
+ '.mindmap',
58
+ '[id^="mermaid"][aria-roledescription="timeline"]',
59
+ '[id^="mermaid"][aria-roledescription="journey"]',
60
+ '[id^="mermaid"][aria-roledescription="mindmap"]',
61
+ '[id^="mermaid"][aria-roledescription="pie"]',
62
+ ];
63
+
64
+ /** True when the SVG renders a diagram type that colors its own labels. */
65
+ const usesSectionColors = (svg: SVGSVGElement): boolean => {
66
+ const role = svg.getAttribute('aria-roledescription') ?? '';
67
+ if (['timeline', 'journey', 'mindmap', 'pie'].includes(role)) return true;
68
+ return SECTION_COLORED_SELECTORS.some((sel) => svg.querySelector(sel) !== null);
69
+ };
70
+
71
+ /**
72
+ * Apply theme text colors to a rendered Mermaid SVG.
73
+ *
74
+ * Mermaid's `base` theme bakes `themeVariables` at render time, but text
75
+ * fill on `<text>` nodes and `color` on foreignObject labels can drift
76
+ * from our tokens — this re-asserts them after render.
77
+ *
78
+ * Diagrams that color their own labels per section (timeline, journey,
79
+ * mindmap, pie) are skipped: their `themeVariables` already encode
80
+ * contrasting label colors, so a blanket override would re-introduce the
81
+ * dark-text-on-dark-box problem.
82
+ */
83
+ export const applyMermaidTextColors = (container: HTMLElement, textColor: string): void => {
7
84
  const svgElement = container.querySelector('svg');
8
- if (svgElement) {
9
- // SVG text elements use 'fill'
10
- svgElement.querySelectorAll('text').forEach((el) => {
11
- (el as SVGElement).style.fill = textColor;
12
- });
85
+ if (!svgElement) return;
86
+ if (usesSectionColors(svgElement)) return;
87
+ // SVG text elements use 'fill'.
88
+ svgElement.querySelectorAll('text').forEach((el) => {
89
+ (el as SVGElement).style.fill = textColor;
90
+ });
91
+ // HTML elements inside foreignObject use 'color'.
92
+ svgElement.querySelectorAll('.nodeLabel, .edgeLabel').forEach((el) => {
93
+ (el as HTMLElement).style.color = textColor;
94
+ });
95
+ };
13
96
 
14
- // HTML elements inside foreignObject use 'color'
15
- svgElement.querySelectorAll('.nodeLabel, .edgeLabel').forEach((el) => {
16
- (el as HTMLElement).style.color = textColor;
17
- });
18
- }
97
+ /**
98
+ * Re-color ER diagram attribute-row backgrounds.
99
+ *
100
+ * Mermaid derives the zebra-stripe fills for `.row-rect-odd` /
101
+ * `.row-rect-even` by lightening `mainBkg` — in dark mode the odd stripe
102
+ * lands on a light gray (`hsl(0 0% 83%)`) while the attribute text stays
103
+ * white, so it vanishes. `themeVariables` has no hook for these, so we
104
+ * re-assert themed fills after render.
105
+ *
106
+ * @param container Host element holding the rendered SVG.
107
+ * @param oddFill Background for odd attribute rows.
108
+ * @param evenFill Background for even attribute rows.
109
+ */
110
+ export const applyMermaidErRowColors = (
111
+ container: HTMLElement,
112
+ oddFill: string,
113
+ evenFill: string,
114
+ ): void => {
115
+ const svgElement = container.querySelector('svg');
116
+ if (!svgElement) return;
117
+ svgElement.querySelectorAll('.row-rect-odd path').forEach((el) => {
118
+ const fill = (el as SVGElement).getAttribute('fill');
119
+ if (fill && fill !== 'none') (el as SVGElement).setAttribute('fill', oddFill);
120
+ });
121
+ svgElement.querySelectorAll('.row-rect-even path').forEach((el) => {
122
+ const fill = (el as SVGElement).getAttribute('fill');
123
+ if (fill && fill !== 'none') (el as SVGElement).setAttribute('fill', evenFill);
124
+ });
19
125
  };
20
126
 
21
- // Detect if diagram is vertical (tall and narrow)
127
+ /**
128
+ * Detect whether a diagram is vertical (tall and narrow).
129
+ *
130
+ * Used to pick a sensible fullscreen fit. Prefers the `viewBox` (stable,
131
+ * available immediately) and falls back to `getBBox()` only when needed.
132
+ */
22
133
  export const isVerticalDiagram = (svgElement: SVGSVGElement): boolean => {
23
134
  const viewBox = svgElement.getAttribute('viewBox');
24
135
  if (viewBox) {
25
- const [, , width, height] = viewBox.split(' ').map(Number);
26
- if (width === undefined || height === undefined) return false;
136
+ const [, , width, height] = viewBox.split(/\s+/).map(Number);
137
+ if (
138
+ width === undefined ||
139
+ height === undefined ||
140
+ !Number.isFinite(width) ||
141
+ !Number.isFinite(height) ||
142
+ width <= 0
143
+ ) {
144
+ return false;
145
+ }
27
146
  return height > width * 1.5;
28
147
  }
29
- const bbox = svgElement.getBBox?.();
30
- if (bbox) {
31
- return bbox.height > bbox.width * 1.5;
148
+ try {
149
+ const bbox = svgElement.getBBox?.();
150
+ if (bbox && bbox.width > 0) {
151
+ return bbox.height > bbox.width * 1.5;
152
+ }
153
+ } catch {
154
+ // getBBox throws if the SVG is not attached / not rendered.
32
155
  }
33
156
  return false;
34
157
  };
@@ -35,6 +35,8 @@ export function FieldRow({ field, depth, showTreeLine = true }: FieldRowProps) {
35
35
  // at smaller text sizes.
36
36
  const padLeft = showTreeLine ? depth * 14 : 0;
37
37
 
38
+ const toggle = () => isExpandable && setOpen((v) => !v);
39
+
38
40
  return (
39
41
  <div className="bg-background">
40
42
  <div
@@ -43,8 +45,16 @@ export function FieldRow({ field, depth, showTreeLine = true }: FieldRowProps) {
43
45
  isExpandable && 'cursor-pointer hover:bg-muted/30',
44
46
  )}
45
47
  style={{ paddingLeft: 12 + padLeft }}
46
- onClick={() => isExpandable && setOpen((v) => !v)}
48
+ onClick={toggle}
49
+ onKeyDown={(e) => {
50
+ if (!isExpandable) return;
51
+ if (e.key === 'Enter' || e.key === ' ') {
52
+ e.preventDefault();
53
+ toggle();
54
+ }
55
+ }}
47
56
  role={isExpandable ? 'button' : undefined}
57
+ tabIndex={isExpandable ? 0 : undefined}
48
58
  aria-expanded={isExpandable ? open : undefined}
49
59
  >
50
60
  <ChevronRight
@@ -3,11 +3,12 @@
3
3
  *
4
4
  * Handles the OpenAPI 3.x subset we actually see: objects with
5
5
  * ``properties`` + ``required``, arrays with ``items``, enums,
6
- * primitives with ``format``. ``allOf`` / ``oneOf`` / ``anyOf`` are
7
- * flattened best-effort we pick the first branch so the UI shows
8
- * *a* shape rather than an empty row. Upstream dereferencing
9
- * (``dereferenceSchema``) is expected to have resolved ``$ref`` before
10
- * this runs.
6
+ * primitives with ``format``. ``allOf`` is merged shallowly.
7
+ * ``oneOf`` / ``anyOf`` object branches are merged so every variant's
8
+ * fields are visible (union of properties); ``required`` is intersected
9
+ * so a field optional in one variant is not shown as globally required.
10
+ * Non-object unions fall back to the first branch. Upstream
11
+ * dereferencing (``dereferenceSchema``) resolves ``$ref`` before this runs.
11
12
  */
12
13
 
13
14
  import type { FieldKind, FieldNode } from './types';
@@ -29,19 +30,40 @@ type JsonSchemaNode = Record<string, unknown> & {
29
30
  * call stack. Anything this deep is unreadable in a docs view anyway. */
30
31
  const MAX_DEPTH = 5;
31
32
 
32
- /** Merge ``allOf`` branches into a single pseudo-object. Shallow merge
33
- * is enough for the display — deep merge would require semantic JSON
34
- * Schema reasoning that we don't need for a read-only viewer. */
35
- function mergeAllOf(branches: JsonSchemaNode[]): JsonSchemaNode {
33
+ /** Merge object branches into a single pseudo-object. Shallow merge is
34
+ * enough for display — deep merge would need semantic JSON Schema
35
+ * reasoning we don't want in a read-only viewer.
36
+ *
37
+ * ``intersectRequired`` controls how ``required`` combines: ``allOf`` is
38
+ * a conjunction so every branch's required fields apply (union); for
39
+ * ``oneOf`` / ``anyOf`` a field is only truly required when *every*
40
+ * object branch requires it (intersection). */
41
+ function mergeObjectBranches(
42
+ branches: JsonSchemaNode[],
43
+ intersectRequired: boolean,
44
+ ): JsonSchemaNode {
36
45
  const properties: Record<string, JsonSchemaNode> = {};
37
- const required: string[] = [];
38
- for (const b of branches) {
46
+ const objectBranches = branches.filter((b) => b.properties);
47
+ const requiredSets = objectBranches.map((b) => new Set(b.required ?? []));
48
+ for (const b of objectBranches) {
39
49
  if (b.properties) Object.assign(properties, b.properties);
40
- if (Array.isArray(b.required)) required.push(...b.required);
50
+ }
51
+ let required: string[];
52
+ if (intersectRequired && requiredSets.length > 0) {
53
+ const [first, ...rest] = requiredSets;
54
+ required = [...first!].filter((k) => rest.every((s) => s.has(k)));
55
+ } else {
56
+ required = [...new Set(requiredSets.flatMap((s) => [...s]))];
41
57
  }
42
58
  return { type: 'object', properties, required };
43
59
  }
44
60
 
61
+ /** True when every branch is (or describes) an object — i.e. the union
62
+ * can be presented as a merged property table. */
63
+ function allObjectBranches(branches: JsonSchemaNode[]): boolean {
64
+ return branches.every((b) => b.type === 'object' || Boolean(b.properties));
65
+ }
66
+
45
67
  function describeType(node: JsonSchemaNode): { label: string; kind: FieldKind } {
46
68
  if (node.type === 'array') {
47
69
  const itemLabel = node.items ? describeType(node.items).label : 'any';
@@ -61,16 +83,23 @@ function describeType(node: JsonSchemaNode): { label: string; kind: FieldKind }
61
83
  }
62
84
 
63
85
  function resolveCombinators(node: JsonSchemaNode): JsonSchemaNode {
64
- // ``allOf`` → merge shallowly. Other combinators → pick the first
65
- // branch (see module docstring rationale).
86
+ // ``allOf`` → conjunction merge (required is a union).
66
87
  if (Array.isArray(node.allOf) && node.allOf.length > 0) {
67
- return { ...mergeAllOf(node.allOf), description: node.description };
68
- }
69
- if (Array.isArray(node.oneOf) && node.oneOf.length > 0) {
70
- return { ...node.oneOf[0]!, description: node.description ?? node.oneOf[0]!.description };
88
+ return { ...mergeObjectBranches(node.allOf, false), description: node.description };
71
89
  }
72
- if (Array.isArray(node.anyOf) && node.anyOf.length > 0) {
73
- return { ...node.anyOf[0]!, description: node.description ?? node.anyOf[0]!.description };
90
+ // ``oneOf`` / ``anyOf`` of objects → merge so every variant's fields
91
+ // are visible; required intersected. Non-object unions → first branch.
92
+ for (const key of ['oneOf', 'anyOf'] as const) {
93
+ const branches = node[key];
94
+ if (Array.isArray(branches) && branches.length > 0) {
95
+ if (branches.length > 1 && allObjectBranches(branches)) {
96
+ return { ...mergeObjectBranches(branches, true), description: node.description };
97
+ }
98
+ return {
99
+ ...branches[0]!,
100
+ description: node.description ?? branches[0]!.description,
101
+ };
102
+ }
74
103
  }
75
104
  return node;
76
105
  }
@@ -53,6 +53,13 @@ export function EndpointDoc({ endpoint, isLoadedInPlayground, onTryIt, schemaId
53
53
  data-endpoint-anchor={anchor}
54
54
  data-schema-id={scopedSchemaId ?? ''}
55
55
  className="scroll-mt-24 py-10 first:pt-0"
56
+ // ``content-visibility: auto`` lets the browser skip layout
57
+ // + paint for endpoint cards that are off-screen — a big
58
+ // win for large specs (hundreds of endpoints) with no
59
+ // behavioural change. ``contain-intrinsic-size`` supplies a
60
+ // height estimate so the scrollbar stays stable before a
61
+ // card is first rendered.
62
+ style={{ contentVisibility: 'auto', containIntrinsicSize: 'auto 480px' }}
56
63
  >
57
64
  <EndpointHeader
58
65
  endpoint={endpoint}
@@ -30,10 +30,13 @@ export type SchemaSection = {
30
30
 
31
31
  const METHOD_ORDER: Record<string, number> = {
32
32
  GET: 0,
33
- POST: 1,
34
- PUT: 2,
35
- PATCH: 3,
36
- DELETE: 4,
33
+ HEAD: 1,
34
+ POST: 2,
35
+ PUT: 3,
36
+ PATCH: 4,
37
+ DELETE: 5,
38
+ OPTIONS: 6,
39
+ TRACE: 7,
37
40
  };
38
41
 
39
42
  const methodRank = (ep: ApiEndpoint) => METHOD_ORDER[ep.method] ?? 99;
@@ -5,6 +5,9 @@ export const HTTP_METHOD_COLORS = {
5
5
  PUT: 'warning',
6
6
  DELETE: 'error',
7
7
  PATCH: 'default',
8
+ HEAD: 'default',
9
+ OPTIONS: 'default',
10
+ TRACE: 'default',
8
11
  } as const;
9
12
 
10
13
  // HTTP status code colors mapping
@@ -10,8 +10,10 @@ import { joinUrl, resolveBaseUrl } from '../utils/url';
10
10
 
11
11
  type JsonSchemaNode = Record<string, unknown>;
12
12
 
13
- // HTTP methods to extract from OpenAPI schema
14
- const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete'] as const;
13
+ // HTTP methods to extract from OpenAPI schema. Covers the full set of
14
+ // OpenAPI Operation Object keys ``head`` / ``options`` / ``trace`` are
15
+ // rarer but valid, and silently dropping them hid real endpoints.
16
+ const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] as const;
15
17
 
16
18
  // Extract endpoints from OpenAPI schema (all methods). ``baseUrl`` is
17
19
  // resolved by the caller via ``resolveBaseUrl`` — we just paste it onto
@@ -79,14 +81,19 @@ const extractEndpoints = (
79
81
  const chosen = chosenContentType ? respContent?.[chosenContentType] : undefined;
80
82
  const respSchema = chosen?.schema as JsonSchemaNode | undefined;
81
83
 
84
+ // Hand-written example wins over a synthesised one.
85
+ const explicit = stringifyExample(explicitMediaExample(chosen));
86
+
82
87
  responses.push({
83
88
  code,
84
89
  description: (response as any).description || `Response ${code}`,
85
90
  contentType: chosenContentType,
86
91
  schema: respSchema,
87
- example: respSchema
88
- ? sampleSchemaJson(respSchema, { skipWriteOnly: true }, specRoot)
89
- : undefined,
92
+ example:
93
+ explicit ??
94
+ (respSchema
95
+ ? sampleSchemaJson(respSchema, { skipWriteOnly: true }, specRoot)
96
+ : undefined),
90
97
  });
91
98
  }
92
99
  }
@@ -102,13 +109,17 @@ const extractEndpoints = (
102
109
  const content = op.requestBody.content;
103
110
  const mediaType = content?.['application/json'] || content?.[Object.keys(content || {})[0]];
104
111
  const rawSchema = mediaType?.schema as JsonSchemaNode | undefined;
112
+ // Hand-written example wins over a synthesised one.
113
+ const explicit = stringifyExample(explicitMediaExample(mediaType));
105
114
  requestBody = {
106
115
  type: (rawSchema?.type as string | undefined) || 'object',
107
116
  description: op.requestBody.description,
108
117
  schema: rawSchema,
109
- example: rawSchema
110
- ? sampleSchemaJson(rawSchema, { skipReadOnly: true }, specRoot)
111
- : undefined,
118
+ example:
119
+ explicit ??
120
+ (rawSchema
121
+ ? sampleSchemaJson(rawSchema, { skipReadOnly: true }, specRoot)
122
+ : undefined),
112
123
  };
113
124
  }
114
125
 
@@ -132,6 +143,34 @@ const extractEndpoints = (
132
143
  return endpoints;
133
144
  };
134
145
 
146
+ // Pull a hand-written example off a media-type object, if present.
147
+ // OpenAPI lets authors attach a canonical example two ways:
148
+ // - ``example`` — a single inline value (3.0 + 3.1).
149
+ // - ``examples`` — a named map of Example Objects (3.0 + 3.1); we use
150
+ // the first entry's ``value``.
151
+ // A hand-written example is more trustworthy than a synthesised one, so
152
+ // callers prefer this over ``openapi-sampler`` output.
153
+ const explicitMediaExample = (media: Record<string, unknown> | undefined): unknown => {
154
+ if (!media) return undefined;
155
+ if (media.example !== undefined) return media.example;
156
+ const examples = media.examples as Record<string, { value?: unknown }> | undefined;
157
+ if (examples && typeof examples === 'object') {
158
+ for (const ex of Object.values(examples)) {
159
+ if (ex && typeof ex === 'object' && 'value' in ex) return ex.value;
160
+ }
161
+ }
162
+ return undefined;
163
+ };
164
+
165
+ const stringifyExample = (value: unknown): string | undefined => {
166
+ if (value === undefined) return undefined;
167
+ try {
168
+ return JSON.stringify(value, null, 2);
169
+ } catch {
170
+ return undefined;
171
+ }
172
+ };
173
+
135
174
  // Get unique categories from endpoints
136
175
  const getCategories = (endpoints: ApiEndpoint[]): string[] => {
137
176
  const categories = new Set<string>();
@@ -139,7 +178,9 @@ const getCategories = (endpoints: ApiEndpoint[]): string[] => {
139
178
  return Array.from(categories).sort();
140
179
  };
141
180
 
142
- // Fetch schema from URL
181
+ // Fetch schema from URL. Parses JSON explicitly (rather than
182
+ // ``response.json()``) so a server that returns HTML or YAML yields a
183
+ // readable error instead of an opaque ``SyntaxError: Unexpected token``.
143
184
  const fetchSchema = async (url: string): Promise<OpenApiSchema> => {
144
185
  const response = await fetch(url, {
145
186
  headers: {
@@ -147,9 +188,19 @@ const fetchSchema = async (url: string): Promise<OpenApiSchema> => {
147
188
  },
148
189
  });
149
190
  if (!response.ok) {
150
- throw new Error(`Failed to fetch schema: ${response.statusText}`);
191
+ throw new Error(`Failed to fetch schema (HTTP ${response.status} ${response.statusText})`);
192
+ }
193
+ const text = await response.text();
194
+ try {
195
+ return JSON.parse(text) as OpenApiSchema;
196
+ } catch {
197
+ const looksYaml = /\.ya?ml($|\?)/i.test(url) || /^\s*openapi\s*:/m.test(text);
198
+ throw new Error(
199
+ looksYaml
200
+ ? 'Schema appears to be YAML — only JSON OpenAPI documents are supported.'
201
+ : 'Schema response is not valid JSON.',
202
+ );
151
203
  }
152
- return response.json();
153
204
  };
154
205
 
155
206
  interface UseOpenApiSchemaProps {
@@ -269,17 +320,28 @@ export default function useOpenApiSchema({
269
320
  return;
270
321
  }
271
322
 
323
+ // Guard against a stale fetch resolving after the user switched
324
+ // schemas — without it a slow earlier request could flip ``loading``
325
+ // off (or surface an error) for a schema that is no longer current.
326
+ let cancelled = false;
327
+
272
328
  fetchSchema(currentSchema.url)
273
329
  .then((schema) => {
330
+ if (cancelled) return;
274
331
  setLoadedSchemas((prev) => new Map(prev).set(currentSchema.id, schema));
275
332
  consola.success(`Schema loaded: ${currentSchema.name}`);
276
333
  setLoading(false);
277
334
  })
278
335
  .catch((err) => {
336
+ if (cancelled) return;
279
337
  consola.error(`Error loading schema from ${currentSchema.url}:`, err);
280
338
  setError(err instanceof Error ? err.message : 'Failed to load schema');
281
339
  setLoading(false);
282
340
  });
341
+
342
+ return () => {
343
+ cancelled = true;
344
+ };
283
345
  }, [currentSchema, loadedSchemas, preloadAll]);
284
346
 
285
347
  // Preload every schema (sections-grouping mode). Each schema is fetched
@@ -23,17 +23,28 @@ import { relativePath, resolveBaseUrl } from './url';
23
23
 
24
24
  /**
25
25
  * Walk a JSON object and replace every ``$ref`` with the resolved value.
26
- * Shallow circular-ref protection: stops at ``maxDepth`` to avoid infinite
27
- * recursion on self-referential schemas (common in recursive OpenAPI types).
26
+ *
27
+ * Cycle handling: each ``$ref`` currently being expanded is tracked on an
28
+ * active set. If a ref points back into a ref already on the stack, the
29
+ * ``$ref`` node is left intact instead of being inlined — downstream
30
+ * consumers (``openapi-sampler``) resolve it against the raw spec, so a
31
+ * left-over local ref is harmless and far cheaper than inlining a
32
+ * recursive type ``maxDepth`` levels deep. ``maxDepth`` is still a hard
33
+ * backstop for pathologically wide graphs.
28
34
  */
29
- export function dereferenceSchema(schema: OpenApiSchema, maxDepth = 10): OpenApiSchema {
35
+ export function dereferenceSchema(schema: OpenApiSchema, maxDepth = 50): OpenApiSchema {
30
36
  const root = schema as unknown as Record<string, unknown>;
31
37
 
32
38
  function resolveRef(ref: string): unknown {
33
39
  // Only handle local refs (#/components/schemas/Foo). External refs are
34
40
  // out of scope — the schema we have in memory is already a single file.
35
41
  if (!ref.startsWith('#/')) return null;
36
- const parts = ref.slice(2).split('/');
42
+ // JSON Pointer un-escaping: ``~1`` → ``/``, ``~0`` → ``~`` (then
43
+ // percent-decoding) so component names with slashes resolve.
44
+ const parts = ref
45
+ .slice(2)
46
+ .split('/')
47
+ .map((p) => decodeURIComponent(p.replace(/~1/g, '/').replace(/~0/g, '~')));
37
48
  let node: unknown = root;
38
49
  for (const part of parts) {
39
50
  if (node && typeof node === 'object' && part in (node as Record<string, unknown>)) {
@@ -45,15 +56,24 @@ export function dereferenceSchema(schema: OpenApiSchema, maxDepth = 10): OpenApi
45
56
  return node;
46
57
  }
47
58
 
59
+ // Refs currently mid-expansion — detects ``A → B → A`` cycles.
60
+ const active = new Set<string>();
61
+
48
62
  function walk(value: unknown, depth: number): unknown {
49
63
  if (depth > maxDepth) return value;
50
64
  if (Array.isArray(value)) return value.map((v) => walk(v, depth + 1));
51
65
  if (value && typeof value === 'object') {
52
66
  const obj = value as Record<string, unknown>;
53
67
  if (typeof obj.$ref === 'string') {
54
- const resolved = resolveRef(obj.$ref);
68
+ const ref = obj.$ref;
69
+ // Cycle — keep the ref node so the sampler can resolve it.
70
+ if (active.has(ref)) return obj;
71
+ const resolved = resolveRef(ref);
55
72
  if (resolved === null) return obj;
56
- return walk(resolved, depth + 1);
73
+ active.add(ref);
74
+ const out = walk(resolved, depth + 1);
75
+ active.delete(ref);
76
+ return out;
57
77
  }
58
78
  const out: Record<string, unknown> = {};
59
79
  for (const [k, v] of Object.entries(obj)) out[k] = walk(v, depth + 1);
@@ -103,7 +103,11 @@ const PrettyCode = ({ data, language, className, mode = 'dark', inline = false,
103
103
  style={customBg ? undefined : surfaceStyle}
104
104
  >
105
105
  <div className="h-full overflow-auto p-4">
106
- <p className="text-sm italic" style={{ color: isDarkMode ? '#9ca3af' : undefined }}>
106
+ <p
107
+ className="text-sm italic"
108
+ role="status"
109
+ style={{ color: isDarkMode ? '#9ca3af' : undefined }}
110
+ >
107
111
  {labels.noContent}
108
112
  </p>
109
113
  </div>
@@ -270,9 +274,9 @@ const PrettyCode = ({ data, language, className, mode = 'dark', inline = false,
270
274
  const inlineBgClass = customBg || (isDarkMode ? '' : 'bg-muted');
271
275
  return (
272
276
  <Highlight theme={prismTheme} code={contentJson} language={normalizedLanguage as Language}>
273
- {({ className, style, tokens, getTokenProps }) => (
277
+ {({ className: prismClassName, style, tokens, getTokenProps }) => (
274
278
  <code
275
- className={`${className} ${inlineBgClass} px-2 py-1 rounded ${isCompact ? 'text-xs' : 'text-sm'} font-mono inline-block`}
279
+ className={`${prismClassName} ${inlineBgClass} px-2 py-1 rounded ${isCompact ? 'text-xs' : 'text-sm'} font-mono inline-block ${className || ''}`}
276
280
  style={{
277
281
  ...style,
278
282
  ...(customBg ? undefined : inlineSurfaceStyle),
@@ -280,11 +284,11 @@ const PrettyCode = ({ data, language, className, mode = 'dark', inline = false,
280
284
  fontFamily: 'monospace',
281
285
  }}
282
286
  >
283
- {tokens.map((line) => (
287
+ {tokens.map((line, i) =>
284
288
  line.map((token, key) => (
285
- <span key={key} {...getTokenProps({ token })} />
286
- ))
287
- ))}
289
+ <span key={`${i}-${key}`} {...getTokenProps({ token })} />
290
+ )),
291
+ )}
288
292
  </code>
289
293
  )}
290
294
  </Highlight>
@@ -299,12 +303,7 @@ const PrettyCode = ({ data, language, className, mode = 'dark', inline = false,
299
303
  <div
300
304
  ref={containerRef}
301
305
  className={`group relative ${bgClass} rounded-lg border ${isDarkMode ? '' : 'border-border'} ${className || ''}`}
302
- style={{
303
- ...(customBg ? undefined : surfaceStyle),
304
- // maxHeight caps growth at ``maxLines`` rows; without maxLines we
305
- // let the block grow to fit its content (no scroll).
306
- ...(maxHeightPx ? { maxHeight: `${maxHeightPx}px` } : null),
307
- }}
306
+ style={customBg ? undefined : surfaceStyle}
308
307
  >
309
308
  {/* Toolbar: hidden by default, appears on hover. Absolute overlay so it doesn't shift layout.
310
309
  scrollIsolation is force-disabled when content fits without scrolling —
@@ -326,14 +325,22 @@ const PrettyCode = ({ data, language, className, mode = 'dark', inline = false,
326
325
  </div>
327
326
  </div>
328
327
 
329
- <div className={shouldScroll ? 'h-full overflow-auto' : ''} style={shouldScroll ? { maxHeight: maxHeightPx } : undefined}>
328
+ <div
329
+ className={shouldScroll ? 'h-full overflow-auto' : ''}
330
+ style={shouldScroll ? { maxHeight: maxHeightPx } : undefined}
331
+ // When the viewer scrolls internally, make it keyboard-reachable
332
+ // so users without a wheel/trackpad can scroll with arrow keys.
333
+ {...(shouldScroll
334
+ ? { tabIndex: 0, role: 'region', 'aria-label': displayLanguage }
335
+ : {})}
336
+ >
330
337
  <Highlight theme={prismTheme} code={contentJson} language={normalizedLanguage as Language}>
331
- {({ className, style, tokens, getLineProps, getTokenProps }) => {
338
+ {({ className: prismClassName, style, tokens, getLineProps, getTokenProps }) => {
332
339
  // Remove background from Prism theme - we use our own via CSS
333
340
  const { backgroundColor: _bg, ...restStyle } = style;
334
341
  return (
335
342
  <pre
336
- className={`${className} rounded-lg`}
343
+ className={`${prismClassName} rounded-lg`}
337
344
  style={{
338
345
  ...restStyle,
339
346
  margin: 0,
@@ -10,7 +10,7 @@
10
10
  * import PrettyCode from '@djangocfg/ui-tools/pretty-code'
11
11
  */
12
12
 
13
- import { createLazyComponent, LoadingFallback } from '../../components';
13
+ import { createLazyComponent } from '../../components';
14
14
  import type { Language } from 'prism-react-renderer';
15
15
 
16
16
  // ============================================================================
@@ -183,7 +183,7 @@ import {
183
183
 
184
184
  <ChatRoot
185
185
  transport={transport}
186
- composerToolbarEnd={<VoiceComposerSlot />}
186
+ composerBlockStart={<VoiceComposerSlot />}
187
187
  />
188
188
 
189
189
  // Header flag-picker is added via ChatLauncher dock slot: