@dxos/plugin-space 0.7.4 → 0.7.5-feature-compute.4d9d99a

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 (255) hide show
  1. package/dist/lib/browser/app-graph-builder-5D2QB43K.mjs +365 -0
  2. package/dist/lib/browser/app-graph-builder-5D2QB43K.mjs.map +7 -0
  3. package/dist/lib/browser/app-graph-serializer-VNWPLPDF.mjs +80 -0
  4. package/dist/lib/browser/app-graph-serializer-VNWPLPDF.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-5TBRONF6.mjs +133 -0
  6. package/dist/lib/browser/chunk-5TBRONF6.mjs.map +7 -0
  7. package/dist/lib/browser/chunk-6SWQRWOD.mjs +1672 -0
  8. package/dist/lib/browser/chunk-6SWQRWOD.mjs.map +7 -0
  9. package/dist/lib/browser/chunk-HCXWKGTE.mjs +316 -0
  10. package/dist/lib/browser/chunk-HCXWKGTE.mjs.map +7 -0
  11. package/dist/lib/browser/chunk-SOXNANA6.mjs +12 -0
  12. package/dist/lib/browser/chunk-SOXNANA6.mjs.map +7 -0
  13. package/dist/lib/browser/chunk-UH5P4UL3.mjs +21 -0
  14. package/dist/lib/browser/chunk-UH5P4UL3.mjs.map +7 -0
  15. package/dist/lib/browser/chunk-ZBKFJNHH.mjs +523 -0
  16. package/dist/lib/browser/chunk-ZBKFJNHH.mjs.map +7 -0
  17. package/dist/lib/browser/identity-created-EC5FOCX2.mjs +28 -0
  18. package/dist/lib/browser/identity-created-EC5FOCX2.mjs.map +7 -0
  19. package/dist/lib/browser/index.mjs +192 -3521
  20. package/dist/lib/browser/index.mjs.map +4 -4
  21. package/dist/lib/browser/intent-resolver-VBL572N7.mjs +459 -0
  22. package/dist/lib/browser/intent-resolver-VBL572N7.mjs.map +7 -0
  23. package/dist/lib/browser/meta.json +1 -1
  24. package/dist/lib/browser/react-root-AZJFNTKK.mjs +28 -0
  25. package/dist/lib/browser/react-root-AZJFNTKK.mjs.map +7 -0
  26. package/dist/lib/browser/react-surface-E2VSYVNZ.mjs +238 -0
  27. package/dist/lib/browser/react-surface-E2VSYVNZ.mjs.map +7 -0
  28. package/dist/lib/browser/settings-ASFF5BZL.mjs +24 -0
  29. package/dist/lib/browser/settings-ASFF5BZL.mjs.map +7 -0
  30. package/dist/lib/browser/spaces-ready-4SFNS5JQ.mjs +200 -0
  31. package/dist/lib/browser/spaces-ready-4SFNS5JQ.mjs.map +7 -0
  32. package/dist/lib/browser/state-MS4KYJWI.mjs +47 -0
  33. package/dist/lib/browser/state-MS4KYJWI.mjs.map +7 -0
  34. package/dist/lib/browser/types/index.mjs +14 -5
  35. package/dist/lib/node/app-graph-builder-ZQ5S62YR.cjs +368 -0
  36. package/dist/lib/node/app-graph-builder-ZQ5S62YR.cjs.map +7 -0
  37. package/dist/lib/node/app-graph-serializer-72S7P33H.cjs +88 -0
  38. package/dist/lib/node/app-graph-serializer-72S7P33H.cjs.map +7 -0
  39. package/dist/lib/node/chunk-56NGXG2A.cjs +41 -0
  40. package/dist/lib/node/chunk-56NGXG2A.cjs.map +7 -0
  41. package/dist/lib/node/{meta.cjs → chunk-AO4EW2RX.cjs} +12 -13
  42. package/dist/lib/node/chunk-AO4EW2RX.cjs.map +7 -0
  43. package/dist/lib/node/chunk-BQRNTKSQ.cjs +150 -0
  44. package/dist/lib/node/chunk-BQRNTKSQ.cjs.map +7 -0
  45. package/dist/lib/node/chunk-DDZYVNVP.cjs +345 -0
  46. package/dist/lib/node/chunk-DDZYVNVP.cjs.map +7 -0
  47. package/dist/lib/node/chunk-M64YG2FY.cjs +1669 -0
  48. package/dist/lib/node/chunk-M64YG2FY.cjs.map +7 -0
  49. package/dist/lib/node/chunk-Z34MTEU7.cjs +551 -0
  50. package/dist/lib/node/chunk-Z34MTEU7.cjs.map +7 -0
  51. package/dist/lib/node/identity-created-IMDS4A6A.cjs +44 -0
  52. package/dist/lib/node/identity-created-IMDS4A6A.cjs.map +7 -0
  53. package/dist/lib/node/index.cjs +175 -3506
  54. package/dist/lib/node/index.cjs.map +4 -4
  55. package/dist/lib/node/intent-resolver-S4HZABYI.cjs +458 -0
  56. package/dist/lib/node/intent-resolver-S4HZABYI.cjs.map +7 -0
  57. package/dist/lib/node/meta.json +1 -1
  58. package/dist/lib/node/react-root-RB3OM3QG.cjs +50 -0
  59. package/dist/lib/node/react-root-RB3OM3QG.cjs.map +7 -0
  60. package/dist/lib/node/react-surface-TLKQEHHT.cjs +233 -0
  61. package/dist/lib/node/react-surface-TLKQEHHT.cjs.map +7 -0
  62. package/dist/lib/node/settings-QLCKAUHK.cjs +38 -0
  63. package/dist/lib/node/settings-QLCKAUHK.cjs.map +7 -0
  64. package/dist/lib/node/spaces-ready-RZTKEXOL.cjs +211 -0
  65. package/dist/lib/node/spaces-ready-RZTKEXOL.cjs.map +7 -0
  66. package/dist/lib/node/state-4UIOUKLJ.cjs +61 -0
  67. package/dist/lib/node/state-4UIOUKLJ.cjs.map +7 -0
  68. package/dist/lib/node/types/index.cjs +23 -14
  69. package/dist/lib/node/types/index.cjs.map +2 -2
  70. package/dist/lib/node-esm/app-graph-builder-CD6IYPSS.mjs +366 -0
  71. package/dist/lib/node-esm/app-graph-builder-CD6IYPSS.mjs.map +7 -0
  72. package/dist/lib/node-esm/app-graph-serializer-CFXS6ZE2.mjs +81 -0
  73. package/dist/lib/node-esm/app-graph-serializer-CFXS6ZE2.mjs.map +7 -0
  74. package/dist/lib/node-esm/chunk-375RB3CZ.mjs +22 -0
  75. package/dist/lib/node-esm/chunk-375RB3CZ.mjs.map +7 -0
  76. package/dist/lib/node-esm/chunk-7FUVU45N.mjs +14 -0
  77. package/dist/lib/node-esm/chunk-7FUVU45N.mjs.map +7 -0
  78. package/dist/lib/node-esm/chunk-CLGCKZ2D.mjs +317 -0
  79. package/dist/lib/node-esm/chunk-CLGCKZ2D.mjs.map +7 -0
  80. package/dist/lib/node-esm/chunk-CMKML5IN.mjs +1673 -0
  81. package/dist/lib/node-esm/chunk-CMKML5IN.mjs.map +7 -0
  82. package/dist/lib/node-esm/chunk-FUMGYUD3.mjs +524 -0
  83. package/dist/lib/node-esm/chunk-FUMGYUD3.mjs.map +7 -0
  84. package/dist/lib/node-esm/chunk-M4XTHK35.mjs +134 -0
  85. package/dist/lib/node-esm/chunk-M4XTHK35.mjs.map +7 -0
  86. package/dist/lib/node-esm/identity-created-SJYZZ7Q3.mjs +29 -0
  87. package/dist/lib/node-esm/identity-created-SJYZZ7Q3.mjs.map +7 -0
  88. package/dist/lib/node-esm/index.mjs +192 -3521
  89. package/dist/lib/node-esm/index.mjs.map +4 -4
  90. package/dist/lib/node-esm/intent-resolver-OIQH7HN7.mjs +460 -0
  91. package/dist/lib/node-esm/intent-resolver-OIQH7HN7.mjs.map +7 -0
  92. package/dist/lib/node-esm/meta.json +1 -1
  93. package/dist/lib/node-esm/react-root-WKJWCHXR.mjs +29 -0
  94. package/dist/lib/node-esm/react-root-WKJWCHXR.mjs.map +7 -0
  95. package/dist/lib/node-esm/react-surface-RVEHOSAD.mjs +239 -0
  96. package/dist/lib/node-esm/react-surface-RVEHOSAD.mjs.map +7 -0
  97. package/dist/lib/node-esm/settings-WLVEO4JM.mjs +25 -0
  98. package/dist/lib/node-esm/settings-WLVEO4JM.mjs.map +7 -0
  99. package/dist/lib/node-esm/spaces-ready-ITGYYT5A.mjs +201 -0
  100. package/dist/lib/node-esm/spaces-ready-ITGYYT5A.mjs.map +7 -0
  101. package/dist/lib/node-esm/state-BMISGQ2O.mjs +48 -0
  102. package/dist/lib/node-esm/state-BMISGQ2O.mjs.map +7 -0
  103. package/dist/lib/node-esm/types/index.mjs +14 -5
  104. package/dist/types/src/SpacePlugin.d.ts +1 -24
  105. package/dist/types/src/SpacePlugin.d.ts.map +1 -1
  106. package/dist/types/src/capabilities/app-graph-builder.d.ts +181 -0
  107. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
  108. package/dist/types/src/capabilities/app-graph-serializer.d.ts +4 -0
  109. package/dist/types/src/capabilities/app-graph-serializer.d.ts.map +1 -0
  110. package/dist/types/src/capabilities/capabilities.d.ts +15 -0
  111. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -0
  112. package/dist/types/src/capabilities/identity-created.d.ts +4 -0
  113. package/dist/types/src/capabilities/identity-created.d.ts.map +1 -0
  114. package/dist/types/src/capabilities/index.d.ts +195 -0
  115. package/dist/types/src/capabilities/index.d.ts.map +1 -0
  116. package/dist/types/src/capabilities/intent-resolver.d.ts +8 -0
  117. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -0
  118. package/dist/types/src/capabilities/react-root.d.ts +7 -0
  119. package/dist/types/src/capabilities/react-root.d.ts.map +1 -0
  120. package/dist/types/src/capabilities/react-surface.d.ts +7 -0
  121. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
  122. package/dist/types/src/capabilities/settings.d.ts +4 -0
  123. package/dist/types/src/capabilities/settings.d.ts.map +1 -0
  124. package/dist/types/src/capabilities/spaces-ready.d.ts +4 -0
  125. package/dist/types/src/capabilities/spaces-ready.d.ts.map +1 -0
  126. package/dist/types/src/capabilities/state.d.ts +5 -0
  127. package/dist/types/src/capabilities/state.d.ts.map +1 -0
  128. package/dist/types/src/components/AdvancedObjectSettings/AdvancedObjectSettings.d.ts +7 -0
  129. package/dist/types/src/components/AdvancedObjectSettings/AdvancedObjectSettings.d.ts.map +1 -0
  130. package/dist/types/src/components/AdvancedObjectSettings/ForeignKeys.d.ts +8 -0
  131. package/dist/types/src/components/AdvancedObjectSettings/ForeignKeys.d.ts.map +1 -0
  132. package/dist/types/src/components/AdvancedObjectSettings/index.d.ts +2 -0
  133. package/dist/types/src/components/AdvancedObjectSettings/index.d.ts.map +1 -0
  134. package/dist/types/src/components/AwaitingObject.d.ts.map +1 -1
  135. package/dist/types/src/components/BaseObjectSettings.d.ts +7 -0
  136. package/dist/types/src/components/BaseObjectSettings.d.ts.map +1 -0
  137. package/dist/types/src/components/CreateDialog/CreateObjectDialog.d.ts +5 -4
  138. package/dist/types/src/components/CreateDialog/CreateObjectDialog.d.ts.map +1 -1
  139. package/dist/types/src/components/CreateDialog/CreateObjectPanel.d.ts +5 -6
  140. package/dist/types/src/components/CreateDialog/CreateObjectPanel.d.ts.map +1 -1
  141. package/dist/types/src/components/CreateDialog/CreateSpaceDialog.d.ts +1 -0
  142. package/dist/types/src/components/CreateDialog/CreateSpaceDialog.d.ts.map +1 -1
  143. package/dist/types/src/components/JoinDialog.d.ts +1 -0
  144. package/dist/types/src/components/JoinDialog.d.ts.map +1 -1
  145. package/dist/types/src/components/PopoverRenameObject.d.ts +1 -0
  146. package/dist/types/src/components/PopoverRenameObject.d.ts.map +1 -1
  147. package/dist/types/src/components/PopoverRenameSpace.d.ts +1 -0
  148. package/dist/types/src/components/PopoverRenameSpace.d.ts.map +1 -1
  149. package/dist/types/src/components/ShareSpaceButton.d.ts.map +1 -1
  150. package/dist/types/src/components/SpacePluginSettings.d.ts.map +1 -1
  151. package/dist/types/src/components/SpacePresence.d.ts +9 -6
  152. package/dist/types/src/components/SpacePresence.d.ts.map +1 -1
  153. package/dist/types/src/components/SpacePresence.stories.d.ts +1 -1
  154. package/dist/types/src/components/SpacePresence.stories.d.ts.map +1 -1
  155. package/dist/types/src/components/SpaceSettings/SpaceSettingsDialog.d.ts +1 -0
  156. package/dist/types/src/components/SpaceSettings/SpaceSettingsDialog.d.ts.map +1 -1
  157. package/dist/types/src/components/SpaceSettings/SpaceSettingsDialog.stories.d.ts.map +1 -1
  158. package/dist/types/src/components/SpaceSettings/SpaceSettingsPanel.d.ts +4 -3
  159. package/dist/types/src/components/SpaceSettings/SpaceSettingsPanel.d.ts.map +1 -1
  160. package/dist/types/src/components/SpaceSettings/SpaceSettingsPanel.stories.d.ts.map +1 -1
  161. package/dist/types/src/components/SyncStatus/InlineSyncStatus.d.ts +3 -3
  162. package/dist/types/src/components/SyncStatus/InlineSyncStatus.d.ts.map +1 -1
  163. package/dist/types/src/components/SyncStatus/SyncStatusDetail.stories.d.ts +2 -2
  164. package/dist/types/src/components/SyncStatus/SyncStatusDetail.stories.d.ts.map +1 -1
  165. package/dist/types/src/components/index.d.ts +2 -1
  166. package/dist/types/src/components/index.d.ts.map +1 -1
  167. package/dist/types/src/events.d.ts +5 -0
  168. package/dist/types/src/events.d.ts.map +1 -0
  169. package/dist/types/src/hooks/index.d.ts +2 -0
  170. package/dist/types/src/hooks/index.d.ts.map +1 -0
  171. package/dist/types/src/hooks/usePath.d.ts +11 -0
  172. package/dist/types/src/hooks/usePath.d.ts.map +1 -0
  173. package/dist/types/src/index.d.ts +3 -5
  174. package/dist/types/src/index.d.ts.map +1 -1
  175. package/dist/types/src/meta.d.ts +1 -27
  176. package/dist/types/src/meta.d.ts.map +1 -1
  177. package/dist/types/src/translations.d.ts +18 -3
  178. package/dist/types/src/translations.d.ts.map +1 -1
  179. package/dist/types/src/types/collection.d.ts +8 -12
  180. package/dist/types/src/types/collection.d.ts.map +1 -1
  181. package/dist/types/src/types/thread.d.ts +180 -186
  182. package/dist/types/src/types/thread.d.ts.map +1 -1
  183. package/dist/types/src/types/types.d.ts +228 -16
  184. package/dist/types/src/types/types.d.ts.map +1 -1
  185. package/dist/types/src/util.d.ts +9 -8
  186. package/dist/types/src/util.d.ts.map +1 -1
  187. package/dist/types/tsconfig.tsbuildinfo +1 -0
  188. package/package.json +38 -46
  189. package/src/SpacePlugin.tsx +119 -1541
  190. package/src/capabilities/app-graph-builder.ts +392 -0
  191. package/src/capabilities/app-graph-serializer.ts +73 -0
  192. package/src/capabilities/capabilities.ts +23 -0
  193. package/src/capabilities/identity-created.ts +26 -0
  194. package/src/capabilities/index.ts +17 -0
  195. package/src/capabilities/intent-resolver.ts +420 -0
  196. package/src/capabilities/react-root.tsx +20 -0
  197. package/src/capabilities/react-surface.tsx +234 -0
  198. package/src/capabilities/settings.ts +17 -0
  199. package/src/capabilities/spaces-ready.ts +231 -0
  200. package/src/capabilities/state.ts +45 -0
  201. package/src/components/AdvancedObjectSettings/AdvancedObjectSettings.tsx +72 -0
  202. package/src/components/AdvancedObjectSettings/ForeignKeys.tsx +51 -0
  203. package/src/components/AdvancedObjectSettings/index.ts +5 -0
  204. package/src/components/AwaitingObject.tsx +15 -19
  205. package/src/components/{DefaultObjectSettings.tsx → BaseObjectSettings.tsx} +2 -2
  206. package/src/components/CreateDialog/CreateObjectDialog.tsx +36 -25
  207. package/src/components/CreateDialog/CreateObjectPanel.tsx +61 -24
  208. package/src/components/CreateDialog/CreateSpaceDialog.tsx +10 -14
  209. package/src/components/JoinDialog.tsx +18 -34
  210. package/src/components/PersistenceStatus.tsx +1 -1
  211. package/src/components/PopoverRenameObject.tsx +2 -0
  212. package/src/components/PopoverRenameSpace.tsx +2 -0
  213. package/src/components/ShareSpaceButton.tsx +5 -4
  214. package/src/components/SpacePluginSettings.tsx +2 -13
  215. package/src/components/SpacePresence.stories.tsx +25 -17
  216. package/src/components/SpacePresence.tsx +42 -21
  217. package/src/components/SpaceSettings/SpaceSettingsDialog.stories.tsx +2 -3
  218. package/src/components/SpaceSettings/SpaceSettingsDialog.tsx +3 -1
  219. package/src/components/SpaceSettings/SpaceSettingsPanel.stories.tsx +7 -5
  220. package/src/components/SpaceSettings/SpaceSettingsPanel.tsx +6 -5
  221. package/src/components/SyncStatus/InlineSyncStatus.tsx +37 -27
  222. package/src/components/SyncStatus/SyncStatusDetail.stories.tsx +55 -51
  223. package/src/components/index.ts +2 -1
  224. package/src/events.ts +12 -0
  225. package/src/hooks/index.ts +5 -0
  226. package/src/hooks/usePath.ts +44 -0
  227. package/src/index.ts +3 -7
  228. package/src/meta.ts +1 -29
  229. package/src/translations.ts +7 -2
  230. package/src/types/collection.ts +3 -3
  231. package/src/types/thread.ts +6 -6
  232. package/src/types/types.ts +177 -42
  233. package/src/util.tsx +77 -64
  234. package/dist/lib/browser/chunk-FTKV32QZ.mjs +0 -43
  235. package/dist/lib/browser/chunk-FTKV32QZ.mjs.map +0 -7
  236. package/dist/lib/browser/chunk-MWKXNS5S.mjs +0 -124
  237. package/dist/lib/browser/chunk-MWKXNS5S.mjs.map +0 -7
  238. package/dist/lib/browser/meta.mjs +0 -15
  239. package/dist/lib/browser/meta.mjs.map +0 -7
  240. package/dist/lib/node/chunk-6SNOZF7Y.cjs +0 -152
  241. package/dist/lib/node/chunk-6SNOZF7Y.cjs.map +0 -7
  242. package/dist/lib/node/chunk-QNVEU2UD.cjs +0 -69
  243. package/dist/lib/node/chunk-QNVEU2UD.cjs.map +0 -7
  244. package/dist/lib/node/meta.cjs.map +0 -7
  245. package/dist/lib/node-esm/chunk-OHEAWSCA.mjs +0 -126
  246. package/dist/lib/node-esm/chunk-OHEAWSCA.mjs.map +0 -7
  247. package/dist/lib/node-esm/chunk-UMV7XREB.mjs +0 -45
  248. package/dist/lib/node-esm/chunk-UMV7XREB.mjs.map +0 -7
  249. package/dist/lib/node-esm/meta.mjs +0 -16
  250. package/dist/lib/node-esm/meta.mjs.map +0 -7
  251. package/dist/types/src/components/DefaultObjectSettings.d.ts +0 -7
  252. package/dist/types/src/components/DefaultObjectSettings.d.ts.map +0 -1
  253. package/dist/types/src/components/SyncStatus/InlineSyncStatus.stories.d.ts +0 -6
  254. package/dist/types/src/components/SyncStatus/InlineSyncStatus.stories.d.ts.map +0 -1
  255. package/src/components/SyncStatus/InlineSyncStatus.stories.tsx +0 -57
