@dxos/plugin-client 0.9.0 → 0.9.1-staging.ee54ba693a

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 (236) hide show
  1. package/dist/lib/neutral/{AccountContainer-FZ3IC6H4.mjs → AccountContainer-7SF53UON.mjs} +17 -13
  2. package/dist/lib/neutral/AccountContainer-7SF53UON.mjs.map +7 -0
  3. package/dist/lib/neutral/ClientPlugin.node.mjs +477 -370
  4. package/dist/lib/neutral/ClientPlugin.node.mjs.map +4 -4
  5. package/dist/lib/neutral/{DevicesContainer-6MWNUC3I.mjs → DevicesContainer-PRWIN36N.mjs} +36 -27
  6. package/dist/lib/neutral/DevicesContainer-PRWIN36N.mjs.map +7 -0
  7. package/dist/lib/neutral/{InvitationsContainer-ME722VKS.mjs → InvitationsContainer-BI5RAWET.mjs} +43 -32
  8. package/dist/lib/neutral/InvitationsContainer-BI5RAWET.mjs.map +7 -0
  9. package/dist/lib/neutral/{JoinDialog-2NXAKPLP.mjs → JoinDialog-4BWV4CYB.mjs} +2 -2
  10. package/dist/lib/neutral/JoinDialog-4BWV4CYB.mjs.map +7 -0
  11. package/dist/lib/neutral/{ProfileContainer-VPX3FPWG.mjs → ProfileContainer-YEAYOTX5.mjs} +22 -21
  12. package/dist/lib/neutral/ProfileContainer-YEAYOTX5.mjs.map +7 -0
  13. package/dist/lib/neutral/{RecoveryCodeDialog-EWHB6U5T.mjs → RecoveryCodeDialog-7EPNKCLB.mjs} +2 -2
  14. package/dist/lib/neutral/RecoveryCodeDialog-7EPNKCLB.mjs.map +7 -0
  15. package/dist/lib/neutral/{RecoveryCredentialsContainer-IPBGDMIA.mjs → RecoveryCredentialsContainer-6BZ2IWX7.mjs} +24 -15
  16. package/dist/lib/neutral/RecoveryCredentialsContainer-6BZ2IWX7.mjs.map +7 -0
  17. package/dist/lib/neutral/UsageContainer-2DWCVVE4.mjs +241 -0
  18. package/dist/lib/neutral/UsageContainer-2DWCVVE4.mjs.map +7 -0
  19. package/dist/lib/neutral/account-cache-YVQKLNV2.mjs +16 -0
  20. package/dist/lib/neutral/account-cache-YVQKLNV2.mjs.map +7 -0
  21. package/dist/lib/neutral/{app-graph-builder-A74W7C36.mjs → app-graph-builder-RVXTD6OE.mjs} +30 -16
  22. package/dist/lib/neutral/app-graph-builder-RVXTD6OE.mjs.map +7 -0
  23. package/dist/lib/neutral/capabilities/index.mjs +6 -6
  24. package/dist/lib/neutral/capabilities/node.mjs +3 -3
  25. package/dist/lib/neutral/chunk-6G4KZLBG.mjs +28 -0
  26. package/dist/lib/neutral/chunk-6G4KZLBG.mjs.map +7 -0
  27. package/dist/lib/neutral/{chunk-D6HPCLTS.mjs → chunk-7DEIKHB7.mjs} +16 -2
  28. package/dist/lib/neutral/chunk-7DEIKHB7.mjs.map +7 -0
  29. package/dist/lib/neutral/chunk-A7WDDIWY.mjs +162 -0
  30. package/dist/lib/neutral/chunk-A7WDDIWY.mjs.map +7 -0
  31. package/dist/lib/neutral/chunk-GSKMAS3S.mjs +13 -0
  32. package/dist/lib/neutral/chunk-GSKMAS3S.mjs.map +7 -0
  33. package/dist/lib/neutral/{chunk-JEF2FQGB.mjs → chunk-JY35S2CN.mjs} +2 -2
  34. package/dist/lib/neutral/{chunk-JEF2FQGB.mjs.map → chunk-JY35S2CN.mjs.map} +1 -1
  35. package/dist/lib/neutral/chunk-ZFABGG2E.mjs +8 -0
  36. package/dist/lib/neutral/{chunk-NTVDGDQ3.mjs.map → chunk-ZFABGG2E.mjs.map} +2 -2
  37. package/dist/lib/neutral/containers/index.mjs +10 -8
  38. package/dist/lib/neutral/containers/index.mjs.map +3 -3
  39. package/dist/lib/neutral/{create-agent-6HLW4D3A.mjs → create-agent-6UKJMZS5.mjs} +3 -3
  40. package/dist/lib/neutral/{create-identity-JIWAR5GW.mjs → create-identity-ULCRB3YH.mjs} +3 -3
  41. package/dist/lib/neutral/{create-passkey-FOXEMXGF.mjs → create-passkey-I7VDS3AC.mjs} +7 -7
  42. package/dist/lib/neutral/create-passkey-I7VDS3AC.mjs.map +7 -0
  43. package/dist/lib/neutral/{create-recovery-code-ZWDKLPH4.mjs → create-recovery-code-DH2B6EGP.mjs} +4 -4
  44. package/dist/lib/neutral/index.mjs +6 -4
  45. package/dist/lib/neutral/{join-identity-QEQFAE2S.mjs → join-identity-PJCHDNLH.mjs} +3 -3
  46. package/dist/lib/neutral/{layer-specs-CQLXQBQJ.mjs → layer-specs-YI7BVWKZ.mjs} +6 -8
  47. package/dist/lib/neutral/layer-specs-YI7BVWKZ.mjs.map +7 -0
  48. package/dist/lib/neutral/meta.json +1 -1
  49. package/dist/lib/neutral/meta.mjs +1 -1
  50. package/dist/lib/neutral/{navigation-handler-LU26CDFR.mjs → navigation-handler-MQ6MZXLX.mjs} +3 -3
  51. package/dist/lib/neutral/open-usage-OJVTPGOY.mjs +27 -0
  52. package/dist/lib/neutral/open-usage-OJVTPGOY.mjs.map +7 -0
  53. package/dist/lib/neutral/operations/index.mjs +2 -2
  54. package/dist/lib/neutral/plugin.mjs +3 -3
  55. package/dist/lib/neutral/{react-context-B5LXMHVM.mjs → react-context-V74T6JOJ.mjs} +2 -2
  56. package/dist/lib/neutral/{react-context-B5LXMHVM.mjs.map → react-context-V74T6JOJ.mjs.map} +3 -3
  57. package/dist/lib/neutral/{react-surface-DEKGWTCD.mjs → react-surface-HPXRA65U.mjs} +8 -3
  58. package/dist/lib/neutral/react-surface-HPXRA65U.mjs.map +7 -0
  59. package/dist/lib/neutral/{recover-identity-JNFKLCUD.mjs → recover-identity-B7YM2BR4.mjs} +3 -3
  60. package/dist/lib/neutral/{redeem-passkey-4SBAI4AG.mjs → redeem-passkey-TYP4XLHO.mjs} +10 -10
  61. package/dist/lib/neutral/redeem-passkey-TYP4XLHO.mjs.map +7 -0
  62. package/dist/lib/neutral/{redeem-token-POBD6TMN.mjs → redeem-token-2J33R3PV.mjs} +3 -3
  63. package/dist/lib/neutral/{reset-storage-LQKUV5RA.mjs → reset-storage-K47HZTGP.mjs} +3 -3
  64. package/dist/lib/neutral/{share-identity-KNQ2XNXV.mjs → share-identity-TOROBLWL.mjs} +6 -6
  65. package/dist/lib/neutral/share-identity-TOROBLWL.mjs.map +7 -0
  66. package/dist/lib/neutral/testing/index.mjs +2 -2
  67. package/dist/lib/neutral/testing/index.mjs.map +3 -3
  68. package/dist/lib/neutral/translations.mjs +24 -1
  69. package/dist/lib/neutral/translations.mjs.map +3 -3
  70. package/dist/lib/neutral/types/index.mjs +3 -1
  71. package/dist/types/dx.config.d.ts +28 -0
  72. package/dist/types/dx.config.d.ts.map +1 -0
  73. package/dist/types/src/capabilities/account-cache.d.ts +1 -31
  74. package/dist/types/src/capabilities/app-graph-builder.d.ts +1 -2
  75. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  76. package/dist/types/src/capabilities/client.d.ts +1 -2
  77. package/dist/types/src/capabilities/client.d.ts.map +1 -1
  78. package/dist/types/src/capabilities/hub-http-client.d.ts +1 -2
  79. package/dist/types/src/capabilities/hub-http-client.d.ts.map +1 -1
  80. package/dist/types/src/capabilities/index.d.ts +9 -43
  81. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  82. package/dist/types/src/capabilities/layer-specs.d.ts +1 -2
  83. package/dist/types/src/capabilities/layer-specs.d.ts.map +1 -1
  84. package/dist/types/src/capabilities/migrations.d.ts +1 -1
  85. package/dist/types/src/capabilities/navigation-handler/index.d.ts +1 -1
  86. package/dist/types/src/capabilities/navigation-handler/index.d.ts.map +1 -1
  87. package/dist/types/src/capabilities/navigation-handler/navigation-handler.d.ts +1 -2
  88. package/dist/types/src/capabilities/navigation-handler/navigation-handler.d.ts.map +1 -1
  89. package/dist/types/src/capabilities/node.d.ts +6 -6
  90. package/dist/types/src/capabilities/node.d.ts.map +1 -1
  91. package/dist/types/src/capabilities/react-context.d.ts +1 -6
  92. package/dist/types/src/capabilities/react-context.d.ts.map +1 -1
  93. package/dist/types/src/capabilities/react-surface.d.ts +2 -2
  94. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  95. package/dist/types/src/capabilities/schema-defs.d.ts +1 -1
  96. package/dist/types/src/commands/account/index.d.ts +3 -0
  97. package/dist/types/src/commands/account/index.d.ts.map +1 -0
  98. package/dist/types/src/commands/account/login/index.d.ts +2 -0
  99. package/dist/types/src/commands/account/login/index.d.ts.map +1 -0
  100. package/dist/types/src/commands/account/login/login.d.ts +12 -0
  101. package/dist/types/src/commands/account/login/login.d.ts.map +1 -0
  102. package/dist/types/src/commands/account/logout/index.d.ts +2 -0
  103. package/dist/types/src/commands/account/logout/index.d.ts.map +1 -0
  104. package/dist/types/src/commands/account/logout/logout.d.ts +9 -0
  105. package/dist/types/src/commands/account/logout/logout.d.ts.map +1 -0
  106. package/dist/types/src/commands/halo/join/join.d.ts +1 -1
  107. package/dist/types/src/commands/halo/join/join.d.ts.map +1 -1
  108. package/dist/types/src/commands/halo/util.d.ts +4 -0
  109. package/dist/types/src/commands/halo/util.d.ts.map +1 -1
  110. package/dist/types/src/commands/index.d.ts +1 -0
  111. package/dist/types/src/commands/index.d.ts.map +1 -1
  112. package/dist/types/src/constants.d.ts.map +1 -1
  113. package/dist/types/src/containers/AccountContainer/AccountContainer.d.ts.map +1 -1
  114. package/dist/types/src/containers/AccountContainer/AccountContainer.stories.d.ts +14 -0
  115. package/dist/types/src/containers/AccountContainer/AccountContainer.stories.d.ts.map +1 -0
  116. package/dist/types/src/containers/DevicesContainer/DevicesContainer.d.ts.map +1 -1
  117. package/dist/types/src/containers/InvitationsContainer/InvitationsContainer.d.ts.map +1 -1
  118. package/dist/types/src/containers/InvitationsContainer/InvitationsContainer.stories.d.ts +14 -0
  119. package/dist/types/src/containers/InvitationsContainer/InvitationsContainer.stories.d.ts.map +1 -0
  120. package/dist/types/src/containers/ProfileContainer/ProfileContainer.d.ts.map +1 -1
  121. package/dist/types/src/containers/RecoveryCredentialsContainer/RecoveryCredentialsContainer.d.ts.map +1 -1
  122. package/dist/types/src/containers/UsageContainer/UsageContainer.d.ts +7 -0
  123. package/dist/types/src/containers/UsageContainer/UsageContainer.d.ts.map +1 -0
  124. package/dist/types/src/containers/UsageContainer/UsageView.d.ts +28 -0
  125. package/dist/types/src/containers/UsageContainer/UsageView.d.ts.map +1 -0
  126. package/dist/types/src/containers/UsageContainer/UsageView.stories.d.ts +21 -0
  127. package/dist/types/src/containers/UsageContainer/UsageView.stories.d.ts.map +1 -0
  128. package/dist/types/src/containers/UsageContainer/index.d.ts +2 -0
  129. package/dist/types/src/containers/UsageContainer/index.d.ts.map +1 -0
  130. package/dist/types/src/containers/index.d.ts +1 -0
  131. package/dist/types/src/containers/index.d.ts.map +1 -1
  132. package/dist/types/src/hooks/index.d.ts +2 -0
  133. package/dist/types/src/hooks/index.d.ts.map +1 -0
  134. package/dist/types/src/{state/use-hub-http.d.ts → hooks/useHubClient.d.ts} +1 -1
  135. package/dist/types/src/hooks/useHubClient.d.ts.map +1 -0
  136. package/dist/types/src/meta.d.ts +32 -2
  137. package/dist/types/src/meta.d.ts.map +1 -1
  138. package/dist/types/src/operations/create-passkey.d.ts.map +1 -1
  139. package/dist/types/src/operations/definitions.d.ts +1 -0
  140. package/dist/types/src/operations/definitions.d.ts.map +1 -1
  141. package/dist/types/src/operations/index.d.ts.map +1 -1
  142. package/dist/types/src/operations/open-usage.d.ts +5 -0
  143. package/dist/types/src/operations/open-usage.d.ts.map +1 -0
  144. package/dist/types/src/testing/initializeIdentity.d.ts.map +1 -1
  145. package/dist/types/src/translations.d.ts.map +1 -1
  146. package/dist/types/src/{state → types}/account-cache.d.ts.map +1 -1
  147. package/dist/types/src/types/capabilities.d.ts.map +1 -1
  148. package/dist/types/src/types/events.d.ts.map +1 -1
  149. package/dist/types/src/types/index.d.ts +1 -0
  150. package/dist/types/src/types/index.d.ts.map +1 -1
  151. package/dist/types/src/types/schema.d.ts +1 -0
  152. package/dist/types/src/types/schema.d.ts.map +1 -1
  153. package/dist/types/tsconfig.tsbuildinfo +1 -1
  154. package/dx.config.ts +19 -0
  155. package/package.json +47 -44
  156. package/src/ClientPlugin.node.ts +2 -2
  157. package/src/ClientPlugin.test.ts +1 -1
  158. package/src/capabilities/account-cache.ts +1 -3
  159. package/src/capabilities/app-graph-builder.ts +81 -71
  160. package/src/capabilities/layer-specs.ts +4 -6
  161. package/src/capabilities/react-context.tsx +1 -1
  162. package/src/capabilities/react-surface.tsx +6 -0
  163. package/src/commands/account/index.ts +13 -0
  164. package/src/commands/account/login/index.ts +5 -0
  165. package/src/commands/account/login/login.ts +147 -0
  166. package/src/commands/account/logout/index.ts +5 -0
  167. package/src/commands/account/logout/logout.ts +49 -0
  168. package/src/commands/halo/create/create.ts +2 -2
  169. package/src/commands/halo/index.ts +6 -6
  170. package/src/commands/halo/join/join.ts +2 -13
  171. package/src/commands/halo/util.ts +24 -0
  172. package/src/commands/index.ts +1 -0
  173. package/src/constants.ts +3 -3
  174. package/src/containers/AccountContainer/AccountContainer.stories.tsx +46 -0
  175. package/src/containers/AccountContainer/AccountContainer.tsx +67 -63
  176. package/src/containers/DevicesContainer/DevicesContainer.tsx +72 -54
  177. package/src/containers/InvitationsContainer/InvitationsContainer.stories.tsx +46 -0
  178. package/src/containers/InvitationsContainer/InvitationsContainer.tsx +65 -62
  179. package/src/containers/JoinDialog/JoinDialog.tsx +1 -1
  180. package/src/containers/ProfileContainer/ProfileContainer.tsx +38 -28
  181. package/src/containers/RecoveryCodeDialog/RecoveryCodeDialog.tsx +1 -1
  182. package/src/containers/RecoveryCredentialsContainer/RecoveryCredentialsContainer.tsx +52 -43
  183. package/src/containers/UsageContainer/UsageContainer.tsx +48 -0
  184. package/src/containers/UsageContainer/UsageView.stories.tsx +127 -0
  185. package/src/containers/UsageContainer/UsageView.tsx +270 -0
  186. package/src/containers/UsageContainer/index.ts +5 -0
  187. package/src/containers/index.ts +1 -0
  188. package/src/hooks/index.ts +5 -0
  189. package/src/meta.ts +3 -12
  190. package/src/operations/create-passkey.ts +6 -4
  191. package/src/operations/definitions.ts +12 -1
  192. package/src/operations/index.ts +1 -0
  193. package/src/operations/open-usage.ts +24 -0
  194. package/src/operations/redeem-passkey.ts +7 -7
  195. package/src/operations/share-identity.ts +3 -3
  196. package/src/testing/initializeIdentity.ts +5 -2
  197. package/src/translations.ts +25 -1
  198. package/src/types/capabilities.ts +8 -6
  199. package/src/types/events.ts +5 -5
  200. package/src/types/index.ts +1 -0
  201. package/src/types/schema.ts +23 -16
  202. package/dist/lib/neutral/AccountContainer-FZ3IC6H4.mjs.map +0 -7
  203. package/dist/lib/neutral/DevicesContainer-6MWNUC3I.mjs.map +0 -7
  204. package/dist/lib/neutral/InvitationsContainer-ME722VKS.mjs.map +0 -7
  205. package/dist/lib/neutral/JoinDialog-2NXAKPLP.mjs.map +0 -7
  206. package/dist/lib/neutral/ProfileContainer-VPX3FPWG.mjs.map +0 -7
  207. package/dist/lib/neutral/RecoveryCodeDialog-EWHB6U5T.mjs.map +0 -7
  208. package/dist/lib/neutral/RecoveryCredentialsContainer-IPBGDMIA.mjs.map +0 -7
  209. package/dist/lib/neutral/account-cache-4EKGEUJI.mjs +0 -43
  210. package/dist/lib/neutral/account-cache-4EKGEUJI.mjs.map +0 -7
  211. package/dist/lib/neutral/app-graph-builder-A74W7C36.mjs.map +0 -7
  212. package/dist/lib/neutral/chunk-D6HPCLTS.mjs.map +0 -7
  213. package/dist/lib/neutral/chunk-JOCCVODY.mjs +0 -21
  214. package/dist/lib/neutral/chunk-JOCCVODY.mjs.map +0 -7
  215. package/dist/lib/neutral/chunk-NTVDGDQ3.mjs +0 -8
  216. package/dist/lib/neutral/chunk-TUK3ELUU.mjs +0 -135
  217. package/dist/lib/neutral/chunk-TUK3ELUU.mjs.map +0 -7
  218. package/dist/lib/neutral/chunk-WWCVZG3K.mjs +0 -13
  219. package/dist/lib/neutral/chunk-WWCVZG3K.mjs.map +0 -7
  220. package/dist/lib/neutral/create-passkey-FOXEMXGF.mjs.map +0 -7
  221. package/dist/lib/neutral/layer-specs-CQLXQBQJ.mjs.map +0 -7
  222. package/dist/lib/neutral/react-surface-DEKGWTCD.mjs.map +0 -7
  223. package/dist/lib/neutral/redeem-passkey-4SBAI4AG.mjs.map +0 -7
  224. package/dist/lib/neutral/share-identity-KNQ2XNXV.mjs.map +0 -7
  225. package/dist/types/src/state/use-hub-http.d.ts.map +0 -1
  226. /package/dist/lib/neutral/{create-agent-6HLW4D3A.mjs.map → create-agent-6UKJMZS5.mjs.map} +0 -0
  227. /package/dist/lib/neutral/{create-identity-JIWAR5GW.mjs.map → create-identity-ULCRB3YH.mjs.map} +0 -0
  228. /package/dist/lib/neutral/{create-recovery-code-ZWDKLPH4.mjs.map → create-recovery-code-DH2B6EGP.mjs.map} +0 -0
  229. /package/dist/lib/neutral/{join-identity-QEQFAE2S.mjs.map → join-identity-PJCHDNLH.mjs.map} +0 -0
  230. /package/dist/lib/neutral/{navigation-handler-LU26CDFR.mjs.map → navigation-handler-MQ6MZXLX.mjs.map} +0 -0
  231. /package/dist/lib/neutral/{recover-identity-JNFKLCUD.mjs.map → recover-identity-B7YM2BR4.mjs.map} +0 -0
  232. /package/dist/lib/neutral/{redeem-token-POBD6TMN.mjs.map → redeem-token-2J33R3PV.mjs.map} +0 -0
  233. /package/dist/lib/neutral/{reset-storage-LQKUV5RA.mjs.map → reset-storage-K47HZTGP.mjs.map} +0 -0
  234. /package/dist/types/src/{state → types}/account-cache.d.ts +0 -0
  235. /package/src/{state/use-hub-http.ts → hooks/useHubClient.ts} +0 -0
  236. /package/src/{state → types}/account-cache.ts +0 -0
