@dxos/app-framework 0.10.0 → 0.11.1

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 +120 -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 @@
1
+ {"version":3,"file":"chunk-SurfaceManager.mjs","names":[],"sources":["../../src/helpers.ts","../../src/ui/components/PluginManager/PluginManagerProvider.ts","../../src/ui/components/Surface/context.ts","../../src/ui/components/Surface/SurfaceMetrics.ts","../../src/ui/components/Surface/SurfaceDebug.tsx","../../src/ui/components/Surface/SurfaceManagerContext.ts","../../src/ui/components/Surface/SurfaceProfilerContext.tsx","../../src/ui/components/Surface/SurfaceComponent.tsx","../../src/ui/components/Surface/types.ts","../../src/ui/components/Surface/SurfaceManager.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\ntype DependencyNode = {\n id: string;\n dependsOn?: string[];\n};\n\n/**\n * Topologically sorts a list of nodes based on their dependencies.\n */\n// TODO(wittjosiah): Factor out?\nexport const topologicalSort = <T extends DependencyNode>(nodes: T[]): T[] => {\n const getDependencies = (nodeId: string, seen = new Set<string>(), path = new Set<string>()): string[] => {\n if (path.has(nodeId)) {\n throw new Error(`Circular dependency detected involving ${nodeId}`);\n }\n if (seen.has(nodeId)) {\n return [];\n }\n\n const node = nodes.find((n) => n.id === nodeId);\n if (!node) {\n throw new Error(`Node ${nodeId} not found but is listed as a dependency`);\n }\n\n const newPath = new Set([...path, nodeId]);\n const newSeen = new Set([...seen, nodeId]);\n\n const dependsOn = node.dependsOn ?? [];\n return [...dependsOn.flatMap((depId) => getDependencies(depId, newSeen, newPath)), nodeId];\n };\n\n // Get all unique dependencies.\n const allDependencies = nodes\n .map((node) => node.id)\n .flatMap((id) => getDependencies(id))\n .filter((id, index, self) => self.indexOf(id) === index);\n\n // Map back to original nodes\n return allDependencies\n .map((id) => nodes.find((node) => node.id === id))\n .filter((node): node is T => node !== undefined);\n};\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport { createContext, useContext } from 'react';\n\nimport { raise } from '@dxos/debug';\n\nimport { type PluginManager } from '../../../core';\n\nconst PluginManagerContext = createContext<PluginManager.PluginManager | undefined>(undefined);\n\n/**\n * Get the plugin manager.\n */\nexport const usePluginManager = (): PluginManager.PluginManager =>\n useContext(PluginManagerContext) ?? raise(new Error('Missing PluginManagerContext'));\n\n/**\n * Get the plugin manager if present, or `undefined` when rendered outside a {@link PluginManagerProvider}\n * (e.g. a standalone component story). Lets components treat plugin capabilities as a progressive\n * enhancement rather than a hard requirement.\n */\nexport const useOptionalPluginManager = (): PluginManager.PluginManager | undefined => useContext(PluginManagerContext);\n\n/**\n * Context provider for a plugin manager.\n */\nexport const PluginManagerProvider = PluginManagerContext.Provider;\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type Context, createContext } from 'react';\n\nexport type SurfaceContext = {\n id?: string;\n /** The resolved role NSID (e.g. `org.dxos.role.article`). */\n role: string;\n data?: Record<string, any>;\n};\n\nexport const SurfaceContext: Context<SurfaceContext | undefined> = createContext<SurfaceContext | undefined>(undefined);\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { useSyncExternalStore } from 'react';\n\n/**\n * Per-surface dev metrics, complementing the render-timing data collected by the\n * React Profiler ({@link SurfaceProfilerStats}). Captures dispatch-level signals\n * the Profiler cannot see: how many candidates matched, whether the consumer's\n * `data` prop is referentially unstable (the most common Surface footgun), error\n * boundary trips, and mount churn.\n *\n * Keyed by the same `surface/<id>/<role>` identifier the Profiler uses, so the\n * two can be joined in the devtools panel.\n */\nexport type SurfaceMetric = {\n /** `surface/<id>/<role>`. */\n id: string;\n /** Surface definition id. */\n surfaceId: string;\n /** Resolved role NSID. */\n role: string;\n /** Number of times the dispatcher resolved candidates for this surface. */\n dispatches: number;\n /** Candidates matched on the last dispatch. */\n candidates: number;\n /** `true` when more candidates matched than `limit` rendered. */\n truncated: boolean;\n /** `true` when the `data` prop identity churns across renders without changing value. */\n dataUnstable: boolean;\n /** Consecutive renders where `data` identity changed but value did not. */\n dataChurn: number;\n /** Error boundary trips. */\n errors: number;\n /** Mounts of the matched component. */\n mounts: number;\n /** Unmounts of the matched component. */\n unmounts: number;\n};\n\n/** Consecutive unstable renders before `dataUnstable` is flagged. */\nconst UNSTABLE_THRESHOLD = 3;\n\nexport const surfaceMetricKey = (surfaceId: string, role: string): string => `surface/${surfaceId}/${role}`;\n\n/**\n * Shallow (top-level) value equality. Used to distinguish a genuinely new `data`\n * value from a new object/array carrying the same content (the unstable-prop case).\n */\nconst shallowEqual = (a: unknown, b: unknown): boolean => {\n if (Object.is(a, b)) {\n return true;\n }\n if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) {\n return false;\n }\n const aKeys = Object.keys(a as Record<string, unknown>);\n const bKeys = Object.keys(b as Record<string, unknown>);\n if (aKeys.length !== bKeys.length) {\n return false;\n }\n return aKeys.every((key) => Object.is((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]));\n};\n\n/**\n * Singleton store of surface dev metrics. A module singleton (rather than a React\n * context) so the debug overlay — which renders in its own root outside the app's\n * provider tree — can read the same data as the in-app devtools panel.\n */\nclass SurfaceMetricsStore {\n #metrics = new Map<string, SurfaceMetric>();\n #snapshot: SurfaceMetric[] = [];\n #listeners = new Set<() => void>();\n #pendingNotify = false;\n\n #entry(surfaceId: string, role: string): SurfaceMetric {\n const id = surfaceMetricKey(surfaceId, role);\n let metric = this.#metrics.get(id);\n if (!metric) {\n metric = {\n id,\n surfaceId,\n role,\n dispatches: 0,\n candidates: 0,\n truncated: false,\n dataUnstable: false,\n dataChurn: 0,\n errors: 0,\n mounts: 0,\n unmounts: 0,\n };\n this.#metrics.set(id, metric);\n }\n return metric;\n }\n\n /**\n * Records a dispatch. `previousData` is the consumer's prior `data` reference;\n * the store derives churn/instability from the identity-vs-value comparison.\n */\n recordDispatch(\n surfaceId: string,\n role: string,\n args: { candidates: number; truncated: boolean; dataChurn: number },\n ): void {\n const metric = this.#entry(surfaceId, role);\n metric.dispatches += 1;\n metric.candidates = args.candidates;\n metric.truncated = args.truncated;\n metric.dataChurn = args.dataChurn;\n metric.dataUnstable = args.dataChurn >= UNSTABLE_THRESHOLD;\n this.#schedule();\n }\n\n recordError(surfaceId: string, role: string): void {\n this.#entry(surfaceId, role).errors += 1;\n this.#schedule();\n }\n\n recordMount(surfaceId: string, role: string): void {\n this.#entry(surfaceId, role).mounts += 1;\n this.#schedule();\n }\n\n recordUnmount(surfaceId: string, role: string): void {\n this.#entry(surfaceId, role).unmounts += 1;\n this.#schedule();\n }\n\n clear(): void {\n this.#metrics.clear();\n this.#snapshot = [];\n this.#notify();\n }\n\n subscribe = (listener: () => void): (() => void) => {\n this.#listeners.add(listener);\n return () => {\n this.#listeners.delete(listener);\n };\n };\n\n getSnapshot = (): SurfaceMetric[] => this.#snapshot;\n\n /** Defers notification to the next frame to coalesce bursts of records. */\n #schedule(): void {\n if (this.#pendingNotify || typeof requestAnimationFrame === 'undefined') {\n if (typeof requestAnimationFrame === 'undefined') {\n this.#notify();\n }\n return;\n }\n this.#pendingNotify = true;\n requestAnimationFrame(() => {\n this.#pendingNotify = false;\n this.#notify();\n });\n }\n\n #notify(): void {\n this.#snapshot = [...this.#metrics.values()];\n for (const listener of this.#listeners) {\n listener();\n }\n }\n}\n\nexport const surfaceMetrics = new SurfaceMetricsStore();\n\n/**\n * Updates the running churn count for a consumer's `data` prop.\n *\n * @returns the new churn count (0 when `data` changed value or is unchanged).\n */\nexport const nextDataChurn = (previous: unknown, next: unknown, churn: number): number => {\n // Unchanged reference: preserve churn. NOTE: do NOT reset to 0 here — `useDefaultValue`\n // (used for the `data` default) lags one render, so each prop change yields two renders\n // (stale-then-new); resetting on the stale (Object.is) render would wipe accumulated churn\n // and never flag genuinely unstable data. The flag clears on the next genuine value change.\n if (Object.is(previous, next)) {\n return churn;\n }\n // New reference: churn only when the value is structurally the same (unstable identity).\n return shallowEqual(previous, next) ? churn + 1 : 0;\n};\n\n/**\n * Subscribes to surface dev metrics, sorted with the most concerning first\n * (unstable data, then errors, then dispatch count).\n */\nexport const useSurfaceMetrics = (): SurfaceMetric[] => {\n const metrics = useSyncExternalStore(\n surfaceMetrics.subscribe,\n surfaceMetrics.getSnapshot,\n surfaceMetrics.getSnapshot,\n );\n return [...metrics].sort(\n (a, b) => Number(b.dataUnstable) - Number(a.dataUnstable) || b.errors - a.errors || b.dispatches - a.dispatches,\n );\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, {\n type PropsWithChildren,\n type ReactNode,\n createElement,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n useSyncExternalStore,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { createRoot } from 'react-dom/client';\n\nimport { addEventListener, combine } from '@dxos/async';\nimport { mx } from '@dxos/ui-theme';\n\nimport { type SurfaceContext } from './context';\nimport { type SurfaceMetric, surfaceMetricKey, surfaceMetrics } from './SurfaceMetrics';\n\ndeclare global {\n interface Window {\n __DX_DEBUG__?: boolean;\n __DX__?: { surfaces: (component?: string) => HTMLElement[] };\n }\n}\n\nconst DEBUG_FLAG = '__DX_DEBUG__';\n\n/**\n * Custom element tag wrapping each React surface in debug builds.\n */\nexport const DX_SURFACE_TAG = 'dx-surface';\n\n/**\n * Installs the `window.__DX__` DevTools helper. `__DX__.surfaces(component?)` returns the mounted\n * `<dx-surface>` elements (optionally filtered by `data-component`) — a console shortcut for the\n * otherwise-verbose `document.querySelectorAll('dx-surface[data-component=\"…\"]')`.\n */\nconst ensureDebugApi = (): void => {\n if (typeof window === 'undefined') {\n return;\n }\n\n window.__DX__ = {\n ...window.__DX__,\n surfaces: (component?: string) =>\n Array.from(\n document.querySelectorAll<HTMLElement>(\n component ? `${DX_SURFACE_TAG}[data-component=\"${component}\"]` : DX_SURFACE_TAG,\n ),\n ),\n };\n};\n\n/**\n * Whether surface debugging is enabled, via the `VITE_DEBUG` build flag or the\n * `__DX_DEBUG__` runtime global. The runtime flag is toggleable without a\n * rebuild and takes effect on the next render.\n */\nexport const isSurfaceDebugEnabled = (): boolean =>\n Boolean(import.meta.env?.VITE_DEBUG) || (typeof window !== 'undefined' && DEBUG_FLAG in window);\n\n/**\n * Whether the `<dx-surface>` wrapper is rendered. Enabled in any development build (or when\n * `VITE_DEBUG` is set) so the wrapper's `data-*` attributes and `window.__DX__` helpers are available\n * for DOM inspection independent of the runtime highlight flag. Production renders no wrapper.\n */\nexport const isSurfaceWrapperEnabled = (): boolean =>\n Boolean(import.meta.env?.DEV) || Boolean(import.meta.env?.VITE_DEBUG);\n\n// Notifies the overlay when the runtime highlight flag toggles: the overlay is a separate React root,\n// so it does not re-render on a surface render and needs an explicit signal to redraw.\nconst flagListeners = new Set<() => void>();\nconst subscribeFlag = (listener: () => void): (() => void) => {\n flagListeners.add(listener);\n return () => {\n flagListeners.delete(listener);\n };\n};\n\n/**\n * Toggles the `__DX_DEBUG__` runtime flag, which gates the visual highlight overlay (not the\n * `<dx-surface>` wrapper). The overlay redraws immediately; surfaces pick it up on their next render.\n */\nexport const setSurfaceDebug = (enabled: boolean): void => {\n if (typeof window === 'undefined') {\n return;\n }\n\n if (enabled) {\n window[DEBUG_FLAG] = true;\n } else {\n delete window[DEBUG_FLAG];\n }\n for (const listener of flagListeners) {\n listener();\n }\n};\n\nlet elementRegistered = false;\n\nconst ensureSurfaceElement = (): void => {\n if (elementRegistered || typeof customElements === 'undefined') {\n return;\n }\n\n elementRegistered = true;\n if (!customElements.get(DX_SURFACE_TAG)) {\n // Defined lazily (not at module scope) so importing this module never evaluates\n // `extends HTMLElement` in non-DOM environments (node tests / SSR).\n // Zero-behavior element; `display: contents` keeps the debug wrapper layout-neutral\n // so the debug DOM matches production (which renders no wrapper).\n class DxSurfaceElement extends HTMLElement {\n connectedCallback(): void {\n this.style.display = 'contents';\n }\n }\n customElements.define(DX_SURFACE_TAG, DxSurfaceElement);\n }\n};\n\ntype InfoRef = { readonly current: SurfaceContext };\n\ntype DebugEntry = {\n key: number;\n element: HTMLElement;\n infoRef: InfoRef;\n};\n\n/**\n * Singleton registry of mounted debug surfaces. Centralizes overlay rendering so\n * a single set of observers/listeners serves every surface, instead of each\n * surface mounting its own portal and observers.\n */\nclass SurfaceDebugManager {\n #entries = new Set<DebugEntry>();\n #snapshot: DebugEntry[] = [];\n #listeners = new Set<() => void>();\n #nextKey = 0;\n\n register(element: HTMLElement, infoRef: InfoRef): () => void {\n const entry: DebugEntry = { key: this.#nextKey++, element, infoRef };\n this.#entries.add(entry);\n this.#update();\n return () => {\n this.#entries.delete(entry);\n this.#update();\n };\n }\n\n subscribe = (listener: () => void): (() => void) => {\n this.#listeners.add(listener);\n return () => {\n this.#listeners.delete(listener);\n };\n };\n\n getSnapshot = (): DebugEntry[] => this.#snapshot;\n\n #update(): void {\n this.#snapshot = [...this.#entries];\n for (const listener of this.#listeners) {\n listener();\n }\n }\n}\n\nconst manager = new SurfaceDebugManager();\n\nlet overlayMounted = false;\n\nconst ensureOverlay = (): void => {\n if (overlayMounted || typeof document === 'undefined') {\n return;\n }\n overlayMounted = true;\n const root = document.createElement('div');\n root.id = 'dx-surface-overlay';\n document.body.appendChild(root);\n createRoot(root).render(<SurfaceDebugOverlay />);\n};\n\n/**\n * Measures a `display: contents` element (which has no box of its own) by unioning the border boxes\n * of its rendered children. Deliberately uses each child's own `getBoundingClientRect` rather than a\n * Range over all contents: a Range unions every descendant rect, so content overflowing an inner\n * `overflow: auto` scroll container inflates the width past the surface's visible bounds. A direct\n * child's box is clipped to its own border box, so the highlight stays tight to what is on screen.\n */\nconst measureContents = (element: HTMLElement): DOMRect | null => {\n if (typeof document === 'undefined' || typeof getComputedStyle !== 'function') {\n return null;\n }\n\n const boxes: DOMRect[] = [];\n const collect = (node: Element): void => {\n for (const child of node.children) {\n // A nested `display: contents` child also has no box; descend to the elements that do.\n if (getComputedStyle(child).display === 'contents') {\n collect(child);\n } else {\n const rect = child.getBoundingClientRect();\n if (rect.width > 0 || rect.height > 0) {\n boxes.push(rect);\n }\n }\n }\n };\n collect(element);\n\n if (boxes.length === 0) {\n return null;\n }\n\n let left = Infinity;\n let top = Infinity;\n let right = -Infinity;\n let bottom = -Infinity;\n for (const box of boxes) {\n left = Math.min(left, box.left);\n top = Math.min(top, box.top);\n right = Math.max(right, box.right);\n bottom = Math.max(bottom, box.bottom);\n }\n return new DOMRect(left, top, right - left, bottom - top);\n};\n\nconst EMPTY_RECTS: ReadonlyMap<number, DOMRect> = new Map();\n\n/**\n * Single overlay that draws boundary highlights for every registered surface.\n * One ResizeObserver plus one scroll/resize listener serve all surfaces.\n */\nconst SurfaceDebugOverlay = (): ReactNode => {\n const entries = useSyncExternalStore(manager.subscribe, manager.getSnapshot, manager.getSnapshot);\n // The `__DX_DEBUG__` flag gates only this visual overlay; the `<dx-surface>` wrappers stay mounted.\n const enabled = useSyncExternalStore(subscribeFlag, isSurfaceDebugEnabled, isSurfaceDebugEnabled);\n const [rects, setRects] = useState<ReadonlyMap<number, DOMRect>>(EMPTY_RECTS);\n\n useLayoutEffect(() => {\n // Draw nothing (and install no listeners) while the flag is off or there are no surfaces.\n if (!enabled || entries.length === 0) {\n setRects(EMPTY_RECTS);\n return;\n }\n\n const measure = () => {\n const next = new Map<number, DOMRect>();\n for (const entry of entries) {\n const rect = measureContents(entry.element);\n if (rect) {\n next.set(entry.key, rect);\n }\n }\n setRects(next);\n };\n\n // Guard for environments without ResizeObserver (SSR / tests); scroll/resize still drive measurement.\n const observer = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(measure) : undefined;\n for (const entry of entries) {\n // The element itself has no box (display: contents); observe its parent.\n if (observer && entry.element.parentElement) {\n observer.observe(entry.element.parentElement);\n }\n }\n measure();\n\n return combine(addEventListener(window, 'scroll', measure, true), addEventListener(window, 'resize', measure), () =>\n observer?.disconnect(),\n );\n }, [entries, enabled]);\n\n if (!enabled) {\n return null;\n }\n\n return createPortal(\n <>\n {entries.map((entry) => {\n const rect = rects.get(entry.key);\n return rect ? <SurfaceHighlight key={entry.key} infoRef={entry.infoRef} rect={rect} /> : null;\n })}\n </>,\n document.body,\n );\n};\n\n/** Subscribes to the metric for a single surface. */\nconst useSurfaceMetric = (surfaceId: string, role: string): SurfaceMetric | undefined => {\n const all = useSyncExternalStore(surfaceMetrics.subscribe, surfaceMetrics.getSnapshot, surfaceMetrics.getSnapshot);\n const key = surfaceMetricKey(surfaceId, role);\n return all.find((metric) => metric.id === key);\n};\n\nconst SurfaceHighlight = ({ infoRef, rect }: { infoRef: InfoRef; rect: DOMRect }): ReactNode => {\n const [expand, setExpand] = useState(false);\n const info = infoRef.current;\n const metric = useSurfaceMetric(info.id ?? '', info.role);\n // A surface with a likely problem (unstable data or a caught error) flags red.\n const concern = !!metric && (metric.dataUnstable || metric.errors > 0);\n return (\n <div\n className='z-[100] fixed flex flex-col-reverse scrollbar-none overflow-auto pointer-events-none'\n style={{ top: rect.top, left: rect.left, width: rect.width, height: rect.height }}\n >\n {expand ? (\n <div\n className='absolute inset-0 border-2 border-rose-500 border-dotted overflow-auto pointer-events-auto'\n onPointerDown={(ev) => ev.stopPropagation()}\n onClick={(ev) => {\n ev.stopPropagation();\n setExpand(false);\n }}\n >\n <div className='absolute left-1 right-1 bottom-1 max-h-[20rem] overflow-auto dx-card-surface ring-2 ring-separator rounded-sm opacity-90'>\n <pre className='inline-block p-2 text-xs text-description font-mono font-thin'>\n {JSON.stringify({ info, metric }, null, 2)}\n </pre>\n </div>\n </div>\n ) : (\n <span\n className={mx(\n concern ? 'text-rose-500' : 'text-green-500',\n 'absolute right-2 bottom-1 flex items-center p-1 opacity-80 hover:opacity-100 text-sm cursor-pointer pointer-events-auto',\n )}\n title={metricSummary(info.id ?? '', metric)}\n onPointerDown={(ev) => ev.stopPropagation()}\n onClick={(ev) => {\n ev.stopPropagation();\n setExpand(true);\n }}\n >\n {concern ? '⚠' : 'ⓘ'}\n </span>\n )}\n </div>\n );\n};\n\nconst metricSummary = (surfaceId: string, metric: SurfaceMetric | undefined): string => {\n if (!metric) {\n return surfaceId;\n }\n const parts = [\n surfaceId,\n `dispatches=${metric.dispatches}`,\n `candidates=${metric.candidates}${metric.truncated ? '(+truncated)' : ''}`,\n `mounts=${metric.mounts}/unmounts=${metric.unmounts}`,\n ];\n if (metric.dataUnstable) {\n parts.push(`UNSTABLE data (churn=${metric.dataChurn})`);\n }\n if (metric.errors > 0) {\n parts.push(`errors=${metric.errors}`);\n }\n return parts.join(' · ');\n};\n\n/**\n * Recovers the rendered surface component's name from the React fiber. Surfaces are registered as\n * anonymous `component: (props) => <Real .../>` wrappers, so the static component carries no useful\n * name; walking the fiber's child chain to the first component with a display name (as DevTools does)\n * finds the real one. The wrapper's own inferred name is the property key ('component'), so skip it.\n */\nconst renderedComponentName = (element: HTMLElement): string | undefined => {\n // React fiber internals are untyped; the fiber hangs off a `__reactFiber$<id>` own-property and its\n // `child` chain is the rendered subtree.\n const node = element as any;\n const fiberKey = Object.keys(node).find((key) => key.startsWith('__reactFiber$'));\n let fiber = fiberKey ? node[fiberKey]?.child : undefined;\n while (fiber) {\n const { type } = fiber;\n if (typeof type === 'function') {\n const name: string | undefined = type.displayName ?? type.name;\n if (name && name !== 'component') {\n return name;\n }\n }\n fiber = fiber.child;\n }\n return undefined;\n};\n\n/**\n * Debug wrapper that mounts each React surface inside a `<dx-surface>` element\n * and registers it with the centralized overlay. The element is named and\n * inspectable in DevTools and queryable (`document.querySelectorAll('dx-surface')`).\n */\nexport const DebugSurface = ({ info, children }: PropsWithChildren<{ info: SurfaceContext }>): ReactNode => {\n // The manager reads the latest info via this ref without re-registering on data change.\n const infoRef = useRef(info);\n infoRef.current = info;\n const elementRef = useRef<HTMLElement>(null);\n\n useEffect(() => {\n ensureSurfaceElement();\n ensureOverlay();\n ensureDebugApi();\n const { id, role } = infoRef.current;\n if (id) {\n surfaceMetrics.recordMount(id, role);\n }\n const element = elementRef.current;\n const unregister = element ? manager.register(element, infoRef) : undefined;\n // Names the rendered component (`data-component`) once mounted — read from the fiber because the\n // surface wrapper is anonymous, so no static name is available at render time.\n const component = element ? renderedComponentName(element) : undefined;\n if (element && component) {\n element.setAttribute('data-component', component);\n }\n\n return () => {\n if (id) {\n surfaceMetrics.recordUnmount(id, role);\n }\n unregister?.();\n };\n }, []);\n\n return createElement(\n DX_SURFACE_TAG,\n {\n 'className': 'contents',\n 'data-id': info.id,\n 'data-role': info.role,\n 'ref': elementRef,\n },\n children,\n );\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { type Context, createContext, useContext } from 'react';\n\nimport { raise } from '@dxos/debug';\n\nimport { type SurfaceManager } from './SurfaceManager';\n\nconst SurfaceManagerContext: Context<SurfaceManager | undefined> = createContext<SurfaceManager | undefined>(undefined);\n\n/**\n * Get the surface manager. Throws when rendered outside a {@link SurfaceManagerProvider};\n * surfaces already hard-require the plugin-manager context, so there is no optional variant.\n */\nexport const useSurfaceManager = (): SurfaceManager =>\n useContext(SurfaceManagerContext) ?? raise(new Error('Missing SurfaceManagerContext'));\n\n/**\n * Context provider for a surface manager.\n */\nexport const SurfaceManagerProvider = SurfaceManagerContext.Provider;\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, {\n type Context,\n type ProfilerOnRenderCallback,\n type PropsWithChildren,\n createContext,\n useContext,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from 'react';\n\nconst MAX_ENTRIES = 500;\n\n/**\n * Single profiler record captured from React Profiler onRender callback.\n */\nexport type SurfaceProfilerEntry = {\n id: string;\n phase: 'mount' | 'update' | 'nested-update';\n actualDuration: number;\n baseDuration: number;\n startTime: number;\n commitTime: number;\n timestamp: number;\n};\n\n/**\n * Aggregated stats for a single profiled surface.\n */\nexport type SurfaceProfilerStats = {\n id: string;\n mountCount: number;\n updateCount: number;\n totalRenders: number;\n avgActualDuration: number;\n maxActualDuration: number;\n avgBaseDuration: number;\n lastActualDuration: number;\n lastCommitTime: number;\n};\n\n/**\n * Store that collects profiler entries and notifies subscribers.\n */\nclass SurfaceProfilerStore {\n private _entries: SurfaceProfilerEntry[] = [];\n private _listeners = new Set<() => void>();\n private _snapshot: readonly SurfaceProfilerEntry[] = [];\n private _pendingNotify = false;\n\n /**\n * Records an entry and schedules a deferred notification to avoid re-render loops.\n * `_snapshot` is rebuilt only when the deferred notification actually fires\n * ({@link _notifySync}) — not here — so `getSnapshot` stays referentially stable for any\n * synchronous re-invocation React makes within the same commit (e.g. the tearing check a\n * profiled subscriber's own `useSyncExternalStore` runs right after this Profiler's\n * `onRender` callback). Rebuilding it synchronously here would change what `getSnapshot`\n * returns before listeners are told, which React reads as a torn store and forces an\n * immediate re-render — and if the re-rendering component is itself profiled, that\n * re-render re-triggers `record`, looping forever.\n */\n record(entry: SurfaceProfilerEntry) {\n this._entries.push(entry);\n if (this._entries.length > MAX_ENTRIES) {\n this._entries = this._entries.slice(-MAX_ENTRIES);\n }\n this._scheduleNotify();\n }\n\n clear() {\n this._entries = [];\n this._snapshot = [];\n this._notifySync();\n }\n\n subscribe = (listener: () => void) => {\n this._listeners.add(listener);\n return () => {\n this._listeners.delete(listener);\n };\n };\n\n getSnapshot = (): readonly SurfaceProfilerEntry[] => {\n return this._snapshot;\n };\n\n /**\n * Defers notification to the next animation frame to break the\n * Profiler onRender → record → notify → re-render → onRender loop.\n */\n private _scheduleNotify() {\n if (!this._pendingNotify) {\n this._pendingNotify = true;\n requestAnimationFrame(() => {\n this._pendingNotify = false;\n this._notifySync();\n });\n }\n }\n\n private _notifySync() {\n this._snapshot = [...this._entries];\n for (const listener of this._listeners) {\n listener();\n }\n }\n}\n\ntype SurfaceProfilerContextValue = {\n store: SurfaceProfilerStore;\n};\n\nconst SurfaceProfilerContext: Context<SurfaceProfilerContextValue | undefined> = createContext<\n SurfaceProfilerContextValue | undefined\n>(undefined);\n\n/**\n * Provider that collects React Profiler data from Surface components.\n */\nexport const SurfaceProfilerProvider = ({ children }: PropsWithChildren) => {\n const storeRef = useRef<SurfaceProfilerStore>(null);\n if (!storeRef.current) {\n storeRef.current = new SurfaceProfilerStore();\n }\n return (\n <SurfaceProfilerContext.Provider value={{ store: storeRef.current }}>{children}</SurfaceProfilerContext.Provider>\n );\n};\n\n/**\n * Returns a stable onRender callback for use with React Profiler.\n */\nexport const useSurfaceProfilerCallback = (): ProfilerOnRenderCallback | undefined => {\n const store = useContext(SurfaceProfilerContext)?.store;\n return useMemo<ProfilerOnRenderCallback | undefined>(() => {\n if (!store) {\n return undefined;\n }\n return (id, phase, actualDuration, baseDuration, startTime, commitTime) => {\n store.record({\n id,\n phase,\n actualDuration,\n baseDuration,\n startTime,\n commitTime,\n timestamp: Date.now(),\n });\n };\n }, [store]);\n};\n\n/**\n * Returns all profiler entries reactively.\n */\nexport const useSurfaceProfilerEntries = (): readonly SurfaceProfilerEntry[] => {\n const context = useContext(SurfaceProfilerContext);\n return useSyncExternalStore(context?.store.subscribe ?? noop, context?.store.getSnapshot ?? emptySnapshot);\n};\n\n/**\n * Returns aggregated stats grouped by surface id.\n */\nexport const useSurfaceProfilerStats = (): SurfaceProfilerStats[] => {\n const entries = useSurfaceProfilerEntries();\n const statsMap = new Map<string, SurfaceProfilerStats>();\n\n for (const entry of entries) {\n let stats = statsMap.get(entry.id);\n if (!stats) {\n stats = {\n id: entry.id,\n mountCount: 0,\n updateCount: 0,\n totalRenders: 0,\n avgActualDuration: 0,\n maxActualDuration: 0,\n avgBaseDuration: 0,\n lastActualDuration: 0,\n lastCommitTime: 0,\n };\n statsMap.set(entry.id, stats);\n }\n\n if (entry.phase === 'mount') {\n stats.mountCount++;\n } else {\n stats.updateCount++;\n }\n stats.totalRenders++;\n stats.avgActualDuration =\n (stats.avgActualDuration * (stats.totalRenders - 1) + entry.actualDuration) / stats.totalRenders;\n stats.avgBaseDuration =\n (stats.avgBaseDuration * (stats.totalRenders - 1) + entry.baseDuration) / stats.totalRenders;\n stats.maxActualDuration = Math.max(stats.maxActualDuration, entry.actualDuration);\n stats.lastActualDuration = entry.actualDuration;\n stats.lastCommitTime = entry.commitTime;\n }\n\n return [...statsMap.values()].sort((a, b) => b.maxActualDuration - a.maxActualDuration);\n};\n\n/**\n * Clears all collected profiler entries.\n */\nexport const useSurfaceProfilerClear = (): (() => void) | undefined => {\n const store = useContext(SurfaceProfilerContext)?.store;\n return useMemo(() => (store ? () => store.clear() : undefined), [store]);\n};\n\nconst noop = () => () => {};\nconst EMPTY_SNAPSHOT: readonly SurfaceProfilerEntry[] = [];\nconst emptySnapshot = () => EMPTY_SNAPSHOT;\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { RegistryContext, useAtomValue } from '@effect-atom/atom-react';\nimport React, {\n type FC,\n Fragment,\n type NamedExoticComponent,\n Profiler,\n Suspense,\n memo,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n} from 'react';\n\nimport { log } from '@dxos/log';\nimport { ErrorBoundary } from '@dxos/react-error-boundary';\nimport { useDefaultValue } from '@dxos/react-hooks';\n\nimport { Capabilities, Role } from '../../../common';\nimport { usePluginManager } from '../PluginManager';\nimport { SurfaceContext } from './context';\nimport { DebugSurface, isSurfaceDebugEnabled, isSurfaceWrapperEnabled } from './SurfaceDebug';\nimport { useSurfaceManager } from './SurfaceManagerContext';\nimport { nextDataChurn, surfaceMetrics } from './SurfaceMetrics';\nimport { useSurfaceProfilerCallback } from './SurfaceProfilerContext';\nimport { type Definition, type Props, type TypedProps, type WebComponentDefinition } from './types';\n\nconst DEBUG = import.meta.env?.VITE_DEBUG;\n\nconst DEFAULT_PLACEHOLDER = <Fragment />;\n\n/**\n * Wrapper component for rendering Web Component surfaces.\n * Handles creation, prop setting, and cleanup of Web Components.\n */\ntype WebComponentWrapperProps = {\n id?: string;\n role: string;\n data?: Record<string, any>;\n limit?: number;\n definition: WebComponentDefinition;\n [key: string]: any;\n};\n\nconst WebComponentWrapper = memo(({ id, role, data, limit, definition, ...rest }: WebComponentWrapperProps) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const elementRef = useRef<HTMLElement | null>(null);\n const propsRef = useRef({ id, role, data, limit, ...rest });\n\n // Update props ref on every render.\n propsRef.current = { id, role, data, limit, ...rest };\n\n // Create element only once.\n useEffect(() => {\n if (!containerRef.current || elementRef.current) {\n return;\n }\n\n // Create the Web Component.\n const element = document.createElement(definition.tagName);\n elementRef.current = element;\n\n // Set initial properties on the Web Component.\n Object.assign(element, propsRef.current);\n\n // Append to container.\n containerRef.current.appendChild(element);\n\n // Cleanup on unmount to prevent memory leaks.\n return () => {\n if (elementRef.current && containerRef.current?.contains(elementRef.current)) {\n containerRef.current.removeChild(elementRef.current);\n }\n elementRef.current = null;\n };\n }, [definition.tagName]);\n\n const previousPropsRef = useRef<Record<string, any>>({});\n\n // Keep all props (including those in `rest`) in sync on the existing element,\n // removing any that disappeared since the last render so no stale config lingers.\n useEffect(() => {\n const element = elementRef.current;\n if (element) {\n for (const key of Object.keys(previousPropsRef.current)) {\n if (!(key in propsRef.current)) {\n Reflect.deleteProperty(element, key);\n }\n }\n Object.assign(element, propsRef.current);\n previousPropsRef.current = propsRef.current;\n }\n });\n\n return <div ref={containerRef} />;\n});\n\nWebComponentWrapper.displayName = 'WebComponentWrapper';\n\ntype SurfaceContextProviderProps = {\n id: string;\n role: string;\n data?: Record<string, any>;\n limit?: number;\n fallback?: FC<{ error: Error; data?: any }>;\n definition: Definition;\n [key: string]: any;\n};\n\n/**\n * Wrapper component that provides context for a surface.\n */\n// TODO(burdon): Allow DebugPlugin to provide different fallback using react-ui ErrorFallback.\nconst SurfaceContextProvider = memo(\n ({ id, role, data, limit, fallback = ErrorFallback, definition, ...rest }: SurfaceContextProviderProps) => {\n const contextValue = useMemo(() => ({ id, role, data }), [id, role, data]);\n const onProfilerRender = useSurfaceProfilerCallback();\n const profilerId = `surface/${id}/${role}`;\n // Count error-boundary trips against this surface (dev only).\n const onError = isSurfaceDebugEnabled() ? () => surfaceMetrics.recordError(id, role) : undefined;\n\n // Handle Web Component surfaces.\n if (definition.kind === 'web-component') {\n return (\n <ErrorBoundary name='surface' resetKeys={[data]} FallbackComponent={fallback} onError={onError}>\n <SurfaceContext.Provider value={contextValue}>\n <WebComponentWrapper id={id} role={role} data={data} limit={limit} definition={definition} {...rest} />\n </SurfaceContext.Provider>\n </ErrorBoundary>\n );\n }\n\n // Handle React component surfaces.\n const Component = definition.component;\n // `props` lets a definition register a plain container and map the surface props onto its own,\n // instead of wrapping it in an adapter component (see `TypedReactDefinition.props`).\n const surfaceProps = { id, role, data, limit, ...rest };\n const component = <Component {...(definition.props?.(surfaceProps) ?? surfaceProps)} />;\n const profiled =\n onProfilerRender && !profilerId.includes('org.dxos.plugin.debug') ? (\n <Profiler id={profilerId} onRender={onProfilerRender}>\n {component}\n </Profiler>\n ) : (\n component\n );\n\n // Dev builds wrap every surface in `<dx-surface>` for DOM inspection / `window.__DX__`; the\n // `__DX_DEBUG__` flag separately gates the visual highlight overlay (see SurfaceDebug).\n if (isSurfaceWrapperEnabled()) {\n return (\n <ErrorBoundary name='surface' resetKeys={[data]} FallbackComponent={fallback} onError={onError}>\n <SurfaceContext.Provider value={contextValue}>\n <DebugSurface info={contextValue}>{profiled}</DebugSurface>\n </SurfaceContext.Provider>\n </ErrorBoundary>\n );\n }\n\n // Production renders the matched component directly: a surface adds no wrapper element of its own.\n return (\n <ErrorBoundary name='surface' resetKeys={[data]} FallbackComponent={fallback} onError={onError}>\n <SurfaceContext.Provider value={contextValue}>{profiled}</SurfaceContext.Provider>\n </ErrorBoundary>\n );\n },\n);\n\nSurfaceContextProvider.displayName = 'SurfaceContextProvider';\n\n/**\n * A surface is a named region of the screen that can be populated by plugins.\n * The `type` prop is a {@link Role.Role} that defines which region and its\n * associated data contract.\n *\n * A surface is a boundary that may resolve to zero, one, or many components, so\n * it intentionally accepts no `ref`; consumers needing an element should own one\n * inside their contributed component.\n */\nexport const SurfaceComponent = memo(\n ({\n id: _id,\n type,\n data: dataProp,\n limit,\n placeholder = DEFAULT_PLACEHOLDER,\n ...rest\n }: TypedProps<Role.Role<any>>) => {\n const data = useDefaultValue(dataProp, () => ({}));\n const surfaceManager = useSurfaceManager();\n // Subscribe only to this role's contributions: contributing/removing a surface for a\n // different role keeps this bucket referentially stable, so the atom does not re-render us.\n const effectiveRole = type?.role ?? '';\n const roleCandidates = useAtomValue(surfaceManager.candidatesAtom(effectiveRole));\n\n // NOTE: The data guard runs per render so the surface re-dispatches on reactive data changes.\n const definitions = matchCandidates(roleCandidates, effectiveRole, data);\n // `limit != null` (not truthiness) so an explicit `limit={0}` renders nothing.\n const candidates = limit != null ? definitions.slice(0, limit) : definitions;\n const truncated = limit != null && definitions.length > limit;\n\n // Dev metrics: track dispatch count, candidate count, and `data` instability (see SurfaceMetrics).\n const churnRef = useRef<{ data: unknown; churn: number }>({ data: undefined, churn: 0 });\n useEffect(() => {\n if (!isSurfaceDebugEnabled() || effectiveRole === '') {\n return;\n }\n const previous = churnRef.current;\n const churn = previous.data === undefined ? 0 : nextDataChurn(previous.data, data, previous.churn);\n churnRef.current = { data, churn };\n for (const definition of candidates) {\n surfaceMetrics.recordDispatch(definition.id, effectiveRole, {\n candidates: candidates.length,\n truncated,\n dataChurn: churn,\n });\n }\n });\n\n if (type?.role == null) {\n if (DEBUG) {\n log.warn('Surface is missing required `type` prop', { id: _id });\n }\n return null;\n }\n\n if (DEBUG && candidates.length === 0) {\n log.warn('no candidates for surface', { role: effectiveRole, data });\n return null;\n }\n\n return (\n <Suspense fallback={placeholder}>\n {candidates.map((definition) => (\n <SurfaceContextProvider\n key={definition.id}\n id={definition.id}\n role={effectiveRole}\n data={data}\n limit={limit}\n definition={definition}\n {...rest}\n />\n ))}\n </Suspense>\n );\n },\n // The generic call signature is reattached here because `memo` erases it from the inferred type.\n) as (<TToken extends Role.Role<any>>(props: TypedProps<TToken>) => React.ReactNode) &\n NamedExoticComponent<TypedProps<Role.Role<any>>>;\n\nSurfaceComponent.displayName = 'Surface';\n\n// TODO(burdon): Make user facing, with telemetry.\nconst ErrorFallback = ({ error }: { error: Error }) => {\n const { message } = error instanceof Error ? error : { message: String(error) };\n return (\n <div role='alert' data-testid='error-boundary-fallback'>\n <h1 className='flex p-2 text-sm text-info-text'>{message}</h1>\n </div>\n );\n};\n\n/**\n * Filters the pre-indexed candidates for a role through their data guards.\n */\nconst matchCandidates = (\n definitions: ReadonlyArray<Definition> | undefined,\n role: string,\n data: Props['data'],\n): Definition[] => {\n if (!definitions) {\n return [];\n }\n return definitions.filter(({ filter }) => (filter ? filter(data ?? {}, role) : true));\n};\n\n/**\n * @internal\n */\nexport const useSurfaces = () => {\n const manager = usePluginManager();\n const surfacesByModule = useAtomValue(manager.capabilities.atomByModule(Capabilities.ReactSurface));\n return useMemo(() => {\n const result: Definition[] = [];\n for (const [_moduleId, surfaces] of Object.entries(surfacesByModule)) {\n for (const def of surfaces.flat()) {\n result.push(def);\n }\n }\n return result;\n }, [surfacesByModule]);\n};\n\n/**\n * Reports whether a contributed surface matches the given role & data, without mounting it.\n *\n * Typed: pass a `type` role token and `data` is constrained to the token's\n * declared contract (e.g. `AppSurface.Section` requires `attendableId`).\n */\ntype IsSurfaceAvailable = <TToken extends Role.Role<any>>(args: { type: TToken; data?: Role.Data<TToken> }) => boolean;\n\n/**\n * @returns a stable function that checks whether a contributed surface is available for a\n * role & data. The surface manager is captured via context, so the returned function carries\n * no dependency of its own and is safe to store and invoke later — e.g. from inside another\n * callback such as a render-prop or event handler — since it calls no hooks itself.\n */\nexport const useIsSurfaceAvailable = (): IsSurfaceAvailable => {\n const surfaceManager = useSurfaceManager();\n const registry = useContext(RegistryContext);\n return useCallback<IsSurfaceAvailable>(\n (args: { type: Role.Role<any>; data?: Props['data'] }) => {\n const effectiveRole = args.type?.role;\n if (effectiveRole == null) {\n return false;\n }\n\n const candidates = matchCandidates(\n registry.get(surfaceManager.candidatesAtom(effectiveRole)),\n effectiveRole,\n args.data,\n );\n\n return candidates.length > 0;\n },\n [surfaceManager, registry],\n );\n};\n","//\n// Copyright 2023 DXOS.org\n//\n\nimport type { ComponentType, FC, PropsWithChildren, ReactNode } from 'react';\n\nimport { log } from '@dxos/log';\nimport type { MakeOptional, Position } from '@dxos/util';\n\nimport * as Role from '../../../common/Role';\n\n/**\n * One entry in a {@link Filter} — a role NSID plus the guard that validates the\n * data shape when the Surface dispatcher is matching that role.\n */\nexport type Binding = {\n readonly role: string;\n readonly guard: (data: unknown) => boolean;\n};\n\n/**\n * Typed filter binding for {@link create}. Combines one or more `(role, guard)`\n * pairs so a provider can register against multiple roles while keeping the\n * role and its data shape in a single expression.\n */\nexport type Filter<TData> = {\n readonly bindings: ReadonlyArray<Binding>;\n /** Covariant phantom; never accessed at runtime. */\n readonly _phantom?: TData;\n};\n\n/**\n * Runtime guard for {@link Filter}. Distinguishes new-style filter bindings from\n * legacy predicate filters.\n */\nexport const isFilter = (value: unknown): value is Filter<any> =>\n typeof value === 'object' && value !== null && Array.isArray((value as { bindings?: unknown }).bindings);\n\n/**\n * Creates a {@link Filter} from a role token and an optional guard.\n *\n * When `guard` is omitted the filter matches any data at the token's role\n * (role-only dispatch). Pass a guard to add runtime data-shape validation on\n * top of the role match.\n *\n * This is the framework-level primitive; `@dxos/app-toolkit` builds richer\n * domain-aware helpers (ECHO schema checks, literal matching, etc.) on top of it.\n */\nexport const makeFilter = <TData>(token: Role.Role<TData>, guard?: (data: TData) => boolean): Filter<TData> => {\n const boundGuard =\n guard == null\n ? () => true\n : (data: unknown): boolean => {\n try {\n return guard(data as TData);\n } catch (err) {\n log.catch(err);\n return false;\n }\n };\n return { bindings: [{ role: token.role, guard: boundGuard }] };\n};\n\n/**\n * Typed Surface consumer props — carries the role/data-shape contract via a\n * {@link Role.Role}. The `type` prop is required; the string `role` prop has\n * been removed from the consumer-facing API.\n */\nexport type Props<T extends Record<string, any> = Record<string, unknown>> = {\n /**\n * If specified, the Surface will be wrapped in an error boundary.\n * The fallback component will be rendered if an error occurs.\n */\n fallback?: FC<{ error: Error; data?: any }>;\n\n /**\n * If specified, the Surface will be wrapped in a suspense boundary.\n * The placeholder component will be rendered while the surface component is loading.\n */\n placeholder?: ReactNode;\n} & MakeOptional<CoreProps<T>, 'id' | 'data'>;\n\n/**\n * Typed Surface consumer props — carries the role/data-shape contract via a\n * {@link Role.Role}. Available as a typed overload of the Surface component so\n * ad-hoc `type` fields on arbitrary spread props don't conflict with the\n * untyped consumer form.\n */\nexport type TypedProps<TToken extends Role.Role<any>> = {\n fallback?: FC<{ error: Error; data?: any }>;\n placeholder?: ReactNode;\n id?: string;\n type: TToken;\n data?: Role.Data<TToken>;\n limit?: number | undefined;\n} & {\n /**\n * Additional pass-through props. Known prop names are excluded so the\n * catch-all doesn't widen `data` / `type` / etc. to `any` at the intersection.\n */\n [K in keyof Record<string, any>]: K extends 'fallback' | 'placeholder' | 'id' | 'type' | 'data' | 'limit'\n ? never\n : any;\n};\n\n/**\n * NOTE: If `[key: string]: unknown` is included in shared types, when re-used other fields become unknown as well.\n * The `type` prop is the only way to set the role; it is required here and\n * optional in {@link Props} via MakeOptional.\n */\nexport type CoreProps<T extends Record<string, any> = Record<string, unknown>> = PropsWithChildren<{\n /**\n * ID for debugging.\n */\n id: string;\n\n /**\n * The role token defining how the data should be rendered.\n */\n type: Role.Role<T>;\n\n /**\n * The data to be rendered by the surface.\n * NOTE: This must be a stable value.\n */\n data: T;\n\n /**\n * If more than one component is resolved, the limit determines how many are rendered.\n */\n limit?: number | undefined;\n}>;\n\nexport type ComponentProps<T extends Record<string, any> = Record<string, any>> = {\n /**\n * The resolved role NSID string (e.g. `org.dxos.role.article`). Present on\n * matched components only — multi-role containers can branch on this value\n * by comparing against a token's `.role` (e.g. `role === AppSurface.Section.role`).\n */\n role: string;\n} & PropsWithChildren<{\n id: string;\n data: T;\n limit?: number | undefined;\n}> &\n Record<string, any>;\n\n/**\n * React component used to render a surface once is has matched.\n */\nexport type ComponentFunction<T extends Record<string, any> = Record<string, any>> = (\n props: ComponentProps<T>,\n) => ReactNode;\n\n/**\n * Definition of when a React component surface should be rendered.\n *\n * The optional `filter` receives the consumer-supplied `role` as its second\n * argument so a single definition can encode role-specific guards (used by the\n * {@link Filter}-based `create` overload).\n *\n * @internal The `role` field is populated by the framework from the filter\n * bindings and is not authored directly.\n */\nexport type ReactDefinition<T extends Record<string, any> = any> = Readonly<{\n kind: 'react';\n id: string;\n role: string | string[];\n position?: Position.Position;\n component: ComponentType<any>;\n /** Maps the surface props onto the component's props; see {@link TypedReactDefinition.props}. */\n props?: (props: ComponentProps<T>) => Record<string, any>;\n filter?: (data: Record<string, unknown>, role?: string) => data is T;\n}>;\n\n/**\n * Definition of when a Web Component surface should be rendered.\n *\n * @internal The `role` field is populated by the framework from the filter\n * bindings and is not authored directly.\n */\nexport type WebComponentDefinition<T extends Record<string, any> = any> = Readonly<{\n kind: 'web-component';\n id: string;\n role: string | string[];\n position?: Position.Position;\n /**\n * The tag name of the Web Component to render.\n * The Web Component will receive the same props as React surfaces via properties/attributes.\n */\n tagName: string;\n filter?: (data: Record<string, unknown>, role?: string) => data is T;\n}>;\n\n/**\n * Definition of when a surface (React or Web Component) should be rendered.\n */\nexport type Definition<T extends Record<string, any> = any> = ReactDefinition<T> | WebComponentDefinition<T>;\n\n/**\n * Typed React surface definition — role is derived from the filter's bindings.\n */\nexport type TypedReactDefinition<\n T extends Record<string, any> = any,\n P extends Record<string, any> = ComponentProps<T>,\n> = Readonly<{\n id: string;\n filter: Filter<T>;\n position?: Position.Position;\n /**\n * Accepts any component type (not just a function) so a container re-exported through a\n * `lazy()` barrel as `ComponentType<any>` can be registered without a cast.\n */\n component: ComponentType<P>;\n /**\n * Maps the surface props onto the component's own props, so a plain container can be registered\n * directly instead of being wrapped in an adapter that unpacks `data`.\n *\n * Prefer this over an inline `component: ({ data }) => <Container … />`: the mapper's input type\n * derives from the same `filter` that defines the surface's data shape, so the unpacking is\n * checked against the filter rather than restated by hand.\n *\n * @example\n * Surface.create({\n * id: 'defaultPluginSettings',\n * filter: AppSurface.settings(AppSurface.Article),\n * component: DefaultSettings,\n * props: ({ data: { subject } }) => ({ subject }),\n * });\n */\n props?: (props: ComponentProps<T>) => P;\n}>;\n\n/**\n * Typed Web Component surface definition.\n */\nexport type TypedWebComponentDefinition<T extends Record<string, any> = any> = Readonly<{\n id: string;\n filter: Filter<T>;\n tagName: string;\n position?: Position.Position;\n}>;\n\nconst expandBindings = <T extends Record<string, any>>(\n filter: Filter<T>,\n): { role: string | string[]; guard: (data: Record<string, unknown>, role?: string) => data is T } => {\n const bindings = filter.bindings;\n const roles = Array.from(new Set(bindings.map((binding) => binding.role)));\n const guard = (data: Record<string, unknown>, role?: string): data is T => {\n if (role != null) {\n // Multiple bindings may share a role (e.g. via `oneOf` of same-role filters);\n // the role matches if ANY of them passes.\n return bindings.some((entry) => entry.role === role && entry.guard(data));\n }\n return bindings.some((entry) => entry.guard(data));\n };\n return { role: roles.length === 1 ? roles[0] : roles, guard };\n};\n\n/**\n * Whether a surface or extension local ID follows NSID conventions: the final\n * dot-separated segment must be camelCase (letters and digits only, starting\n * with a letter — no hyphens or underscores). This mirrors the rule enforced\n * when the id is appended to a plugin's NSID to form a full DXN path.\n *\n * A definition with an invalid id is dropped at dispatch rather than rejected\n * here, so a single malformed contribution cannot crash plugin activation.\n *\n * @example Valid: 'about', 'integrationArticle', 'article.journal'\n * @example Invalid: 'integration-article', 'plugin-spec'\n */\nexport const isValidLocalId = (id: string): boolean => /^[a-zA-Z][a-zA-Z0-9]*$/.test(id.split('.').pop() ?? '');\n\n/**\n * Creates a React surface definition from a typed filter.\n */\nexport function create<T extends Record<string, any> = any, P extends Record<string, any> = ComponentProps<T>>(\n definition: TypedReactDefinition<T, P>,\n): ReactDefinition<T>;\nexport function create<T extends Record<string, any> = any, P extends Record<string, any> = ComponentProps<T>>(\n definition: TypedReactDefinition<T, P>,\n): ReactDefinition<T> {\n const { id, filter, component, props, position } = definition;\n const { role, guard } = expandBindings(filter);\n return { kind: 'react', id, role, position, component, props, filter: guard };\n}\n\n/**\n * Creates a Web Component surface definition from a typed filter.\n */\nexport function createWeb<T extends Record<string, any> = any>(\n definition: TypedWebComponentDefinition<T>,\n): WebComponentDefinition<T>;\nexport function createWeb<T extends Record<string, any> = any>(\n definition: TypedWebComponentDefinition<T>,\n): WebComponentDefinition<T> {\n const { id, filter, tagName, position } = definition;\n const { role, guard } = expandBindings(filter);\n return { kind: 'web-component', id, role, position, tagName, filter: guard };\n}\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { Atom } from '@effect-atom/atom';\nimport * as Data from 'effect/Data';\n\nimport { log } from '@dxos/log';\nimport { Position } from '@dxos/util';\n\nimport { Capabilities } from '../../../common';\nimport { type CapabilityManager } from '../../../core';\nimport { type Definition, isValidLocalId } from './types';\n\nconst EMPTY_CANDIDATES: ReadonlyArray<Definition> = Data.array<Definition[]>([]);\n\n/**\n * Groups definitions by role with each bucket pre-sorted by {@link Position}, so\n * dispatch avoids a full scan and re-sort on every render. Pure helper — callers\n * are responsible for filtering out invalid definitions before calling this.\n */\nexport const indexByRole = (definitions: Definition[]): Map<string, Definition[]> => {\n const index = new Map<string, Definition[]>();\n for (const definition of definitions) {\n const roles = Array.isArray(definition.role) ? definition.role : [definition.role];\n for (const role of roles) {\n let bucket = index.get(role);\n if (!bucket) {\n bucket = [];\n index.set(role, bucket);\n }\n bucket.push(definition);\n }\n }\n for (const bucket of index.values()) {\n bucket.sort(Position.compare);\n }\n return index;\n};\n\n/**\n * Owns the per-manager surface memoization: one derived index atom plus a per-role\n * family of candidate atoms. A single instance is provided via\n * {@link SurfaceManagerProvider}, so instance identity does the per-manager keying\n * (replacing module-level WeakMaps) and `Atom.family` does the per-role keying.\n * Atom lifecycle is tied to the provider rather than module-global state.\n */\nexport class SurfaceManager {\n readonly #capabilities: CapabilityManager.CapabilityManager;\n\n // Role index (each bucket position-sorted); rebuilt once per contribution change.\n readonly #index = Atom.make((get) => {\n const definitions = get(this.#capabilities.atom(Capabilities.ReactSurface)).flat();\n return indexByRole(this.#dropInvalid(definitions));\n }).pipe(Atom.keepAlive);\n\n // Per-role candidate atoms. `Data.array` gives the result structural equality, so a\n // contribution to a different role recomputes to an equal value and is dropped —\n // that role's subscribers never re-render.\n readonly #candidates = Atom.family<string, Atom.Atom<ReadonlyArray<Definition>>>((role) =>\n Atom.make((get) => {\n const bucket = get(this.#index).get(role);\n return bucket ? Data.array(bucket) : EMPTY_CANDIDATES;\n }).pipe(Atom.keepAlive),\n );\n\n // Ids already reported as invalid on this manager, so a persistently-malformed\n // contribution warns once rather than on every index rebuild.\n #warnedInvalidIds = new Set<string>();\n\n constructor(capabilities: CapabilityManager.CapabilityManager) {\n this.#capabilities = capabilities;\n }\n\n /** Derived atom yielding the (position-sorted) candidates for a single role. */\n candidatesAtom(role: string): Atom.Atom<ReadonlyArray<Definition>> {\n return this.#candidates(role);\n }\n\n /** Drops definitions with an invalid local id, warning once per id. */\n #dropInvalid(definitions: Definition[]): Definition[] {\n return definitions.filter((definition) => {\n if (isValidLocalId(definition.id)) {\n return true;\n }\n if (!this.#warnedInvalidIds.has(definition.id)) {\n this.#warnedInvalidIds.add(definition.id);\n log.warn('dropping surface with invalid id; the final segment must be camelCase (no hyphens or underscores)', {\n id: definition.id,\n });\n }\n return false;\n });\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { Service, contributes, get, makeModule } from "./core/capability.mjs";
2
+ import { HistoryTracker, OperationInvoker, UndoMapping, UndoRegistry } from "./common/capabilities.mjs";
3
+ import { n as make$1, r as make } from "./chunk-history-tracker.mjs";
4
+ import * as Effect from "effect/Effect";
5
+ //#region src/plugin-process-manager/history/capability.ts
6
+ var capability_default = makeModule(Effect.fnUntraced(function* () {
7
+ const capabilities = yield* Service;
8
+ const undoRegistry = make(() => capabilities.getAll(UndoMapping).flat());
9
+ const historyTracker = make$1(yield* get(OperationInvoker), undoRegistry);
10
+ return [contributes(UndoRegistry, undoRegistry), contributes(HistoryTracker, historyTracker)];
11
+ }));
12
+ //#endregion
13
+ export { capability_default as default };
14
+
15
+ //# sourceMappingURL=chunk-capability.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-capability.mjs","names":[],"sources":["../../src/plugin-process-manager/history/capability.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { OperationInvoker } from '@dxos/operation';\n\nimport { Capabilities } from '../../common';\nimport { Capability } from '../../core';\nimport * as HistoryTracker from './history-tracker';\nimport * as UndoRegistry from './undo-registry';\n\n//\n// Capability Module - contributes both UndoRegistry and HistoryTracker.\n//\n\nexport default Capability.makeModule(\n Effect.fnUntraced(function* () {\n // Get the context for synchronous access in callbacks.\n const capabilities = yield* Capability.Service;\n\n // Create UndoRegistry.\n const undoRegistry = UndoRegistry.make(() => capabilities.getAll(Capabilities.UndoMapping).flat());\n\n // Create HistoryTracker (depends on UndoRegistry and OperationInvoker).\n const invoker = yield* Capability.get(Capabilities.OperationInvoker);\n // Cast to internal type - the factory always returns OperationInvokerInternal.\n const historyTracker = HistoryTracker.make(invoker as OperationInvoker.OperationInvokerInternal, undoRegistry);\n\n return [\n Capability.contributes(Capabilities.UndoRegistry, undoRegistry),\n Capability.contributes(Capabilities.HistoryTracker, historyTracker),\n ];\n }),\n);\n"],"mappings":";;;;;AAiBA,IAAA,qBAAe,WACb,OAAO,WAAW,aAAa;CAE7B,MAAM,eAAe,OAAO;CAG5B,MAAM,eAAe,WAAwB,aAAa,OAAO,WAAwB,CAAC,CAAC,KAAK,CAAC;CAKjG,MAAM,iBAAiB,OAAoB,OAFpB,IAAe,gBAA6B,GAE8B,YAAY;CAE7G,OAAO,CACL,YAAuB,cAA2B,YAAY,GAC9D,YAAuB,gBAA6B,cAAc,CACpE;AACF,CAAC,CACH"}
@@ -1,8 +1,7 @@
1
- // src/context.ts
2
1
  import { createContext } from "@dxos/web-context";
2
+ //#region src/context.ts
3
3
  var PluginManagerContext = createContext("org.dxos.app-framework.plugin-manager");
4
+ //#endregion
5
+ export { PluginManagerContext as t };
4
6
 
5
- export {
6
- PluginManagerContext
7
- };
8
- //# sourceMappingURL=chunk-FJ4765WW.mjs.map
7
+ //# sourceMappingURL=chunk-context.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-context.mjs","names":[],"sources":["../../src/context.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport { createContext } from '@dxos/web-context';\n\nimport { type PluginManager } from './core';\n\nexport const PluginManagerContext = createContext<PluginManager.PluginManager>('org.dxos.app-framework.plugin-manager');\n"],"mappings":";;AAQA,IAAa,uBAAuB,cAA2C,uCAAuC"}
@@ -0,0 +1,160 @@
1
+ import { t as __exportAll } from "./chunk-rolldown-runtime.mjs";
2
+ import * as Effect from "effect/Effect";
3
+ import { log } from "@dxos/log";
4
+ import { BaseError } from "@dxos/errors";
5
+ import * as PubSub from "effect/PubSub";
6
+ import { EffectEx } from "@dxos/effect";
7
+ import * as Stream from "effect/Stream";
8
+ //#region src/plugin-process-manager/history/errors.ts
9
+ var EmptyHistoryError = class extends BaseError.extend("EmptyHistoryError", "Cannot undo: history is empty.") {};
10
+ //#endregion
11
+ //#region src/plugin-process-manager/history/undo-mapping.ts
12
+ var undo_mapping_exports = /* @__PURE__ */ __exportAll({
13
+ make: () => make$2,
14
+ resolveMessage: () => resolveMessage
15
+ });
16
+ /**
17
+ * Creates a type-safe undo mapping.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * // Static message
22
+ * const mapping = UndoMapping.make({
23
+ * operation: DeleteThread,
24
+ * inverse: RestoreThread,
25
+ * deriveContext: (input, output) => ({
26
+ * thread: output.thread,
27
+ * anchor: output.anchor,
28
+ * }),
29
+ * message: ['thread deleted label', { ns: '@dxos/plugin-thread' }],
30
+ * });
31
+ *
32
+ * // Dynamic message based on input/output
33
+ * const mapping = UndoMapping.make({
34
+ * operation: RemoveObjects,
35
+ * inverse: RestoreObjects,
36
+ * deriveContext: (_input, output) => output,
37
+ * message: (input, _output) =>
38
+ * input.objects.length === 1
39
+ * ? ['object deleted label', { ns: getTypename(input.objects[0]) }]
40
+ * : ['objects deleted label', { ns: '@dxos/plugin-space' }],
41
+ * });
42
+ * ```
43
+ */
44
+ var make$2 = (props) => props;
45
+ /**
46
+ * Resolves a message provider to a Label.
47
+ */
48
+ var resolveMessage = (message, input, output) => {
49
+ if (message === void 0) return;
50
+ if (typeof message === "function") return message(input, output);
51
+ return message;
52
+ };
53
+ //#endregion
54
+ //#region src/plugin-process-manager/history/undo-registry.ts
55
+ var undo_registry_exports = /* @__PURE__ */ __exportAll({ make: () => make$1 });
56
+ /**
57
+ * Creates an UndoRegistry that looks up inverse operations.
58
+ */
59
+ var make$1 = (getMappings) => {
60
+ const lookup = (operation) => {
61
+ const mapping = getMappings().find((m) => m.operation.meta.key === operation.meta.key);
62
+ if (!mapping) return;
63
+ return {
64
+ inverse: mapping.inverse,
65
+ deriveContext: mapping.deriveContext,
66
+ message: mapping.message
67
+ };
68
+ };
69
+ return { lookup };
70
+ };
71
+ //#endregion
72
+ //#region src/plugin-process-manager/history/history-tracker.ts
73
+ var history_tracker_exports = /* @__PURE__ */ __exportAll({ make: () => make });
74
+ var __dxlog_file = "/__w/dxos/dxos/packages/sdk/app-framework/src/plugin-process-manager/history/history-tracker.ts";
75
+ var HISTORY_LIMIT = 100;
76
+ /**
77
+ * Creates a HistoryTracker that subscribes to (successful) invocation events, derives undoability from
78
+ * the undo registry, maintains the undo stack, and publishes an {@link UndoableEvent} for each undoable
79
+ * action so the UI can offer an undo affordance. The invoker itself is undo-agnostic.
80
+ */
81
+ var make = (invoker, undoRegistry) => {
82
+ const history = [];
83
+ const undoable = Effect.runSync(PubSub.unbounded());
84
+ const recordInvocation = (event) => {
85
+ const mapping = undoRegistry.lookup(event.operation);
86
+ if (!mapping) return;
87
+ const inverseInput = mapping.deriveContext(event.input, event.output);
88
+ if (inverseInput === void 0) return;
89
+ const entry = {
90
+ operation: event.operation,
91
+ input: event.input,
92
+ output: event.output,
93
+ inverse: mapping.inverse,
94
+ inverseInput,
95
+ timestamp: event.timestamp
96
+ };
97
+ history.push(entry);
98
+ log("history entry added", {
99
+ key: event.operation.meta.key,
100
+ historyLength: history.length
101
+ }, {
102
+ "~LogMeta": "~LogMeta",
103
+ F: __dxlog_file,
104
+ L: 85,
105
+ S: void 0
106
+ });
107
+ if (history.length > HISTORY_LIMIT) history.splice(0, history.length - HISTORY_LIMIT);
108
+ return { message: resolveMessage(mapping.message, event.input, event.output) };
109
+ };
110
+ Effect.runFork(Stream.fromPubSub(invoker.invocations).pipe(Stream.runForEach((event) => Effect.gen(function* () {
111
+ const undoableEvent = recordInvocation(event);
112
+ if (undoableEvent) yield* PubSub.publish(undoable, undoableEvent);
113
+ }))));
114
+ const undo = () => {
115
+ return Effect.gen(function* () {
116
+ const entry = history.pop();
117
+ if (!entry) return yield* Effect.fail(new EmptyHistoryError());
118
+ log("undoing operation", {
119
+ key: entry.operation.meta.key,
120
+ inverseKey: entry.inverse.meta.key
121
+ }, {
122
+ "~LogMeta": "~LogMeta",
123
+ F: __dxlog_file,
124
+ L: 116,
125
+ S: this
126
+ });
127
+ yield* invoker._invokeCore(entry.inverse, entry.inverseInput);
128
+ log("undo completed", { key: entry.operation.meta.key }, {
129
+ "~LogMeta": "~LogMeta",
130
+ F: __dxlog_file,
131
+ L: 121,
132
+ S: this
133
+ });
134
+ });
135
+ };
136
+ const undoPromise = async () => {
137
+ return EffectEx.runAndForwardErrors(undo()).then(() => ({})).catch((error) => {
138
+ log.catch(error, void 0, {
139
+ "~LogMeta": "~LogMeta",
140
+ F: __dxlog_file,
141
+ L: 129,
142
+ S: void 0
143
+ });
144
+ return { error };
145
+ });
146
+ };
147
+ const canUndo = () => {
148
+ return history.length > 0;
149
+ };
150
+ return {
151
+ undo,
152
+ undoPromise,
153
+ canUndo,
154
+ undoable
155
+ };
156
+ };
157
+ //#endregion
158
+ export { undo_mapping_exports as a, undo_registry_exports as i, make as n, EmptyHistoryError as o, make$1 as r, history_tracker_exports as t };
159
+
160
+ //# sourceMappingURL=chunk-history-tracker.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-history-tracker.mjs","names":[],"sources":["../../src/plugin-process-manager/history/errors.ts","../../src/plugin-process-manager/history/undo-mapping.ts","../../src/plugin-process-manager/history/undo-registry.ts","../../src/plugin-process-manager/history/history-tracker.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport { BaseError } from '@dxos/errors';\n\nexport class EmptyHistoryError extends BaseError.extend('EmptyHistoryError', 'Cannot undo: history is empty.') {}\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport type { Operation } from '@dxos/compute';\n\n/**\n * Label type for translatable text (canonical definition in @dxos/app-toolkit).\n */\ntype Label = string | [string, { ns: string | readonly string[]; count?: number; defaultValue?: string }];\n\n/**\n * Extract the input type from an OperationDefinition.\n */\nexport type InputOf<T> = T extends Operation.Definition<infer I, any> ? I : never;\n\n/**\n * Extract the output type from an OperationDefinition.\n */\nexport type OutputOf<T> = T extends Operation.Definition<any, infer O> ? O : never;\n\n/**\n * Message provider for undo toast.\n * Can be a static Label or a function that derives the message from input/output.\n */\nexport type MessageProvider<Op extends Operation.Definition<any, any>> =\n | Label\n | ((input: InputOf<Op>, output: OutputOf<Op>) => Label);\n\n/**\n * Undo mapping that links a forward operation to its inverse.\n * Type parameters ensure deriveContext has correctly typed arguments.\n *\n * @template Op - The forward operation definition type.\n * @template Inv - The inverse operation definition type.\n */\nexport interface UndoMapping<\n Op extends Operation.Definition<any, any> = Operation.Definition<any, any>,\n Inv extends Operation.Definition<any, any> = Operation.Definition<any, any>,\n> {\n /** The forward operation. */\n readonly operation: Op;\n\n /** The inverse operation to invoke for undo. */\n readonly inverse: Inv;\n\n /**\n * Derives the input for the inverse operation from the forward operation's input and output.\n * @param input - The input that was passed to the forward operation.\n * @param output - The output that was returned by the forward operation.\n * @returns The input to pass to the inverse operation, or undefined to indicate the operation is not undoable.\n */\n readonly deriveContext: (input: InputOf<Op>, output: OutputOf<Op>) => InputOf<Inv> | undefined;\n\n /**\n * Optional message to show in the undo toast.\n * Can be a static Label or a function that derives the message from input/output.\n */\n readonly message?: MessageProvider<Op>;\n}\n\n/**\n * Props for creating an UndoMapping.\n */\nexport interface UndoMappingProps<\n Op extends Operation.Definition<any, any>,\n Inv extends Operation.Definition<any, any>,\n> {\n /** The forward operation. */\n operation: Op;\n\n /** The inverse operation to invoke for undo. */\n inverse: Inv;\n\n /**\n * Derives the input for the inverse operation from the forward operation's input and output.\n * Return undefined to indicate the operation is not undoable.\n */\n deriveContext: (input: InputOf<Op>, output: OutputOf<Op>) => InputOf<Inv> | undefined;\n\n /**\n * Optional message to show in the undo toast.\n * Can be a static Label or a function that derives the message from input/output.\n */\n message?: MessageProvider<Op>;\n}\n\n/**\n * Creates a type-safe undo mapping.\n *\n * @example\n * ```ts\n * // Static message\n * const mapping = UndoMapping.make({\n * operation: DeleteThread,\n * inverse: RestoreThread,\n * deriveContext: (input, output) => ({\n * thread: output.thread,\n * anchor: output.anchor,\n * }),\n * message: ['thread deleted label', { ns: '@dxos/plugin-thread' }],\n * });\n *\n * // Dynamic message based on input/output\n * const mapping = UndoMapping.make({\n * operation: RemoveObjects,\n * inverse: RestoreObjects,\n * deriveContext: (_input, output) => output,\n * message: (input, _output) =>\n * input.objects.length === 1\n * ? ['object deleted label', { ns: getTypename(input.objects[0]) }]\n * : ['objects deleted label', { ns: '@dxos/plugin-space' }],\n * });\n * ```\n */\nexport const make = <Op extends Operation.Definition<any, any>, Inv extends Operation.Definition<any, any>>(\n props: UndoMappingProps<Op, Inv>,\n): UndoMapping<Op, Inv> => props;\n\n/**\n * Resolves a message provider to a Label.\n */\nexport const resolveMessage = <Op extends Operation.Definition<any, any>>(\n message: MessageProvider<Op> | undefined,\n input: InputOf<Op>,\n output: OutputOf<Op>,\n): Label | undefined => {\n if (message === undefined) {\n return undefined;\n }\n if (typeof message === 'function') {\n return message(input, output);\n }\n return message;\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport type { Operation } from '@dxos/compute';\n\nimport type * as UndoMapping from './undo-mapping';\n\n//\n// Public Interface\n//\n\n/**\n * Lookup result from UndoRegistry.\n */\nexport type UndoMappingResult = {\n inverse: Operation.Definition<any, any>;\n /** Returns undefined to indicate the operation is not undoable. */\n deriveContext: (input: any, output: any) => any | undefined;\n /** Message provider - may be a static Label or a function. */\n message?: UndoMapping.MessageProvider<Operation.Definition<any, any>>;\n};\n\n/**\n * UndoRegistry interface - looks up inverse operations.\n */\nexport interface UndoRegistry {\n lookup: (operation: Operation.Definition<any, any>) => UndoMappingResult | undefined;\n}\n\n//\n// Factory\n//\n\n/**\n * Creates an UndoRegistry that looks up inverse operations.\n */\nexport const make = (getMappings: () => UndoMapping.UndoMapping[]): UndoRegistry => {\n const lookup = (operation: Operation.Definition<any, any>): UndoMappingResult | undefined => {\n const mappings = getMappings();\n const mapping = mappings.find((m) => m.operation.meta.key === operation.meta.key);\n if (!mapping) {\n return undefined;\n }\n\n return {\n inverse: mapping.inverse,\n deriveContext: mapping.deriveContext,\n message: mapping.message,\n };\n };\n\n return { lookup };\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport * as PubSub from 'effect/PubSub';\nimport * as Stream from 'effect/Stream';\n\nimport { EffectEx } from '@dxos/effect';\nimport { log } from '@dxos/log';\nimport { OperationInvoker } from '@dxos/operation';\n\nimport { type Label } from '../../common';\nimport { EmptyHistoryError } from './errors';\nimport type { HistoryEntry } from './types';\nimport { resolveMessage } from './undo-mapping';\nimport type { UndoRegistry } from './undo-registry';\n\nconst HISTORY_LIMIT = 100;\n\n//\n// Public Interface\n//\n\n/**\n * Emitted when a new undoable action is recorded; consumed by the UI to surface an undo affordance\n * (e.g. the deck notification tracker's undo toast).\n */\nexport type UndoableEvent = {\n /** Message describing the undoable action. */\n message?: Label;\n};\n\n/**\n * HistoryTracker interface - tracks operation history and provides undo.\n */\nexport interface HistoryTracker {\n undo: () => Effect.Effect<void, Error>;\n undoPromise: () => Promise<{ error?: Error }>;\n canUndo: () => boolean;\n /** Stream of undoable actions, published as they are recorded. */\n undoable: PubSub.PubSub<UndoableEvent>;\n}\n\n//\n// Factory\n//\n\n/**\n * Creates a HistoryTracker that subscribes to (successful) invocation events, derives undoability from\n * the undo registry, maintains the undo stack, and publishes an {@link UndoableEvent} for each undoable\n * action so the UI can offer an undo affordance. The invoker itself is undo-agnostic.\n */\nexport const make = (\n invoker: OperationInvoker.OperationInvokerInternal,\n undoRegistry: UndoRegistry,\n): HistoryTracker => {\n const history: HistoryEntry[] = [];\n const undoable = Effect.runSync(PubSub.unbounded<UndoableEvent>());\n\n // Record an invocation; returns the undoable event to publish, or undefined if not undoable.\n const recordInvocation = (event: OperationInvoker.InvocationEvent): UndoableEvent | undefined => {\n const mapping = undoRegistry.lookup(event.operation);\n if (!mapping) {\n // Operation is not undoable.\n return undefined;\n }\n\n const inverseInput = mapping.deriveContext(event.input, event.output);\n if (inverseInput === undefined) {\n // Operation is conditionally not undoable (deriveContext returned undefined).\n return undefined;\n }\n\n const entry: HistoryEntry = {\n operation: event.operation,\n input: event.input,\n output: event.output,\n inverse: mapping.inverse,\n inverseInput,\n timestamp: event.timestamp,\n };\n\n history.push(entry);\n log('history entry added', { key: event.operation.meta.key, historyLength: history.length });\n\n // Trim history if it exceeds limit.\n if (history.length > HISTORY_LIMIT) {\n history.splice(0, history.length - HISTORY_LIMIT);\n }\n\n return { message: resolveMessage(mapping.message, event.input, event.output) };\n };\n\n // Fork a fiber to consume the invocation stream.\n Effect.runFork(\n Stream.fromPubSub(invoker.invocations).pipe(\n Stream.runForEach((event) =>\n Effect.gen(function* () {\n const undoableEvent = recordInvocation(event);\n if (undoableEvent) {\n yield* PubSub.publish(undoable, undoableEvent);\n }\n }),\n ),\n ),\n );\n\n const undo = (): Effect.Effect<void, Error> => {\n return Effect.gen(function* () {\n const entry = history.pop();\n if (!entry) {\n return yield* Effect.fail(new EmptyHistoryError());\n }\n\n log('undoing operation', { key: entry.operation.meta.key, inverseKey: entry.inverse.meta.key });\n\n // Use _invokeCore to skip event emission (avoid undo-of-undo loops).\n yield* invoker._invokeCore(entry.inverse, entry.inverseInput);\n\n log('undo completed', { key: entry.operation.meta.key });\n });\n };\n\n const undoPromise = async (): Promise<{ error?: Error }> => {\n return EffectEx.runAndForwardErrors(undo())\n .then(() => ({}))\n .catch((error) => {\n log.catch(error);\n return { error };\n });\n };\n\n const canUndo = (): boolean => {\n return history.length > 0;\n };\n\n return {\n undo,\n undoPromise,\n canUndo,\n undoable,\n };\n};\n"],"mappings":"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"}