@principal-ai/principal-view-core 0.5.6

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 (204) hide show
  1. package/README.md +126 -0
  2. package/dist/ConfigurationLoader.d.ts +76 -0
  3. package/dist/ConfigurationLoader.d.ts.map +1 -0
  4. package/dist/ConfigurationLoader.js +144 -0
  5. package/dist/ConfigurationLoader.js.map +1 -0
  6. package/dist/ConfigurationValidator.d.ts +31 -0
  7. package/dist/ConfigurationValidator.d.ts.map +1 -0
  8. package/dist/ConfigurationValidator.js +242 -0
  9. package/dist/ConfigurationValidator.js.map +1 -0
  10. package/dist/EventProcessor.d.ts +49 -0
  11. package/dist/EventProcessor.d.ts.map +1 -0
  12. package/dist/EventProcessor.js +215 -0
  13. package/dist/EventProcessor.js.map +1 -0
  14. package/dist/EventRecorderService.d.ts +305 -0
  15. package/dist/EventRecorderService.d.ts.map +1 -0
  16. package/dist/EventRecorderService.js +463 -0
  17. package/dist/EventRecorderService.js.map +1 -0
  18. package/dist/LibraryLoader.d.ts +63 -0
  19. package/dist/LibraryLoader.d.ts.map +1 -0
  20. package/dist/LibraryLoader.js +188 -0
  21. package/dist/LibraryLoader.js.map +1 -0
  22. package/dist/PathBasedEventProcessor.d.ts +90 -0
  23. package/dist/PathBasedEventProcessor.d.ts.map +1 -0
  24. package/dist/PathBasedEventProcessor.js +239 -0
  25. package/dist/PathBasedEventProcessor.js.map +1 -0
  26. package/dist/SessionManager.d.ts +194 -0
  27. package/dist/SessionManager.d.ts.map +1 -0
  28. package/dist/SessionManager.js +299 -0
  29. package/dist/SessionManager.js.map +1 -0
  30. package/dist/ValidationEngine.d.ts +31 -0
  31. package/dist/ValidationEngine.d.ts.map +1 -0
  32. package/dist/ValidationEngine.js +158 -0
  33. package/dist/ValidationEngine.js.map +1 -0
  34. package/dist/helpers/GraphInstrumentationHelper.d.ts +93 -0
  35. package/dist/helpers/GraphInstrumentationHelper.d.ts.map +1 -0
  36. package/dist/helpers/GraphInstrumentationHelper.js +248 -0
  37. package/dist/helpers/GraphInstrumentationHelper.js.map +1 -0
  38. package/dist/index.d.ts +33 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +34 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/rules/config.d.ts +57 -0
  43. package/dist/rules/config.d.ts.map +1 -0
  44. package/dist/rules/config.js +382 -0
  45. package/dist/rules/config.js.map +1 -0
  46. package/dist/rules/engine.d.ts +70 -0
  47. package/dist/rules/engine.d.ts.map +1 -0
  48. package/dist/rules/engine.js +252 -0
  49. package/dist/rules/engine.js.map +1 -0
  50. package/dist/rules/implementations/connection-type-references.d.ts +7 -0
  51. package/dist/rules/implementations/connection-type-references.d.ts.map +1 -0
  52. package/dist/rules/implementations/connection-type-references.js +104 -0
  53. package/dist/rules/implementations/connection-type-references.js.map +1 -0
  54. package/dist/rules/implementations/dead-end-states.d.ts +17 -0
  55. package/dist/rules/implementations/dead-end-states.d.ts.map +1 -0
  56. package/dist/rules/implementations/dead-end-states.js +72 -0
  57. package/dist/rules/implementations/dead-end-states.js.map +1 -0
  58. package/dist/rules/implementations/index.d.ts +24 -0
  59. package/dist/rules/implementations/index.d.ts.map +1 -0
  60. package/dist/rules/implementations/index.js +62 -0
  61. package/dist/rules/implementations/index.js.map +1 -0
  62. package/dist/rules/implementations/library-node-type-match.d.ts +17 -0
  63. package/dist/rules/implementations/library-node-type-match.d.ts.map +1 -0
  64. package/dist/rules/implementations/library-node-type-match.js +123 -0
  65. package/dist/rules/implementations/library-node-type-match.js.map +1 -0
  66. package/dist/rules/implementations/minimum-node-sources.d.ts +22 -0
  67. package/dist/rules/implementations/minimum-node-sources.d.ts.map +1 -0
  68. package/dist/rules/implementations/minimum-node-sources.js +54 -0
  69. package/dist/rules/implementations/minimum-node-sources.js.map +1 -0
  70. package/dist/rules/implementations/no-unknown-fields.d.ts +7 -0
  71. package/dist/rules/implementations/no-unknown-fields.d.ts.map +1 -0
  72. package/dist/rules/implementations/no-unknown-fields.js +211 -0
  73. package/dist/rules/implementations/no-unknown-fields.js.map +1 -0
  74. package/dist/rules/implementations/orphaned-edge-types.d.ts +7 -0
  75. package/dist/rules/implementations/orphaned-edge-types.d.ts.map +1 -0
  76. package/dist/rules/implementations/orphaned-edge-types.js +47 -0
  77. package/dist/rules/implementations/orphaned-edge-types.js.map +1 -0
  78. package/dist/rules/implementations/orphaned-node-types.d.ts +7 -0
  79. package/dist/rules/implementations/orphaned-node-types.d.ts.map +1 -0
  80. package/dist/rules/implementations/orphaned-node-types.js +50 -0
  81. package/dist/rules/implementations/orphaned-node-types.js.map +1 -0
  82. package/dist/rules/implementations/required-metadata.d.ts +7 -0
  83. package/dist/rules/implementations/required-metadata.d.ts.map +1 -0
  84. package/dist/rules/implementations/required-metadata.js +57 -0
  85. package/dist/rules/implementations/required-metadata.js.map +1 -0
  86. package/dist/rules/implementations/state-transition-references.d.ts +7 -0
  87. package/dist/rules/implementations/state-transition-references.d.ts.map +1 -0
  88. package/dist/rules/implementations/state-transition-references.js +135 -0
  89. package/dist/rules/implementations/state-transition-references.js.map +1 -0
  90. package/dist/rules/implementations/unreachable-states.d.ts +7 -0
  91. package/dist/rules/implementations/unreachable-states.d.ts.map +1 -0
  92. package/dist/rules/implementations/unreachable-states.js +80 -0
  93. package/dist/rules/implementations/unreachable-states.js.map +1 -0
  94. package/dist/rules/implementations/valid-action-patterns.d.ts +17 -0
  95. package/dist/rules/implementations/valid-action-patterns.d.ts.map +1 -0
  96. package/dist/rules/implementations/valid-action-patterns.js +109 -0
  97. package/dist/rules/implementations/valid-action-patterns.js.map +1 -0
  98. package/dist/rules/implementations/valid-color-format.d.ts +7 -0
  99. package/dist/rules/implementations/valid-color-format.d.ts.map +1 -0
  100. package/dist/rules/implementations/valid-color-format.js +91 -0
  101. package/dist/rules/implementations/valid-color-format.js.map +1 -0
  102. package/dist/rules/implementations/valid-edge-types.d.ts +7 -0
  103. package/dist/rules/implementations/valid-edge-types.d.ts.map +1 -0
  104. package/dist/rules/implementations/valid-edge-types.js +244 -0
  105. package/dist/rules/implementations/valid-edge-types.js.map +1 -0
  106. package/dist/rules/implementations/valid-node-types.d.ts +7 -0
  107. package/dist/rules/implementations/valid-node-types.d.ts.map +1 -0
  108. package/dist/rules/implementations/valid-node-types.js +175 -0
  109. package/dist/rules/implementations/valid-node-types.js.map +1 -0
  110. package/dist/rules/index.d.ts +28 -0
  111. package/dist/rules/index.d.ts.map +1 -0
  112. package/dist/rules/index.js +45 -0
  113. package/dist/rules/index.js.map +1 -0
  114. package/dist/rules/types.d.ts +309 -0
  115. package/dist/rules/types.d.ts.map +1 -0
  116. package/dist/rules/types.js +35 -0
  117. package/dist/rules/types.js.map +1 -0
  118. package/dist/types/canvas.d.ts +409 -0
  119. package/dist/types/canvas.d.ts.map +1 -0
  120. package/dist/types/canvas.js +70 -0
  121. package/dist/types/canvas.js.map +1 -0
  122. package/dist/types/index.d.ts +311 -0
  123. package/dist/types/index.d.ts.map +1 -0
  124. package/dist/types/index.js +13 -0
  125. package/dist/types/index.js.map +1 -0
  126. package/dist/types/library.d.ts +185 -0
  127. package/dist/types/library.d.ts.map +1 -0
  128. package/dist/types/library.js +15 -0
  129. package/dist/types/library.js.map +1 -0
  130. package/dist/types/path-based-config.d.ts +230 -0
  131. package/dist/types/path-based-config.d.ts.map +1 -0
  132. package/dist/types/path-based-config.js +9 -0
  133. package/dist/types/path-based-config.js.map +1 -0
  134. package/dist/utils/CanvasConverter.d.ts +118 -0
  135. package/dist/utils/CanvasConverter.d.ts.map +1 -0
  136. package/dist/utils/CanvasConverter.js +315 -0
  137. package/dist/utils/CanvasConverter.js.map +1 -0
  138. package/dist/utils/GraphConverter.d.ts +18 -0
  139. package/dist/utils/GraphConverter.d.ts.map +1 -0
  140. package/dist/utils/GraphConverter.js +61 -0
  141. package/dist/utils/GraphConverter.js.map +1 -0
  142. package/dist/utils/LibraryConverter.d.ts +113 -0
  143. package/dist/utils/LibraryConverter.d.ts.map +1 -0
  144. package/dist/utils/LibraryConverter.js +166 -0
  145. package/dist/utils/LibraryConverter.js.map +1 -0
  146. package/dist/utils/PathMatcher.d.ts +55 -0
  147. package/dist/utils/PathMatcher.d.ts.map +1 -0
  148. package/dist/utils/PathMatcher.js +172 -0
  149. package/dist/utils/PathMatcher.js.map +1 -0
  150. package/dist/utils/YamlParser.d.ts +36 -0
  151. package/dist/utils/YamlParser.d.ts.map +1 -0
  152. package/dist/utils/YamlParser.js +63 -0
  153. package/dist/utils/YamlParser.js.map +1 -0
  154. package/package.json +47 -0
  155. package/src/ConfigurationLoader.test.ts +490 -0
  156. package/src/ConfigurationLoader.ts +185 -0
  157. package/src/ConfigurationValidator.test.ts +200 -0
  158. package/src/ConfigurationValidator.ts +283 -0
  159. package/src/EventProcessor.test.ts +405 -0
  160. package/src/EventProcessor.ts +250 -0
  161. package/src/EventRecorderService.test.ts +541 -0
  162. package/src/EventRecorderService.ts +744 -0
  163. package/src/LibraryLoader.ts +215 -0
  164. package/src/PathBasedEventProcessor.test.ts +567 -0
  165. package/src/PathBasedEventProcessor.ts +332 -0
  166. package/src/SessionManager.test.ts +424 -0
  167. package/src/SessionManager.ts +470 -0
  168. package/src/ValidationEngine.test.ts +371 -0
  169. package/src/ValidationEngine.ts +196 -0
  170. package/src/helpers/GraphInstrumentationHelper.test.ts +340 -0
  171. package/src/helpers/GraphInstrumentationHelper.ts +326 -0
  172. package/src/index.ts +85 -0
  173. package/src/rules/config.test.ts +278 -0
  174. package/src/rules/config.ts +459 -0
  175. package/src/rules/engine.test.ts +332 -0
  176. package/src/rules/engine.ts +318 -0
  177. package/src/rules/implementations/connection-type-references.ts +117 -0
  178. package/src/rules/implementations/dead-end-states.ts +101 -0
  179. package/src/rules/implementations/index.ts +73 -0
  180. package/src/rules/implementations/library-node-type-match.ts +148 -0
  181. package/src/rules/implementations/minimum-node-sources.ts +82 -0
  182. package/src/rules/implementations/no-unknown-fields.ts +342 -0
  183. package/src/rules/implementations/orphaned-edge-types.ts +55 -0
  184. package/src/rules/implementations/orphaned-node-types.ts +58 -0
  185. package/src/rules/implementations/required-metadata.ts +64 -0
  186. package/src/rules/implementations/state-transition-references.ts +151 -0
  187. package/src/rules/implementations/unreachable-states.ts +94 -0
  188. package/src/rules/implementations/valid-action-patterns.ts +136 -0
  189. package/src/rules/implementations/valid-color-format.ts +140 -0
  190. package/src/rules/implementations/valid-edge-types.ts +258 -0
  191. package/src/rules/implementations/valid-node-types.ts +189 -0
  192. package/src/rules/index.ts +95 -0
  193. package/src/rules/types.ts +426 -0
  194. package/src/types/canvas.ts +496 -0
  195. package/src/types/index.ts +382 -0
  196. package/src/types/library.ts +233 -0
  197. package/src/types/path-based-config.ts +281 -0
  198. package/src/utils/CanvasConverter.ts +431 -0
  199. package/src/utils/GraphConverter.test.ts +195 -0
  200. package/src/utils/GraphConverter.ts +71 -0
  201. package/src/utils/LibraryConverter.ts +245 -0
  202. package/src/utils/PathMatcher.test.ts +148 -0
  203. package/src/utils/PathMatcher.ts +183 -0
  204. package/src/utils/YamlParser.ts +75 -0
