@dxos/app-framework 0.10.0 → 0.11.0

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 (294) hide show
  1. package/.storybook/main.mts +1 -1
  2. package/.storybook/preview.mts +2 -2
  3. package/CHANGELOG.md +93 -0
  4. package/TASKS.md +11 -0
  5. package/dist/lib/chunk-SurfaceManager.mjs +960 -0
  6. package/dist/lib/chunk-SurfaceManager.mjs.map +1 -0
  7. package/dist/lib/chunk-capability.mjs +15 -0
  8. package/dist/lib/chunk-capability.mjs.map +1 -0
  9. package/dist/lib/{browser/chunk-FJ4765WW.mjs → chunk-context.mjs} +4 -5
  10. package/dist/lib/chunk-context.mjs.map +1 -0
  11. package/dist/lib/chunk-history-tracker.mjs +160 -0
  12. package/dist/lib/chunk-history-tracker.mjs.map +1 -0
  13. package/dist/lib/chunk-plugin-manager.mjs +1390 -0
  14. package/dist/lib/chunk-plugin-manager.mjs.map +1 -0
  15. package/dist/lib/chunk-plugin-manifest.mjs +125 -0
  16. package/dist/lib/chunk-plugin-manifest.mjs.map +1 -0
  17. package/dist/lib/chunk-process-manager-capability.mjs +126 -0
  18. package/dist/lib/chunk-process-manager-capability.mjs.map +1 -0
  19. package/dist/lib/chunk-rolldown-runtime.mjs +13 -0
  20. package/dist/lib/chunk-url-loader.mjs +347 -0
  21. package/dist/lib/chunk-url-loader.mjs.map +1 -0
  22. package/dist/lib/cli.mjs +78 -0
  23. package/dist/lib/cli.mjs.map +1 -0
  24. package/dist/lib/common/activation-events.mjs +35 -0
  25. package/dist/lib/common/activation-events.mjs.map +1 -0
  26. package/dist/lib/common/capabilities.mjs +184 -0
  27. package/dist/lib/common/capabilities.mjs.map +1 -0
  28. package/dist/lib/config.mjs +2 -0
  29. package/dist/lib/core/activation-event.mjs +55 -0
  30. package/dist/lib/core/activation-event.mjs.map +1 -0
  31. package/dist/lib/core/capability.mjs +189 -0
  32. package/dist/lib/core/capability.mjs.map +1 -0
  33. package/dist/lib/core/plugin-manager.mjs +2 -0
  34. package/dist/lib/core/plugin.mjs +340 -0
  35. package/dist/lib/core/plugin.mjs.map +1 -0
  36. package/dist/lib/core/url-loader.mjs +2 -0
  37. package/dist/lib/index.mjs +134 -0
  38. package/dist/lib/index.mjs.map +1 -0
  39. package/dist/lib/testing/react.mjs +70 -0
  40. package/dist/lib/testing/react.mjs.map +1 -0
  41. package/dist/lib/testing.mjs +399 -0
  42. package/dist/lib/testing.mjs.map +1 -0
  43. package/dist/lib/ui.mjs +636 -0
  44. package/dist/lib/ui.mjs.map +1 -0
  45. package/dist/lib/vite-plugin.mjs +986 -0
  46. package/dist/lib/vite-plugin.mjs.map +1 -0
  47. package/dist/plugin/node-esm/index.mjs +4 -3
  48. package/dist/plugin/node-esm/index.mjs.map +3 -3
  49. package/dist/plugin/node-esm/meta.json +1 -1
  50. package/dist/types/src/common/Role.d.ts +31 -0
  51. package/dist/types/src/common/Role.d.ts.map +1 -0
  52. package/dist/types/src/common/Role.test.d.ts +2 -0
  53. package/dist/types/src/common/Role.test.d.ts.map +1 -0
  54. package/dist/types/src/common/capabilities.d.ts +11 -2
  55. package/dist/types/src/common/capabilities.d.ts.map +1 -1
  56. package/dist/types/src/common/index.d.ts +1 -0
  57. package/dist/types/src/common/index.d.ts.map +1 -1
  58. package/dist/types/src/core/capability-manager.d.ts +1 -1
  59. package/dist/types/src/core/capability-manager.d.ts.map +1 -1
  60. package/dist/types/src/core/capability.d.ts +38 -5
  61. package/dist/types/src/core/capability.d.ts.map +1 -1
  62. package/dist/types/src/core/plugin-manager.d.ts +1 -1
  63. package/dist/types/src/core/plugin-manager.d.ts.map +1 -1
  64. package/dist/types/src/core/plugin.d.ts +14 -1
  65. package/dist/types/src/core/plugin.d.ts.map +1 -1
  66. package/dist/types/src/core/registry.d.ts +1 -1
  67. package/dist/types/src/core/registry.d.ts.map +1 -1
  68. package/dist/types/src/plugin-process-manager/history/capability.d.ts +1 -1
  69. package/dist/types/src/plugin-process-manager/process-manager-capability.d.ts +1 -1
  70. package/dist/types/src/plugin-process-manager/process-manager-capability.d.ts.map +1 -1
  71. package/dist/types/src/testing/StorybookErrorFallback.d.ts +15 -0
  72. package/dist/types/src/testing/StorybookErrorFallback.d.ts.map +1 -0
  73. package/dist/types/src/testing/harness.d.ts +1 -1
  74. package/dist/types/src/testing/harness.d.ts.map +1 -1
  75. package/dist/types/src/testing/index.d.ts +2 -0
  76. package/dist/types/src/testing/index.d.ts.map +1 -1
  77. package/dist/types/src/testing/react.d.ts +4 -4
  78. package/dist/types/src/testing/react.d.ts.map +1 -1
  79. package/dist/types/src/testing/withPluginManager.d.ts.map +1 -1
  80. package/dist/types/src/testing/withPluginManager.stories.d.ts +6 -0
  81. package/dist/types/src/testing/withPluginManager.stories.d.ts.map +1 -1
  82. package/dist/types/src/testing/withSurfaceDebug.d.ts +10 -0
  83. package/dist/types/src/testing/withSurfaceDebug.d.ts.map +1 -0
  84. package/dist/types/src/ui/components/App/App.d.ts +0 -32
  85. package/dist/types/src/ui/components/App/App.d.ts.map +1 -1
  86. package/dist/types/src/ui/components/App/index.d.ts +1 -0
  87. package/dist/types/src/ui/components/App/index.d.ts.map +1 -1
  88. package/dist/types/src/ui/components/App/loader.d.ts +35 -0
  89. package/dist/types/src/ui/components/App/loader.d.ts.map +1 -0
  90. package/dist/types/src/ui/components/HomeSection/HomeSection.d.ts +26 -0
  91. package/dist/types/src/ui/components/HomeSection/HomeSection.d.ts.map +1 -0
  92. package/dist/types/src/ui/components/HomeSection/HomeSection.stories.d.ts +15 -0
  93. package/dist/types/src/ui/components/HomeSection/HomeSection.stories.d.ts.map +1 -0
  94. package/dist/types/src/ui/components/HomeSection/index.d.ts +2 -0
  95. package/dist/types/src/ui/components/HomeSection/index.d.ts.map +1 -0
  96. package/dist/types/src/ui/components/NamePopover/NamePopover.d.ts +14 -0
  97. package/dist/types/src/ui/components/NamePopover/NamePopover.d.ts.map +1 -0
  98. package/dist/types/src/ui/components/NamePopover/index.d.ts +2 -0
  99. package/dist/types/src/ui/components/NamePopover/index.d.ts.map +1 -0
  100. package/dist/types/src/ui/components/Surface/SurfaceComponent.d.ts +21 -9
  101. package/dist/types/src/ui/components/Surface/SurfaceComponent.d.ts.map +1 -1
  102. package/dist/types/src/ui/components/Surface/SurfaceComponent.stories.d.ts +4 -1
  103. package/dist/types/src/ui/components/Surface/SurfaceComponent.stories.d.ts.map +1 -1
  104. package/dist/types/src/ui/components/Surface/SurfaceComponent.test.d.ts +2 -0
  105. package/dist/types/src/ui/components/Surface/SurfaceComponent.test.d.ts.map +1 -0
  106. package/dist/types/src/ui/components/Surface/SurfaceDebug.d.ts +40 -0
  107. package/dist/types/src/ui/components/Surface/SurfaceDebug.d.ts.map +1 -0
  108. package/dist/types/src/ui/components/Surface/SurfaceManager.d.ts +23 -0
  109. package/dist/types/src/ui/components/Surface/SurfaceManager.d.ts.map +1 -0
  110. package/dist/types/src/ui/components/Surface/SurfaceManagerContext.d.ts +11 -0
  111. package/dist/types/src/ui/components/Surface/SurfaceManagerContext.d.ts.map +1 -0
  112. package/dist/types/src/ui/components/Surface/SurfaceMetrics.d.ts +72 -0
  113. package/dist/types/src/ui/components/Surface/SurfaceMetrics.d.ts.map +1 -0
  114. package/dist/types/src/ui/components/Surface/SurfaceProfilerContext.d.ts.map +1 -1
  115. package/dist/types/src/ui/components/Surface/index.d.ts +20 -25
  116. package/dist/types/src/ui/components/Surface/index.d.ts.map +1 -1
  117. package/dist/types/src/ui/components/Surface/types.d.ts +56 -52
  118. package/dist/types/src/ui/components/Surface/types.d.ts.map +1 -1
  119. package/dist/types/src/ui/components/index.d.ts +2 -0
  120. package/dist/types/src/ui/components/index.d.ts.map +1 -1
  121. package/dist/types/src/ui/hooks/useApp.d.ts.map +1 -1
  122. package/dist/types/tsconfig.tsbuildinfo +1 -1
  123. package/moon.yml +25 -18
  124. package/package.json +58 -73
  125. package/src/common/Role.test.ts +41 -0
  126. package/src/common/Role.ts +54 -0
  127. package/src/common/capabilities.ts +17 -2
  128. package/src/common/index.ts +1 -0
  129. package/src/core/capability-manager.test.ts +1 -1
  130. package/src/core/capability-manager.ts +1 -1
  131. package/src/core/capability.ts +45 -6
  132. package/src/core/plugin-manager.test.ts +38 -3
  133. package/src/core/plugin-manager.ts +18 -1
  134. package/src/core/plugin.ts +16 -1
  135. package/src/core/registry.ts +1 -1
  136. package/src/plugin-process-manager/process-manager-capability.ts +25 -2
  137. package/src/testing/StorybookErrorFallback.tsx +40 -0
  138. package/src/testing/harness.ts +1 -1
  139. package/src/testing/index.ts +2 -0
  140. package/src/testing/react.test.tsx +2 -1
  141. package/src/testing/react.tsx +14 -8
  142. package/src/testing/withPluginManager.stories.tsx +16 -1
  143. package/src/testing/withPluginManager.tsx +13 -2
  144. package/src/testing/withSurfaceDebug.tsx +22 -0
  145. package/src/ui/components/App/App.tsx +7 -35
  146. package/src/ui/components/App/index.ts +1 -0
  147. package/src/ui/components/App/loader.ts +39 -0
  148. package/src/ui/components/HomeSection/HomeSection.stories.tsx +43 -0
  149. package/src/ui/components/HomeSection/HomeSection.tsx +69 -0
  150. package/src/ui/components/HomeSection/index.ts +5 -0
  151. package/src/ui/components/NamePopover/NamePopover.tsx +70 -0
  152. package/src/ui/components/NamePopover/index.ts +5 -0
  153. package/src/ui/components/Surface/DESIGN.md +136 -0
  154. package/src/ui/components/Surface/SurfaceComponent.stories.tsx +70 -41
  155. package/src/ui/components/Surface/SurfaceComponent.test.tsx +403 -0
  156. package/src/ui/components/Surface/SurfaceComponent.tsx +209 -189
  157. package/src/ui/components/Surface/SurfaceDebug.tsx +435 -0
  158. package/src/ui/components/Surface/SurfaceManager.ts +95 -0
  159. package/src/ui/components/Surface/SurfaceManagerContext.ts +23 -0
  160. package/src/ui/components/Surface/SurfaceMetrics.ts +202 -0
  161. package/src/ui/components/Surface/SurfaceProfilerContext.tsx +12 -2
  162. package/src/ui/components/Surface/index.ts +24 -22
  163. package/src/ui/components/Surface/types.test.ts +47 -54
  164. package/src/ui/components/Surface/types.ts +66 -91
  165. package/src/ui/components/index.ts +2 -0
  166. package/src/ui/hooks/useApp.tsx +7 -3
  167. package/src/vite-plugin/boot-loader/loader-app/boot-loader.css +1 -1
  168. package/tsconfig.json +3 -0
  169. package/vite.config.ts +26 -0
  170. package/dist/lib/browser/capability-S44TE2H7.mjs +0 -35
  171. package/dist/lib/browser/capability-S44TE2H7.mjs.map +0 -7
  172. package/dist/lib/browser/chunk-3NQLTQCP.mjs +0 -47
  173. package/dist/lib/browser/chunk-3NQLTQCP.mjs.map +0 -7
  174. package/dist/lib/browser/chunk-53HJHMS5.mjs +0 -7
  175. package/dist/lib/browser/chunk-53HJHMS5.mjs.map +0 -7
  176. package/dist/lib/browser/chunk-FJ4765WW.mjs.map +0 -7
  177. package/dist/lib/browser/chunk-J5LGTIGS.mjs +0 -10
  178. package/dist/lib/browser/chunk-J5LGTIGS.mjs.map +0 -7
  179. package/dist/lib/browser/chunk-KRJ4KK2W.mjs +0 -505
  180. package/dist/lib/browser/chunk-KRJ4KK2W.mjs.map +0 -7
  181. package/dist/lib/browser/chunk-LUH4Y5F2.mjs +0 -430
  182. package/dist/lib/browser/chunk-LUH4Y5F2.mjs.map +0 -7
  183. package/dist/lib/browser/chunk-NKVRSMFN.mjs +0 -95
  184. package/dist/lib/browser/chunk-NKVRSMFN.mjs.map +0 -7
  185. package/dist/lib/browser/chunk-OO53M5UK.mjs +0 -260
  186. package/dist/lib/browser/chunk-OO53M5UK.mjs.map +0 -7
  187. package/dist/lib/browser/chunk-OPTS3ZSN.mjs +0 -83
  188. package/dist/lib/browser/chunk-OPTS3ZSN.mjs.map +0 -7
  189. package/dist/lib/browser/chunk-Q2GLJTVV.mjs +0 -12
  190. package/dist/lib/browser/chunk-Q2GLJTVV.mjs.map +0 -7
  191. package/dist/lib/browser/chunk-SYXIYT6T.mjs +0 -143
  192. package/dist/lib/browser/chunk-SYXIYT6T.mjs.map +0 -7
  193. package/dist/lib/browser/chunk-U5MESPM5.mjs +0 -603
  194. package/dist/lib/browser/chunk-U5MESPM5.mjs.map +0 -7
  195. package/dist/lib/browser/chunk-UGYCLOXE.mjs +0 -1471
  196. package/dist/lib/browser/chunk-UGYCLOXE.mjs.map +0 -7
  197. package/dist/lib/browser/chunk-VF37YTXJ.mjs +0 -28
  198. package/dist/lib/browser/chunk-VF37YTXJ.mjs.map +0 -7
  199. package/dist/lib/browser/chunk-YUVQIOTB.mjs +0 -60
  200. package/dist/lib/browser/chunk-YUVQIOTB.mjs.map +0 -7
  201. package/dist/lib/browser/cli/index.mjs +0 -74
  202. package/dist/lib/browser/cli/index.mjs.map +0 -7
  203. package/dist/lib/browser/common/activation-events.mjs +0 -20
  204. package/dist/lib/browser/common/activation-events.mjs.map +0 -7
  205. package/dist/lib/browser/common/capabilities.mjs +0 -56
  206. package/dist/lib/browser/common/capabilities.mjs.map +0 -7
  207. package/dist/lib/browser/config/index.mjs +0 -8
  208. package/dist/lib/browser/config/index.mjs.map +0 -7
  209. package/dist/lib/browser/core/activation-event.mjs +0 -20
  210. package/dist/lib/browser/core/activation-event.mjs.map +0 -7
  211. package/dist/lib/browser/core/capability.mjs +0 -32
  212. package/dist/lib/browser/core/capability.mjs.map +0 -7
  213. package/dist/lib/browser/core/plugin-manager.mjs +0 -19
  214. package/dist/lib/browser/core/plugin-manager.mjs.map +0 -7
  215. package/dist/lib/browser/core/plugin.mjs +0 -51
  216. package/dist/lib/browser/core/plugin.mjs.map +0 -7
  217. package/dist/lib/browser/core/url-loader.mjs +0 -24
  218. package/dist/lib/browser/core/url-loader.mjs.map +0 -7
  219. package/dist/lib/browser/index.mjs +0 -98
  220. package/dist/lib/browser/index.mjs.map +0 -7
  221. package/dist/lib/browser/meta.json +0 -1
  222. package/dist/lib/browser/process-manager-capability-QK4BMKMN.mjs +0 -132
  223. package/dist/lib/browser/process-manager-capability-QK4BMKMN.mjs.map +0 -7
  224. package/dist/lib/browser/testing/index.mjs +0 -359
  225. package/dist/lib/browser/testing/index.mjs.map +0 -7
  226. package/dist/lib/browser/testing/react.mjs +0 -78
  227. package/dist/lib/browser/testing/react.mjs.map +0 -7
  228. package/dist/lib/browser/ui/index.mjs +0 -60
  229. package/dist/lib/browser/ui/index.mjs.map +0 -7
  230. package/dist/lib/node-esm/capability-3YRF77LV.mjs +0 -36
  231. package/dist/lib/node-esm/capability-3YRF77LV.mjs.map +0 -7
  232. package/dist/lib/node-esm/chunk-2UPUZXGU.mjs +0 -9
  233. package/dist/lib/node-esm/chunk-2UPUZXGU.mjs.map +0 -7
  234. package/dist/lib/node-esm/chunk-37Z53PXZ.mjs +0 -10
  235. package/dist/lib/node-esm/chunk-37Z53PXZ.mjs.map +0 -7
  236. package/dist/lib/node-esm/chunk-4SE7SX7N.mjs +0 -261
  237. package/dist/lib/node-esm/chunk-4SE7SX7N.mjs.map +0 -7
  238. package/dist/lib/node-esm/chunk-BMW6P6QA.mjs +0 -48
  239. package/dist/lib/node-esm/chunk-BMW6P6QA.mjs.map +0 -7
  240. package/dist/lib/node-esm/chunk-COHOWGGJ.mjs +0 -144
  241. package/dist/lib/node-esm/chunk-COHOWGGJ.mjs.map +0 -7
  242. package/dist/lib/node-esm/chunk-DRT75WGD.mjs +0 -604
  243. package/dist/lib/node-esm/chunk-DRT75WGD.mjs.map +0 -7
  244. package/dist/lib/node-esm/chunk-FKMVORZN.mjs +0 -431
  245. package/dist/lib/node-esm/chunk-FKMVORZN.mjs.map +0 -7
  246. package/dist/lib/node-esm/chunk-FPW45EZH.mjs +0 -14
  247. package/dist/lib/node-esm/chunk-FPW45EZH.mjs.map +0 -7
  248. package/dist/lib/node-esm/chunk-GLH4LGHZ.mjs +0 -84
  249. package/dist/lib/node-esm/chunk-GLH4LGHZ.mjs.map +0 -7
  250. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
  251. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +0 -7
  252. package/dist/lib/node-esm/chunk-IL7O7IRX.mjs +0 -1472
  253. package/dist/lib/node-esm/chunk-IL7O7IRX.mjs.map +0 -7
  254. package/dist/lib/node-esm/chunk-ND2HETHD.mjs +0 -506
  255. package/dist/lib/node-esm/chunk-ND2HETHD.mjs.map +0 -7
  256. package/dist/lib/node-esm/chunk-TLKYZKPW.mjs +0 -29
  257. package/dist/lib/node-esm/chunk-TLKYZKPW.mjs.map +0 -7
  258. package/dist/lib/node-esm/chunk-VCPGX7WN.mjs +0 -62
  259. package/dist/lib/node-esm/chunk-VCPGX7WN.mjs.map +0 -7
  260. package/dist/lib/node-esm/chunk-XSZMX53L.mjs +0 -96
  261. package/dist/lib/node-esm/chunk-XSZMX53L.mjs.map +0 -7
  262. package/dist/lib/node-esm/cli/index.mjs +0 -75
  263. package/dist/lib/node-esm/cli/index.mjs.map +0 -7
  264. package/dist/lib/node-esm/common/activation-events.mjs +0 -21
  265. package/dist/lib/node-esm/common/activation-events.mjs.map +0 -7
  266. package/dist/lib/node-esm/common/capabilities.mjs +0 -57
  267. package/dist/lib/node-esm/common/capabilities.mjs.map +0 -7
  268. package/dist/lib/node-esm/config/index.mjs +0 -9
  269. package/dist/lib/node-esm/config/index.mjs.map +0 -7
  270. package/dist/lib/node-esm/core/activation-event.mjs +0 -21
  271. package/dist/lib/node-esm/core/activation-event.mjs.map +0 -7
  272. package/dist/lib/node-esm/core/capability.mjs +0 -33
  273. package/dist/lib/node-esm/core/capability.mjs.map +0 -7
  274. package/dist/lib/node-esm/core/plugin-manager.mjs +0 -20
  275. package/dist/lib/node-esm/core/plugin-manager.mjs.map +0 -7
  276. package/dist/lib/node-esm/core/plugin.mjs +0 -52
  277. package/dist/lib/node-esm/core/plugin.mjs.map +0 -7
  278. package/dist/lib/node-esm/core/url-loader.mjs +0 -25
  279. package/dist/lib/node-esm/core/url-loader.mjs.map +0 -7
  280. package/dist/lib/node-esm/index.mjs +0 -99
  281. package/dist/lib/node-esm/index.mjs.map +0 -7
  282. package/dist/lib/node-esm/meta.json +0 -1
  283. package/dist/lib/node-esm/process-manager-capability-5S7WZ2JR.mjs +0 -133
  284. package/dist/lib/node-esm/process-manager-capability-5S7WZ2JR.mjs.map +0 -7
  285. package/dist/lib/node-esm/testing/index.mjs +0 -360
  286. package/dist/lib/node-esm/testing/index.mjs.map +0 -7
  287. package/dist/lib/node-esm/testing/react.mjs +0 -79
  288. package/dist/lib/node-esm/testing/react.mjs.map +0 -7
  289. package/dist/lib/node-esm/ui/index.mjs +0 -61
  290. package/dist/lib/node-esm/ui/index.mjs.map +0 -7
  291. package/dist/types/src/ui/components/Surface/SurfaceInfo.d.ts +0 -11
  292. package/dist/types/src/ui/components/Surface/SurfaceInfo.d.ts.map +0 -1
  293. package/src/ui/components/Surface/SurfaceInfo.tsx +0 -106
  294. package/vitest.config.ts +0 -14
