@dxos/plugin-client 0.8.1-main.ba2dec9 → 0.8.1-staging.31c3ee1

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 (176) hide show
  1. package/dist/lib/browser/app-graph-builder-WAS4YZNA.mjs +119 -0
  2. package/dist/lib/browser/app-graph-builder-WAS4YZNA.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-OLUST5AO.mjs → chunk-4PLF3BEB.mjs} +2 -2
  4. package/dist/lib/browser/{chunk-7DPLXOMO.mjs → chunk-AN7FJKIJ.mjs} +21 -4
  5. package/dist/lib/browser/{chunk-7DPLXOMO.mjs.map → chunk-AN7FJKIJ.mjs.map} +3 -3
  6. package/dist/lib/browser/{chunk-XK4HM5M7.mjs → chunk-BGY6BSUC.mjs} +3 -2
  7. package/dist/lib/browser/{chunk-XK4HM5M7.mjs.map → chunk-BGY6BSUC.mjs.map} +3 -3
  8. package/dist/lib/browser/chunk-CAM6IWZ2.mjs +619 -0
  9. package/dist/lib/browser/chunk-CAM6IWZ2.mjs.map +7 -0
  10. package/dist/lib/browser/{chunk-NHIL4T3L.mjs → chunk-MCEXD5XH.mjs} +1 -1
  11. package/dist/lib/browser/{chunk-NHIL4T3L.mjs.map → chunk-MCEXD5XH.mjs.map} +2 -2
  12. package/dist/lib/browser/{client-UUGITHCW.mjs → client-LV67Q3R7.mjs} +5 -5
  13. package/dist/lib/browser/{client-UUGITHCW.mjs.map → client-LV67Q3R7.mjs.map} +3 -3
  14. package/dist/lib/browser/index.mjs +51 -22
  15. package/dist/lib/browser/index.mjs.map +2 -2
  16. package/dist/lib/browser/{intent-resolver-AIUHL34E.mjs → intent-resolver-JAQCJLKQ.mjs} +65 -27
  17. package/dist/lib/browser/intent-resolver-JAQCJLKQ.mjs.map +7 -0
  18. package/dist/lib/browser/meta.json +1 -1
  19. package/dist/lib/browser/{migrations-XODC625X.mjs → migrations-3DWFOL5Q.mjs} +3 -3
  20. package/dist/lib/browser/{react-context-JBD7Y3SN.mjs → react-context-WEH7SL4I.mjs} +3 -3
  21. package/dist/lib/browser/react-surface-M6Q6RT3Q.mjs +66 -0
  22. package/dist/lib/browser/react-surface-M6Q6RT3Q.mjs.map +7 -0
  23. package/dist/lib/browser/{schema-Y7ZNXYSL.mjs → schema-JQAT6Q7S.mjs} +3 -3
  24. package/dist/lib/browser/types.mjs +4 -2
  25. package/dist/lib/node/app-graph-builder-PXOJZJO5.cjs +130 -0
  26. package/dist/lib/node/app-graph-builder-PXOJZJO5.cjs.map +7 -0
  27. package/dist/lib/node/chunk-ARZVO2AR.cjs +644 -0
  28. package/dist/lib/node/chunk-ARZVO2AR.cjs.map +7 -0
  29. package/dist/lib/node/{chunk-OIFPM5ZW.cjs → chunk-DJKWB5L6.cjs} +4 -4
  30. package/dist/lib/node/{chunk-OIFPM5ZW.cjs.map → chunk-DJKWB5L6.cjs.map} +2 -2
  31. package/dist/lib/node/{chunk-TREVKZCJ.cjs → chunk-FROKBEVH.cjs} +25 -7
  32. package/dist/lib/node/chunk-FROKBEVH.cjs.map +7 -0
  33. package/dist/lib/node/{chunk-FR2K56EV.cjs → chunk-W64EOF2B.cjs} +10 -10
  34. package/dist/lib/node/{chunk-T4K27K2F.cjs → chunk-ZVQIICEN.cjs} +9 -8
  35. package/dist/lib/node/{chunk-T4K27K2F.cjs.map → chunk-ZVQIICEN.cjs.map} +3 -3
  36. package/dist/lib/node/{client-5BHSU35T.cjs → client-BKTGRBYM.cjs} +9 -9
  37. package/dist/lib/node/{client-5BHSU35T.cjs.map → client-BKTGRBYM.cjs.map} +3 -3
  38. package/dist/lib/node/index.cjs +70 -41
  39. package/dist/lib/node/index.cjs.map +2 -2
  40. package/dist/lib/node/{intent-resolver-PWCHHNV3.cjs → intent-resolver-Y73DGA7F.cjs} +81 -44
  41. package/dist/lib/node/intent-resolver-Y73DGA7F.cjs.map +7 -0
  42. package/dist/lib/node/meta.json +1 -1
  43. package/dist/lib/node/{migrations-YCBZUPWO.cjs → migrations-TGNI4FVL.cjs} +8 -8
  44. package/dist/lib/node/{react-context-MPHNGZHT.cjs → react-context-SF3M3YLR.cjs} +8 -8
  45. package/dist/lib/node/{react-surface-TPISDIHR.cjs → react-surface-KWTTP7ZX.cjs} +34 -26
  46. package/dist/lib/node/react-surface-KWTTP7ZX.cjs.map +7 -0
  47. package/dist/lib/node/{schema-NMMCMK64.cjs → schema-INQ72F54.cjs} +8 -8
  48. package/dist/lib/node/types.cjs +5 -3
  49. package/dist/lib/node/types.cjs.map +2 -2
  50. package/dist/lib/node-esm/app-graph-builder-KIN6NWFI.mjs +120 -0
  51. package/dist/lib/node-esm/app-graph-builder-KIN6NWFI.mjs.map +7 -0
  52. package/dist/lib/node-esm/chunk-6WHEPWQD.mjs +620 -0
  53. package/dist/lib/node-esm/chunk-6WHEPWQD.mjs.map +7 -0
  54. package/dist/lib/node-esm/{chunk-QJMF2MWP.mjs → chunk-AD5W5QCQ.mjs} +21 -4
  55. package/dist/lib/node-esm/{chunk-QJMF2MWP.mjs.map → chunk-AD5W5QCQ.mjs.map} +3 -3
  56. package/dist/lib/node-esm/{chunk-XLLNVLWU.mjs → chunk-BP56BGZR.mjs} +2 -2
  57. package/dist/lib/node-esm/{chunk-HO7WDE7C.mjs → chunk-HM7VB4ZV.mjs} +1 -1
  58. package/dist/lib/node-esm/{chunk-HO7WDE7C.mjs.map → chunk-HM7VB4ZV.mjs.map} +2 -2
  59. package/dist/lib/node-esm/{chunk-3OU2ZXLK.mjs → chunk-K3KGGSJZ.mjs} +3 -2
  60. package/dist/lib/node-esm/{chunk-3OU2ZXLK.mjs.map → chunk-K3KGGSJZ.mjs.map} +3 -3
  61. package/dist/lib/node-esm/{client-ZDGFGLX4.mjs → client-QQD6WFOB.mjs} +5 -5
  62. package/dist/lib/node-esm/{client-ZDGFGLX4.mjs.map → client-QQD6WFOB.mjs.map} +3 -3
  63. package/dist/lib/node-esm/index.mjs +51 -22
  64. package/dist/lib/node-esm/index.mjs.map +2 -2
  65. package/dist/lib/node-esm/{intent-resolver-FIXGPH7C.mjs → intent-resolver-K3CA4JRC.mjs} +65 -27
  66. package/dist/lib/node-esm/intent-resolver-K3CA4JRC.mjs.map +7 -0
  67. package/dist/lib/node-esm/meta.json +1 -1
  68. package/dist/lib/node-esm/{migrations-VIEIPYJW.mjs → migrations-IVGATGZR.mjs} +3 -3
  69. package/dist/lib/node-esm/{react-context-KUEOXWWK.mjs → react-context-EAKKTE5A.mjs} +3 -3
  70. package/dist/lib/node-esm/react-surface-ORRNXP2X.mjs +67 -0
  71. package/dist/lib/node-esm/react-surface-ORRNXP2X.mjs.map +7 -0
  72. package/dist/lib/node-esm/{schema-NE5IYLMD.mjs → schema-OK7HY3JJ.mjs} +3 -3
  73. package/dist/lib/node-esm/types.mjs +4 -2
  74. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  75. package/dist/types/src/capabilities/capabilities.d.ts +1 -0
  76. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  77. package/dist/types/src/capabilities/index.d.ts +4 -4
  78. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  79. package/dist/types/src/capabilities/intent-resolver.d.ts +2 -3
  80. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  81. package/dist/types/src/capabilities/react-surface.d.ts +3 -2
  82. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  83. package/dist/types/src/components/DevicesContainer.d.ts +5 -0
  84. package/dist/types/src/components/DevicesContainer.d.ts.map +1 -0
  85. package/dist/types/src/components/DevicesContainer.stories.d.ts +8 -0
  86. package/dist/types/src/components/DevicesContainer.stories.d.ts.map +1 -0
  87. package/dist/types/src/components/ProfileContainer.d.ts +3 -0
  88. package/dist/types/src/components/ProfileContainer.d.ts.map +1 -0
  89. package/dist/types/src/components/ProfileContainer.stories.d.ts +8 -0
  90. package/dist/types/src/components/ProfileContainer.stories.d.ts.map +1 -0
  91. package/dist/types/src/components/RecoveryCodeDialog.d.ts +1 -1
  92. package/dist/types/src/components/RecoveryCodeDialog.d.ts.map +1 -1
  93. package/dist/types/src/components/RecoveryCredentialsContainer.d.ts +4 -0
  94. package/dist/types/src/components/RecoveryCredentialsContainer.d.ts.map +1 -0
  95. package/dist/types/src/components/RecoveryCredentialsContainer.stories.d.ts +8 -0
  96. package/dist/types/src/components/RecoveryCredentialsContainer.stories.d.ts.map +1 -0
  97. package/dist/types/src/components/ResetDialog.d.ts +7 -0
  98. package/dist/types/src/components/ResetDialog.d.ts.map +1 -0
  99. package/dist/types/src/components/ResetDialog.stories.d.ts +10 -0
  100. package/dist/types/src/components/ResetDialog.stories.d.ts.map +1 -0
  101. package/dist/types/src/components/index.d.ts +4 -2
  102. package/dist/types/src/components/index.d.ts.map +1 -1
  103. package/dist/types/src/meta.d.ts +2 -4
  104. package/dist/types/src/meta.d.ts.map +1 -1
  105. package/dist/types/src/translations.d.ts +28 -1
  106. package/dist/types/src/translations.d.ts.map +1 -1
  107. package/dist/types/src/types.d.ts +17 -1
  108. package/dist/types/src/types.d.ts.map +1 -1
  109. package/package.json +26 -19
  110. package/src/ClientPlugin.ts +2 -2
  111. package/src/capabilities/app-graph-builder.ts +62 -6
  112. package/src/capabilities/capabilities.ts +1 -0
  113. package/src/capabilities/client.ts +1 -1
  114. package/src/capabilities/intent-resolver.ts +68 -23
  115. package/src/capabilities/react-surface.tsx +32 -24
  116. package/src/components/DevicesContainer.stories.tsx +45 -0
  117. package/src/components/DevicesContainer.tsx +268 -0
  118. package/src/components/ProfileContainer.stories.tsx +29 -0
  119. package/src/components/ProfileContainer.tsx +163 -0
  120. package/src/components/RecoveryCodeDialog.stories.tsx +2 -2
  121. package/src/components/RecoveryCodeDialog.tsx +1 -2
  122. package/src/components/{ManageCredentialsDialog.stories.tsx → RecoveryCredentialsContainer.stories.tsx} +4 -17
  123. package/src/components/RecoveryCredentialsContainer.tsx +74 -0
  124. package/src/components/ResetDialog.stories.tsx +47 -0
  125. package/src/components/ResetDialog.tsx +49 -0
  126. package/src/components/index.ts +4 -2
  127. package/src/meta.ts +2 -2
  128. package/src/translations.ts +45 -9
  129. package/src/types.ts +20 -1
  130. package/dist/lib/browser/app-graph-builder-ON64R7SO.mjs +0 -44
  131. package/dist/lib/browser/app-graph-builder-ON64R7SO.mjs.map +0 -7
  132. package/dist/lib/browser/chunk-HNJZ74DZ.mjs +0 -281
  133. package/dist/lib/browser/chunk-HNJZ74DZ.mjs.map +0 -7
  134. package/dist/lib/browser/intent-resolver-AIUHL34E.mjs.map +0 -7
  135. package/dist/lib/browser/react-surface-QGPWIUFR.mjs +0 -57
  136. package/dist/lib/browser/react-surface-QGPWIUFR.mjs.map +0 -7
  137. package/dist/lib/node/app-graph-builder-RW7ESEX3.cjs +0 -58
  138. package/dist/lib/node/app-graph-builder-RW7ESEX3.cjs.map +0 -7
  139. package/dist/lib/node/chunk-TREVKZCJ.cjs.map +0 -7
  140. package/dist/lib/node/chunk-UTG3UR3N.cjs +0 -309
  141. package/dist/lib/node/chunk-UTG3UR3N.cjs.map +0 -7
  142. package/dist/lib/node/intent-resolver-PWCHHNV3.cjs.map +0 -7
  143. package/dist/lib/node/react-surface-TPISDIHR.cjs.map +0 -7
  144. package/dist/lib/node-esm/app-graph-builder-TDYRWNI7.mjs +0 -45
  145. package/dist/lib/node-esm/app-graph-builder-TDYRWNI7.mjs.map +0 -7
  146. package/dist/lib/node-esm/chunk-SHJBU4QR.mjs +0 -282
  147. package/dist/lib/node-esm/chunk-SHJBU4QR.mjs.map +0 -7
  148. package/dist/lib/node-esm/intent-resolver-FIXGPH7C.mjs.map +0 -7
  149. package/dist/lib/node-esm/react-surface-KBROL4VH.mjs +0 -58
  150. package/dist/lib/node-esm/react-surface-KBROL4VH.mjs.map +0 -7
  151. package/dist/types/src/components/IdentityDialog.d.ts +0 -5
  152. package/dist/types/src/components/IdentityDialog.d.ts.map +0 -1
  153. package/dist/types/src/components/ManageCredentialsDialog.d.ts +0 -4
  154. package/dist/types/src/components/ManageCredentialsDialog.d.ts.map +0 -1
  155. package/dist/types/src/components/ManageCredentialsDialog.stories.d.ts +0 -8
  156. package/dist/types/src/components/ManageCredentialsDialog.stories.d.ts.map +0 -1
  157. package/dist/types/src/components/RecoverySetupDialog.d.ts +0 -4
  158. package/dist/types/src/components/RecoverySetupDialog.d.ts.map +0 -1
  159. package/dist/types/src/components/RecoverySetupDialog.stories.d.ts +0 -8
  160. package/dist/types/src/components/RecoverySetupDialog.stories.d.ts.map +0 -1
  161. package/src/components/IdentityDialog.tsx +0 -75
  162. package/src/components/ManageCredentialsDialog.tsx +0 -64
  163. package/src/components/RecoverySetupDialog.stories.tsx +0 -77
  164. package/src/components/RecoverySetupDialog.tsx +0 -52
  165. /package/dist/lib/browser/{chunk-OLUST5AO.mjs.map → chunk-4PLF3BEB.mjs.map} +0 -0
  166. /package/dist/lib/browser/{migrations-XODC625X.mjs.map → migrations-3DWFOL5Q.mjs.map} +0 -0
  167. /package/dist/lib/browser/{react-context-JBD7Y3SN.mjs.map → react-context-WEH7SL4I.mjs.map} +0 -0
  168. /package/dist/lib/browser/{schema-Y7ZNXYSL.mjs.map → schema-JQAT6Q7S.mjs.map} +0 -0
  169. /package/dist/lib/node/{chunk-FR2K56EV.cjs.map → chunk-W64EOF2B.cjs.map} +0 -0
  170. /package/dist/lib/node/{migrations-YCBZUPWO.cjs.map → migrations-TGNI4FVL.cjs.map} +0 -0
  171. /package/dist/lib/node/{react-context-MPHNGZHT.cjs.map → react-context-SF3M3YLR.cjs.map} +0 -0
  172. /package/dist/lib/node/{schema-NMMCMK64.cjs.map → schema-INQ72F54.cjs.map} +0 -0
  173. /package/dist/lib/node-esm/{chunk-XLLNVLWU.mjs.map → chunk-BP56BGZR.mjs.map} +0 -0
  174. /package/dist/lib/node-esm/{migrations-VIEIPYJW.mjs.map → migrations-IVGATGZR.mjs.map} +0 -0
  175. /package/dist/lib/node-esm/{react-context-KUEOXWWK.mjs.map → react-context-EAKKTE5A.mjs.map} +0 -0
  176. /package/dist/lib/node-esm/{schema-NE5IYLMD.mjs.map → schema-OK7HY3JJ.mjs.map} +0 -0
