@djangocfg/ui-tools 2.1.430 → 2.1.432

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 (188) hide show
  1. package/dist/tree/index.cjs +43 -8
  2. package/dist/tree/index.cjs.map +1 -1
  3. package/dist/tree/index.d.cts +31 -1
  4. package/dist/tree/index.d.ts +31 -1
  5. package/dist/tree/index.mjs +43 -8
  6. package/dist/tree/index.mjs.map +1 -1
  7. package/package.json +28 -14
  8. package/src/common/CodePanelHeader/CodePanelHeader.tsx +121 -0
  9. package/src/common/CodePanelHeader/index.ts +1 -0
  10. package/src/common/blocks/BlockError.tsx +46 -0
  11. package/src/common/blocks/index.ts +34 -0
  12. package/src/common/blocks/schemas.ts +143 -0
  13. package/src/common/blocks/types.ts +76 -0
  14. package/src/common/chips/README.md +67 -0
  15. package/src/common/chips/UrlChip.tsx +105 -0
  16. package/src/common/chips/index.ts +20 -0
  17. package/src/common/link-preview/LinkPreviewCard.tsx +178 -0
  18. package/src/common/link-preview/index.ts +12 -0
  19. package/src/common/link-preview/types.ts +44 -0
  20. package/src/common/tiptap/index.ts +10 -0
  21. package/src/common/tiptap/useNodeAttrs.ts +128 -0
  22. package/src/lib/page-snapshot/react/PageSnapshotChip.tsx +1 -1
  23. package/src/tools/chat/README.md +109 -2
  24. package/src/tools/chat/composer/Composer.tsx +45 -12
  25. package/src/tools/chat/composer/ComposerRichTextarea.tsx +16 -4
  26. package/src/tools/chat/composer/PastedTextDialog.tsx +88 -0
  27. package/src/tools/chat/composer/index.ts +9 -0
  28. package/src/tools/chat/composer/textToAttachment.ts +69 -0
  29. package/src/tools/chat/composer/types.ts +13 -0
  30. package/src/tools/chat/composer/useComposerAttach.tsx +47 -0
  31. package/src/tools/chat/constants.ts +9 -2
  32. package/src/tools/chat/core/extractFirstUrl.ts +45 -0
  33. package/src/tools/chat/core/reducer.ts +32 -8
  34. package/src/tools/chat/core/transport/mappers/pydantic-ai.ts +1 -1
  35. package/src/tools/chat/core/transport/mock.ts +4 -1
  36. package/src/tools/chat/hooks/useChat.ts +20 -20
  37. package/src/tools/chat/lazy.tsx +10 -0
  38. package/src/tools/chat/messages/Attachments.tsx +335 -46
  39. package/src/tools/chat/messages/BubbleContextMenu.tsx +203 -0
  40. package/src/tools/chat/messages/MessageBubble.tsx +175 -31
  41. package/src/tools/chat/messages/MessageList.tsx +107 -21
  42. package/src/tools/chat/messages/ToolCalls.tsx +28 -13
  43. package/src/tools/chat/messages/blocks/MessageBlocks.tsx +70 -17
  44. package/src/tools/chat/messages/blocks/builtin.tsx +6 -3
  45. package/src/tools/chat/messages/blocks/renderers/AudioBlock.tsx +6 -0
  46. package/src/tools/chat/messages/blocks/renderers/ChatImageAlbum.tsx +154 -0
  47. package/src/tools/chat/messages/blocks/renderers/CodeBlock.tsx +94 -6
  48. package/src/tools/chat/messages/blocks/renderers/DiffBlock.tsx +29 -0
  49. package/src/tools/chat/messages/blocks/renderers/GalleryBlock.tsx +17 -10
  50. package/src/tools/chat/messages/blocks/renderers/ImageBlock.tsx +37 -16
  51. package/src/tools/chat/messages/blocks/renderers/JsonBlock.tsx +11 -6
  52. package/src/tools/chat/messages/blocks/renderers/LinkBlock.tsx +32 -0
  53. package/src/tools/chat/messages/blocks/renderers/MapBlock.tsx +181 -19
  54. package/src/tools/chat/messages/blocks/renderers/VideoBlock.tsx +2 -5
  55. package/src/tools/chat/messages/index.ts +13 -0
  56. package/src/tools/chat/public.ts +5 -1
  57. package/src/tools/chat/shell/ChatRoot.tsx +29 -0
  58. package/src/tools/chat/styles/bubbleTokens.ts +18 -0
  59. package/src/tools/chat/styles/index.ts +1 -0
  60. package/src/tools/chat/types/attachment.ts +38 -3
  61. package/src/tools/chat/types/block.ts +56 -9
  62. package/src/tools/chat/types/blockSchemas.ts +197 -0
  63. package/src/tools/chat/types/config.ts +33 -0
  64. package/src/tools/chat/types/events.ts +14 -7
  65. package/src/tools/chat/types/index.ts +4 -2
  66. package/src/tools/chat/types/message.ts +7 -0
  67. package/src/tools/data/JsonTree/JsonViewer.tsx +7 -7
  68. package/src/tools/data/Tree/README.md +30 -0
  69. package/src/tools/data/Tree/components/TreeRow.tsx +11 -2
  70. package/src/tools/data/Tree/context/menu/render.tsx +56 -27
  71. package/src/tools/data/Tree/data/appearance.ts +65 -3
  72. package/src/tools/data/Tree/data/index.ts +2 -0
  73. package/src/tools/data/Tree/index.tsx +2 -0
  74. package/src/tools/dev/Map/README.md +30 -345
  75. package/src/tools/dev/Map/cards/index.ts +6 -0
  76. package/src/tools/dev/Map/cards/types.ts +67 -0
  77. package/src/tools/dev/Map/components/CompassChip.tsx +67 -0
  78. package/src/tools/dev/Map/components/DraggableMarkers.tsx +79 -0
  79. package/src/tools/dev/Map/components/GeocoderControl.tsx +148 -63
  80. package/src/tools/dev/Map/components/GeocoderInput.tsx +181 -0
  81. package/src/tools/dev/Map/components/MapContainer/FullscreenDialog.tsx +140 -0
  82. package/src/tools/dev/Map/components/MapContainer/GeolocateHandle.tsx +70 -0
  83. package/src/tools/dev/Map/components/MapContainer/MapInner.tsx +326 -0
  84. package/src/tools/dev/Map/components/MapContainer/MapToolbar.tsx +286 -0
  85. package/src/tools/dev/Map/components/MapContainer/RotateHint.tsx +25 -0
  86. package/src/tools/dev/Map/components/MapContainer/ScrollLockOverlay.tsx +52 -0
  87. package/src/tools/dev/Map/components/MapContainer/constants.ts +15 -0
  88. package/src/tools/dev/Map/components/MapContainer/hooks/useAutoReset.ts +51 -0
  89. package/src/tools/dev/Map/components/MapContainer/hooks/useBasemapState.ts +48 -0
  90. package/src/tools/dev/Map/components/MapContainer/hooks/useExternalMapsUrl.ts +29 -0
  91. package/src/tools/dev/Map/components/MapContainer/index.tsx +29 -0
  92. package/src/tools/dev/Map/components/MapContainer/types.ts +198 -0
  93. package/src/tools/dev/Map/components/MapControls.tsx +27 -0
  94. package/src/tools/dev/Map/components/MapMarkers.tsx +94 -0
  95. package/src/tools/dev/Map/components/MarkerCard.tsx +102 -0
  96. package/src/tools/dev/Map/components/MeasureControl.tsx +219 -0
  97. package/src/tools/dev/Map/components/index.ts +35 -1
  98. package/src/tools/dev/Map/geocode/index.ts +2 -0
  99. package/src/tools/dev/Map/geocode/photon.ts +265 -0
  100. package/src/tools/dev/Map/hooks/index.ts +37 -0
  101. package/src/tools/dev/Map/hooks/useCompass.ts +44 -0
  102. package/src/tools/dev/Map/hooks/useDraggableMarkers.ts +112 -0
  103. package/src/tools/dev/Map/hooks/useGeocoder.ts +155 -0
  104. package/src/tools/dev/Map/hooks/useGeolocation.ts +174 -0
  105. package/src/tools/dev/Map/hooks/useMapControl.ts +5 -1
  106. package/src/tools/dev/Map/hooks/useMapImages.ts +112 -0
  107. package/src/tools/dev/Map/hooks/useMapScrollProtection.ts +155 -0
  108. package/src/tools/dev/Map/hooks/useMapTouchMode.ts +21 -0
  109. package/src/tools/dev/Map/hooks/useMarkerCard.ts +39 -0
  110. package/src/tools/dev/Map/hooks/useMarkers.ts +9 -3
  111. package/src/tools/dev/Map/hooks/useMeasure.ts +169 -0
  112. package/src/tools/dev/Map/hooks/useTerrain.ts +123 -0
  113. package/src/tools/dev/Map/index.ts +59 -2
  114. package/src/tools/dev/Map/layers/index.ts +1 -0
  115. package/src/tools/dev/Map/layers/symbol.ts +93 -0
  116. package/src/tools/dev/Map/lazy.tsx +120 -0
  117. package/src/tools/dev/Map/protocols/pmtiles.ts +73 -0
  118. package/src/tools/dev/Map/styles/index.ts +62 -0
  119. package/src/tools/dev/Map/styles/terrain.ts +29 -0
  120. package/src/tools/dev/Map/types.ts +31 -3
  121. package/src/tools/dev/api/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MetaActions.tsx +1 -1
  122. package/src/tools/dev/api/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/StatusTag.tsx +5 -12
  123. package/src/tools/dev/api/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/SectionHeader.tsx +1 -1
  124. package/src/tools/dev/api/OpenapiViewer/components/DocsLayout/SchemaCopyMenu.tsx +1 -1
  125. package/src/tools/dev/api/OpenapiViewer/components/shared/SendButton.tsx +1 -1
  126. package/src/tools/dev/api/OpenapiViewer/components/shared/http-colors.ts +61 -0
  127. package/src/tools/dev/api/OpenapiViewer/components/shared/ui.tsx +4 -21
  128. package/src/tools/dev/code/MarkdownMessage/MarkdownMessage.tsx +150 -58
  129. package/src/tools/dev/code/MarkdownMessage/README.md +38 -0
  130. package/src/tools/dev/code/MarkdownMessage/builtinRules.tsx +92 -0
  131. package/src/tools/dev/code/MarkdownMessage/index.ts +3 -0
  132. package/src/tools/dev/code/MarkdownMessage/streamingBlocks.ts +164 -0
  133. package/src/tools/dev/code/MarkdownMessage/types.ts +20 -0
  134. package/src/tools/dev/code/PrettyCode/PrettyCode.client.tsx +27 -30
  135. package/src/tools/dev/code/PrettyCode/index.tsx +4 -1
  136. package/src/tools/dev/code/PrettyCode/lazy.tsx +3 -1
  137. package/src/tools/forms/MarkdownEditor/MarkdownEditor.tsx +168 -3
  138. package/src/tools/forms/MarkdownEditor/README.md +84 -0
  139. package/src/tools/forms/MarkdownEditor/chip/ChipNode.ts +454 -0
  140. package/src/tools/forms/MarkdownEditor/chip/syncChips.ts +80 -0
  141. package/src/tools/forms/MarkdownEditor/filePath/FilePathChip.tsx +68 -0
  142. package/src/tools/forms/MarkdownEditor/filePath/__tests__/detect.test.ts +161 -0
  143. package/src/tools/forms/MarkdownEditor/filePath/detect.ts +289 -0
  144. package/src/tools/forms/MarkdownEditor/filePath/index.ts +15 -0
  145. package/src/tools/forms/MarkdownEditor/index.ts +19 -0
  146. package/src/tools/forms/MarkdownEditor/lazy.tsx +10 -0
  147. package/src/tools/forms/MarkdownEditor/styles.css +134 -2
  148. package/src/tools/forms/MarkdownEditor/url/__tests__/detect.test.ts +121 -0
  149. package/src/tools/forms/MarkdownEditor/url/detect.ts +227 -0
  150. package/src/tools/forms/NotionEditor/BookmarkNode.ts +158 -0
  151. package/src/tools/forms/NotionEditor/BookmarkView.tsx +45 -0
  152. package/src/tools/forms/NotionEditor/MapEditLayer.tsx +199 -0
  153. package/src/tools/forms/NotionEditor/MapNode.ts +218 -0
  154. package/src/tools/forms/NotionEditor/MapView.tsx +103 -0
  155. package/src/tools/forms/NotionEditor/NotionEditor.tsx +7 -2
  156. package/src/tools/forms/NotionEditor/README.md +109 -188
  157. package/src/tools/forms/NotionEditor/extensions.ts +15 -0
  158. package/src/tools/forms/NotionEditor/slashItems.ts +19 -0
  159. package/src/tools/forms/NotionEditor/styles.css +25 -0
  160. package/src/tools/forms/NotionEditor/types.ts +9 -0
  161. package/src/tools/input/SpeechRecognition/core/engine/external.ts +5 -0
  162. package/src/tools/input/SpeechRecognition/types.ts +10 -0
  163. package/src/tools/media/Gallery/components/lightbox/GalleryLightbox.tsx +39 -22
  164. package/src/tools/media/ImageViewer/README.md +9 -0
  165. package/src/tools/media/ImageViewer/components/ImageViewer.tsx +165 -79
  166. package/src/tools/media/ImageViewer/components/LightboxChrome.tsx +137 -0
  167. package/src/tools/media/ImageViewer/components/MiddleEllipsis.tsx +46 -0
  168. package/src/tools/media/ImageViewer/components/index.ts +2 -0
  169. package/src/tools/media/ImageViewer/hooks/index.ts +3 -0
  170. package/src/tools/media/ImageViewer/hooks/useIdleChrome.ts +73 -0
  171. package/src/tools/media/ImageViewer/types.ts +10 -0
  172. package/src/tools/media/ImageViewer/utils/constants.ts +28 -0
  173. package/src/tools/media/ImageViewer/utils/index.ts +4 -0
  174. package/src/tools/media/VideoPlayer/VideoPlayer.tsx +34 -40
  175. package/src/tools/media/VideoPlayer/canvas/youtube-canvas.tsx +13 -13
  176. package/src/tools/media/VideoPlayer/parts/center-play.tsx +26 -0
  177. package/src/tools/media/VideoPlayer/parts/index.ts +2 -0
  178. package/src/tools/media/VideoPlayer/styles/video-player.css +102 -53
  179. package/src/tools/visual/charts/SmoothLine/README.md +97 -0
  180. package/src/tools/visual/charts/SmoothLine/SmoothLine.tsx +177 -0
  181. package/src/tools/visual/charts/SmoothLine/index.ts +21 -0
  182. package/src/tools/visual/charts/SmoothLine/lazy.tsx +23 -0
  183. package/src/tools/visual/charts/SmoothLine/smoothPath.ts +209 -0
  184. package/src/tools/visual/charts/SmoothLine/types.ts +98 -0
  185. package/src/tools/visual/charts/SmoothLine/useSmoothSeries.ts +429 -0
  186. package/src/tools/visual/indicators/Fps/types.ts +1 -1
  187. package/src/tools/chat/messages/blocks/renderers/LottieBlock.tsx +0 -11
  188. package/src/tools/dev/Map/components/MapContainer.tsx +0 -198
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ui-tools",
3
- "version": "2.1.430",
3
+ "version": "2.1.432",
4
4
  "description": "Heavy React tools with lazy loading - for Electron, Vite, CRA, Next.js apps",
