@dxos/plugin-client 0.8.2-main.fbd8ed0 → 0.8.2-staging.7ac8446

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 (134) hide show
  1. package/dist/lib/browser/{app-graph-builder-ROTCC3C4.mjs → app-graph-builder-WAS4YZNA.mjs} +31 -35
  2. package/dist/lib/browser/app-graph-builder-WAS4YZNA.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-AN7FJKIJ.mjs +105 -0
  4. package/dist/lib/browser/chunk-AN7FJKIJ.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-7OW22D2O.mjs → chunk-XZ4TQC56.mjs} +25 -29
  6. package/dist/lib/browser/chunk-XZ4TQC56.mjs.map +7 -0
  7. package/dist/lib/browser/{client-ICXXK55V.mjs → client-LV67Q3R7.mjs} +1 -1
  8. package/dist/lib/browser/client-LV67Q3R7.mjs.map +7 -0
  9. package/dist/lib/browser/index.mjs +9 -9
  10. package/dist/lib/browser/index.mjs.map +3 -3
  11. package/dist/lib/browser/{intent-resolver-L4STUWGK.mjs → intent-resolver-NCISOZW4.mjs} +10 -10
  12. package/dist/lib/browser/intent-resolver-NCISOZW4.mjs.map +7 -0
  13. package/dist/lib/browser/meta.json +1 -1
  14. package/dist/lib/browser/migrations-3DWFOL5Q.mjs +21 -0
  15. package/dist/lib/browser/migrations-3DWFOL5Q.mjs.map +7 -0
  16. package/dist/lib/browser/{react-surface-VXFG7BEM.mjs → react-surface-J7SZR2J6.mjs} +3 -3
  17. package/dist/lib/browser/schema-JQAT6Q7S.mjs +23 -0
  18. package/dist/lib/browser/schema-JQAT6Q7S.mjs.map +7 -0
  19. package/dist/lib/browser/types.mjs +1 -1
  20. package/dist/lib/node/{app-graph-builder-DOHFMHXU.cjs → app-graph-builder-PXOJZJO5.cjs} +40 -44
  21. package/dist/lib/node/app-graph-builder-PXOJZJO5.cjs.map +7 -0
  22. package/dist/lib/node/{chunk-FABH7MJ3.cjs → chunk-3JE5BFCT.cjs} +25 -29
  23. package/dist/lib/node/chunk-3JE5BFCT.cjs.map +7 -0
  24. package/dist/lib/node/{chunk-J5SQ5Q22.cjs → chunk-FROKBEVH.cjs} +47 -47
  25. package/dist/lib/node/chunk-FROKBEVH.cjs.map +7 -0
  26. package/dist/lib/node/{client-OUBG63HJ.cjs → client-BKTGRBYM.cjs} +4 -4
  27. package/dist/lib/node/client-BKTGRBYM.cjs.map +7 -0
  28. package/dist/lib/node/index.cjs +9 -9
  29. package/dist/lib/node/index.cjs.map +3 -3
  30. package/dist/lib/node/{intent-resolver-IDLAVEHS.cjs → intent-resolver-U2576ALY.cjs} +29 -29
  31. package/dist/lib/node/intent-resolver-U2576ALY.cjs.map +7 -0
  32. package/dist/lib/node/meta.json +1 -1
  33. package/dist/lib/node/{migrations-ZI52SX4M.cjs → migrations-TGNI4FVL.cjs} +9 -11
  34. package/dist/lib/node/migrations-TGNI4FVL.cjs.map +7 -0
  35. package/dist/lib/node/{react-surface-UGXE6OAD.cjs → react-surface-BSTIXMER.cjs} +24 -24
  36. package/dist/lib/node/{schema-defs-BYGBDKOU.cjs → schema-INQ72F54.cjs} +11 -13
  37. package/dist/lib/node/schema-INQ72F54.cjs.map +7 -0
  38. package/dist/lib/node/types.cjs +3 -3
  39. package/dist/lib/node/types.cjs.map +1 -1
  40. package/dist/lib/node-esm/{app-graph-builder-XMXJG5YT.mjs → app-graph-builder-KIN6NWFI.mjs} +31 -35
  41. package/dist/lib/node-esm/app-graph-builder-KIN6NWFI.mjs.map +7 -0
  42. package/dist/lib/node-esm/chunk-AD5W5QCQ.mjs +106 -0
  43. package/dist/lib/node-esm/chunk-AD5W5QCQ.mjs.map +7 -0
  44. package/dist/lib/node-esm/{chunk-34X2AMVA.mjs → chunk-LZN2UNN7.mjs} +25 -29
  45. package/dist/lib/node-esm/chunk-LZN2UNN7.mjs.map +7 -0
  46. package/dist/lib/node-esm/{client-YJNEHNUV.mjs → client-QQD6WFOB.mjs} +1 -1
  47. package/dist/lib/node-esm/client-QQD6WFOB.mjs.map +7 -0
  48. package/dist/lib/node-esm/index.mjs +9 -9
  49. package/dist/lib/node-esm/index.mjs.map +3 -3
  50. package/dist/lib/node-esm/{intent-resolver-73FZIXX5.mjs → intent-resolver-G25U4UGI.mjs} +10 -10
  51. package/dist/lib/node-esm/intent-resolver-G25U4UGI.mjs.map +7 -0
  52. package/dist/lib/node-esm/meta.json +1 -1
  53. package/dist/lib/node-esm/{migrations-GHO52HKW.mjs → migrations-IVGATGZR.mjs} +6 -8
  54. package/dist/lib/node-esm/migrations-IVGATGZR.mjs.map +7 -0
  55. package/dist/lib/node-esm/{react-surface-R65DYUV6.mjs → react-surface-ZPI7T4EM.mjs} +3 -3
  56. package/dist/lib/node-esm/schema-OK7HY3JJ.mjs +24 -0
  57. package/dist/lib/node-esm/schema-OK7HY3JJ.mjs.map +7 -0
  58. package/dist/lib/node-esm/types.mjs +1 -1
  59. package/dist/types/src/ClientPlugin.d.ts.map +1 -1
  60. package/dist/types/src/capabilities/app-graph-builder.d.ts +179 -2
  61. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  62. package/dist/types/src/capabilities/client.d.ts +2 -2
  63. package/dist/types/src/capabilities/client.d.ts.map +1 -1
  64. package/dist/types/src/capabilities/index.d.ts +181 -5
  65. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  66. package/dist/types/src/capabilities/intent-resolver.d.ts +2 -2
  67. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  68. package/dist/types/src/capabilities/migrations.d.ts +2 -2
  69. package/dist/types/src/capabilities/migrations.d.ts.map +1 -1
  70. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  71. package/dist/types/src/capabilities/schema.d.ts +4 -0
  72. package/dist/types/src/capabilities/schema.d.ts.map +1 -0
  73. package/dist/types/src/components/DevicesContainer.d.ts +3 -4
  74. package/dist/types/src/components/DevicesContainer.d.ts.map +1 -1
  75. package/dist/types/src/components/DevicesContainer.stories.d.ts.map +1 -1
  76. package/dist/types/src/components/JoinDialog.d.ts.map +1 -1
  77. package/dist/types/src/components/ProfileContainer.d.ts.map +1 -1
  78. package/dist/types/src/components/RecoveryCodeDialog.d.ts.map +1 -1
  79. package/dist/types/src/components/RecoveryCredentialsContainer.d.ts.map +1 -1
  80. package/dist/types/src/components/ResetDialog.d.ts.map +1 -1
  81. package/dist/types/src/components/ResetDialog.stories.d.ts.map +1 -1
  82. package/dist/types/src/types.d.ts +53 -53
  83. package/dist/types/src/types.d.ts.map +1 -1
  84. package/dist/types/tsconfig.tsbuildinfo +1 -1
  85. package/package.json +25 -31
  86. package/src/ClientPlugin.ts +2 -2
  87. package/src/capabilities/app-graph-builder.ts +76 -94
  88. package/src/capabilities/client.ts +2 -2
  89. package/src/capabilities/index.ts +1 -1
  90. package/src/capabilities/intent-resolver.ts +9 -9
  91. package/src/capabilities/migrations.ts +11 -14
  92. package/src/capabilities/schema.ts +26 -0
  93. package/src/components/DevicesContainer.stories.tsx +3 -7
  94. package/src/components/DevicesContainer.tsx +61 -85
  95. package/src/components/ProfileContainer.stories.tsx +1 -1
  96. package/src/components/ProfileContainer.tsx +21 -30
  97. package/src/components/RecoveryCodeDialog.stories.tsx +1 -1
  98. package/src/components/RecoveryCredentialsContainer.stories.tsx +2 -2
  99. package/src/components/RecoveryCredentialsContainer.tsx +46 -48
  100. package/src/components/ResetDialog.stories.tsx +1 -7
  101. package/src/translations.ts +2 -2
  102. package/src/types.ts +46 -50
  103. package/dist/lib/browser/app-graph-builder-ROTCC3C4.mjs.map +0 -7
  104. package/dist/lib/browser/chunk-7OW22D2O.mjs.map +0 -7
  105. package/dist/lib/browser/chunk-XPHDZ277.mjs +0 -105
  106. package/dist/lib/browser/chunk-XPHDZ277.mjs.map +0 -7
  107. package/dist/lib/browser/client-ICXXK55V.mjs.map +0 -7
  108. package/dist/lib/browser/intent-resolver-L4STUWGK.mjs.map +0 -7
  109. package/dist/lib/browser/migrations-DYDZ7DVP.mjs +0 -23
  110. package/dist/lib/browser/migrations-DYDZ7DVP.mjs.map +0 -7
  111. package/dist/lib/browser/schema-defs-QVXP2JGN.mjs +0 -25
  112. package/dist/lib/browser/schema-defs-QVXP2JGN.mjs.map +0 -7
  113. package/dist/lib/node/app-graph-builder-DOHFMHXU.cjs.map +0 -7
  114. package/dist/lib/node/chunk-FABH7MJ3.cjs.map +0 -7
  115. package/dist/lib/node/chunk-J5SQ5Q22.cjs.map +0 -7
  116. package/dist/lib/node/client-OUBG63HJ.cjs.map +0 -7
  117. package/dist/lib/node/intent-resolver-IDLAVEHS.cjs.map +0 -7
  118. package/dist/lib/node/migrations-ZI52SX4M.cjs.map +0 -7
  119. package/dist/lib/node/schema-defs-BYGBDKOU.cjs.map +0 -7
  120. package/dist/lib/node-esm/app-graph-builder-XMXJG5YT.mjs.map +0 -7
  121. package/dist/lib/node-esm/chunk-34X2AMVA.mjs.map +0 -7
  122. package/dist/lib/node-esm/chunk-V6SJNSKG.mjs +0 -106
  123. package/dist/lib/node-esm/chunk-V6SJNSKG.mjs.map +0 -7
  124. package/dist/lib/node-esm/client-YJNEHNUV.mjs.map +0 -7
  125. package/dist/lib/node-esm/intent-resolver-73FZIXX5.mjs.map +0 -7
  126. package/dist/lib/node-esm/migrations-GHO52HKW.mjs.map +0 -7
  127. package/dist/lib/node-esm/schema-defs-O4OHIIBN.mjs +0 -26
  128. package/dist/lib/node-esm/schema-defs-O4OHIIBN.mjs.map +0 -7
  129. package/dist/types/src/capabilities/schema-defs.d.ts +0 -4
  130. package/dist/types/src/capabilities/schema-defs.d.ts.map +0 -1
  131. package/src/capabilities/schema-defs.ts +0 -29
  132. /package/dist/lib/browser/{react-surface-VXFG7BEM.mjs.map → react-surface-J7SZR2J6.mjs.map} +0 -0
  133. /package/dist/lib/node/{react-surface-UGXE6OAD.cjs.map → react-surface-BSTIXMER.cjs.map} +0 -0
  134. /package/dist/lib/node-esm/{react-surface-R65DYUV6.mjs.map → react-surface-ZPI7T4EM.mjs.map} +0 -0
