@djangocfg/ui-tools 2.1.407 → 2.1.409

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. package/README.md +9 -10
  2. package/dist/file-icon/index.cjs +449 -61
  3. package/dist/file-icon/index.cjs.map +1 -1
  4. package/dist/file-icon/index.d.cts +56 -18
  5. package/dist/file-icon/index.d.ts +56 -18
  6. package/dist/file-icon/index.mjs +448 -62
  7. package/dist/file-icon/index.mjs.map +1 -1
  8. package/dist/tree/index.cjs +49 -22
  9. package/dist/tree/index.cjs.map +1 -1
  10. package/dist/tree/index.d.cts +9 -3
  11. package/dist/tree/index.d.ts +9 -3
  12. package/dist/tree/index.mjs +49 -22
  13. package/dist/tree/index.mjs.map +1 -1
  14. package/dist/{types-B_zhyAqR.d.cts → types-eEu8SeiQ.d.cts} +4 -0
  15. package/dist/{types-B_zhyAqR.d.ts → types-eEu8SeiQ.d.ts} +4 -0
  16. package/package.json +8 -13
  17. package/src/components/FloatingToolbar/index.tsx +37 -3
  18. package/src/lib/page-snapshot/__tests__/capture-integration.test.ts +85 -0
  19. package/src/lib/page-snapshot/__tests__/engine.test.ts +36 -0
  20. package/src/lib/page-snapshot/__tests__/redaction-integration.test.ts +99 -0
  21. package/src/lib/page-snapshot/__tests__/tokens.test.ts +17 -0
  22. package/src/lib/page-snapshot/capture/__tests__/budget.test.ts +49 -0
  23. package/src/lib/page-snapshot/capture/__tests__/chrome-filter.test.ts +47 -0
  24. package/src/lib/page-snapshot/capture/__tests__/fold.test.ts +66 -0
  25. package/src/lib/page-snapshot/capture/__tests__/scope.test.ts +74 -0
  26. package/src/lib/page-snapshot/capture/__tests__/walk.test.ts +129 -0
  27. package/src/lib/page-snapshot/capture/accessible-name.ts +73 -0
  28. package/src/lib/page-snapshot/capture/budget.ts +95 -0
  29. package/src/lib/page-snapshot/capture/chrome-filter.ts +81 -0
  30. package/src/lib/page-snapshot/capture/classify.ts +111 -0
  31. package/src/lib/page-snapshot/capture/dom-utils.ts +111 -0
  32. package/src/lib/page-snapshot/capture/fold.ts +96 -0
  33. package/src/lib/page-snapshot/capture/scope.ts +169 -0
  34. package/src/lib/page-snapshot/capture/walk.ts +250 -0
  35. package/src/lib/page-snapshot/cst/__tests__/serialize.test.ts +50 -0
  36. package/src/lib/page-snapshot/cst/directives.ts +47 -0
  37. package/src/lib/page-snapshot/cst/payload.ts +50 -0
  38. package/src/lib/page-snapshot/cst/serialize.ts +84 -0
  39. package/src/lib/page-snapshot/cst/types.ts +115 -0
  40. package/src/lib/page-snapshot/engine.ts +176 -0
  41. package/src/lib/page-snapshot/index.ts +93 -0
  42. package/src/lib/page-snapshot/react/PageSnapshotChip.tsx +72 -0
  43. package/src/lib/page-snapshot/react/PageSnapshotPreview.tsx +78 -0
  44. package/src/lib/page-snapshot/react/__tests__/PageSnapshotChip.test.tsx +54 -0
  45. package/src/lib/page-snapshot/react/__tests__/provider.test.tsx +103 -0
  46. package/src/lib/page-snapshot/react/__tests__/use-page-snapshot-toggle.test.tsx +62 -0
  47. package/src/lib/page-snapshot/react/provider.tsx +162 -0
  48. package/src/lib/page-snapshot/react/use-page-snapshot-toggle.ts +47 -0
  49. package/src/lib/page-snapshot/react/use-page-snapshot.ts +67 -0
  50. package/src/lib/page-snapshot/redaction/__tests__/audit.test.ts +25 -0
  51. package/src/lib/page-snapshot/redaction/__tests__/heuristics.test.ts +73 -0
  52. package/src/lib/page-snapshot/redaction/__tests__/luhn.test.ts +26 -0
  53. package/src/lib/page-snapshot/redaction/__tests__/patterns.test.ts +60 -0
  54. package/src/lib/page-snapshot/redaction/audit.ts +58 -0
  55. package/src/lib/page-snapshot/redaction/heuristics.ts +75 -0
  56. package/src/lib/page-snapshot/redaction/index.ts +75 -0
  57. package/src/lib/page-snapshot/redaction/luhn.ts +25 -0
  58. package/src/lib/page-snapshot/redaction/patterns.ts +111 -0
  59. package/src/lib/page-snapshot/refs/__tests__/registry.test.ts +24 -0
  60. package/src/lib/page-snapshot/refs/registry.ts +46 -0
  61. package/src/lib/page-snapshot/staleness/__tests__/hash.test.ts +34 -0
  62. package/src/lib/page-snapshot/staleness/hash.ts +20 -0
  63. package/src/lib/page-snapshot/tokens.ts +15 -0
  64. package/src/tools/AudioPlayer/context/PlayerProvider.tsx +13 -14
  65. package/src/tools/AudioPlayer/hooks/useAudioElementEvents.ts +55 -6
  66. package/src/tools/AudioPlayer/parts/Meta/TimeDisplay.tsx +2 -5
  67. package/src/tools/Chat/README.md +277 -39
  68. package/src/tools/Chat/composer/Composer.tsx +471 -0
  69. package/src/tools/Chat/composer/ComposerActionBar.tsx +65 -0
  70. package/src/tools/Chat/composer/ComposerBanner.tsx +128 -0
  71. package/src/tools/Chat/composer/ComposerButton.tsx +64 -0
  72. package/src/tools/Chat/composer/ComposerFooter.tsx +90 -0
  73. package/src/tools/Chat/composer/ComposerMenuButton.tsx +62 -0
  74. package/src/tools/Chat/composer/ComposerModelPicker.tsx +104 -0
  75. package/src/tools/Chat/composer/ComposerRichTextarea.tsx +88 -0
  76. package/src/tools/Chat/composer/ComposerToolPill.tsx +95 -0
  77. package/src/tools/Chat/composer/index.ts +45 -0
  78. package/src/tools/Chat/composer/size-context.tsx +26 -0
  79. package/src/tools/Chat/composer/types.ts +143 -0
  80. package/src/tools/Chat/composer/useComposerActions.tsx +164 -0
  81. package/src/tools/Chat/context/ChatProvider.tsx +54 -3
  82. package/src/tools/Chat/core/__tests__/metadata.test.ts +69 -0
  83. package/src/tools/Chat/core/index.ts +23 -1
  84. package/src/tools/Chat/core/markdown.ts +1 -1
  85. package/src/tools/Chat/core/metadata.ts +47 -0
  86. package/src/tools/Chat/core/payload-dispatch.ts +1 -1
  87. package/src/tools/Chat/core/transport/http.ts +71 -32
  88. package/src/tools/Chat/core/transport/sse.ts +18 -10
  89. package/src/tools/Chat/highlight/HighlightOverlay.tsx +101 -0
  90. package/src/tools/Chat/highlight/README.md +103 -0
  91. package/src/tools/Chat/highlight/SpotlightCanvas.tsx +153 -0
  92. package/src/tools/Chat/highlight/__tests__/HighlightOverlay.test.tsx +112 -0
  93. package/src/tools/Chat/highlight/__tests__/resolveRef.test.ts +55 -0
  94. package/src/tools/Chat/highlight/index.ts +21 -0
  95. package/src/tools/Chat/highlight/resolveRef.ts +42 -0
  96. package/src/tools/Chat/highlight/types.ts +49 -0
  97. package/src/tools/Chat/highlight/useHighlightTargets.ts +128 -0
  98. package/src/tools/Chat/hooks/index.ts +0 -5
  99. package/src/tools/Chat/hooks/useAutoFocusOnStreamEnd.ts +28 -47
  100. package/src/tools/Chat/hooks/useChat.ts +47 -14
  101. package/src/tools/Chat/hooks/useChatComposer.ts +2 -2
  102. package/src/tools/Chat/hooks/useChatLayout.ts +1 -1
  103. package/src/tools/Chat/hooks/useStreamEndFocus.ts +54 -0
  104. package/src/tools/Chat/index.ts +25 -219
  105. package/src/tools/Chat/launcher/ChatDock.tsx +1 -1
  106. package/src/tools/Chat/launcher/ChatLauncher.tsx +1 -1
  107. package/src/tools/Chat/launcher/{ChatHeader.tsx → header/ChatHeader.tsx} +24 -11
  108. package/src/tools/Chat/launcher/{ChatHeaderActionButton.tsx → header/ChatHeaderActionButton.tsx} +34 -3
  109. package/src/tools/Chat/launcher/{ChatHeaderLanguageButton.tsx → header/ChatHeaderLanguageButton.tsx} +2 -2
  110. package/src/tools/Chat/launcher/{ChatHeaderModeToggle.tsx → header/ChatHeaderModeToggle.tsx} +1 -1
  111. package/src/tools/Chat/launcher/{ChatHeaderResetButton.tsx → header/ChatHeaderResetButton.tsx} +2 -1
  112. package/src/tools/Chat/launcher/{HeaderSlots.tsx → header/HeaderSlots.tsx} +3 -3
  113. package/src/tools/Chat/launcher/header/index.ts +26 -0
  114. package/src/tools/Chat/launcher/index.ts +3 -10
  115. package/src/tools/Chat/lazy.tsx +38 -284
  116. package/src/tools/Chat/{components → messages}/MessageBubble.tsx +58 -5
  117. package/src/tools/Chat/{components → messages}/MessageList.tsx +8 -25
  118. package/src/tools/Chat/messages/blocks/MessageBlocks.tsx +131 -0
  119. package/src/tools/Chat/messages/blocks/builtin.tsx +91 -0
  120. package/src/tools/Chat/messages/blocks/index.ts +12 -0
  121. package/src/tools/Chat/messages/blocks/registry.tsx +42 -0
  122. package/src/tools/Chat/messages/blocks/renderers/AudioBlock.tsx +20 -0
  123. package/src/tools/Chat/messages/blocks/renderers/CodeBlock.tsx +19 -0
  124. package/src/tools/Chat/messages/blocks/renderers/GalleryBlock.tsx +26 -0
  125. package/src/tools/Chat/messages/blocks/renderers/ImageBlock.tsx +27 -0
  126. package/src/tools/Chat/messages/blocks/renderers/JsonBlock.tsx +12 -0
  127. package/src/tools/Chat/messages/blocks/renderers/LottieBlock.tsx +11 -0
  128. package/src/tools/Chat/messages/blocks/renderers/MapBlock.tsx +36 -0
  129. package/src/tools/Chat/messages/blocks/renderers/MermaidBlock.tsx +11 -0
  130. package/src/tools/Chat/messages/blocks/renderers/VideoBlock.tsx +24 -0
  131. package/src/tools/Chat/messages/blocks/renderers/types.ts +8 -0
  132. package/src/tools/Chat/{components → messages}/index.ts +11 -5
  133. package/src/tools/Chat/public.ts +212 -0
  134. package/src/tools/Chat/shell/ChatRoot.tsx +345 -0
  135. package/src/tools/Chat/{components → shell}/EmptyState.tsx +4 -2
  136. package/src/tools/Chat/shell/index.ts +15 -0
  137. package/src/tools/Chat/types/block.ts +120 -0
  138. package/src/tools/Chat/types/config.ts +0 -5
  139. package/src/tools/Chat/types/index.ts +17 -0
  140. package/src/tools/Chat/types/message.ts +3 -0
  141. package/src/tools/Chat/utils/index.ts +4 -0
  142. package/src/tools/CodeEditor/README.md +4 -6
  143. package/src/tools/CodeEditor/components/DiffEditor.tsx +48 -13
  144. package/src/tools/CodeEditor/components/Editor.tsx +96 -44
  145. package/src/tools/CodeEditor/context/EditorProvider.tsx +34 -17
  146. package/src/tools/CodeEditor/hooks/useEditorTheme.ts +92 -99
  147. package/src/tools/CodeEditor/hooks/useMonaco.ts +37 -22
  148. package/src/tools/CodeEditor/lazy.tsx +6 -0
  149. package/src/tools/CodeEditor/lib/index.ts +1 -1
  150. package/src/tools/CodeEditor/lib/themes.ts +3 -39
  151. package/src/tools/CronScheduler/CronScheduler.client.tsx +230 -61
  152. package/src/tools/CronScheduler/components/CustomInput.tsx +21 -4
  153. package/src/tools/CronScheduler/components/DayChips.tsx +13 -11
  154. package/src/tools/CronScheduler/components/MonthDayGrid.tsx +4 -4
  155. package/src/tools/CronScheduler/components/SchedulePreview.tsx +7 -3
  156. package/src/tools/CronScheduler/components/TimeSelector.tsx +1 -1
  157. package/src/tools/CronScheduler/index.tsx +1 -1
  158. package/src/tools/CronScheduler/types/index.ts +8 -3
  159. package/src/tools/CronScheduler/utils/cron-humanize.ts +61 -16
  160. package/src/tools/CronScheduler/utils/cron-parser.ts +13 -4
  161. package/src/tools/FileIcon/FileIcon.tsx +24 -39
  162. package/src/tools/FileIcon/get-file-icon.ts +73 -0
  163. package/src/tools/FileIcon/icons/icon-data.ts +399 -0
  164. package/src/tools/FileIcon/index.ts +4 -0
  165. package/src/tools/FileIcon/loader.ts +17 -35
  166. package/src/tools/FileIcon/specialFolders.ts +18 -0
  167. package/src/tools/Gallery/components/lightbox/GalleryLightbox.tsx +112 -35
  168. package/src/tools/Gallery/components/media/GalleryVideo.tsx +21 -2
  169. package/src/tools/Gallery/components/preview/GalleryCarousel.tsx +11 -1
  170. package/src/tools/Gallery/hooks/usePreloadImages.ts +54 -7
  171. package/src/tools/ImageViewer/components/ImageInfo.tsx +12 -1
  172. package/src/tools/ImageViewer/components/ImageToolbar.tsx +51 -43
  173. package/src/tools/ImageViewer/components/ImageViewer.tsx +96 -24
  174. package/src/tools/ImageViewer/hooks/useImageLoading.ts +13 -0
  175. package/src/tools/ImageViewer/utils/constants.ts +3 -0
  176. package/src/tools/ImageViewer/utils/index.ts +1 -0
  177. package/src/tools/JsonForm/JsonSchemaForm.tsx +4 -1
  178. package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +5 -3
  179. package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +7 -4
  180. package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +3 -1
  181. package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +23 -3
  182. package/src/tools/JsonForm/widgets/ColorWidget.tsx +20 -12
  183. package/src/tools/JsonForm/widgets/NumberWidget.tsx +14 -9
  184. package/src/tools/JsonForm/widgets/RadioWidget.tsx +78 -0
  185. package/src/tools/JsonForm/widgets/SelectWidget.tsx +1 -0
  186. package/src/tools/JsonForm/widgets/SliderWidget.tsx +7 -4
  187. package/src/tools/JsonForm/widgets/TextWidget.tsx +41 -17
  188. package/src/tools/JsonForm/widgets/index.ts +1 -0
  189. package/src/tools/JsonTree/components/JsonContent.tsx +115 -40
  190. package/src/tools/LottiePlayer/LottiePlayer.client.tsx +177 -72
  191. package/src/tools/LottiePlayer/index.tsx +14 -4
  192. package/src/tools/LottiePlayer/lazy.tsx +11 -3
  193. package/src/tools/LottiePlayer/types.ts +31 -1
  194. package/src/tools/LottiePlayer/useLottie.ts +32 -9
  195. package/src/tools/LottiePlayer/usePrefersReducedMotion.ts +46 -0
  196. package/src/tools/Map/components/LayerSwitcher.tsx +54 -21
  197. package/src/tools/Map/components/MapCluster.tsx +28 -21
  198. package/src/tools/Map/components/MapContainer.tsx +11 -4
  199. package/src/tools/Map/components/MapLegend.tsx +46 -15
  200. package/src/tools/Map/components/MapMarker.tsx +31 -2
  201. package/src/tools/Map/hooks/useMapEvents.ts +64 -105
  202. package/src/tools/MarkdownEditor/MarkdownEditor.tsx +61 -6
  203. package/src/tools/MarkdownEditor/MentionList.tsx +37 -4
  204. package/src/tools/MarkdownEditor/createMentionSuggestion.ts +11 -0
  205. package/src/tools/MarkdownEditor/lazy.tsx +32 -7
  206. package/src/tools/MarkdownEditor/styles.css +13 -0
  207. package/src/tools/MarkdownMessage/CodeBlock.tsx +40 -17
  208. package/src/tools/MarkdownMessage/MarkdownMessage.tsx +26 -6
  209. package/src/tools/MarkdownMessage/components.tsx +22 -9
  210. package/src/tools/MarkdownMessage/types.ts +24 -1
  211. package/src/tools/Mermaid/Mermaid.client.tsx +27 -5
  212. package/src/tools/Mermaid/components/MermaidErrorPanel.tsx +31 -0
  213. package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +14 -17
  214. package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +264 -168
  215. package/src/tools/Mermaid/hooks/useMermaidValidation.ts +76 -10
  216. package/src/tools/Mermaid/index.tsx +6 -0
  217. package/src/tools/Mermaid/utils/mermaid-helpers.ts +141 -18
  218. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/FieldRow.tsx +11 -1
  219. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts +49 -20
  220. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/index.tsx +7 -0
  221. package/src/tools/OpenapiViewer/components/DocsLayout/grouping.ts +7 -4
  222. package/src/tools/OpenapiViewer/constants.ts +3 -0
  223. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +73 -11
  224. package/src/tools/OpenapiViewer/utils/schemaExport.ts +26 -6
  225. package/src/tools/PrettyCode/PrettyCode.client.tsx +23 -16
  226. package/src/tools/PrettyCode/lazy.tsx +1 -1
  227. package/src/tools/SpeechRecognition/README.md +1 -1
  228. package/src/tools/SpeechRecognition/__tests__/language.test.ts +9 -3
  229. package/src/tools/SpeechRecognition/components/RecordingPulse.tsx +59 -0
  230. package/src/tools/SpeechRecognition/components/index.ts +2 -0
  231. package/src/tools/SpeechRecognition/core/engine/external.ts +24 -7
  232. package/src/tools/SpeechRecognition/core/language.ts +23 -6
  233. package/src/tools/SpeechRecognition/hooks/usePushToTalk.ts +36 -5
  234. package/src/tools/SpeechRecognition/hooks/useSpeechRecognition.ts +18 -11
  235. package/src/tools/SpeechRecognition/widgets/VoiceComposerSlot.tsx +94 -26
  236. package/src/tools/SpeechRecognition/widgets/index.ts +1 -1
  237. package/src/tools/Tree/README.md +4 -8
  238. package/src/tools/Tree/TreeRoot.tsx +22 -10
  239. package/src/tools/Tree/components/TreeContent.tsx +24 -4
  240. package/src/tools/Tree/components/TreeLabel.tsx +8 -2
  241. package/src/tools/Tree/components/TreeRow.tsx +16 -6
  242. package/src/tools/Tree/data/flatten.ts +10 -4
  243. package/src/tools/Tree/types.ts +4 -0
  244. package/src/tools/Uploader/components/UploadAddButton.tsx +29 -6
  245. package/src/tools/Uploader/components/UploadDropzone.tsx +63 -7
  246. package/src/tools/Uploader/components/UploadPageDropOverlay.tsx +19 -5
  247. package/src/tools/Uploader/components/UploadPreviewItem.tsx +47 -17
  248. package/src/tools/Uploader/components/UploadPreviewList.tsx +24 -12
  249. package/src/tools/Uploader/utils/formatters.ts +8 -3
  250. package/src/tools/VideoPlayer/canvas/hls-canvas.tsx +1 -0
  251. package/src/tools/VideoPlayer/canvas/{jsx.d.ts → jsx-augmentation.ts} +12 -19
  252. package/src/tools/VideoPlayer/canvas/vimeo-canvas.tsx +1 -0
  253. package/src/tools/VideoPlayer/canvas/youtube-canvas.tsx +1 -0
  254. package/src/tools/VideoPlayer/parts/fullscreen.tsx +1 -1
  255. package/src/tools/VideoPlayer/parts/pip.tsx +1 -1
  256. package/src/tools/VideoPlayer/parts/playback-rate.tsx +1 -1
  257. package/src/tools/VideoPlayer/parts/seek-bar.tsx +2 -2
  258. package/src/tools/VideoPlayer/parts/volume.tsx +2 -2
  259. package/src/tools/index.ts +2 -1
  260. package/src/tools/Chat/components/AudioToggle.tsx +0 -78
  261. package/src/tools/Chat/components/ChatRoot.tsx +0 -305
  262. package/src/tools/Chat/components/Composer.tsx +0 -216
  263. package/src/tools/Chat/hooks/useChatScroll.ts +0 -145
  264. package/src/tools/Chat/types.ts +0 -9
  265. package/src/tools/JsonTree/components/JsonToolbar.tsx +0 -95
  266. package/src/tools/JsonTree/hooks/useElementCorner.ts +0 -84
  267. package/src/tools/JsonTree/hooks/useNavbarHeight.ts +0 -83
  268. package/src/tools/OpenapiViewer/components/DocsLayout/schemaFields.ts +0 -121
  269. package/src/tools/Tour/README.md +0 -373
  270. package/src/tools/Tour/components/Tour.tsx +0 -12
  271. package/src/tools/Tour/components/TourContent.tsx +0 -171
  272. package/src/tools/Tour/components/TourNavigation.tsx +0 -77
  273. package/src/tools/Tour/components/TourProgress.tsx +0 -88
  274. package/src/tools/Tour/components/TourSpotlight.tsx +0 -199
  275. package/src/tools/Tour/components/index.ts +0 -5
  276. package/src/tools/Tour/context/TourContext.ts +0 -19
  277. package/src/tools/Tour/context/TourProvider.tsx +0 -292
  278. package/src/tools/Tour/context/index.ts +0 -2
  279. package/src/tools/Tour/hooks/index.ts +0 -3
  280. package/src/tools/Tour/hooks/useKeyboardNavigation.ts +0 -59
  281. package/src/tools/Tour/hooks/useStepTarget.ts +0 -121
  282. package/src/tools/Tour/hooks/useTour.ts +0 -42
  283. package/src/tools/Tour/index.ts +0 -38
  284. package/src/tools/Tour/types/index.ts +0 -224
  285. package/src/tools/Tour/utils/dom.ts +0 -98
  286. package/src/tools/Tour/utils/index.ts +0 -3
  287. package/src/tools/Tour/utils/logger.ts +0 -3
  288. package/src/tools/Tour/utils/scrollIntoView.ts +0 -24
  289. /package/src/tools/Chat/{config.ts → constants.ts} +0 -0
  290. /package/src/tools/Chat/launcher/{ChatHeaderAudioToggle.tsx → header/ChatHeaderAudioToggle.tsx} +0 -0
  291. /package/src/tools/Chat/{components → messages}/Attachments.tsx +0 -0
  292. /package/src/tools/Chat/{components → messages}/JumpToLatest.tsx +0 -0
  293. /package/src/tools/Chat/{components → messages}/MessageActions.tsx +0 -0
  294. /package/src/tools/Chat/{components → messages}/Sources.tsx +0 -0
  295. /package/src/tools/Chat/{components → messages}/StreamingIndicator.tsx +0 -0
  296. /package/src/tools/Chat/{components → messages}/ToolCalls.tsx +0 -0
  297. /package/src/tools/Chat/{components → shell}/ErrorBanner.tsx +0 -0
@@ -0,0 +1,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 {
@@ -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