@dxos/plugin-explorer 0.8.4-main.e8ec1fe → 0.8.4-main.effb148878

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 (271) hide show
  1. package/LICENSE +102 -5
  2. package/dist/lib/neutral/ExplorerArticle-LLNHXWNG.mjs +420 -0
  3. package/dist/lib/neutral/ExplorerArticle-LLNHXWNG.mjs.map +7 -0
  4. package/dist/lib/neutral/ExplorerPlugin.mjs +10 -0
  5. package/dist/lib/neutral/capabilities/index.mjs +11 -0
  6. package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
  7. package/dist/lib/neutral/chunk-7FSP4SPO.mjs +69 -0
  8. package/dist/lib/neutral/chunk-7FSP4SPO.mjs.map +7 -0
  9. package/dist/lib/neutral/chunk-CWN2BELW.mjs +287 -0
  10. package/dist/lib/neutral/chunk-CWN2BELW.mjs.map +7 -0
  11. package/dist/lib/neutral/chunk-GRJXLL4Z.mjs +25 -0
  12. package/dist/lib/neutral/chunk-GRJXLL4Z.mjs.map +7 -0
  13. package/dist/lib/neutral/chunk-IKHJV3Q4.mjs +20 -0
  14. package/dist/lib/neutral/chunk-IKHJV3Q4.mjs.map +7 -0
  15. package/dist/lib/neutral/chunk-LL3PXKB5.mjs +40 -0
  16. package/dist/lib/neutral/chunk-LL3PXKB5.mjs.map +7 -0
  17. package/dist/lib/{node-esm/chunk-NPIP4VEH.mjs → neutral/components/index.mjs} +890 -314
  18. package/dist/lib/neutral/components/index.mjs.map +7 -0
  19. package/dist/lib/neutral/containers/index.mjs +9 -0
  20. package/dist/lib/neutral/containers/index.mjs.map +7 -0
  21. package/dist/lib/neutral/create-object-F6TKVAGV.mjs +39 -0
  22. package/dist/lib/neutral/create-object-F6TKVAGV.mjs.map +7 -0
  23. package/dist/lib/neutral/hooks/index.mjs +45 -0
  24. package/dist/lib/neutral/hooks/index.mjs.map +7 -0
  25. package/dist/lib/neutral/index.mjs +14 -0
  26. package/dist/lib/neutral/meta.json +1 -0
  27. package/dist/lib/{browser → neutral}/meta.mjs +1 -1
  28. package/dist/lib/neutral/plugin.mjs +12 -0
  29. package/dist/lib/neutral/plugin.mjs.map +7 -0
  30. package/dist/lib/neutral/react-surface-APBW2VQG.mjs +26 -0
  31. package/dist/lib/neutral/react-surface-APBW2VQG.mjs.map +7 -0
  32. package/dist/lib/neutral/testing/index.mjs +193 -0
  33. package/dist/lib/neutral/testing/index.mjs.map +7 -0
  34. package/dist/lib/neutral/translations.mjs +33 -0
  35. package/dist/lib/neutral/translations.mjs.map +7 -0
  36. package/dist/lib/{browser → neutral}/types/index.mjs +1 -2
  37. package/dist/types/data/cities.d.ts +4 -4
  38. package/dist/types/data/cities.d.ts.map +1 -1
  39. package/dist/types/data/countries-110m.d.ts +19 -22
  40. package/dist/types/data/countries-110m.d.ts.map +1 -1
  41. package/dist/types/src/ExplorerPlugin.d.ts +3 -1
  42. package/dist/types/src/ExplorerPlugin.d.ts.map +1 -1
  43. package/dist/types/src/ExplorerPlugin.test.d.ts +2 -0
  44. package/dist/types/src/ExplorerPlugin.test.d.ts.map +1 -0
  45. package/dist/types/src/capabilities/create-object.d.ts +11 -0
  46. package/dist/types/src/capabilities/create-object.d.ts.map +1 -0
  47. package/dist/types/src/capabilities/index.d.ts +8 -2
  48. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  49. package/dist/types/src/capabilities/react-surface.d.ts +3 -2
  50. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  51. package/dist/types/src/components/Chart/Chart.d.ts +1 -1
  52. package/dist/types/src/components/Chart/Chart.d.ts.map +1 -1
  53. package/dist/types/src/components/Chart/Chart.stories.d.ts +4 -1
  54. package/dist/types/src/components/Chart/Chart.stories.d.ts.map +1 -1
  55. package/dist/types/src/components/Globe/Globe.d.ts +1 -1
  56. package/dist/types/src/components/Globe/Globe.d.ts.map +1 -1
  57. package/dist/types/src/components/Globe/Globe.stories.d.ts +5 -2
  58. package/dist/types/src/components/Globe/Globe.stories.d.ts.map +1 -1
  59. package/dist/types/src/components/Graph/CanvasForceGraph.d.ts +13 -0
  60. package/dist/types/src/components/Graph/CanvasForceGraph.d.ts.map +1 -0
  61. package/dist/types/src/components/Graph/CanvasForceGraph.stories.d.ts +17 -0
  62. package/dist/types/src/components/Graph/CanvasForceGraph.stories.d.ts.map +1 -0
  63. package/dist/types/src/components/Graph/ForceGraph.d.ts +12 -5
  64. package/dist/types/src/components/Graph/ForceGraph.d.ts.map +1 -1
  65. package/dist/types/src/components/Graph/ForceGraph.stories.d.ts +4 -2
  66. package/dist/types/src/components/Graph/ForceGraph.stories.d.ts.map +1 -1
  67. package/dist/types/src/components/Graph/{adapter.d.ts → graph-adapter.d.ts} +2 -2
  68. package/dist/types/src/components/Graph/graph-adapter.d.ts.map +1 -0
  69. package/dist/types/src/components/Graph/index.d.ts +1 -1
  70. package/dist/types/src/components/Graph/index.d.ts.map +1 -1
  71. package/dist/types/src/components/Lattice/Lattice.d.ts +20 -0
  72. package/dist/types/src/components/Lattice/Lattice.d.ts.map +1 -0
  73. package/dist/types/src/components/Lattice/Lattice.stories.d.ts +8 -0
  74. package/dist/types/src/components/Lattice/Lattice.stories.d.ts.map +1 -0
  75. package/dist/types/src/components/Lattice/index.d.ts +2 -0
  76. package/dist/types/src/components/Lattice/index.d.ts.map +1 -0
  77. package/dist/types/src/components/Tree/EdgeBundling.stories.d.ts +21 -0
  78. package/dist/types/src/components/Tree/EdgeBundling.stories.d.ts.map +1 -0
  79. package/dist/types/src/components/Tree/Tree.d.ts +20 -23
  80. package/dist/types/src/components/Tree/Tree.d.ts.map +1 -1
  81. package/dist/types/src/components/Tree/Tree.stories.d.ts +5 -12
  82. package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
  83. package/dist/types/src/components/Tree/index.d.ts +2 -0
  84. package/dist/types/src/components/Tree/index.d.ts.map +1 -1
  85. package/dist/types/src/components/Tree/layout/HierarchicalEdgeBundling.d.ts +37 -2
  86. package/dist/types/src/components/Tree/layout/HierarchicalEdgeBundling.d.ts.map +1 -1
  87. package/dist/types/src/components/Tree/layout/RadialTree.d.ts +35 -2
  88. package/dist/types/src/components/Tree/layout/RadialTree.d.ts.map +1 -1
  89. package/dist/types/src/components/Tree/layout/TidyTree.d.ts +24 -2
  90. package/dist/types/src/components/Tree/layout/TidyTree.d.ts.map +1 -1
  91. package/dist/types/src/components/Tree/layout/hierarchy.d.ts +17 -0
  92. package/dist/types/src/components/Tree/layout/hierarchy.d.ts.map +1 -0
  93. package/dist/types/src/components/Tree/layout/index.d.ts +5 -4
  94. package/dist/types/src/components/Tree/layout/index.d.ts.map +1 -1
  95. package/dist/types/src/components/Tree/layout/slots.d.ts +7 -0
  96. package/dist/types/src/components/Tree/layout/slots.d.ts.map +1 -0
  97. package/dist/types/src/components/Tree/layout/useContainerSize.d.ts +15 -0
  98. package/dist/types/src/components/Tree/layout/useContainerSize.d.ts.map +1 -0
  99. package/dist/types/src/components/Tree/types/tree.d.ts +45 -22
  100. package/dist/types/src/components/Tree/types/tree.d.ts.map +1 -1
  101. package/dist/types/src/components/Tree/types/types.d.ts +14 -4
  102. package/dist/types/src/components/Tree/types/types.d.ts.map +1 -1
  103. package/dist/types/src/components/index.d.ts +1 -2
  104. package/dist/types/src/components/index.d.ts.map +1 -1
  105. package/dist/types/src/containers/ExplorerArticle/ExplorerArticle.d.ts +13 -0
  106. package/dist/types/src/containers/ExplorerArticle/ExplorerArticle.d.ts.map +1 -0
  107. package/dist/types/src/containers/ExplorerArticle/ExplorerArticle.stories.d.ts +30 -0
  108. package/dist/types/src/containers/ExplorerArticle/ExplorerArticle.stories.d.ts.map +1 -0
  109. package/dist/types/src/containers/ExplorerArticle/Visualization.d.ts +24 -0
  110. package/dist/types/src/containers/ExplorerArticle/Visualization.d.ts.map +1 -0
  111. package/dist/types/src/containers/ExplorerArticle/experimental.stories.d.ts +7 -0
  112. package/dist/types/src/containers/ExplorerArticle/experimental.stories.d.ts.map +1 -0
  113. package/dist/types/src/containers/ExplorerArticle/index.d.ts +2 -0
  114. package/dist/types/src/containers/ExplorerArticle/index.d.ts.map +1 -0
  115. package/dist/types/src/containers/ExplorerArticle/variants.d.ts +9 -0
  116. package/dist/types/src/containers/ExplorerArticle/variants.d.ts.map +1 -0
  117. package/dist/types/src/containers/index.d.ts +3 -0
  118. package/dist/types/src/containers/index.d.ts.map +1 -0
  119. package/dist/types/src/hooks/useGraphModel.d.ts +2 -2
  120. package/dist/types/src/hooks/useGraphModel.d.ts.map +1 -1
  121. package/dist/types/src/index.d.ts +1 -3
  122. package/dist/types/src/index.d.ts.map +1 -1
  123. package/dist/types/src/meta.d.ts +2 -2
  124. package/dist/types/src/meta.d.ts.map +1 -1
  125. package/dist/types/src/plugin.d.ts +3 -0
  126. package/dist/types/src/plugin.d.ts.map +1 -0
  127. package/dist/types/src/{components/Tree/testing → testing}/generator.d.ts +1 -1
  128. package/dist/types/src/testing/generator.d.ts.map +1 -0
  129. package/dist/types/src/testing/index.d.ts +4 -0
  130. package/dist/types/src/testing/index.d.ts.map +1 -0
  131. package/dist/types/src/testing/relations.d.ts +47 -0
  132. package/dist/types/src/testing/relations.d.ts.map +1 -0
  133. package/dist/types/src/translations.d.ts +31 -22
  134. package/dist/types/src/translations.d.ts.map +1 -1
  135. package/dist/types/src/types/ExplorerAction.d.ts +1 -18
  136. package/dist/types/src/types/ExplorerAction.d.ts.map +1 -1
  137. package/dist/types/src/types/Graph.d.ts +14 -25
  138. package/dist/types/src/types/Graph.d.ts.map +1 -1
  139. package/dist/types/src/util/index.d.ts +3 -0
  140. package/dist/types/src/util/index.d.ts.map +1 -0
  141. package/dist/types/src/util/node-color.d.ts +13 -0
  142. package/dist/types/src/util/node-color.d.ts.map +1 -0
  143. package/dist/types/src/{components → util}/plot.d.ts +1 -1
  144. package/dist/types/src/util/plot.d.ts.map +1 -0
  145. package/dist/types/tsconfig.tsbuildinfo +1 -1
  146. package/package.json +114 -62
  147. package/src/ExplorerPlugin.test.ts +26 -0
  148. package/src/ExplorerPlugin.tsx +15 -56
  149. package/src/capabilities/create-object.ts +36 -0
  150. package/src/capabilities/index.ts +3 -3
  151. package/src/capabilities/react-surface.tsx +24 -19
  152. package/src/components/Chart/Chart.stories.tsx +16 -23
  153. package/src/components/Chart/Chart.tsx +1 -1
  154. package/src/components/Globe/Globe.stories.tsx +19 -22
  155. package/src/components/Globe/Globe.tsx +1 -1
  156. package/src/components/Graph/CanvasForceGraph.stories.tsx +83 -0
  157. package/src/components/Graph/CanvasForceGraph.tsx +124 -0
  158. package/src/components/Graph/ForceGraph.stories.tsx +83 -42
  159. package/src/components/Graph/ForceGraph.tsx +105 -85
  160. package/src/components/Graph/{adapter.ts → graph-adapter.ts} +14 -8
  161. package/src/components/Graph/index.ts +1 -1
  162. package/src/components/Lattice/Lattice.stories.tsx +90 -0
  163. package/src/components/Lattice/Lattice.tsx +182 -0
  164. package/src/components/Lattice/index.ts +5 -0
  165. package/src/components/Tree/EdgeBundling.stories.tsx +144 -0
  166. package/src/components/Tree/Tree.stories.tsx +20 -38
  167. package/src/components/Tree/Tree.tsx +69 -95
  168. package/src/components/Tree/index.ts +2 -0
  169. package/src/components/Tree/layout/HierarchicalEdgeBundling.tsx +335 -0
  170. package/src/components/Tree/layout/RadialTree.tsx +242 -0
  171. package/src/components/Tree/layout/TidyTree.tsx +246 -0
  172. package/src/components/Tree/layout/hierarchy.ts +32 -0
  173. package/src/components/Tree/layout/index.ts +5 -5
  174. package/src/components/Tree/layout/slots.ts +19 -0
  175. package/src/components/Tree/layout/useContainerSize.ts +43 -0
  176. package/src/components/Tree/types/tree.test.ts +6 -5
  177. package/src/components/Tree/types/tree.ts +42 -26
  178. package/src/components/Tree/types/types.ts +38 -29
  179. package/src/components/index.ts +1 -4
  180. package/src/containers/ExplorerArticle/ExplorerArticle.stories.tsx +142 -0
  181. package/src/containers/ExplorerArticle/ExplorerArticle.tsx +112 -0
  182. package/src/containers/ExplorerArticle/Visualization.tsx +497 -0
  183. package/src/containers/ExplorerArticle/experimental.stories.tsx +446 -0
  184. package/src/containers/ExplorerArticle/index.ts +5 -0
  185. package/src/containers/ExplorerArticle/variants.ts +37 -0
  186. package/src/containers/index.ts +7 -0
  187. package/src/hooks/useGraphModel.ts +25 -14
  188. package/src/index.ts +1 -4
  189. package/src/meta.ts +24 -6
  190. package/src/plugin.ts +9 -0
  191. package/src/{components/Tree/testing → testing}/generator.ts +5 -3
  192. package/src/testing/index.ts +9 -0
  193. package/src/testing/relations.ts +192 -0
  194. package/src/translations.ts +16 -13
  195. package/src/types/ExplorerAction.ts +9 -19
  196. package/src/types/Graph.ts +25 -25
  197. package/src/typings.d.ts +8 -0
  198. package/src/util/index.ts +6 -0
  199. package/src/util/node-color.ts +23 -0
  200. package/src/{components → util}/plot.ts +16 -4
  201. package/dist/lib/browser/ExplorerContainer-NOLLVUTE.mjs +0 -50
  202. package/dist/lib/browser/ExplorerContainer-NOLLVUTE.mjs.map +0 -7
  203. package/dist/lib/browser/chunk-2MKBRIUT.mjs +0 -31
  204. package/dist/lib/browser/chunk-2MKBRIUT.mjs.map +0 -7
  205. package/dist/lib/browser/chunk-6BVXZQPP.mjs +0 -188
  206. package/dist/lib/browser/chunk-6BVXZQPP.mjs.map +0 -7
  207. package/dist/lib/browser/chunk-ARBGXQFH.mjs +0 -11089
  208. package/dist/lib/browser/chunk-ARBGXQFH.mjs.map +0 -7
  209. package/dist/lib/browser/chunk-JDSUIUNR.mjs +0 -80
  210. package/dist/lib/browser/chunk-JDSUIUNR.mjs.map +0 -7
  211. package/dist/lib/browser/chunk-UBHZGWZQ.mjs +0 -24
  212. package/dist/lib/browser/chunk-UBHZGWZQ.mjs.map +0 -7
  213. package/dist/lib/browser/index.mjs +0 -119
  214. package/dist/lib/browser/index.mjs.map +0 -7
  215. package/dist/lib/browser/intent-resolver-YS5LZC3A.mjs +0 -31
  216. package/dist/lib/browser/intent-resolver-YS5LZC3A.mjs.map +0 -7
  217. package/dist/lib/browser/meta.json +0 -1
  218. package/dist/lib/browser/react-surface-BVTCOVLK.mjs +0 -35
  219. package/dist/lib/browser/react-surface-BVTCOVLK.mjs.map +0 -7
  220. package/dist/lib/node-esm/ExplorerContainer-N3S5KSUX.mjs +0 -51
  221. package/dist/lib/node-esm/ExplorerContainer-N3S5KSUX.mjs.map +0 -7
  222. package/dist/lib/node-esm/chunk-3ODK27PU.mjs +0 -33
  223. package/dist/lib/node-esm/chunk-3ODK27PU.mjs.map +0 -7
  224. package/dist/lib/node-esm/chunk-CRSVAZNA.mjs +0 -190
  225. package/dist/lib/node-esm/chunk-CRSVAZNA.mjs.map +0 -7
  226. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
  227. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +0 -7
  228. package/dist/lib/node-esm/chunk-MS72BATS.mjs +0 -81
  229. package/dist/lib/node-esm/chunk-MS72BATS.mjs.map +0 -7
  230. package/dist/lib/node-esm/chunk-NPIP4VEH.mjs.map +0 -7
  231. package/dist/lib/node-esm/chunk-UXZM5VJB.mjs +0 -26
  232. package/dist/lib/node-esm/chunk-UXZM5VJB.mjs.map +0 -7
  233. package/dist/lib/node-esm/index.mjs +0 -120
  234. package/dist/lib/node-esm/index.mjs.map +0 -7
  235. package/dist/lib/node-esm/intent-resolver-VCEC67WX.mjs +0 -32
  236. package/dist/lib/node-esm/intent-resolver-VCEC67WX.mjs.map +0 -7
  237. package/dist/lib/node-esm/meta.json +0 -1
  238. package/dist/lib/node-esm/meta.mjs +0 -9
  239. package/dist/lib/node-esm/react-surface-4HFEX52O.mjs +0 -36
  240. package/dist/lib/node-esm/react-surface-4HFEX52O.mjs.map +0 -7
  241. package/dist/lib/node-esm/types/index.mjs +0 -12
  242. package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
  243. package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
  244. package/dist/types/src/components/ExplorerContainer.d.ts +0 -9
  245. package/dist/types/src/components/ExplorerContainer.d.ts.map +0 -1
  246. package/dist/types/src/components/Graph/D3ForceGraph.d.ts +0 -14
  247. package/dist/types/src/components/Graph/D3ForceGraph.d.ts.map +0 -1
  248. package/dist/types/src/components/Graph/D3ForceGraph.stories.d.ts +0 -15
  249. package/dist/types/src/components/Graph/D3ForceGraph.stories.d.ts.map +0 -1
  250. package/dist/types/src/components/Graph/adapter.d.ts.map +0 -1
  251. package/dist/types/src/components/Graph/testing.d.ts +0 -14
  252. package/dist/types/src/components/Graph/testing.d.ts.map +0 -1
  253. package/dist/types/src/components/Tree/testing/generator.d.ts.map +0 -1
  254. package/dist/types/src/components/Tree/testing/index.d.ts +0 -2
  255. package/dist/types/src/components/Tree/testing/index.d.ts.map +0 -1
  256. package/dist/types/src/components/plot.d.ts.map +0 -1
  257. package/src/capabilities/intent-resolver.ts +0 -21
  258. package/src/components/ExplorerContainer.tsx +0 -54
  259. package/src/components/Graph/D3ForceGraph.stories.tsx +0 -78
  260. package/src/components/Graph/D3ForceGraph.tsx +0 -101
  261. package/src/components/Graph/testing.ts +0 -55
  262. package/src/components/Tree/layout/HierarchicalEdgeBundling.ts +0 -162
  263. package/src/components/Tree/layout/RadialTree.ts +0 -94
  264. package/src/components/Tree/layout/TidyTree.ts +0 -101
  265. package/src/components/Tree/testing/index.ts +0 -5
  266. /package/dist/lib/{browser/chunk-J5LGTIGS.mjs.map → neutral/ExplorerPlugin.mjs.map} +0 -0
  267. /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs +0 -0
  268. /package/dist/lib/{browser/meta.mjs.map → neutral/chunk-J5LGTIGS.mjs.map} +0 -0
  269. /package/dist/lib/{browser/types → neutral}/index.mjs.map +0 -0
  270. /package/dist/lib/{node-esm → neutral}/meta.mjs.map +0 -0
  271. /package/dist/lib/{node-esm → neutral}/types/index.mjs.map +0 -0