@@ -0,0 +1,281 @@
1
+ /**
2
+ * Path-based configuration extensions for Milestone 1 & 2
3
+ *
4
+ * These types extend the core GraphConfiguration to support:
5
+ * - Path-based log association (Milestone 1)
6
+ * - Action pattern refinement (Milestone 2)
7
+ */
8
+
9
+ /**
10
+ * Log levels (matches logger package)
11
+ */
12
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
13
+
14
+ /**
15
+ * Extended node type definition with source path mapping
16
+ */
17
+ export interface PathBasedNodeTypeDefinition {
18
+ /** Base node type properties (inherited from NodeTypeDefinition) */
19
+ shape: 'circle' | 'rectangle' | 'hexagon' | 'diamond' | 'custom';
20
+ icon?: string;
21
+ color?: string;
22
+ size?: { width: number; height: number };
23
+ dataSchema: {
24
+ [field: string]: {
25
+ type: 'string' | 'number' | 'boolean' | 'object' | 'array';
26
+ required?: boolean;
27
+ displayInLabel?: boolean;
28
+ };
29
+ };
30
+ states?: Record<
31
+ string,
32
+ {
33
+ color?: string;
34
+ icon?: string;
35
+ label?: string;
36
+ }
37
+ >;
38
+ layout?: {
39
+ layer?: number;
40
+ cluster?: string;
41
+ };
42
+
43
+ /** Manual position for 'manual' layout mode */
44
+ position?: { x: number; y: number };
45
+
46
+ /** MILESTONE 1: Source path mapping */
47
+ sources?: string[]; // e.g., ["lib/lock-manager.ts", "lib/**/*.ts"]
48
+
49
+ /** MILESTONE 2: Optional action patterns for refinement */
50
+ actions?: ActionPattern[];
51
+ }
52
+
53
+ /**
54
+ * Action pattern for extracting structured events from logs (Milestone 2)
55
+ */
56
+ export interface ActionPattern {
57
+ /** Regex pattern to match log message (with named capture groups) */
58
+ pattern: string;
59
+
60
+ /** Event type to emit when pattern matches */
61
+ event: string;
62
+
63
+ /** Component state to transition to (optional) */
64
+ state?: string;
65
+
66
+ /** Metadata template - extracts data from capture groups */
67
+ metadata?: Record<string, string>; // e.g., { lockId: "$lockId" }
68
+
69
+ /** Whether this action should trigger edge animations */
70
+ triggerEdges?: string[]; // Edge IDs or patterns
71
+ }
72
+
73
+ /**
74
+ * Extended edge definition with action-based activation (Milestone 2)
75
+ */
76
+ export interface PathBasedEdgeTypeDefinition {
77
+ /** Base edge properties */
78
+ style: 'solid' | 'dashed' | 'dotted' | 'animated';
79
+ color?: string;
80
+ width?: number;
81
+ directed?: boolean;
82
+ animated?: boolean;
83
+ label?: {
84
+ field?: string;
85
+ position?: 'start' | 'middle' | 'end';
86
+ };
87
+ animation?: {
88
+ type: 'flow' | 'pulse' | 'particle' | 'glow';
89
+ duration?: number;
90
+ color?: string;
91
+ };
92
+
93
+ /** MILESTONE 2: Edge activation triggers */
94
+ activatedBy?: EdgeActivationTrigger[];
95
+ }
96
+
97
+ /**
98
+ * Configuration for activating edges based on component actions
99
+ */
100
+ export interface EdgeActivationTrigger {
101
+ /** Action that triggers this edge animation */
102
+ action: string;
103
+
104
+ /** Animation type to play */
105
+ animation: 'flow' | 'particle' | 'pulse' | 'glow';
106
+
107
+ /** Animation direction */
108
+ direction?: 'forward' | 'backward' | 'bidirectional';
109
+
110
+ /** Animation duration in milliseconds */
111
+ duration?: number;
112
+ }
113
+
114
+ /**
115
+ * Extended graph configuration with path-based support
116
+ */
117
+ export interface PathBasedGraphConfiguration {
118
+ metadata: {
119
+ name: string;
120
+ version: string;
121
+ description?: string;
122
+ };
123
+
124
+ /** Node types with source path mapping */
125
+ nodeTypes: Record<string, PathBasedNodeTypeDefinition>;
126
+
127
+ /** Edge types with action-based activation */
128
+ edgeTypes: Record<string, PathBasedEdgeTypeDefinition>;
129
+
130
+ /** Allowed connections between node types */
131
+ allowedConnections: Array<{
132
+ from: string;
133
+ to: string;
134
+ via: string;
135
+ constraints?: {
136
+ maxInstances?: number;
137
+ bidirectional?: boolean;
138
+ exclusive?: boolean;
139
+ };
140
+ }>;
141
+
142
+ /** Optional validation rules */
143
+ validation?: any; // Reuse from base types
144
+
145
+ /** Display preferences */
146
+ display?: any; // Reuse from base types
147
+
148
+ /** Path-based configuration options */
149
+ pathBasedConfig?: PathBasedConfigOptions;
150
+ }
151
+
152
+ /**
153
+ * Configuration options for path-based log association
154
+ */
155
+ export interface PathBasedConfigOptions {
156
+ /** Project root for normalizing paths */
157
+ projectRoot?: string;
158
+
159
+ /** Whether to enable source capture (default: true) */
160
+ captureSource?: boolean;
161
+
162
+ /** Whether to enable action pattern matching (default: false in M1, true in M2) */
163
+ enableActionPatterns?: boolean;
164
+
165
+ /** Default log level for filtering (default: 'info') */
166
+ logLevel?: LogLevel;
167
+
168
+ /** Whether to ignore logs without source information (default: false) */
169
+ ignoreUnsourced?: boolean;
170
+ }
171
+
172
+ /**
173
+ * Component activity event (Milestone 1)
174
+ * Generated when a log is associated with a component by source path
175
+ */
176
+ export interface ComponentActivityEvent {
177
+ type: 'component-activity';
178
+
179
+ /** Component ID (node type) */
180
+ componentId: string;
181
+
182
+ /**
183
+ * Instance identifier for multi-instance components.
184
+ * If provided, targets a specific node instance (e.g., "client-1").
185
+ * If undefined, targets the node type as a whole.
186
+ */
187
+ instanceId?: string;
188
+
189
+ /** Timestamp of the log */
190
+ timestamp: number;
191
+
192
+ /** Log level */
193
+ level: LogLevel;
194
+
195
+ /** Log message */
196
+ message: string;
197
+
198
+ /** Source location */
199
+ source: {
200
+ file: string;
201
+ line?: number;
202
+ column?: number;
203
+ };
204
+
205
+ /** Additional log arguments */
206
+ args?: any[];
207
+ }
208
+
209
+ /**
210
+ * Component action event (Milestone 2)
211
+ * Generated when a log matches an action pattern
212
+ */
213
+ export interface ComponentActionEvent {
214
+ type: 'component-action';
215
+
216
+ /** Component ID (node type) */
217
+ componentId: string;
218
+
219
+ /**
220
+ * Instance identifier for multi-instance components.
221
+ * If provided, targets a specific node instance (e.g., "client-1").
222
+ * If undefined, targets the node type as a whole.
223
+ */
224
+ instanceId?: string;
225
+
226
+ /** Action type (from pattern) */
227
+ action: string;
228
+
229
+ /** New state (if specified in pattern) */
230
+ state?: string;
231
+
232
+ /** Timestamp of the log */
233
+ timestamp: number;
234
+
235
+ /** Extracted metadata from capture groups */
236
+ metadata?: Record<string, any>;
237
+
238
+ /** Source location */
239
+ source: {
240
+ file: string;
241
+ line?: number;
242
+ column?: number;
243
+ };
244
+ }
245
+
246
+ /**
247
+ * Edge animation event (Milestone 2)
248
+ * Generated when a component action triggers edge activation
249
+ */
250
+ export interface EdgeAnimationEvent {
251
+ type: 'edge-animation';
252
+
253
+ /** Edge ID to animate */
254
+ edgeId: string;
255
+
256
+ /** Animation type */
257
+ animation: 'flow' | 'particle' | 'pulse' | 'glow';
258
+
259
+ /** Animation direction */
260
+ direction?: 'forward' | 'backward' | 'bidirectional';
261
+
262
+ /** Animation duration */
263
+ duration: number;
264
+
265
+ /** Timestamp */
266
+ timestamp: number;
267
+
268
+ /** Source action that triggered this */
269
+ triggeredBy?: {
270
+ componentId: string;
271
+ action: string;
272
+ };
273
+ }
274
+
275
+ /**
276
+ * Union type of all path-based events
277
+ */
278
+ export type PathBasedEvent =
279
+ | ComponentActivityEvent
280
+ | ComponentActionEvent
281
+ | EdgeAnimationEvent;
@@ -0,0 +1,431 @@
1
+ /**
2
+ * Canvas Converter
3
+ *
4
+ * Utilities for converting between Extended Canvas format and React Flow nodes/edges.
5
+ */
6
+
7
+ import type {
8
+ ExtendedCanvas,
9
+ ExtendedCanvasNode,
10
+ ExtendedCanvasEdge,
11
+ PVEdgeTypeDefinition,
12
+ PVNodeShape,
13
+ PVEdgeStyle,
14
+ PVAnimationType,
15
+ } from '../types/canvas';
16
+ import { resolveCanvasColor } from '../types/canvas';
17
+ import type { NodeState, EdgeState } from '../types';
18
+
19
+ /**
20
+ * React Flow node format
21
+ */
22
+ export interface ReactFlowNode {
23
+ id: string;
24
+ type: string;
25
+ position: { x: number; y: number };
26
+ data: {
27
+ label: string;
28
+ nodeType: string;
29
+ shape?: string;
30
+ icon?: string;
31
+ color?: string;
32
+ width?: number;
33
+ height?: number;
34
+ states?: Record<string, { color?: string; icon?: string; label?: string }>;
35
+ sources?: string[];
36
+ actions?: any[];
37
+ canvasType?: 'text' | 'file' | 'link' | 'group';
38
+ text?: string;
39
+ file?: string;
40
+ url?: string;
41
+ [key: string]: any;
42
+ };
43
+ style?: {
44
+ width?: number;
45
+ height?: number;
46
+ };
47
+ }
48
+
49
+ /**
50
+ * React Flow edge format
51
+ */
52
+ export interface ReactFlowEdge {
53
+ id: string;
54
+ source: string;
55
+ target: string;
56
+ type?: string;
57
+ sourceHandle?: string;
58
+ targetHandle?: string;
59
+ label?: string;
60
+ data?: {
61
+ edgeType: string;
62
+ style?: string;
63
+ color?: string;
64
+ width?: number;
65
+ animation?: {
66
+ type: string;
67
+ duration?: number;
68
+ color?: string;
69
+ };
70
+ activatedBy?: any[];
71
+ [key: string]: any;
72
+ };
73
+ style?: {
74
+ stroke?: string;
75
+ strokeWidth?: number;
76
+ strokeDasharray?: string;
77
+ };
78
+ animated?: boolean;
79
+ markerEnd?: {
80
+ type: string;
81
+ color?: string;
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Convert canvas side to React Flow handle position
87
+ */
88
+ function sideToHandle(side?: string): string | undefined {
89
+ if (!side) return undefined;
90
+ // React Flow uses 'top', 'right', 'bottom', 'left' - same as canvas
91
+ return side;
92
+ }
93
+
94
+ /**
95
+ * Convert edge style to stroke-dasharray
96
+ */
97
+ function styleToStrokeDasharray(style?: string): string | undefined {
98
+ switch (style) {
99
+ case 'dashed':
100
+ return '5,5';
101
+ case 'dotted':
102
+ return '2,2';
103
+ default:
104
+ return undefined;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Canvas Converter utility class
110
+ */
111
+ export class CanvasConverter {
112
+ /**
113
+ * Convert Extended Canvas to React Flow nodes and edges
114
+ */
115
+ static canvasToReactFlow(canvas: ExtendedCanvas): {
116
+ nodes: ReactFlowNode[];
117
+ edges: ReactFlowEdge[];
118
+ } {
119
+ const nodes: ReactFlowNode[] = [];
120
+ const edges: ReactFlowEdge[] = [];
121
+
122
+ // Convert nodes
123
+ if (canvas.nodes) {
124
+ for (const node of canvas.nodes) {
125
+ nodes.push(this.convertNode(node, canvas));
126
+ }
127
+ }
128
+
129
+ // Convert edges
130
+ if (canvas.edges) {
131
+ for (const edge of canvas.edges) {
132
+ edges.push(this.convertEdge(edge, canvas));
133
+ }
134
+ }
135
+
136
+ return { nodes, edges };
137
+ }
138
+
139
+ /**
140
+ * Convert a single canvas node to React Flow node
141
+ */
142
+ private static convertNode(node: ExtendedCanvasNode, canvas: ExtendedCanvas): ReactFlowNode {
143
+ const pv = node.pv;
144
+ const color = resolveCanvasColor(node.color);
145
+
146
+ // Build the data object based on canvas node type
147
+ const data: ReactFlowNode['data'] = {
148
+ label: this.getNodeLabel(node),
149
+ nodeType: pv?.nodeType || node.id,
150
+ canvasType: node.type,
151
+ shape: pv?.shape || 'rectangle',
152
+ icon: pv?.icon,
153
+ color: pv?.states?.idle?.color || color,
154
+ width: node.width,
155
+ height: node.height,
156
+ };
157
+
158
+ // Add type-specific data
159
+ if (node.type === 'text') {
160
+ data.text = node.text;
161
+ } else if (node.type === 'file') {
162
+ data.file = node.file;
163
+ } else if (node.type === 'link') {
164
+ data.url = node.url;
165
+ } else if (node.type === 'group') {
166
+ data.label = node.label || data.label;
167
+ }
168
+
169
+ // Add PV extensions if present
170
+ if (pv) {
171
+ data.states = pv.states;
172
+ data.sources = pv.sources;
173
+ data.actions = pv.actions;
174
+ if (pv.dataSchema) {
175
+ data.dataSchema = pv.dataSchema;
176
+ }
177
+ }
178
+
179
+ return {
180
+ id: node.id,
181
+ type: pv?.shape || 'default',
182
+ position: { x: node.x, y: node.y },
183
+ data,
184
+ style: {
185
+ width: node.width,
186
+ height: node.height,
187
+ },
188
+ };
189
+ }
190
+
191
+ /**
192
+ * Get display label for a node
193
+ */
194
+ private static getNodeLabel(node: ExtendedCanvasNode): string {
195
+ if (node.pv?.nodeType) {
196
+ return node.pv.nodeType;
197
+ }
198
+ switch (node.type) {
199
+ case 'text':
200
+ // Use first line of text as label
201
+ const firstLine = node.text.split('\n')[0];
202
+ return firstLine.replace(/^#+ /, '').substring(0, 50);
203
+ case 'file':
204
+ // Use filename as label
205
+ return node.file.split('/').pop() || node.file;
206
+ case 'link':
207
+ return node.url;
208
+ case 'group':
209
+ return node.label || 'Group';
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Convert a single canvas edge to React Flow edge
215
+ */
216
+ private static convertEdge(edge: ExtendedCanvasEdge, canvas: ExtendedCanvas): ReactFlowEdge {
217
+ const pv = edge.pv;
218
+ const edgeTypeDef = pv?.edgeType ? canvas.pv?.edgeTypes?.[pv.edgeType] : undefined;
219
+ const color = resolveCanvasColor(edge.color) || edgeTypeDef?.color;
220
+
221
+ const rfEdge: ReactFlowEdge = {
222
+ id: edge.id,
223
+ source: edge.fromNode,
224
+ target: edge.toNode,
225
+ sourceHandle: sideToHandle(edge.fromSide),
226
+ targetHandle: sideToHandle(edge.toSide),
227
+ label: edge.label,
228
+ data: {
229
+ edgeType: pv?.edgeType || 'default',
230
+ style: pv?.style || edgeTypeDef?.style || 'solid',
231
+ color,
232
+ width: pv?.width || edgeTypeDef?.width || 2,
233
+ animation: pv?.animation || edgeTypeDef?.animation,
234
+ activatedBy: pv?.activatedBy || edgeTypeDef?.activatedBy,
235
+ },
236
+ style: {
237
+ stroke: color,
238
+ strokeWidth: pv?.width || edgeTypeDef?.width || 2,
239
+ strokeDasharray: styleToStrokeDasharray(pv?.style || edgeTypeDef?.style),
240
+ },
241
+ animated: pv?.style === 'animated' || edgeTypeDef?.style === 'animated',
242
+ };
243
+
244
+ // Add marker based on canvas endpoint settings
245
+ if (edge.toEnd !== 'none') {
246
+ rfEdge.markerEnd = {
247
+ type: 'arrowclosed',
248
+ color,
249
+ };
250
+ }
251
+
252
+ return rfEdge;
253
+ }
254
+
255
+ /**
256
+ * Convert Extended Canvas to internal NodeState/EdgeState format
257
+ */
258
+ static canvasToGraph(canvas: ExtendedCanvas): {
259
+ nodes: NodeState[];
260
+ edges: EdgeState[];
261
+ } {
262
+ const nodes: NodeState[] = [];
263
+ const edges: EdgeState[] = [];
264
+ const now = Date.now();
265
+
266
+ // Convert nodes
267
+ if (canvas.nodes) {
268
+ for (const node of canvas.nodes) {
269
+ const pv = node.pv;
270
+ nodes.push({
271
+ id: node.id,
272
+ type: pv?.nodeType || node.type,
273
+ data: {
274
+ label: this.getNodeLabel(node),
275
+ shape: pv?.shape || 'rectangle',
276
+ icon: pv?.icon,
277
+ // Color priority: pv.fill > node.color
278
+ color: pv?.fill || resolveCanvasColor(node.color),
279
+ // Stroke color for borders
280
+ stroke: pv?.stroke,
281
+ width: node.width,
282
+ height: node.height,
283
+ sources: pv?.sources || [],
284
+ actions: pv?.actions || [],
285
+ states: pv?.states,
286
+ canvasType: node.type,
287
+ ...(node.type === 'text' ? { text: node.text } : {}),
288
+ ...(node.type === 'file' ? { file: node.file } : {}),
289
+ ...(node.type === 'link' ? { url: node.url } : {}),
290
+ },
291
+ position: { x: node.x, y: node.y },
292
+ // Don't set a default state - only show state labels when explicitly set via events
293
+ state: undefined,
294
+ createdAt: now,
295
+ updatedAt: now,
296
+ });
297
+ }
298
+ }
299
+
300
+ // Convert edges
301
+ if (canvas.edges) {
302
+ for (const edge of canvas.edges) {
303
+ const pv = edge.pv;
304
+ const edgeTypeDef = pv?.edgeType ? canvas.pv?.edgeTypes?.[pv.edgeType] : undefined;
305
+
306
+ edges.push({
307
+ id: edge.id,
308
+ type: pv?.edgeType || 'default',
309
+ from: edge.fromNode,
310
+ to: edge.toNode,
311
+ data: {
312
+ label: edge.label,
313
+ style: pv?.style || edgeTypeDef?.style || 'solid',
314
+ color: resolveCanvasColor(edge.color) || edgeTypeDef?.color,
315
+ width: pv?.width || edgeTypeDef?.width,
316
+ animation: pv?.animation || edgeTypeDef?.animation,
317
+ activatedBy: pv?.activatedBy || edgeTypeDef?.activatedBy,
318
+ fromSide: edge.fromSide,
319
+ toSide: edge.toSide,
320
+ },
321
+ createdAt: now,
322
+ updatedAt: now,
323
+ });
324
+ }
325
+ }
326
+
327
+ return { nodes, edges };
328
+ }
329
+
330
+ /**
331
+ * Convert React Flow nodes/edges back to Extended Canvas format
332
+ */
333
+ static reactFlowToCanvas(
334
+ nodes: ReactFlowNode[],
335
+ edges: ReactFlowEdge[],
336
+ metadata?: { name?: string; version?: string; description?: string }
337
+ ): ExtendedCanvas {
338
+ const canvas: ExtendedCanvas = {
339
+ nodes: [],
340
+ edges: [],
341
+ pv: {
342
+ version: metadata?.version || '1.0.0',
343
+ name: metadata?.name || 'Untitled',
344
+ description: metadata?.description,
345
+ edgeTypes: {},
346
+ },
347
+ };
348
+
349
+ // Collect edge types
350
+ const edgeTypes = new Map<string, PVEdgeTypeDefinition>();
351
+
352
+ // Convert nodes
353
+ for (const node of nodes) {
354
+ const canvasNode: ExtendedCanvasNode = {
355
+ id: node.id,
356
+ type: node.data.canvasType || 'text',
357
+ x: node.position.x,
358
+ y: node.position.y,
359
+ width: node.style?.width || node.data.width || 150,
360
+ height: node.style?.height || node.data.height || 80,
361
+ text: node.data.text || node.data.label || '',
362
+ } as ExtendedCanvasNode;
363
+
364
+ // Add color if present
365
+ if (node.data.color) {
366
+ canvasNode.color = node.data.color;
367
+ }
368
+
369
+ // Add PV extension if there's custom data
370
+ if (node.data.nodeType || node.data.shape || node.data.sources?.length) {
371
+ canvasNode.pv = {
372
+ nodeType: node.data.nodeType || node.id,
373
+ shape: node.data.shape as PVNodeShape | undefined,
374
+ icon: node.data.icon,
375
+ states: node.data.states,
376
+ sources: node.data.sources,
377
+ actions: node.data.actions,
378
+ dataSchema: node.data.dataSchema,
379
+ };
380
+ }
381
+
382
+ canvas.nodes!.push(canvasNode);
383
+ }
384
+
385
+ // Convert edges
386
+ for (const edge of edges) {
387
+ const canvasEdge: ExtendedCanvasEdge = {
388
+ id: edge.id,
389
+ fromNode: edge.source,
390
+ toNode: edge.target,
391
+ fromSide: edge.sourceHandle as any,
392
+ toSide: edge.targetHandle as any,
393
+ label: edge.label as string | undefined,
394
+ };
395
+
396
+ // Add color
397
+ if (edge.style?.stroke) {
398
+ canvasEdge.color = edge.style.stroke;
399
+ }
400
+
401
+ // Add PV extension
402
+ if (edge.data?.edgeType) {
403
+ canvasEdge.pv = {
404
+ edgeType: edge.data.edgeType,
405
+ style: edge.data.style as PVEdgeStyle | undefined,
406
+ width: edge.data.width,
407
+ animation: edge.data.animation as { type: PVAnimationType; duration?: number; color?: string } | undefined,
408
+ activatedBy: edge.data.activatedBy,
409
+ };
410
+
411
+ // Collect edge type definition
412
+ if (!edgeTypes.has(edge.data.edgeType)) {
413
+ edgeTypes.set(edge.data.edgeType, {
414
+ style: edge.data.style as PVEdgeStyle | undefined,
415
+ color: edge.data.color,
416
+ width: edge.data.width,
417
+ animation: edge.data.animation as { type: PVAnimationType; duration?: number; color?: string } | undefined,
418
+ activatedBy: edge.data.activatedBy,
419
+ });
420
+ }
421
+ }
422
+
423
+ canvas.edges!.push(canvasEdge);
424
+ }
425
+
426
+ // Add collected edge types to canvas
427
+ canvas.pv!.edgeTypes = Object.fromEntries(edgeTypes);
428
+
429
+ return canvas;
430
+ }
431
+ }