symbiote-ui 0.3.0-alpha.4

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 (322) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/LICENSE +21 -0
  3. package/README.md +76 -0
  4. package/canvas/AutoLayout.js +731 -0
  5. package/canvas/Breadcrumb/Breadcrumb.css.js +75 -0
  6. package/canvas/Breadcrumb/Breadcrumb.js +96 -0
  7. package/canvas/Breadcrumb/Breadcrumb.tpl.js +7 -0
  8. package/canvas/CanvasConnectionRenderer.js +971 -0
  9. package/canvas/CanvasGraph/CanvasGraph.css.js +29 -0
  10. package/canvas/CanvasGraph/CanvasGraph.js +1697 -0
  11. package/canvas/CanvasGraph/CanvasGraphDrawState.js +280 -0
  12. package/canvas/CanvasGraph/CanvasGraphGeometry.js +194 -0
  13. package/canvas/CanvasViewport.js +550 -0
  14. package/canvas/ConnectionRenderer.js +1283 -0
  15. package/canvas/FlowSimulator.js +326 -0
  16. package/canvas/ForceLayout.js +226 -0
  17. package/canvas/ForceWorker.js +1303 -0
  18. package/canvas/FrameManager.js +223 -0
  19. package/canvas/GraphExplorerShell/GraphExplorerShell.css.js +136 -0
  20. package/canvas/GraphExplorerShell/GraphExplorerShell.js +129 -0
  21. package/canvas/GraphExplorerShell/GraphExplorerShell.tpl.js +12 -0
  22. package/canvas/GraphTabs/GraphTabs.css.js +101 -0
  23. package/canvas/GraphTabs/GraphTabs.js +189 -0
  24. package/canvas/GraphTabs/GraphTabs.tpl.js +12 -0
  25. package/canvas/LODManager.js +88 -0
  26. package/canvas/Minimap/Minimap.css.js +73 -0
  27. package/canvas/Minimap/Minimap.js +210 -0
  28. package/canvas/Minimap/Minimap.tpl.js +7 -0
  29. package/canvas/NodeCanvas/NodeCanvas.css.js +398 -0
  30. package/canvas/NodeCanvas/NodeCanvas.js +1499 -0
  31. package/canvas/NodeCanvas/NodeCanvas.tpl.js +22 -0
  32. package/canvas/NodeSearch/NodeSearch.css.js +97 -0
  33. package/canvas/NodeSearch/NodeSearch.js +140 -0
  34. package/canvas/NodeSearch/NodeSearch.tpl.js +25 -0
  35. package/canvas/NodeViewManager.js +748 -0
  36. package/canvas/PcbRouteDiagnostics.js +463 -0
  37. package/canvas/PcbRouter.js +1127 -0
  38. package/canvas/PinExpansion.js +134 -0
  39. package/canvas/PseudoConnection.js +84 -0
  40. package/canvas/SelectionSync.js +163 -0
  41. package/canvas/SubgraphManager.js +203 -0
  42. package/canvas/SubgraphRouter.js +452 -0
  43. package/canvas/ViewportActions.js +473 -0
  44. package/canvas/graph-explorer.js +339 -0
  45. package/canvas/graph-layout.js +148 -0
  46. package/canvas/graph-model.js +68 -0
  47. package/canvas/html-in-canvas.js +202 -0
  48. package/canvas/project-graph-builder.js +440 -0
  49. package/canvas/project-graph-model.js +183 -0
  50. package/chat/ChatComposer/ChatComposer.css.js +652 -0
  51. package/chat/ChatComposer/ChatComposer.js +304 -0
  52. package/chat/ChatList/ChatList.css.js +102 -0
  53. package/chat/ChatList/ChatList.js +99 -0
  54. package/chat/ChatList/ChatList.tpl.js +20 -0
  55. package/chat/ChatListItem/ChatListItem.css.js +117 -0
  56. package/chat/ChatListItem/ChatListItem.js +32 -0
  57. package/chat/ChatListItem/ChatListItem.tpl.js +17 -0
  58. package/chat/ChatMessageItem/ChatMessageItem.css.js +628 -0
  59. package/chat/ChatMessageItem/ChatMessageItem.js +156 -0
  60. package/chat/ChatSidebar/ChatSidebar.css.js +150 -0
  61. package/chat/ChatSidebar/ChatSidebar.js +230 -0
  62. package/chat/ChatSidebar/ChatSidebar.tpl.js +18 -0
  63. package/chat/ChatSidebar/constants.js +11 -0
  64. package/chat/ChatSidebarItem/ChatSidebarItem.css.js +445 -0
  65. package/chat/ChatSidebarItem/ChatSidebarItem.js +304 -0
  66. package/chat/ChatTranscript/ChatTranscript.css.js +90 -0
  67. package/chat/ChatTranscript/ChatTranscript.js +244 -0
  68. package/chat/chat-context.js +123 -0
  69. package/chat/message-model.js +156 -0
  70. package/cli.js +20 -0
  71. package/control/Button/Button.css.js +93 -0
  72. package/control/Button/Button.js +78 -0
  73. package/control/Button/Button.tpl.js +3 -0
  74. package/control/Field/Field.css.js +91 -0
  75. package/control/Field/Field.js +17 -0
  76. package/control/Field/Field.tpl.js +3 -0
  77. package/core/Connection.js +47 -0
  78. package/core/Editor.js +449 -0
  79. package/core/Frame.js +33 -0
  80. package/core/GraphMermaid.js +348 -0
  81. package/core/GraphText.js +228 -0
  82. package/core/Node.js +145 -0
  83. package/core/Portal.js +106 -0
  84. package/core/Socket.js +187 -0
  85. package/core/SubgraphNode.js +121 -0
  86. package/core/base-path.js +55 -0
  87. package/core/dom-utils.js +14 -0
  88. package/core/index.js +18 -0
  89. package/core/local-cache.js +26 -0
  90. package/core/state-sync.js +227 -0
  91. package/custom-elements.json +6380 -0
  92. package/discover.js +240 -0
  93. package/display/Badge/Badge.css.js +44 -0
  94. package/display/Badge/Badge.js +17 -0
  95. package/display/Badge/Badge.tpl.js +3 -0
  96. package/display/Banner/Banner.css.js +61 -0
  97. package/display/Banner/Banner.js +17 -0
  98. package/display/Banner/Banner.tpl.js +3 -0
  99. package/display/CodeBlock/CodeBlock.css.js +194 -0
  100. package/display/CodeBlock/CodeBlock.js +220 -0
  101. package/display/CodeBlock/CodeBlock.tpl.js +11 -0
  102. package/display/DataTable/DataTable.css.js +101 -0
  103. package/display/DataTable/DataTable.js +136 -0
  104. package/display/DataTable/DataTable.tpl.js +13 -0
  105. package/display/EmptyState/EmptyState.css.js +33 -0
  106. package/display/EmptyState/EmptyState.js +17 -0
  107. package/display/EmptyState/EmptyState.tpl.js +3 -0
  108. package/display/EventFeed/EventFeed.css.js +145 -0
  109. package/display/EventFeed/EventFeed.js +64 -0
  110. package/display/EventFeed/EventFeed.tpl.js +14 -0
  111. package/display/EventFeed/EventFeedItem.js +116 -0
  112. package/display/EventFeed/EventFeedItem.tpl.js +22 -0
  113. package/display/LoadingOverlay/LoadingOverlay.css.js +91 -0
  114. package/display/LoadingOverlay/LoadingOverlay.js +48 -0
  115. package/display/LoadingOverlay/LoadingOverlay.tpl.js +12 -0
  116. package/display/Metric/Metric.css.js +60 -0
  117. package/display/Metric/Metric.js +17 -0
  118. package/display/Metric/Metric.tpl.js +6 -0
  119. package/display/OutputGraphPreview/OutputGraphPreview.css.js +122 -0
  120. package/display/OutputGraphPreview/OutputGraphPreview.js +89 -0
  121. package/display/OutputGraphPreview/OutputGraphPreview.tpl.js +13 -0
  122. package/display/OutputListPreview/OutputListPreview.css.js +109 -0
  123. package/display/OutputListPreview/OutputListPreview.js +77 -0
  124. package/display/OutputListPreview/OutputListPreview.tpl.js +13 -0
  125. package/display/SourceEditor/SourceEditor.css.js +39 -0
  126. package/display/SourceEditor/SourceEditor.js +129 -0
  127. package/display/SourceEditor/SourceEditor.tpl.js +10 -0
  128. package/display/SourceViewer/SourceViewer.css.js +80 -0
  129. package/display/SourceViewer/SourceViewer.js +418 -0
  130. package/display/SourceViewer/SourceViewer.tpl.js +17 -0
  131. package/display/StatusRibbon/StatusRibbon.css.js +73 -0
  132. package/display/StatusRibbon/StatusRibbon.js +87 -0
  133. package/display/StatusRibbon/StatusRibbon.tpl.js +7 -0
  134. package/display/event-feed-adapter.js +72 -0
  135. package/display/format-utils.js +29 -0
  136. package/display/highlight.js +659 -0
  137. package/display/icons.js +37 -0
  138. package/display/markdown-formatter.js +60 -0
  139. package/display/network-approval-page.js +487 -0
  140. package/display/output-preview.js +261 -0
  141. package/effects/CellBg/CellBg.css.js +33 -0
  142. package/effects/CellBg/CellBg.js +410 -0
  143. package/effects/CellBg/CellBg.tpl.js +5 -0
  144. package/graph/canvas-adapter.js +223 -0
  145. package/graph/graph-algorithms.js +31 -0
  146. package/graph/index.js +46 -0
  147. package/graph/model.js +176 -0
  148. package/graph/project-graph-build.js +66 -0
  149. package/graph/project-graph-metadata.js +253 -0
  150. package/graph/project-package.js +128 -0
  151. package/graph/project-runtime.js +116 -0
  152. package/graph/project-transaction.js +284 -0
  153. package/graph/skeleton-utils.js +84 -0
  154. package/graph/theme-contract.js +36 -0
  155. package/graph/transaction-parser.js +56 -0
  156. package/icons/MaterialSymbols.js +69 -0
  157. package/icons/material-symbols-outlined-400.ttf +0 -0
  158. package/icons/material-symbols.css +24 -0
  159. package/index.js +95 -0
  160. package/inspector/InspectorPanel/InspectorPanel.css.js +375 -0
  161. package/inspector/InspectorPanel/InspectorPanel.js +368 -0
  162. package/inspector/InspectorPanel/InspectorPanel.tpl.js +96 -0
  163. package/inspector/TemplatePreview/TemplatePreview.css.js +104 -0
  164. package/inspector/TemplatePreview/TemplatePreview.js +145 -0
  165. package/inspector/TemplatePreview/TemplatePreview.tpl.js +33 -0
  166. package/interactions/ConnectFlow.js +304 -0
  167. package/interactions/Drag.js +104 -0
  168. package/interactions/Selector.js +133 -0
  169. package/interactions/SnapGrid.js +66 -0
  170. package/interactions/Zoom.js +139 -0
  171. package/layout/ActionZone/ActionZone.css.js +88 -0
  172. package/layout/ActionZone/ActionZone.js +261 -0
  173. package/layout/ActionZone/ActionZone.tpl.js +11 -0
  174. package/layout/CrossLayoutPortalBridge/CrossLayoutPortalBridge.js +255 -0
  175. package/layout/Layout/Layout.css.js +91 -0
  176. package/layout/Layout/Layout.js +637 -0
  177. package/layout/Layout/Layout.tpl.js +27 -0
  178. package/layout/LayoutNode/LayoutNode.css.js +302 -0
  179. package/layout/LayoutNode/LayoutNode.js +509 -0
  180. package/layout/LayoutNode/LayoutNode.tpl.js +39 -0
  181. package/layout/LayoutPreview/LayoutPreview.css.js +46 -0
  182. package/layout/LayoutPreview/LayoutPreview.js +102 -0
  183. package/layout/LayoutPreview/LayoutPreview.tpl.js +6 -0
  184. package/layout/LayoutRouter/LayoutRouter.js +274 -0
  185. package/layout/LayoutRouter/SectionRegistry.js +135 -0
  186. package/layout/LayoutRouter/routerSync.js +250 -0
  187. package/layout/LayoutSidebar/LayoutSidebar.css.js +411 -0
  188. package/layout/LayoutSidebar/LayoutSidebar.js +368 -0
  189. package/layout/LayoutSidebar/LayoutSidebar.tpl.js +26 -0
  190. package/layout/LayoutSidebar/SidebarSection.css.js +20 -0
  191. package/layout/LayoutSidebar/SidebarSection.js +184 -0
  192. package/layout/LayoutSidebar/SidebarSection.tpl.js +22 -0
  193. package/layout/LayoutTree.js +373 -0
  194. package/layout/PanelMenu/PanelMenu.css.js +43 -0
  195. package/layout/PanelMenu/PanelMenu.js +95 -0
  196. package/layout/PanelMenu/PanelMenu.tpl.js +17 -0
  197. package/layout/ProjectTabs/ProjectTabs.css.js +188 -0
  198. package/layout/ProjectTabs/ProjectTabs.js +77 -0
  199. package/layout/ProjectTabs/ProjectTabs.tpl.js +15 -0
  200. package/layout/index.js +40 -0
  201. package/list/ListDetailShell/ListDetailShell.css.js +128 -0
  202. package/list/ListDetailShell/ListDetailShell.js +72 -0
  203. package/list/ListDetailShell/ListDetailShell.tpl.js +36 -0
  204. package/list/ListItem/ListItem.css.js +111 -0
  205. package/list/ListItem/ListItem.js +66 -0
  206. package/list/ListItem/ListItem.tpl.js +18 -0
  207. package/locale/index.js +503 -0
  208. package/manifest/component-registry.js +2446 -0
  209. package/manifest/graph-schema.js +285 -0
  210. package/manifest/index.js +6 -0
  211. package/manifest/project-schema-catalog.js +246 -0
  212. package/manifest/rule-catalog.js +201 -0
  213. package/manifest/theme-catalog.js +2149 -0
  214. package/manifest/ui-schema-catalog.js +334 -0
  215. package/menu/ContextMenu/ContextMenu.css.js +61 -0
  216. package/menu/ContextMenu/ContextMenu.js +82 -0
  217. package/menu/ContextMenu/ContextMenu.tpl.js +19 -0
  218. package/navigation/QuickOpen/QuickOpen.css.js +92 -0
  219. package/navigation/QuickOpen/QuickOpen.js +185 -0
  220. package/navigation/QuickOpen/QuickOpen.tpl.js +15 -0
  221. package/navigation/quick-open-utils.js +101 -0
  222. package/node/CtrlItem/CtrlItem.css.js +41 -0
  223. package/node/CtrlItem/CtrlItem.js +24 -0
  224. package/node/CtrlItem/CtrlItem.tpl.js +17 -0
  225. package/node/GraphFrame/GraphFrame.css.js +66 -0
  226. package/node/GraphFrame/GraphFrame.js +32 -0
  227. package/node/GraphFrame/GraphFrame.tpl.js +13 -0
  228. package/node/GraphNode/GraphNode.css.js +815 -0
  229. package/node/GraphNode/GraphNode.js +173 -0
  230. package/node/GraphNode/GraphNode.tpl.js +33 -0
  231. package/node/NodeCallout/NodeCallout.css.js +91 -0
  232. package/node/NodeCallout/NodeCallout.js +281 -0
  233. package/node/NodeCallout/NodeCallout.tpl.js +8 -0
  234. package/node/NodeSocket/NodeSocket.css.js +68 -0
  235. package/node/NodeSocket/NodeSocket.js +26 -0
  236. package/node/NodeSocket/NodeSocket.tpl.js +7 -0
  237. package/node/PortItem/PortItem.css.js +93 -0
  238. package/node/PortItem/PortItem.js +87 -0
  239. package/node/PortItem/PortItem.tpl.js +10 -0
  240. package/package.json +165 -0
  241. package/palette/PaletteBrowser/PaletteBrowser.css.js +143 -0
  242. package/palette/PaletteBrowser/PaletteBrowser.js +152 -0
  243. package/palette/PaletteBrowser/PaletteBrowser.tpl.js +23 -0
  244. package/plugins/History.js +408 -0
  245. package/plugins/Readonly.js +60 -0
  246. package/rules/symbiote-3x.json +170 -0
  247. package/schemas/component-descriptor-v1.json +91 -0
  248. package/schemas/component-descriptor-v2.json +145 -0
  249. package/schemas/graph-model-v1.json +179 -0
  250. package/schemas/graph-v1.json +91 -0
  251. package/schemas/project-package-v1.json +102 -0
  252. package/schemas/project-transaction-v1.json +114 -0
  253. package/schemas/runtime-ui-v1.json +80 -0
  254. package/schemas/theme-rule-block-v1.json +73 -0
  255. package/shapes/CircleShape.js +79 -0
  256. package/shapes/CommentShape.js +35 -0
  257. package/shapes/DiamondShape.js +130 -0
  258. package/shapes/NodeShape.js +79 -0
  259. package/shapes/PillShape.js +91 -0
  260. package/shapes/RectShape.js +84 -0
  261. package/shapes/SVGShape.js +525 -0
  262. package/shapes/index.js +63 -0
  263. package/surface/Card/Card.css.js +57 -0
  264. package/surface/Card/Card.js +17 -0
  265. package/surface/Card/Card.tpl.js +3 -0
  266. package/themes/Palette.js +30 -0
  267. package/themes/Skin.js +113 -0
  268. package/themes/Theme.js +82 -0
  269. package/themes/carbon.js +135 -0
  270. package/themes/dark.js +140 -0
  271. package/themes/default-dark.js +714 -0
  272. package/themes/default-provider.css +635 -0
  273. package/themes/default-provider.js +718 -0
  274. package/themes/ebook.js +136 -0
  275. package/themes/grey.js +137 -0
  276. package/themes/light.js +139 -0
  277. package/themes/neon.js +138 -0
  278. package/themes/pcb.js +273 -0
  279. package/themes/synthwave.js +138 -0
  280. package/tokens/base.json +29 -0
  281. package/tokens/themes/carbon.json +11 -0
  282. package/tokens/themes/dark.json +12 -0
  283. package/tokens/themes/default-dark.json +1543 -0
  284. package/tokens/themes/default-provider.json +1543 -0
  285. package/tokens/themes/ebook.json +11 -0
  286. package/tokens/themes/grey.json +11 -0
  287. package/tokens/themes/light.json +12 -0
  288. package/tokens/themes/neon.json +11 -0
  289. package/tokens/themes/pcb.json +11 -0
  290. package/tokens/themes/synthwave.json +11 -0
  291. package/toolbar/QuickToolbar/QuickToolbar.css.js +152 -0
  292. package/toolbar/QuickToolbar/QuickToolbar.js +529 -0
  293. package/toolbar/QuickToolbar/QuickToolbar.tpl.js +34 -0
  294. package/tree/TreePanel/TreePanel.css.js +112 -0
  295. package/tree/TreePanel/TreePanel.js +147 -0
  296. package/tree/TreePanel/TreePanel.tpl.js +18 -0
  297. package/tree/TreeView/TreeView.css.js +122 -0
  298. package/tree/TreeView/TreeView.js +365 -0
  299. package/tree/TreeView/TreeView.tpl.js +10 -0
  300. package/ui/dialogs.js +221 -0
  301. package/ui/host-adapters.js +114 -0
  302. package/ui/index.js +660 -0
  303. package/ui/locale.js +50 -0
  304. package/ui/overlay-stack.js +89 -0
  305. package/ui/shared-styles.js +26 -0
  306. package/webmcp.js +37 -0
  307. package/xr/deep-graph.js +646 -0
  308. package/xr/emulation.js +198 -0
  309. package/xr/gesture.js +228 -0
  310. package/xr/html-canvas-renderer.js +472 -0
  311. package/xr/index.js +15 -0
  312. package/xr/layout-projection.js +1046 -0
  313. package/xr/panel-frame.js +128 -0
  314. package/xr/panel-host.js +267 -0
  315. package/xr/pointer.js +258 -0
  316. package/xr/scene-controller.js +242 -0
  317. package/xr/spatial-scene.js +212 -0
  318. package/xr/theme-bridge.js +105 -0
  319. package/xr/three-webxr-adapter.js +3439 -0
  320. package/xr/webgl-layer-renderer.js +419 -0
  321. package/xr/webxr.js +679 -0
  322. package/xr/workbench.js +516 -0