@@ -7,44 +7,45 @@ import React, { useCallback, useState } from 'react';
7
7
 
8
8
  import { useCapability } from '@dxos/app-framework/ui';
9
9
  import { Context } from '@dxos/context';
10
- import { Clipboard, Icon, IconButton, List, ListItem, useAsyncEffect, useTranslation } from '@dxos/react-ui';
11
- import { Settings } from '@dxos/react-ui-form';
10
+ import { Clipboard, Icon, IconButton, useAsyncEffect, useTranslation } from '@dxos/react-ui';
11
+ import { Form } from '@dxos/react-ui-form';
12
+ import { Listbox } from '@dxos/react-ui-list';
12
13
 
13
14
  import { meta } from '#meta';
14
- import { ClientCapabilities } from '#types';
15
+ import { type AccountCacheInvitation, ClientCapabilities } from '#types';
15
16
 
16
- import { type AccountCacheInvitation } from '../../state/account-cache';
17
- import { useHubHttpClient } from '../../state/use-hub-http';
17
+ import { useHubHttpClient } from '../../hooks';
18
18
 
19
19
  export const InvitationsContainer = () => {
20
- const { t } = useTranslation(meta.id);
20
+ const { t } = useTranslation(meta.profile.key);
21
21
  const accountCacheAtom = useCapability(ClientCapabilities.AccountCache);
22
22
  const [cache] = useAtom(accountCacheAtom);
23
23
  const setCache = useAtomSet(accountCacheAtom);
24
24
  const [pending, setPending] = useState(false);
25
25
 
26
26
  // Account/invitation routes live on hub-service, not the edge worker.
27
- const hubHttp = useHubHttpClient();
27
+ const hubClient = useHubHttpClient();
28
28
 
29
29
  useAsyncEffect(async () => {
30
- if (!hubHttp) {
30
+ if (!hubClient) {
31
31
  return;
32
32
  }
33
+
33
34
  try {
34
- const result = await hubHttp.listAccountInvitations(new Context());
35
+ const result = await hubClient.listAccountInvitations(new Context());
35
36
  setCache((prev) => ({ ...prev, invitations: result.invitations, fetchedAt: Date.now() }));
36
37
  } catch {
37
38
  // Offline: keep cache.
38
39
  }
39
- }, [hubHttp, setCache]);
40
+ }, [hubClient, setCache]);
40
41
 