@@ -0,0 +1,231 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { contributes, createIntent, openIds, type PluginsContext, Capabilities } from '@dxos/app-framework';
6
+ import { EventSubscriptions } from '@dxos/async';
7
+ import { Expando } from '@dxos/echo-schema';
8
+ import { scheduledEffect } from '@dxos/echo-signals/core';
9
+ import { create } from '@dxos/live-object';
10
+ import { log } from '@dxos/log';
11
+ import { AttentionCapabilities } from '@dxos/plugin-attention';
12
+ import { ClientCapabilities } from '@dxos/plugin-client';
13
+ import { EdgeReplicationSetting } from '@dxos/protocols/proto/dxos/echo/metadata';
14
+ import { PublicKey } from '@dxos/react-client';
15
+ import { Filter, FQ_ID_LENGTH, parseFullyQualifiedId, SpaceState } from '@dxos/react-client/echo';
16
+ import { ComplexMap, reduceGroupBy } from '@dxos/util';
17
+
18
+ import { SpaceCapabilities } from './capabilities';
19
+ import { SpaceAction } from '../types';
20
+ import { COMPOSER_SPACE_LOCK, SHARED } from '../util';
21
+
22
+ const ACTIVE_NODE_BROADCAST_INTERVAL = 30_000;
23
+ const WAIT_FOR_OBJECT_TIMEOUT = 1000;
24
+
25
+ export default async (context: PluginsContext) => {
26
+ const subscriptions = new EventSubscriptions();
27
+ const spaceSubscriptions = new EventSubscriptions();
28
+
29
+ const { dispatchPromise: dispatch } = context.requestCapability(Capabilities.IntentDispatcher);
30
+ const { graph } = context.requestCapability(Capabilities.AppGraph);
31
+ const layout = context.requestCapability(Capabilities.Layout);
32
+ const location = context.requestCapability(Capabilities.Location);
33
+ const attention = context.requestCapability(AttentionCapabilities.Attention);
34
+ const state = context.requestCapability(SpaceCapabilities.MutableState);
35
+ const client = context.requestCapability(ClientCapabilities.Client);
36
+
37
+ const defaultSpace = client.spaces.default;
38
+ await defaultSpace.waitUntilReady();
39
+
40
+ // Initialize space sharing lock in default space.
41
+ if (typeof defaultSpace.properties[COMPOSER_SPACE_LOCK] !== 'boolean') {
42
+ defaultSpace.properties[COMPOSER_SPACE_LOCK] = true;
43
+ }
44
+
45
+ const {
46
+ objects: [spacesOrder],
47
+ } = await defaultSpace.db.query(Filter.schema(Expando, { key: SHARED })).run();
48
+ if (!spacesOrder) {
49
+ // TODO(wittjosiah): Cannot be a Folder because Spaces are not TypedObjects so can't be saved in the database.
50
+ // Instead, we store order as an array of space ids.
51
+ defaultSpace.db.add(create({ key: SHARED, order: [] }));
52
+ }
53
+
54
+ // Await missing objects.
55
+ subscriptions.add(
56
+ scheduledEffect(
57
+ () => ({
58
+ layoutMode: layout.layoutMode,
59
+ soloPart: location.active.solo?.[0],
60
+ }),
61
+ ({ layoutMode, soloPart }) => {
62
+ if (layoutMode !== 'solo' || !soloPart) {
63
+ return;
64
+ }
65
+
66
+ const node = graph.findNode(soloPart.id);
67
+ if (!node && soloPart.id.length === FQ_ID_LENGTH) {
68
+ const timeout = setTimeout(async () => {
69
+ const node = graph.findNode(soloPart.id);
70
+ if (!node) {
71
+ await dispatch(createIntent(SpaceAction.WaitForObject, { id: soloPart.id }));
72
+ }
73
+ }, WAIT_FOR_OBJECT_TIMEOUT);
74
+
75
+ return () => clearTimeout(timeout);
76
+ }
77
+ },
78
+ ),
79
+ );
80
+
81
+ // Cache space names.
82
+ subscriptions.add(
83
+ client.spaces.subscribe(async (spaces) => {
84
+ // TODO(wittjosiah): Remove. This is a hack to be able to migrate the default space properties.
85
+ if (defaultSpace.state.get() === SpaceState.SPACE_REQUIRES_MIGRATION) {
86
+ await defaultSpace.internal.migrate();
87
+ }
88
+
89
+ spaces
90
+ .filter((space) => space.state.get() === SpaceState.SPACE_READY)
91
+ .forEach((space) => {
92
+ subscriptions.add(
93
+ scheduledEffect(
94
+ () => ({ name: space.properties.name }),
95
+ ({ name }) => (state.spaceNames[space.id] = name),
96
+ ),
97
+ );
98
+ });
99
+ }).unsubscribe,
100
+ );
101
+
102
+ // Broadcast active node to other peers in the space.
103
+ subscriptions.add(
104
+ scheduledEffect(
105
+ () => ({
106
+ open: openIds(location.active, layout.layoutMode === 'solo' ? ['solo'] : ['main']),
107
+ closed: [...location.closed],
108
+ }),
109
+ ({ open, closed }) => {
110
+ const send = () => {
111
+ const spaces = client.spaces.get();
112
+ const identity = client.halo.identity.get();
113
+ if (identity && location.active) {
114
+ // Group parts by space for efficient messaging.
115
+ const idsBySpace = reduceGroupBy(open, (id) => {
116
+ try {
117
+ const [spaceId] = parseFullyQualifiedId(id);
118
+ return spaceId;
119
+ } catch {
120
+ return null;
121
+ }
122
+ });
123
+
124
+ const removedBySpace = reduceGroupBy(closed, (id) => {
125
+ try {
126
+ const [spaceId] = parseFullyQualifiedId(id);
127
+ return spaceId;
128
+ } catch {
129
+ return null;
130
+ }
131
+ });
132
+
133
+ // NOTE: Ensure all spaces are included so that we send the correct `removed` object arrays.
134
+ for (const space of spaces) {
135
+ if (!idsBySpace.has(space.id)) {
136
+ idsBySpace.set(space.id, []);
137
+ }
138
+ }
139
+
140
+ for (const [spaceId, added] of idsBySpace) {
141
+ const removed = removedBySpace.get(spaceId) ?? [];
142
+ const space = spaces.find((space) => space.id === spaceId);
143
+ if (!space) {
144
+ continue;
145
+ }
146
+
147
+ void space
148
+ .postMessage('viewing', {
149
+ identityKey: identity.identityKey.toHex(),
150
+ attended: attention.current ? [...attention.current] : [],
151
+ added,
152
+ removed,
153
+ })
154
+ // TODO(burdon): This seems defensive; why would this fail? Backoff interval.
155
+ .catch((err) => {
156
+ log.warn('Failed to broadcast active node for presence.', { err: err.message });
157
+ });
158
+ }
159
+ }
160
+ };
161
+
162
+ send();
163
+ // Send at interval to allow peers to expire entries if they become disconnected.
164
+ const interval = setInterval(() => send(), ACTIVE_NODE_BROADCAST_INTERVAL);
165
+ return () => clearInterval(interval);
166
+ },
167
+ ),
168
+ );
169
+
170
+ // Listen for active nodes from other peers in the space.
171
+ subscriptions.add(
172
+ client.spaces.subscribe((spaces) => {
173
+ spaceSubscriptions.clear();
174
+ spaces.forEach((space) => {
175
+ spaceSubscriptions.add(
176
+ space.listen('viewing', (message) => {
177
+ const { added, removed, attended } = message.payload;
178
+
179
+ const identityKey = PublicKey.safeFrom(message.payload.identityKey);
180
+ const currentIdentity = client.halo.identity.get();
181
+ if (
182
+ identityKey &&
183
+ !currentIdentity?.identityKey.equals(identityKey) &&
184
+ Array.isArray(added) &&
185
+ Array.isArray(removed)
186
+ ) {
187
+ added.forEach((id) => {
188
+ if (typeof id === 'string') {
189
+ if (!(id in state.viewersByObject)) {
190
+ state.viewersByObject[id] = new ComplexMap(PublicKey.hash);
191
+ }
192
+ state.viewersByObject[id]!.set(identityKey, {
193
+ lastSeen: Date.now(),
194
+ currentlyAttended: new Set(attended).has(id),
195
+ });
196
+ if (!state.viewersByIdentity.has(identityKey)) {
197
+ state.viewersByIdentity.set(identityKey, new Set());
198
+ }
199
+ state.viewersByIdentity.get(identityKey)!.add(id);
200
+ }
201
+ });
202
+
203
+ removed.forEach((id) => {
204
+ if (typeof id === 'string') {
205
+ state.viewersByObject[id]?.delete(identityKey);
206
+ state.viewersByIdentity.get(identityKey)?.delete(id);
207
+ // It’s okay for these to be empty sets/maps, reduces churn.
208
+ }
209
+ });
210
+ }
211
+ }),
212
+ );
213
+ });
214
+ }).unsubscribe,
215
+ );
216
+
217
+ // Enable edge replication for all spaces.
218
+ try {
219
+ await Promise.all(
220
+ client.spaces.get().map((space) => space.internal.setEdgeReplicationPreference(EdgeReplicationSetting.ENABLED)),
221
+ );
222
+ state.enabledEdgeReplication = true;
223
+ } catch (err) {
224
+ log.catch(err);
225
+ }
226
+
227
+ return contributes(Capabilities.Null, null, () => {
228
+ spaceSubscriptions.clear();
229
+ subscriptions.clear();
230
+ });
231
+ };
@@ -0,0 +1,45 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { effect } from '@preact/signals-core';
6
+
7
+ import { Capabilities, contributes, type PluginsContext } from '@dxos/app-framework';
8
+ import { PublicKey } from '@dxos/keys';
9
+ import { LocalStorageStore } from '@dxos/local-storage';
10
+ import { ComplexMap } from '@dxos/util';
11
+
12
+ import { SpaceCapabilities } from './capabilities';
13
+ import { SPACE_PLUGIN } from '../meta';
14
+ import { type PluginState } from '../types';
15
+
16
+ export default (context: PluginsContext) => {
17
+ const state = new LocalStorageStore<PluginState>(SPACE_PLUGIN, {
18
+ awaiting: undefined,
19
+ spaceNames: {},
20
+ viewersByObject: {},
21
+ // TODO(wittjosiah): Stop using (Complex)Map inside reactive object.
22
+ viewersByIdentity: new ComplexMap(PublicKey.hash),
23
+ sdkMigrationRunning: {},
24
+ navigableCollections: false,
25
+ enabledEdgeReplication: false,
26
+ });
27
+
28
+ state
29
+ .prop({ key: 'spaceNames', type: LocalStorageStore.json<Record<string, string>>() })
30
+ .prop({ key: 'enabledEdgeReplication', type: LocalStorageStore.bool() });
31
+
32
+ const manager = context.requestCapability(Capabilities.PluginManager);
33
+ const unsubscribe = effect(() => {
34
+ // TODO(wittjosiah): Find a way to make this capability-based.
35
+ const enabled = manager.enabled.includes('dxos.org/plugin/stack');
36
+ if (enabled !== state.values.navigableCollections) {
37
+ state.values.navigableCollections = enabled;
38
+ }
39
+ });
40
+
41
+ return contributes(SpaceCapabilities.State, state.values, () => {
42
+ unsubscribe();
43
+ state.close();
44
+ });
45
+ };
@@ -0,0 +1,72 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React, { useCallback, useState } from 'react';
6
+
7
+ import { type ForeignKey, ForeignKeySchema } from '@dxos/echo-schema';
8
+ import { getMeta, type ReactiveEchoObject } from '@dxos/react-client/echo';
9
+ import { IconButton, useTranslation, Separator } from '@dxos/react-ui';
10
+ import { Form } from '@dxos/react-ui-form';
11
+
12
+ import { ForeignKeys } from './ForeignKeys';
13
+ import { SPACE_PLUGIN } from '../../meta';
14
+
15
+ const initialValues = {
16
+ source: '',
17
+ id: '',
18
+ };
19
+
20
+ export type AdvancedObjectSettingsProps = {
21
+ object: ReactiveEchoObject<any>;
22
+ };
23
+
24
+ export const AdvancedObjectSettings = ({ object }: AdvancedObjectSettingsProps) => {
25
+ const { t } = useTranslation(SPACE_PLUGIN);
26
+ const [adding, setAdding] = useState(false);
27
+ const keys = getMeta(object).keys;
28
+
29
+ const handleNew = useCallback(() => setAdding(true), []);
30
+ const handleCancel = useCallback(() => setAdding(false), []);
31
+ const handleSave = useCallback(
32
+ (key: ForeignKey) => {
33
+ const index = keys.findIndex(({ source, id }) => source === key.source && id === key.id);
34
+ if (index === -1) {
35
+ keys.push(key);
36
+ }
37
+ setAdding(false);
38
+ },
39
+ [keys],
40
+ );
41
+ const handleDelete = useCallback(
42
+ (key: ForeignKey) => {
43
+ const index = keys.findIndex(({ source, id }) => source === key.source && id === key.id);
44
+ if (index !== -1) {
45
+ keys.splice(index, 1);
46
+ }
47
+ },
48
+ [keys],
49
+ );
50
+
51
+ // TODO(wittjosiah): This should be wrapped in an "Advanced" accordion.
52
+ return (
53
+ <>
54
+ <Separator />
55
+ <div className='p-2 flex flex-col gap-4'>
56
+ <h2>{t('advanced settings label')}</h2>
57
+ <div className='flex items-center'>
58
+ <h3 className='text-sm font-semibold'>{t('foreign keys')}</h3>
59
+ <div className='grow' />
60
+ <IconButton
61
+ classNames={adding && 'invisible'}
62
+ icon='ph--plus--regular'
63
+ label={t('add key')}
64
+ onClick={handleNew}
65
+ />
66
+ </div>
67
+ {!adding && <ForeignKeys keys={keys} onDelete={handleDelete} />}
68
+ </div>
69
+ {adding && <Form schema={ForeignKeySchema} values={initialValues} onSave={handleSave} onCancel={handleCancel} />}
70
+ </>
71
+ );
72
+ };
@@ -0,0 +1,51 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React, { useCallback } from 'react';
6
+
7
+ import { type ForeignKey } from '@dxos/echo-schema';
8
+ import { IconButton, List, ListItem, useTranslation } from '@dxos/react-ui';
9
+
10
+ import { SPACE_PLUGIN } from '../../meta';
11
+
12
+ export type ForeignKeysProps = {
13
+ keys: ForeignKey[];
14
+ onDelete?: (key: ForeignKey) => void;
15
+ };
16
+
17
+ // TODO(wittjosiah): This is a clone of `TokenManager`. Consider a form variant for arrays of read-only objects.
18
+ export const ForeignKeys = ({ keys, onDelete }: ForeignKeysProps) => {
19
+ return (
20
+ <List classNames='flex flex-col gap-2'>
21
+ {keys.map((key) => (
22
+ <KeyItem key={key.id} forignKey={key} onDelete={onDelete} />
23
+ ))}
24
+ </List>
25
+ );
26
+ };
27
+
28
+ type KeyItemProps = {
29
+ forignKey: ForeignKey;
30
+ onDelete?: (key: ForeignKey) => void;
31
+ };
32
+
33
+ const KeyItem = ({ forignKey, onDelete }: KeyItemProps) => {
34
+ const { t } = useTranslation(SPACE_PLUGIN);
35
+
36
+ const handleDelete = useCallback(() => {
37
+ onDelete?.(forignKey);
38
+ }, [forignKey, onDelete]);
39
+
40
+ return (
41
+ <ListItem.Root classNames='px-2'>
42
+ <ListItem.Heading classNames='flex flex-col grow truncate'>
43
+ <div>{forignKey.source}</div>
44
+ <div className='text-description text-sm truncate'>{forignKey.id}</div>
45
+ </ListItem.Heading>
46
+ <ListItem.Endcap>
47
+ <IconButton iconOnly icon='ph--x--regular' variant='ghost' label={t('delete key')} onClick={handleDelete} />
48
+ </ListItem.Endcap>
49
+ </ListItem.Root>
50
+ );
51
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './AdvancedObjectSettings';
@@ -3,15 +3,16 @@
3
3
  //
4
4
 
5
5
  import { CheckCircle, CircleDashed, CircleNotch } from '@phosphor-icons/react';
6
- import React, { useEffect, useState } from 'react';
6
+ import React, { useCallback, useEffect, useState } from 'react';
7
7
 
8
- import { parseIntentPlugin, useResolvePlugin, parseNavigationPlugin, NavigationAction } from '@dxos/app-framework';
8
+ import { NavigationAction, useIntentDispatcher, createIntent, useCapability, Capabilities } from '@dxos/app-framework';
9
9
  import { useClient } from '@dxos/react-client';
10
10
  import { Filter, fullyQualifiedId, useQuery } from '@dxos/react-client/echo';
11
11
  import { Button, Toast, useTranslation } from '@dxos/react-ui';
12
12
  import { getSize, mx } from '@dxos/react-ui-theme';
13
13
 
14
- import { SpaceAction, SPACE_PLUGIN } from '../meta';
14
+ import { SPACE_PLUGIN } from '../meta';
15
+ import { SpaceAction } from '../types';
15
16
 
16
17
  const WAIT_FOR_OBJECT_TIMEOUT = 180e3; // 3 minutes
17
18
  const TOAST_TIMEOUT = 240e3; // 4 minutes
@@ -21,8 +22,8 @@ export const AwaitingObject = ({ id }: { id: string }) => {
21
22
  const [waiting, setWaiting] = useState(true);
22
23
  const [found, setFound] = useState(false);
23
24
  const { t } = useTranslation(SPACE_PLUGIN);
24
- const intentPlugin = useResolvePlugin(parseIntentPlugin);
25
- const navigationPlugin = useResolvePlugin(parseNavigationPlugin);
25
+ const { dispatchPromise: dispatch } = useIntentDispatcher();
26
+ const location = useCapability(Capabilities.Location);
26
27
 
27
28
  const client = useClient();
28
29
  const objects = useQuery(client.spaces, Filter.all());
@@ -43,26 +44,21 @@ export const AwaitingObject = ({ id }: { id: string }) => {
43
44
  if (objects.findIndex((object) => fullyQualifiedId(object) === id) > -1) {
44
45
  setFound(true);
45
46
 
46
- if (navigationPlugin?.provides.location.active.solo?.[0].id === id) {
47
+ if (location.active.solo?.[0].id === id) {
47
48
  setOpen(false);
48
49
  }
49
50
  }
50
- }, [id, objects, intentPlugin]);
51
+ }, [id, objects, location]);
51
52
 
