@tldiagram/core-ui 1.95.1 → 2.0.1
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.
- package/dist/api/client.d.ts +184 -3
- package/dist/components/ConnectorPanel.d.ts +5 -1
- package/dist/components/CrossBranchControls.d.ts +4 -3
- package/dist/components/ElementNode.d.ts +5 -0
- package/dist/components/ElementPanel.d.ts +6 -1
- package/dist/components/LayoutSection.d.ts +2 -1
- package/dist/components/MergeDialog.d.ts +16 -0
- package/dist/components/NodeContainer.d.ts +2 -0
- package/dist/components/ProxyConnectorPanel.d.ts +4 -1
- package/dist/components/ViewExplorer/index.d.ts +1 -1
- package/dist/components/ViewFloatingMenu-vscode.d.ts +5 -0
- package/dist/components/ViewFloatingMenu.d.ts +8 -1
- package/dist/components/ViewGridNode.d.ts +3 -0
- package/dist/components/ViewPanel.d.ts +2 -1
- package/dist/components/WorkspacePanel.d.ts +2 -0
- package/dist/components/ZUI/ZUICanvas.d.ts +4 -0
- package/dist/components/ZUI/focus.d.ts +32 -0
- package/dist/components/ZUI/focus.test.d.ts +1 -0
- package/dist/components/ZUI/layout.d.ts +2 -2
- package/dist/components/ZUI/proxy.d.ts +20 -4
- package/dist/components/ZUI/renderer.d.ts +35 -1
- package/dist/components/ZUI/types.d.ts +6 -0
- package/dist/components/ZUI/useZUIInteraction.d.ts +1 -0
- package/dist/context/WorkspaceVersionContext.d.ts +49 -0
- package/dist/crossBranch/resolve.d.ts +39 -2
- package/dist/crossBranch/resolve.test.d.ts +1 -0
- package/dist/crossBranch/settings.d.ts +6 -1
- package/dist/crossBranch/types.d.ts +8 -0
- package/dist/hooks/useElementSearch.d.ts +8 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +16529 -14030
- package/dist/pages/InfiniteZoom.d.ts +1 -0
- package/dist/pages/ViewEditor/hooks/useCanvasInteractions.d.ts +6 -1
- package/dist/pages/ViewEditor/hooks/useViewContextNeighbours.d.ts +2 -0
- package/dist/pages/ViewEditor/hooks/useViewData.d.ts +4 -2
- package/dist/pages/ViewEditor/hooks/useViewEditHistory.d.ts +13 -0
- package/dist/pages/viewsJumpSearch.d.ts +22 -0
- package/dist/pages/viewsJumpSearch.test.d.ts +1 -0
- package/dist/store/useStore.d.ts +3 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/utils/elementIcon.d.ts +2 -0
- package/dist/utils/elementIcon.test.d.ts +1 -0
- package/dist/utils/sourceEditor.d.ts +7 -0
- package/dist/utils/watchDiffSummary.d.ts +34 -0
- package/package.json +2 -2
- package/src/App.tsx +12 -8
- package/src/api/client.ts +488 -26
- package/src/components/CodePreviewPanel.tsx +90 -16
- package/src/components/ConnectorPanel.tsx +34 -3
- package/src/components/ContextNeighborElement.tsx +2 -5
- package/src/components/CrossBranchControls.tsx +46 -17
- package/src/components/ElementNode.tsx +98 -47
- package/src/components/ElementPanel.tsx +62 -25
- package/src/components/InlineElementAdder.tsx +8 -3
- package/src/components/LayoutSection.tsx +4 -1
- package/src/components/MergeDialog.tsx +269 -0
- package/src/components/NodeContainer.tsx +55 -17
- package/src/components/ProxyConnectorPanel.tsx +58 -16
- package/src/components/ViewBezierConnector.tsx +116 -21
- package/src/components/ViewExplorer/index.tsx +1 -1
- package/src/components/ViewFloatingMenu-vscode.tsx +5 -0
- package/src/components/ViewFloatingMenu.tsx +110 -1
- package/src/components/ViewGridNode.tsx +59 -8
- package/src/components/ViewPanel.tsx +3 -2
- package/src/components/WorkspacePanel.tsx +938 -0
- package/src/components/ZUI/ZUICanvas.tsx +216 -122
- package/src/components/ZUI/focus.test.ts +534 -0
- package/src/components/ZUI/focus.ts +293 -0
- package/src/components/ZUI/layout.ts +7 -11
- package/src/components/ZUI/proxy.ts +470 -114
- package/src/components/ZUI/renderer.ts +510 -134
- package/src/components/ZUI/types.ts +6 -0
- package/src/components/ZUI/useZUIInteraction.ts +66 -29
- package/src/context/WorkspaceVersionContext.tsx +126 -0
- package/src/crossBranch/resolve.test.ts +342 -0
- package/src/crossBranch/resolve.ts +368 -68
- package/src/crossBranch/settings.ts +49 -3
- package/src/crossBranch/types.ts +9 -0
- package/src/hooks/useElementSearch.ts +45 -0
- package/src/index.css +11 -0
- package/src/index.ts +7 -0
- package/src/pages/AppearanceSettings.tsx +24 -1
- package/src/pages/Dependencies.tsx +231 -65
- package/src/pages/InfiniteZoom.tsx +41 -19
- package/src/pages/Settings.tsx +1 -1
- package/src/pages/ViewEditor/hooks/useCanvasInteractions.ts +103 -24
- package/src/pages/ViewEditor/hooks/useViewContextNeighbours.ts +102 -6
- package/src/pages/ViewEditor/hooks/useViewData.ts +42 -26
- package/src/pages/ViewEditor/hooks/useViewEditHistory.ts +62 -0
- package/src/pages/ViewEditor/index.tsx +549 -59
- package/src/pages/Views.tsx +112 -41
- package/src/pages/ViewsGrid.tsx +332 -113
- package/src/pages/viewsJumpSearch.test.ts +193 -0
- package/src/pages/viewsJumpSearch.ts +111 -0
- package/src/store/useStore.ts +58 -0
- package/src/types/index.ts +10 -0
- package/src/utils/elementIcon.test.ts +28 -0
- package/src/utils/elementIcon.ts +20 -0
- package/src/utils/sourceEditor.ts +46 -0
- package/src/utils/watchDiffSummary.ts +159 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Connector, PlacedElement } from '../types'
|
|
2
|
-
import { CROSS_BRANCH_DEPTH_ALL } from './types'
|
|
2
|
+
import { CROSS_BRANCH_CONNECTOR_BUDGET_DEFAULT, CROSS_BRANCH_DEPTH_ALL } from './types'
|
|
3
3
|
import type {
|
|
4
4
|
AggregatedProxyConnector,
|
|
5
|
+
CrossBranchConnectorPriority,
|
|
5
6
|
CrossBranchContextSettings,
|
|
6
7
|
GraphPlacementRef,
|
|
7
8
|
ProxyConnectorDetails,
|
|
@@ -12,6 +13,31 @@ import type {
|
|
|
12
13
|
} from './types'
|
|
13
14
|
import { allConnectors, findLowestCommonAncestorViewId, isDescendantView, relativeOwnerElementPath, viewName } from './graph'
|
|
14
15
|
|
|
16
|
+
const connectorsBySnapshotCache = new WeakMap<WorkspaceGraphSnapshot, Connector[]>()
|
|
17
|
+
const endpointPathCacheBySnapshot = new WeakMap<WorkspaceGraphSnapshot, Map<string, number[]>>()
|
|
18
|
+
|
|
19
|
+
function connectorsForSnapshot(snapshot: WorkspaceGraphSnapshot): Connector[] {
|
|
20
|
+
const cached = connectorsBySnapshotCache.get(snapshot)
|
|
21
|
+
if (cached) return cached
|
|
22
|
+
|
|
23
|
+
const connectors = allConnectors(snapshot)
|
|
24
|
+
connectorsBySnapshotCache.set(snapshot, connectors)
|
|
25
|
+
return connectors
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function endpointPathCacheForSnapshot(snapshot: WorkspaceGraphSnapshot): Map<string, number[]> {
|
|
29
|
+
let cache = endpointPathCacheBySnapshot.get(snapshot)
|
|
30
|
+
if (!cache) {
|
|
31
|
+
cache = new Map()
|
|
32
|
+
endpointPathCacheBySnapshot.set(snapshot, cache)
|
|
33
|
+
}
|
|
34
|
+
return cache
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function endpointPathCacheKey(ownerViewId: number, elementId: number): string {
|
|
38
|
+
return `${ownerViewId}:${elementId}`
|
|
39
|
+
}
|
|
40
|
+
|
|
15
41
|
function firstPlacementForElement(snapshot: WorkspaceGraphSnapshot, elementId: number): GraphPlacementRef | null {
|
|
16
42
|
return snapshot.placementsByElementId[elementId]?.[0] ?? null
|
|
17
43
|
}
|
|
@@ -514,95 +540,306 @@ export interface ZUIResolvedConnector {
|
|
|
514
540
|
direction: string
|
|
515
541
|
style: string
|
|
516
542
|
label: string
|
|
543
|
+
sourceDepth: number
|
|
544
|
+
targetDepth: number
|
|
545
|
+
maxDepth: number
|
|
546
|
+
details: ProxyConnectorDetails
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
export interface ZUIHiddenProxyBadge {
|
|
550
|
+
key: string
|
|
551
|
+
sourceAnchorElementId: number
|
|
552
|
+
targetAnchorElementId: number
|
|
553
|
+
sourceNodeId: string
|
|
554
|
+
targetNodeId: string
|
|
555
|
+
count: number
|
|
517
556
|
details: ProxyConnectorDetails
|
|
518
557
|
}
|
|
519
558
|
|
|
559
|
+
export interface ZUIProxyResolution {
|
|
560
|
+
connectors: ZUIResolvedConnector[]
|
|
561
|
+
hiddenBadges: ZUIHiddenProxyBadge[]
|
|
562
|
+
omittedConnectorCount: number
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
export interface ZUIViewportBounds {
|
|
566
|
+
minX: number
|
|
567
|
+
minY: number
|
|
568
|
+
maxX: number
|
|
569
|
+
maxY: number
|
|
570
|
+
centerX: number
|
|
571
|
+
centerY: number
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
export interface ZUIConnectorAnchorInfo {
|
|
575
|
+
nodeId: string
|
|
576
|
+
worldX: number
|
|
577
|
+
worldY: number
|
|
578
|
+
worldW: number
|
|
579
|
+
worldH: number
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
export interface ResolveZUIProxyConnectorOptions {
|
|
583
|
+
viewport?: ZUIViewportBounds | null
|
|
584
|
+
anchorsByElementId?: Map<number, ZUIConnectorAnchorInfo>
|
|
585
|
+
connectorPriority?: CrossBranchConnectorPriority
|
|
586
|
+
}
|
|
587
|
+
|
|
520
588
|
function endpointPathForOwnerView(snapshot: WorkspaceGraphSnapshot, ownerViewId: number, elementId: number): number[] {
|
|
589
|
+
const cache = endpointPathCacheForSnapshot(snapshot)
|
|
590
|
+
const key = endpointPathCacheKey(ownerViewId, elementId)
|
|
591
|
+
const cached = cache.get(key)
|
|
592
|
+
if (cached) return cached
|
|
593
|
+
|
|
521
594
|
const placement = chooseBestPlacement(snapshot, elementId, ownerViewId, ownerViewId)
|
|
522
|
-
if (!placement)
|
|
595
|
+
if (!placement) {
|
|
596
|
+
const path = [elementId]
|
|
597
|
+
cache.set(key, path)
|
|
598
|
+
return path
|
|
599
|
+
}
|
|
523
600
|
const owners = relativeOwnerElementPath(snapshot, snapshot.ancestorsByViewId[placement.viewId]?.[0] ?? placement.viewId, placement.viewId)
|
|
524
601
|
const path = [...owners]
|
|
525
602
|
if (path[path.length - 1] !== elementId) path.push(elementId)
|
|
526
|
-
|
|
603
|
+
const resolvedPath = path.length > 0 ? path : [elementId]
|
|
604
|
+
cache.set(key, resolvedPath)
|
|
605
|
+
return resolvedPath
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
interface ZUIEndpointCandidate {
|
|
609
|
+
actualElementId: number
|
|
610
|
+
actualElementName: string
|
|
611
|
+
anchorElementId: number
|
|
612
|
+
anchorElementName: string
|
|
613
|
+
anchorViewId: number | null
|
|
614
|
+
anchorViewName: string | null
|
|
615
|
+
placementViewId: number | null
|
|
616
|
+
placementViewName: string | null
|
|
617
|
+
depth: number
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function visibleEndpointCandidates(
|
|
621
|
+
snapshot: WorkspaceGraphSnapshot,
|
|
622
|
+
ownerViewId: number,
|
|
623
|
+
actualElementId: number,
|
|
624
|
+
visibleElements: Set<number>,
|
|
625
|
+
): ZUIEndpointCandidate[] {
|
|
626
|
+
const path = endpointPathForOwnerView(snapshot, ownerViewId, actualElementId)
|
|
627
|
+
const visibleIndexes = path
|
|
628
|
+
.map((elementId, index) => visibleElements.has(elementId) ? index : -1)
|
|
629
|
+
.filter((index) => index >= 0)
|
|
630
|
+
|
|
631
|
+
if (visibleIndexes.length === 0) return []
|
|
632
|
+
|
|
633
|
+
const actualElementName = firstPlacementForElement(snapshot, actualElementId)?.element.name ?? `Element ${actualElementId}`
|
|
634
|
+
const deepestVisibleIndex = visibleIndexes[visibleIndexes.length - 1]
|
|
635
|
+
const candidateIndexes = [deepestVisibleIndex]
|
|
636
|
+
if (visibleIndexes.length >= 2) candidateIndexes.push(visibleIndexes[visibleIndexes.length - 2])
|
|
637
|
+
|
|
638
|
+
return candidateIndexes.map((pathIndex) => {
|
|
639
|
+
const anchorElementId = path[pathIndex]
|
|
640
|
+
const anchorPlacement = firstPlacementForElement(snapshot, anchorElementId)
|
|
641
|
+
return {
|
|
642
|
+
actualElementId,
|
|
643
|
+
actualElementName,
|
|
644
|
+
anchorElementId,
|
|
645
|
+
anchorElementName: anchorPlacement?.element.name ?? `Element ${anchorElementId}`,
|
|
646
|
+
anchorViewId: anchorPlacement?.viewId ?? ownerViewId,
|
|
647
|
+
anchorViewName: anchorPlacement?.viewName ?? viewName(snapshot, ownerViewId),
|
|
648
|
+
placementViewId: ownerViewId,
|
|
649
|
+
placementViewName: viewName(snapshot, ownerViewId),
|
|
650
|
+
depth: Math.max(0, path.length - 1 - pathIndex),
|
|
651
|
+
}
|
|
652
|
+
})
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function isNativelyRenderedInZUI(
|
|
656
|
+
connector: Connector,
|
|
657
|
+
sourceAnchorElementId: number,
|
|
658
|
+
targetAnchorElementId: number,
|
|
659
|
+
visibleNodeIdsByElementId: Map<number, string>,
|
|
660
|
+
): boolean {
|
|
661
|
+
return visibleNodeIdsByElementId.get(sourceAnchorElementId) === `d${connector.view_id}-o${sourceAnchorElementId}` &&
|
|
662
|
+
visibleNodeIdsByElementId.get(targetAnchorElementId) === `d${connector.view_id}-o${targetAnchorElementId}`
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function visibleEndpointCandidateCacheKey(ownerViewId: number, actualElementId: number): string {
|
|
666
|
+
return `${ownerViewId}:${actualElementId}`
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function anchorCenter(anchor: ZUIConnectorAnchorInfo) {
|
|
670
|
+
return {
|
|
671
|
+
x: anchor.worldX + anchor.worldW / 2,
|
|
672
|
+
y: anchor.worldY + anchor.worldH / 2,
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function anchorIsInViewport(anchor: ZUIConnectorAnchorInfo, viewport: ZUIViewportBounds): boolean {
|
|
677
|
+
const center = anchorCenter(anchor)
|
|
678
|
+
return center.x >= viewport.minX &&
|
|
679
|
+
center.x <= viewport.maxX &&
|
|
680
|
+
center.y >= viewport.minY &&
|
|
681
|
+
center.y <= viewport.maxY
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function normalizedDistanceToViewportCenter(anchor: ZUIConnectorAnchorInfo, viewport: ZUIViewportBounds): number {
|
|
685
|
+
const center = anchorCenter(anchor)
|
|
686
|
+
const dx = center.x - viewport.centerX
|
|
687
|
+
const dy = center.y - viewport.centerY
|
|
688
|
+
const diagonal = Math.max(1, Math.hypot(viewport.maxX - viewport.minX, viewport.maxY - viewport.minY))
|
|
689
|
+
return Math.hypot(dx, dy) / diagonal
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function viewportPriorityScore(
|
|
693
|
+
connector: ZUIResolvedConnector,
|
|
694
|
+
options: ResolveZUIProxyConnectorOptions | undefined,
|
|
695
|
+
): number {
|
|
696
|
+
const viewport = options?.viewport
|
|
697
|
+
const anchors = options?.anchorsByElementId
|
|
698
|
+
const source = anchors?.get(connector.sourceAnchorElementId)
|
|
699
|
+
const target = anchors?.get(connector.targetAnchorElementId)
|
|
700
|
+
if (!viewport || !source || !target) {
|
|
701
|
+
return connector.maxDepth * 100 + connector.sourceDepth + connector.targetDepth
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const sourceDistance = normalizedDistanceToViewportCenter(source, viewport)
|
|
705
|
+
const targetDistance = normalizedDistanceToViewportCenter(target, viewport)
|
|
706
|
+
const nearDistance = Math.min(sourceDistance, targetDistance)
|
|
707
|
+
const farDistance = Math.max(sourceDistance, targetDistance)
|
|
708
|
+
const sourceInViewport = anchorIsInViewport(source, viewport)
|
|
709
|
+
const targetInViewport = anchorIsInViewport(target, viewport)
|
|
710
|
+
const inViewportCount = Number(sourceInViewport) + Number(targetInViewport)
|
|
711
|
+
|
|
712
|
+
if (connector.details.connectors.length === 0) return Number.MAX_SAFE_INTEGER
|
|
713
|
+
|
|
714
|
+
if (options?.connectorPriority === 'internal') {
|
|
715
|
+
return (sourceDistance + targetDistance) * 1000 + farDistance * 400 - inViewportCount * 250
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
return nearDistance * 1000 - farDistance * 320 - (inViewportCount > 0 ? 300 : 0) + (inViewportCount === 2 ? 160 : 0)
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function connectorTouchesViewport(
|
|
722
|
+
connector: ZUIResolvedConnector,
|
|
723
|
+
options: ResolveZUIProxyConnectorOptions | undefined,
|
|
724
|
+
): boolean {
|
|
725
|
+
const viewport = options?.viewport
|
|
726
|
+
const anchors = options?.anchorsByElementId
|
|
727
|
+
if (!viewport || !anchors) return true
|
|
728
|
+
const source = anchors.get(connector.sourceAnchorElementId)
|
|
729
|
+
const target = anchors.get(connector.targetAnchorElementId)
|
|
730
|
+
if (!source || !target) return false
|
|
731
|
+
return anchorIsInViewport(source, viewport) || anchorIsInViewport(target, viewport)
|
|
527
732
|
}
|
|
528
733
|
|
|
529
734
|
export function resolveZUIProxyConnectors(
|
|
530
735
|
snapshot: WorkspaceGraphSnapshot | null,
|
|
531
736
|
visibleNodeIdsByElementId: Map<number, string>,
|
|
532
737
|
settings: CrossBranchContextSettings,
|
|
533
|
-
|
|
534
|
-
|
|
738
|
+
options?: ResolveZUIProxyConnectorOptions,
|
|
739
|
+
): ZUIProxyResolution {
|
|
740
|
+
if (!snapshot || !settings.enabled || visibleNodeIdsByElementId.size === 0) {
|
|
741
|
+
return { connectors: [], hiddenBadges: [], omittedConnectorCount: 0 }
|
|
742
|
+
}
|
|
535
743
|
|
|
536
744
|
const visibleElements = new Set(visibleNodeIdsByElementId.keys())
|
|
745
|
+
const connectors = connectorsForSnapshot(snapshot)
|
|
746
|
+
const endpointCandidateCache = new Map<string, ZUIEndpointCandidate[]>()
|
|
747
|
+
const endpointCandidates = (ownerViewId: number, actualElementId: number): ZUIEndpointCandidate[] => {
|
|
748
|
+
const key = visibleEndpointCandidateCacheKey(ownerViewId, actualElementId)
|
|
749
|
+
const cached = endpointCandidateCache.get(key)
|
|
750
|
+
if (cached) return cached
|
|
751
|
+
|
|
752
|
+
const candidates = visibleEndpointCandidates(snapshot, ownerViewId, actualElementId, visibleElements)
|
|
753
|
+
endpointCandidateCache.set(key, candidates)
|
|
754
|
+
return candidates
|
|
755
|
+
}
|
|
537
756
|
const grouped = new Map<string, ProxyConnectorLeaf[]>()
|
|
757
|
+
const nativeVisiblePairs = new Set<string>()
|
|
538
758
|
|
|
539
|
-
for (const connector of
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
if (sourceAnchorElementId == null || targetAnchorElementId == null) continue
|
|
546
|
-
if (sourceAnchorElementId === targetAnchorElementId) continue
|
|
547
|
-
// If both real endpoints are already visible, the normal edge renderer will
|
|
548
|
-
// draw this connector in-place. Only keep connectors that need anchoring to
|
|
549
|
-
// an ancestor/summary node.
|
|
550
|
-
if (
|
|
551
|
-
sourceAnchorElementId === connector.source_element_id &&
|
|
552
|
-
targetAnchorElementId === connector.target_element_id
|
|
553
|
-
) continue
|
|
554
|
-
|
|
555
|
-
const sourceDepth = Math.max(0, sourcePath.length - 1 - sourcePath.indexOf(sourceAnchorElementId))
|
|
556
|
-
const targetDepth = Math.max(0, targetPath.length - 1 - targetPath.indexOf(targetAnchorElementId))
|
|
557
|
-
if (settings.depth < CROSS_BRANCH_DEPTH_ALL && Math.max(sourceDepth, targetDepth) > settings.depth) continue
|
|
558
|
-
|
|
559
|
-
const sourceEndpoint: ProxyEndpoint = {
|
|
560
|
-
actualElementId: connector.source_element_id,
|
|
561
|
-
actualElementName: firstPlacementForElement(snapshot, connector.source_element_id)?.element.name ?? `Element ${connector.source_element_id}`,
|
|
562
|
-
anchorElementId: sourceAnchorElementId,
|
|
563
|
-
anchorElementName: firstPlacementForElement(snapshot, sourceAnchorElementId)?.element.name ?? `Element ${sourceAnchorElementId}`,
|
|
564
|
-
anchorViewId: firstPlacementForElement(snapshot, sourceAnchorElementId)?.viewId ?? connector.view_id,
|
|
565
|
-
anchorViewName: firstPlacementForElement(snapshot, sourceAnchorElementId)?.viewName ?? viewName(snapshot, connector.view_id),
|
|
566
|
-
placementViewId: connector.view_id,
|
|
567
|
-
placementViewName: viewName(snapshot, connector.view_id),
|
|
568
|
-
depth: sourceDepth,
|
|
569
|
-
externalToView: sourceAnchorElementId !== connector.source_element_id,
|
|
570
|
-
currentBranchElementId: null,
|
|
571
|
-
commonAncestorViewId: null,
|
|
572
|
-
commonAncestorViewName: null,
|
|
573
|
-
}
|
|
574
|
-
const targetEndpoint: ProxyEndpoint = {
|
|
575
|
-
actualElementId: connector.target_element_id,
|
|
576
|
-
actualElementName: firstPlacementForElement(snapshot, connector.target_element_id)?.element.name ?? `Element ${connector.target_element_id}`,
|
|
577
|
-
anchorElementId: targetAnchorElementId,
|
|
578
|
-
anchorElementName: firstPlacementForElement(snapshot, targetAnchorElementId)?.element.name ?? `Element ${targetAnchorElementId}`,
|
|
579
|
-
anchorViewId: firstPlacementForElement(snapshot, targetAnchorElementId)?.viewId ?? connector.view_id,
|
|
580
|
-
anchorViewName: firstPlacementForElement(snapshot, targetAnchorElementId)?.viewName ?? viewName(snapshot, connector.view_id),
|
|
581
|
-
placementViewId: connector.view_id,
|
|
582
|
-
placementViewName: viewName(snapshot, connector.view_id),
|
|
583
|
-
depth: targetDepth,
|
|
584
|
-
externalToView: targetAnchorElementId !== connector.target_element_id,
|
|
585
|
-
currentBranchElementId: null,
|
|
586
|
-
commonAncestorViewId: null,
|
|
587
|
-
commonAncestorViewName: null,
|
|
588
|
-
}
|
|
759
|
+
for (const connector of connectors) {
|
|
760
|
+
if (!visibleElements.has(connector.source_element_id) || !visibleElements.has(connector.target_element_id)) continue
|
|
761
|
+
if (!isNativelyRenderedInZUI(connector, connector.source_element_id, connector.target_element_id, visibleNodeIdsByElementId)) continue
|
|
762
|
+
const [leftAnchorElementId, rightAnchorElementId] = canonicalPairElements(connector.source_element_id, connector.target_element_id)
|
|
763
|
+
nativeVisiblePairs.add([leftAnchorElementId, rightAnchorElementId].join('::'))
|
|
764
|
+
}
|
|
589
765
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
766
|
+
for (const connector of connectors) {
|
|
767
|
+
const sourceCandidates = endpointCandidates(connector.view_id, connector.source_element_id)
|
|
768
|
+
const targetCandidates = endpointCandidates(connector.view_id, connector.target_element_id)
|
|
769
|
+
const seenPairsForConnector = new Set<string>()
|
|
770
|
+
|
|
771
|
+
for (const sourceCandidate of sourceCandidates) {
|
|
772
|
+
for (const targetCandidate of targetCandidates) {
|
|
773
|
+
if (sourceCandidate.anchorElementId === targetCandidate.anchorElementId) continue
|
|
774
|
+
if (
|
|
775
|
+
sourceCandidate.actualElementId === sourceCandidate.anchorElementId &&
|
|
776
|
+
targetCandidate.actualElementId === targetCandidate.anchorElementId &&
|
|
777
|
+
isNativelyRenderedInZUI(
|
|
778
|
+
connector,
|
|
779
|
+
sourceCandidate.anchorElementId,
|
|
780
|
+
targetCandidate.anchorElementId,
|
|
781
|
+
visibleNodeIdsByElementId,
|
|
782
|
+
)
|
|
783
|
+
) {
|
|
784
|
+
continue
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
const sourceEndpoint: ProxyEndpoint = {
|
|
788
|
+
actualElementId: sourceCandidate.actualElementId,
|
|
789
|
+
actualElementName: sourceCandidate.actualElementName,
|
|
790
|
+
anchorElementId: sourceCandidate.anchorElementId,
|
|
791
|
+
anchorElementName: sourceCandidate.anchorElementName,
|
|
792
|
+
anchorViewId: sourceCandidate.anchorViewId,
|
|
793
|
+
anchorViewName: sourceCandidate.anchorViewName,
|
|
794
|
+
placementViewId: sourceCandidate.placementViewId,
|
|
795
|
+
placementViewName: sourceCandidate.placementViewName,
|
|
796
|
+
depth: sourceCandidate.depth,
|
|
797
|
+
externalToView: sourceCandidate.anchorElementId !== sourceCandidate.actualElementId,
|
|
798
|
+
currentBranchElementId: null,
|
|
799
|
+
commonAncestorViewId: null,
|
|
800
|
+
commonAncestorViewName: null,
|
|
801
|
+
}
|
|
802
|
+
const targetEndpoint: ProxyEndpoint = {
|
|
803
|
+
actualElementId: targetCandidate.actualElementId,
|
|
804
|
+
actualElementName: targetCandidate.actualElementName,
|
|
805
|
+
anchorElementId: targetCandidate.anchorElementId,
|
|
806
|
+
anchorElementName: targetCandidate.anchorElementName,
|
|
807
|
+
anchorViewId: targetCandidate.anchorViewId,
|
|
808
|
+
anchorViewName: targetCandidate.anchorViewName,
|
|
809
|
+
placementViewId: targetCandidate.placementViewId,
|
|
810
|
+
placementViewName: targetCandidate.placementViewName,
|
|
811
|
+
depth: targetCandidate.depth,
|
|
812
|
+
externalToView: targetCandidate.anchorElementId !== targetCandidate.actualElementId,
|
|
813
|
+
currentBranchElementId: null,
|
|
814
|
+
commonAncestorViewId: null,
|
|
815
|
+
commonAncestorViewName: null,
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
const leaf: ProxyConnectorLeaf = {
|
|
819
|
+
connector,
|
|
820
|
+
ownerViewId: connector.view_id,
|
|
821
|
+
ownerViewName: viewName(snapshot, connector.view_id) ?? `View ${connector.view_id}`,
|
|
822
|
+
source: sourceEndpoint,
|
|
823
|
+
target: targetEndpoint,
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
const [leftAnchorElementId, rightAnchorElementId] = canonicalPairElements(
|
|
827
|
+
sourceCandidate.anchorElementId,
|
|
828
|
+
targetCandidate.anchorElementId,
|
|
829
|
+
)
|
|
830
|
+
const key = [leftAnchorElementId, rightAnchorElementId].join('::')
|
|
831
|
+
const pairKey = `${connector.id}:${key}`
|
|
832
|
+
if (seenPairsForConnector.has(pairKey)) continue
|
|
833
|
+
seenPairsForConnector.add(pairKey)
|
|
834
|
+
const existing = grouped.get(key)
|
|
835
|
+
if (existing) existing.push(leaf)
|
|
836
|
+
else grouped.set(key, [leaf])
|
|
837
|
+
}
|
|
596
838
|
}
|
|
597
|
-
|
|
598
|
-
const [leftAnchorElementId, rightAnchorElementId] = canonicalPairElements(sourceAnchorElementId, targetAnchorElementId)
|
|
599
|
-
const key = [leftAnchorElementId, rightAnchorElementId].join('::')
|
|
600
|
-
const existing = grouped.get(key)
|
|
601
|
-
if (existing) existing.push(leaf)
|
|
602
|
-
else grouped.set(key, [leaf])
|
|
603
839
|
}
|
|
604
840
|
|
|
605
841
|
const resolved: ZUIResolvedConnector[] = []
|
|
842
|
+
const hiddenBadges: ZUIHiddenProxyBadge[] = []
|
|
606
843
|
for (const [key, leaves] of grouped) {
|
|
607
844
|
const [first] = leaves
|
|
608
845
|
const { ownerViewIds, ownerViewNames } = ownerViewsFromLeaves(leaves)
|
|
@@ -611,6 +848,8 @@ export function resolveZUIProxyConnectors(
|
|
|
611
848
|
first.target.anchorElementId,
|
|
612
849
|
)
|
|
613
850
|
const canonicalFirstIsSource = canonicalSourceAnchorElementId === first.source.anchorElementId
|
|
851
|
+
const canonicalSourceDepth = canonicalFirstIsSource ? first.source.depth : first.target.depth
|
|
852
|
+
const canonicalTargetDepth = canonicalFirstIsSource ? first.target.depth : first.source.depth
|
|
614
853
|
const details: ProxyConnectorDetails = {
|
|
615
854
|
key,
|
|
616
855
|
label: proxyDisplayLabel(leaves),
|
|
@@ -624,6 +863,30 @@ export function resolveZUIProxyConnectors(
|
|
|
624
863
|
connectors: leaves,
|
|
625
864
|
}
|
|
626
865
|
|
|
866
|
+
const isDirectChildBadgeOnly = leaves.every((leaf) => {
|
|
867
|
+
if (Math.max(leaf.source.depth, leaf.target.depth) !== 1) return false
|
|
868
|
+
const sourceOk = leaf.source.actualElementId === leaf.source.anchorElementId ||
|
|
869
|
+
endpointCandidates(leaf.ownerViewId, leaf.source.actualElementId)[0]?.anchorElementId === leaf.source.anchorElementId
|
|
870
|
+
const targetOk = leaf.target.actualElementId === leaf.target.anchorElementId ||
|
|
871
|
+
endpointCandidates(leaf.ownerViewId, leaf.target.actualElementId)[0]?.anchorElementId === leaf.target.anchorElementId
|
|
872
|
+
return sourceOk && targetOk
|
|
873
|
+
})
|
|
874
|
+
const pairHasNativeDirect = nativeVisiblePairs.has(key)
|
|
875
|
+
if (pairHasNativeDirect) {
|
|
876
|
+
if (isDirectChildBadgeOnly) {
|
|
877
|
+
hiddenBadges.push({
|
|
878
|
+
key: `badge:${key}`,
|
|
879
|
+
sourceAnchorElementId: canonicalSourceAnchorElementId,
|
|
880
|
+
targetAnchorElementId: canonicalTargetAnchorElementId,
|
|
881
|
+
sourceNodeId: visibleNodeIdsByElementId.get(canonicalSourceAnchorElementId) ?? '',
|
|
882
|
+
targetNodeId: visibleNodeIdsByElementId.get(canonicalTargetAnchorElementId) ?? '',
|
|
883
|
+
count: details.count,
|
|
884
|
+
details,
|
|
885
|
+
})
|
|
886
|
+
}
|
|
887
|
+
continue
|
|
888
|
+
}
|
|
889
|
+
|
|
627
890
|
resolved.push({
|
|
628
891
|
key,
|
|
629
892
|
sourceElementId: canonicalFirstIsSource ? first.source.actualElementId : first.target.actualElementId,
|
|
@@ -635,9 +898,46 @@ export function resolveZUIProxyConnectors(
|
|
|
635
898
|
direction: 'merged',
|
|
636
899
|
style: first.connector.style || 'bezier',
|
|
637
900
|
label: details.label,
|
|
901
|
+
sourceDepth: canonicalSourceDepth,
|
|
902
|
+
targetDepth: canonicalTargetDepth,
|
|
903
|
+
maxDepth: Math.max(canonicalSourceDepth, canonicalTargetDepth),
|
|
638
904
|
details,
|
|
639
905
|
})
|
|
640
906
|
}
|
|
641
907
|
|
|
642
|
-
|
|
908
|
+
const visibleResolved = resolved
|
|
909
|
+
.filter((connector) => connector.sourceNodeId && connector.targetNodeId)
|
|
910
|
+
.filter((connector) => connectorTouchesViewport(connector, options))
|
|
911
|
+
.sort((left, right) => {
|
|
912
|
+
const scoreDelta = viewportPriorityScore(left, {
|
|
913
|
+
...options,
|
|
914
|
+
connectorPriority: settings.connectorPriority,
|
|
915
|
+
}) - viewportPriorityScore(right, {
|
|
916
|
+
...options,
|
|
917
|
+
connectorPriority: settings.connectorPriority,
|
|
918
|
+
})
|
|
919
|
+
if (scoreDelta !== 0) return scoreDelta
|
|
920
|
+
if (right.details.count !== left.details.count) return right.details.count - left.details.count
|
|
921
|
+
if (left.maxDepth !== right.maxDepth) return left.maxDepth - right.maxDepth
|
|
922
|
+
const depthDelta = (left.sourceDepth + left.targetDepth) - (right.sourceDepth + right.targetDepth)
|
|
923
|
+
if (depthDelta !== 0) return depthDelta
|
|
924
|
+
return left.key.localeCompare(right.key)
|
|
925
|
+
})
|
|
926
|
+
const maxGroups = settings.connectorBudget ?? settings.maxProxyConnectorGroups ?? CROSS_BRANCH_CONNECTOR_BUDGET_DEFAULT
|
|
927
|
+
const budgetedResolved = maxGroups > 0 ? visibleResolved.slice(0, maxGroups) : visibleResolved
|
|
928
|
+
const omittedConnectorIds = new Set<number>()
|
|
929
|
+
if (maxGroups > 0) {
|
|
930
|
+
for (const connector of visibleResolved.slice(maxGroups)) {
|
|
931
|
+
for (const leaf of connector.details.connectors) {
|
|
932
|
+
omittedConnectorIds.add(leaf.connector.id)
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
const omittedConnectorCount = omittedConnectorIds.size
|
|
937
|
+
|
|
938
|
+
return {
|
|
939
|
+
connectors: budgetedResolved,
|
|
940
|
+
hiddenBadges: hiddenBadges.filter((badge) => badge.sourceNodeId && badge.targetNodeId),
|
|
941
|
+
omittedConnectorCount,
|
|
942
|
+
}
|
|
643
943
|
}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
2
|
-
import type { CrossBranchContextSettings, CrossBranchSurface } from './types'
|
|
3
|
-
import {
|
|
2
|
+
import type { CrossBranchConnectorPriority, CrossBranchContextSettings, CrossBranchSurface } from './types'
|
|
3
|
+
import {
|
|
4
|
+
CROSS_BRANCH_CONNECTOR_BUDGET_DEFAULT,
|
|
5
|
+
CROSS_BRANCH_CONNECTOR_BUDGET_MAX,
|
|
6
|
+
CROSS_BRANCH_CONNECTOR_BUDGET_MIN,
|
|
7
|
+
CROSS_BRANCH_DEPTH_ALL,
|
|
8
|
+
} from './types'
|
|
4
9
|
|
|
5
10
|
const STORAGE_PREFIX = 'diag:cross-branch'
|
|
11
|
+
export const DEFAULT_MIN_CONNECTOR_ANCHOR_ALPHA = 0.35
|
|
12
|
+
export const DEFAULT_MAX_PROXY_CONNECTOR_GROUPS = 32
|
|
13
|
+
export const DEFAULT_CONNECTOR_PRIORITY: CrossBranchConnectorPriority = 'external'
|
|
6
14
|
|
|
7
15
|
function storageKey(surface: CrossBranchSurface) {
|
|
8
16
|
return `${STORAGE_PREFIX}:${surface}`
|
|
@@ -12,9 +20,25 @@ function defaultSettings(surface: CrossBranchSurface): CrossBranchContextSetting
|
|
|
12
20
|
return {
|
|
13
21
|
enabled: surface !== 'zui-shared',
|
|
14
22
|
depth: CROSS_BRANCH_DEPTH_ALL,
|
|
23
|
+
connectorBudget: CROSS_BRANCH_CONNECTOR_BUDGET_DEFAULT,
|
|
24
|
+
connectorPriority: DEFAULT_CONNECTOR_PRIORITY,
|
|
25
|
+
minConnectorAnchorAlpha: DEFAULT_MIN_CONNECTOR_ANCHOR_ALPHA,
|
|
26
|
+
maxProxyConnectorGroups: DEFAULT_MAX_PROXY_CONNECTOR_GROUPS,
|
|
15
27
|
}
|
|
16
28
|
}
|
|
17
29
|
|
|
30
|
+
function normalizeConnectorBudget(value: unknown, fallback: number): number {
|
|
31
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) return fallback
|
|
32
|
+
return Math.max(
|
|
33
|
+
CROSS_BRANCH_CONNECTOR_BUDGET_MIN,
|
|
34
|
+
Math.min(CROSS_BRANCH_CONNECTOR_BUDGET_MAX, Math.round(value)),
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function normalizeConnectorPriority(value: unknown, fallback: CrossBranchConnectorPriority): CrossBranchConnectorPriority {
|
|
39
|
+
return value === 'internal' || value === 'external' ? value : fallback
|
|
40
|
+
}
|
|
41
|
+
|
|
18
42
|
function readSettings(surface: CrossBranchSurface): CrossBranchContextSettings {
|
|
19
43
|
const defaults = defaultSettings(surface)
|
|
20
44
|
if (typeof window === 'undefined') return defaults
|
|
@@ -25,6 +49,14 @@ function readSettings(surface: CrossBranchSurface): CrossBranchContextSettings {
|
|
|
25
49
|
return {
|
|
26
50
|
enabled: parsed.enabled ?? defaults.enabled,
|
|
27
51
|
depth: typeof parsed.depth === 'number' ? parsed.depth : CROSS_BRANCH_DEPTH_ALL,
|
|
52
|
+
connectorBudget: normalizeConnectorBudget(parsed.connectorBudget, defaults.connectorBudget),
|
|
53
|
+
connectorPriority: normalizeConnectorPriority(parsed.connectorPriority, defaults.connectorPriority),
|
|
54
|
+
minConnectorAnchorAlpha: typeof parsed.minConnectorAnchorAlpha === 'number'
|
|
55
|
+
? parsed.minConnectorAnchorAlpha
|
|
56
|
+
: defaults.minConnectorAnchorAlpha,
|
|
57
|
+
maxProxyConnectorGroups: typeof parsed.maxProxyConnectorGroups === 'number'
|
|
58
|
+
? parsed.maxProxyConnectorGroups
|
|
59
|
+
: defaults.maxProxyConnectorGroups,
|
|
28
60
|
}
|
|
29
61
|
} catch {
|
|
30
62
|
return defaults
|
|
@@ -51,9 +83,23 @@ export function useCrossBranchContextSettings(surface: CrossBranchSurface) {
|
|
|
51
83
|
setSettings((prev) => ({ ...prev, depth }))
|
|
52
84
|
}, [])
|
|
53
85
|
|
|
86
|
+
const setConnectorBudget = useCallback((connectorBudget: number) => {
|
|
87
|
+
setSettings((prev) => ({
|
|
88
|
+
...prev,
|
|
89
|
+
connectorBudget: normalizeConnectorBudget(connectorBudget, prev.connectorBudget),
|
|
90
|
+
maxProxyConnectorGroups: normalizeConnectorBudget(connectorBudget, prev.connectorBudget),
|
|
91
|
+
}))
|
|
92
|
+
}, [])
|
|
93
|
+
|
|
94
|
+
const setConnectorPriority = useCallback((connectorPriority: CrossBranchConnectorPriority) => {
|
|
95
|
+
setSettings((prev) => ({ ...prev, connectorPriority }))
|
|
96
|
+
}, [])
|
|
97
|
+
|
|
54
98
|
return useMemo(() => ({
|
|
55
99
|
settings,
|
|
56
100
|
setEnabled,
|
|
57
101
|
setDepth,
|
|
58
|
-
|
|
102
|
+
setConnectorBudget,
|
|
103
|
+
setConnectorPriority,
|
|
104
|
+
}), [settings, setEnabled, setDepth, setConnectorBudget, setConnectorPriority])
|
|
59
105
|
}
|
package/src/crossBranch/types.ts
CHANGED
|
@@ -3,12 +3,21 @@ import type { Connector, ExploreData, PlacedElement, ViewTreeNode } from '../typ
|
|
|
3
3
|
export const CROSS_BRANCH_DEPTH_ALL = 5
|
|
4
4
|
export const CROSS_BRANCH_DEPTH_MIN = 1
|
|
5
5
|
export const CROSS_BRANCH_DEPTH_MAX = CROSS_BRANCH_DEPTH_ALL
|
|
6
|
+
export const CROSS_BRANCH_CONNECTOR_BUDGET_MIN = 10
|
|
7
|
+
export const CROSS_BRANCH_CONNECTOR_BUDGET_MAX = 200
|
|
8
|
+
export const CROSS_BRANCH_CONNECTOR_BUDGET_DEFAULT = 50
|
|
9
|
+
|
|
10
|
+
export type CrossBranchConnectorPriority = 'external' | 'internal'
|
|
6
11
|
|
|
7
12
|
export type CrossBranchSurface = 'editor' | 'zui' | 'zui-shared'
|
|
8
13
|
|
|
9
14
|
export interface CrossBranchContextSettings {
|
|
10
15
|
enabled: boolean
|
|
11
16
|
depth: number
|
|
17
|
+
connectorBudget: number
|
|
18
|
+
connectorPriority: CrossBranchConnectorPriority
|
|
19
|
+
minConnectorAnchorAlpha?: number
|
|
20
|
+
maxProxyConnectorGroups?: number
|
|
12
21
|
}
|
|
13
22
|
|
|
14
23
|
export interface GraphPlacementRef {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import type { LibraryElement } from '../types'
|
|
3
|
+
import { api } from '../api/client'
|
|
4
|
+
|
|
5
|
+
export interface ElementSearchResult {
|
|
6
|
+
query: string
|
|
7
|
+
setQuery: (q: string) => void
|
|
8
|
+
remoteElements: LibraryElement[]
|
|
9
|
+
fetching: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useElementSearch(): ElementSearchResult {
|
|
13
|
+
const [query, setQuery] = useState('')
|
|
14
|
+
const [remoteElements, setRemoteElements] = useState<LibraryElement[]>([])
|
|
15
|
+
const [fetching, setFetching] = useState(false)
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const trimmed = query.trim()
|
|
19
|
+
if (!trimmed) {
|
|
20
|
+
setRemoteElements([])
|
|
21
|
+
setFetching(false)
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
let cancelled = false
|
|
25
|
+
setFetching(true)
|
|
26
|
+
const timer = setTimeout(() => {
|
|
27
|
+
api.elements.list({ limit: 0, offset: 0, search: trimmed })
|
|
28
|
+
.then((items) => {
|
|
29
|
+
if (!cancelled) setRemoteElements(items)
|
|
30
|
+
})
|
|
31
|
+
.catch(() => {
|
|
32
|
+
if (!cancelled) setRemoteElements([])
|
|
33
|
+
})
|
|
34
|
+
.finally(() => {
|
|
35
|
+
if (!cancelled) setFetching(false)
|
|
36
|
+
})
|
|
37
|
+
}, 150)
|
|
38
|
+
return () => {
|
|
39
|
+
cancelled = true
|
|
40
|
+
clearTimeout(timer)
|
|
41
|
+
}
|
|
42
|
+
}, [query])
|
|
43
|
+
|
|
44
|
+
return { query, setQuery, remoteElements, fetching }
|
|
45
|
+
}
|
package/src/index.css
CHANGED
package/src/index.ts
CHANGED
|
@@ -94,6 +94,13 @@ export { default as theme } from './theme'
|
|
|
94
94
|
// ─── Contexts ────────────────────────────────────────────────────────────────
|
|
95
95
|
export { ThemeProvider, useAccentColor, useTheme } from './context/ThemeContext'
|
|
96
96
|
export { HeaderProvider, useSetHeader, useHeader } from './components/HeaderContext'
|
|
97
|
+
export {
|
|
98
|
+
WorkspaceVersionProvider,
|
|
99
|
+
buildWorkspaceVersionPreview,
|
|
100
|
+
useWorkspaceVersionPreview,
|
|
101
|
+
type WorkspaceVersionFollowTarget,
|
|
102
|
+
type WorkspaceVersionPreview,
|
|
103
|
+
} from './context/WorkspaceVersionContext'
|
|
97
104
|
|
|
98
105
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
99
106
|
export * from './types'
|