@@ -0,0 +1,163 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Schema as S } from 'effect';
6
+ import React, { type ChangeEvent, useCallback, useMemo, useState } from 'react';
7
+
8
+ import { debounce } from '@dxos/async';
9
+ import { useClient } from '@dxos/react-client';
10
+ import { type Identity, useIdentity } from '@dxos/react-client/halo';
11
+ import { ButtonGroup, Clipboard, Input, useTranslation } from '@dxos/react-ui';
12
+ import { Form, type InputComponent, ControlItem, ControlItemInput, ControlSection } from '@dxos/react-ui-form';
13
+ import { EmojiPickerBlock, HuePicker } from '@dxos/react-ui-pickers';
14
+ import { StackItem } from '@dxos/react-ui-stack';
15
+ import { hexToHue, hexToEmoji } from '@dxos/util';
16
+
17
+ import { CLIENT_PLUGIN } from '../meta';
18
+
19
+ // TODO(thure): Factor out?
20
+ const getDefaultHueValue = (identity: Identity | null) => hexToHue(identity?.identityKey.toHex() ?? '0');
21
+ const getDefaultEmojiValue = (identity: Identity | null) => hexToEmoji(identity?.identityKey.toHex() ?? '0');
22
+ const getHueValue = (identity: Identity | null) => identity?.profile?.data?.hue || getDefaultHueValue(identity);
23
+ const getEmojiValue = (identity: Identity | null) => identity?.profile?.data?.emoji || getDefaultEmojiValue(identity);
24
+
25
+ export const ProfileContainer = () => {
26
+ const { t } = useTranslation(CLIENT_PLUGIN);
27
+ const client = useClient();
28
+ const identity = useIdentity();
29
+ const [displayName, setDisplayNameDirectly] = useState(identity?.profile?.displayName ?? '');
30
+ const [emoji, setEmojiDirectly] = useState<string>(getEmojiValue(identity));
31
+ const [hue, setHueDirectly] = useState<string>(getHueValue(identity));
32
+
33
+ const updateProfile = useMemo(
34
+ () =>
35
+ debounce(
36
+ (profile: Partial<Profile>) =>
37
+ client.halo.updateProfile({
38
+ displayName: profile.displayName,
39
+ data: {
40
+ emoji: profile.emoji,
41
+ hue: profile.hue,
42
+ },
43
+ }),
44
+ 2_000,
45
+ ),
46
+ [],
47
+ );
48
+
49
+ const handleSave = useCallback(
50
+ (profile: Profile) => {
51
+ setDisplayNameDirectly(profile.displayName);
52
+ setEmojiDirectly(profile.emoji);
53
+ setHueDirectly(profile.hue);
54
+ updateProfile(profile);
55
+ },
56
+ [identity],
57
+ );
58
+
59
+ const values = useMemo(
60
+ () => ({
61
+ displayName,
62
+ emoji,
63
+ hue,
64
+ did: identity?.did,
65
+ }),
66
+ [identity, displayName, emoji, hue],
67
+ );
68
+
69
+ // TODO(wittjosiah): Integrate descriptions with the form schema.
70
+ const customElements: Partial<Record<string, InputComponent>> = useMemo(
71
+ () => ({
72
+ displayName: ({ type, label, getValue, onValueChange }) => {
73
+ const handleChange = useCallback(
74
+ ({ target: { value } }: ChangeEvent<HTMLInputElement>) => onValueChange(type, value),
75
+ [onValueChange, type],
76
+ );
77
+ return (
78
+ <ControlItemInput title={label} description={t('display name description')}>
79
+ <Input.TextInput
80
+ value={getValue()}
81
+ onChange={handleChange}
82
+ placeholder={t('display name input placeholder')}
83
+ classNames='min-is-64'
84
+ />
85
+ </ControlItemInput>
86
+ );
87
+ },
88
+ emoji: ({ type, label, getValue, onValueChange }) => {
89
+ const handleChange = useCallback((nextEmoji: string) => onValueChange(type, nextEmoji), [onValueChange, type]);
90
+ const handleEmojiReset = useCallback(
91
+ () => onValueChange(type, getDefaultEmojiValue(identity)),
92
+ [onValueChange, type],
93
+ );
94
+ return (
95
+ <ControlItem title={label} description={t('icon description')}>
96
+ <EmojiPickerBlock
97
+ triggerVariant='default'
98
+ emoji={getValue()}
99
+ onChangeEmoji={handleChange}
100
+ onClickClear={handleEmojiReset}
101
+ classNames='justify-self-end'
102
+ />
103
+ </ControlItem>
104
+ );
105
+ },
106
+ hue: ({ type, label, getValue, onValueChange }) => {
107
+ const handleChange = useCallback((nextHue: string) => onValueChange(type, nextHue), [onValueChange, type]);
108
+ const handleHueReset = useCallback(
109
+ () => onValueChange(type, getDefaultHueValue(identity)),
110
+ [onValueChange, type],
111
+ );
112
+ return (
113
+ <ControlItem title={label} description={t('hue description')}>
114
+ <HuePicker
115
+ value={getValue()}
116
+ onChange={handleChange}
117
+ onReset={handleHueReset}
118
+ classNames='[--hue-preview-size:1.5rem] justify-self-end'
119
+ />
120
+ </ControlItem>
121
+ );
122
+ },
123
+ // TODO(wittjosiah): We need text input annotations for disabled and copyable.
124
+ did: ({ label, getValue }) => {
125
+ return (
126
+ <ControlItemInput title={label} description={t('did description')}>
127
+ <ButtonGroup>
128
+ <Input.TextInput value={getValue()} disabled classNames='min-is-64' />
129
+ <Clipboard.IconButton value={getValue() ?? ''} />
130
+ </ButtonGroup>
131
+ </ControlItemInput>
132
+ );
133
+ },
134
+ }),
135
+ [t],
136
+ );
137
+
138
+ return (
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>
152
+ </StackItem.Content>
153
+ );
154
+ };
155
+
156
+ // TODO(wittjosiah): Integrate annotations with translations.
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' }),
162
+ });
163
+ type Profile = S.Schema.Type<typeof ProfileSchema>;
@@ -15,7 +15,7 @@ import { withTheme, withLayout } from '@dxos/storybook-utils';
15
15
  import { RecoveryCodeDialog, type RecoveryCodeDialogProps } from './RecoveryCodeDialog';
