@tldiagram/core-ui 1.95.1 → 2.0.0

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 (100) hide show
  1. package/dist/api/client.d.ts +184 -3
  2. package/dist/components/ConnectorPanel.d.ts +5 -1
  3. package/dist/components/CrossBranchControls.d.ts +4 -3
  4. package/dist/components/ElementNode.d.ts +5 -0
  5. package/dist/components/ElementPanel.d.ts +6 -1
  6. package/dist/components/LayoutSection.d.ts +2 -1
  7. package/dist/components/MergeDialog.d.ts +16 -0
  8. package/dist/components/NodeContainer.d.ts +2 -0
  9. package/dist/components/ProxyConnectorPanel.d.ts +4 -1
  10. package/dist/components/ViewExplorer/index.d.ts +1 -1
  11. package/dist/components/ViewFloatingMenu-vscode.d.ts +5 -0
  12. package/dist/components/ViewFloatingMenu.d.ts +8 -1
  13. package/dist/components/ViewGridNode.d.ts +3 -0
  14. package/dist/components/ViewPanel.d.ts +2 -1
  15. package/dist/components/WorkspacePanel.d.ts +2 -0
  16. package/dist/components/ZUI/ZUICanvas.d.ts +4 -0
  17. package/dist/components/ZUI/focus.d.ts +32 -0
  18. package/dist/components/ZUI/focus.test.d.ts +1 -0
  19. package/dist/components/ZUI/layout.d.ts +2 -2
  20. package/dist/components/ZUI/proxy.d.ts +20 -4
  21. package/dist/components/ZUI/renderer.d.ts +35 -1
  22. package/dist/components/ZUI/types.d.ts +6 -0
  23. package/dist/components/ZUI/useZUIInteraction.d.ts +1 -0
  24. package/dist/context/WorkspaceVersionContext.d.ts +49 -0
  25. package/dist/crossBranch/resolve.d.ts +39 -2
  26. package/dist/crossBranch/resolve.test.d.ts +1 -0
  27. package/dist/crossBranch/settings.d.ts +6 -1
  28. package/dist/crossBranch/types.d.ts +8 -0
  29. package/dist/hooks/useElementSearch.d.ts +8 -0
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.js +16529 -14030
  32. package/dist/pages/InfiniteZoom.d.ts +1 -0
  33. package/dist/pages/ViewEditor/hooks/useCanvasInteractions.d.ts +6 -1
  34. package/dist/pages/ViewEditor/hooks/useViewContextNeighbours.d.ts +2 -0
  35. package/dist/pages/ViewEditor/hooks/useViewData.d.ts +4 -2
  36. package/dist/pages/ViewEditor/hooks/useViewEditHistory.d.ts +13 -0
  37. package/dist/pages/viewsJumpSearch.d.ts +22 -0
  38. package/dist/pages/viewsJumpSearch.test.d.ts +1 -0
  39. package/dist/store/useStore.d.ts +3 -0
  40. package/dist/types/index.d.ts +9 -0
  41. package/dist/utils/elementIcon.d.ts +2 -0
  42. package/dist/utils/elementIcon.test.d.ts +1 -0
  43. package/dist/utils/sourceEditor.d.ts +7 -0
  44. package/dist/utils/watchDiffSummary.d.ts +34 -0
  45. package/package.json +2 -2
  46. package/src/App.tsx +12 -8
  47. package/src/api/client.ts +488 -26
  48. package/src/components/CodePreviewPanel.tsx +90 -16
  49. package/src/components/ConnectorPanel.tsx +34 -3
  50. package/src/components/ContextNeighborElement.tsx +2 -5
  51. package/src/components/CrossBranchControls.tsx +46 -17
  52. package/src/components/ElementNode.tsx +98 -47
  53. package/src/components/ElementPanel.tsx +62 -25
  54. package/src/components/InlineElementAdder.tsx +8 -3
  55. package/src/components/LayoutSection.tsx +4 -1
  56. package/src/components/MergeDialog.tsx +269 -0
  57. package/src/components/NodeContainer.tsx +55 -17
  58. package/src/components/ProxyConnectorPanel.tsx +58 -16
  59. package/src/components/ViewBezierConnector.tsx +116 -21
  60. package/src/components/ViewExplorer/index.tsx +1 -1
  61. package/src/components/ViewFloatingMenu-vscode.tsx +5 -0
  62. package/src/components/ViewFloatingMenu.tsx +110 -1
  63. package/src/components/ViewGridNode.tsx +59 -8
  64. package/src/components/ViewPanel.tsx +3 -2
  65. package/src/components/WorkspacePanel.tsx +938 -0
  66. package/src/components/ZUI/ZUICanvas.tsx +216 -122
  67. package/src/components/ZUI/focus.test.ts +534 -0
  68. package/src/components/ZUI/focus.ts +293 -0
  69. package/src/components/ZUI/layout.ts +7 -11
  70. package/src/components/ZUI/proxy.ts +470 -114
  71. package/src/components/ZUI/renderer.ts +510 -134
  72. package/src/components/ZUI/types.ts +6 -0
  73. package/src/components/ZUI/useZUIInteraction.ts +66 -29
  74. package/src/context/WorkspaceVersionContext.tsx +126 -0
  75. package/src/crossBranch/resolve.test.ts +342 -0
  76. package/src/crossBranch/resolve.ts +368 -68
  77. package/src/crossBranch/settings.ts +49 -3
  78. package/src/crossBranch/types.ts +9 -0
  79. package/src/hooks/useElementSearch.ts +45 -0
  80. package/src/index.css +11 -0
  81. package/src/index.ts +7 -0
  82. package/src/pages/AppearanceSettings.tsx +24 -1
  83. package/src/pages/Dependencies.tsx +231 -65
  84. package/src/pages/InfiniteZoom.tsx +41 -19
  85. package/src/pages/Settings.tsx +1 -1
  86. package/src/pages/ViewEditor/hooks/useCanvasInteractions.ts +103 -24
  87. package/src/pages/ViewEditor/hooks/useViewContextNeighbours.ts +102 -6
  88. package/src/pages/ViewEditor/hooks/useViewData.ts +42 -26
  89. package/src/pages/ViewEditor/hooks/useViewEditHistory.ts +62 -0
  90. package/src/pages/ViewEditor/index.tsx +549 -59
  91. package/src/pages/Views.tsx +112 -41
  92. package/src/pages/ViewsGrid.tsx +332 -113
  93. package/src/pages/viewsJumpSearch.test.ts +193 -0
  94. package/src/pages/viewsJumpSearch.ts +111 -0
  95. package/src/store/useStore.ts +58 -0
  96. package/src/types/index.ts +10 -0
  97. package/src/utils/elementIcon.test.ts +28 -0
  98. package/src/utils/elementIcon.ts +20 -0
  99. package/src/utils/sourceEditor.ts +46 -0
  100. package/src/utils/watchDiffSummary.ts +159 -0