@@ -2,112 +2,94 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Rx } from '@effect-rx/rx-react';
6
- import { Option, pipe } from 'effect';
7
-
8
5
  import { createIntent } from '@dxos/app-framework';
9
- import { Capabilities, contributes, type PluginContext } from '@dxos/app-framework';
10
- import { createExtension, rxFromObservable, ROOT_ID } from '@dxos/plugin-graph';
6
+ import { Capabilities, contributes, type PluginsContext } from '@dxos/app-framework';
7
+ import { createExtension, toSignal, type Node } from '@dxos/plugin-graph';
11
8
  import { ConnectionState } from '@dxos/react-client/mesh';
12
9
 
13
10
  import { ClientCapabilities } from './capabilities';
14
11
  import { CLIENT_PLUGIN } from '../meta';
15
12
  import { Account, ClientAction } from '../types';
16
13
 
17
- export default (context: PluginContext) =>
14
+ export default (context: PluginsContext) =>
18
15
  contributes(
19
16
  Capabilities.AppGraphBuilder,
20
17
  createExtension({
21
18
  id: CLIENT_PLUGIN,
22
- actions: (node) =>
23
- Rx.make((get) =>
24
- pipe(
25
- get(node),
26
- Option.flatMap((node) => (node.id === ROOT_ID ? Option.some(node) : Option.none())),
27
- Option.map(() => {
28
- return [
29
- {
30
- id: `${CLIENT_PLUGIN}/open-user-account`,
31
- data: async () => {
32
- const { dispatchPromise: dispatch } = context.getCapability(Capabilities.IntentDispatcher);
33
- await dispatch(createIntent(ClientAction.ShareIdentity));
34
- },
35
- properties: {
36
- label: ['open user account label', { ns: CLIENT_PLUGIN }],
37
- icon: 'ph--user--regular',
38
- disposition: 'menu',
39
- keyBinding: {
40
- macos: 'meta+shift+.',
41
- // TODO(wittjosiah): Test on windows to see if it behaves the same as linux.
42
- windows: 'alt+shift+.',
43
- linux: 'alt+shift+>',
44
- },
45
- },
46
- },
47
- ];
48
- }),
49
- Option.getOrElse(() => []),
50
- ),
51
- ),
52
- connector: (node) =>
53
- Rx.make((get) =>
54
- pipe(
55
- get(node),
56
- Option.flatMap((node) => (node.id === ROOT_ID ? Option.some(node) : Option.none())),
57
- Option.map(() => {
58
- const client = context.getCapability(ClientCapabilities.Client);
59
- const identity = get(rxFromObservable(client.halo.identity));
60
- const status = get(rxFromObservable(client.mesh.networkStatus));
19
+ filter: (node): node is Node<null> => node.id === 'root',
20
+ actions: () => [
21
+ {
22
+ id: `${CLIENT_PLUGIN}/open-user-account`,
23
+ data: async () => {
24
+ const { dispatchPromise: dispatch } = context.requestCapability(Capabilities.IntentDispatcher);
25
+ await dispatch(createIntent(ClientAction.ShareIdentity));
26
+ },
27
+ properties: {
28
+ label: ['open user account label', { ns: CLIENT_PLUGIN }],
29
+ icon: 'ph--user--regular',
30
+ keyBinding: {
31
+ macos: 'meta+shift+.',
32
+ // TODO(wittjosiah): Test on windows to see if it behaves the same as linux.
33
+ windows: 'alt+shift+.',
34
+ linux: 'alt+shift+>',
35
+ },
36
+ },
37
+ },
38
+ ],
39
+ connector: () => {
40
+ const client = context.requestCapability(ClientCapabilities.Client);
41
+ const identity = client.halo.identity.get();
42
+ const status = toSignal(
43
+ (onChange) => client.mesh.networkStatus.subscribe(() => onChange()).unsubscribe,
44
+ () => client.mesh.networkStatus.get(),
45
+ );
61
46
 
62
- return [
63
- {
64
- id: Account.id,
65
- type: CLIENT_PLUGIN,
66
- properties: {
67
- label: ['account label', { ns: CLIENT_PLUGIN }],
68
- icon: 'ph--user--regular',
69
- disposition: 'user-account',
70
- // NOTE: This currently needs to be the identity key because the fallback is generated from hex.
71
- userId: identity?.identityKey.toHex(),
72
- hue: identity?.profile?.data?.hue,
73
- emoji: identity?.profile?.data?.emoji,
74
- status: status.swarm === ConnectionState.OFFLINE ? 'error' : 'active',
75
- },
76
- nodes: [
77
- {
78
- id: Account.Profile,
79
- data: Account.Profile,
80
- type: CLIENT_PLUGIN,
81
- properties: {
82
- label: ['profile label', { ns: CLIENT_PLUGIN }],
83
- icon: 'ph--user--regular',
84
- },
85
- },
86
- {
87
- id: Account.Devices,
88
- data: Account.Devices,
89
- type: CLIENT_PLUGIN,
90
- properties: {
91
- label: ['devices label', { ns: CLIENT_PLUGIN }],
92
- icon: 'ph--devices--regular',
93
- testId: 'clientPlugin.devices',
94
- },
95
- },
96
- {
97
- id: Account.Security,
98
- data: Account.Security,
99
- type: CLIENT_PLUGIN,
100
- properties: {
101
- label: ['security label', { ns: CLIENT_PLUGIN }],
102
- icon: 'ph--key--regular',
103
- },
104
- },
105
- ],
47
+ return [
48
+ {
49
+ id: Account.id,
50
+ type: CLIENT_PLUGIN,
51
+ properties: {
52
+ label: ['account label', { ns: CLIENT_PLUGIN }],
53
+ icon: 'ph--user--regular',
54
+ disposition: 'user-account',
55
+ // NOTE: This currently needs to be the identity key because the fallback is generated from hex.
56
+ userId: identity?.identityKey.toHex(),
57
+ hue: identity?.profile?.data?.hue,
58
+ emoji: identity?.profile?.data?.emoji,
59
+ status: status?.swarm === ConnectionState.OFFLINE ? 'error' : 'active',
60
+ },
61
+ nodes: [
62
+ {
63
+ id: Account.Profile,
64
+ data: Account.Profile,
65
+ type: CLIENT_PLUGIN,
66
+ properties: {
67
+ label: ['profile label', { ns: CLIENT_PLUGIN }],
68
+ icon: 'ph--user--regular',
69
+ },
70
+ },
71
+ {
72
+ id: Account.Devices,
73
+ data: Account.Devices,
74
+ type: CLIENT_PLUGIN,
75
+ properties: {
76
+ label: ['devices label', { ns: CLIENT_PLUGIN }],
77
+ icon: 'ph--devices--regular',
78
+ testId: 'clientPlugin.devices',
79
+ },
80
+ },
81
+ {
82
+ id: Account.Security,
83
+ data: Account.Security,
84
+ type: CLIENT_PLUGIN,
85
+ properties: {
86
+ label: ['security label', { ns: CLIENT_PLUGIN }],
87
+ icon: 'ph--key--regular',
106
88
  },
107
- ];
108
- }),
109
- Option.getOrElse(() => []),
110
- ),
111
- ),
89
+ },
90
+ ],
91
+ },
92
+ ];
93
+ },
112
94
  }),
113
95
  );
@@ -2,7 +2,7 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { contributes, type PluginContext } from '@dxos/app-framework';
5
+ import { contributes, type PluginsContext } from '@dxos/app-framework';
6
6
  import { Client } from '@dxos/react-client';
7
7
 
8
8
  import { ClientCapabilities } from './capabilities';
@@ -10,7 +10,7 @@ import { ClientEvents } from '../events';
10
10
  import { type ClientPluginOptions } from '../types';
11
11
 
12
12
  type ClientCapabilityOptions = Omit<ClientPluginOptions, 'appKey' | 'invitationUrl' | 'invitationParam' | 'onReset'> & {
13
- context: PluginContext;
13
+ context: PluginsContext;
14
14
  };
15
15
 
16
16
  export default async ({ context, onClientInitialized, ...options }: ClientCapabilityOptions) => {
@@ -10,6 +10,6 @@ export const IntentResolver = lazy(async () => import('./intent-resolver'));
10
10
  export const Migrations = lazy(async () => import('./migrations'));
11
11
  export const ReactContext = lazy(async () => import('./react-context'));
12
12
  export const ReactSurface = lazy(async () => import('./react-surface'));
13
- export const SchemaDefs = lazy(async () => import('./schema-defs'));
13
+ export const Schema = lazy(async () => import('./schema'));
14
14
 
15
15
  export * from './capabilities';
@@ -11,7 +11,7 @@ import {
11
11
  createIntent,
12
12
  createResolver,
13
13
  LayoutAction,
14
- type PluginContext,
14
+ type PluginsContext,
15
15
  } from '@dxos/app-framework';
16
16
  import { invariant } from '@dxos/invariant';
17
17
  import { ObservabilityAction } from '@dxos/plugin-observability/types';
@@ -24,7 +24,7 @@ import { ClientEvents } from '../events';
24
24
  import { Account, ClientAction } from '../types';
25
25
 
26
26
  type IntentResolverOptions = {
27
- context: PluginContext;
27
+ context: PluginsContext;
28
28
  appName?: string;
29
29
  };
30
30
 
@@ -35,8 +35,8 @@ export default ({ context, appName = 'Composer' }: IntentResolverOptions) =>
35
35
  createResolver({
36
36
  intent: ClientAction.CreateIdentity,
37
37
  resolve: async () => {
38
- const manager = context.getCapability(Capabilities.PluginManager);
39
- const client = context.getCapability(ClientCapabilities.Client);
38
+ const manager = context.requestCapability(Capabilities.PluginManager);
39
+ const client = context.requestCapability(ClientCapabilities.Client);
40
40
  const data = await client.halo.createIdentity();
41
41
  await manager.activate(ClientEvents.IdentityCreated);
42
42
  return {
@@ -132,7 +132,7 @@ export default ({ context, appName = 'Composer' }: IntentResolverOptions) =>
132
132
  createResolver({
133
133
  intent: ClientAction.CreateAgent,
134
134
  resolve: async () => {
135
- const client = context.getCapability(ClientCapabilities.Client);
135
+ const client = context.requestCapability(ClientCapabilities.Client);
136
136
  invariant(client.services.services.EdgeAgentService, 'Missing EdgeAgentService');
137
137
  await client.services.services.EdgeAgentService.createAgent(null as any, { timeout: 10_000 });
138
138
  },
@@ -140,7 +140,7 @@ export default ({ context, appName = 'Composer' }: IntentResolverOptions) =>
140
140
  createResolver({
141
141
  intent: ClientAction.CreateRecoveryCode,
142
142
  resolve: async () => {
143
- const client = context.getCapability(ClientCapabilities.Client);
143
+ const client = context.requestCapability(ClientCapabilities.Client);
144
144
  invariant(client.services.services.IdentityService, 'IdentityService not available');
145
145
  // TODO(wittjosiah): This needs a proper api.
146
146
  const { recoveryCode } = await client.services.services.IdentityService.createRecoveryCredential({});
@@ -162,7 +162,7 @@ export default ({ context, appName = 'Composer' }: IntentResolverOptions) =>
162
162
  createResolver({
163
163
  intent: ClientAction.CreatePasskey,
164
164
  resolve: async () => {
165
- const client = context.getCapability(ClientCapabilities.Client);
165
+ const client = context.requestCapability(ClientCapabilities.Client);
166
166
  const identity = client.halo.identity.get();
167
167
  invariant(identity, 'Identity not available');
168
168
 
@@ -207,7 +207,7 @@ export default ({ context, appName = 'Composer' }: IntentResolverOptions) =>
207
207
  createResolver({
208
208
  intent: ClientAction.RedeemPasskey,
209
209
  resolve: async () => {
210
- const client = context.getCapability(ClientCapabilities.Client);
210
+ const client = context.requestCapability(ClientCapabilities.Client);
211
211
  // TODO(wittjosiah): This needs a proper api.
212
212
  invariant(client.services.services.IdentityService, 'IdentityService not available');
213
213
  const { deviceKey, controlFeedKey, challenge } =
@@ -238,7 +238,7 @@ export default ({ context, appName = 'Composer' }: IntentResolverOptions) =>
238
238
  createResolver({
239
239
  intent: ClientAction.RedeemToken,
240
240
  resolve: async (data) => {
241
- const client = context.getCapability(ClientCapabilities.Client);
241
+ const client = context.requestCapability(ClientCapabilities.Client);
242
242
  // TODO(wittjosiah): This needs a proper api.
243
243
  invariant(client.services.services.IdentityService, 'IdentityService not available');
244
244
  await client.services.services.IdentityService.recoverIdentity(
@@ -2,24 +2,21 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Capabilities, contributes, type PluginContext } from '@dxos/app-framework';
5
+ import { effect } from '@preact/signals-core';
6
+
7
+ import { Capabilities, contributes, type PluginsContext } from '@dxos/app-framework';
6
8
 
7
9
  import { ClientCapabilities } from './capabilities';
8
10
 
9
- export default (context: PluginContext) => {
10
- const registry = context.getCapability(Capabilities.RxRegistry);
11
- const client = context.getCapability(ClientCapabilities.Client);
11
+ export default (context: PluginsContext) => {
12
+ const client = context.requestCapability(ClientCapabilities.Client);
12
13
 
13
14
  // NOTE: Migrations are currently unidirectional and idempotent.
14
- const cancel = registry.subscribe(
15
- context.capabilities(ClientCapabilities.Migration),
16
- (_migrations) => {
17
- const migrations = Array.from(new Set(_migrations.flat()));
18
- const spaces = client.spaces.get();
19
- void Promise.all(spaces.map((space) => space.db.runMigrations(migrations)));
20
- },
21
- { immediate: true },
22
- );
15
+ const unsubscribe = effect(() => {
16
+ const migrations = Array.from(new Set(context.requestCapabilities(ClientCapabilities.Migration).flat()));
17
+ const spaces = client.spaces.get();
18
+ void Promise.all(spaces.map((space) => space.db.runMigrations(migrations)));
19
+ });
23
20
 
24
- return contributes(Capabilities.Null, null, () => cancel());
21
+ return contributes(Capabilities.Null, null, () => unsubscribe());
25
22
  };
@@ -0,0 +1,26 @@
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 { type TypedObject } from '@dxos/echo-schema';
9
+
10
+ import { ClientCapabilities } from './capabilities';
11
+
12
+ export default (context: PluginsContext) => {
13
+ const client = context.requestCapability(ClientCapabilities.Client);
14
+
15
+ // TODO(wittjosiah): Unregister schemas when they are disabled.
16
+ let previous: TypedObject[] = [];
17
+ const unsubscribe = effect(() => {
18
+ const schemas = Array.from(new Set(context.requestCapabilities(ClientCapabilities.Schema).flat()));
19
+ // TODO(wittjosiah): Filter out schemas which the client has already registered.
20
+ const newSchemas = schemas.filter((schema) => !previous.includes(schema));
21
+ previous = schemas;
22
+ client.addTypes(newSchemas);
23
+ });
24
+
25
+ return contributes(Capabilities.Null, null, () => unsubscribe());
26
+ };
@@ -21,16 +21,16 @@ const meta: Meta = {
21
21
  decorators: [
22
22
  withPluginManager({
23
23
  plugins: [
24
+ IntentPlugin(),
24
25
  ClientPlugin({
25
26
  onClientInitialized: async (_, client) => {
26
27
  await client.halo.createIdentity();
27
28
  },
28
29
  }),
29
- IntentPlugin(),
30
30
  ],
31
31
  }),
32
32
  withTheme,
33
- withLayout(),
33
+ withLayout({ tooltips: true }),
34
34
  ],
35
35
  parameters: {
36
36
  layout: 'fullscreen',
@@ -42,8 +42,4 @@ export default meta;
42
42
 
43
43
  type Story = StoryObj<typeof DevicesContainer>;
44
44
 
45
- export const Default: Story = {
46
- args: {
47
- createInvitationUrl: () => 'https://example.com',
48
- },
49
- };
45
+ export const Default: Story = {};
@@ -13,14 +13,7 @@ import { type Device, useDevices } from '@dxos/react-client/halo';
13
13
  import { type CancellableInvitationObservable, Invitation, InvitationEncoder } from '@dxos/react-client/invitations';
14
14
  import { useNetworkStatus } from '@dxos/react-client/mesh';
15
15
  import { Button, Clipboard, IconButton, List, useId, useTranslation } from '@dxos/react-ui';
16
- import {
17
- ControlFrame,
18
- ControlFrameItem,
19
- ControlGroup,
20
- ControlItem,
21
- ControlPage,
22
- ControlSection,
23
- } from '@dxos/react-ui-form';
16
+ import { ControlFrame, ControlFrameItem, ControlGroup, ControlItem, ControlSection } from '@dxos/react-ui-form';
24
17
  import { StackItem } from '@dxos/react-ui-stack';
25
18
  import { getSize, mx } from '@dxos/react-ui-theme';
26
19
  import { AuthCode, Centered, DeviceListItem, Emoji, Viewport } from '@dxos/shell/react';
@@ -29,11 +22,11 @@ import { hexToEmoji } from '@dxos/util';
29
22
  import { CLIENT_PLUGIN } from '../meta';
30
23
  import { ClientAction } from '../types';
31
24
 
32
- export type DevicesContainerProps = {
33
- createInvitationUrl?: (invitationCode: string) => string;
34
- };
35
-
36
- export const DevicesContainer = ({ createInvitationUrl }: DevicesContainerProps) => {
25
+ export const DevicesContainer = ({
26
+ createInvitationUrl,
27
+ }: {
28
+ createInvitationUrl: (invitationCode: string) => string;
29
+ }) => {
37
30
  const { t } = useTranslation('os');
38
31
  const { dispatchPromise: dispatch } = useIntentDispatcher();
39
32
  const devices = useDevices();
@@ -53,69 +46,57 @@ export const DevicesContainer = ({ createInvitationUrl }: DevicesContainerProps)
53
46
 
54
47
  return (
55
48
  <Clipboard.Provider>
56
- <StackItem.Content classNames='block overflow-y-auto'>
57
- <ControlPage>
58
- <ControlSection
59
- title={t('devices verbose label', { ns: CLIENT_PLUGIN })}
60
- description={t('devices description', { ns: CLIENT_PLUGIN })}
61
- >
62
- <ControlFrame>
63
- <ControlFrameItem title={t('devices label', { ns: CLIENT_PLUGIN })}>
64
- <List>
65
- {devices.map((device: Device) => {
66
- return (
67
- <DeviceListItem
68
- key={device.deviceKey.toHex()}
69
- device={device}
70
- connectionState={connectionState}
71
- />
72
- );
73
- })}
74
- </List>
75
- </ControlFrameItem>
76
- {createInvitationUrl && (
77
- <ControlFrameItem title='Add device'>
78
- <DeviceInvitation createInvitationUrl={createInvitationUrl} />
79
- </ControlFrameItem>
80
- )}
81
- </ControlFrame>
82
- </ControlSection>
83
- <ControlSection
84
- title={t('danger zone title', { ns: CLIENT_PLUGIN })}
85
- description={t('danger zone description', { ns: CLIENT_PLUGIN })}
86
- >
87
- <ControlGroup>
88
- <ControlItem
89
- title={t('reset device label')}
90
- description={t('reset device description', { ns: CLIENT_PLUGIN })}
91
- >
92
- <Button variant='destructive' onClick={handleResetStorage} data-testid='devicesContainer.reset'>
93
- {t('reset device label')}
94
- </Button>
95
- </ControlItem>
96
- <ControlItem
97
- title={t('recover identity label')}
98
- description={t('recover identity description', { ns: CLIENT_PLUGIN })}
99
- >
100
- <Button variant='destructive' onClick={handleRecover} data-testid='devicesContainer.recover'>
101
- {t('recover identity label')}
102
- </Button>
103
- </ControlItem>
104
- <ControlItem
105
- title={t('join new identity label')}
106
- description={t('join new identity description', { ns: CLIENT_PLUGIN })}
107
- >
108
- <Button
109
- variant='destructive'
110
- onClick={handleJoinNewIdentity}
111
- data-testid='devicesContainer.joinExisting'
112
- >
113
- {t('join new identity label')}
114
- </Button>
115
- </ControlItem>
116
- </ControlGroup>
117
- </ControlSection>
118
- </ControlPage>
49
+ <StackItem.Content classNames='p-2 block overflow-y-auto'>
50
+ <ControlSection
51
+ title={t('devices verbose label', { ns: CLIENT_PLUGIN })}
52
+ description={t('devices description', { ns: CLIENT_PLUGIN })}
53
+ >
54
+ <ControlFrame>
55
+ <ControlFrameItem title={t('devices label', { ns: CLIENT_PLUGIN })}>
56
+ <List>
57
+ {devices.map((device: Device) => {
58
+ return (
59
+ <DeviceListItem key={device.deviceKey.toHex()} device={device} connectionState={connectionState} />
60
+ );
61
+ })}
62
+ </List>
63
+ </ControlFrameItem>
64
+ <ControlFrameItem title='Add device'>
65
+ <DeviceInvitation createInvitationUrl={createInvitationUrl} />
66
+ </ControlFrameItem>
67
+ </ControlFrame>
68
+ </ControlSection>
69
+ <ControlSection
70
+ title={t('danger zone title', { ns: CLIENT_PLUGIN })}
71
+ description={t('danger zone description', { ns: CLIENT_PLUGIN })}
72
+ >
73
+ <ControlGroup>
74
+ <ControlItem
75
+ title={t('reset device label')}
76
+ description={t('reset device description', { ns: CLIENT_PLUGIN })}
77
+ >
78
+ <Button variant='destructive' onClick={handleResetStorage} data-testid='devicesContainer.reset'>
79
+ {t('reset device label')}
80
+ </Button>
81
+ </ControlItem>
82
+ <ControlItem
83
+ title={t('recover identity label')}
84
+ description={t('recover identity description', { ns: CLIENT_PLUGIN })}
85
+ >
86
+ <Button variant='destructive' onClick={handleRecover} data-testid='devicesContainer.recover'>
87
+ {t('recover identity label')}
88
+ </Button>
89
+ </ControlItem>
90
+ <ControlItem
91
+ title={t('join new identity label')}
92
+ description={t('join new identity description', { ns: CLIENT_PLUGIN })}
93
+ >
94
+ <Button variant='destructive' onClick={handleJoinNewIdentity} data-testid='devicesContainer.joinExisting'>
95
+ {t('join new identity label')}
96
+ </Button>
97
+ </ControlItem>
98
+ </ControlGroup>
99
+ </ControlSection>
119
100
  </StackItem.Content>
120
101
  </Clipboard.Provider>
121
102
  );
@@ -258,16 +239,11 @@ const InvitationQR = ({ id, url, onCancel }: { id: string; url: string; onCancel
258
239
  <span id={qrLabel} className='sr-only'>
259
240
  {t('qr label')}
260
241
  </span>
242
+ <Clipboard.Button value={url ?? 'never'} />
261
243
  </div>
262
- {/* TODO(burdon): Factor out button bar */}
263
- <div className='flex justify-center'>
264
- <div className='flex gap-2'>
265
- <Clipboard.Button value={url ?? 'never'} />
266
- <Button variant='ghost' onClick={onCancel}>
267
- {t('cancel label')}
268
- </Button>
269
- </div>
270
- </div>
244
+ <Button variant='ghost' onClick={onCancel}>
245
+ {t('cancel label')}
246
+ </Button>
271
247
  </>
272
248
  );
273
249
  };
@@ -15,7 +15,7 @@ import translations from '../translations';
15
15
  const meta: Meta = {
16
16
  title: 'plugins/plugin-client/ProfileContainer',
17
17
  component: ProfileContainer,
18
- decorators: [withClientProvider({ createIdentity: true }), withTheme, withLayout()],
18
+ decorators: [withClientProvider({ createIdentity: true }), withTheme, withLayout({ tooltips: true })],
19
19
  parameters: {
20
20
  layout: 'fullscreen',
21
21
  translations,
@@ -2,21 +2,14 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Schema } from 'effect';
5
+ import { Schema as S } from 'effect';
6
6
  import React, { type ChangeEvent, useCallback, useMemo, useState } from 'react';
7
7
 
8
8
  import { debounce } from '@dxos/async';
9
9
  import { useClient } from '@dxos/react-client';
10
10
  import { type Identity, useIdentity } from '@dxos/react-client/halo';
11
11
  import { ButtonGroup, Clipboard, Input, useTranslation } from '@dxos/react-ui';
12
- import {
13
- Form,
14
- type InputComponent,
15
- ControlItem,
16
- ControlItemInput,
17
- ControlSection,
18
- ControlPage,
19
- } from '@dxos/react-ui-form';
12
+ import { Form, type InputComponent, ControlItem, ControlItemInput, ControlSection } from '@dxos/react-ui-form';
20
13
  import { EmojiPickerBlock, HuePicker } from '@dxos/react-ui-pickers';
21
14
  import { StackItem } from '@dxos/react-ui-stack';
22
15
  import { hexToHue, hexToEmoji } from '@dxos/util';
@@ -143,30 +136,28 @@ export const ProfileContainer = () => {
143
136
  );
144
137
 
145
138
  return (
146
- <StackItem.Content classNames='block overflow-y-auto'>
147
- <ControlPage>
148
- <Clipboard.Provider>
149
- <ControlSection title={t('profile label')} description={t('profile description')}>
150
- <Form
151
- schema={ProfileSchema}
152
- values={values}
153
- autoSave
154
- onSave={handleSave}
155
- Custom={customElements}
156
- classNames='p-0 container-max-width [&_[role="form"]]:grid [&_[role="form"]]:grid-cols-1 md:[&_[role="form"]]:grid-cols-[1fr_min-content] [&_[role="form"]]:gap-4'
157
- />
158
- </ControlSection>
159
- </Clipboard.Provider>
160
- </ControlPage>
139
+ <StackItem.Content classNames='p-2 block overflow-y-auto'>
140
+ <Clipboard.Provider>
141
+ <ControlSection title={t('profile label')} description={t('profile description')}>
142
+ <Form
143
+ schema={ProfileSchema}
144
+ values={values}
145
+ autoSave
146
+ onSave={handleSave}
147
+ Custom={customElements}
148
+ classNames='p-0 container-max-width [&_[role="form"]]:grid [&_[role="form"]]:grid-cols-1 md:[&_[role="form"]]:grid-cols-[1fr_min-content] [&_[role="form"]]:gap-4'
149
+ />
150
+ </ControlSection>
151
+ </Clipboard.Provider>
161
152
  </StackItem.Content>
162
153
  );
163
154
  };
164
155
 
165
156
  // TODO(wittjosiah): Integrate annotations with translations.
166
- const ProfileSchema = Schema.Struct({
167
- displayName: Schema.String.annotations({ title: 'Display name' }),
168
- emoji: Schema.String.annotations({ title: 'Avatar' }),
169
- hue: Schema.String.annotations({ title: 'Color' }),
170
- did: Schema.String.annotations({ title: 'DID' }),
157
+ const ProfileSchema = S.Struct({
158
+ displayName: S.String.annotations({ title: 'Display name' }),
159
+ emoji: S.String.annotations({ title: 'Avatar' }),
160
+ hue: S.String.annotations({ title: 'Color' }),
161
+ did: S.String.annotations({ title: 'DID' }),
171
162
  });
172
- type Profile = Schema.Schema.Type<typeof ProfileSchema>;
163
+ type Profile = S.Schema.Type<typeof ProfileSchema>;