52
- const handleClose = async () =>
53
- intentPlugin?.provides.intent.dispatch({
54
- plugin: SPACE_PLUGIN,
55
- action: SpaceAction.WAIT_FOR_OBJECT,
56
- data: { id: undefined },
57
- });
53
+ const handleClose = useCallback(
54
+ async () => dispatch(createIntent(SpaceAction.WaitForObject, { id: undefined })),
55
+ [dispatch],
56
+ );
58
57
 
59
- const handleNavigate = () => {
60
- void intentPlugin?.provides.intent.dispatch({
61
- action: NavigationAction.OPEN,
62
- data: { activeParts: { main: [id] } },
63
- });
58
+ const handleNavigate = useCallback(() => {
59
+ void dispatch(createIntent(NavigationAction.Open, { activeParts: { main: [id] } }));
64
60
  void handleClose();
65
- };
61
+ }, [id, handleClose, dispatch]);
66
62
 
67
63
  return (
68
64
  <Toast.Root open={open} duration={TOAST_TIMEOUT} onOpenChange={setOpen}>
@@ -9,11 +9,11 @@ import { Input, useTranslation } from '@dxos/react-ui';
9
9
 
10
10
  import { SPACE_PLUGIN } from '../meta';
11
11
 
12
- export type DefaultObjectSettingsProps = {
12
+ export type BaseObjectSettingsProps = {
13
13
  object: ReactiveEchoObject<any>;
14
14
  };
15
15
 
16
- export const DefaultObjectSettings = ({ object }: DefaultObjectSettingsProps) => {
16
+ export const BaseObjectSettings = ({ object }: BaseObjectSettingsProps) => {
17
17
  const { t } = useTranslation(SPACE_PLUGIN);
18
18
  // TODO(burdon): Standardize forms.
19
19
  return (
@@ -2,20 +2,30 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import { pipe } from 'effect';
5
6
  import React, { useCallback, useRef } from 'react';
6
7
 
7
- import { type MetadataResolver, NavigationAction, useIntentDispatcher } from '@dxos/app-framework';
8
+ import { chain, createIntent, NavigationAction, useIntentDispatcher } from '@dxos/app-framework';
8
9
  import { useClient } from '@dxos/react-client';
9
- import { type AbstractTypedObject, getSpace, isReactiveObject, isSpace, useSpaces } from '@dxos/react-client/echo';
10
+ import {
11
+ getSpace,
12
+ isReactiveObject,
13
+ isSpace,
14
+ type ReactiveObject,
15
+ type TypedObject,
16
+ useSpaces,
17
+ } from '@dxos/react-client/echo';
10
18
  import { Button, Dialog, Icon, useTranslation } from '@dxos/react-ui';
11
19
 
12
20
  import { CreateObjectPanel, type CreateObjectPanelProps } from './CreateObjectPanel';
13
- import { SPACE_PLUGIN, SpaceAction } from '../../meta';
14
- import { CollectionType } from '../../types';
21
+ import { SPACE_PLUGIN } from '../../meta';
22
+ import { CollectionType, SpaceAction } from '../../types';
23
+
24
+ export const CREATE_OBJECT_DIALOG = `${SPACE_PLUGIN}/CreateObjectDialog`;
15
25
 
16
26
  export type CreateObjectDialogProps = Pick<CreateObjectPanelProps, 'schemas' | 'target' | 'typename' | 'name'> & {
17
- resolve?: MetadataResolver;
18
- navigableCollections?: boolean;
27
+ resolve?: (typename: string) => Record<string, any>;
28
+ shouldNavigate?: (object: ReactiveObject<any>) => boolean;
19
29
  };
20
30
 
21
31
  export const CreateObjectDialog = ({
@@ -23,28 +33,30 @@ export const CreateObjectDialog = ({
23
33
  target,
24
34
  typename,
25
35
  name,
26
- navigableCollections,
36
+ shouldNavigate: _shouldNavigate,
27
37
  resolve,
28
38
  }: CreateObjectDialogProps) => {
29
39
  const closeRef = useRef<HTMLButtonElement | null>(null);
30
40
  const { t } = useTranslation(SPACE_PLUGIN);
31
41
  const client = useClient();
32
42
  const spaces = useSpaces();
33
- const dispatch = useIntentDispatcher();
43
+ const { dispatchPromise: dispatch } = useIntentDispatcher();
34
44
 
35
45
  const handleCreateObject = useCallback(
36
46
  async ({
37
47
  schema,
38
48
  target: _target,
39
- name,
49
+ data,
40
50
  }: {
41
- schema: AbstractTypedObject;
51
+ schema: TypedObject;
42
52
  target: CreateObjectPanelProps['target'];
43
- name?: string;
53
+ data?: Record<string, any>;
44
54
  }) => {
45
- const target = isSpace(_target) ? (_target.properties[CollectionType.typename] as CollectionType) : _target;
46
- const createObjectAction = resolve?.(schema.typename)?.createObject;
47
- if (!createObjectAction || !target) {
55
+ const target = isSpace(_target)
56
+ ? (_target.properties[CollectionType.typename]?.target as CollectionType | undefined)
57
+ : _target;
58
+ const createObjectIntent = resolve?.(schema.typename)?.createObject;
59
+ if (!createObjectIntent || !target) {
48
60
  // TODO(wittjosiah): UI feedback.
49
61
  return;
50
62
  }
@@ -53,17 +65,16 @@ export const CreateObjectDialog = ({
53
65
  closeRef.current?.click();
54
66
 
55
67
  const space = isSpace(target) ? target : getSpace(target);
56
- const result = await dispatch({ action: createObjectAction, data: { name, space } });
57
- const object = result?.data;
68
+ const result = await dispatch(createObjectIntent(data, { space }));
69
+ const object = result.data?.object;
58
70
  if (isReactiveObject(object)) {
59
- await dispatch([
60
- {
61
- plugin: SPACE_PLUGIN,
62
- action: SpaceAction.ADD_OBJECT,
63
- data: { target, object },
64
- },
65
- ...(!(object instanceof CollectionType) || navigableCollections ? [{ action: NavigationAction.OPEN }] : []),
66
- ]);
71
+ const addObjectIntent = createIntent(SpaceAction.AddObject, { target, object });
72
+ const shouldNavigate = _shouldNavigate ?? (() => true);
73
+ if (shouldNavigate(object)) {
74
+ await dispatch(pipe(addObjectIntent, chain(NavigationAction.Open, {})));
75
+ } else {
76
+ await dispatch(addObjectIntent);
77
+ }
67
78
  }
68
79
  },
69
80
  [dispatch, resolve],
@@ -73,7 +84,7 @@ export const CreateObjectDialog = ({
73
84
  // TODO(wittjosiah): The tablist dialog pattern is copied from @dxos/plugin-manager.
74
85
  // Consider factoring it out to the tabs package.
75
86
  <Dialog.Content classNames='p-0 bs-content min-bs-[15rem] max-bs-full md:max-is-[40rem] overflow-hidden'>
76
- <div role='none' className='flex justify-between pbs-3 pis-2 pie-3 @md:pbs-4 @md:pis-4 @md:pie-5'>
87
+ <div role='none' className='flex justify-between pbs-2 pis-2 pie-2 @md:pbs-4 @md:pis-4 @md:pie-4'>
77
88
  <Dialog.Title>{t('create object dialog title')}</Dialog.Title>
78
89
  <Dialog.Close asChild>
79
90
  <Button ref={closeRef} density='fine' variant='ghost' autoFocus>