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,202 @@
1
+ export const HTML_IN_CANVAS_RENDERER_NAME = 'html-in-canvas';
2
+
3
+ export const HTML_IN_CANVAS_APIS = Object.freeze({
4
+ layoutSubtreeAttribute: 'layoutsubtree',
5
+ paintEvent: 'paint',
6
+ canvas2dDraw: 'drawElementImage',
7
+ elementCapture: 'captureElementImage',
8
+ webglTextureUpload: 'texElementImage2D',
9
+ webgpuTextureCopy: 'copyElementImageToTexture',
10
+ elementTransform: 'getElementTransform',
11
+ requestPaint: 'requestPaint',
12
+ });
13
+
14
+ export const HTML_IN_CANVAS_RENDERER = Object.freeze({
15
+ name: HTML_IN_CANVAS_RENDERER_NAME,
16
+ status: 'experimental',
17
+ specifier: 'symbiote-ui/ui',
18
+ description: 'Experimental DOM-to-canvas renderer adapter for packaged Chromium hosts and origin-trial browsers.',
19
+ modes: ['canvas2d', 'offscreen2d', 'webgl', 'webgpu'],
20
+ fallback: 'dom-overlay',
21
+ requiredCanvasAttribute: HTML_IN_CANVAS_APIS.layoutSubtreeAttribute,
22
+ capabilities: [
23
+ 'dom-content-in-canvas',
24
+ 'interactive-canvas-ui',
25
+ 'accessible-canvas-ui',
26
+ 'text-selection',
27
+ 'native-form-controls',
28
+ 'offscreen-worker-snapshots',
29
+ 'canvas-texture-upload',
30
+ 'feature-detected-fallback',
31
+ ],
32
+ apis: HTML_IN_CANVAS_APIS,
33
+ });
34
+
35
+ function hasFn(source, name) {
36
+ return typeof source?.[name] === 'function';
37
+ }
38
+
39
+ function getPrototype(target, name) {
40
+ return target?.[name]?.prototype || null;
41
+ }
42
+
43
+ function supportsLayoutSubtree(target) {
44
+ let canvasProto = getPrototype(target, 'HTMLCanvasElement');
45
+ if (canvasProto && 'layoutSubtree' in canvasProto) return true;
46
+ let canvas = target?.document?.createElement?.('canvas');
47
+ return Boolean(canvas && 'layoutSubtree' in canvas);
48
+ }
49
+
50
+ export function getHtmlInCanvasSupport(target = globalThis) {
51
+ let canvas2dProto = getPrototype(target, 'CanvasRenderingContext2D');
52
+ let offscreen2dProto = getPrototype(target, 'OffscreenCanvasRenderingContext2D');
53
+ let webglProto = getPrototype(target, 'WebGLRenderingContext');
54
+ let webgl2Proto = getPrototype(target, 'WebGL2RenderingContext');
55
+ let gpuQueueProto = getPrototype(target, 'GPUQueue');
56
+ let canvasProto = getPrototype(target, 'HTMLCanvasElement');
57
+ let offscreenCanvasProto = getPrototype(target, 'OffscreenCanvas');
58
+
59
+ let canvas2d = hasFn(canvas2dProto, HTML_IN_CANVAS_APIS.canvas2dDraw);
60
+ let offscreen2d = hasFn(offscreen2dProto, HTML_IN_CANVAS_APIS.canvas2dDraw);
61
+ let webgl = hasFn(webglProto, HTML_IN_CANVAS_APIS.webglTextureUpload) ||
62
+ hasFn(webgl2Proto, HTML_IN_CANVAS_APIS.webglTextureUpload);
63
+ let webgpu = hasFn(gpuQueueProto, HTML_IN_CANVAS_APIS.webgpuTextureCopy);
64
+ let elementCapture = hasFn(canvasProto, HTML_IN_CANVAS_APIS.elementCapture);
65
+ let elementTransform = hasFn(canvasProto, HTML_IN_CANVAS_APIS.elementTransform) ||
66
+ hasFn(offscreenCanvasProto, HTML_IN_CANVAS_APIS.elementTransform);
67
+ let requestPaint = hasFn(canvasProto, HTML_IN_CANVAS_APIS.requestPaint);
68
+
69
+ return {
70
+ name: HTML_IN_CANVAS_RENDERER_NAME,
71
+ status: 'experimental',
72
+ supported: canvas2d || offscreen2d || webgl || webgpu,
73
+ fallback: HTML_IN_CANVAS_RENDERER.fallback,
74
+ modes: {
75
+ canvas2d,
76
+ offscreen2d,
77
+ webgl,
78
+ webgpu,
79
+ },
80
+ apis: {
81
+ ...HTML_IN_CANVAS_APIS,
82
+ canvas2dDrawAvailable: canvas2d,
83
+ offscreen2dDrawAvailable: offscreen2d,
84
+ webglTextureUploadAvailable: webgl,
85
+ webgpuTextureCopyAvailable: webgpu,
86
+ elementCaptureAvailable: elementCapture,
87
+ elementTransformAvailable: elementTransform,
88
+ requestPaintAvailable: requestPaint,
89
+ layoutSubtreeAvailable: supportsLayoutSubtree(target),
90
+ },
91
+ };
92
+ }
93
+
94
+ export function setupHtmlInCanvas(canvas) {
95
+ if (!canvas || typeof canvas.setAttribute !== 'function') return false;
96
+ canvas.setAttribute(HTML_IN_CANVAS_APIS.layoutSubtreeAttribute, '');
97
+ return true;
98
+ }
99
+
100
+ export function requestHtmlInCanvasPaint(canvas) {
101
+ if (hasFn(canvas, HTML_IN_CANVAS_APIS.requestPaint)) {
102
+ canvas.requestPaint();
103
+ return true;
104
+ }
105
+ return false;
106
+ }
107
+
108
+ export function getHtmlInCanvasChangedElements(event) {
109
+ if (!event || !Array.isArray(event.changedElements)) return [];
110
+ return event.changedElements;
111
+ }
112
+
113
+ export function captureHtmlElementImage(canvas, element) {
114
+ if (!hasFn(canvas, HTML_IN_CANVAS_APIS.elementCapture)) {
115
+ return { captured: false, reason: 'unsupported' };
116
+ }
117
+ return {
118
+ captured: true,
119
+ elementImage: canvas[HTML_IN_CANVAS_APIS.elementCapture](element),
120
+ };
121
+ }
122
+
123
+ export function closeHtmlElementImage(elementImage) {
124
+ if (!hasFn(elementImage, 'close')) return false;
125
+ elementImage.close();
126
+ return true;
127
+ }
128
+
129
+ export function drawHtmlElement2d(ctx, element, options = {}) {
130
+ if (!hasFn(ctx, HTML_IN_CANVAS_APIS.canvas2dDraw)) {
131
+ return { rendered: false, mode: 'canvas2d', reason: 'unsupported' };
132
+ }
133
+
134
+ let args = Array.isArray(options.rect)
135
+ ? [element, ...options.rect.map((value) => Number(value))]
136
+ : [element, Number(options.x ?? 0), Number(options.y ?? 0)];
137
+ if (options.width != null && options.height != null) {
138
+ args.push(Number(options.width), Number(options.height));
139
+ }
140
+
141
+ let transform = ctx[HTML_IN_CANVAS_APIS.canvas2dDraw](...args);
142
+ if (options.syncTransform !== false && transform && element?.style) {
143
+ element.style.transform = transform.toString();
144
+ }
145
+
146
+ return { rendered: true, mode: 'canvas2d', transform };
147
+ }
148
+
149
+ export function uploadHtmlElementToWebGLTexture(gl, element, options = {}) {
150
+ if (!hasFn(gl, HTML_IN_CANVAS_APIS.webglTextureUpload)) {
151
+ return { rendered: false, mode: 'webgl', reason: 'unsupported' };
152
+ }
153
+
154
+ let target = options.target ?? gl.TEXTURE_2D;
155
+ let level = options.level ?? 0;
156
+ let internalFormat = options.internalFormat ?? gl.RGBA;
157
+ let format = options.format ?? gl.RGBA;
158
+ let type = options.type ?? gl.UNSIGNED_BYTE;
159
+ gl[HTML_IN_CANVAS_APIS.webglTextureUpload](target, level, internalFormat, format, type, element);
160
+ return { rendered: true, mode: 'webgl' };
161
+ }
162
+
163
+ export function copyHtmlElementToWebGPUTexture(queue, element, options = {}) {
164
+ if (!hasFn(queue, HTML_IN_CANVAS_APIS.webgpuTextureCopy)) {
165
+ return { rendered: false, mode: 'webgpu', reason: 'unsupported' };
166
+ }
167
+ if (!options.destination) {
168
+ return { rendered: false, mode: 'webgpu', reason: 'missing-destination' };
169
+ }
170
+
171
+ let args = [element, options.destination];
172
+ if (options.copySize) args.push(options.copySize);
173
+ queue[HTML_IN_CANVAS_APIS.webgpuTextureCopy](...args);
174
+ return { rendered: true, mode: 'webgpu' };
175
+ }
176
+
177
+ export function getHtmlElementCanvasTransform(canvas, element, matrix) {
178
+ if (!hasFn(canvas, HTML_IN_CANVAS_APIS.elementTransform)) return null;
179
+ return canvas[HTML_IN_CANVAS_APIS.elementTransform](element, matrix) || null;
180
+ }
181
+
182
+ export function createHtmlInCanvasAdapter(options = {}) {
183
+ let target = options.globalThis || globalThis;
184
+ let support = getHtmlInCanvasSupport(target);
185
+
186
+ return {
187
+ ...HTML_IN_CANVAS_RENDERER,
188
+ support,
189
+ canRender(mode = 'canvas2d') {
190
+ return Boolean(support.modes[mode]);
191
+ },
192
+ setupCanvas: setupHtmlInCanvas,
193
+ requestPaint: requestHtmlInCanvasPaint,
194
+ getChangedElements: getHtmlInCanvasChangedElements,
195
+ captureElementImage: captureHtmlElementImage,
196
+ closeElementImage: closeHtmlElementImage,
197
+ draw2d: drawHtmlElement2d,
198
+ uploadWebGLTexture: uploadHtmlElementToWebGLTexture,
199
+ copyWebGPUTexture: copyHtmlElementToWebGPUTexture,
200
+ getElementTransform: getHtmlElementCanvasTransform,
201
+ };
202
+ }
@@ -0,0 +1,440 @@
1
+ import { NodeEditor } from '../core/Editor.js'
2
+ import { Node } from '../core/Node.js'
3
+ import { SubgraphNode } from '../core/SubgraphNode.js'
4
+ import { Connection } from '../core/Connection.js'
5
+ import { Socket, Input, Output } from '../core/Socket.js'
6
+ import { computeAutoLayout } from './AutoLayout.js'
7
+ import { baseName, collectSkeletonFiles, dirOf, resolveImport } from '../graph/skeleton-utils.js'
8
+
9
+ // ── Socket types (for wire coloring) ──
10
+ const S_IMPORT = new Socket('import')
11
+ S_IMPORT.color = 'var(--sn-dot-input)'
12
+ const S_EXPORT = new Socket('export')
13
+ S_EXPORT.color = 'var(--sn-dot-output)'
14
+
15
+ /**
16
+ * Build a file-level graph from skeleton data.
17
+ * Each file becomes a Node, each import relationship becomes a Connection.
18
+ *
19
+ * @param {object} skeleton - skeleton from get_skeleton
20
+ * @returns {{ editor: NodeEditor, fileMap: Map<string, string> }}
21
+ */
22
+ function buildFileGraph(skeleton) {
23
+ const editor = new NodeEditor()
24
+ const fileMap = new Map() // filePath → nodeId
25
+ const dirMap = new Map() // dirPath → nodeId (hub nodes)
26
+
27
+ const { files, assetFiles } = collectSkeletonFiles(skeleton)
28
+
29
+ if (files.size === 0) return { editor, fileMap }
30
+
31
+ // Group files by directory
32
+ const dirFiles = new Map()
33
+ for (const file of files) {
34
+ const dir = dirOf(file)
35
+ if (!dirFiles.has(dir)) dirFiles.set(dir, [])
36
+ dirFiles.get(dir).push(file)
37
+ }
38
+
39
+ // Phase 2 candidate: create directory hub nodes when LOD zoom expansion is ready.
40
+ // Hub nodes without connections create disconnected groups — skip for now
41
+
42
+ // Create file nodes (standard HTML nodes with icons)
43
+ for (const file of files) {
44
+ const dir = dirOf(file)
45
+ const label = baseName(file)
46
+ const isAsset = assetFiles.has(file)
47
+ const node = new Node(label, {
48
+ type: isAsset ? 'asset' : 'file',
49
+ category: isAsset ? 'asset' : 'file',
50
+ })
51
+ node.params = { path: file, dir }
52
+
53
+ // Every file has one output (exports) and one input (imports)
54
+ node.addOutput('out', new Output(S_EXPORT, ''))
55
+ node.addInput('in', new Input(S_IMPORT, ''))
56
+
57
+ editor.addNode(node)
58
+ fileMap.set(file, node.id)
59
+ }
60
+
61
+ // Build import edges from skeleton.I (file-level import map)
62
+ // skeleton.I[file] = [source1, source2, ...]
63
+ const edgesAdded = new Set()
64
+ for (const [srcFile, sources] of Object.entries(skeleton.I || {})) {
65
+ const srcId = fileMap.get(srcFile)
66
+ if (!srcId) continue
67
+
68
+ for (const impPath of sources) {
69
+ // Skip node builtins and external packages
70
+ if (impPath.startsWith('node:') || (!impPath.startsWith('.') && !impPath.startsWith('/'))) continue
71
+
72
+ const targetFile = resolveImport(impPath, srcFile, files)
73
+ if (!targetFile) continue
74
+
75
+ const tgtId = fileMap.get(targetFile)
76
+ if (!tgtId || tgtId === srcId) continue
77
+
78
+ const edgeKey = `${srcId}->${tgtId}`
79
+ if (edgesAdded.has(edgeKey)) continue
80
+ edgesAdded.add(edgeKey)
81
+
82
+ const srcNode = editor.getNode(srcId)
83
+ const tgtNode = editor.getNode(tgtId)
84
+ try {
85
+ const conn = new Connection(srcNode, 'out', tgtNode, 'in')
86
+ // Phase 3: tag cross-directory connections as "via"
87
+ const srcDir = dirOf(srcFile)
88
+ const tgtDir = dirOf(targetFile)
89
+ if (srcDir !== tgtDir) {
90
+ conn._via = true
91
+ conn._srcDir = srcDir
92
+ conn._tgtDir = tgtDir
93
+ }
94
+ editor.addConnection(conn)
95
+ } catch {
96
+ // Skip invalid connections
97
+ }
98
+ }
99
+ }
100
+
101
+ // Hub node: find node with highest connectivity → module category
102
+ const connCounts = new Map()
103
+ for (const conn of editor.getConnections()) {
104
+ connCounts.set(conn.from, (connCounts.get(conn.from) || 0) + 1)
105
+ connCounts.set(conn.to, (connCounts.get(conn.to) || 0) + 1)
106
+ }
107
+ let maxConns = 0
108
+ let hubId = null
109
+ for (const [nodeId, count] of connCounts) {
110
+ if (count > maxConns) {
111
+ maxConns = count
112
+ hubId = nodeId
113
+ }
114
+ }
115
+ if (hubId) {
116
+ const hubNode = editor.getNode(hubId)
117
+ if (hubNode && hubNode.options) {
118
+ hubNode.options.category = 'module'
119
+ }
120
+ }
121
+
122
+ // ── Build Reverse ID Lookup ──
123
+ const idToPath = new Map()
124
+ for (const [path, id] of fileMap.entries()) idToPath.set(id, path)
125
+
126
+ return { editor, fileMap, dirMap, dirFiles, idToPath }
127
+ }
128
+
129
+ /**
130
+ * Build a hierarchical SubgraphNode graph:
131
+ * Level 0: directories (SubgraphNode)
132
+ * Level 1: files inside directories (SubgraphNode or Node)
133
+ * Level 2: functions/exports inside files (Node)
134
+ *
135
+ * @param {object} skeleton
136
+ * @returns {{ editor: NodeEditor, fileMap: Map<string, string> }}
137
+ */
138
+ function buildStructuredGraph(skeleton) {
139
+ const editor = new NodeEditor()
140
+ const fileMap = new Map()
141
+ const symbolMap = new Map()
142
+ const L = skeleton.L || {} // legend: abbreviation → full name
143
+ const N = skeleton.n || {} // classes: className → { f, m, ... }
144
+
145
+ // Build class-name set for classification
146
+ const classNames = new Set(Object.keys(N))
147
+ // Map file → set of class names defined in it
148
+ const fileClasses = new Map()
149
+ for (const [className, data] of Object.entries(N)) {
150
+ if (data.f) {
151
+ if (!fileClasses.has(data.f)) fileClasses.set(data.f, new Set())
152
+ fileClasses.get(data.f).add(className)
153
+ }
154
+ }
155
+
156
+ const { files, assetFiles } = collectSkeletonFiles(skeleton)
157
+
158
+ if (files.size === 0) return { editor, fileMap }
159
+
160
+ // Group files by directory
161
+ const dirFiles = new Map()
162
+ for (const file of files) {
163
+ const dir = dirOf(file)
164
+ if (!dirFiles.has(dir)) dirFiles.set(dir, [])
165
+ dirFiles.get(dir).push(file)
166
+ }
167
+
168
+ /**
169
+ * Classify a file based on its content and name
170
+ * @param {string} file
171
+ * @returns {string} category
172
+ */
173
+ function classifyFile(file) {
174
+ if (assetFiles.has(file)) return 'asset'
175
+ const name = baseName(file).toLowerCase()
176
+ const classes = fileClasses.get(file)
177
+ if (classes && classes.size > 0) return 'class'
178
+ if (name === 'index.js' || name === 'index.mjs') return 'module'
179
+ if (name.includes('test') || name.includes('spec')) return 'control'
180
+ if (name.includes('config') || name.includes('.json')) return 'data'
181
+ return 'file'
182
+ }
183
+
184
+ /**
185
+ * Resolve export abbreviation to full name
186
+ * @param {string} abbr
187
+ * @returns {string}
188
+ */
189
+ function resolveName(abbr) {
190
+ return L[abbr] || abbr
191
+ }
192
+
193
+ // ── Phase 1: Create all Directory SubgraphNodes (without nesting yet) ──
194
+ const dirNodeMap = new Map() // dirPath → nodeId
195
+ const dirSubgraphs = new Map() // dirPath → SubgraphNode instance
196
+
197
+ // Sort directories by depth (shortest first) so parents are created before children
198
+ const sortedDirs = [...dirFiles.keys()].sort((a, b) => {
199
+ const dA = a.split('/').filter(Boolean).length
200
+ const dB = b.split('/').filter(Boolean).length
201
+ return dA - dB || a.localeCompare(b)
202
+ })
203
+
204
+ for (const dir of sortedDirs) {
205
+ const dirFileList = dirFiles.get(dir)
206
+
207
+ // Root directory './' is NOT a node — its contents go directly into the root editor
208
+ const isRoot = (dir === './')
209
+ const targetEditor = isRoot ? editor : null
210
+
211
+ let dirSubgraph = null
212
+ let innerEditor
213
+
214
+ if (isRoot) {
215
+ innerEditor = editor
216
+ } else {
217
+ const dirLabel = dir.replace(/\/$/, '').split('/').pop() || 'root'
218
+ dirSubgraph = new SubgraphNode(dirLabel, {
219
+ category: 'directory',
220
+ })
221
+ dirSubgraph.params = { path: dir, isDirectory: true }
222
+ dirSubgraph.addOutput('out', new Output(S_EXPORT, ''))
223
+ dirSubgraph.addInput('in', new Input(S_IMPORT, ''))
224
+ innerEditor = dirSubgraph.getInnerEditor()
225
+ }
226
+
227
+ // ── File nodes inside this directory ──
228
+ for (const file of dirFileList) {
229
+ const fileLabel = baseName(file)
230
+ const exports = skeleton.X?.[file] || []
231
+ const fileCategory = classifyFile(file)
232
+ const classes = fileClasses.get(file)
233
+
234
+ let fileNode
235
+ if (exports.length > 0) {
236
+ fileNode = new SubgraphNode(fileLabel, {
237
+ category: fileCategory,
238
+ })
239
+ fileNode.params = { path: file, dir, calculatedHeight: 60 + exports.length * 50 }
240
+
241
+ const fileInnerEditor = fileNode.getInnerEditor()
242
+ for (const abbr of exports) {
243
+ const abbrId = typeof abbr === 'object' ? abbr.id : abbr
244
+ const fullName = resolveName(abbrId)
245
+ const isClass = classes && classes.has(fullName)
246
+ const fnNode = new Node(fullName, {
247
+ type: isClass ? 'class' : 'function',
248
+ category: isClass ? 'class' : 'function',
249
+ })
250
+ fnNode.params = { name: fullName, file }
251
+ symbolMap.set(fnNode.id, fnNode.params)
252
+ fileInnerEditor.addNode(fnNode)
253
+ }
254
+ } else {
255
+ fileNode = new Node(fileLabel, {
256
+ type: 'file',
257
+ category: fileCategory,
258
+ })
259
+ fileNode.params = { path: file, dir }
260
+ }
261
+
262
+ fileNode.addOutput('out', new Output(S_EXPORT, ''))
263
+ fileNode.addInput('in', new Input(S_IMPORT, ''))
264
+
265
+ innerEditor.addNode(fileNode)
266
+ fileMap.set(file, fileNode.id)
267
+ }
268
+
269
+ // ── File-level import edges within this directory ──
270
+ const edgesAdded = new Set()
271
+ for (const [srcFile, sources] of Object.entries(skeleton.I || {})) {
272
+ const srcId = fileMap.get(srcFile)
273
+ if (!srcId) continue
274
+ const srcDir = dirOf(srcFile)
275
+ if (srcDir !== dir) continue
276
+
277
+ for (const impPath of sources) {
278
+ if (impPath.startsWith('node:') || (!impPath.startsWith('.') && !impPath.startsWith('/'))) continue
279
+ const targetFile = resolveImport(impPath, srcFile, files)
280
+ if (!targetFile) continue
281
+
282
+ const tgtId = fileMap.get(targetFile)
283
+ if (!tgtId || tgtId === srcId) continue
284
+ if (dirOf(targetFile) !== dir) continue
285
+
286
+ const edgeKey = `${srcId}->${tgtId}`
287
+ if (edgesAdded.has(edgeKey)) continue
288
+ edgesAdded.add(edgeKey)
289
+
290
+ const srcNode = innerEditor.getNode(srcId)
291
+ const tgtNode = innerEditor.getNode(tgtId)
292
+ if (srcNode && tgtNode) {
293
+ try {
294
+ innerEditor.addConnection(new Connection(srcNode, 'out', tgtNode, 'in'))
295
+ } catch { /* skip */ }
296
+ }
297
+ }
298
+ }
299
+
300
+ if (dirSubgraph) {
301
+ dirSubgraphs.set(dir, dirSubgraph)
302
+ dirNodeMap.set(dir, dirSubgraph.id)
303
+ }
304
+ }
305
+
306
+ // ── Phase 2: Nest child directories inside parent directories ──
307
+ // Root './' is not a node, so its children go directly into root editor.
308
+ for (const dir of sortedDirs) {
309
+ if (dir === './') continue // root dir contents already in root editor
310
+ const dirSubgraph = dirSubgraphs.get(dir)
311
+ if (!dirSubgraph) continue
312
+
313
+ // Find parent directory
314
+ const segments = dir.replace(/\/$/, '').split('/')
315
+ segments.pop()
316
+
317
+ let parentDir = null
318
+ while (segments.length > 0) {
319
+ const candidate = segments.join('/') + '/'
320
+ if (dirSubgraphs.has(candidate)) {
321
+ parentDir = candidate
322
+ break
323
+ }
324
+ segments.pop()
325
+ }
326
+
327
+ if (parentDir) {
328
+ // Nest inside parent's inner editor
329
+ const parentSubgraph = dirSubgraphs.get(parentDir)
330
+ parentSubgraph.getInnerEditor().addNode(dirSubgraph)
331
+ } else {
332
+ // No parent (or parent is './') → add to root editor
333
+ editor.addNode(dirSubgraph)
334
+ }
335
+ }
336
+
337
+ // ── Cross-directory edges ──
338
+ // Edges between directories that share the same parent go into that parent's inner editor.
339
+ // Edges between top-level directories go into the root editor.
340
+ const crossEdges = new Set()
341
+ for (const [srcFile, sources] of Object.entries(skeleton.I || {})) {
342
+ const srcDir = dirOf(srcFile)
343
+ const srcDirId = dirNodeMap.get(srcDir)
344
+ if (!srcDirId) continue
345
+
346
+ for (const impPath of sources) {
347
+ if (impPath.startsWith('node:') || (!impPath.startsWith('.') && !impPath.startsWith('/'))) continue
348
+ const targetFile = resolveImport(impPath, srcFile, files)
349
+ if (!targetFile) continue
350
+
351
+ const tgtDir = dirOf(targetFile)
352
+ if (tgtDir === srcDir) continue
353
+
354
+ const tgtDirId = dirNodeMap.get(tgtDir)
355
+ if (!tgtDirId || tgtDirId === srcDirId) continue
356
+
357
+ const edgeKey = `${srcDirId}->${tgtDirId}`
358
+ if (crossEdges.has(edgeKey)) continue
359
+ crossEdges.add(edgeKey)
360
+
361
+ // Find the common parent editor that contains BOTH directory nodes
362
+ // Walk up both paths to find shared ancestor
363
+ const srcSegments = srcDir.replace(/\/$/, '').split('/')
364
+ const tgtSegments = tgtDir.replace(/\/$/, '').split('/')
365
+
366
+ // Find common prefix
367
+ let commonLen = 0
368
+ while (commonLen < srcSegments.length && commonLen < tgtSegments.length &&
369
+ srcSegments[commonLen] === tgtSegments[commonLen]) {
370
+ commonLen++
371
+ }
372
+ const commonPath = commonLen > 0 ? srcSegments.slice(0, commonLen).join('/') + '/' : null
373
+
374
+ // The editor that holds both nodes is the common parent's inner editor,
375
+ // or the root editor if they share no parent.
376
+ let targetEditor = editor // default: root editor
377
+ if (commonPath && dirSubgraphs.has(commonPath)) {
378
+ targetEditor = dirSubgraphs.get(commonPath).getInnerEditor()
379
+ }
380
+
381
+ const srcNode = targetEditor.getNode(srcDirId)
382
+ const tgtNode = targetEditor.getNode(tgtDirId)
383
+ if (srcNode && tgtNode) {
384
+ try {
385
+ targetEditor.addConnection(new Connection(srcNode, 'out', tgtNode, 'in'))
386
+ } catch { /* skip */ }
387
+ }
388
+ }
389
+ }
390
+
391
+ // ── Pre-compute inner positions for drill-down (recursive) ──
392
+ const symbolNodes = [] // Track internal symbol nodes for idToPath linking
393
+
394
+ function computeInnerPositions(subgraph) {
395
+ if (!subgraph._isSubgraph) return
396
+ const inner = subgraph.getInnerEditor()
397
+ const innerPos = computeAutoLayout(inner, { nodeHeight: 80, gapY: 100 })
398
+ subgraph.setInnerPositions(innerPos)
399
+
400
+ let minX = 0, maxX = 260
401
+ let minY = 0, maxY = 60
402
+
403
+ for (const pos of Object.values(innerPos)) {
404
+ if (pos.x < minX) minX = pos.x
405
+ if (pos.x + 260 > maxX) maxX = pos.x + 260
406
+ if (pos.y < minY) minY = pos.y
407
+ if (pos.y + 60 > maxY) maxY = pos.y + 60
408
+ }
409
+
410
+ subgraph.params = subgraph.params || {}
411
+ subgraph.params.calculatedWidth = maxX - minX + 60
412
+ subgraph.params.calculatedHeight = maxY - minY + 100
413
+
414
+ for (const childNode of inner.getNodes()) {
415
+ if (childNode._isSubgraph) {
416
+ computeInnerPositions(childNode)
417
+ // If it's a file node (has params.path and no isDirectory), collect symbols
418
+ if (childNode.params?.path && !childNode.params?.isDirectory) {
419
+ const fileInner = childNode.getInnerEditor()
420
+ for (const fnNode of fileInner.getNodes()) {
421
+ symbolNodes.push({ id: fnNode.id, file: childNode.params.path })
422
+ }
423
+ }
424
+ }
425
+ }
426
+ }
427
+
428
+ for (const rootNode of editor.getNodes()) {
429
+ computeInnerPositions(rootNode)
430
+ }
431
+
432
+ // ── Build Reverse ID Lookup ──
433
+ const idToPath = new Map()
434
+ for (const [path, id] of fileMap.entries()) idToPath.set(id, path)
435
+ for (const [path, id] of dirNodeMap.entries()) idToPath.set(id, path)
436
+ for (const node of symbolNodes) idToPath.set(node.id, node.file)
437
+
438
+ return { editor, fileMap, dirFiles, dirNodeMap, idToPath, symbolMap }
439
+ }
440
+ export { buildFileGraph, buildStructuredGraph }