@visualli/core 0.1.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.
Files changed (43) hide show
  1. package/README.md +263 -0
  2. package/dist/index.cjs +2325 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +1181 -0
  5. package/dist/index.d.ts +1181 -0
  6. package/dist/index.js +2119 -0
  7. package/dist/index.js.map +1 -0
  8. package/package.json +80 -0
  9. package/src/animations/constants.ts +72 -0
  10. package/src/animations/easing.ts +70 -0
  11. package/src/animations/index.ts +2 -0
  12. package/src/config/blobShapes.ts +146 -0
  13. package/src/config/textScaling.ts +54 -0
  14. package/src/constants/design.ts +281 -0
  15. package/src/constants/index.ts +3 -0
  16. package/src/constants/performanceConstants.ts +157 -0
  17. package/src/constants/renderConfig.ts +134 -0
  18. package/src/index.ts +18 -0
  19. package/src/layout/circularLayout.ts +95 -0
  20. package/src/layout/index.ts +3 -0
  21. package/src/layout/layoutUtils.ts +132 -0
  22. package/src/layout/linearLayout.ts +84 -0
  23. package/src/parser/configUtils.ts +129 -0
  24. package/src/parser/index.ts +4 -0
  25. package/src/parser/middleware.ts +24 -0
  26. package/src/parser/mindmapUtils.ts +76 -0
  27. package/src/parser/visualliConverter.ts +328 -0
  28. package/src/parser/visualliParser.ts +191 -0
  29. package/src/performance/index.ts +1 -0
  30. package/src/performance/performanceMonitor.ts +145 -0
  31. package/src/rendering/culling.ts +71 -0
  32. package/src/spatial/index.ts +1 -0
  33. package/src/spatial/spatialIndex.ts +83 -0
  34. package/src/stores/index.ts +61 -0
  35. package/src/types/document.ts +15 -0
  36. package/src/types/index.ts +17 -0
  37. package/src/types/layer.ts +67 -0
  38. package/src/types/meta.ts +9 -0
  39. package/src/types/mindmap.ts +136 -0
  40. package/src/types/schema.ts +36 -0
  41. package/src/utils/navigation.ts +161 -0
  42. package/src/viewport/index.ts +1 -0
  43. package/src/viewport/viewportUtils.ts +195 -0