@@ -0,0 +1,348 @@
1
+ /**
2
+ * GraphMermaid — bidirectional Mermaid ↔ graph serialization
3
+ *
4
+ * Converts NodeEditor state to Mermaid flowchart syntax and back.
5
+ * Supports shapes, labeled connections, and subgraphs (frames).
6
+ *
7
+ * Mermaid shape mapping:
8
+ * circle → ((label))
9
+ * diamond → {label}
10
+ * pill → ([label])
11
+ * rect → [label]
12
+ * comment → >label]
13
+ *
14
+ * @module symbiote-node/core/GraphMermaid
15
+ */
16
+
17
+
18
+ const SHAPE_TO_MERMAID = {
19
+ circle: (id, label) => `${id}((${label}))`,
20
+ diamond: (id, label) => `${id}{${label}}`,
21
+ pill: (id, label) => `${id}([${label}])`,
22
+ rect: (id, label) => `${id}[${label}]`,
23
+ comment: (id, label) => `${id}>${label}]`,
24
+ };
25
+
26
+ /**
27
+ * Pattern matchers for Mermaid node shapes.
28
+ * Order matters — more specific patterns first.
29
+ * @type {Array<{re: RegExp, shape: string}>}
30
+ */
31
+ const MERMAID_SHAPE_PATTERNS = [
32
+ { re: /^(\w+)\(\((.+?)\)\)$/, shape: 'circle' },
33
+ { re: /^(\w+)\(\[(.+?)\]\)$/, shape: 'pill' },
34
+ { re: /^(\w+)\{(.+?)\}$/, shape: 'diamond' },
35
+ { re: /^(\w+)>(.+?)\]$/, shape: 'comment' },
36
+ { re: /^(\w+)\[(.+?)\]$/, shape: 'rect' },
37
+ ];
38
+
39
+
40
+ /**
41
+ * Arrow patterns with optional label.
42
+ * Supports: -->, --->, -->|label|, -- label -->
43
+ * @type {Array<{re: RegExp}>}
44
+ */
45
+ const ARROW_PATTERNS = [
46
+
47
+ /^(.+?)\s*-->\|([^|]*)\|\s*(.+)$/,
48
+
49
+ /^(.+?)\s*--\s+(.+?)\s+-->\s*(.+)$/,
50
+
51
+ /^(.+?)\s*-->\s*(.+)$/,
52
+ ];
53
+
54
+ /**
55
+ * Parse a node reference that might include inline shape definition.
56
+ *
57
+ * @param {string} raw - e.g. "trigger((Job Event))" or just "trigger"
58
+ * @returns {{ id: string, label: string|null, shape: string|null }}
59
+ */
60
+ function parseNodeRef(raw) {
61
+ let trimmed = raw.trim();
62
+ for (const { re, shape } of MERMAID_SHAPE_PATTERNS) {
63
+ let m = trimmed.match(re);
64
+ if (m) return { id: m[1], label: m[2], shape };
65
+ }
66
+
67
+ return { id: trimmed, label: null, shape: null };
68
+ }
69
+
70
+ /**
71
+ * Convert a NodeEditor to Mermaid flowchart syntax
72
+ *
73
+ * @param {import('./Editor.js').NodeEditor} editor
74
+ * @param {object} [options]
75
+ * @param {'LR'|'TB'|'RL'|'BT'} [options.direction='LR']
76
+ * @returns {string}
77
+ */
78
+ export function editorToMermaid(editor, options = {}) {
79
+ let { direction = 'LR' } = options;
80
+ let lines = [];
81
+
82
+ lines.push(`graph ${direction}`);
83
+
84
+
85
+ let frames = editor.getFrames();
86
+ let nodeToFrame = new Map();
87
+
88
+
89
+ let framedNodes = new Map();
90
+ let freeNodes = [];
91
+
92
+
93
+ let allNodes = editor.getNodes();
94
+ let allConnections = editor.getConnections();
95
+
96
+
97
+ if (frames.length) {
98
+
99
+
100
+ for (const frame of frames) {
101
+ let nodeIds = frame._nodeIds || [];
102
+ if (nodeIds.length) {
103
+ for (const nid of nodeIds) {
104
+ nodeToFrame.set(nid, frame);
105
+ }
106
+ }
107
+ }
108
+ }
109
+
110
+
111
+ for (const node of allNodes) {
112
+ if (nodeToFrame.has(node.id)) {
113
+ let frame = nodeToFrame.get(node.id);
114
+ if (!framedNodes.has(frame.id)) framedNodes.set(frame.id, []);
115
+ framedNodes.get(frame.id).push(node);
116
+ } else {
117
+ freeNodes.push(node);
118
+ }
119
+ }
120
+
121
+
122
+ for (const node of freeNodes) {
123
+ lines.push(' ' + nodeToMermaid(node));
124
+ }
125
+
126
+
127
+ if (framedNodes.size === 0 && frames.length > 0) {
128
+
129
+ if (freeNodes.length === 0) {
130
+ for (const node of allNodes) {
131
+ lines.push(' ' + nodeToMermaid(node));
132
+ }
133
+ }
134
+
135
+ for (const frame of frames) {
136
+ lines.push('');
137
+ lines.push(` subgraph ${sanitizeId(frame.label)}["${frame.label}"]`);
138
+ lines.push(' direction TB');
139
+ lines.push(' end');
140
+ }
141
+ } else {
142
+
143
+ for (const [frameId, nodes] of framedNodes) {
144
+ let frame = frames.find((f) => f.id === frameId);
145
+ if (!frame) continue;
146
+ lines.push('');
147
+ lines.push(` subgraph ${sanitizeId(frame.label)}["${frame.label}"]`);
148
+ lines.push(' direction TB');
149
+ for (const node of nodes) {
150
+ lines.push(' ' + nodeToMermaid(node));
151
+ }
152
+ lines.push(' end');
153
+ }
154
+ }
155
+
156
+
157
+ lines.push('');
158
+ for (const conn of allConnections) {
159
+ let label = conn.out === 'exec' ? '' : conn.out;
160
+ if (label) {
161
+ lines.push(` ${conn.from} -->|${label}| ${conn.to}`);
162
+ } else {
163
+ lines.push(` ${conn.from} --> ${conn.to}`);
164
+ }
165
+ }
166
+
167
+ return lines.join('\n');
168
+ }
169
+
170
+ /**
171
+ * Convert a single node to Mermaid declaration
172
+ * @param {import('./Node.js').Node} node
173
+ * @returns {string}
174
+ */
175
+ function nodeToMermaid(node) {
176
+ let shapeFn = SHAPE_TO_MERMAID[node.shape] || SHAPE_TO_MERMAID.rect;
177
+ return shapeFn(node.id, node.label);
178
+ }
179
+
180
+ /**
181
+ * Sanitize a string for use as Mermaid subgraph ID
182
+ * @param {string} str
183
+ * @returns {string}
184
+ */
185
+ function sanitizeId(str) {
186
+ return str.replace(/[^a-zA-Z0-9_]/g, '_');
187
+ }
188
+
189
+ /**
190
+ * Parse Mermaid flowchart text into graph data structure.
191
+ * Supports: node shapes, labeled arrows, subgraphs.
192
+ *
193
+ * @param {string} text
194
+ * @returns {{ nodes: Array, connections: Array, frames: Array, direction: string }}
195
+ */
196
+ export function mermaidToGraph(text) {
197
+ let nodes = new Map();
198
+ let connections = [];
199
+ let frames = [];
200
+ let frameStack = [];
201
+
202
+ let direction = 'LR';
203
+
204
+ /**
205
+ * Register a node from a parsed reference
206
+ * @param {{ id: string, label: string|null, shape: string|null }} ref
207
+ */
208
+ function registerNode(ref) {
209
+ if (!nodes.has(ref.id)) {
210
+ nodes.set(ref.id, {
211
+ id: ref.id,
212
+ name: ref.label || ref.id,
213
+ type: 'default',
214
+ shape: ref.shape || 'rect',
215
+ category: 'default',
216
+ });
217
+ } else if (ref.label && !nodes.get(ref.id).name) {
218
+
219
+ let existing = nodes.get(ref.id);
220
+ if (existing.name === existing.id) {
221
+ existing.name = ref.label;
222
+ }
223
+ if (ref.shape) existing.shape = ref.shape;
224
+ }
225
+
226
+ if (frameStack.length > 0) {
227
+ let currentFrame = frameStack[frameStack.length - 1];
228
+ if (!currentFrame._nodeIds) currentFrame._nodeIds = [];
229
+ if (!currentFrame._nodeIds.includes(ref.id)) {
230
+ currentFrame._nodeIds.push(ref.id);
231
+ }
232
+ }
233
+ }
234
+
235
+ for (const rawLine of text.split('\n')) {
236
+ let line = rawLine.trim();
237
+ if (!line || line.startsWith('%%')) continue;
238
+
239
+
240
+ let dirMatch = line.match(/^graph\s+(LR|RL|TB|BT|TD)\s*$/);
241
+ if (dirMatch) {
242
+ direction = dirMatch[1] === 'TD' ? 'TB' : dirMatch[1];
243
+ continue;
244
+ }
245
+
246
+
247
+ let flowMatch = line.match(/^flowchart\s+(LR|RL|TB|BT|TD)\s*$/);
248
+ if (flowMatch) {
249
+ direction = flowMatch[1] === 'TD' ? 'TB' : flowMatch[1];
250
+ continue;
251
+ }
252
+
253
+
254
+ let subMatch = line.match(/^subgraph\s+(\w+)(?:\["(.+?)"\])?\s*$/);
255
+ if (subMatch) {
256
+ let frame = {
257
+ label: subMatch[2] || subMatch[1],
258
+ color: '#4a9eff',
259
+ x: 0,
260
+ y: 0,
261
+ width: 400,
262
+ height: 300,
263
+ _nodeIds: [],
264
+ };
265
+ frameStack.push(frame);
266
+ frames.push(frame);
267
+ continue;
268
+ }
269
+
270
+
271
+ if (line === 'end') {
272
+ frameStack.pop();
273
+ continue;
274
+ }
275
+
276
+
277
+ if (line.match(/^direction\s+(LR|RL|TB|BT|TD)$/)) continue;
278
+
279
+
280
+ let matched = false;
281
+ for (const pattern of ARROW_PATTERNS) {
282
+ let m = line.match(pattern);
283
+ if (m) {
284
+ matched = true;
285
+ if (m.length === 4) {
286
+
287
+ let source = parseNodeRef(m[1]);
288
+ let label = m[2].trim();
289
+ let target = parseNodeRef(m[3]);
290
+ registerNode(source);
291
+ registerNode(target);
292
+ connections.push({
293
+ from: source.id,
294
+ out: label || 'exec',
295
+ to: target.id,
296
+ in: 'exec',
297
+ });
298
+ } else if (m.length === 3) {
299
+
300
+ let source = parseNodeRef(m[1]);
301
+ let target = parseNodeRef(m[2]);
302
+ registerNode(source);
303
+ registerNode(target);
304
+ connections.push({
305
+ from: source.id,
306
+ out: 'exec',
307
+ to: target.id,
308
+ in: 'exec',
309
+ });
310
+ }
311
+ break;
312
+ }
313
+ }
314
+
315
+
316
+ if (!matched) {
317
+
318
+ let parts = line.split(/\s*&\s*/);
319
+ for (const part of parts) {
320
+ let ref = parseNodeRef(part.trim());
321
+ if (
322
+ ref.id &&
323
+ ref.id !== 'end' &&
324
+ !ref.id.startsWith('style') &&
325
+ !ref.id.startsWith('class')
326
+ ) {
327
+ registerNode(ref);
328
+ }
329
+ }
330
+ }
331
+ }
332
+
333
+ return {
334
+ nodes: [...nodes.values()],
335
+ connections,
336
+ frames: frames.map((f) => ({
337
+ label: f.label,
338
+ color: f.color,
339
+ x: f.x,
340
+ y: f.y,
341
+ width: f.width,
342
+ height: f.height,
343
+ })),
344
+ direction,
345
+ };
346
+ }
347
+
348
+ export { editorToMermaid as default };
@@ -0,0 +1,228 @@
1
+ /**
2
+ * GraphText — bidirectional text ↔ graph serialization
3
+ *
4
+ * Converts NodeEditor state to human-readable text and back.
5
+ * Useful for debugging, LLM-based generation, and quick iteration.
6
+ *
7
+ * Text format:
8
+ * NODES:
9
+ * [○ trigger] Job Event: RU (queue/job-event) shape=circle
10
+ * [◇ switch_status] Status? (flow/switch) shape=diamond
11
+ *
12
+ * CONNECTIONS:
13
+ * trigger.exec --> switch_status.exec
14
+ * switch_status.created --> fmt_created.exec
15
+ *
16
+ * FRAMES:
17
+ * [Formatters] color=#5cd87a x=490 y=-10 w=260 h=520
18
+ *
19
+ * @module symbiote-node/core/GraphText
20
+ */
21
+
22
+ const SHAPE_ICONS = {
23
+ circle: '○',
24
+ diamond: '◇',
25
+ pill: '⊃',
26
+ rect: '□',
27
+ comment: '✎',
28
+ };
29
+
30
+ const ICON_SHAPES = Object.fromEntries(Object.entries(SHAPE_ICONS).map(([k, v]) => [v, k]));
31
+
32
+ /**
33
+ * Convert a NodeEditor to human-readable text
34
+ *
35
+ * @param {import('./Editor.js').NodeEditor} editor
36
+ * @param {Object<string, number[]>} [positions] - {nodeId: [x, y]}
37
+ * @returns {string}
38
+ */
39
+ export function editorToText(editor, positions = {}) {
40
+ let lines = [];
41
+
42
+
43
+ lines.push('NODES:');
44
+ for (const node of editor.getNodes()) {
45
+ let icon = SHAPE_ICONS[node.shape] || '□';
46
+ let ins = Object.keys(node.inputs);
47
+ let outs = Object.keys(node.outputs);
48
+ let line = `[${icon} ${node.id}] ${node.label} (${node.type})`;
49
+ if (node.shape !== 'rect') line += ` shape=${node.shape}`;
50
+ if (node.category !== 'default') line += ` cat=${node.category}`;
51
+ if (ins.length) line += ` in=[${ins.join(',')}]`;
52
+ if (outs.length) line += ` out=[${outs.join(',')}]`;
53
+ let pos = positions[node.id];
54
+ if (pos) line += ` @${pos[0]},${pos[1]}`;
55
+ lines.push(' ' + line);
56
+ }
57
+
58
+
59
+ lines.push('');
60
+ lines.push('CONNECTIONS:');
61
+ for (const conn of editor.getConnections()) {
62
+ lines.push(` ${conn.from}.${conn.out} --> ${conn.to}.${conn.in}`);
63
+ }
64
+
65
+
66
+ let frames = editor.getFrames();
67
+ if (frames.length) {
68
+ lines.push('');
69
+ lines.push('FRAMES:');
70
+ for (const frame of frames) {
71
+ lines.push(
72
+ ` [${frame.label}] color=${frame.color} x=${frame.x} y=${frame.y} w=${frame.width} h=${frame.height}`
73
+ );
74
+ }
75
+ }
76
+
77
+ return lines.join('\n');
78
+ }
79
+
80
+ /**
81
+ * Parse text representation back into graph data structure.
82
+ * Returns plain objects suitable for building a NodeEditor.
83
+ *
84
+ * @param {string} text
85
+ * @returns {{ nodes: Array, connections: Array, frames: Array, positions: Object }}
86
+ */
87
+ export function textToGraph(text) {
88
+ let nodes = [];
89
+ let connections = [];
90
+ let frames = [];
91
+ let positions = {};
92
+
93
+ let section = '';
94
+ for (const raw of text.split('\n')) {
95
+ let line = raw.trim();
96
+ if (!line) continue;
97
+
98
+ if (line === 'NODES:') {
99
+ section = 'nodes';
100
+ continue;
101
+ }
102
+ if (line === 'CONNECTIONS:') {
103
+ section = 'connections';
104
+ continue;
105
+ }
106
+ if (line === 'FRAMES:') {
107
+ section = 'frames';
108
+ continue;
109
+ }
110
+
111
+ if (section === 'nodes') {
112
+
113
+ let m = line.match(/^\[(.)\s+(\S+)\]\s+(.+?)\s+\(([^)]+)\)(.*)$/);
114
+ if (!m) continue;
115
+
116
+ let [, shapeIcon, id, name, type, rest] = m;
117
+ let shape = ICON_SHAPES[shapeIcon] || 'rect';
118
+ let category = rest.match(/cat=(\S+)/)?.[1] || 'default';
119
+ let posMatch = rest.match(/@(-?\d+),(-?\d+)/);
120
+
121
+ nodes.push({ id, name, type, shape, category });
122
+ if (posMatch) {
123
+ positions[id] = [parseInt(posMatch[1]), parseInt(posMatch[2])];
124
+ }
125
+ }
126
+
127
+ if (section === 'connections') {
128
+
129
+ let m = line.match(/^(\S+)\.(\S+)\s+-->\s+(\S+)\.(\S+)$/);
130
+ if (!m) continue;
131
+ let [, from, out, to, inp] = m;
132
+ connections.push({ from, out, to, in: inp });
133
+ }
134
+
135
+ if (section === 'frames') {
136
+
137
+ let m = line.match(/^\[([^\]]+)\]\s+(.*)$/);
138
+ if (!m) continue;
139
+ let [, label, rest] = m;
140
+ let color = rest.match(/color=(\S+)/)?.[1] || '#4a9eff';
141
+ let x = parseInt(rest.match(/x=(-?\d+)/)?.[1] || '0');
142
+ let y = parseInt(rest.match(/y=(-?\d+)/)?.[1] || '0');
143
+ let w = parseInt(rest.match(/w=(\d+)/)?.[1] || '400');
144
+ let h = parseInt(rest.match(/h=(\d+)/)?.[1] || '300');
145
+ frames.push({ label, color, x, y, width: w, height: h });
146
+ }
147
+ }
148
+
149
+ return { nodes, connections, frames, positions };
150
+ }
151
+
152
+ /**
153
+ * Build a NodeEditor from text representation
154
+ *
155
+ * @param {string} text
156
+ * @param {import('./Editor.js').NodeEditor} editor
157
+ * @param {{ Socket: Function, Node: Function, Input: Function, Output: Function, Connection: Function, Frame: Function }} classes
158
+ * @returns {{ editor: import('./Editor.js').NodeEditor, positions: Object }}
159
+ */
160
+ export function textToEditor(text, editor, classes) {
161
+ let { Node, Connection, Socket, Input, Output, Frame } = classes;
162
+ let { nodes, connections, frames, positions } = textToGraph(text);
163
+
164
+ let execSocket = new Socket('exec', { color: '#ffffff' });
165
+ let dataSocket = new Socket('data', { color: '#5cb8ff' });
166
+ let nodeMap = new Map();
167
+
168
+
169
+ let inPorts = {};
170
+ let outPorts = {};
171
+ for (const conn of connections) {
172
+ if (!inPorts[conn.to]) inPorts[conn.to] = new Set();
173
+ inPorts[conn.to].add(conn.in);
174
+ if (!outPorts[conn.from]) outPorts[conn.from] = new Set();
175
+ outPorts[conn.from].add(conn.out);
176
+ }
177
+
178
+ for (const n of nodes) {
179
+ let node = new Node(n.name, {
180
+ id: n.id,
181
+ type: n.type,
182
+ category: n.category,
183
+ shape: n.shape,
184
+ });
185
+
186
+ let ins = inPorts[n.id] || new Set();
187
+ let outs = outPorts[n.id] || new Set();
188
+
189
+ for (const port of ins) {
190
+ let isExec = port === 'exec' || port === 'trigger';
191
+ node.addInput(port, new Input(isExec ? execSocket : dataSocket, port === 'exec' ? '' : port));
192
+ }
193
+ for (const port of outs) {
194
+ let isExec = port === 'exec' || port === 'trigger';
195
+ node.addOutput(
196
+ port,
197
+ new Output(isExec ? execSocket : dataSocket, port === 'exec' ? '' : port)
198
+ );
199
+ }
200
+
201
+ editor.addNode(node);
202
+ nodeMap.set(n.id, node);
203
+ }
204
+
205
+ for (const conn of connections) {
206
+ let fromNode = nodeMap.get(conn.from);
207
+ let toNode = nodeMap.get(conn.to);
208
+ if (fromNode && toNode) {
209
+ editor.addConnection(new Connection(fromNode, conn.out, toNode, conn.in));
210
+ }
211
+ }
212
+
213
+ for (const f of frames) {
214
+ editor.addFrame(
215
+ new Frame(f.label, {
216
+ x: f.x,
217
+ y: f.y,
218
+ width: f.width,
219
+ height: f.height,
220
+ color: f.color,
221
+ })
222
+ );
223
+ }
224
+
225
+ return { editor, positions };
226
+ }
227
+
228
+ export { editorToText as default };
package/core/Node.js ADDED
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Node — graph node with typed ports and controls
3
+ *
4
+ * Compatible with symbiote-node GraphNode structure.
5
+ * Ports are explicit objects (unlike symbiote-node where they're implicit).
6
+ *
7
+ * @module symbiote-node/core/Node
8
+ */
9
+
10
+ import { uid } from './Socket.js';
11
+
12
+ export class Node {
13
+ /**
14
+ * @param {string} label - Display name
15
+ * @param {object} [options]
16
+ * @param {string} [options.id] - Custom ID (default: auto-generated)
17
+ * @param {string} [options.type] - Node type identifier (e.g. 'ai/llm')
18
+ * @param {string} [options.category] - Category for styling (server/instance/control)
19
+ * @param {string} [options.shape] - Shape name (rect/pill/circle/diamond/comment)
20
+ * @param {string} [options.icon] - Material icon name for visual rendering
21
+ */
22
+ constructor(label, options = {}) {
23
+ /** @type {string} */
24
+ this.id = options.id || uid('nd');
25
+
26
+ /** @type {string} */
27
+ this.label = label;
28
+
29
+ /** @type {string} */
30
+ this.type = options.type || 'default';
31
+
32
+ /** @type {string} */
33
+ this.category = options.category || 'default';
34
+
35
+ /** @type {string} */
36
+ this.shape = options.shape || 'rect';
37
+
38
+ /** @type {string} */
39
+ this.icon = options.icon || '';
40
+
41
+ /** @type {Object<string, Input>} */
42
+ this.inputs = {};
43
+
44
+ /** @type {Object<string, Output>} */
45
+ this.outputs = {};
46
+
47
+ /** @type {Object<string, import('./Socket.js').Control>} */
48
+ this.controls = {};
49
+
50
+ /** @type {Object<string, *>} */
51
+ this.params = {};
52
+
53
+ /** @type {boolean} */
54
+ this.selected = false;
55
+
56
+ /** @type {boolean} */
57
+ this.collapsed = false;
58
+
59
+ /** @type {boolean} */
60
+ this.muted = false;
61
+ }
62
+
63
+ /**
64
+ * Check if input exists
65
+ * @param {string} key
66
+ * @returns {boolean}
67
+ */
68
+ hasInput(key) {
69
+ return key in this.inputs;
70
+ }
71
+
72
+ /**
73
+ * Add input port
74
+ * @param {string} key - Port key
75
+ * @param {Input} input - Input instance
76
+ */
77
+ addInput(key, input) {
78
+ if (this.hasInput(key)) throw new Error(`input '${key}' already exists`);
79
+ this.inputs[key] = input;
80
+ }
81
+
82
+ /**
83
+ * Remove input port
84
+ * @param {string} key
85
+ */
86
+ removeInput(key) {
87
+ delete this.inputs[key];
88
+ }
89
+
90
+ /**
91
+ * Check if output exists
92
+ * @param {string} key
93
+ * @returns {boolean}
94
+ */
95
+ hasOutput(key) {
96
+ return key in this.outputs;
97
+ }
98
+
99
+ /**
100
+ * Add output port
101
+ * @param {string} key - Port key
102
+ * @param {Output} output - Output instance
103
+ */
104
+ addOutput(key, output) {
105
+ if (this.hasOutput(key)) throw new Error(`output '${key}' already exists`);
106
+ this.outputs[key] = output;
107
+ }
108
+
109
+ /**
110
+ * Remove output port
111
+ * @param {string} key
112
+ */
113
+ removeOutput(key) {
114
+ delete this.outputs[key];
115
+ }
116
+
117
+ /**
118
+ * Check if control exists
119
+ * @param {string} key
120
+ * @returns {boolean}
121
+ */
122
+ hasControl(key) {
123
+ return key in this.controls;
124
+ }
125
+
126
+ /**
127
+ * Add control widget
128
+ * @param {string} key
129
+ * @param {import('./Socket.js').Control} control
130
+ */
131
+ addControl(key, control) {
132
+ if (this.hasControl(key)) throw new Error(`control '${key}' already exists`);
133
+ this.controls[key] = control;
134
+ }
135
+
136
+ /**
137
+ * Remove control widget
138
+ * @param {string} key
139
+ */
140
+ removeControl(key) {
141
+ delete this.controls[key];
142
+ }
143
+ }
144
+
145
+ export { Node as default };