@@ -0,0 +1,446 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import { linkRadial } from 'd3';
7
+ import React from 'react';
8
+
9
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
10
+
11
+ const meta: Meta = {
12
+ title: 'plugins/plugin-explorer/containers/ExplorerArticle/experimental',
13
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
14
+ parameters: {
15
+ layout: 'fullscreen',
16
+ },
17
+ };
18
+
19
+ export default meta;
20
+
21
+ type Story = StoryObj;
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // PathProbe — renders a fixed set of example bezier paths the cluster layout
25
+ // might emit, side-by-side across five columns:
26
+ // 1. Baked path strings (cubic / quadratic) as the cluster projector once
27
+ // emitted them.
28
+ // 2. linkRadial output from polar coordinates — should match column 1.
29
+ // 3. radialQuadratic — Q with control at (target.angle, source.radius).
30
+ // 4. radialElbow — sharp 2-segment elbow through the same knee.
31
+ // 5. radialSmoothElbow — cubic with both controls at the knee.
32
+ //
33
+ // Kept around so future curve experiments can compare candidate generators
34
+ // at a glance without touching the production cluster projector.
35
+ // ---------------------------------------------------------------------------
36
+
37
+ const PROBE_PATHS = [
38
+ // Source group at M(-174.87, -44.729). All three paths share that origin.
39
+ {
40
+ label: 'good (target angle 5.19, clockwise)',
41
+ color: 'lime',
42
+ d: 'M-174.87,-44.729 C-262.305,-67.094, -240.395,-124.563, -320.527,-166.083',
43
+ target: [-320.527, -166.083] as [number, number],
44
+ },
45
+ {
46
+ label: 'good (target angle 5.55, clockwise)',
47
+ color: 'cyan',
48
+ d: 'M-174.87,-44.729 C-262.305,-67.094, -180.249,-202.029, -240.332,-269.372',
49
+ target: [-240.332, -269.372] as [number, number],
50
+ },
51
+ {
52
+ label: 'bad (target angle 4.28, counter-clockwise)',
53
+ color: 'orange',
54
+ d: 'M-174.87,-44.729 C-262.305,-67.094, -245.815,113.492, -327.754,151.323',
55
+ target: [-327.754, 151.323] as [number, number],
56
+ },
57
+ // Live path the user reports as not rendering correctly in the full cluster.
58
+ // Source polar ≈ (1.14, 190.5); target polar ≈ (1.82, 380.9). Clockwise direction.
59
+ {
60
+ label: 'live broken (clockwise, source.x=1.14 → target.x=1.82)',
61
+ color: 'magenta',
62
+ d: 'M172.95585588832455,-79.85312713937451Q184.55815606041466,47.20738323164743 369.1163121208293,94.41476646329485',
63
+ target: [369.1163121208293, 94.41476646329485] as [number, number],
64
+ },
65
+ ];
66
+
67
+ const SOURCE_XY: [number, number] = [-174.87, -44.729];
68
+
69
+ // Reverse-engineered polar coords from the user's reported path strings.
70
+ // Source group: polar (4.96 rad, 181) → cartesian (-174.87, -44.729).
71
+ // We use linkRadial here directly to verify the strings d3 produces match what we expect.
72
+ const SOURCE_POLAR = { x: 4.96, y: 181 } as { x: number; y: number };
73
+ const PROBE_POLAR_TARGETS = [
74
+ { label: 'target 5.19 (clockwise)', color: 'lime', polar: { x: 5.19, y: 360 } },
75
+ { label: 'target 5.55 (clockwise)', color: 'cyan', polar: { x: 5.55, y: 361 } },
76
+ { label: 'target 4.28 (counter-clockwise)', color: 'orange', polar: { x: 4.28, y: 361 } },
77
+ ];
78
+
79
+ /** Convert d3-cluster polar (angle from 12 o'clock, sweeping clockwise) to cartesian. */
80
+ const polarToCartesian = (angle: number, radius: number): [number, number] => [
81
+ Math.cos(angle - Math.PI / 2) * radius,
82
+ Math.sin(angle - Math.PI / 2) * radius,
83
+ ];
84
+
85
+ /**
86
+ * Alternative curve: quadratic bezier from source to target with a single control point at
87
+ * (target.angle, source.radius). This makes the curve emerge from source heading TOWARD
88
+ * the target's angular direction (vs. linkRadial's curveBumpPolar, which emerges along
89
+ * source's angle and may bow far away from the target on the way).
90
+ */
91
+ const radialQuadratic = (source: { x: number; y: number }, target: { x: number; y: number }) => {
92
+ const [sx, sy] = polarToCartesian(source.x, source.y);
93
+ const [tx, ty] = polarToCartesian(target.x, target.y);
94
+ const [cx, cy] = polarToCartesian(target.x, source.y);
95
+ return `M${sx},${sy}Q${cx},${cy} ${tx},${ty}`;
96
+ };
97
+
98
+ /** Sharp elbow: M source L (target.angle, source.radius) L target. Hard-corner radial elbow. */
99
+ const radialElbow = (source: { x: number; y: number }, target: { x: number; y: number }) => {
100
+ const [sx, sy] = polarToCartesian(source.x, source.y);
101
+ const [tx, ty] = polarToCartesian(target.x, target.y);
102
+ const [cx, cy] = polarToCartesian(target.x, source.y);
103
+ return `M${sx},${sy}L${cx},${cy}L${tx},${ty}`;
104
+ };
105
+
106
+ /**
107
+ * Smoothed radial elbow: cubic bezier whose control points sit at the elbow knee. Curve
108
+ * emerges from source TOWARD the target angle (around the source ring), then bends OUT to
109
+ * target's radius. Eliminates the wide-ear from linkRadial AND the missing-stub from the
110
+ * pure quadratic — both halves of the curve are visible and follow the radial layout.
111
+ */
112
+ const radialSmoothElbow = (source: { x: number; y: number }, target: { x: number; y: number }) => {
113
+ const [sx, sy] = polarToCartesian(source.x, source.y);
114
+ const [tx, ty] = polarToCartesian(target.x, target.y);
115
+ const [kx, ky] = polarToCartesian(target.x, source.y); // "knee" — at source radius, target angle
116
+ // Both control points near the knee so the curve hugs the elbow.
117
+ return `M${sx},${sy}C${kx},${ky} ${kx},${ky} ${tx},${ty}`;
118
+ };
119
+
120
+ const PathProbe = () => {
121
+ const radialLink = linkRadial<any, any>()
122
+ .angle((d) => d.x)
123
+ .radius((d) => d.y);
124
+
125
+ return (
126
+ <div style={{ width: '100vw', height: '100vh', background: '#0c0c0c', position: 'relative' }}>
127
+ <svg
128
+ xmlns='http://www.w3.org/2000/svg'
129
+ width='100%'
130
+ height='100%'
131
+ viewBox='-450 -350 4000 700'
132
+ style={{ display: 'block' }}
133
+ >
134
+ {/* Center reference (column 1: baked paths) */}
135
+ <line x1={-450} y1={0} x2={450} y2={0} stroke='#222' />
136
+ <line x1={0} y1={-350} x2={0} y2={350} stroke='#222' />
137
+
138
+ {/* Origin marker */}
139
+ <circle cx={0} cy={0} r={3} fill='#444' />
140
+ <text x={-440} y={-330} fill='#666' fontSize={11}>
141
+ baked path strings (left)
142
+ </text>
143
+
144
+ {/* Source group circle */}
145
+ <circle cx={SOURCE_XY[0]} cy={SOURCE_XY[1]} r={5} fill='#888' />
146
+
147
+ {/* Pre-baked path strings (left column visualization). Handles both cubic (C) and
148
+ quadratic (Q) bezier commands. */}
149
+ {PROBE_PATHS.map((p, i) => {
150
+ const cubic = p.d.match(
151
+ /M\s*([-\d.]+),\s*([-\d.]+)\s*C\s*([-\d.]+),\s*([-\d.]+),\s*([-\d.]+),\s*([-\d.]+),\s*([-\d.]+),\s*([-\d.]+)/,
152
+ );
153
+ const quad = p.d.match(/M\s*([-\d.]+),\s*([-\d.]+)\s*Q\s*([-\d.]+),\s*([-\d.]+)\s+([-\d.]+),\s*([-\d.]+)/);
154
+ const controls: Array<[number, number]> = [];
155
+ let mx = 0,
156
+ my = 0,
157
+ ex = 0,
158
+ ey = 0;
159
+ if (cubic) {
160
+ const v = cubic.map(parseFloat);
161
+ mx = v[1];
162
+ my = v[2];
163
+ controls.push([v[3], v[4]], [v[5], v[6]]);
164
+ ex = v[7];
165
+ ey = v[8];
166
+ } else if (quad) {
167
+ const v = quad.map(parseFloat);
168
+ mx = v[1];
169
+ my = v[2];
170
+ controls.push([v[3], v[4]]);
171
+ ex = v[5];
172
+ ey = v[6];
173
+ }
174
+ return (
175
+ <g key={`baked-${i}`}>
176
+ <path d={p.d} fill='none' stroke={p.color} strokeWidth={2} />
177
+ <polyline
178
+ points={[`${mx},${my}`, ...controls.map(([x, y]) => `${x},${y}`), `${ex},${ey}`].join(' ')}
179
+ fill='none'
180
+ stroke={p.color}
181
+ strokeOpacity={0.3}
182
+ strokeWidth={1}
183
+ strokeDasharray='2 3'
184
+ />
185
+ {controls.map(([x, y], j) => (
186
+ <circle key={j} cx={x} cy={y} r={3} fill={p.color} opacity={0.5} />
187
+ ))}
188
+ <circle cx={p.target[0]} cy={p.target[1]} r={5} fill={p.color} />
189
+ <text x={p.target[0] + 8} y={p.target[1]} fill={p.color} fontSize={11}>
190
+ {p.label}
191
+ </text>
192
+ </g>
193
+ );
194
+ })}
195
+
196
+ {/* Re-generate the same paths via linkRadial from polar coords. Shift right by 700 so they
197
+ sit in a second column for direct visual comparison. */}
198
+ <g transform='translate(750, 0)'>
199
+ <line x1={-450} y1={0} x2={450} y2={0} stroke='#222' />
200
+ <line x1={0} y1={-350} x2={0} y2={350} stroke='#222' />
201
+ <circle cx={0} cy={0} r={3} fill='#444' />
202
+ <circle cx={SOURCE_XY[0]} cy={SOURCE_XY[1]} r={5} fill='#888' />
203
+ <text x={-440} y={-330} fill='#666' fontSize={11}>
204
+ linkRadial output from polar (middle)
205
+ </text>
206
+ {PROBE_POLAR_TARGETS.map((p, i) => {
207
+ const d = radialLink({ source: SOURCE_POLAR, target: p.polar }) ?? '';
208
+ const m = d.match(
209
+ /M\s*([-\d.]+),\s*([-\d.]+)\s*C\s*([-\d.]+),\s*([-\d.]+),\s*([-\d.]+),\s*([-\d.]+),\s*([-\d.]+),\s*([-\d.]+)/,
210
+ );
211
+ if (!m) {
212
+ return null;
213
+ }
214
+ const [, , , c1x, c1y, c2x, c2y, ex, ey] = m.map(parseFloat);
215
+ return (
216
+ <g key={`fresh-${i}`}>
217
+ <path d={d} fill='none' stroke={p.color} strokeWidth={2} />
218
+ <circle cx={c1x} cy={c1y} r={3} fill={p.color} opacity={0.5} />
219
+ <circle cx={c2x} cy={c2y} r={3} fill={p.color} opacity={0.5} />
220
+ <circle cx={ex} cy={ey} r={5} fill={p.color} />
221
+ <text x={ex + 8} y={ey} fill={p.color} fontSize={11}>
222
+ {p.label}
223
+ </text>
224
+ </g>
225
+ );
226
+ })}
227
+ </g>
228
+
229
+ {/* Alternative A: quadratic with control at (target.angle, source.radius). */}
230
+ <g transform='translate(1500, 0)'>
231
+ <line x1={-450} y1={0} x2={450} y2={0} stroke='#222' />
232
+ <line x1={0} y1={-350} x2={0} y2={350} stroke='#222' />
233
+ <circle cx={0} cy={0} r={3} fill='#444' />
234
+ <circle cx={SOURCE_XY[0]} cy={SOURCE_XY[1]} r={5} fill='#888' />
235
+ <text x={-440} y={-330} fill='#666' fontSize={11}>
236
+ radialQuadratic — control at (target.angle, source.radius)
237
+ </text>
238
+ {PROBE_POLAR_TARGETS.map((p, i) => {
239
+ const d = radialQuadratic(SOURCE_POLAR, p.polar);
240
+ const [tx, ty] = polarToCartesian(p.polar.x, p.polar.y);
241
+ const [cx, cy] = polarToCartesian(p.polar.x, SOURCE_POLAR.y);
242
+ return (
243
+ <g key={`alt-${i}`}>
244
+ <path d={d} fill='none' stroke={p.color} strokeWidth={2} />
245
+ <circle cx={cx} cy={cy} r={3} fill={p.color} opacity={0.5} />
246
+ <circle cx={tx} cy={ty} r={5} fill={p.color} />
247
+ <text x={tx + 8} y={ty} fill={p.color} fontSize={11}>
248
+ {p.label}
249
+ </text>
250
+ </g>
251
+ );
252
+ })}
253
+ </g>
254
+
255
+ {/* Alternative B: sharp elbow — two line segments. */}
256
+ <g transform='translate(2250, 0)'>
257
+ <line x1={-450} y1={0} x2={450} y2={0} stroke='#222' />
258
+ <line x1={0} y1={-350} x2={0} y2={350} stroke='#222' />
259
+ <circle cx={0} cy={0} r={3} fill='#444' />
260
+ <circle cx={SOURCE_XY[0]} cy={SOURCE_XY[1]} r={5} fill='#888' />
261
+ <text x={-440} y={-330} fill='#666' fontSize={11}>
262
+ radialElbow — sharp 2-segment elbow
263
+ </text>
264
+ {PROBE_POLAR_TARGETS.map((p, i) => {
265
+ const d = radialElbow(SOURCE_POLAR, p.polar);
266
+ const [tx, ty] = polarToCartesian(p.polar.x, p.polar.y);
267
+ return (
268
+ <g key={`elbow-${i}`}>
269
+ <path d={d} fill='none' stroke={p.color} strokeWidth={2} />
270
+ <circle cx={tx} cy={ty} r={5} fill={p.color} />
271
+ <text x={tx + 8} y={ty} fill={p.color} fontSize={11}>
272
+ {p.label}
273
+ </text>
274
+ </g>
275
+ );
276
+ })}
277
+ </g>
278
+
279
+ {/* Alternative C: smoothed elbow — cubic with both controls at the knee. */}
280
+ <g transform='translate(3000, 0)'>
281
+ <line x1={-450} y1={0} x2={450} y2={0} stroke='#222' />
282
+ <line x1={0} y1={-350} x2={0} y2={350} stroke='#222' />
283
+ <circle cx={0} cy={0} r={3} fill='#444' />
284
+ <circle cx={SOURCE_XY[0]} cy={SOURCE_XY[1]} r={5} fill='#888' />
285
+ <text x={-440} y={-330} fill='#666' fontSize={11}>
286
+ radialSmoothElbow — cubic with both controls at the knee
287
+ </text>
288
+ {PROBE_POLAR_TARGETS.map((p, i) => {
289
+ const d = radialSmoothElbow(SOURCE_POLAR, p.polar);
290
+ const [tx, ty] = polarToCartesian(p.polar.x, p.polar.y);
291
+ return (
292
+ <g key={`smooth-${i}`}>
293
+ <path d={d} fill='none' stroke={p.color} strokeWidth={2} />
294
+ <circle cx={tx} cy={ty} r={5} fill={p.color} />
295
+ <text x={tx + 8} y={ty} fill={p.color} fontSize={11}>
296
+ {p.label}
297
+ </text>
298
+ </g>
299
+ );
300
+ })}
301
+ </g>
302
+ </svg>
303
+ </div>
304
+ );
305
+ };
306
+
307
+ export const PathProbeStory: Story = {
308
+ name: 'PathProbe',
309
+ render: () => <PathProbe />,
310
+ };
311
+
312
+ // ---------------------------------------------------------------------------
313
+ // PathInspector — paste any `d` string, click Diagnose. The path is rendered
314
+ // in an SVG and the same diagnostics (bbox, pathLength, computed CSS) we'd
315
+ // otherwise pull from the console are printed alongside. Loops to avoid the
316
+ // console-snippet ping-pong when debugging "this path doesn't render."
317
+ // ---------------------------------------------------------------------------
318
+
319
+ const PathInspector = () => {
320
+ const [input, setInput] = React.useState(
321
+ 'M172.95585588832455,-79.85312713937451Q184.55815606041466,47.20738323164743 369.1163121208293,94.41476646329485',
322
+ );
323
+ const [info, setInfo] = React.useState<Record<string, unknown> | null>(null);
324
+ const pathRef = React.useRef<SVGPathElement | null>(null);
325
+
326
+ const diagnose = React.useCallback(() => {
327
+ const el = pathRef.current;
328
+ if (!el) {
329
+ setInfo({ error: 'no path element' });
330
+ return;
331
+ }
332
+ const cs = getComputedStyle(el);
333
+ let bbox: any = null;
334
+ try {
335
+ const b = el.getBBox();
336
+ bbox = { x: b.x, y: b.y, width: b.width, height: b.height };
337
+ } catch (e: any) {
338
+ bbox = `error: ${e?.message}`;
339
+ }
340
+ let len: number | string = 0;
341
+ try {
342
+ len = el.getTotalLength();
343
+ } catch (e: any) {
344
+ len = `error: ${e?.message}`;
345
+ }
346
+ setInfo({
347
+ d: el.getAttribute('d'),
348
+ bbox,
349
+ pathLength: len,
350
+ stroke: cs.stroke,
351
+ strokeWidth: cs.strokeWidth,
352
+ strokeOpacity: cs.strokeOpacity,
353
+ opacity: cs.opacity,
354
+ visibility: cs.visibility,
355
+ display: cs.display,
356
+ clipPath: cs.clipPath,
357
+ transform: cs.transform,
358
+ });
359
+ }, []);
360
+
361
+ // Run on mount.
362
+ React.useEffect(() => {
363
+ diagnose();
364
+ // eslint-disable-next-line react-hooks/exhaustive-deps
365
+ }, []);
366
+
367
+ return (
368
+ <div
369
+ style={{
370
+ width: '100vw',
371
+ height: '100vh',
372
+ background: '#0c0c0c',
373
+ color: '#ddd',
374
+ fontFamily: 'monospace',
375
+ padding: 12,
376
+ boxSizing: 'border-box',
377
+ display: 'flex',
378
+ flexDirection: 'column',
379
+ gap: 8,
380
+ }}
381
+ >
382
+ <div style={{ display: 'flex', gap: 8, alignItems: 'flex-start' }}>
383
+ <textarea
384
+ value={input}
385
+ onChange={(e) => setInput(e.target.value)}
386
+ rows={3}
387
+ style={{
388
+ flex: 1,
389
+ background: '#111',
390
+ color: '#ddd',
391
+ border: '1px solid #333',
392
+ padding: 6,
393
+ fontFamily: 'monospace',
394
+ fontSize: 12,
395
+ }}
396
+ placeholder='paste a `d` attribute string (M ... C/Q ...)'
397
+ />
398
+ <button
399
+ onClick={diagnose}
400
+ style={{
401
+ background: '#222',
402
+ color: '#ddd',
403
+ border: '1px solid #444',
404
+ padding: '8px 14px',
405
+ cursor: 'pointer',
406
+ }}
407
+ >
408
+ Diagnose
409
+ </button>
410
+ </div>
411
+
412
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, flex: 1, minHeight: 0 }}>
413
+ <svg
414
+ xmlns='http://www.w3.org/2000/svg'
415
+ width='100%'
416
+ height='100%'
417
+ viewBox='-450 -350 900 700'
418
+ style={{ background: '#000', border: '1px solid #222' }}
419
+ >
420
+ <line x1={-450} y1={0} x2={450} y2={0} stroke='#222' />
421
+ <line x1={0} y1={-350} x2={0} y2={350} stroke='#222' />
422
+ <circle cx={0} cy={0} r={3} fill='#444' />
423
+ <path ref={pathRef} d={input} fill='none' stroke='magenta' strokeWidth={2} />
424
+ </svg>
425
+
426
+ <pre
427
+ style={{
428
+ background: '#111',
429
+ padding: 10,
430
+ overflow: 'auto',
431
+ fontSize: 12,
432
+ margin: 0,
433
+ border: '1px solid #222',
434
+ }}
435
+ >
436
+ {info ? JSON.stringify(info, null, 2) : '(click Diagnose)'}
437
+ </pre>
438
+ </div>
439
+ </div>
440
+ );
441
+ };
442
+
443
+ export const PathInspectorStory: Story = {
444
+ name: 'PathInspector',
445
+ render: () => <PathInspector />,
446
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export { ExplorerArticle as default } from './ExplorerArticle';
@@ -0,0 +1,37 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ /** Visualization variants exposed by `ExplorerArticle`. */
6
+ export type ExplorerArticleVariant = 'force' | 'cluster' | 'bundle' | 'lattice' | 'swarm';
7
+
8
+ export const VARIANTS: { value: ExplorerArticleVariant; icon: string; label: string }[] = [
9
+ {
10
+ value: 'force',
11
+ icon: 'ph--graph--regular',
12
+ label: 'Force-directed',
13
+ },
14
+ {
15
+ value: 'cluster',
16
+ icon: 'ph--asterisk-simple--regular',
17
+ label: 'Radial cluster',
18
+ },
19
+ {
20
+ value: 'bundle',
21
+ icon: 'ph--circles-three-plus--regular',
22
+ label: 'Edge bundling',
23
+ },
24
+ {
25
+ value: 'lattice',
26
+ icon: 'ph--grid-four--regular',
27
+ label: 'Lattice',
28
+ },
29
+ {
30
+ value: 'swarm',
31
+ icon: 'ph--fish--regular',
32
+ label: 'Swarm',
33
+ },
34
+ ];
35
+
36
+ export const isVariant = (value: unknown): value is ExplorerArticleVariant =>
37
+ value === 'force' || value === 'cluster' || value === 'bundle' || value === 'lattice' || value === 'swarm';
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { type ComponentType, lazy } from 'react';
6
+
7
+ export const ExplorerArticle: ComponentType<any> = lazy(() => import('./ExplorerArticle'));
@@ -4,33 +4,44 @@
4
4
 
5
5
  import { useEffect, useState } from 'react';
6
6
 
7
- import { type Filter, type Queue, type Space } from '@dxos/client/echo';
7
+ import { Capabilities } from '@dxos/app-framework';
8
+ import { useCapability } from '@dxos/app-framework/ui';
9
+ import { type Database, type Entity, type Filter } from '@dxos/echo';
8
10
  import { SpaceGraphModel, type SpaceGraphModelOptions } from '@dxos/schema';
9
11
 
10
12
  // TODO(burdon): Factor out.
11
13
  export const useGraphModel = (
12
- space: Space | undefined,
14
+ db: Database.Database | undefined,
13
15
  filter?: Filter.Any | undefined,
14
16
  options?: SpaceGraphModelOptions,
15
- queue?: Queue,
17
+ items?: readonly Entity.Unknown[],
16
18
  ): SpaceGraphModel | undefined => {
19
+ const registry = useCapability(Capabilities.AtomRegistry);
17
20
  const [model, setModel] = useState<SpaceGraphModel | undefined>(undefined);
21
+
18
22
  useEffect(() => {
19
- if (!space) {
20
- void model?.close();
23
+ if (!db) {
21
24
  setModel(undefined);
22
25
  return;
23
26
  }
24
27
 
25
- // TODO(burdon): Does this need to be a dependency?
26
- if (!model || model.queue !== queue) {
27
- const model = new SpaceGraphModel().setFilter(filter).setOptions(options);
28
- void model.open(space, queue);
29
- setModel(model);
30
- } else {
31
- model.setFilter(filter).setOptions(options);
32
- }
33
- }, [space, filter, options, queue]);
28
+ const newModel = new SpaceGraphModel(registry);
29
+ void newModel.open(db);
30
+ setModel(newModel);
31
+
32
+ return () => {
33
+ setModel(undefined);
34
+ void newModel.close();
35
+ };
36
+ }, [db, registry]);
37
+
38
+ useEffect(() => {
39
+ model?.setFilter(filter).setOptions(options);
40
+ }, [model, filter, options]);
41
+
42
+ useEffect(() => {
43
+ model?.setItems(items);
44
+ }, [model, items]);
34
45
 
35
46
  return model;
36
47
  };