16
16
  import translations from '../translations';
17
17
 
18
- const Render = () => {
18
+ const DefaultStory = () => {
19
19
  const client = useClient();
20
20
  const [recoveryCode, setRecoveryCode] = useState<string>();
21
21
  useAsyncEffect(async () => {
@@ -35,7 +35,7 @@ const Render = () => {
35
35
  const meta: Meta<RecoveryCodeDialogProps> = {
36
36
  title: 'plugins/plugin-client/RecoveryCodeDialog',
37
37
  component: RecoveryCodeDialog,
38
- render: Render,
38
+ render: DefaultStory,
39
39
  decorators: [withClientProvider({ createIdentity: true }), withTheme, withLayout({ tooltips: true })],
40
40
  parameters: {
41
41
  layout: 'fullscreen',
@@ -8,13 +8,12 @@ import { AlertDialog, Button, Clipboard, Input, useTranslation } from '@dxos/rea
8
8
 
9
9
  import { CLIENT_PLUGIN } from '../meta';
10
10
 
11
- export const RECOVER_CODE_DIALOG = `${CLIENT_PLUGIN}/RecoveryCodeDialog`;
11
+ export const RECOVERY_CODE_DIALOG = `${CLIENT_PLUGIN}/RecoveryCodeDialog`;
12
12
 
13
13
  export type RecoveryCodeDialogProps = {
14
14
  code: string;
15
15
  };
16
16
 
17
- // TODO(wittjosiah): Factor panel out to @dxos/shell.
18
17
  export const RecoveryCodeDialog = ({ code }: RecoveryCodeDialogProps) => {
19
18
  const { t } = useTranslation(CLIENT_PLUGIN);
20
19
  const [confirmation, setConfirmation] = useState(false);
@@ -5,31 +5,18 @@
5
5
  import '@dxos-theme';
6
6
 
7
7
  import { type StoryObj, type Meta } from '@storybook/react';
8
- import React from 'react';
9
8
 
10
9
  import { IntentPlugin } from '@dxos/app-framework';
11
10
  import { withPluginManager } from '@dxos/app-framework/testing';
12
- import { AlertDialog } from '@dxos/react-ui';
13
11
  import { withTheme, withLayout } from '@dxos/storybook-utils';
14
12
 
15
- import { ManageCredentialsDialog } from './ManageCredentialsDialog';
13
+ import { RecoveryCredentialsContainer } from './RecoveryCredentialsContainer';
16
14
  import { ClientPlugin } from '../ClientPlugin';
17
15
  import translations from '../translations';
18
16
 
19
- const Render = () => {
20
- return (
21
- <AlertDialog.Root open>
22
- <AlertDialog.Overlay>
23
- <ManageCredentialsDialog />
24
- </AlertDialog.Overlay>
25
- </AlertDialog.Root>
26
- );
27
- };
28
-
29
17
  const meta: Meta = {
30
- title: 'plugins/plugin-client/ManageCredentialsDialog',
31
- component: ManageCredentialsDialog,
32
- render: Render,
18
+ title: 'plugins/plugin-client/RecoveryCredentialsContainer',
19
+ component: RecoveryCredentialsContainer,
33
20
  decorators: [
34
21
  withPluginManager({
35
22
  plugins: [
@@ -52,6 +39,6 @@ const meta: Meta = {
52
39
 
53
40
  export default meta;
54
41
 
55
- type Story = StoryObj<typeof ManageCredentialsDialog>;
42
+ type Story = StoryObj<typeof RecoveryCredentialsContainer>;
56
43
 
57
44
  export const Default: Story = {};
@@ -0,0 +1,74 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { createIntent, useIntentDispatcher } from '@dxos/app-framework';
8
+ import { useCredentials } from '@dxos/react-client/halo';
9
+ import { Icon, IconButton, List, ListItem, useTranslation, Message } from '@dxos/react-ui';
10
+ import { ControlGroup, ControlItem, ControlSection } from '@dxos/react-ui-form';
11
+ import { StackItem } from '@dxos/react-ui-stack';
12
+
13
+ import { CLIENT_PLUGIN } from '../meta';
14
+ import { ClientAction } from '../types';
15
+
16
+ export const MANAGE_CREDENTIALS_DIALOG = `${CLIENT_PLUGIN}/ManageCredentialsDialog`;
17
+
18
+ export const RecoveryCredentialsContainer = () => {
19
+ const { t } = useTranslation(CLIENT_PLUGIN);
20
+ const { dispatchPromise: dispatch } = useIntentDispatcher();
21
+ const credentials = useCredentials();
22
+ const recoveryCredentials = credentials.filter(
23
+ (credential) => credential.subject.assertion['@type'] === 'dxos.halo.credentials.IdentityRecovery',
24
+ );
25
+
26
+ return (
27
+ <StackItem.Content classNames='p-2 block overflow-y-auto'>
28
+ <ControlSection title={t('recovery setup dialog title')} description={t('recovery setup dialog description')}>
29
+ <ControlGroup>
30
+ <ControlItem title={t('create passkey label')} description={t('create passkey description')}>
31
+ <IconButton
32
+ label={t('create passkey label')}
33
+ icon='ph--key--duotone'
34
+ variant='primary'
35
+ size={5}
36
+ onClick={() => dispatch(createIntent(ClientAction.CreatePasskey))}
37
+ />
38
+ </ControlItem>
39
+ <ControlItem title={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
+ size={5}
45
+ onClick={() => dispatch(createIntent(ClientAction.CreateRecoveryCode))}
46
+ />
47
+ </ControlItem>
48
+ </ControlGroup>
49
+ </ControlSection>
50
+ <ControlSection title={t('credentials list label')}>
51
+ {recoveryCredentials.length < 1 ? (
52
+ <Message.Root valence='error' className='container-max-width'>
53
+ <Message.Title>
54
+ <Icon icon='ph--shield-warning--duotone' size={5} classNames='inline-block align-top mbs-px mie-1' />
55
+ {t('no credentials title')}
56
+ </Message.Title>
57
+ <Message.Body>{t('no credentials message')}</Message.Body>
58
+ </Message.Root>
59
+ ) : (
60
+ <List classNames='container-max-width pli-2'>
61
+ {recoveryCredentials.map((credential) => (
62
+ <ListItem.Root key={credential.id?.toHex()}>
63
+ <ListItem.Endcap>
64
+ <Icon icon='ph--key--regular' size={5} />
65
+ </ListItem.Endcap>
66
+ <ListItem.Heading>{credential.issuanceDate.toLocaleString()}</ListItem.Heading>
67
+ </ListItem.Root>
68
+ ))}
69
+ </List>
70
+ )}
71
+ </ControlSection>
72
+ </StackItem.Content>
73
+ );
74
+ };
@@ -0,0 +1,47 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import { type StoryObj, type Meta } from '@storybook/react';
8
+ import React from 'react';
9
+
10
+ import { IntentPlugin } from '@dxos/app-framework';
11
+ import { withPluginManager } from '@dxos/app-framework/testing';
12
+ import { Dialog } from '@dxos/react-ui';
13
+ import { withTheme, withLayout } from '@dxos/storybook-utils';
14
+
15
+ import { ResetDialog, type ResetDialogProps } from './ResetDialog';
16
+ import translations from '../translations';
17
+
18
+ const Render = (props: ResetDialogProps) => {
19
+ return (
20
+ <Dialog.Root open>
21
+ <Dialog.Overlay>
22
+ <ResetDialog onReset={() => console.log('reset')} {...props} />
23
+ </Dialog.Overlay>
24
+ </Dialog.Root>
25
+ );
26
+ };
27
+
28
+ const meta: Meta<ResetDialogProps> = {
29
+ title: 'plugins/plugin-client/ResetDialog',
30
+ component: ResetDialog,
31
+ render: Render,
32
+ decorators: [withPluginManager({ plugins: [IntentPlugin()] }), withTheme, withLayout({ tooltips: true })],
33
+ parameters: {
34
+ layout: 'fullscreen',
35
+ translations,
36
+ },
37
+ };
38
+
39
+ export default meta;
40
+
41
+ type Story = StoryObj<ResetDialogProps>;
42
+
43
+ export const Default: Story = { args: { mode: 'reset storage' } };
44
+
45
+ export const JoinNewIdentity: Story = { args: { mode: 'join new identity' } };
46
+
47
+ export const Recover: Story = { args: { mode: 'recover' } };
@@ -0,0 +1,49 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React, { useCallback } from 'react';
6
+
7
+ import { createIntent, LayoutAction, useIntentDispatcher } from '@dxos/app-framework';
8
+ import { useClient } from '@dxos/react-client';
9
+ import { Dialog, useTranslation } from '@dxos/react-ui';
10
+ import { ConfirmReset, type ConfirmResetProps } from '@dxos/shell/react';
11
+
12
+ import { CLIENT_PLUGIN } from '../meta';
13
+ import { type ClientPluginOptions } from '../types';
14
+
15
+ export const RESET_DIALOG = `${CLIENT_PLUGIN}/ResetDialog`;
16
+
17
+ export type ResetDialogProps = Pick<ConfirmResetProps, 'mode'> & Pick<ClientPluginOptions, 'onReset'>;
18
+
19
+ export const ResetDialog = ({ mode, onReset }: ResetDialogProps) => {
20
+ const { t } = useTranslation(CLIENT_PLUGIN);
21
+ const { dispatchPromise: dispatch } = useIntentDispatcher();
22
+ const client = useClient();
23
+
24
+ const handleReset = useCallback(async () => {
25
+ await client.reset();
26
+ const target =
27
+ mode === 'join new identity' ? 'deviceInvitation' : mode === 'recover' ? 'recoverIdentity' : undefined;
28
+ await onReset?.({ target });
29
+ }, [dispatch, client, mode, onReset]);
30
+
31
+ const handleCancel = useCallback(() => {
32
+ void dispatch(
33
+ createIntent(LayoutAction.UpdateDialog, {
34
+ part: 'dialog',
35
+ options: { state: false },
36
+ }),
37
+ );
38
+ }, [dispatch]);
39
+
40
+ // TODO(wittjosiah): Add the sr-only translations.
41
+ // TODO(wittjosiah): Add missing descriptions to other dialogs.
42
+ return (
43
+ <Dialog.Content classNames='bs-content min-bs-[15rem] max-bs-full md:max-is-[40rem] overflow-hidden'>
44
+ <Dialog.Title classNames='sr-only'>{t('reset dialog title')}</Dialog.Title>
45
+ <Dialog.Description classNames='sr-only'>{t('reset dialog description')}</Dialog.Description>
46
+ <ConfirmReset active mode={mode} onConfirm={handleReset} onCancel={handleCancel} />
47
+ </Dialog.Content>
48
+ );
49
+ };
@@ -2,7 +2,9 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- export * from './IdentityDialog';
5
+ export * from './DevicesContainer';
6
6
  export * from './JoinDialog';
7
+ export * from './ProfileContainer';
7
8
  export * from './RecoveryCodeDialog';
8
- export * from './RecoverySetupDialog';
9
+ export * from './RecoveryCredentialsContainer';
10
+ export * from './ResetDialog';
package/src/meta.ts CHANGED
@@ -6,7 +6,7 @@ import { type PluginMeta } from '@dxos/app-framework';
6
6
 
7
7
  export const CLIENT_PLUGIN = 'dxos.org/plugin/client';
8
8
 
9
- export const meta = {
9
+ export const meta: PluginMeta = {
10
10
  id: CLIENT_PLUGIN,
11
11
  name: 'Client',
12
- } satisfies PluginMeta;
12
+ };
@@ -8,23 +8,59 @@ export default [
8
8
  {
9
9
  'en-US': {
10
10
  [CLIENT_PLUGIN]: {
11
- 'open shell label': 'Open HALO',
11
+ 'account label': 'User Account',
12
+ 'profile label': 'Profile',
13
+ 'profile description': 'You can adjust how you’re represented in Composer here.',
14
+ 'devices label': 'Devices',
15
+ 'devices verbose label': 'Manage devices',
16
+ 'add device description':
17
+ 'Authenticating another device is a two-step process. To get started, create a code using the button below.',
18
+ 'devices description':
19
+ 'You can add and remove devices, configure your agent in the cloud, reset this device, or log in as a different identity here.',
20
+ 'create device invitation label': 'Create code',
21
+ 'qr code description':
22
+ 'Scan this QR code using the device you want to log in to, or copy the URL and share it with the new device.',
23
+ 'security label': 'Security',
24
+ 'reset device description': 'Log out from this device, erasing all the data on this device.',
25
+ 'join new identity description':
26
+ 'Log out from this device, erasing all the data currently on this device, and use a QR code or URL to log in.',
27
+ 'recover identity description':
28
+ 'Log out from this device, erasing all the data currently on this device, and use a passkey or recovery code to log in.',
29
+ 'danger zone title': 'Log out',
30
+ 'danger zone description':
31
+ 'Because Composer is decentralized, logging out entails erasing all the data on this device. If you have any data on this device that you’d like to keep, you can log in on a separate device using a passkey or complete a peer-to-peer device invitation above.',
32
+ 'display name label': 'Display name',
33
+ 'display name description': 'Your name as it appears in the app.',
34
+ 'display name input placeholder': 'Enter a name',
35
+ 'icon label': 'Avatar',
36
+ 'icon description': 'The emoji used to represent you in the app.',
37
+ 'hue label': 'Color',
38
+ 'hue description':
39
+ 'The color used to represent you in the app, including as your avatar’s background, your cursor as it appears to others, and the messages you send in group threads.',
40
+ 'did label': 'DID',
41
+ 'did description': 'Your unique ID.',
42
+ 'open user account label': 'Open user account',
12
43
  'manage credentials dialog title': 'Manage Account Recovery',
13
- 'recovery setup dialog title': 'Account Recovery',
44
+ 'credentials list label': 'Recovery credentials',
45
+ 'no credentials title': 'There’s no way to recover your account yet.',
46
+ 'no credentials message': 'Create a recovery credential above to secure your account.',
47
+ 'recovery setup dialog title': 'Account Security',
14
48
  'recovery setup dialog description':
15
- 'In order to recover your account you need to register a recovery credential. Passkeys are the recommended way to do this, they can be stored in your browser or in a password manager.',
49
+ 'In order to maintain access to your account you need to register a recovery credential. Passkeys are the recommended way to do this, they can be stored in your browser or in a password manager.',
16
50
  'create passkey label': 'Create Passkey',
51
+ // TODO(wittjosiah): At link to user-focused passkey information.
52
+ // Something like https://www.tomsguide.com/news/what-are-passkeys.
17
53
  'create passkey description':
18
- 'A passkey is a secure and easy to use credential that can be used to recover your account.',
54
+ 'A passkey is a secure and easy to use credential that can be used to recover your account.',
19
55
  'create recovery code label': 'Create Recovery Code',
20
- 'create recovery code description': 'A recovery code is 12 word phrase representing a private key.',
56
+ 'create recovery code description': 'A recovery code is 12 word phrase representing a private key.',
21
57
  'recovery code dialog title': 'Recovery Code',
22
58
  'recovery code dialog description':
23
- 'This is your identity recovery code, store it in a safe place. You can use it to recover your identity if you ever lose access to your devices.',
24
- 'recovery code dialog warning 1': 'NOTE: This code will not be displayed again.',
59
+ 'This is your identity recovery code, store it in a safe place. You can use it to recover your identity if you ever lose access to your devices.',
60
+ 'recovery code dialog warning 1': 'NOTE: This code will not be displayed again.',
25
61
  'recovery code dialog warning 2':
26
- 'It is your private key for recovering DXOS data. Anyone with this key will be able to gain access to your account.',
27
- 'recovery code confirmation label': 'Please confirm you have saved the code.',
62
+ 'It is your private key for recovering DXOS data. Anyone with this key will be able to gain access to your account.',
63
+ 'recovery code confirmation label': 'Please confirm you have saved the code.',
28
64
  'continue label': 'Continue',
29
65
  },
30
66
  },
package/src/types.ts CHANGED
@@ -50,7 +50,7 @@ export namespace ClientAction {
50
50
 
51
51
  export class ResetStorage extends S.TaggedClass<ResetStorage>()(`${CLIENT_ACTION}/reset-storage`, {
52
52
  input: S.Struct({
53
- target: S.optional(S.String),
53
+ mode: S.optional(S.String),
54
54
  }),
55
55
  output: S.Void,
56
56
  }) {}
@@ -74,6 +74,13 @@ export namespace ClientAction {
74
74
  input: S.Void,
75
75
  output: S.Void,
76
76
  }) {}
77
+
78
+ export class RedeemToken extends S.TaggedClass<RedeemToken>()(`${CLIENT_ACTION}/redeem-token`, {
79
+ input: S.Struct({
80
+ token: S.String,
81
+ }),
82
+ output: S.Void,
83
+ }) {}
77
84
  }
78
85
 
79
86
  export type ClientPluginOptions = ClientOptions & {
@@ -97,3 +104,15 @@ export type ClientPluginOptions = ClientOptions & {
97
104
  */
98
105
  onReset?: (params: { target?: string }) => MaybePromise<void>;
99
106
  };
107
+
108
+ export namespace Account {
109
+ // TODO(wittjosiah): Cannot use slashes in ids until we have a router which decouples ids from url paths.
110
+ const _id = 'dxos.org.plugin.client.account';
111
+ // TODO(wittjosiah): This is a hack to prevent the previous deck from being set for pinned items.
112
+ // Ideally this should be worked into the data model in a generic way.
113
+ export const id = `!${_id}`;
114
+
115
+ export const Profile = `${_id}.profile`;
116
+ export const Devices = `${_id}.devices`;
117
+ export const Security = `${_id}.security`;
118
+ }
@@ -1,44 +0,0 @@
1
- import {
2
- ClientAction
3
- } from "./chunk-7DPLXOMO.mjs";
4
- import {
5
- CLIENT_PLUGIN
6
- } from "./chunk-NHIL4T3L.mjs";
7
-
8
- // packages/plugins/plugin-client/src/capabilities/app-graph-builder.ts
9
- import { createIntent } from "@dxos/app-framework";
10
- import { Capabilities, contributes } from "@dxos/app-framework";
11
- import { createExtension } from "@dxos/plugin-graph";
12
- var app_graph_builder_default = (context) => contributes(Capabilities.AppGraphBuilder, createExtension({
13
- id: CLIENT_PLUGIN,
14
- filter: (node) => node.id === "root",
15
- actions: () => [
16
- {
17
- id: `${CLIENT_PLUGIN}/open-shell`,
18
- data: async () => {
19
- const { dispatchPromise: dispatch } = context.requestCapability(Capabilities.IntentDispatcher);
20
- await dispatch(createIntent(ClientAction.ShareIdentity));
21
- },
22
- properties: {
23
- label: [
24
- "open shell label",
25
- {
26
- ns: CLIENT_PLUGIN
27
- }
28
- ],
29
- icon: "ph--address-book--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
- testId: "clientPlugin.openShell"
37
- }
38
- }
39
- ]
40
- }));
41
- export {
42
- app_graph_builder_default as default
43
- };
44
- //# sourceMappingURL=app-graph-builder-ON64R7SO.mjs.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/capabilities/app-graph-builder.ts"],
4
- "sourcesContent": ["//\n// Copyright 2025 DXOS.org\n//\n\nimport { createIntent } from '@dxos/app-framework';\nimport { Capabilities, contributes, type PluginsContext } from '@dxos/app-framework';\nimport { createExtension, type Node } from '@dxos/plugin-graph';\n\nimport { CLIENT_PLUGIN } from '../meta';\nimport { ClientAction } from '../types';\n\nexport default (context: PluginsContext) =>\n contributes(\n Capabilities.AppGraphBuilder,\n createExtension({\n id: CLIENT_PLUGIN,\n filter: (node): node is Node<null> => node.id === 'root',\n actions: () => [\n {\n id: `${CLIENT_PLUGIN}/open-shell`,\n data: async () => {\n const { dispatchPromise: dispatch } = context.requestCapability(Capabilities.IntentDispatcher);\n await dispatch(createIntent(ClientAction.ShareIdentity));\n },\n properties: {\n label: ['open shell label', { ns: CLIENT_PLUGIN }],\n icon: 'ph--address-book--regular',\n keyBinding: {\n macos: 'meta+shift+.',\n // TODO(wittjosiah): Test on windows to see if it behaves the same as linux.\n windows: 'alt+shift+.',\n linux: 'alt+shift+>',\n },\n testId: 'clientPlugin.openShell',\n },\n },\n ],\n }),\n );\n"],
5
- "mappings": ";;;;;;;;AAIA,SAASA,oBAAoB;AAC7B,SAASC,cAAcC,mBAAwC;AAC/D,SAASC,uBAAkC;AAK3C,IAAA,4BAAe,CAACC,YACdC,YACEC,aAAaC,iBACbC,gBAAgB;EACdC,IAAIC;EACJC,QAAQ,CAACC,SAA6BA,KAAKH,OAAO;EAClDI,SAAS,MAAM;IACb;MACEJ,IAAI,GAAGC,aAAAA;MACPI,MAAM,YAAA;AACJ,cAAM,EAAEC,iBAAiBC,SAAQ,IAAKZ,QAAQa,kBAAkBX,aAAaY,gBAAgB;AAC7F,cAAMF,SAASG,aAAaC,aAAaC,aAAa,CAAA;MACxD;MACAC,YAAY;QACVC,OAAO;UAAC;UAAoB;YAAEC,IAAId;UAAc;;QAChDe,MAAM;QACNC,YAAY;UACVC,OAAO;;UAEPC,SAAS;UACTC,OAAO;QACT;QACAC,QAAQ;MACV;IACF;;AAEJ,CAAA,CAAA;",
6
- "names": ["createIntent", "Capabilities", "contributes", "createExtension", "context", "contributes", "Capabilities", "AppGraphBuilder", "createExtension", "id", "CLIENT_PLUGIN", "filter", "node", "actions", "data", "dispatchPromise", "dispatch", "requestCapability", "IntentDispatcher", "createIntent", "ClientAction", "ShareIdentity", "properties", "label", "ns", "icon", "keyBinding", "macos", "windows", "linux", "testId"]
7
- }