5
5
  "keywords": [
6
6
  "ui-tools",
@@ -276,6 +276,11 @@
276
276
  "import": "./src/tools/visual/charts/Sparkline/index.ts",
277
277
  "require": "./src/tools/visual/charts/Sparkline/index.ts"
278
278
  },
279
+ "./smooth-line": {
280
+ "types": "./src/tools/visual/charts/SmoothLine/index.ts",
281
+ "import": "./src/tools/visual/charts/SmoothLine/index.ts",
282
+ "require": "./src/tools/visual/charts/SmoothLine/index.ts"
283
+ },
279
284
  "./rating": {
280
285
  "types": "./src/tools/visual/indicators/Rating/index.ts",
281
286
  "import": "./src/tools/visual/indicators/Rating/index.ts",
@@ -329,22 +334,30 @@
329
334
  "test:watch": "vitest"
330
335
  },
331
336
  "peerDependencies": {
332
- "@djangocfg/i18n": "^2.1.430",
333
- "@djangocfg/ui-core": "^2.1.430",
337
+ "@djangocfg/i18n": "^2.1.432",
338
+ "@djangocfg/ui-core": "^2.1.432",
334
339
  "consola": "^3.4.2",
335
340
  "lodash-es": "^4.18.1",
336
341
  "lucide-react": "^0.545.0",
337
342
  "react": "^19.2.4",
338
343
  "react-dom": "^19.2.4",
339
344
  "tailwindcss": "^4.1.18",
345
+ "zod": "^4.3.6",
340
346
  "zustand": "^5.0.0"
341
347
  },