@@ -18,6 +18,7 @@ import {
18
18
  getVisualHandleSlot,
19
19
  } from '../../../utils/edgeDistribution'
20
20
  import { buildViewContentLinks, useStore } from '../../../store/useStore'
21
+ import type { WorkspaceVersionFollowTarget, WorkspaceVersionPreview } from '../../../context/WorkspaceVersionContext'
21
22
 
22
23
  interface ViewDataOptions {
23
24
  viewId: number | null
@@ -29,6 +30,8 @@ interface ViewDataOptions {
29
30
  hoveredLayerTags: string[] | null
30
31
  hoveredLayerColor: string | null
31
32
  tagColors: Record<string, Tag>
33
+ versionPreview?: WorkspaceVersionPreview | null
34
+ versionFollowTarget?: WorkspaceVersionFollowTarget | null
32
35
  // Node-level callbacks (stable refs from parent)
33
36
  stableOnZoomIn: (elementId: number) => Promise<void>
34
37
  stableOnZoomOut: (elementId: number) => Promise<void>
@@ -52,6 +55,7 @@ function alphaColor(color: string, opacity: number): string {
52
55
  // letting structural-sharing fast-path bail out without rebuilding the node.
53
56
  const HIDDEN_STYLE: CSSProperties = { opacity: 0.1, pointerEvents: 'none' }
54
57
  const SOFT_FOCUS_STYLE: CSSProperties = { opacity: 0.2 }
58
+ const VERSION_DIM_STYLE: CSSProperties = { opacity: 0.1 }
55
59
  const EMPTY_ARRAY: readonly never[] = Object.freeze([])
56
60
  const EMPTY_NODE_CONNECTION_META = Object.freeze({
57
61
  key: '',
@@ -158,6 +162,8 @@ export function useViewData({
158
162
  hoveredLayerTags,
159
163
  hoveredLayerColor,
160
164
  tagColors,
165
+ versionPreview,
166
+ versionFollowTarget,
161
167
  stableOnZoomIn,
162
168
  stableOnZoomOut,
163
169
  stableOnNavigateToView,
@@ -189,7 +195,6 @@ export function useViewData({
189
195
  const incomingLinks = useStore((state) => state.incomingLinks)
190
196
  const treeData = useStore((state) => state.treeData)
191
197
  const allElements = useStore((state) => state.allElements)
192
- const setAllElements = useStore((state) => state.setAllElements)
193
198
  const hydrateViewContent = useStore((state) => state.hydrateViewContent)
194
199
  const resetCanvas = useStore((state) => state.resetCanvas)
195
200
  const removeElementPlacement = useStore((state) => state.removeElementPlacement)
@@ -216,13 +221,14 @@ export function useViewData({
216
221
 
217
222
  // ── Fetch tree ─────────────────────────────────────────────────────────────
218
223
  const refreshGrid = useCallback(async () => {
224
+ if (viewId === null) return
219
225
  const tree = await queryClient.fetchQuery({
220
- queryKey: ['workspace', 'views', 'tree'],
221
- queryFn: () => api.workspace.views.tree(),
226
+ queryKey: ['workspace', 'views', viewId, 'editor-tree'],
227
+ queryFn: () => api.workspace.views.treeAround(viewId, { ancestorLevels: 2, descendantLevels: 2 }),
222
228
  staleTime: 0,
223
229
  }).catch(() => null)
224
230
  if (tree) useStore.getState().setTreeData(tree)
225
- }, [queryClient])
231
+ }, [queryClient, viewId])
226
232
 
227
233
  // ── Fetch view content ──────────────────────────────────────────────────
228
234
  const viewContentQuery = useQuery({
@@ -233,7 +239,7 @@ export function useViewData({
233
239
  const [diag, content, tree] = await Promise.all([
234
240
  api.workspace.views.get(viewId),
235
241
  api.workspace.views.content(viewId),
236
- api.workspace.views.tree(),
242
+ api.workspace.views.treeAround(viewId, { ancestorLevels: 2, descendantLevels: 2 }),
237
243
  ])
238
244
  const viewElements = content.placements || []
239
245
  const connectors = content.connectors || []
@@ -264,16 +270,6 @@ export function useViewData({
264
270
  resetCanvas()
265
271
  }, [resetCanvas, viewId])
266
272
 
267
- // ── Keep all-org elements for inline adder ──────────────────────────────────
268
- const allElementsQuery = useQuery({
269
- queryKey: ['elements', 'list'],
270
- queryFn: () => api.elements.list(),
271
- })
272
-
273
- useEffect(() => {
274
- if (allElementsQuery.data) setAllElements(allElementsQuery.data)
275
- }, [allElementsQuery.data, setAllElements])
276
-
277
273
  // ── Refresh elements ────────────────────────────────────────────────────────
278
274
  const refreshElements = useCallback(async () => {
279
275
  if (viewId === null) return
@@ -428,6 +424,9 @@ export function useViewData({
428
424
  const activeSet = activeTags.length > 0 ? new Set(activeTags) : null
429
425
  const hoveredSet = hoveredLayerTags !== null ? new Set(hoveredLayerTags) : null
430
426
  const isClickConnectMode = clickConnectMode !== null
427
+ const versionElementChanges = versionPreview?.elementChanges
428
+ const versionElementLineDeltas = versionPreview?.elementLineDeltas
429
+ const versionActive = !!versionPreview
431
430
 
432
431
  return viewElements.map((obj) => {
433
432
  const nodeId = String(obj.element_id)
@@ -438,13 +437,21 @@ export function useViewData({
438
437
  const isInactive = isHiddenByLayer || (activeSet !== null && !objTags.some((t) => activeSet.has(t)))
439
438
  const isLayerHighlighted = hoveredSet !== null && objTags.some((t) => hoveredSet.has(t))
440
439
  const isSoftFocused = hoveredSet !== null && !isLayerHighlighted
441
-
442
- const newZIndex = isLayerHighlighted ? 10 : interactionSourceId === obj.element_id ? 1000 : 0
440
+ const versionChangeType = versionElementChanges?.get(obj.element_id)
441
+ const versionLineDelta = versionElementLineDeltas?.get(obj.element_id)
442
+ const versionPulseChangeType = versionFollowTarget?.resourceType === 'element' && versionFollowTarget.resourceId === obj.element_id
443
+ ? versionFollowTarget.changeType ?? versionChangeType
444
+ : undefined
445
+ const isDimmedByVersionPreview = versionActive && !versionChangeType
446
+
447
+ const newZIndex = versionPulseChangeType ? 20 : isLayerHighlighted ? 10 : interactionSourceId === obj.element_id ? 1000 : 0
443
448
  const newStyle = isInactive
444
449
  ? HIDDEN_STYLE
445
450
  : isSoftFocused
446
451
  ? SOFT_FOCUS_STYLE
447
- : undefined
452
+ : isDimmedByVersionPreview
453
+ ? VERSION_DIM_STYLE
454
+ : undefined
448
455
  const layerHighlightColor = isLayerHighlighted ? (hoveredLayerColor ?? undefined) : undefined
449
456
  const position = existing?.dragging ? existing.position : { x: obj.position_x ?? 0, y: obj.position_y ?? 0 }
450
457
  const isZoomHovered = hoveredZoomRef.current?.elementId === obj.element_id ? hoveredZoomRef.current.type : null
@@ -487,7 +494,9 @@ export function useViewData({
487
494
  existing.data.connectedHandleIds === connectionMeta.connectedHandleIds &&
488
495
  existing.data.selectedHandleIds === connectionMeta.selectedHandleIds &&
489
496
  existing.data.reconnectCandidates === connectionMeta.reconnectCandidates &&
490
- existing.data.isConnectorHighlighted === connectionMeta.isConnectorHighlighted
497
+ existing.data.isConnectorHighlighted === connectionMeta.isConnectorHighlighted &&
498
+ existing.data.versionChangeType === versionPulseChangeType &&
499
+ existing.data.versionLineDelta === versionLineDelta
491
500
  ) {
492
501
  return existing
493
502
  }
@@ -527,6 +536,8 @@ export function useViewData({
527
536
  selectedHandleIds: connectionMeta.selectedHandleIds,
528
537
  reconnectCandidates: connectionMeta.reconnectCandidates,
529
538
  isConnectorHighlighted: connectionMeta.isConnectorHighlighted,
539
+ versionChangeType: versionPulseChangeType,
540
+ versionLineDelta,
530
541
  },
531
542
  }
532
543
  })
@@ -537,7 +548,7 @@ export function useViewData({
537
548
  stableOnZoomIn, stableOnZoomOut, stableOnNavigateToView, stableOnSelect,
538
549
  stableOnInteractionStart, stableOnConnectTo, stableOnStartHandleReconnect, stableOnRemoveElement, stableOnHoverZoom,
539
550
  stableOnOpenCodePreview, hoveredZoomRef, activeTags, hiddenLayerTags, hoveredLayerTags, hoveredLayerColor, tagColors,
540
- nodeConnectionMetaByElementId, setRfNodes,
551
+ nodeConnectionMetaByElementId, setRfNodes, versionPreview, versionFollowTarget,
541
552
  ])
542
553
 
543
554
  // ── Derive RF connectors ────────────────────────────────────────────────────────
@@ -545,6 +556,8 @@ export function useViewData({
545
556
  const hiddenSet = hiddenLayerTags.length > 0 ? new Set(hiddenLayerTags) : null
546
557
  const activeSet = activeTags.length > 0 ? new Set(activeTags) : null
547
558
  const hoveredSet = hoveredLayerTags !== null ? new Set(hoveredLayerTags) : null
559
+ const versionConnectorChanges = versionPreview?.connectorChanges
560
+ const versionActive = !!versionPreview
548
561
 
549
562
  setRfEdges((prevConnectors) => {
550
563
  const prevEdgeMap = new Map(prevConnectors.map((e) => [e.id, e]))
@@ -572,11 +585,13 @@ export function useViewData({
572
585
  !srcTags.some((t) => hoveredSet.has(t)) ||
573
586
  !tgtTags.some((t) => hoveredSet.has(t))
574
587
  )
575
- const edgeOpacity = isInactive ? 0.1 : isSoftFocused ? 0.2 : 0.8
576
- const markerOpacity = isInactive ? 0.1 : isSoftFocused ? 0.2 : 1
588
+ const versionChangeType = versionConnectorChanges?.get(e.id)
589
+ const isDimmedByVersionPreview = versionActive && !versionChangeType
590
+ const edgeOpacity = isInactive || isDimmedByVersionPreview ? 0.1 : isSoftFocused ? 0.2 : 0.8
591
+ const markerOpacity = isInactive || isDimmedByVersionPreview ? 0.1 : isSoftFocused ? 0.2 : 1
577
592
  const newZIndex = selectedEdgeId !== null && edgeId === String(selectedEdgeId) ? 1000 : 100
578
593
  const pointerEvents = (isInactive || isSoftFocused) ? 'none' : 'auto'
579
- const labelBgOpacity = isInactive ? 0.1 : isSoftFocused ? 0.2 : 0.95
594
+ const labelBgOpacity = isInactive || isDimmedByVersionPreview ? 0.1 : isSoftFocused ? 0.2 : 0.95
580
595
 
581
596
  // Structural sharing: when all user-visible outputs match prev exactly, reuse prev ref.
582
597
  // We match on the underlying connector ref plus every computed visibility/layout value.
@@ -594,7 +609,8 @@ export function useViewData({
594
609
  (existing.data as { sourceGroupIndex?: number }).sourceGroupIndex === layout.sourceGroupIndex &&
595
610
  (existing.data as { targetGroupIndex?: number }).targetGroupIndex === layout.targetGroupIndex &&
596
611
  (existing.data as { sourceGroupCount?: number }).sourceGroupCount === layout.sourceGroupCount &&
597
- (existing.data as { targetGroupCount?: number }).targetGroupCount === layout.targetGroupCount
612
+ (existing.data as { targetGroupCount?: number }).targetGroupCount === layout.targetGroupCount &&
613
+ (existing.data as { versionChangeType?: string }).versionChangeType === versionChangeType
598
614
  ) {
599
615
  return existing
600
616
  }
@@ -620,6 +636,7 @@ export function useViewData({
620
636
  targetHandleSide: layout.targetHandleSide,
621
637
  sourceHandleSlot: layout.sourceHandleSlot,
622
638
  targetHandleSlot: layout.targetHandleSlot,
639
+ versionChangeType,
623
640
  },
624
641
 
625
642
  style: { stroke: 'var(--accent)', strokeWidth: 2, opacity: edgeOpacity, pointerEvents },
@@ -632,7 +649,7 @@ export function useViewData({
632
649
  }
633
650
  })
634
651
  })
635
- }, [connectorLayouts, selectedEdgeId, activeTags, hiddenLayerTags, hoveredLayerTags, elementMap, setRfEdges])
652
+ }, [connectorLayouts, selectedEdgeId, activeTags, hiddenLayerTags, hoveredLayerTags, elementMap, setRfEdges, versionPreview])
636
653
 
637
654
 
638
655
  // ── Boost z-index of selected connector ────────────────────────────────────────
@@ -685,6 +702,5 @@ export function useViewData({
685
702
  handleElementDeleted,
686
703
  handleElementPermanentlyDeleted,
687
704
  handleElementSaved,
688
- setAllElements,
689
705
  }
690
706
  }
@@ -0,0 +1,62 @@
1
+ import { useCallback, useState } from 'react'
2
+
3
+ const MAX_HISTORY_ITEMS = 20
4
+
5
+ export interface ViewEditHistoryAction {
6
+ undo: () => Promise<void>
7
+ redo: () => Promise<void>
8
+ }
9
+
10
+ export function useViewEditHistory() {
11
+ const [undoStack, setUndoStack] = useState<ViewEditHistoryAction[]>([])
12
+ const [redoStack, setRedoStack] = useState<ViewEditHistoryAction[]>([])
13
+ const [isApplyingHistory, setIsApplyingHistory] = useState(false)
14
+
15
+ const pushAction = useCallback((action: ViewEditHistoryAction) => {
16
+ setUndoStack((stack) => [...stack, action].slice(-MAX_HISTORY_ITEMS))
17
+ setRedoStack([])
18
+ }, [])
19
+
20
+ const clearHistory = useCallback(() => {
21
+ setUndoStack([])
22
+ setRedoStack([])
23
+ }, [])
24
+
25
+ const undo = useCallback(async () => {
26
+ if (isApplyingHistory) return
27
+ const action = undoStack[undoStack.length - 1]
28
+ if (!action) return
29
+ setIsApplyingHistory(true)
30
+ try {
31
+ await action.undo()
32
+ setUndoStack((stack) => stack.slice(0, -1))
33
+ setRedoStack((stack) => [...stack, action].slice(-MAX_HISTORY_ITEMS))
34
+ } finally {
35
+ setIsApplyingHistory(false)
36
+ }
37
+ }, [isApplyingHistory, undoStack])
38
+
39
+ const redo = useCallback(async () => {
40
+ if (isApplyingHistory) return
41
+ const action = redoStack[redoStack.length - 1]
42
+ if (!action) return
43
+ setIsApplyingHistory(true)
44
+ try {
45
+ await action.redo()
46
+ setRedoStack((stack) => stack.slice(0, -1))
47
+ setUndoStack((stack) => [...stack, action].slice(-MAX_HISTORY_ITEMS))
48
+ } finally {
49
+ setIsApplyingHistory(false)
50
+ }
51
+ }, [isApplyingHistory, redoStack])
52
+
53
+ return {
54
+ canUndo: undoStack.length > 0,
55
+ canRedo: redoStack.length > 0,
56
+ isApplyingHistory,
57
+ pushAction,
58
+ clearHistory,
59
+ undo,
60
+ redo,
61
+ }
62
+ }