@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
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Redaction entry point — scrub secrets / PII from a captured value.
3
+ *
4
+ * Two layers, applied at traverse time:
5
+ * 1. heuristic — a whole field redacted by element shape
6
+ * (password input, `*token*`-named field).
7
+ * 2. pattern — sensitive substrings scrubbed out of otherwise-kept
8
+ * text (JWT, API keys, emails, Luhn-validated cards, …).
9
+ *
10
+ * Safe-by-default: an un-annotated page is still covered. `data-ai-redact`
11
+ * (drop subtree) is handled in the walk; `data-ai-include` overrides the
12
+ * heuristic layer for a known-safe value.
13
+ *
14
+ */
15
+
16
+ import type { RedactContext, RedactValue } from '../capture/walk';
17
+ import type { RedactionAuditor, RedactionReason } from './audit';
18
+ import {
19
+ hasIncludeAnnotation,
20
+ heuristicRedactKind,
21
+ } from './heuristics';
22
+ import { redactPatterns } from './patterns';
23
+
24
+ export type { RedactionAuditReport, RedactionReason } from './audit';
25
+ export { RedactionAuditor } from './audit';
26
+
27
+ /** Token substituted for a fully-redacted value. */
28
+ export function redactionToken(kind: string): string {
29
+ return `‹redacted:${kind}›`;
30
+ }
31
+
32
+ /**
33
+ * Build a redaction function bound to an auditor.
34
+ *
35
+ * The returned `RedactValue` is the hook the DOM walk calls for every
36
+ * captured input value and text run.
37
+ */
38
+ export function createRedactor(auditor: RedactionAuditor): RedactValue {
39
+ return (value: string, context: RedactContext): string => {
40
+ auditor.markScanned();
41
+ if (!value) return value;
42
+
43
+ const { element, kind, ref } = context;
44
+
45
+ // Layer 1 — heuristic field-level redaction (input values only;
46
+ // static text has no single owning field shape).
47
+ if (kind === 'value') {
48
+ const heuristic = heuristicRedactKind(element);
49
+ if (heuristic && !hasIncludeAnnotation(element)) {
50
+ auditor.log({
51
+ elementRole: element.tagName.toLowerCase(),
52
+ triggerReason: 'heuristic',
53
+ assignedRef: ref,
54
+ });
55
+ return redactionToken(heuristic);
56
+ }
57
+ }
58
+
59
+ // Layer 2 — pattern substring redaction.
60
+ const { value: scrubbed, matched } = redactPatterns(value);
61
+ if (matched.length > 0) {
62
+ const reason: RedactionReason = 'regex';
63
+ for (const _name of matched) {
64
+ auditor.log({
65
+ elementRole: element.tagName.toLowerCase(),
66
+ triggerReason: reason,
67
+ assignedRef: ref,
68
+ });
69
+ }
70
+ return scrubbed;
71
+ }
72
+
73
+ return value;
74
+ };
75
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Luhn checksum — validates credit-card-shaped digit runs so order
3
+ * numbers and IDs are not falsely redacted as cards.
4
+ *
5
+ */
6
+
7
+ /** Validate a digit string with the Luhn algorithm. */
8
+ export function validateLuhn(digits: string): boolean {
9
+ const normalized = digits.replace(/\D/g, '');
10
+ if (normalized.length < 13 || normalized.length > 19) return false;
11
+
12
+ let sum = 0;
13
+ let double = false;
14
+ for (let i = normalized.length - 1; i >= 0; i--) {
15
+ let digit = normalized.charCodeAt(i) - 48; // '0' === 48
16
+ if (digit < 0 || digit > 9) return false;
17
+ if (double) {
18
+ digit *= 2;
19
+ if (digit > 9) digit -= 9;
20
+ }
21
+ sum += digit;
22
+ double = !double;
23
+ }
24
+ return sum % 10 === 0;
25
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Redaction pattern catalog — secret / PII value patterns.
3
+ *
4
+ * Applied to string values (input values, static text) — these scrub
5
+ * sensitive substrings out of otherwise-keepable text. A separate
6
+ * heuristic layer (heuristics.ts) drops whole fields by element shape.
7
+ *
8
+ */
9
+
10
+ import { validateLuhn } from './luhn';
11
+
12
+ /** A named value-redaction rule. */
13
+ export interface RedactionPattern {
14
+ /** Identifier, surfaced in the audit report. */
15
+ name: string;
16
+ /** Matcher applied to string values (must be global for replaceAll). */
17
+ test: RegExp;
18
+ /**
19
+ * Optional extra check on a match before redacting — used to avoid
20
+ * false positives (e.g. Luhn for cards). Return true to redact.
21
+ */
22
+ confirm?: (match: string) => boolean;
23
+ }
24
+
25
+ /** Placeholder a matched secret/PII substring is replaced with. */
26
+ export function patternToken(name: string): string {
27
+ return `‹redacted:${name}›`;
28
+ }
29
+
30
+ /**
31
+ * The catalog. Order matters — more specific patterns first so a token
32
+ * is not partially eaten by a broader rule.
33
+ */
34
+ export const REDACTION_PATTERNS: RedactionPattern[] = [
35
+ {
36
+ // JWT — three base64url segments.
37
+ name: 'jwt',
38
+ test: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g,
39
+ },
40
+ {
41
+ // Bearer / authorization header value.
42
+ name: 'bearer-token',
43
+ test: /\bBearer\s+[A-Za-z0-9\-._~+/]{16,}=*/gi,
44
+ },
45
+ {
46
+ // Common API-key prefixes (Stripe, OpenAI, GitHub, generic sk-/pk-).
47
+ name: 'api-key',
48
+ test: /\b(?:sk|pk|rk|ghp|gho|github_pat|xox[baprs])[-_][A-Za-z0-9-_]{16,}\b/g,
49
+ },
50
+ {
51
+ // AWS access key id.
52
+ name: 'aws-key',
53
+ test: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g,
54
+ },
55
+ {
56
+ // Email address (RFC-5322-ish, pragmatic).
57
+ name: 'email',
58
+ test: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
59
+ },
60
+ {
61
+ // IBAN — country code + check digits + up to 30 alphanumerics.
62
+ name: 'iban',
63
+ test: /\b[A-Z]{2}\d{2}[A-Z0-9]{11,30}\b/g,
64
+ },
65
+ {
66
+ // Credit-card-shaped digit runs — Luhn-confirmed to avoid eating
67
+ // order numbers and other long IDs.
68
+ name: 'credit-card',
69
+ test: /\b(?:\d[ -]?){13,19}\b/g,
70
+ confirm: validateLuhn,
71
+ },
72
+ {
73
+ // Phone number — must look like a phone, not just a long digit run.
74
+ // Requires either a leading "+" or separators (space/()/-), and a
75
+ // digit count in the 7–15 range. A bare 16-digit string is an ID,
76
+ // not a phone, and must survive.
77
+ name: 'phone',
78
+ test: /\+?\d[\d\s().-]{5,}\d/g,
79
+ confirm: (m) => {
80
+ const digits = m.replace(/\D/g, '');
81
+ if (digits.length < 7 || digits.length > 15) return false;
82
+ const hasPlus = m.trimStart().startsWith('+');
83
+ const hasSeparators = /[\s().-]/.test(m);
84
+ return hasPlus || hasSeparators;
85
+ },
86
+ },
87
+ ];
88
+
89
+ /**
90
+ * Redact every pattern match within a string.
91
+ * Returns the scrubbed string and the names of patterns that fired.
92
+ */
93
+ export function redactPatterns(value: string): {
94
+ value: string;
95
+ matched: string[];
96
+ } {
97
+ let result = value;
98
+ const matched: string[] = [];
99
+
100
+ for (const pattern of REDACTION_PATTERNS) {
101
+ // Fresh lastIndex each pattern.
102
+ pattern.test.lastIndex = 0;
103
+ result = result.replace(pattern.test, (match) => {
104
+ if (pattern.confirm && !pattern.confirm(match)) return match;
105
+ if (!matched.includes(pattern.name)) matched.push(pattern.name);
106
+ return patternToken(pattern.name);
107
+ });
108
+ }
109
+
110
+ return { value: result, matched };
111
+ }
@@ -0,0 +1,24 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ import { RefRegistry } from '../registry';
5
+
6
+ describe('RefRegistry', () => {
7
+ it('reports unknown refs as unresolved', () => {
8
+ const reg = new RefRegistry('snap-1');
9
+ expect(reg.has('@e9')).toBe(false);
10
+ expect(reg.resolve('@e9')).toBeNull();
11
+ expect(reg.size).toBe(0);
12
+ expect(reg.snapshotId).toBe('snap-1');
13
+ });
14
+
15
+ it('resolves a registered ref to its element', () => {
16
+ const reg = new RefRegistry('snap-1');
17
+ const el = document.createElement('button');
18
+ reg.set('@e1', el);
19
+
20
+ expect(reg.has('@e1')).toBe(true);
21
+ expect(reg.resolve('@e1')).toBe(el);
22
+ expect(reg.size).toBe(1);
23
+ });
24
+ });
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Ref registry — maps CST ref ids (`@e4`) back to DOM elements.
3
+ *
4
+ * Built during the DOM walk; kept after serialization so AI-driven
5
+ * highlight/focus directives can resolve a ref to a live element.
6
+ * Elements are held weakly so a removed node can be garbage-collected —
7
+ * an unresolvable ref is the "stale" case.
8
+ */
9
+
10
+ import type { CSTRefId } from '../cst/types';
11
+
12
+ /**
13
+ * A per-snapshot ref → element map. Elements are held weakly so a
14
+ * removed node can be garbage-collected — an unresolvable ref is the
15
+ * "stale" case.
16
+ */
17
+ export class RefRegistry {
18
+ /** Unique id of the snapshot these refs belong to. */
19
+ readonly snapshotId: string;
20
+
21
+ private readonly map = new Map<CSTRefId, WeakRef<HTMLElement>>();
22
+
23
+ constructor(snapshotId: string) {
24
+ this.snapshotId = snapshotId;
25
+ }
26
+
27
+ /** Register a ref → element pair (called during the walk). */
28
+ set(ref: CSTRefId, element: HTMLElement): void {
29
+ this.map.set(ref, new WeakRef(element));
30
+ }
31
+
32
+ /** Resolve a ref to its element, or null if gone / unknown (stale). */
33
+ resolve(ref: CSTRefId): HTMLElement | null {
34
+ return this.map.get(ref)?.deref() ?? null;
35
+ }
36
+
37
+ /** Whether a ref is known to this snapshot (regardless of liveness). */
38
+ has(ref: CSTRefId): boolean {
39
+ return this.map.has(ref);
40
+ }
41
+
42
+ /** Number of registered refs. */
43
+ get size(): number {
44
+ return this.map.size;
45
+ }
46
+ }
@@ -0,0 +1,34 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { serializeCST } from '../../cst/serialize';
4
+ import type { CSTRootNode } from '../../cst/types';
5
+ import { hashSnapshot } from '../hash';
6
+
7
+ const sampleRoot: CSTRootNode = {
8
+ type: 'root',
9
+ title: 'Billing Settings',
10
+ url: 'https://app.example.com/settings/billing',
11
+ children: [{ type: 'text', content: 'Configure your subscription.' }],
12
+ };
13
+
14
+ describe('hashSnapshot', () => {
15
+ it('is stable for identical input', () => {
16
+ const s = serializeCST(sampleRoot);
17
+ expect(hashSnapshot(s)).toBe(hashSnapshot(s));
18
+ });
19
+
20
+ it('changes when the snapshot changes', () => {
21
+ const a = hashSnapshot(serializeCST(sampleRoot));
22
+ const mutated: CSTRootNode = { ...sampleRoot, title: 'Other Page' };
23
+ const b = hashSnapshot(serializeCST(mutated));
24
+ expect(a).not.toBe(b);
25
+ });
26
+
27
+ it('returns an 8-char hex string', () => {
28
+ expect(hashSnapshot('anything')).toMatch(/^[0-9a-f]{8}$/);
29
+ });
30
+
31
+ it('handles an empty string', () => {
32
+ expect(hashSnapshot('')).toMatch(/^[0-9a-f]{8}$/);
33
+ });
34
+ });
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Snapshot content hash — staleness detection.
3
+ *
4
+ * A fast non-cryptographic hash (FNV-1a, 32-bit). Two captures of an
5
+ * unchanged page produce the same hash; a changed page produces a
6
+ * different one. Used to decide whether to send a fresh snapshot.
7
+ */
8
+
9
+ /** FNV-1a 32-bit hash of a string, returned as a hex string. */
10
+ export function hashSnapshot(serialized: string): string {
11
+ // FNV-1a constants (32-bit).
12
+ let hash = 0x811c9dc5;
13
+ for (let i = 0; i < serialized.length; i++) {
14
+ hash ^= serialized.charCodeAt(i);
15
+ // hash *= 16777619, kept in 32-bit range via Math.imul.
16
+ hash = Math.imul(hash, 0x01000193);
17
+ }
18
+ // Unsigned, hex.
19
+ return (hash >>> 0).toString(16).padStart(8, '0');
20
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Token estimation.
3
+ *
4
+ * A cheap heuristic — chars / 4.2 — good enough to drive budget
5
+ * decisions client-side. The backend does the authoritative count.
6
+ */
7
+
8
+ /** Average characters per token, empirically ~4.2 for mixed UI text. */
9
+ const CHARS_PER_TOKEN = 4.2;
10
+
11
+ /** Estimate the token count of a string. */
12
+ export function estimateTokens(text: string): number {
13
+ if (!text) return 0;
14
+ return Math.ceil(text.length / CHARS_PER_TOKEN);
15
+ }
@@ -89,12 +89,17 @@ export function PlayerProvider(props: PlayerProviderProps) {
89
89
  const [source] = useState(() => createAudioSnapshotSource(audio));
90
90
  const [levelsStore] = useState<LevelsStore>(() => createLevelsStore());
91
91
 
92
+ const [activePeaks, setActivePeaks] = useState<Float32Array | undefined>(
93
+ () => peaksProp ?? getPeaksFromCache(src),
94
+ );
95
+
92
96
  // Seed peaks-from-prop into the module cache so other consumers benefit too.
93
- if (peaksProp && getPeaksFromCache(src) === undefined) {
94
- setPeaks(src, peaksProp);
95
- }
96
- const initialPeaks = peaksProp ?? getPeaksFromCache(src);
97
- const [activePeaks, setActivePeaks] = useState<Float32Array | undefined>(initialPeaks);
97
+ // Done in an effect (not render) to keep render pure under StrictMode.
98
+ useEffect(() => {
99
+ if (peaksProp && getPeaksFromCache(src) === undefined) {
100
+ setPeaks(src, peaksProp);
101
+ }
102
+ }, [src, peaksProp]);
98
103
 
99
104
  // Apply src / preload / loop / volume / muted imperatively.
100
105
  useEffect(() => {
@@ -147,10 +152,10 @@ export function PlayerProvider(props: PlayerProviderProps) {
147
152
  });
148
153
  }, [audio, volumeIsControlled, mutedIsControlled]);
149
154
 
150
- // When src changes, swap in cached peaks if available; clear otherwise.
155
+ // When src changes, swap in peaks: prop wins, then module cache, else clear.
151
156
  useEffect(() => {
152
- setActivePeaks(getPeaksFromCache(src));
153
- }, [src]);
157
+ setActivePeaks(peaksProp ?? getPeaksFromCache(src));
158
+ }, [src, peaksProp]);
154
159
 
155
160
  // Lifecycle event hooks.
156
161
  useEffect(() => {
@@ -306,9 +311,3 @@ export function PlayerProvider(props: PlayerProviderProps) {
306
311
  </AudioRefCtx.Provider>
307
312
  );
308
313
  }
309
-
310
- // Internal helper for parts that want to expose peaks-update from the lazy hook.
311
- export function usePeaksSetter() {
312
- // The provider seeds peaks via setPeaks() in the module cache; consumers
313
- // re-read on src change. usePeaks hook re-uses the cache directly.
314
- }
@@ -9,6 +9,7 @@ const TRACKED = [
9
9
  'play',
10
10
  'pause',
11
11
  'ended',
12
+ 'loadstart',
12
13
  'loadedmetadata',
13
14
  'durationchange',
14
15
  'emptied',
@@ -17,6 +18,13 @@ const TRACKED = [
17
18
  'error',
18
19
  ] as const;
19
20
 
21
+ // A detached <audio> whose `src` 404s (or points at a dead host) often never
22
+ // fires an `error` event — it stalls in NETWORK_LOADING indefinitely. After
23
+ // this long without metadata/canplay/error we synthesize a network error so
24
+ // the UI can surface it instead of showing a stuck skeleton forever.
25
+ const LOAD_WATCHDOG_MS = 12_000;
26
+ const SYNTHETIC_NETWORK_ERROR = 2;
27
+
20
28
  type Snapshot = {
21
29
  paused: boolean;
22
30
  ended: boolean;
@@ -25,13 +33,14 @@ type Snapshot = {
25
33
  errorCode: number | null;
26
34
  };
27
35
 
28
- function readSnapshot(audio: HTMLAudioElement): Snapshot {
36
+ function readSnapshot(audio: HTMLAudioElement, stalled: boolean): Snapshot {
37
+ const nativeCode = audio.error?.code ?? null;
29
38
  return {
30
39
  paused: audio.paused,
31
40
  ended: audio.ended,
32
41
  duration: Number.isFinite(audio.duration) ? audio.duration : 0,
33
42
  ready: audio.readyState >= 2,
34
- errorCode: audio.error?.code ?? null,
43
+ errorCode: nativeCode ?? (stalled ? SYNTHETIC_NETWORK_ERROR : null),
35
44
  };
36
45
  }
37
46
 
@@ -46,19 +55,59 @@ function snapshotsEqual(a: Snapshot, b: Snapshot): boolean {
46
55
  }
47
56
 
48
57
  export function createAudioSnapshotSource(audio: HTMLAudioElement) {
49
- let cached: Snapshot = readSnapshot(audio);
58
+ let stalled = false;
59
+ let cached: Snapshot = readSnapshot(audio, stalled);
50
60
  return {
51
61
  subscribe(cb: () => void): () => void {
62
+ let watchdog: ReturnType<typeof setTimeout> | null = null;
63
+
64
+ const clearWatchdog = () => {
65
+ if (watchdog !== null) {
66
+ clearTimeout(watchdog);
67
+ watchdog = null;
68
+ }
69
+ };
52
70
  const refresh = () => {
53
- const next = readSnapshot(audio);
71
+ const next = readSnapshot(audio, stalled);
54
72
  if (!snapshotsEqual(cached, next)) {
55
73
  cached = next;
56
74
  cb();
57
75
  }
58
76
  };
59
- for (const e of TRACKED) audio.addEventListener(e, refresh);
77
+ const armWatchdog = () => {
78
+ clearWatchdog();
79
+ if (audio.readyState >= 2 || audio.error) return;
80
+ watchdog = setTimeout(() => {
81
+ watchdog = null;
82
+ if (audio.readyState < 2 && !audio.error) {
83
+ stalled = true;
84
+ refresh();
85
+ }
86
+ }, LOAD_WATCHDOG_MS);
87
+ };
88
+ const onProgress = (e: Event) => {
89
+ if (e.type === 'loadstart' || e.type === 'emptied') {
90
+ // New load cycle — drop any prior stall and restart the watchdog.
91
+ stalled = false;
92
+ armWatchdog();
93
+ } else if (audio.readyState >= 2 || audio.error) {
94
+ // Real progress (or a native error) clears a pending stall timer.
95
+ clearWatchdog();
96
+ if (stalled) stalled = false;
97
+ }
98
+ refresh();
99
+ };
100
+
101
+ for (const e of TRACKED) audio.addEventListener(e, onProgress);
102
+ // Re-sync immediately: events (error, loadedmetadata, …) may have fired
103
+ // between source creation and this subscription. Without this, a stale
104
+ // snapshot from construction time would never update — e.g. a `src` that
105
+ // already errored would stay stuck on `loading`.
106
+ armWatchdog();
107
+ refresh();
60
108
  return () => {
61
- for (const e of TRACKED) audio.removeEventListener(e, refresh);
109
+ clearWatchdog();
110
+ for (const e of TRACKED) audio.removeEventListener(e, onProgress);
62
111
  };
63
112
  },
64
113
  getSnapshot(): Snapshot {
@@ -3,39 +3,25 @@
3
3
  /**
4
4
  * `@djangocfg/ui-tools/audio-player` subpath entrypoint.
5
5
  *
6
- * `LazyPlayer` is the only heavy export it dynamically imports the full
7
- * `Player` tree (PlayerShell + Layout + Cover + Waveform + Controls + audio
8
- * decoding helpers). Everything else listed here is light:
9
- * - types are erased,
10
- * - the Zustand store, context hooks, and selectors carry no UI,
11
- * - the slot components (`Cover`, `Title`, `PlayButton`, `Waveform`, …)
12
- * are presentational React components that read from PlayerContext
13
- * they only become meaningful inside a `<PlayerProvider>` (which only
14
- * exists inside `LazyPlayer` once loaded).
6
+ * `LazyPlayer` is exported as a direct (synchronous) alias of `Player`. We
7
+ * intentionally avoid `React.lazy` + `import('./Player')` here: under bundlers
8
+ * that pre-bundle subpath entries (Vite optimizeDeps in Next.js/Vite/SB), the
9
+ * dynamic import creates a second chunk that re-instantiates the React
10
+ * Contexts (AudioRefCtx/ControlsCtx/MetaCtx/StateCtx/LevelsCtx). The slot
11
+ * components and selector hooks re-exported below would then read from a
12
+ * different context instance than `<PlayerProvider>` writes to, which made
13
+ * `usePlayerAudio` throw "must be used inside <PlayerProvider>".
15
14
  *
16
- * Consumers building a fully custom layout: render `<LazyPlayer>` once to
17
- * get the provider, then arrange slot components inside via render-children
18
- * pattern, or use the bare `Player` re-exported from the root barrel.
15
+ * Heavy audio-decoding work (peaks, AudioContext) already happens lazily at
16
+ * runtime via effects inside `PlayerProvider`/`PlayerShell` there is no
17
+ * benefit to splitting the React shell behind a second chunk.
19
18
  */
20
19
 
21
- import { createLazyComponent } from '../../components';
22
- import type { PlayerProps } from './types';
23
-
24
20
  // ============================================================================
25
- // Lazy heavy component
21
+ // Player component (synchronous; previously lazy — see note above)
26
22
  // ============================================================================
27
23
 
28
- export const LazyPlayer = createLazyComponent<PlayerProps>(
29
- () => import('./Player').then((mod) => ({ default: mod.Player })),
30
- {
31
- displayName: 'LazyAudioPlayer',
32
- fallback: (
33
- <div className="rounded-lg border border-border/60 bg-card px-4 py-6 text-sm text-muted-foreground">
34
- Loading audio player…
35
- </div>
36
- ),
37
- },
38
- );
24
+ export { Player, Player as LazyPlayer } from './Player';
39
25
 
40
26
  // ============================================================================
41
27
  // Light surface — types, store, context, slot components, hooks
@@ -14,9 +14,7 @@ export function TimeDisplay() {
14
14
  useEffect(() => {
15
15
  const el = currentRef.current;
16
16
  if (!el) return;
17
- let raf = 0;
18
17
  let last = -1;
19
- let timer: ReturnType<typeof setInterval> | null = null;
20
18
  const write = () => {
21
19
  const t = audio.currentTime;
22
20
  if (Math.abs(t - last) < 0.5) return;
@@ -28,14 +26,13 @@ export function TimeDisplay() {
28
26
  audio.addEventListener('seeked', onSeek);
29
27
  audio.addEventListener('timeupdate', onSeek);
30
28
  // Backup poll when timeupdate is throttled (e.g. background tab → visible).
31
- timer = setInterval(() => {
29
+ const timer = setInterval(() => {
32
30
  if (!audio.paused) write();
33
31
  }, READ_INTERVAL_MS);
34
32
  return () => {
35
- cancelAnimationFrame(raf);
36
33
  audio.removeEventListener('seeked', onSeek);
37
34
  audio.removeEventListener('timeupdate', onSeek);
38
- if (timer) clearInterval(timer);
35
+ clearInterval(timer);
39
36
  };
40
37
  }, [audio]);
41
38