@@ -0,0 +1,145 @@
1
+ // ─── Performance Monitors ─────────────────────────────────────────────────────
2
+ //
3
+ // FPSMonitor – tracks frame rate via requestAnimationFrame (browser)
4
+ // MemoryMonitor – polls performance.memory (Chrome)
5
+ // PerformanceProfiler – mark / measure utility for arbitrary operations
6
+
7
+ import {
8
+ FPS_TARGET,
9
+ FPS_SAMPLE_WINDOW,
10
+ MEMORY_CHECK_INTERVAL,
11
+ } from '../constants/performanceConstants.js';
12
+
13
+ // ── FPS Monitor ───────────────────────────────────────────────────────────────
14
+
15
+ export class FPSMonitor {
16
+ private frameTimes: number[] = [];
17
+ private lastTime = 0;
18
+ private currentFPS = FPS_TARGET;
19
+ private rafId: number | null = null;
20
+
21
+ start(): void {
22
+ if (this.rafId !== null) return;
23
+
24
+ const measure = (ts: number) => {
25
+ if (this.lastTime > 0) {
26
+ this.frameTimes.push(ts - this.lastTime);
27
+ if (this.frameTimes.length > FPS_SAMPLE_WINDOW) this.frameTimes.shift();
28
+ this.currentFPS = this.calc();
29
+ }
30
+ this.lastTime = ts;
31
+ this.rafId = requestAnimationFrame(measure);
32
+ };
33
+
34
+ this.rafId = requestAnimationFrame(measure);
35
+ }
36
+
37
+ stop(): void {
38
+ if (this.rafId !== null) { cancelAnimationFrame(this.rafId); this.rafId = null; }
39
+ }
40
+
41
+ private calc(): number {
42
+ if (this.frameTimes.length === 0) return FPS_TARGET;
43
+ const avg = this.frameTimes.reduce((a, b) => a + b, 0) / this.frameTimes.length;
44
+ return Math.round(1000 / avg);
45
+ }
46
+
47
+ getFPS(): number { return this.currentFPS; }
48
+ getAverageFPS(): number { return this.currentFPS; }
49
+
50
+ getP95FrameTime(): number {
51
+ if (this.frameTimes.length === 0) return 0;
52
+ const sorted = [...this.frameTimes].sort((a, b) => a - b);
53
+ return sorted[Math.floor(sorted.length * 0.95)];
54
+ }
55
+
56
+ reset(): void { this.frameTimes = []; this.currentFPS = FPS_TARGET; }
57
+ }
58
+
59
+ // ── Memory Monitor ────────────────────────────────────────────────────────────
60
+
61
+ export interface MemoryStats {
62
+ usedJSHeapSize: number;
63
+ totalJSHeapSize: number;
64
+ jsHeapSizeLimit: number;
65
+ heapUsagePercent: number;
66
+ }
67
+
68
+ export class MemoryMonitor {
69
+ private timer: ReturnType<typeof setInterval> | null = null;
70
+ private current = 0;
71
+
72
+ start(onUpdate?: (mb: number) => void): void {
73
+ if (this.timer !== null) return;
74
+ this.timer = setInterval(() => {
75
+ this.current = this.measure();
76
+ onUpdate?.(this.current);
77
+ }, MEMORY_CHECK_INTERVAL);
78
+ }
79
+
80
+ stop(): void {
81
+ if (this.timer !== null) { clearInterval(this.timer); this.timer = null; }
82
+ }
83
+
84
+ private measure(): number {
85
+ const mem = (performance as unknown as Record<string, unknown>)['memory'] as Record<string, number> | undefined;
86
+ return mem ? mem['usedJSHeapSize'] / (1024 * 1024) : 0;
87
+ }
88
+
89
+ getMemoryMB(): number { return this.current; }
90
+
91
+ getStats(): MemoryStats | null {
92
+ const mem = (performance as unknown as Record<string, unknown>)['memory'] as Record<string, number> | undefined;
93
+ if (!mem) return null;
94
+ return {
95
+ usedJSHeapSize: mem['usedJSHeapSize'],
96
+ totalJSHeapSize: mem['totalJSHeapSize'],
97
+ jsHeapSizeLimit: mem['jsHeapSizeLimit'],
98
+ heapUsagePercent: (mem['usedJSHeapSize'] / mem['jsHeapSizeLimit']) * 100,
99
+ };
100
+ }
101
+
102
+ static isSupported(): boolean {
103
+ return !!(performance as unknown as Record<string, unknown>)['memory'];
104
+ }
105
+ }
106
+
107
+ // ── Performance Profiler ──────────────────────────────────────────────────────
108
+
109
+ export class PerformanceProfiler {
110
+ private marks: Map<string, number> = new Map();
111
+ private measures: Map<string, number[]> = new Map();
112
+
113
+ mark(label: string): void {
114
+ this.marks.set(label, performance.now());
115
+ }
116
+
117
+ measure(label: string): number {
118
+ const start = this.marks.get(label);
119
+ if (start === undefined) return 0;
120
+ const duration = performance.now() - start;
121
+ const list = this.measures.get(label) ?? [];
122
+ list.push(duration);
123
+ this.measures.set(label, list);
124
+ this.marks.delete(label);
125
+ return duration;
126
+ }
127
+
128
+ getAverage(label: string): number {
129
+ const list = this.measures.get(label);
130
+ if (!list?.length) return 0;
131
+ return list.reduce((a, b) => a + b, 0) / list.length;
132
+ }
133
+
134
+ getP95(label: string): number {
135
+ const list = this.measures.get(label);
136
+ if (!list?.length) return 0;
137
+ const sorted = [...list].sort((a, b) => a - b);
138
+ return sorted[Math.floor(sorted.length * 0.95)];
139
+ }
140
+
141
+ clear(label?: string): void {
142
+ if (label) { this.marks.delete(label); this.measures.delete(label); }
143
+ else { this.marks.clear(); this.measures.clear(); }
144
+ }
145
+ }
@@ -0,0 +1,71 @@
1
+ import type { FlatNode, ViewportState } from '../types/index.js';
2
+ import { RBushSpatialIndex } from '../spatial/spatialIndex.js';
3
+ import { calculateViewportBounds } from '../viewport/viewportUtils.js';
4
+
5
+ export interface CullingOptions {
6
+ nodes: FlatNode[];
7
+ viewport: ViewportState;
8
+ canvasWidth: number;
9
+ canvasHeight: number;
10
+ levelFilter?: number;
11
+ skipCulling?: boolean;
12
+ isDragging?: boolean;
13
+ spatialIndex?: RBushSpatialIndex | null;
14
+ spatialIndexThreshold?: number;
15
+ }
16
+
17
+ const DEFAULT_SPATIAL_INDEX_THRESHOLD = 200;
18
+
19
+ /**
20
+ * Returns the subset of nodes currently visible in the viewport.
21
+ * Framework-agnostic logic for culling and spatial indexing.
22
+ */
23
+ export function getViewportVisibleNodes(options: CullingOptions): FlatNode[] {
24
+ const {
25
+ nodes,
26
+ viewport,
27
+ canvasWidth,
28
+ canvasHeight,
29
+ levelFilter,
30
+ skipCulling = false,
31
+ isDragging = false,
32
+ spatialIndex,
33
+ spatialIndexThreshold = DEFAULT_SPATIAL_INDEX_THRESHOLD,
34
+ } = options;
35
+
36
+ const filtered = levelFilter !== undefined
37
+ ? nodes.filter(n => n.level === levelFilter)
38
+ : nodes;
39
+
40
+ if (filtered.length === 0) return [];
41
+
42
+ // During transitions or drags show all nodes to avoid partial layer appearance
43
+ if (skipCulling || isDragging) return filtered;
44
+
45
+ const bounds = calculateViewportBounds(viewport, canvasWidth, canvasHeight);
46
+
47
+ if (filtered.length < spatialIndexThreshold) {
48
+ return filtered.filter(n =>
49
+ n.x + n.width >= bounds.minX &&
50
+ n.x <= bounds.maxX &&
51
+ n.y + n.height >= bounds.minY &&
52
+ n.y <= bounds.maxY,
53
+ );
54
+ }
55
+
56
+ if (spatialIndex) {
57
+ const nodeIds = spatialIndex.query({
58
+ minX: bounds.minX, minY: bounds.minY, maxX: bounds.maxX, maxY: bounds.maxY,
59
+ });
60
+ const idSet = new Set(nodeIds);
61
+ return filtered.filter(n => idSet.has(n.id));
62
+ }
63
+
64
+ // Fallback to simple filter if no index provided but threshold exceeded
65
+ return filtered.filter(n =>
66
+ n.x + n.width >= bounds.minX &&
67
+ n.x <= bounds.maxX &&
68
+ n.y + n.height >= bounds.minY &&
69
+ n.y <= bounds.maxY,
70
+ );
71
+ }
@@ -0,0 +1 @@
1
+ export * from './spatialIndex.js';
@@ -0,0 +1,83 @@
1
+ // ─── Spatial Index ────────────────────────────────────────────────────────────
2
+ //
3
+ // RBush-backed R-tree for O(log n) viewport culling queries.
4
+ // Wraps the `rbush` library with a typed, domain-specific interface.
5
+
6
+ import RBush from 'rbush';
7
+
8
+ export interface BoundingBox {
9
+ minX: number;
10
+ minY: number;
11
+ maxX: number;
12
+ maxY: number;
13
+ }
14
+
15
+ interface SpatialItem extends BoundingBox {
16
+ nodeId: string;
17
+ }
18
+
19
+ export interface ISpatialIndex {
20
+ insert(nodeId: string, bounds: BoundingBox): void;
21
+ remove(nodeId: string, bounds: BoundingBox): void;
22
+ update(nodeId: string, oldBounds: BoundingBox, newBounds: BoundingBox): void;
23
+ query(bounds: BoundingBox): string[];
24
+ clear(): void;
25
+ size(): number;
26
+ bulkLoad(items: Array<{ nodeId: string; bounds: BoundingBox }>): void;
27
+ }
28
+
29
+ /**
30
+ * RBush-based spatial index for efficient viewport culling.
31
+ *
32
+ * - Insert / remove / update: O(log n)
33
+ * - Query: O(log n + k) where k = result count
34
+ * - Bulk load: O(n log n) — use instead of repeated inserts for initial load
35
+ */
36
+ export class RBushSpatialIndex implements ISpatialIndex {
37
+ private tree: RBush<SpatialItem>;
38
+ private cache: Map<string, BoundingBox>;
39
+
40
+ constructor() {
41
+ this.tree = new RBush<SpatialItem>();
42
+ this.cache = new Map();
43
+ }
44
+
45
+ insert(nodeId: string, bounds: BoundingBox): void {
46
+ this.tree.insert({ ...bounds, nodeId });
47
+ this.cache.set(nodeId, bounds);
48
+ }
49
+
50
+ remove(nodeId: string, bounds: BoundingBox): void {
51
+ this.tree.remove({ ...bounds, nodeId }, (a, b) => a.nodeId === b.nodeId);
52
+ this.cache.delete(nodeId);
53
+ }
54
+
55
+ update(nodeId: string, oldBounds: BoundingBox, newBounds: BoundingBox): void {
56
+ this.remove(nodeId, oldBounds);
57
+ this.insert(nodeId, newBounds);
58
+ }
59
+
60
+ query(bounds: BoundingBox): string[] {
61
+ return this.tree.search(bounds).map(item => item.nodeId);
62
+ }
63
+
64
+ clear(): void {
65
+ this.tree.clear();
66
+ this.cache.clear();
67
+ }
68
+
69
+ size(): number {
70
+ return this.cache.size;
71
+ }
72
+
73
+ bulkLoad(items: Array<{ nodeId: string; bounds: BoundingBox }>): void {
74
+ const rbushItems = items.map(({ nodeId, bounds }) => ({ ...bounds, nodeId }));
75
+ this.tree.load(rbushItems);
76
+ for (const { nodeId, bounds } of items) this.cache.set(nodeId, bounds);
77
+ }
78
+
79
+ /** Return the cached bounding box for a node (undefined if not indexed). */
80
+ getBounds(nodeId: string): BoundingBox | undefined {
81
+ return this.cache.get(nodeId);
82
+ }
83
+ }
@@ -0,0 +1,61 @@
1
+ import { createStore } from 'zustand/vanilla';
2
+ import type { NodeSelection, RenderConfig } from '../types/index.js';
3
+ import {
4
+ DEFAULT_RENDER_CONFIG,
5
+ getOptimalQualityLevel,
6
+ getRenderConfigForQuality,
7
+ isFeatureEnabled,
8
+ FEATURE_FLAGS,
9
+ } from '../constants/renderConfig.js';
10
+
11
+ // ─── Selection Store ──────────────────────────────────────────────────────────
12
+
13
+ export interface ISelectionStore extends NodeSelection {
14
+ select: (nodeId: string) => void;
15
+ clear: () => void;
16
+ toggle: (nodeId: string) => void;
17
+ isSelected: (nodeId: string) => boolean;
18
+ }
19
+
20
+ export const selectionStore = createStore<ISelectionStore>((set, get) => ({
21
+ selectedId: null,
22
+ select: (nodeId) => set({ selectedId: nodeId }),
23
+ clear: () => set({ selectedId: null }),
24
+ toggle: (nodeId) => set((s) => ({ selectedId: s.selectedId === nodeId ? null : nodeId })),
25
+ isSelected: (nodeId) => get().selectedId === nodeId,
26
+ }));
27
+
28
+ // ─── Render Config Store ──────────────────────────────────────────────────────
29
+
30
+ export interface IRenderConfigStore extends RenderConfig {
31
+ setQualityLevel: (level: RenderConfig['qualityLevel']) => void;
32
+ setRenderMode: (mode: RenderConfig['renderMode']) => void;
33
+ setCullingEnabled: (enabled: boolean) => void;
34
+ autoAdjustQuality: (currentFPS: number) => void;
35
+ autoAdjustQualityByMemory: (memoryMB: number) => void;
36
+ resetToDefaults: () => void;
37
+ isFeatureEnabled: (feature: keyof typeof FEATURE_FLAGS) => boolean;
38
+ }
39
+
40
+ export const renderConfigStore = createStore<IRenderConfigStore>((set, get) => ({
41
+ ...DEFAULT_RENDER_CONFIG,
42
+
43
+ setQualityLevel: (level) => set(getRenderConfigForQuality(level)),
44
+ setRenderMode: (mode) => set({ renderMode: mode }),
45
+ setCullingEnabled: (enabled) => set({ cullingEnabled: enabled }),
46
+
47
+ autoAdjustQuality: (fps) =>
48
+ set((s) => {
49
+ const q = getOptimalQualityLevel(fps, s.qualityLevel);
50
+ return q !== s.qualityLevel ? getRenderConfigForQuality(q) : s;
51
+ }),
52
+
53
+ autoAdjustQualityByMemory: (mb) =>
54
+ set((s) => {
55
+ const q = mb > 500 ? 'low' : mb > 400 && s.qualityLevel === 'high' ? 'medium' : s.qualityLevel;
56
+ return q !== s.qualityLevel ? getRenderConfigForQuality(q as RenderConfig['qualityLevel']) : s;
57
+ }),
58
+
59
+ resetToDefaults: () => set({ ...DEFAULT_RENDER_CONFIG }),
60
+ isFeatureEnabled: (feature) => isFeatureEnabled(feature, get()),
61
+ }));
@@ -0,0 +1,15 @@
1
+ // ─── Core .visualli Document ───────────────────────────────────────────────────
2
+
3
+ import type { VisualliMeta } from './meta.js';
4
+ import type { VisualliLayer } from './layer.js';
5
+
6
+ /** Parsed .visualli document structure. */
7
+ export interface VisualliDocument {
8
+ meta: VisualliMeta;
9
+ /** Keyed by layer ID */
10
+ layers: Map<string, VisualliLayer>;
11
+ /** Quick lookup by level */
12
+ layersByLevel: Map<number, VisualliLayer[]>;
13
+ /** The level-0 layer */
14
+ rootLayer: VisualliLayer | null;
15
+ }
@@ -0,0 +1,17 @@
1
+ // ─── Types Barrel Export ───────────────────────────────────────────────────────
2
+
3
+ export * from './mindmap.js';
4
+ export * from './meta.js';
5
+ export * from './layer.js';
6
+ export * from './document.js';
7
+ // Re-export schema types for external use
8
+ export {
9
+ type Meta,
10
+ type Node,
11
+ type Container,
12
+ type Layer,
13
+ type Connection,
14
+ type Extension,
15
+ type VisualliSpec011,
16
+ type VisualliFragment,
17
+ } from './schema.js';
@@ -0,0 +1,67 @@
1
+ // ─── Visualli Layer Types ──────────────────────────────────────────────────────
2
+ //
3
+ // These types extend the auto-generated schema types from bindings.
4
+ // They provide stricter runtime guarantees (required arrays) and SDK-specific fields.
5
+
6
+ import type { Layer, Node, Connection, Container } from './schema.js';
7
+
8
+ /** Alias to schema Node type for SDK consistency */
9
+ export type LayerNode = Node;
10
+
11
+ /** Alias to schema Connection type for SDK consistency */
12
+ export type LayerConnection = Connection;
13
+
14
+ /** Alias to schema Container type for SDK consistency */
15
+ export type LayerContainer = Container;
16
+
17
+ /**
18
+ * SDK's stricter version of schema Layer.
19
+ * - Makes nodes/connections/containers required (always arrays, even if empty)
20
+ * - Adds SDK-specific `description` field
21
+ * - Explicitly types key fields to avoid 'unknown' from schema's index signature
22
+ */
23
+ export interface VisualliLayer {
24
+ type: 'layer';
25
+ /** UUID of the layer */
26
+ id: string;
27
+ /** Hierarchy level (0 = root) */
28
+ level: number;
29
+ /** UUID of the parent layer */
30
+ parentLayerId?: string;
31
+ /** UUID of the specific node in parent layer */
32
+ parentNodeId?: string;
33
+ /** SDK-specific description field (not in schema) */
34
+ description?: string;
35
+ /** Spatial arrangement (inherits from schema) */
36
+ layout?: 'radial' | 'linear-horizontal' | 'linear-vertical';
37
+ /** Required array of nodes (schema has optional) */
38
+ nodes: LayerNode[];
39
+ /** Required array of connections (schema has optional) */
40
+ connections: LayerConnection[];
41
+ /** Required array of containers (schema has optional) */
42
+ containers: LayerContainer[];
43
+ }
44
+
45
+ /** Intermediate format for rendering – converts VisualliLayer to flat nodes. */
46
+ export interface VisualliRenderNode {
47
+ id: string;
48
+ x: number;
49
+ y: number;
50
+ title: string;
51
+ summary: string;
52
+ level: number;
53
+ layerId: string;
54
+ layerLevel: number;
55
+ color: string;
56
+ width: number;
57
+ height: number;
58
+ branchCount: number;
59
+ }
60
+
61
+ export interface VisualliRenderConnection {
62
+ id: string;
63
+ from: string;
64
+ to: string;
65
+ label?: string;
66
+ style?: 'dashed' | 'solid';
67
+ }
@@ -0,0 +1,9 @@
1
+ // ─── Visualli Meta ─────────────────────────────────────────────────────────────
2
+ //
3
+ // VisualliMeta is an alias to the schema Meta type.
4
+ // It's imported from the auto-generated bindings to ensure sync with schema changes.
5
+
6
+ import type { Meta } from './schema.js';
7
+
8
+ /** Alias to schema Meta type for SDK consistency */
9
+ export type VisualliMeta = Meta;
@@ -0,0 +1,136 @@
1
+ // ─── Mindmap Core Types ────────────────────────────────────────────────────────
2
+
3
+ export interface MindMapNode {
4
+ id: string;
5
+ title: string;
6
+ level: number;
7
+ x: number;
8
+ y: number;
9
+ children?: MindMapNode[];
10
+ parent?: string;
11
+ color?: string;
12
+ relationshipLabel?: string;
13
+ }
14
+
15
+ /** Flat structure for performance-optimised storage (10K+ nodes). */
16
+ export interface FlatNode {
17
+ // Identity
18
+ id: string;
19
+ parentId: string | null;
20
+ /** IDs of child nodes for quick tree traversal */
21
+ childrenIds?: string[];
22
+
23
+ // Position (world coordinates)
24
+ x: number;
25
+ y: number;
26
+ /** Alternative position format for compatibility */
27
+ position?: { x: number; y: number };
28
+ /** Depth in tree hierarchy */
29
+ level: number;
30
+
31
+ // Content
32
+ title: string;
33
+ description?: string;
34
+ /** Label describing relationship to parent */
35
+ relationshipLabel?: string;
36
+
37
+ // Visual
38
+ color: string;
39
+ width: number;
40
+ height: number;
41
+
42
+ // Expansion state (for hierarchical reveal)
43
+ /** Whether this node's children are visible */
44
+ isExpanded?: boolean;
45
+
46
+ // Metadata
47
+ branchCount?: number;
48
+ createdAt: Date;
49
+ updatedAt: Date;
50
+ }
51
+
52
+ /** Map-based node storage for O(1) lookups. */
53
+ export type NodeMap = Map<string, FlatNode>;
54
+
55
+ /** Spatial bounds for RBush indexing. */
56
+ export interface SpatialBounds {
57
+ minX: number;
58
+ minY: number;
59
+ maxX: number;
60
+ maxY: number;
61
+ nodeId: string;
62
+ }
63
+
64
+ /** Viewport state for camera position and zoom. */
65
+ export interface ViewportState {
66
+ centerX: number;
67
+ centerY: number;
68
+ zoomLevel: number;
69
+ rotation: number;
70
+ visibleBounds: {
71
+ minX: number;
72
+ minY: number;
73
+ maxX: number;
74
+ maxY: number;
75
+ };
76
+ }
77
+
78
+ /** Single-selection node state. */
79
+ export interface NodeSelection {
80
+ selectedId: string | null;
81
+ }
82
+
83
+ /** Render configuration flags. */
84
+ export interface RenderConfig {
85
+ renderMode: 'canvas' | 'simplified';
86
+ qualityLevel: 'high' | 'medium' | 'low';
87
+ cullingEnabled: boolean;
88
+ fpsTarget: number;
89
+ memoryLimit: number;
90
+ }
91
+
92
+ export interface MindMapData {
93
+ nodes: MindMapNode[];
94
+ connections: MindMapConnection[];
95
+ }
96
+
97
+ /** Simplified connection type for mindmap rendering (not to be confused with schema Connection) */
98
+ export interface MindMapConnection {
99
+ from: string;
100
+ to: string;
101
+ level: number;
102
+ label?: string;
103
+ }
104
+
105
+ export interface TopLevelConnection {
106
+ from: string;
107
+ to: string;
108
+ label: string;
109
+ }
110
+
111
+ /** Complete mindmap configuration schema. */
112
+ export interface MindMapConfig {
113
+ metadata: {
114
+ id?: string;
115
+ title: string;
116
+ description?: string;
117
+ version: string;
118
+ created: string;
119
+ lastModified: string;
120
+ };
121
+ nodes: MindMapNode[];
122
+ topLevelConnections: TopLevelConnection[];
123
+ settings?: {
124
+ defaultColors?: {
125
+ level0: string;
126
+ level1: string;
127
+ level2: string;
128
+ level3: string;
129
+ [key: `level${number}`]: string;
130
+ };
131
+ layout?: {
132
+ nodeSpacing: number;
133
+ levelSpacing: number;
134
+ };
135
+ };
136
+ }
@@ -0,0 +1,36 @@
1
+ // ─── Schema-aligned Types (JSON-schema surface) ────────────────────────────────
2
+ //
3
+ // These types are imported from the auto-generated bindings package.
4
+ // The bindings are generated from visualli.schema.json via GitHub workflow.
5
+ // DO NOT manually define schema types here - they come from @visualli/bindings.
6
+
7
+ import type { VisualliMeta } from './meta.js';
8
+ import type { VisualliLayer, LayerNode, LayerConnection, LayerContainer } from './layer.js';
9
+
10
+ // ─── Import Generated Schema Types ────────────────────────────────────────────
11
+ // These are auto-generated from visualli.schema.json
12
+ import type {
13
+ Meta,
14
+ Node,
15
+ Connection,
16
+ Container,
17
+ Layer,
18
+ Extension,
19
+ VisualliSpec011,
20
+ } from '@visualli/bindings';
21
+
22
+ // ─── Re-exports ────────────────────────────────────────────────────────────────
23
+ // Export the generated types from bindings
24
+ export type { Meta, Node, Connection, Container, Layer, Extension, VisualliSpec011 };
25
+
26
+ // Union type for JSONL fragments
27
+ export type VisualliFragment = Meta | Layer;
28
+
29
+ // Re-export domain types for convenience
30
+ export type {
31
+ VisualliMeta,
32
+ VisualliLayer,
33
+ LayerNode,
34
+ LayerConnection,
35
+ LayerContainer,
36
+ };