348
+ "peerDependenciesMeta": {
349
+ "zod": {
350
+ "optional": true
351
+ }
352
+ },
342
353
  "dependencies": {
343
354
  "@dnd-kit/core": "^6.3.1",
344
355
  "@dnd-kit/modifiers": "^9.0.0",
345
356
  "@dnd-kit/sortable": "^10.0.0",
346
357
  "@dnd-kit/utilities": "^3.2.2",
347
358
  "@floating-ui/dom": "^1.7.4",
359
+ "@radix-ui/react-direction": "^1.1.1",
360
+ "@radix-ui/react-slot": "^1.2.4",
348
361
  "@rjsf/core": "^6.1.2",
349
362
  "@rjsf/utils": "^6.1.2",
350
363
  "@rjsf/validator-ajv8": "^6.1.2",
@@ -366,22 +379,23 @@
366
379
  "@tiptap/react": "^3.23.0",
367
380
  "@tiptap/starter-kit": "^3.23.0",
368
381
  "@tiptap/suggestion": "^3.23.0",
369
- "lowlight": "^3.3.0",
370
- "tiptap-extension-global-drag-handle": "^0.1.18",
382
+ "@turf/turf": "^7.3.5",
383
+ "@types/qrcode": "^1.5.5",
371
384
  "@wavesurfer/react": "^1.0.12",
372
- "@radix-ui/react-slot": "^1.2.4",
373
- "@radix-ui/react-direction": "^1.1.1",
374
385
  "class-variance-authority": "^0.7.1",
375
386
  "hls-video-element": "^1.5.11",
387
+ "json-edit-react": "^1.30.1",
388
+ "lowlight": "^3.3.0",
376
389
  "maplibre-gl": "^4.7.1",
377
390
  "media-chrome": "^4.19.0",
378
391
  "media-icons": "next",
379
392
  "mermaid": "^11.12.0",
380
393
  "monaco-editor": "^0.55.1",
381
394
  "openapi-sampler": "^1.7.2",
395
+ "pmtiles": "^4.4.1",
382
396
  "prism-react-renderer": "^2.4.1",
383
397
  "prismjs": "^1.30.0",
384
- "json-edit-react": "^1.30.1",
398
+ "qrcode": "^1.5.4",
385
399
  "react-lottie-player": "^2.1.0",
386
400
  "react-map-gl": "^8.1.0",
387
401
  "react-markdown": "10.1.0",
@@ -394,8 +408,7 @@
394
408
  "remark-emoji": "^5.0.2",
395
409
  "remark-gfm": "4.0.1",
396
410
  "remark-smartypants": "^3.0.2",
397
- "qrcode": "^1.5.4",
398
- "@types/qrcode": "^1.5.5",
411
+ "tiptap-extension-global-drag-handle": "^0.1.18",
399
412
  "vimeo-video-element": "^1.7.2",
400
413
  "wavesurfer.js": "^7.12.1",
401
414
  "youtube-video-element": "^1.9.0"
@@ -405,9 +418,9 @@
405
418
  "@maplibre/maplibre-gl-geocoder": "^1.7.0"
406
419
  },