@@ -0,0 +1,960 @@
1
+ import { ReactSurface } from "./common/capabilities.mjs";
2
+ import { Position } from "@dxos/util";
3
+ import { Atom } from "@effect-atom/atom";
4
+ import { log } from "@dxos/log";
5
+ import { Fragment, Profiler, Suspense, createContext, createElement, memo, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
6
+ import { ErrorBoundary } from "@dxos/react-error-boundary";
7
+ import { Fragment as Fragment$1, jsx } from "react/jsx-runtime";
8
+ import { raise } from "@dxos/debug";
9
+ import { useDefaultValue } from "@dxos/react-hooks";
10
+ import { RegistryContext, useAtomValue } from "@effect-atom/atom-react";
11
+ import { mx } from "@dxos/ui-theme";
12
+ import { createPortal } from "react-dom";
13
+ import { createRoot } from "react-dom/client";
14
+ import { addEventListener, combine } from "@dxos/async";
15
+ import * as Data from "effect/Data";
16
+ //#region src/helpers.ts
17
+ /**
18
+ * Topologically sorts a list of nodes based on their dependencies.
19
+ */
20
+ var topologicalSort = (nodes) => {
21
+ const getDependencies = (nodeId, seen = /* @__PURE__ */ new Set(), path = /* @__PURE__ */ new Set()) => {
22
+ if (path.has(nodeId)) throw new Error(`Circular dependency detected involving ${nodeId}`);
23
+ if (seen.has(nodeId)) return [];
24
+ const node = nodes.find((n) => n.id === nodeId);
25
+ if (!node) throw new Error(`Node ${nodeId} not found but is listed as a dependency`);
26
+ const newPath = /* @__PURE__ */ new Set([...path, nodeId]);
27
+ const newSeen = /* @__PURE__ */ new Set([...seen, nodeId]);
28
+ return [...(node.dependsOn ?? []).flatMap((depId) => getDependencies(depId, newSeen, newPath)), nodeId];
29
+ };
30
+ return nodes.map((node) => node.id).flatMap((id) => getDependencies(id)).filter((id, index, self) => self.indexOf(id) === index).map((id) => nodes.find((node) => node.id === id)).filter((node) => node !== void 0);
31
+ };
32
+ //#endregion
33
+ //#region src/ui/components/PluginManager/PluginManagerProvider.ts
34
+ var PluginManagerContext = createContext(void 0);
35
+ /**
36
+ * Get the plugin manager.
37
+ */
38
+ var usePluginManager = () => useContext(PluginManagerContext) ?? raise(/* @__PURE__ */ new Error("Missing PluginManagerContext"));
39
+ /**
40
+ * Get the plugin manager if present, or `undefined` when rendered outside a {@link PluginManagerProvider}
41
+ * (e.g. a standalone component story). Lets components treat plugin capabilities as a progressive
42
+ * enhancement rather than a hard requirement.
43
+ */
44
+ var useOptionalPluginManager = () => useContext(PluginManagerContext);
45
+ /**
46
+ * Context provider for a plugin manager.
47
+ */
48
+ var PluginManagerProvider = PluginManagerContext.Provider;
49
+ //#endregion
50
+ //#region src/ui/components/Surface/context.ts
51
+ var SurfaceContext = createContext(void 0);
52
+ //#endregion
53
+ //#region src/ui/components/Surface/SurfaceMetrics.ts
54
+ /** Consecutive unstable renders before `dataUnstable` is flagged. */
55
+ var UNSTABLE_THRESHOLD = 3;
56
+ var surfaceMetricKey = (surfaceId, role) => `surface/${surfaceId}/${role}`;
57
+ /**
58
+ * Shallow (top-level) value equality. Used to distinguish a genuinely new `data`
59
+ * value from a new object/array carrying the same content (the unstable-prop case).
60
+ */
61
+ var shallowEqual = (a, b) => {
62
+ if (Object.is(a, b)) return true;
63
+ if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false;
64
+ const aKeys = Object.keys(a);
65
+ const bKeys = Object.keys(b);
66
+ if (aKeys.length !== bKeys.length) return false;
67
+ return aKeys.every((key) => Object.is(a[key], b[key]));
68
+ };
69
+ /**
70
+ * Singleton store of surface dev metrics. A module singleton (rather than a React
71
+ * context) so the debug overlay — which renders in its own root outside the app's
72
+ * provider tree — can read the same data as the in-app devtools panel.
73
+ */
74
+ var SurfaceMetricsStore = class {
75
+ #metrics = /* @__PURE__ */ new Map();
76
+ #snapshot = [];
77
+ #listeners = /* @__PURE__ */ new Set();
78
+ #pendingNotify = false;
79
+ #entry(surfaceId, role) {
80
+ const id = surfaceMetricKey(surfaceId, role);
81
+ let metric = this.#metrics.get(id);
82
+ if (!metric) {
83
+ metric = {
84
+ id,
85
+ surfaceId,
86
+ role,
87
+ dispatches: 0,
88
+ candidates: 0,
89
+ truncated: false,
90
+ dataUnstable: false,
91
+ dataChurn: 0,
92
+ errors: 0,
93
+ mounts: 0,
94
+ unmounts: 0
95
+ };
96
+ this.#metrics.set(id, metric);
97
+ }
98
+ return metric;
99
+ }
100
+ /**
101
+ * Records a dispatch. `previousData` is the consumer's prior `data` reference;
102
+ * the store derives churn/instability from the identity-vs-value comparison.
103
+ */
104
+ recordDispatch(surfaceId, role, args) {
105
+ const metric = this.#entry(surfaceId, role);
106
+ metric.dispatches += 1;
107
+ metric.candidates = args.candidates;
108
+ metric.truncated = args.truncated;
109
+ metric.dataChurn = args.dataChurn;
110
+ metric.dataUnstable = args.dataChurn >= UNSTABLE_THRESHOLD;
111
+ this.#schedule();
112
+ }
113
+ recordError(surfaceId, role) {
114
+ this.#entry(surfaceId, role).errors += 1;
115
+ this.#schedule();
116
+ }
117
+ recordMount(surfaceId, role) {
118
+ this.#entry(surfaceId, role).mounts += 1;
119
+ this.#schedule();
120
+ }
121
+ recordUnmount(surfaceId, role) {
122
+ this.#entry(surfaceId, role).unmounts += 1;
123
+ this.#schedule();
124
+ }
125
+ clear() {
126
+ this.#metrics.clear();
127
+ this.#snapshot = [];
128
+ this.#notify();
129
+ }
130
+ subscribe = (listener) => {
131
+ this.#listeners.add(listener);
132
+ return () => {
133
+ this.#listeners.delete(listener);
134
+ };
135
+ };
136
+ getSnapshot = () => this.#snapshot;
137
+ /** Defers notification to the next frame to coalesce bursts of records. */
138
+ #schedule() {
139
+ if (this.#pendingNotify || typeof requestAnimationFrame === "undefined") {
140
+ if (typeof requestAnimationFrame === "undefined") this.#notify();
141
+ return;
142
+ }
143
+ this.#pendingNotify = true;
144
+ requestAnimationFrame(() => {
145
+ this.#pendingNotify = false;
146
+ this.#notify();
147
+ });
148
+ }
149
+ #notify() {
150
+ this.#snapshot = [...this.#metrics.values()];
151
+ for (const listener of this.#listeners) listener();
152
+ }
153
+ };
154
+ var surfaceMetrics = new SurfaceMetricsStore();
155
+ /**
156
+ * Updates the running churn count for a consumer's `data` prop.
157
+ *
158
+ * @returns the new churn count (0 when `data` changed value or is unchanged).
159
+ */
160
+ var nextDataChurn = (previous, next, churn) => {
161
+ if (Object.is(previous, next)) return churn;
162
+ return shallowEqual(previous, next) ? churn + 1 : 0;
163
+ };
164
+ /**
165
+ * Subscribes to surface dev metrics, sorted with the most concerning first
166
+ * (unstable data, then errors, then dispatch count).
167
+ */
168
+ var useSurfaceMetrics = () => {
169
+ return [...useSyncExternalStore(surfaceMetrics.subscribe, surfaceMetrics.getSnapshot, surfaceMetrics.getSnapshot)].sort((a, b) => Number(b.dataUnstable) - Number(a.dataUnstable) || b.errors - a.errors || b.dispatches - a.dispatches);
170
+ };
171
+ //#endregion
172
+ //#region src/ui/components/Surface/SurfaceDebug.tsx
173
+ var DEBUG_FLAG = "__DX_DEBUG__";
174
+ /**
175
+ * Custom element tag wrapping each React surface in debug builds.
176
+ */
177
+ var DX_SURFACE_TAG = "dx-surface";
178
+ /**
179
+ * Installs the `window.__DX__` DevTools helper. `__DX__.surfaces(component?)` returns the mounted
180
+ * `<dx-surface>` elements (optionally filtered by `data-component`) — a console shortcut for the
181
+ * otherwise-verbose `document.querySelectorAll('dx-surface[data-component="…"]')`.
182
+ */
183
+ var ensureDebugApi = () => {
184
+ if (typeof window === "undefined") return;
185
+ window.__DX__ = {
186
+ ...window.__DX__,
187
+ surfaces: (component) => Array.from(document.querySelectorAll(component ? `${DX_SURFACE_TAG}[data-component="${component}"]` : DX_SURFACE_TAG))
188
+ };
189
+ };
190
+ /**
191
+ * Whether surface debugging is enabled, via the `VITE_DEBUG` build flag or the
192
+ * `__DX_DEBUG__` runtime global. The runtime flag is toggleable without a
193
+ * rebuild and takes effect on the next render.
194
+ */
195
+ var isSurfaceDebugEnabled = () => Boolean(void 0) || typeof window !== "undefined" && DEBUG_FLAG in window;
196
+ /**
197
+ * Whether the `<dx-surface>` wrapper is rendered. Enabled in any development build (or when
198
+ * `VITE_DEBUG` is set) so the wrapper's `data-*` attributes and `window.__DX__` helpers are available
199
+ * for DOM inspection independent of the runtime highlight flag. Production renders no wrapper.
200
+ */
201
+ var isSurfaceWrapperEnabled = () => Boolean(false) || Boolean(void 0);
202
+ var flagListeners = /* @__PURE__ */ new Set();
203
+ var subscribeFlag = (listener) => {
204
+ flagListeners.add(listener);
205
+ return () => {
206
+ flagListeners.delete(listener);
207
+ };
208
+ };
209
+ /**
210
+ * Toggles the `__DX_DEBUG__` runtime flag, which gates the visual highlight overlay (not the
211
+ * `<dx-surface>` wrapper). The overlay redraws immediately; surfaces pick it up on their next render.
212
+ */
213
+ var setSurfaceDebug = (enabled) => {
214
+ if (typeof window === "undefined") return;
215
+ if (enabled) window[DEBUG_FLAG] = true;
216
+ else delete window[DEBUG_FLAG];
217
+ for (const listener of flagListeners) listener();
218
+ };
219
+ var elementRegistered = false;
220
+ var ensureSurfaceElement = () => {
221
+ if (elementRegistered || typeof customElements === "undefined") return;
222
+ elementRegistered = true;
223
+ if (!customElements.get("dx-surface")) {
224
+ class DxSurfaceElement extends HTMLElement {
225
+ connectedCallback() {
226
+ this.style.display = "contents";
227
+ }
228
+ }
229
+ customElements.define(DX_SURFACE_TAG, DxSurfaceElement);
230
+ }
231
+ };
232
+ /**
233
+ * Singleton registry of mounted debug surfaces. Centralizes overlay rendering so
234
+ * a single set of observers/listeners serves every surface, instead of each
235
+ * surface mounting its own portal and observers.
236
+ */
237
+ var SurfaceDebugManager = class {
238
+ #entries = /* @__PURE__ */ new Set();
239
+ #snapshot = [];
240
+ #listeners = /* @__PURE__ */ new Set();
241
+ #nextKey = 0;
242
+ register(element, infoRef) {
243
+ const entry = {
244
+ key: this.#nextKey++,
245
+ element,
246
+ infoRef
247
+ };
248
+ this.#entries.add(entry);
249
+ this.#update();
250
+ return () => {
251
+ this.#entries.delete(entry);
252
+ this.#update();
253
+ };
254
+ }
255
+ subscribe = (listener) => {
256
+ this.#listeners.add(listener);
257
+ return () => {
258
+ this.#listeners.delete(listener);
259
+ };
260
+ };
261
+ getSnapshot = () => this.#snapshot;
262
+ #update() {
263
+ this.#snapshot = [...this.#entries];
264
+ for (const listener of this.#listeners) listener();
265
+ }
266
+ };
267
+ var manager = new SurfaceDebugManager();
268
+ var overlayMounted = false;
269
+ var ensureOverlay = () => {
270
+ if (overlayMounted || typeof document === "undefined") return;
271
+ overlayMounted = true;
272
+ const root = document.createElement("div");
273
+ root.id = "dx-surface-overlay";
274
+ document.body.appendChild(root);
275
+ createRoot(root).render(/* @__PURE__ */ jsx(SurfaceDebugOverlay, {}));
276
+ };
277
+ /**
278
+ * Measures a `display: contents` element (which has no box of its own) by unioning the border boxes
279
+ * of its rendered children. Deliberately uses each child's own `getBoundingClientRect` rather than a
280
+ * Range over all contents: a Range unions every descendant rect, so content overflowing an inner
281
+ * `overflow: auto` scroll container inflates the width past the surface's visible bounds. A direct
282
+ * child's box is clipped to its own border box, so the highlight stays tight to what is on screen.
283
+ */
284
+ var measureContents = (element) => {
285
+ if (typeof document === "undefined" || typeof getComputedStyle !== "function") return null;
286
+ const boxes = [];
287
+ const collect = (node) => {
288
+ for (const child of node.children) if (getComputedStyle(child).display === "contents") collect(child);
289
+ else {
290
+ const rect = child.getBoundingClientRect();
291
+ if (rect.width > 0 || rect.height > 0) boxes.push(rect);
292
+ }
293
+ };
294
+ collect(element);
295
+ if (boxes.length === 0) return null;
296
+ let left = Infinity;
297
+ let top = Infinity;
298
+ let right = -Infinity;
299
+ let bottom = -Infinity;
300
+ for (const box of boxes) {
301
+ left = Math.min(left, box.left);
302
+ top = Math.min(top, box.top);
303
+ right = Math.max(right, box.right);
304
+ bottom = Math.max(bottom, box.bottom);
305
+ }
306
+ return new DOMRect(left, top, right - left, bottom - top);
307
+ };
308
+ var EMPTY_RECTS = /* @__PURE__ */ new Map();
309
+ /**
310
+ * Single overlay that draws boundary highlights for every registered surface.
311
+ * One ResizeObserver plus one scroll/resize listener serve all surfaces.
312
+ */
313
+ var SurfaceDebugOverlay = () => {
314
+ const entries = useSyncExternalStore(manager.subscribe, manager.getSnapshot, manager.getSnapshot);
315
+ const enabled = useSyncExternalStore(subscribeFlag, isSurfaceDebugEnabled, isSurfaceDebugEnabled);
316
+ const [rects, setRects] = useState(EMPTY_RECTS);
317
+ useLayoutEffect(() => {
318
+ if (!enabled || entries.length === 0) {
319
+ setRects(EMPTY_RECTS);
320
+ return;
321
+ }
322
+ const measure = () => {
323
+ const next = /* @__PURE__ */ new Map();
324
+ for (const entry of entries) {
325
+ const rect = measureContents(entry.element);
326
+ if (rect) next.set(entry.key, rect);
327
+ }
328
+ setRects(next);
329
+ };
330
+ const observer = typeof ResizeObserver !== "undefined" ? new ResizeObserver(measure) : void 0;
331
+ for (const entry of entries) if (observer && entry.element.parentElement) observer.observe(entry.element.parentElement);
332
+ measure();
333
+ return combine(addEventListener(window, "scroll", measure, true), addEventListener(window, "resize", measure), () => observer?.disconnect());
334
+ }, [entries, enabled]);
335
+ if (!enabled) return null;
336
+ return createPortal(/* @__PURE__ */ jsx(Fragment$1, { children: entries.map((entry) => {
337
+ const rect = rects.get(entry.key);
338
+ return rect ? /* @__PURE__ */ jsx(SurfaceHighlight, {
339
+ infoRef: entry.infoRef,
340
+ rect
341
+ }, entry.key) : null;
342
+ }) }), document.body);
343
+ };
344
+ /** Subscribes to the metric for a single surface. */
345
+ var useSurfaceMetric = (surfaceId, role) => {
346
+ const all = useSyncExternalStore(surfaceMetrics.subscribe, surfaceMetrics.getSnapshot, surfaceMetrics.getSnapshot);
347
+ const key = surfaceMetricKey(surfaceId, role);
348
+ return all.find((metric) => metric.id === key);
349
+ };
350
+ var SurfaceHighlight = ({ infoRef, rect }) => {
351
+ const [expand, setExpand] = useState(false);
352
+ const info = infoRef.current;
353
+ const metric = useSurfaceMetric(info.id ?? "", info.role);
354
+ const concern = !!metric && (metric.dataUnstable || metric.errors > 0);
355
+ return /* @__PURE__ */ jsx("div", {
356
+ className: "z-[100] fixed flex flex-col-reverse scrollbar-none overflow-auto pointer-events-none",
357
+ style: {
358
+ top: rect.top,
359
+ left: rect.left,
360
+ width: rect.width,
361
+ height: rect.height
362
+ },
363
+ children: expand ? /* @__PURE__ */ jsx("div", {
364
+ className: "absolute inset-0 border-2 border-rose-500 border-dotted overflow-auto pointer-events-auto",
365
+ onPointerDown: (ev) => ev.stopPropagation(),
366
+ onClick: (ev) => {
367
+ ev.stopPropagation();
368
+ setExpand(false);
369
+ },
370
+ children: /* @__PURE__ */ jsx("div", {
371
+ className: "absolute left-1 right-1 bottom-1 max-h-[20rem] overflow-auto dx-card-surface ring-2 ring-separator rounded-sm opacity-90",
372
+ children: /* @__PURE__ */ jsx("pre", {
373
+ className: "inline-block p-2 text-xs text-description font-mono font-thin",
374
+ children: JSON.stringify({
375
+ info,
376
+ metric
377
+ }, null, 2)
378
+ })
379
+ })
380
+ }) : /* @__PURE__ */ jsx("span", {
381
+ className: mx(concern ? "text-rose-500" : "text-green-500", "absolute right-2 bottom-1 flex items-center p-1 opacity-80 hover:opacity-100 text-sm cursor-pointer pointer-events-auto"),
382
+ title: metricSummary(info.id ?? "", metric),
383
+ onPointerDown: (ev) => ev.stopPropagation(),
384
+ onClick: (ev) => {
385
+ ev.stopPropagation();
386
+ setExpand(true);
387
+ },
388
+ children: concern ? "⚠" : "ⓘ"
389
+ })
390
+ });
391
+ };
392
+ var metricSummary = (surfaceId, metric) => {
393
+ if (!metric) return surfaceId;
394
+ const parts = [
395
+ surfaceId,
396
+ `dispatches=${metric.dispatches}`,
397
+ `candidates=${metric.candidates}${metric.truncated ? "(+truncated)" : ""}`,
398
+ `mounts=${metric.mounts}/unmounts=${metric.unmounts}`
399
+ ];
400
+ if (metric.dataUnstable) parts.push(`UNSTABLE data (churn=${metric.dataChurn})`);
401
+ if (metric.errors > 0) parts.push(`errors=${metric.errors}`);
402
+ return parts.join(" · ");
403
+ };
404
+ /**
405
+ * Recovers the rendered surface component's name from the React fiber. Surfaces are registered as
406
+ * anonymous `component: (props) => <Real .../>` wrappers, so the static component carries no useful
407
+ * name; walking the fiber's child chain to the first component with a display name (as DevTools does)
408
+ * finds the real one. The wrapper's own inferred name is the property key ('component'), so skip it.
409
+ */
410
+ var renderedComponentName = (element) => {
411
+ const node = element;
412
+ const fiberKey = Object.keys(node).find((key) => key.startsWith("__reactFiber$"));
413
+ let fiber = fiberKey ? node[fiberKey]?.child : void 0;
414
+ while (fiber) {
415
+ const { type } = fiber;
416
+ if (typeof type === "function") {
417
+ const name = type.displayName ?? type.name;
418
+ if (name && name !== "component") return name;
419
+ }
420
+ fiber = fiber.child;
421
+ }
422
+ };
423
+ /**
424
+ * Debug wrapper that mounts each React surface inside a `<dx-surface>` element
425
+ * and registers it with the centralized overlay. The element is named and
426
+ * inspectable in DevTools and queryable (`document.querySelectorAll('dx-surface')`).
427
+ */
428
+ var DebugSurface = ({ info, children }) => {
429
+ const infoRef = useRef(info);
430
+ infoRef.current = info;
431
+ const elementRef = useRef(null);
432
+ useEffect(() => {
433
+ ensureSurfaceElement();
434
+ ensureOverlay();
435
+ ensureDebugApi();
436
+ const { id, role } = infoRef.current;
437
+ if (id) surfaceMetrics.recordMount(id, role);
438
+ const element = elementRef.current;
439
+ const unregister = element ? manager.register(element, infoRef) : void 0;
440
+ const component = element ? renderedComponentName(element) : void 0;
441
+ if (element && component) element.setAttribute("data-component", component);
442
+ return () => {
443
+ if (id) surfaceMetrics.recordUnmount(id, role);
444
+ unregister?.();
445
+ };
446
+ }, []);
447
+ return createElement(DX_SURFACE_TAG, {
448
+ "className": "contents",
449
+ "data-id": info.id,
450
+ "data-role": info.role,
451
+ "ref": elementRef
452
+ }, children);
453
+ };
454
+ //#endregion
455
+ //#region src/ui/components/Surface/SurfaceManagerContext.ts
456
+ var SurfaceManagerContext = createContext(void 0);
457
+ /**
458
+ * Get the surface manager. Throws when rendered outside a {@link SurfaceManagerProvider};
459
+ * surfaces already hard-require the plugin-manager context, so there is no optional variant.
460
+ */
461
+ var useSurfaceManager = () => useContext(SurfaceManagerContext) ?? raise(/* @__PURE__ */ new Error("Missing SurfaceManagerContext"));
462
+ /**
463
+ * Context provider for a surface manager.
464
+ */
465
+ var SurfaceManagerProvider = SurfaceManagerContext.Provider;
466
+ //#endregion
467
+ //#region src/ui/components/Surface/SurfaceProfilerContext.tsx
468
+ var MAX_ENTRIES = 500;
469
+ /**
470
+ * Store that collects profiler entries and notifies subscribers.
471
+ */
472
+ var SurfaceProfilerStore = class {
473
+ _entries = [];
474
+ _listeners = /* @__PURE__ */ new Set();
475
+ _snapshot = [];
476
+ _pendingNotify = false;
477
+ /**
478
+ * Records an entry and schedules a deferred notification to avoid re-render loops.
479
+ * `_snapshot` is rebuilt only when the deferred notification actually fires
480
+ * ({@link _notifySync}) — not here — so `getSnapshot` stays referentially stable for any
481
+ * synchronous re-invocation React makes within the same commit (e.g. the tearing check a
482
+ * profiled subscriber's own `useSyncExternalStore` runs right after this Profiler's
483
+ * `onRender` callback). Rebuilding it synchronously here would change what `getSnapshot`
484
+ * returns before listeners are told, which React reads as a torn store and forces an
485
+ * immediate re-render — and if the re-rendering component is itself profiled, that
486
+ * re-render re-triggers `record`, looping forever.
487
+ */
488
+ record(entry) {
489
+ this._entries.push(entry);
490
+ if (this._entries.length > MAX_ENTRIES) this._entries = this._entries.slice(-500);
491
+ this._scheduleNotify();
492
+ }
493
+ clear() {
494
+ this._entries = [];
495
+ this._snapshot = [];
496
+ this._notifySync();
497
+ }
498
+ subscribe = (listener) => {
499
+ this._listeners.add(listener);
500
+ return () => {
501
+ this._listeners.delete(listener);
502
+ };
503
+ };
504
+ getSnapshot = () => {
505
+ return this._snapshot;
506
+ };
507
+ /**
508
+ * Defers notification to the next animation frame to break the
509
+ * Profiler onRender → record → notify → re-render → onRender loop.
510
+ */
511
+ _scheduleNotify() {
512
+ if (!this._pendingNotify) {
513
+ this._pendingNotify = true;
514
+ requestAnimationFrame(() => {
515
+ this._pendingNotify = false;
516
+ this._notifySync();
517
+ });
518
+ }
519
+ }
520
+ _notifySync() {
521
+ this._snapshot = [...this._entries];
522
+ for (const listener of this._listeners) listener();
523
+ }
524
+ };
525
+ var SurfaceProfilerContext = createContext(void 0);
526
+ /**
527
+ * Provider that collects React Profiler data from Surface components.
528
+ */
529
+ var SurfaceProfilerProvider = ({ children }) => {
530
+ const storeRef = useRef(null);
531
+ if (!storeRef.current) storeRef.current = new SurfaceProfilerStore();
532
+ return /* @__PURE__ */ jsx(SurfaceProfilerContext.Provider, {
533
+ value: { store: storeRef.current },
534
+ children
535
+ });
536
+ };
537
+ /**
538
+ * Returns a stable onRender callback for use with React Profiler.
539
+ */
540
+ var useSurfaceProfilerCallback = () => {
541
+ const store = useContext(SurfaceProfilerContext)?.store;
542
+ return useMemo(() => {
543
+ if (!store) return;
544
+ return (id, phase, actualDuration, baseDuration, startTime, commitTime) => {
545
+ store.record({
546
+ id,
547
+ phase,
548
+ actualDuration,
549
+ baseDuration,
550
+ startTime,
551
+ commitTime,
552
+ timestamp: Date.now()
553
+ });
554
+ };
555
+ }, [store]);
556
+ };
557
+ /**
558
+ * Returns all profiler entries reactively.
559
+ */
560
+ var useSurfaceProfilerEntries = () => {
561
+ const context = useContext(SurfaceProfilerContext);
562
+ return useSyncExternalStore(context?.store.subscribe ?? noop, context?.store.getSnapshot ?? emptySnapshot);
563
+ };
564
+ /**
565
+ * Returns aggregated stats grouped by surface id.
566
+ */
567
+ var useSurfaceProfilerStats = () => {
568
+ const entries = useSurfaceProfilerEntries();
569
+ const statsMap = /* @__PURE__ */ new Map();
570
+ for (const entry of entries) {
571
+ let stats = statsMap.get(entry.id);
572
+ if (!stats) {
573
+ stats = {
574
+ id: entry.id,
575
+ mountCount: 0,
576
+ updateCount: 0,
577
+ totalRenders: 0,
578
+ avgActualDuration: 0,
579
+ maxActualDuration: 0,
580
+ avgBaseDuration: 0,
581
+ lastActualDuration: 0,
582
+ lastCommitTime: 0
583
+ };
584
+ statsMap.set(entry.id, stats);
585
+ }
586
+ if (entry.phase === "mount") stats.mountCount++;
587
+ else stats.updateCount++;
588
+ stats.totalRenders++;
589
+ stats.avgActualDuration = (stats.avgActualDuration * (stats.totalRenders - 1) + entry.actualDuration) / stats.totalRenders;
590
+ stats.avgBaseDuration = (stats.avgBaseDuration * (stats.totalRenders - 1) + entry.baseDuration) / stats.totalRenders;
591
+ stats.maxActualDuration = Math.max(stats.maxActualDuration, entry.actualDuration);
592
+ stats.lastActualDuration = entry.actualDuration;
593
+ stats.lastCommitTime = entry.commitTime;
594
+ }
595
+ return [...statsMap.values()].sort((a, b) => b.maxActualDuration - a.maxActualDuration);
596
+ };
597
+ /**
598
+ * Clears all collected profiler entries.
599
+ */
600
+ var useSurfaceProfilerClear = () => {
601
+ const store = useContext(SurfaceProfilerContext)?.store;
602
+ return useMemo(() => store ? () => store.clear() : void 0, [store]);
603
+ };
604
+ var noop = () => () => {};
605
+ var EMPTY_SNAPSHOT = [];
606
+ var emptySnapshot = () => EMPTY_SNAPSHOT;
607
+ //#endregion
608
+ //#region src/ui/components/Surface/SurfaceComponent.tsx
609
+ var DEFAULT_PLACEHOLDER = /* @__PURE__ */ jsx(Fragment, {});
610
+ var WebComponentWrapper = memo(({ id, role, data, limit, definition, ...rest }) => {
611
+ const containerRef = useRef(null);
612
+ const elementRef = useRef(null);
613
+ const propsRef = useRef({
614
+ id,
615
+ role,
616
+ data,
617
+ limit,
618
+ ...rest
619
+ });
620
+ propsRef.current = {
621
+ id,
622
+ role,
623
+ data,
624
+ limit,
625
+ ...rest
626
+ };
627
+ useEffect(() => {
628
+ if (!containerRef.current || elementRef.current) return;
629
+ const element = document.createElement(definition.tagName);
630
+ elementRef.current = element;
631
+ Object.assign(element, propsRef.current);
632
+ containerRef.current.appendChild(element);
633
+ return () => {
634
+ if (elementRef.current && containerRef.current?.contains(elementRef.current)) containerRef.current.removeChild(elementRef.current);
635
+ elementRef.current = null;
636
+ };
637
+ }, [definition.tagName]);
638
+ const previousPropsRef = useRef({});
639
+ useEffect(() => {
640
+ const element = elementRef.current;
641
+ if (element) {
642
+ for (const key of Object.keys(previousPropsRef.current)) if (!(key in propsRef.current)) Reflect.deleteProperty(element, key);
643
+ Object.assign(element, propsRef.current);
644
+ previousPropsRef.current = propsRef.current;
645
+ }
646
+ });
647
+ return /* @__PURE__ */ jsx("div", { ref: containerRef });
648
+ });
649
+ WebComponentWrapper.displayName = "WebComponentWrapper";
650
+ /**
651
+ * Wrapper component that provides context for a surface.
652
+ */
653
+ var SurfaceContextProvider = memo(({ id, role, data, limit, fallback = ErrorFallback$1, definition, ...rest }) => {
654
+ const contextValue = useMemo(() => ({
655
+ id,
656
+ role,
657
+ data
658
+ }), [
659
+ id,
660
+ role,
661
+ data
662
+ ]);
663
+ const onProfilerRender = useSurfaceProfilerCallback();
664
+ const profilerId = `surface/${id}/${role}`;
665
+ const onError = isSurfaceDebugEnabled() ? () => surfaceMetrics.recordError(id, role) : void 0;
666
+ if (definition.kind === "web-component") return /* @__PURE__ */ jsx(ErrorBoundary, {
667
+ name: "surface",
668
+ resetKeys: [data],
669
+ FallbackComponent: fallback,
670
+ onError,
671
+ children: /* @__PURE__ */ jsx(SurfaceContext.Provider, {
672
+ value: contextValue,
673
+ children: /* @__PURE__ */ jsx(WebComponentWrapper, {
674
+ id,
675
+ role,
676
+ data,
677
+ limit,
678
+ definition,
679
+ ...rest
680
+ })
681
+ })
682
+ });
683
+ const Component = definition.component;
684
+ const surfaceProps = {
685
+ id,
686
+ role,
687
+ data,
688
+ limit,
689
+ ...rest
690
+ };
691
+ const component = /* @__PURE__ */ jsx(Component, { ...definition.props?.(surfaceProps) ?? surfaceProps });
692
+ const profiled = onProfilerRender && !profilerId.includes("org.dxos.plugin.debug") ? /* @__PURE__ */ jsx(Profiler, {
693
+ id: profilerId,
694
+ onRender: onProfilerRender,
695
+ children: component
696
+ }) : component;
697
+ if (isSurfaceWrapperEnabled()) return /* @__PURE__ */ jsx(ErrorBoundary, {
698
+ name: "surface",
699
+ resetKeys: [data],
700
+ FallbackComponent: fallback,
701
+ onError,
702
+ children: /* @__PURE__ */ jsx(SurfaceContext.Provider, {
703
+ value: contextValue,
704
+ children: /* @__PURE__ */ jsx(DebugSurface, {
705
+ info: contextValue,
706
+ children: profiled
707
+ })
708
+ })
709
+ });
710
+ return /* @__PURE__ */ jsx(ErrorBoundary, {
711
+ name: "surface",
712
+ resetKeys: [data],
713
+ FallbackComponent: fallback,
714
+ onError,
715
+ children: /* @__PURE__ */ jsx(SurfaceContext.Provider, {
716
+ value: contextValue,
717
+ children: profiled
718
+ })
719
+ });
720
+ });
721
+ SurfaceContextProvider.displayName = "SurfaceContextProvider";
722
+ /**
723
+ * A surface is a named region of the screen that can be populated by plugins.
724
+ * The `type` prop is a {@link Role.Role} that defines which region and its
725
+ * associated data contract.
726
+ *
727
+ * A surface is a boundary that may resolve to zero, one, or many components, so
728
+ * it intentionally accepts no `ref`; consumers needing an element should own one
729
+ * inside their contributed component.
730
+ */
731
+ var SurfaceComponent = memo(({ id: _id, type, data: dataProp, limit, placeholder = DEFAULT_PLACEHOLDER, ...rest }) => {
732
+ const data = useDefaultValue(dataProp, () => ({}));
733
+ const surfaceManager = useSurfaceManager();
734
+ const effectiveRole = type?.role ?? "";
735
+ const definitions = matchCandidates(useAtomValue(surfaceManager.candidatesAtom(effectiveRole)), effectiveRole, data);
736
+ const candidates = limit != null ? definitions.slice(0, limit) : definitions;
737
+ const truncated = limit != null && definitions.length > limit;
738
+ const churnRef = useRef({
739
+ data: void 0,
740
+ churn: 0
741
+ });
742
+ useEffect(() => {
743
+ if (!isSurfaceDebugEnabled() || effectiveRole === "") return;
744
+ const previous = churnRef.current;
745
+ const churn = previous.data === void 0 ? 0 : nextDataChurn(previous.data, data, previous.churn);
746
+ churnRef.current = {
747
+ data,
748
+ churn
749
+ };
750
+ for (const definition of candidates) surfaceMetrics.recordDispatch(definition.id, effectiveRole, {
751
+ candidates: candidates.length,
752
+ truncated,
753
+ dataChurn: churn
754
+ });
755
+ });
756
+ if (type?.role == null) return null;
757
+ return /* @__PURE__ */ jsx(Suspense, {
758
+ fallback: placeholder,
759
+ children: candidates.map((definition) => /* @__PURE__ */ jsx(SurfaceContextProvider, {
760
+ id: definition.id,
761
+ role: effectiveRole,
762
+ data,
763
+ limit,
764
+ definition,
765
+ ...rest
766
+ }, definition.id))
767
+ });
768
+ });
769
+ SurfaceComponent.displayName = "Surface";
770
+ var ErrorFallback$1 = ({ error }) => {
771
+ const { message } = error instanceof Error ? error : { message: String(error) };
772
+ return /* @__PURE__ */ jsx("div", {
773
+ role: "alert",
774
+ "data-testid": "error-boundary-fallback",
775
+ children: /* @__PURE__ */ jsx("h1", {
776
+ className: "flex p-2 text-sm text-info-text",
777
+ children: message
778
+ })
779
+ });
780
+ };
781
+ /**
782
+ * Filters the pre-indexed candidates for a role through their data guards.
783
+ */
784
+ var matchCandidates = (definitions, role, data) => {
785
+ if (!definitions) return [];
786
+ return definitions.filter(({ filter }) => filter ? filter(data ?? {}, role) : true);
787
+ };
788
+ /**
789
+ * @returns a stable function that checks whether a contributed surface is available for a
790
+ * role & data. The surface manager is captured via context, so the returned function carries
791
+ * no dependency of its own and is safe to store and invoke later — e.g. from inside another
792
+ * callback such as a render-prop or event handler — since it calls no hooks itself.
793
+ */
794
+ var useIsSurfaceAvailable = () => {
795
+ const surfaceManager = useSurfaceManager();
796
+ const registry = useContext(RegistryContext);
797
+ return useCallback((args) => {
798
+ const effectiveRole = args.type?.role;
799
+ if (effectiveRole == null) return false;
800
+ return matchCandidates(registry.get(surfaceManager.candidatesAtom(effectiveRole)), effectiveRole, args.data).length > 0;
801
+ }, [surfaceManager, registry]);
802
+ };
803
+ //#endregion
804
+ //#region src/ui/components/Surface/types.ts
805
+ var __dxlog_file$1 = "/__w/dxos/dxos/packages/sdk/app-framework/src/ui/components/Surface/types.ts";
806
+ /**
807
+ * Runtime guard for {@link Filter}. Distinguishes new-style filter bindings from
808
+ * legacy predicate filters.
809
+ */
810
+ var isFilter = (value) => typeof value === "object" && value !== null && Array.isArray(value.bindings);
811
+ /**
812
+ * Creates a {@link Filter} from a role token and an optional guard.
813
+ *
814
+ * When `guard` is omitted the filter matches any data at the token's role
815
+ * (role-only dispatch). Pass a guard to add runtime data-shape validation on
816
+ * top of the role match.
817
+ *
818
+ * This is the framework-level primitive; `@dxos/app-toolkit` builds richer
819
+ * domain-aware helpers (ECHO schema checks, literal matching, etc.) on top of it.
820
+ */
821
+ var makeFilter = (token, guard) => {
822
+ const boundGuard = guard == null ? () => true : (data) => {
823
+ try {
824
+ return guard(data);
825
+ } catch (err) {
826
+ log.catch(err, void 0, {
827
+ "~LogMeta": "~LogMeta",
828
+ F: __dxlog_file$1,
829
+ L: 57,
830
+ S: void 0
831
+ });
832
+ return false;
833
+ }
834
+ };
835
+ return { bindings: [{
836
+ role: token.role,
837
+ guard: boundGuard
838
+ }] };
839
+ };
840
+ var expandBindings = (filter) => {
841
+ const bindings = filter.bindings;
842
+ const roles = Array.from(new Set(bindings.map((binding) => binding.role)));
843
+ const guard = (data, role) => {
844
+ if (role != null) return bindings.some((entry) => entry.role === role && entry.guard(data));
845
+ return bindings.some((entry) => entry.guard(data));
846
+ };
847
+ return {
848
+ role: roles.length === 1 ? roles[0] : roles,
849
+ guard
850
+ };
851
+ };
852
+ /**
853
+ * Whether a surface or extension local ID follows NSID conventions: the final
854
+ * dot-separated segment must be camelCase (letters and digits only, starting
855
+ * with a letter — no hyphens or underscores). This mirrors the rule enforced
856
+ * when the id is appended to a plugin's NSID to form a full DXN path.
857
+ *
858
+ * A definition with an invalid id is dropped at dispatch rather than rejected
859
+ * here, so a single malformed contribution cannot crash plugin activation.
860
+ *
861
+ * @example Valid: 'about', 'integrationArticle', 'article.journal'
862
+ * @example Invalid: 'integration-article', 'plugin-spec'
863
+ */
864
+ var isValidLocalId = (id) => /^[a-zA-Z][a-zA-Z0-9]*$/.test(id.split(".").pop() ?? "");
865
+ function create(definition) {
866
+ const { id, filter, component, props, position } = definition;
867
+ const { role, guard } = expandBindings(filter);
868
+ return {
869
+ kind: "react",
870
+ id,
871
+ role,
872
+ position,
873
+ component,
874
+ props,
875
+ filter: guard
876
+ };
877
+ }
878
+ function createWeb(definition) {
879
+ const { id, filter, tagName, position } = definition;
880
+ const { role, guard } = expandBindings(filter);
881
+ return {
882
+ kind: "web-component",
883
+ id,
884
+ role,
885
+ position,
886
+ tagName,
887
+ filter: guard
888
+ };
889
+ }
890
+ //#endregion
891
+ //#region src/ui/components/Surface/SurfaceManager.ts
892
+ var __dxlog_file = "/__w/dxos/dxos/packages/sdk/app-framework/src/ui/components/Surface/SurfaceManager.ts";
893
+ var EMPTY_CANDIDATES = Data.array([]);
894
+ /**
895
+ * Groups definitions by role with each bucket pre-sorted by {@link Position}, so
896
+ * dispatch avoids a full scan and re-sort on every render. Pure helper — callers
897
+ * are responsible for filtering out invalid definitions before calling this.
898
+ */
899
+ var indexByRole = (definitions) => {
900
+ const index = /* @__PURE__ */ new Map();
901
+ for (const definition of definitions) {
902
+ const roles = Array.isArray(definition.role) ? definition.role : [definition.role];
903
+ for (const role of roles) {
904
+ let bucket = index.get(role);
905
+ if (!bucket) {
906
+ bucket = [];
907
+ index.set(role, bucket);
908
+ }
909
+ bucket.push(definition);
910
+ }
911
+ }
912
+ for (const bucket of index.values()) bucket.sort(Position.compare);
913
+ return index;
914
+ };
915
+ /**
916
+ * Owns the per-manager surface memoization: one derived index atom plus a per-role
917
+ * family of candidate atoms. A single instance is provided via
918
+ * {@link SurfaceManagerProvider}, so instance identity does the per-manager keying
919
+ * (replacing module-level WeakMaps) and `Atom.family` does the per-role keying.
920
+ * Atom lifecycle is tied to the provider rather than module-global state.
921
+ */
922
+ var SurfaceManager = class {
923
+ #capabilities;
924
+ #index = Atom.make((get) => {
925
+ const definitions = get(this.#capabilities.atom(ReactSurface)).flat();
926
+ return indexByRole(this.#dropInvalid(definitions));
927
+ }).pipe(Atom.keepAlive);
928
+ #candidates = Atom.family((role) => Atom.make((get) => {
929
+ const bucket = get(this.#index).get(role);
930
+ return bucket ? Data.array(bucket) : EMPTY_CANDIDATES;
931
+ }).pipe(Atom.keepAlive));
932
+ #warnedInvalidIds = /* @__PURE__ */ new Set();
933
+ constructor(capabilities) {
934
+ this.#capabilities = capabilities;
935
+ }
936
+ /** Derived atom yielding the (position-sorted) candidates for a single role. */
937
+ candidatesAtom(role) {
938
+ return this.#candidates(role);
939
+ }
940
+ /** Drops definitions with an invalid local id, warning once per id. */
941
+ #dropInvalid(definitions) {
942
+ return definitions.filter((definition) => {
943
+ if (isValidLocalId(definition.id)) return true;
944
+ if (!this.#warnedInvalidIds.has(definition.id)) {
945
+ this.#warnedInvalidIds.add(definition.id);
946
+ log.warn("dropping surface with invalid id; the final segment must be camelCase (no hyphens or underscores)", { id: definition.id }, {
947
+ "~LogMeta": "~LogMeta",
948
+ F: __dxlog_file,
949
+ L: 88,
950
+ S: this
951
+ });
952
+ }
953
+ return false;
954
+ });
955
+ }
956
+ };
957
+ //#endregion
958
+ export { topologicalSort as C, usePluginManager as S, surfaceMetrics as _, makeFilter as a, PluginManagerProvider as b, SurfaceProfilerProvider as c, useSurfaceProfilerEntries as d, useSurfaceProfilerStats as f, setSurfaceDebug as g, isSurfaceDebugEnabled as h, isFilter as i, useSurfaceProfilerCallback as l, useSurfaceManager as m, create as n, SurfaceComponent as o, SurfaceManagerProvider as p, createWeb as r, useIsSurfaceAvailable as s, SurfaceManager as t, useSurfaceProfilerClear as u, useSurfaceMetrics as v, useOptionalPluginManager as x, SurfaceContext as y };
959
+
960
+ //# sourceMappingURL=chunk-SurfaceManager.mjs.map