package/src/index.ts CHANGED
@@ -2,8 +2,5 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- export * from './components';
6
- export * from './hooks';
7
5
  export * from './meta';
8
-
9
- export * from './ExplorerPlugin';
6
+ export * from './types';
package/src/meta.ts CHANGED
@@ -2,19 +2,37 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { type PluginMeta } from '@dxos/app-framework';
5
+ import { type Plugin } from '@dxos/app-framework';
6
6
  import { trim } from '@dxos/util';
7
7
 
8
- export const meta: PluginMeta = {
9
- id: 'dxos.org/plugin/explorer',
8
+ export const meta: Plugin.Meta = {
9
+ id: 'org.dxos.plugin.explorer',
10
10
  name: 'Explorer',
11
+ author: 'DXOS',
11
12
  description: trim`
12
- Interactive hypergraph visualization that reveals relationships between objects in your workspace.
13
- Navigate complex data structures and discover connections through a dynamic network view.
13
+ Explorer is an interactive hypergraph visualization plugin that reveals the relationships
14
+ between objects stored in your DXOS workspace. Each Graph document is backed by a live
15
+ ECHO query and a View that you configure — Explorer keeps the visualization synchronized
16
+ with the database in real time so every peer immediately sees changes made by collaborators.
17
+
18
+ The plugin offers four switchable layout algorithms for the same data set: a physics-based
19
+ force-directed graph for freeform exploration, a radial cluster layout that groups objects
20
+ by type around a central root, an edge-bundling layout that tames visual clutter on dense
21
+ graphs, and a lattice grid that arranges objects in a sorted matrix sorted by type and label.
22
+ Switching between layouts smoothly tweens node positions so spatial context is preserved.
23
+
24
+ Nodes are coloured by their ECHO object type, and hovering any node opens an anchor-card
25
+ preview panel with the object's details without leaving the graph view.
26
+ The toolbar's query editor lets you filter the visible node set on the fly using
27
+ the same query syntax available across Composer.
28
+
29
+ A new Graph document can be created from the object-creation dialog; Explorer automatically
30
+ derives an initial View from the chosen type and persists both to ECHO so the graph is
31
+ immediately shareable and replicable across the space.
14
32
  `,
15
33
  icon: 'ph--graph--regular',
16
34
  iconHue: 'green',
17
35
  source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-explorer',
18
- tags: ['labs'],
36
+ spec: 'PLUGIN.mdl',
19
37
  screenshots: ['https://dxos.network/plugin-details-explorer-dark.png'],
20
38
  };
package/src/plugin.ts ADDED
@@ -0,0 +1,9 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { Plugin } from '@dxos/app-framework';
6
+
7
+ import { meta } from './meta';
8
+
9
+ export const ExplorerPlugin = Plugin.lazy(meta, () => import('#plugin'));
@@ -2,10 +2,10 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Key } from '@dxos/echo';
5
+ import { Key, Obj } from '@dxos/echo';
6
6
  import { range } from '@dxos/util';
7
7
 
8
- import { Tree, type TreeNodeType } from '../types';
8
+ import { Tree, type TreeNodeType } from '../components/Tree/types';
9
9
 
10
10
  type NumberOrNumberArray = number | number[];
11
11
 
@@ -16,7 +16,9 @@ const random = (min: number, max: number) => Math.floor(Math.random() * (max - m
16
16
  */
17
17
  export const createTree = (spec: NumberOrNumberArray[] = [], createText?: () => string): Tree => {
18
18
  const tree = new Tree();
19
- tree.root.data = { text: 'root' };
19
+ Obj.update(tree.tree, () => {
20
+ tree.root.data = { text: 'root' };
21
+ });
20
22
 
21
23
  const createNodes = (parent: TreeNodeType, spec: NumberOrNumberArray = 0): TreeNodeType[] => {
22
24
  const count = Array.isArray(spec) ? random(spec[0], spec[1]) : spec;
@@ -0,0 +1,9 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ // Eager re-export of `ExplorerPlugin`. See `@dxos/plugin-testing/src/core.ts` for the rationale.
6
+ export * from '../ExplorerPlugin';
7
+
8
+ export * from './generator';
9
+ export * from './relations';