407
420
  "devDependencies": {
408
- "@djangocfg/i18n": "^2.1.430",
409
- "@djangocfg/typescript-config": "^2.1.430",
410
- "@djangocfg/ui-core": "^2.1.430",
421
+ "@djangocfg/i18n": "^2.1.432",
422
+ "@djangocfg/typescript-config": "^2.1.432",
423
+ "@djangocfg/ui-core": "^2.1.432",
411
424
  "@types/lodash-es": "^4.17.12",
412
425
  "@types/mapbox__mapbox-gl-draw": "^1.4.8",
413
426
  "@types/node": "^25.2.3",
@@ -421,7 +434,8 @@
421
434
  "tailwindcss": "^4.1.18",
422
435
  "tsup": "^8.5.0",
423
436
  "typescript": "^5.9.3",
424
- "vitest": "^3.1.4"
437
+ "vitest": "^3.1.4",
438
+ "zod": "^4.3.6"
425
439
  },
426
440
  "publishConfig": {
427
441
  "access": "public"
@@ -0,0 +1,121 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * CodePanelHeader — a slim, persistent header bar for the code panel
5
+ * (PrettyCode), GitHub / ChatGPT style: the detected language on the left,
6
+ * a copy button on the right. In flow (not an absolute hover overlay), so
7
+ * the copy button never floats over the code.
8
+ *
9
+ * Code panels are usually DARK (like the map basemap is always light), so
10
+ * the header defaults to FIXED on-dark colors — theme tokens
11
+ * (`text-foreground`, `border-border`) would render dark-on-dark / an
12
+ * over-bright divider on the dark panel. Pass `dark={false}` for a
13
+ * light-surfaced panel, which switches to muted semantic tokens.
14
+ */
15
+
16
+ import { useCallback, useState, type ReactNode } from 'react';
17
+
18
+ import { cn } from '@djangocfg/ui-core/lib';
19
+ import { Check, Copy, Maximize2 } from 'lucide-react';
20
+
21
+ export interface CodePanelHeaderProps {
22
+ /** Display label on the left (e.g. "typescript", "json", "bash"). */
23
+ language?: ReactNode;
24
+ /** Text copied when the copy button is pressed. Omit to hide copy. */
25
+ copyValue?: string;
26
+ /** a11y label for the copy button. */
27
+ copyLabel?: string;
28
+ /** When set, an expand (⤢) button is shown that calls this — used to open
29
+ * the panel full-screen when it's height-capped. */
30
+ onExpand?: () => void;
31
+ /** a11y / tooltip label for the expand button. */
32
+ expandLabel?: string;
33
+ /** Extra trailing actions, rendered left of the copy button. */
34
+ actions?: ReactNode;
35
+ /** Dark panel (default). `false` → light-surface muted tokens. */
36
+ dark?: boolean;
37
+ className?: string;
38
+ }
39
+
40
+ export function CodePanelHeader({
41
+ language,
42
+ copyValue,
43
+ copyLabel = 'Copy',
44
+ onExpand,
45
+ expandLabel = 'Expand',
46
+ actions,
47
+ dark = true,
48
+ className,
49
+ }: CodePanelHeaderProps) {
50
+ const [copied, setCopied] = useState(false);
51
+
52
+ const onCopy = useCallback(() => {
53
+ if (copyValue == null) return;
54
+ void navigator.clipboard?.writeText(copyValue).then(() => {
55
+ setCopied(true);
56
+ setTimeout(() => setCopied(false), 1400);
57
+ });
58
+ }, [copyValue]);
59
+
60
+ // Fixed on-dark vs semantic light. The hairline is faint so it never
61
+ // reads as a heavy rule across the panel.
62
+ const dividerCls = dark ? 'border-white/[0.06]' : 'border-border/60';
63
+ const labelCls = dark ? 'text-white/40' : 'text-muted-foreground';
64
+ const btnCls = dark
65
+ ? 'text-white/45 hover:bg-white/10 hover:text-white/80 focus-visible:ring-white/40'
66
+ : 'text-muted-foreground hover:bg-foreground/10 hover:text-foreground focus-visible:ring-ring';
67
+
68
+ return (
69
+ <div
70
+ className={cn(
71
+ 'flex items-center justify-between gap-2 border-b px-3 py-1',
72
+ dividerCls,
73
+ className,
74
+ )}
75
+ >
76
+ <span
77
+ className={cn(
78
+ 'min-w-0 truncate font-mono text-[11px] lowercase tracking-wide',
79
+ labelCls,
80
+ )}
81
+ >
82
+ {language}
83
+ </span>
84
+
85
+ <div className="flex shrink-0 items-center gap-0.5">
86
+ {actions}
87
+ {onExpand ? (
88
+ <button
89
+ type="button"
90
+ onClick={onExpand}
91
+ aria-label={expandLabel}
92
+ title={expandLabel}
93
+ className={cn(
94
+ 'inline-flex size-5 items-center justify-center rounded-md transition-colors focus-visible:outline-none focus-visible:ring-1',
95
+ btnCls,
96
+ )}
97
+ >
98
+ <Maximize2 className="size-3" aria-hidden />
99
+ </button>
100
+ ) : null}
101
+ {copyValue != null ? (
102
+ <button
103
+ type="button"
104
+ onClick={onCopy}
105
+ aria-label={copyLabel}
106
+ className={cn(
107
+ 'inline-flex size-5 items-center justify-center rounded-md transition-colors focus-visible:outline-none focus-visible:ring-1',
108
+ btnCls,
109
+ )}
110
+ >
111
+ {copied ? (
112
+ <Check className="size-3" aria-hidden />
113
+ ) : (
114
+ <Copy className="size-3" aria-hidden />
115
+ )}
116
+ </button>
117
+ ) : null}
118
+ </div>
119
+ </div>
120
+ );
121
+ }
@@ -0,0 +1 @@
1
+ export { CodePanelHeader, type CodePanelHeaderProps } from './CodePanelHeader';
@@ -0,0 +1,46 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * `<BlockError>` — the safe presentational fallback a data-driven block renders
5
+ * when its payload fails schema validation (a corrupted ```map JSON, a
6
+ * malformed wire block). Informative, not scary: a compact muted card that says
7
+ * the block couldn't render, with an optional short reason. Never throws, so the
8
+ * surrounding editor / message list stays alive.
9
+ *
10
+ * Semantic tokens only (`bg-muted` / `border-border` / `text-muted-foreground`).
11
+ */
12
+
13
+ import { AlertTriangle } from 'lucide-react';
14
+
15
+ export interface BlockErrorProps {
16
+ /**
17
+ * Short, human-readable reason (e.g. the first Zod issue). Optional — when
18
+ * omitted only the generic headline shows.
19
+ */
20
+ reason?: string;
21
+ /** What couldn't be rendered (e.g. "map", "bookmark"). Defaults to "block". */
22
+ label?: string;
23
+ className?: string;
24
+ }
25
+
26
+ export function BlockError({ reason, label = 'block', className }: BlockErrorProps) {
27
+ return (
28
+ <div
29
+ role="status"
30
+ className={
31
+ 'flex items-start gap-2.5 rounded-lg border border-border bg-muted px-3 py-2.5 text-sm text-muted-foreground' +
32
+ (className ? ` ${className}` : '')
33
+ }
34
+ >
35
+ <AlertTriangle className="mt-0.5 h-4 w-4 shrink-0" aria-hidden="true" />
36
+ <div className="min-w-0">
37
+ <div className="font-medium text-foreground">Couldn&apos;t render this {label}</div>
38
+ {reason ? (
39
+ <div className="mt-0.5 break-words text-xs text-muted-foreground">{reason}</div>
40
+ ) : null}
41
+ </div>
42
+ </div>
43
+ );
44
+ }
45
+
46
+ export default BlockError;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Shared block payload types — the serializable wire shapes that both the
3
+ * chat message blocks and the NotionEditor block NodeViews reference, so the
4
+ * shapes can't drift. The link-preview data shape lives in
5
+ * `common/link-preview`; the map payload lives here. Re-exported below so a
6
+ * single import (`common/blocks`) reaches every shared block payload.
7
+ */
8
+
9
+ export type {
10
+ MapBlockPayload,
11
+ MapMarkerPayload,
12
+ MapRoutePayload,
13
+ MapPolygonPayload,
14
+ } from './types';
15
+
16
+ // Re-export the link-preview data contract from its home so consumers can
17
+ // reach every shared block payload from one place.
18
+ export type { LinkPreviewData, ResolveLinkPreview } from '../link-preview';
19
+
20
+ // Runtime validators + the safe-render fallback. A block NodeView/renderer
21
+ // parses its payload through the matching schema and renders `<BlockError>`
22
+ // on failure instead of throwing.
23
+ export {
24
+ MapMarkerPayloadSchema,
25
+ MapRoutePayloadSchema,
26
+ MapPolygonPayloadSchema,
27
+ MapBlockPayloadSchema,
28
+ LinkPreviewDataSchema,
29
+ safeParseBlock,
30
+ } from './schemas';
31
+ export type { SafeParseResult } from './schemas';
32
+
33
+ export { BlockError } from './BlockError';
34
+ export type { BlockErrorProps } from './BlockError';
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Zod schemas for the shared, serializable block payloads in `./types.ts` and
3
+ * the link-preview data contract. These are the runtime VALIDATORS that let a
4
+ * block NodeView render a safe `<BlockError>` fallback instead of throwing when
5
+ * it's handed a hand-authored / corrupted payload (a bad ```map JSON, a
6
+ * malformed wire block).
7
+ *
8
+ * The TS payload types stay the source of truth in `./types.ts` (and
9
+ * `common/link-preview/types.ts`); these schemas are kept in SYNC with them and
10
+ * `satisfies`-checked below so they can't silently drift. We deliberately do
11
+ * NOT re-export `z.infer` types under the same names — existing imports
12
+ * (`MapBlockPayload`, `LinkPreviewData`) keep coming from their type homes.
13
+ *
14
+ * Schemas are permissive on optional/extra fields (`.passthrough` is implied by
15
+ * Zod object stripping unknowns) but STRICT on the load-bearing shape: numbers
16
+ * are numbers, `center`/`url` are required. Anything that wouldn't render
17
+ * cleanly fails `safeParse`.
18
+ */
19
+
20
+ import { z } from 'zod';
21
+ import type {
22
+ MapMarkerPayload,
23
+ MapRoutePayload,
24
+ MapPolygonPayload,
25
+ MapBlockPayload,
26
+ } from './types';
27
+ import type { LinkPreviewData } from '../link-preview';
28
+
29
+ /** A finite number — rejects NaN/Infinity that would break the map camera. */
30
+ const finiteNumber = z.number().finite();
31
+
32
+ /** `{ lat, lng }` — a geographic point; both finite numbers, required. */
33
+ const LatLngSchema = z.object({
34
+ lat: finiteNumber,
35
+ lng: finiteNumber,
36
+ });
37
+
38
+ /** Marker info-card (pure JSON; `href`-only actions). */
39
+ const MarkerCardSchema = z.object({
40
+ title: z.string(),
41
+ description: z.string().optional(),
42
+ image: z.string().optional(),
43
+ badge: z.string().optional(),
44
+ actions: z
45
+ .array(z.object({ label: z.string(), href: z.string().optional() }))
46
+ .optional(),
47
+ });
48
+
49
+ /** A single map marker — pin + optional label / icon / colour / card. */
50
+ export const MapMarkerPayloadSchema = z.object({
51
+ id: z.string(),
52
+ lat: finiteNumber,
53
+ lng: finiteNumber,
54
+ label: z.string().optional(),
55
+ icon: z.string().optional(),
56
+ color: z.string().optional(),
57
+ card: MarkerCardSchema.optional(),
58
+ });
59
+
60
+ /** An ordered polyline (route / path). */
61
+ export const MapRoutePayloadSchema = z.object({
62
+ id: z.string(),
63
+ points: z.array(LatLngSchema),
64
+ color: z.string().optional(),
65
+ label: z.string().optional(),
66
+ });
67
+
68
+ /** A filled polygon (zone), auto-closed. */
69
+ export const MapPolygonPayloadSchema = z.object({
70
+ id: z.string(),
71
+ points: z.array(LatLngSchema),
72
+ fillColor: z.string().optional(),
73
+ strokeColor: z.string().optional(),
74
+ label: z.string().optional(),
75
+ });
76
+
77
+ /** The serializable map payload — center + optional viewport + overlays. */
78
+ export const MapBlockPayloadSchema = z.object({
79
+ center: LatLngSchema,
80
+ zoom: finiteNumber.optional(),
81
+ basemap: z.string().optional(),
82
+ terrain: z.boolean().optional(),
83
+ markers: z.array(MapMarkerPayloadSchema).optional(),
84
+ routes: z.array(MapRoutePayloadSchema).optional(),
85
+ polygons: z.array(MapPolygonPayloadSchema).optional(),
86
+ });
87
+
88
+ /** Resolved link-preview metadata — `url` required, rest optional. */
89
+ export const LinkPreviewDataSchema = z.object({
90
+ url: z.string(),
91
+ title: z.string().optional(),
92
+ description: z.string().optional(),
93
+ image: z.string().optional(),
94
+ favicon: z.string().optional(),
95
+ siteName: z.string().optional(),
96
+ });
97
+
98
+ // ── Drift guards ──────────────────────────────────────────────────────────
99
+ // A type-level assertion that each schema's inferred output is assignable to
100
+ // the canonical TS payload type. If `./types.ts` and a schema diverge, the
101
+ // corresponding `Extends<…>` resolves to `never` and the assignment fails to
102
+ // compile — keeping the schemas and types in lockstep.
103
+ type Extends<A, B> = A extends B ? true : never;
104
+ const _markerOk: Extends<z.infer<typeof MapMarkerPayloadSchema>, MapMarkerPayload> = true;
105
+ const _routeOk: Extends<z.infer<typeof MapRoutePayloadSchema>, MapRoutePayload> = true;
106
+ const _polygonOk: Extends<z.infer<typeof MapPolygonPayloadSchema>, MapPolygonPayload> = true;
107
+ const _mapOk: Extends<z.infer<typeof MapBlockPayloadSchema>, MapBlockPayload> = true;
108
+ const _linkOk: Extends<z.infer<typeof LinkPreviewDataSchema>, LinkPreviewData> = true;
109
+ // Reference the guards so `noUnusedLocals` stays happy without exporting them.
110
+ void [_markerOk, _routeOk, _polygonOk, _mapOk, _linkOk];
111
+
112
+ /**
113
+ * Discriminated result of a guarded parse — never throws.
114
+ *
115
+ * The mutually-exclusive `?: never` fields make narrowing robust: a plain
116
+ * `if (!r.ok)` flows into the error branch even when `T` is a deep object type
117
+ * (TS's discriminant else-narrowing otherwise mis-fires on very large `data`
118
+ * shapes). Callers can therefore use `!r.ok` / `r.ok` freely.
119
+ */
120
+ export type SafeParseResult<T> =
121
+ | { ok: true; data: T; error?: never }
122
+ | { ok: false; error: string; data?: never };
123
+
124
+ /**
125
+ * Validate `data` against `schema`, returning a discriminated result instead of
126
+ * throwing. On failure `error` is a short, human-readable reason (the first
127
+ * Zod issue) suitable for a `<BlockError reason=…>` card.
128
+ *
129
+ * The output type is taken from the schema (`z.infer`) so callers get the
130
+ * precisely-typed `data` on the success branch with no extra type argument.
131
+ */
132
+ export function safeParseBlock<S extends z.ZodTypeAny>(
133
+ schema: S,
134
+ data: unknown,
135
+ ): SafeParseResult<z.infer<S>> {
136
+ const result = schema.safeParse(data);
137
+ if (result.success) return { ok: true, data: result.data };
138
+ const first = result.error.issues[0];
139
+ const reason = first
140
+ ? `${first.path.length ? first.path.map(String).join('.') + ': ' : ''}${first.message}`
141
+ : 'Invalid payload';
142
+ return { ok: false, error: reason };
143
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Shared, serializable block payload TYPES — the wire shapes that more than
3
+ * one surface (chat message blocks + the NotionEditor block NodeViews) must
4
+ * agree on. Defining them ONCE here keeps the shapes from drifting: chat's
5
+ * `tools/chat/types/block.ts` and the Notion nodes both reference these.
6
+ *
7
+ * Plain JSON only (no Date / no functions / no React nodes) so payloads
8
+ * survive history persistence, SSE transport, AND TipTap node attrs +
9
+ * markdown round-trips.
10
+ */
11
+
12
+ /** A single map marker — a pin plus optional label / icon / colour / card. */
13
+ export interface MapMarkerPayload {
14
+ id: string;
15
+ lat: number;
16
+ lng: number;
17
+ label?: string;
18
+ /** Image URL rendered as the marker pin (rounded thumbnail). */
19
+ icon?: string;
20
+ /** CSS color for the default pin (ignored when `icon` is set). */
21
+ color?: string;
22
+ /**
23
+ * Info-card opened above the pin on click (Google-Maps style). Pure JSON —
24
+ * strings/URLs only; actions are `href`-only links (no `onClick`/`icon`,
25
+ * which can't cross the wire). Rendered by the map's default `MarkerCard`.
26
+ */
27
+ card?: {
28
+ title: string;
29
+ description?: string;
30
+ image?: string;
31
+ badge?: string;
32
+ actions?: Array<{ label: string; href?: string }>;
33
+ };
34
+ }
35
+
36
+ /** An ordered polyline (route / path). */
37
+ export interface MapRoutePayload {
38
+ id: string;
39
+ points: Array<{ lat: number; lng: number }>;
40
+ color?: string;
41
+ label?: string;
42
+ }
43
+
44
+ /** A filled polygon (zone), auto-closed. */
45
+ export interface MapPolygonPayload {
46
+ id: string;
47
+ points: Array<{ lat: number; lng: number }>;
48
+ fillColor?: string;
49
+ strokeColor?: string;
50
+ label?: string;
51
+ }
52
+
53
+ /**
54
+ * Serializable map payload shared by the chat `MapBlock` and the NotionEditor
55
+ * `mapBlock` node. A center + optional zoom / basemap / terrain / markers /
56
+ * routes / polygons. The chat block extends this with chat-only concerns
57
+ * (a `caption`, `userLocation` opt-in); the Notion node serializes this whole
58
+ * object to/from a fenced ```` ```map ```` JSON block.
59
+ */
60
+ export interface MapBlockPayload {
61
+ center: { lat: number; lng: number };
62
+ zoom?: number;
63
+ /**
64
+ * Basemap style — a built-in key (`'light'`/`'dark'`/`'streets'` ·
65
+ * `'liberty'`/`'bright'`/`'positron'`) or a full style-JSON URL. When
66
+ * omitted the map uses its default basemap.
67
+ */
68
+ basemap?: string;
69
+ /** Enable 3D terrain + hillshade (free AWS Terrarium DEM, no key). */
70
+ terrain?: boolean;
71
+ markers?: MapMarkerPayload[];
72
+ /** Polylines (routes / paths). */
73
+ routes?: MapRoutePayload[];
74
+ /** Filled zones (polygons). */
75
+ polygons?: MapPolygonPayload[];
76
+ }
@@ -0,0 +1,67 @@
1
+ # chips
2
+
3
+ Surface-agnostic presentational "chips" — compact inline pills that render a
4
+ URL or a file path with a favicon/icon + middle-ellipsis label. They match the
5
+ TipTap composer's `editor-chip` look (see
6
+ [`forms/MarkdownEditor/chip/ChipNode.ts`](../../tools/forms/MarkdownEditor/chip/ChipNode.ts))
7
+ but are plain React, so the same visual is reusable on any surface — composer,
8
+ chat bubble, link lists.
9
+
10
+ Internal module (`src/common/chips/`) — consumed by other tools via relative
11
+ import; not a package subpath export:
12
+
13
+ ```tsx
14
+ import { UrlChip, FilePathChip } from '../../common/chips';
15
+ ```
16
+
17
+ ## `UrlChip`
18
+
19
+ A compact, clickable URL chip (Chrome-omnibox style): a favicon + the domain
20
+ with a middle-ellipsised path (`github.com/wailsapp/…/README.md`), monospace,
21
+ with the full href on `title` hover. Renders an `<a target="_blank"
22
+ rel="noopener noreferrer">`, so it opens in a new tab.
23
+
24
+ - **Favicon fallback.** The favicon `<img>` swaps to a lucide `Globe` glyph on
25
+ load error (offline / blocked / unknown host) — and also when the host is
26
+ unparseable (empty favicon URL) — so the chip never shows a broken image.
27
+ - **Presentational + self-contained.** It imports only the pure URL detector
28
+ helpers from [`forms/MarkdownEditor/url/detect`](../../tools/forms/MarkdownEditor/url/detect.ts)
29
+ — no TipTap / ProseMirror — so it can render a known URL anywhere:
30
+ `splitUrl` (host/domain/path split), `truncateUrlLabel` (the
31
+ middle-ellipsis label), and `faviconUrl` (Google S2 favicon).
32
+
33
+ ### Props (`UrlChipProps`)
34
+
35
+ | Prop | Type | Default | Description |
36
+ |---|---|---|---|
37
+ | `href` | `string` | required | The full, navigable href (`https://…`). |
38
+ | `maxChars` | `number` | `40` | Approximate max chars for the truncated label. The domain is always kept in full; only the path's middle collapses. |
39
+ | `className` | `string` | — | Extra classes merged onto the chip anchor. |
40
+
41
+ ```tsx
42
+ <UrlChip href="https://github.com/wailsapp/wails/blob/main/v3/README.md" />
43
+ // → 🟣 github.com/…/README.md
44
+ ```
45
+
46
+ ## `FilePathChip`
47
+
48
+ Re-exported (with its `FilePathChipProps` type) from the editor's standalone
49
+ path chip,
50
+ [`forms/MarkdownEditor/filePath/FilePathChip.tsx`](../../tools/forms/MarkdownEditor/filePath/FilePathChip.tsx).
51
+ It's already presentational and dependency-light, so this module re-exports it
52
+ rather than duplicating it — `UrlChip` and `FilePathChip` then live behind one
53
+ import.
54
+
55
+ ## Where these are reused
56
+
57
+ - **Chat markdown bubble.** `UrlChip` is what
58
+ [`MarkdownMessage`'s `urlChipRule`](../../tools/dev/code/MarkdownMessage/README.md)
59
+ renders for a bare URL when the host opts in via `ChatConfig.linkChips`.
60
+ - **Composer.** The TipTap composer renders its **own** DOM chip via `ChipNode`
61
+ (a single inline atom node, `kind: 'url'`), not this React component — but the
62
+ two are built to match pixel-for-pixel using the same semantic tokens, so a
63
+ URL reads identically whether it's being typed in the composer or sitting in a
64
+ rendered bubble.
65
+
66
+ This shared look is the point: the same URL is one consistent chip across every
67
+ surface.