41
42
  const handleIssue = useCallback(async () => {
42
- if (!hubHttp) {
43
+ if (!hubClient) {
43
44
  return;
44
45
  }
45
46
  setPending(true);
46
47
  try {
47
- const result = await hubHttp.issueAccountInvitation(new Context());
48
+ const result = await hubClient.issueAccountInvitation(new Context());
48
49
  // Optimistically push the new code and decrement the remaining quota; the
49
50
  // server consumes one slot at issue time. Next refresh reconciles.
50
51
  setCache((prev) => ({
@@ -57,7 +58,7 @@ export const InvitationsContainer = () => {
57
58
  } finally {
58
59
  setPending(false);
59
60
  }
60
- }, [hubHttp, setCache]);
61
+ }, [hubClient, setCache]);
61
62
 
62
63
  const remaining = cache.account?.invitationsRemaining ?? 0;
63
64
  const list = cache.invitations ?? [];
@@ -66,72 +67,74 @@ export const InvitationsContainer = () => {
66
67
 
67
68
  return (
68
69
  <Clipboard.Provider>
69
- <Settings.Viewport>
70
- <Settings.Section title={t('invitations-section.title')} description={t('invitations-section.description')}>
71
- <Settings.Item
72
- title={t('generate-invitation.label')}
73
- description={t('generate-invitation.description', { count: remaining })}
74
- >
75
- <IconButton
76
- icon='ph--plus--regular'
77
- label={t('generate-invitation.label')}
78
- variant='primary'
79
- onClick={handleIssue}
80
- disabled={pending || remaining <= 0}
81
- />
82
- </Settings.Item>
83
- </Settings.Section>
70
+ <Form.Root variant='settings'>
71
+ <Form.Viewport scroll>
72
+ <Form.Content>
73
+ <Form.Section title={t('invitations-section.title')} description={t('invitations-section.description')}>
74
+ <Form.Row
75
+ label={t('generate-invitation.label')}
76
+ description={t('generate-invitation.description', { count: remaining })}
77
+ >
78
+ <IconButton
79
+ icon='ph--plus--regular'
80
+ label={t('generate-invitation.label')}
81
+ variant='primary'
82
+ onClick={handleIssue}
83
+ disabled={pending || remaining <= 0}
84
+ />
85
+ </Form.Row>
86
+ </Form.Section>
84
87
 
85
- {available.length > 0 ? (
86
- <Settings.Section title={t('available-invitations.title')}>
87
- <List>
88
- {available.map((row) => (
89
- <AvailableInvitationItem key={row.code} row={row} />
90
- ))}
91
- </List>
92
- </Settings.Section>
93
- ) : null}
88
+ {available.length > 0 ? (
89
+ <Form.Section title={t('available-invitations.title')}>
90
+ <Listbox.Root>
91
+ <Listbox.Content classNames='gap-1'>
92
+ {available.map((row) => (
93
+ <AvailableInvitationItem key={row.code} row={row} />
94
+ ))}
95
+ </Listbox.Content>
96
+ </Listbox.Root>
97
+ </Form.Section>
98
+ ) : null}
94
99
 
95
- {redeemed.length > 0 ? (
96
- <Settings.Section title={t('redeemed-invitations.title')}>
97
- <List>
98
- {redeemed.map((row) => (
99
- <RedeemedInvitationItem key={row.code} row={row} />
100
- ))}
101
- </List>
102
- </Settings.Section>
103
- ) : null}
104
- </Settings.Viewport>
100
+ {redeemed.length > 0 ? (
101
+ <Form.Section title={t('redeemed-invitations.title')}>
102
+ <Listbox.Root>
103
+ <Listbox.Content classNames='gap-1'>
104
+ {redeemed.map((row) => (
105
+ <RedeemedInvitationItem key={row.code} row={row} />
106
+ ))}
107
+ </Listbox.Content>
108
+ </Listbox.Root>
109
+ </Form.Section>
110
+ ) : null}
111
+ </Form.Content>
112
+ </Form.Viewport>
113
+ </Form.Root>
105
114
  </Clipboard.Provider>
106
115
  );
107
116
  };
108
117
 
109
118
  const AvailableInvitationItem = ({ row }: { row: AccountCacheInvitation }) => (
110
- <ListItem.Root classNames='grid grid-cols-[min-content_1fr_min-content] items-center gap-2'>
111
- <ListItem.Endcap>
112
- <Icon icon='ph--paper-plane-tilt--duotone' size={5} classNames='text-description' />
113
- </ListItem.Endcap>
119
+ <Listbox.Item id={row.code} classNames='grid grid-cols-[min-content_1fr_min-content] items-center gap-2'>
120
+ <Icon icon='ph--paper-plane-tilt--duotone' size={5} classNames='text-description' />
114
121
  <div className='flex flex-col min-w-0'>
115
- <ListItem.Heading classNames='font-mono truncate'>{row.code}</ListItem.Heading>
122
+ <div className='font-mono truncate'>{row.code}</div>
116
123
  <p className='text-description text-xs'>{new Date(row.createdAt).toLocaleString()}</p>
117
124
  </div>
118
- <ListItem.Endcap>
119
- <Clipboard.IconButton value={row.code} />
120
- </ListItem.Endcap>
121
- </ListItem.Root>
125
+ <Clipboard.IconButton value={row.code} />
126
+ </Listbox.Item>
122
127
  );
123
128
 
124
129
  const RedeemedInvitationItem = ({ row }: { row: AccountCacheInvitation }) => {
125
130
  const date = row.redeemedAt ?? row.createdAt;
126
131
  return (
127
- <ListItem.Root classNames='grid grid-cols-[min-content_1fr] items-center gap-2'>
128
- <ListItem.Endcap>
129
- <Icon icon='ph--check-circle--duotone' size={5} classNames='text-success-text' />
130
- </ListItem.Endcap>
132
+ <Listbox.Item id={row.code} classNames='grid grid-cols-[min-content_1fr] items-center gap-2'>
133
+ <Icon icon='ph--check-circle--duotone' size={5} classNames='text-success-text' />
131
134
  <div className='flex flex-col min-w-0'>
132
- <ListItem.Heading classNames='font-mono truncate'>{row.code}</ListItem.Heading>
135
+ <div className='font-mono truncate'>{row.code}</div>
133
136
  <p className='text-description text-xs'>{new Date(date).toLocaleString()}</p>
134
137
  </div>
135
- </ListItem.Root>
138
+ </Listbox.Item>
136
139
  );
137
140
  };
@@ -17,7 +17,7 @@ import { ClientOperation } from '#operations';
17
17
 
18
18
  export const JoinDialog = (props: JoinPanelProps) => {
19
19
  const { invokePromise } = useOperationInvoker();
20
- const { t } = useTranslation(meta.id);
20
+ const { t } = useTranslation(meta.profile.key);
21
21
 
22
22
  const handleCancelResetStorage = useCallback(() => invokePromise(ClientOperation.ShareIdentity), [invokePromise]);
23
23
 
@@ -9,7 +9,7 @@ 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 { Form, type FormFieldMap, type FormUpdateMeta, Settings } from '@dxos/react-ui-form';
12
+ import { Form, type FormFieldMap, type FormUpdateMeta } from '@dxos/react-ui-form';
13
13
  import { EmojiPickerBlock, HuePicker } from '@dxos/react-ui-pickers';
14
14
  import { hexToEmoji, hexToHue } from '@dxos/util';
15
15
 
@@ -33,7 +33,7 @@ const getDefaultEmojiValue = (identity: Identity | null) => hexToEmoji(identity?
33
33
  const getEmojiValue = (identity: Identity | null) => identity?.profile?.data?.emoji || getDefaultEmojiValue(identity);
34
34
 
35
35
  export const ProfileContainer = () => {
36
- const { t } = useTranslation(meta.id);
36
+ const { t } = useTranslation(meta.profile.key);
37
37
  const client = useClient();
38
38
  const identity = useIdentity();
39
39
  const [displayName, setDisplayNameDirectly] = useState(identity?.profile?.displayName ?? '');
@@ -101,14 +101,16 @@ export const ProfileContainer = () => {
101
101
  );
102
102
 
103
103
  return (
104
- <Settings.Item title={label} description={t('display-name.description')}>
105
- <Input.TextInput
106
- value={getValue()}
107
- onChange={handleChange}
108
- placeholder={t('display-name-input.placeholder')}
109
- classNames='min-w-64'
110
- />
111
- </Settings.Item>
104
+ <Form.Row label={label} description={t('display-name.description')}>
105
+ <Input.Root>
106
+ <Input.TextInput
107
+ value={getValue()}
108
+ onChange={handleChange}
109
+ placeholder={t('display-name-input.placeholder')}
110
+ classNames='min-w-64'
111
+ />
112
+ </Input.Root>
113
+ </Form.Row>
112
114
  );
113
115
  },
114
116
  emoji: ({ type, label, getValue, onValueChange }) => {
@@ -119,7 +121,7 @@ export const ProfileContainer = () => {
119
121
  );
120
122
 
121
123
  return (
122
- <Settings.Item title={label} description={t('icon.description')}>
124
+ <Form.Row label={label} description={t('icon.description')}>
123
125
  <EmojiPickerBlock
124
126
  triggerVariant='default'
125
127
  emoji={getValue()}
@@ -127,7 +129,7 @@ export const ProfileContainer = () => {
127
129
  onClickClear={handleEmojiReset}
128
130
  classNames='justify-self-end'
129
131
  />
130
- </Settings.Item>
132
+ </Form.Row>
131
133
  );
132
134
  },
133
135
  hue: ({ type, label, getValue, onValueChange }) => {
@@ -138,22 +140,24 @@ export const ProfileContainer = () => {
138
140
  );
139
141
 
140
142
  return (
141
- <Settings.Item title={label} description={t('hue.description')}>
143
+ <Form.Row label={label} description={t('hue.description')}>
142
144
  <div className='flex justify-self-end'>
143
145
  <HuePicker value={getValue()} onChange={handleChange} onReset={handleHueReset} />
144
146
  </div>
145
- </Settings.Item>
147
+ </Form.Row>
146
148
  );
147
149
  },
148
150
  // TODO(wittjosiah): We need text input annotations for disabled and copyable.
149
151
  did: ({ label, getValue }) => {
150
152
  return (
151
- <Settings.Item title={label} description={t('did.description')}>
152
- <ButtonGroup classNames='w-full'>
153
- <Input.TextInput value={getValue()} disabled classNames='min-w-64' />
154
- <Clipboard.IconButton value={getValue() ?? ''} />
155
- </ButtonGroup>
156
- </Settings.Item>
153
+ <Form.Row label={label} description={t('did.description')}>
154
+ <Input.Root>
155
+ <ButtonGroup classNames='w-full'>
156
+ <Input.TextInput value={getValue()} disabled classNames='min-w-64' />
157
+ <Clipboard.IconButton value={getValue() ?? ''} />
158
+ </ButtonGroup>
159
+ </Input.Root>
160
+ </Form.Row>
157
161
  );
158
162
  },
159
163
  }),
@@ -162,15 +166,21 @@ export const ProfileContainer = () => {
162
166
 
163
167
  return (
164
168
  <Clipboard.Provider>
165
- <Settings.Viewport>
166
- <Settings.Section title={t('profile.label')} description={t('profile.description')}>
167
- <Form.Root schema={UserProfile} values={values} fieldMap={fieldMap} onValuesChanged={handleChange}>
168
- <Form.Content classNames='gap-form-gap'>
169
+ <Form.Root
170
+ variant='settings'
171
+ schema={UserProfile}
172
+ values={values}
173
+ fieldMap={fieldMap}
174
+ onValuesChanged={handleChange}
175
+ >
176
+ <Form.Viewport scroll>
177
+ <Form.Content>
178
+ <Form.Section title={t('profile.label')} description={t('profile.description')}>
169
179
  <Form.FieldSet />
170
- </Form.Content>
171
- </Form.Root>
172
- </Settings.Section>
173
- </Settings.Viewport>
180
+ </Form.Section>
181
+ </Form.Content>
182
+ </Form.Viewport>
183
+ </Form.Root>
174
184
  </Clipboard.Provider>
175
185
  );
176
186
  };
@@ -14,7 +14,7 @@ export type RecoveryCodeDialogProps = {
14
14
 
15
15
  // TODO(burdon): Should have cancel button.
16
16
  export const RecoveryCodeDialog = ({ code }: RecoveryCodeDialogProps) => {
17
- const { t } = useTranslation(meta.id);
17
+ const { t } = useTranslation(meta.profile.key);
18
18
  const [confirmation, setConfirmation] = useState(false);
19
19
 
20
20
  const handleConfirmation = useCallback((checked: boolean) => setConfirmation(checked), []);
@@ -6,16 +6,17 @@ import React from 'react';
6
6
 
7
7
  import { useOperationInvoker } from '@dxos/app-framework/ui';
8
8
  import { useCredentials } from '@dxos/react-client/halo';
9
- import { Icon, IconButton, List, ListItem, Message, useTranslation } from '@dxos/react-ui';
10
- import { Settings } from '@dxos/react-ui-form';
9
+ import { Icon, IconButton, Message, useTranslation } from '@dxos/react-ui';
10
+ import { Form } from '@dxos/react-ui-form';
11
+ import { Listbox } from '@dxos/react-ui-list';
11
12
 
12
13
  import { meta } from '#meta';
13
14
  import { ClientOperation } from '#operations';
14
15
 
15
- export const MANAGE_CREDENTIALS_DIALOG = `${meta.id}.ManageCredentialsDialog`;
16
+ export const MANAGE_CREDENTIALS_DIALOG = `${meta.profile.key}.ManageCredentialsDialog`;
16
17
 
17
18
  export const RecoveryCredentialsContainer = () => {
18
- const { t } = useTranslation(meta.id);
19
+ const { t } = useTranslation(meta.profile.key);
19
20
  const { invokePromise } = useOperationInvoker();
20
21
  const credentials = useCredentials();
21
22
  const recoveryCredentials = credentials.filter(
@@ -23,44 +24,52 @@ export const RecoveryCredentialsContainer = () => {
23
24
  );
24
25
 
25
26
  return (
26
- <Settings.Viewport>
27
- <Settings.Section title={t('recovery-setup-dialog.title')} description={t('recovery-setup-dialog.description')}>
28
- <Settings.Item title={t('create-passkey.label')} description={t('create-passkey.description')}>
29
- <IconButton
30
- label={t('create-passkey.label')}
31
- icon='ph--key--duotone'
32
- variant='primary'
33
- onClick={() => invokePromise(ClientOperation.CreatePasskey)}
34
- />
35
- </Settings.Item>
36
- <Settings.Item title={t('create-recovery-code.label')} description={t('create-recovery-code.description')}>
37
- <IconButton
38
- label={t('create-recovery-code.label')}
39
- icon='ph--receipt--duotone'
40
- variant='default'
41
- onClick={() => invokePromise(ClientOperation.CreateRecoveryCode)}
42
- />
43
- </Settings.Item>
44
- </Settings.Section>
45
- <Settings.Section title={t('credentials-list.label')}>
46
- {recoveryCredentials.length < 1 ? (
47
- <Message.Root valence='error'>
48
- <Message.Title icon='ph--shield-warning--duotone'>{t('no-credentials.title')}</Message.Title>
49
- <Message.Content>{t('no-credentials.message')}</Message.Content>
50
- </Message.Root>
51
- ) : (
52
- <List>
53
- {recoveryCredentials.map((credential) => (
54
- <ListItem.Root key={credential.id?.toHex()}>
55
- <ListItem.Endcap>
56
- <Icon icon='ph--key--regular' />
57
- </ListItem.Endcap>
58
- <ListItem.Heading>{credential.issuanceDate.toLocaleString()}</ListItem.Heading>
59
- </ListItem.Root>
60
- ))}
61
- </List>
62
- )}
63
- </Settings.Section>
64
- </Settings.Viewport>
27
+ <Form.Root variant='settings'>
28
+ <Form.Viewport scroll>
29
+ <Form.Content>
30
+ <Form.Section title={t('recovery-setup-dialog.title')} description={t('recovery-setup-dialog.description')}>
31
+ <Form.Row label={t('create-passkey.label')} description={t('create-passkey.description')}>
32
+ <IconButton
33
+ label={t('create-passkey.label')}
34
+ icon='ph--key--duotone'
35
+ variant='primary'
36
+ onClick={() => invokePromise(ClientOperation.CreatePasskey)}
37
+ />
38
+ </Form.Row>
39
+ <Form.Row label={t('create-recovery-code.label')} description={t('create-recovery-code.description')}>
40
+ <IconButton
41
+ label={t('create-recovery-code.label')}
42
+ icon='ph--receipt--duotone'
43
+ variant='default'
44
+ onClick={() => invokePromise(ClientOperation.CreateRecoveryCode)}
45
+ />
46
+ </Form.Row>
47
+ </Form.Section>
48
+ <Form.Section title={t('credentials-list.label')}>
49
+ {recoveryCredentials.length < 1 ? (
50
+ <Message.Root valence='error'>
51
+ <Message.Title icon='ph--shield-warning--duotone'>{t('no-credentials.title')}</Message.Title>
52
+ <Message.Content>{t('no-credentials.message')}</Message.Content>
53
+ </Message.Root>
54
+ ) : (
55
+ <Listbox.Root>
56
+ <Listbox.Content classNames='gap-1'>
57
+ {recoveryCredentials.map((credential, index) => (
58
+ <Listbox.Item
59
+ key={credential.id?.toHex() ?? index}
60
+ id={credential.id?.toHex() ?? `${index}`}
61
+ classNames='gap-2'
62
+ >
63
+ <Icon icon='ph--key--regular' />
64
+ <Listbox.ItemLabel>{credential.issuanceDate.toLocaleString()}</Listbox.ItemLabel>
65
+ </Listbox.Item>
66
+ ))}
67
+ </Listbox.Content>
68
+ </Listbox.Root>
69
+ )}
70
+ </Form.Section>
71
+ </Form.Content>
72
+ </Form.Viewport>
73
+ </Form.Root>
65
74
  );
66
75
  };
@@ -0,0 +1,48 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import React, { useCallback, useState } from 'react';
6
+
7
+ import { Context } from '@dxos/context';
8
+ import { log } from '@dxos/log';
9
+ import { type GetProfileUsageResponse } from '@dxos/protocols';
10
+ import { useAsyncEffect } from '@dxos/react-ui';
11
+
12
+ import { useHubHttpClient } from '../../hooks';
13
+ import { UsageView, type UsageViewState } from './UsageView';
14
+
15
+ /** State + payload kept together so `ready` always carries data (mirrors the discriminated `UsageViewProps`). */
16
+ type UsageFetchState = { state: Exclude<UsageViewState, 'ready'> } | { state: 'ready'; data: GetProfileUsageResponse };
17
+
18
+ /**
19
+ * Fetches rolling-window profile usage from the hub service and renders it.
20
+ * Connected wrapper around the presentational {@link UsageView}.
21
+ */
22
+ export const UsageContainer = () => {
23
+ const hubHttp = useHubHttpClient();
24
+ const [fetchState, setFetchState] = useState<UsageFetchState>({ state: 'loading' });
25
+ const [lastUpdated, setLastUpdated] = useState<number | undefined>();
26
+ const [refreshCount, setRefreshCount] = useState(0);
27
+
28
+ useAsyncEffect(async () => {
29
+ if (!hubHttp) {
30
+ setFetchState({ state: 'unavailable' });
31
+ return;
32
+ }
33
+ // Keep prior data visible while refetching; only show the loading state on first load.
34
+ setFetchState((previous) => (previous.state === 'ready' ? previous : { state: 'loading' }));
35
+ try {
36
+ const result = await hubHttp.getProfileUsage(new Context());
37
+ setFetchState({ state: 'ready', data: result });
38
+ setLastUpdated(Date.now());
39
+ } catch (err) {
40
+ log.catch(err);
41
+ setFetchState({ state: 'error' });
42
+ }
43
+ }, [hubHttp, refreshCount]);
44
+
45
+ const handleRefresh = useCallback(() => setRefreshCount((count) => count + 1), []);
46
+
47
+ return <UsageView {...fetchState} lastUpdated={lastUpdated} onRefresh={handleRefresh} />;
48
+ };
@@ -0,0 +1,127 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import { expect, userEvent, within } from 'storybook/test';
7
+
8
+ import { type GetProfileUsageResponse } from '@dxos/protocols';
9
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
10
+
11
+ import { translations } from '#translations';
12
+
13
+ import { UsageView } from './UsageView';
14
+
15
+ // Mock payload shaped like the `/api/metering/profile/usage` response.
16
+ const usageData: GetProfileUsageResponse = {
17
+ profileId: 'did:key:z6MkExampleProfileIdentity',
18
+ usage: [
19
+ { eventType: 'ai', valueKey: 'inputTokens', subtypePattern: ['claude-opus-4'], amount: 1_284_500 },
20
+ { eventType: 'ai', valueKey: 'outputTokens', subtypePattern: ['claude-opus-4'], amount: 238_900 },
21
+ { eventType: 'ai', valueKey: 'inputTokens', subtypePattern: ['claude-sonnet-4'], amount: 92_300 },
22
+ { eventType: 'ai', valueKey: 'outputTokens', subtypePattern: ['claude-sonnet-4'], amount: 18_400 },
23
+ ],
24
+ limits: [
25
+ {
26
+ eventType: 'ai',
27
+ valueKey: 'inputTokens',
28
+ subtypePattern: ['claude-opus-4'],
29
+ limit: 5_000_000,
30
+ windowDuration: 24 * 7 * 60 * 60,
31
+ },
32
+ {
33
+ eventType: 'ai',
34
+ valueKey: 'outputTokens',
35
+ subtypePattern: ['*'],
36
+ limit: 1_000_000,
37
+ windowDuration: 24 * 30 * 60 * 60,
38
+ },
39
+ {
40
+ eventType: 'ai',
41
+ valueKey: 'inputTokens',
42
+ subtypePattern: ['*'],
43
+ limit: 20_000_000,
44
+ windowDuration: 24 * 30 * 60 * 60,
45
+ },
46
+ ],
47
+ buckets: [],
48
+ };
49
+
50
+ const meta = {
51
+ title: 'plugins/plugin-client/containers/UsageView',
52
+ component: UsageView,
53
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
54
+ tags: ['test'],
55
+ parameters: {
56
+ layout: 'fullscreen',
57
+ translations,
58
+ },
59
+ } satisfies Meta<typeof UsageView>;
60
+
61
+ export default meta;
62
+
63
+ type Story = StoryObj<typeof meta>;
64
+
65
+ export const Default: Story = {
66
+ args: {
67
+ state: 'ready',
68
+ data: usageData,
69
+ lastUpdated: new Date('2026-06-16T12:00:00Z').getTime(),
70
+ onRefresh: () => {},
71
+ },
72
+ };
73
+
74
+ // Internal plan: a single unlimited (`limit: null`) `ai` limit — renders without a meter.
75
+ export const Internal: Story = {
76
+ args: {
77
+ state: 'ready',
78
+ data: {
79
+ profileId: usageData.profileId,
80
+ usage: usageData.usage,
81
+ limits: [
82
+ {
83
+ eventType: 'ai',
84
+ valueKey: 'outputTokens',
85
+ subtypePattern: ['*'],
86
+ limit: null,
87
+ windowDuration: 24 * 30 * 60 * 60,
88
+ },
89
+ ],
90
+ buckets: [],
91
+ },
92
+ lastUpdated: new Date('2026-06-16T12:00:00Z').getTime(),
93
+ onRefresh: () => {},
94
+ },
95
+ };
96
+
97
+ export const Loading: Story = {
98
+ args: { state: 'loading' },
99
+ };
100
+
101
+ export const Error: Story = {
102
+ args: { state: 'error' },
103
+ };
104
+
105
+ export const Empty: Story = {
106
+ args: {
107
+ state: 'ready',
108
+ data: { profileId: usageData.profileId, usage: [], limits: [], buckets: [] },
109
+ lastUpdated: new Date('2026-06-16T12:00:00Z').getTime(),
110
+ onRefresh: () => {},
111
+ },
112
+ };
113
+
114
+ export const Unavailable: Story = {
115
+ args: { state: 'unavailable' },
116
+ };
117
+
118
+ // Drives the raw-response disclosure: collapsed until toggled, then the JSON viewer appears.
119
+ export const Test: Story = {
120
+ args: Default.args,
121
+ play: async () => {
122
+ const screen = within(document.body);
123
+ await expect(screen.queryByTestId('usage-raw-json')).toBeNull();
124
+ await userEvent.click(await screen.findByRole('button', { name: 'Raw JSON' }, { timeout: 10_000 }));
125
+ await screen.findByTestId('usage-raw-json');
126
+ },
127
+ };