@dxos/react-client 0.8.4-main.dedc0f3 → 0.8.4-main.dfabb4ec29

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/.storybook/main.mts +9 -0
  2. package/.storybook/preview.mts +8 -0
  3. package/dist/lib/browser/chunk-3LVU7RNK.mjs +16 -0
  4. package/dist/lib/browser/chunk-3LVU7RNK.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-CZMIVAGI.mjs +8 -0
  6. package/dist/lib/browser/chunk-CZMIVAGI.mjs.map +7 -0
  7. package/dist/lib/browser/chunk-XT352YI6.mjs +134 -0
  8. package/dist/lib/browser/chunk-XT352YI6.mjs.map +7 -0
  9. package/dist/lib/browser/{chunk-BXOLJYAY.mjs → chunk-YHE6WD44.mjs} +4 -14
  10. package/dist/lib/browser/chunk-YHE6WD44.mjs.map +7 -0
  11. package/dist/lib/browser/devtools/index.mjs +3 -7
  12. package/dist/lib/browser/devtools/index.mjs.map +2 -2
  13. package/dist/lib/browser/echo/index.mjs +53 -119
  14. package/dist/lib/browser/echo/index.mjs.map +4 -4
  15. package/dist/lib/browser/halo/index.mjs +8 -21
  16. package/dist/lib/browser/halo/index.mjs.map +2 -2
  17. package/dist/lib/browser/index.mjs +99 -12
  18. package/dist/lib/browser/index.mjs.map +4 -4
  19. package/dist/lib/browser/invitations/index.mjs +1 -1
  20. package/dist/lib/browser/mesh/index.mjs +2 -1
  21. package/dist/lib/browser/mesh/index.mjs.map +1 -1
  22. package/dist/lib/browser/meta.json +1 -1
  23. package/dist/lib/browser/testing/index.mjs +86 -127
  24. package/dist/lib/browser/testing/index.mjs.map +3 -3
  25. package/dist/lib/node-esm/chunk-CUDKNAMV.mjs +17 -0
  26. package/dist/lib/node-esm/chunk-CUDKNAMV.mjs.map +7 -0
  27. package/dist/lib/node-esm/{chunk-256IHY6F.mjs → chunk-RQJPEHDY.mjs} +4 -14
  28. package/dist/lib/node-esm/chunk-RQJPEHDY.mjs.map +7 -0
  29. package/dist/lib/node-esm/chunk-WL3Z5HOH.mjs +135 -0
  30. package/dist/lib/node-esm/chunk-WL3Z5HOH.mjs.map +7 -0
  31. package/dist/lib/node-esm/chunk-WUTK2IJF.mjs +10 -0
  32. package/dist/lib/node-esm/chunk-WUTK2IJF.mjs.map +7 -0
  33. package/dist/lib/node-esm/devtools/index.mjs +3 -7
  34. package/dist/lib/node-esm/devtools/index.mjs.map +2 -2
  35. package/dist/lib/node-esm/echo/index.mjs +53 -119
  36. package/dist/lib/node-esm/echo/index.mjs.map +4 -4
  37. package/dist/lib/node-esm/halo/index.mjs +8 -21
  38. package/dist/lib/node-esm/halo/index.mjs.map +2 -2
  39. package/dist/lib/node-esm/index.mjs +99 -12
  40. package/dist/lib/node-esm/index.mjs.map +4 -4
  41. package/dist/lib/node-esm/invitations/index.mjs +1 -1
  42. package/dist/lib/node-esm/mesh/index.mjs +2 -1
  43. package/dist/lib/node-esm/mesh/index.mjs.map +1 -1
  44. package/dist/lib/node-esm/meta.json +1 -1
  45. package/dist/lib/node-esm/testing/index.mjs +86 -127
  46. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  47. package/dist/types/src/banner.d.ts.map +1 -1
  48. package/dist/types/src/client/AgentHostingProvider.d.ts.map +1 -1
  49. package/dist/types/src/client/ClientProvider.d.ts +1 -11
  50. package/dist/types/src/client/ClientProvider.d.ts.map +1 -1
  51. package/dist/types/src/client/useClientStatus.d.ts.map +1 -1
  52. package/dist/types/src/devtools/useStream.d.ts.map +1 -1
  53. package/dist/types/src/echo/index.d.ts +2 -2
  54. package/dist/types/src/echo/index.d.ts.map +1 -1
  55. package/dist/types/src/echo/useMembers.d.ts +2 -2
  56. package/dist/types/src/echo/useMembers.d.ts.map +1 -1
  57. package/dist/types/src/echo/useQueue.d.ts +3 -2
  58. package/dist/types/src/echo/useQueue.d.ts.map +1 -1
  59. package/dist/types/src/echo/useSpaceInvitations.d.ts +3 -2
  60. package/dist/types/src/echo/useSpaceInvitations.d.ts.map +1 -1
  61. package/dist/types/src/echo/useSpaceProperties.d.ts +13 -0
  62. package/dist/types/src/echo/useSpaceProperties.d.ts.map +1 -0
  63. package/dist/types/src/echo/useSpaces.d.ts +19 -9
  64. package/dist/types/src/echo/useSpaces.d.ts.map +1 -1
  65. package/dist/types/src/echo/useSubscription.d.ts +1 -1
  66. package/dist/types/src/echo/useSubscription.d.ts.map +1 -1
  67. package/dist/types/src/echo/useSyncState.d.ts.map +1 -1
  68. package/dist/types/src/halo/Passkey.stories.d.ts +1 -1
  69. package/dist/types/src/halo/Passkey.stories.d.ts.map +1 -1
  70. package/dist/types/src/halo/useHaloInvitations.d.ts.map +1 -1
  71. package/dist/types/src/halo/useKeyStore.d.ts.map +1 -1
  72. package/dist/types/src/invitations/useInvitationStatus.d.ts +1 -1
  73. package/dist/types/src/invitations/useInvitationStatus.d.ts.map +1 -1
  74. package/dist/types/src/testing/ClientRepeater.d.ts +6 -5
  75. package/dist/types/src/testing/ClientRepeater.d.ts.map +1 -1
  76. package/dist/types/src/testing/ClientRepeater.stories.d.ts +0 -1
  77. package/dist/types/src/testing/ClientRepeater.stories.d.ts.map +1 -1
  78. package/dist/types/src/testing/context.d.ts +4 -2
  79. package/dist/types/src/testing/context.d.ts.map +1 -1
  80. package/dist/types/src/testing/util.d.ts.map +1 -1
  81. package/dist/types/src/testing/withClientProvider.d.ts +5 -9
  82. package/dist/types/src/testing/withClientProvider.d.ts.map +1 -1
  83. package/dist/types/src/testing/withClientProvider.stories.d.ts +3 -2
  84. package/dist/types/src/testing/withClientProvider.stories.d.ts.map +1 -1
  85. package/dist/types/tsconfig.tsbuildinfo +1 -1
  86. package/docs/README.md +38 -38
  87. package/moon.yml +3 -2
  88. package/package.json +35 -58
  89. package/src/client/ClientProvider.test.tsx +0 -1
  90. package/src/client/ClientProvider.tsx +12 -27
  91. package/src/client/useConfig.test.tsx +0 -1
  92. package/src/echo/index.ts +2 -2
  93. package/src/echo/useMembers.ts +5 -3
  94. package/src/echo/useQueue.ts +3 -4
  95. package/src/echo/useSpaceInvitations.ts +9 -5
  96. package/src/echo/useSpaceProperties.ts +28 -0
  97. package/src/echo/useSpaces.test.tsx +7 -14
  98. package/src/echo/useSpaces.ts +33 -27
  99. package/src/echo/useSubscription.ts +1 -1
  100. package/src/echo/useSyncState.ts +4 -3
  101. package/src/halo/Passkey.stories.tsx +16 -22
  102. package/src/halo/useDevices.test.tsx +0 -1
  103. package/src/invitations/useInvitationStatus.ts +9 -8
  104. package/src/testing/ClientRepeater.stories.tsx +12 -12
  105. package/src/testing/ClientRepeater.tsx +14 -18
  106. package/src/testing/context.tsx +4 -3
  107. package/src/testing/util.tsx +0 -1
  108. package/src/testing/withClientProvider.stories.tsx +23 -15
  109. package/src/testing/withClientProvider.tsx +74 -92
  110. package/tsconfig.json +3 -6
  111. package/typedoc.json +1 -1
  112. package/vitest.config.ts +10 -16
  113. package/.swc/plugins/linux_x86_64_19.0.0/727453fb3a62f7f1d952a41e051ca8a6f88cadc45cee43c6a4d1aa45f9b75665.wasmer-v7 +0 -0
  114. package/dist/lib/browser/chunk-BXOLJYAY.mjs.map +0 -7
  115. package/dist/lib/browser/chunk-TA2HGRAF.mjs +0 -309
  116. package/dist/lib/browser/chunk-TA2HGRAF.mjs.map +0 -7
  117. package/dist/lib/node-esm/chunk-256IHY6F.mjs.map +0 -7
  118. package/dist/lib/node-esm/chunk-ZJMNF7GC.mjs +0 -311
  119. package/dist/lib/node-esm/chunk-ZJMNF7GC.mjs.map +0 -7
  120. package/dist/types/src/echo/useQuery.d.ts +0 -11
  121. package/dist/types/src/echo/useQuery.d.ts.map +0 -1
  122. package/dist/types/src/echo/useQuery.test.d.ts +0 -2
  123. package/dist/types/src/echo/useQuery.test.d.ts.map +0 -1
  124. package/dist/types/src/echo/useSchema.d.ts +0 -8
  125. package/dist/types/src/echo/useSchema.d.ts.map +0 -1
  126. package/dist/types/src/playwright/playwright.config.d.ts +0 -3
  127. package/dist/types/src/playwright/playwright.config.d.ts.map +0 -1
  128. package/dist/types/src/playwright/smoke.spec.d.ts +0 -2
  129. package/dist/types/src/playwright/smoke.spec.d.ts.map +0 -1
  130. package/src/echo/useQuery.test.tsx +0 -129
  131. package/src/echo/useQuery.ts +0 -56
  132. package/src/echo/useSchema.ts +0 -54
  133. package/src/playwright/playwright.config.ts +0 -17
  134. package/src/playwright/smoke.spec.ts +0 -24
@@ -8,11 +8,12 @@ import { type PeerSyncState, type Space, type SpaceSyncStateMap } from '@dxos/cl
8
8
  import { Context } from '@dxos/context';
9
9
  import { type SpaceId } from '@dxos/keys';
10
10
  import { EdgeService } from '@dxos/protocols';
11
+ import { compositeKey } from '@dxos/util';
11
12
 
12
13
  import { useClient } from '../client';
13
14
 
14
15
  const isEdgePeerId = (peerId: string, spaceId: SpaceId) =>
15
- peerId.startsWith(`${EdgeService.AUTOMERGE_REPLICATOR}:${spaceId}`);
16
+ peerId.startsWith(compositeKey(EdgeService.AUTOMERGE_REPLICATOR, spaceId));
16
17
 
17
18
  /**
18
19
  * Hook Subscribes to sync state for each space.
@@ -30,7 +31,7 @@ export const useSyncState = (): SpaceSyncStateMap => {
30
31
  }
31
32
 
32
33
  ctx.onDispose(
33
- space.db.subscribeToSyncState(ctx, ({ peers = [] }) => {
34
+ space.internal.db.subscribeToSyncState(ctx, ({ peers = [] }) => {
34
35
  const syncState = peers.find((state) => isEdgePeerId(state.peerId, space.id));
35
36
  if (syncState) {
36
37
  setSpaceState((spaceState) => ({ ...spaceState, [space.id]: syncState }));
@@ -62,7 +63,7 @@ export const useSpaceSyncState = (space: Space): PeerSyncState | undefined => {
62
63
 
63
64
  useEffect(() => {
64
65
  const ctx = new Context();
65
- space.db.subscribeToSyncState(ctx, ({ peers = [] }) => {
66
+ space.internal.db.subscribeToSyncState(ctx, ({ peers = [] }) => {
66
67
  const syncState = peers.find((state) => isEdgePeerId(state.peerId, space.id));
67
68
  if (syncState) {
68
69
  setSpaceState(syncState);
@@ -2,8 +2,6 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React, { useCallback } from 'react';
9
7
 
@@ -11,12 +9,11 @@ import { Config, PublicKey } from '@dxos/client';
11
9
  import { invariant } from '@dxos/invariant';
12
10
  import { log } from '@dxos/log';
13
11
  import { Button } from '@dxos/react-ui';
14
- import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
15
- import { withTheme } from '@dxos/storybook-utils';
12
+ import { JsonHighlighter } from '@dxos/react-ui-syntax-highlighter';
13
+ import { withTheme } from '@dxos/react-ui/testing';
16
14
 
17
15
  import { useClient } from '../client';
18
16
  import { withClientProvider } from '../testing';
19
-
20
17
  import { useCredentials } from './useCredentials';
21
18
  import { useIdentity } from './useIdentity';
22
19
 
@@ -107,7 +104,7 @@ const Test = () => {
107
104
 
108
105
  return (
109
106
  <>
110
- <div className='mbe-4 flex gap-2'>
107
+ <div className='mb-4 flex gap-2'>
111
108
  <Button disabled={!!identity} onClick={handleCreateIdentity}>
112
109
  Create Identity
113
110
  </Button>
@@ -118,24 +115,13 @@ const Test = () => {
118
115
  Authenticate with Passkey
119
116
  </Button>
120
117
  </div>
121
- <div className='flex flex-col min-w-[28rem] divide-y divide-separator border border-separator rounded'>
122
- <SyntaxHighlighter language='json'>
123
- {JSON.stringify({ identity, credentials: credentials.length }, null, 2)}
124
- </SyntaxHighlighter>
118
+ <div className='flex flex-col min-w-[28rem] divide-y divide-separator border border-separator rounded-sm'>
119
+ <JsonHighlighter data={{ identity, credentials: credentials.length }} />
125
120
  </div>
126
121
  </>
127
122
  );
128
123
  };
129
124
 
130
- const meta = {
131
- title: 'sdk/react-client/Passkeys',
132
- render: Test,
133
- } satisfies Meta<typeof Config>;
134
-
135
- export default meta;
136
-
137
- type Story = StoryObj<typeof meta>;
138
-
139
125
  const config = new Config({
140
126
  runtime: {
141
127
  client: {
@@ -156,6 +142,14 @@ const config = new Config({
156
142
  },
157
143
  });
158
144
 
159
- export const Default: Story = {
160
- decorators: [withClientProvider({ config }), withTheme],
161
- };
145
+ const meta = {
146
+ title: 'sdk/react-client/Passkeys',
147
+ render: Test,
148
+ decorators: [withClientProvider({ config }), withTheme()],
149
+ } satisfies Meta;
150
+
151
+ export default meta;
152
+
153
+ type Story = StoryObj<typeof meta>;
154
+
155
+ export const Default: Story = {};
@@ -6,7 +6,6 @@ import { renderHook } from '@testing-library/react';
6
6
  import { describe, expect, test } from 'vitest';
7
7
 
8
8
  import { createClient, createClientContextProvider } from '../testing/util';
9
-
10
9
  import { useDevices } from './useDevices';
11
10
 
12
11
  describe('useDevices', () => {
@@ -2,7 +2,7 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import { type Reducer, useCallback, useEffect, useMemo, useReducer } from 'react';
5
+ import { useCallback, useEffect, useMemo, useReducer } from 'react';
6
6
 
7
7
  import { type PublicKey } from '@dxos/client';
8
8
  import {
@@ -24,9 +24,10 @@ interface InvitationReducerState {
24
24
  status: Invitation.State; // TODO(burdon): Rename state.
25
25
  haltedAt?: Invitation.State;
26
26
  result: InvitationResult;
27
- error?: number;
27
+ error?: Error;
28
28
  id?: string;
29
29
  multiUse?: boolean;
30
+ shareable?: boolean;
30
31
  invitationCode?: string;
31
32
  authCode?: string;
32
33
  authMethod?: Invitation.AuthMethod;
@@ -72,7 +73,7 @@ export type InvitationStatus = {
72
73
  multiUse?: boolean;
73
74
  shareable?: boolean;
74
75
  result: InvitationResult;
75
- error?: number;
76
+ error?: Error;
76
77
  cancel(): void;
77
78
  // TODO(wittjosiah): Remove?
78
79
  connect(observable: CancellableInvitationObservable): void;
@@ -82,11 +83,11 @@ export type InvitationStatus = {
82
83
  // Without private key, the invitation code cannot be created.
83
84
  // These invitations are only available to be accepted but not shared.
84
85
  const isShareableInvitation = (invitation: Invitation) =>
85
- invitation.authMethod !== Invitation.AuthMethod.KNOWN_PUBLIC_KEY || invitation.guestKeypair?.privateKey;
86
+ invitation.authMethod !== Invitation.AuthMethod.KNOWN_PUBLIC_KEY || !!invitation.guestKeypair?.privateKey;
86
87
 
87
88
  export const useInvitationStatus = (observable?: CancellableInvitationObservable): InvitationStatus => {
88
- const [state, dispatch] = useReducer<Reducer<InvitationReducerState, InvitationAction>, null>(
89
- (prev, action) => {
89
+ const [state, dispatch] = useReducer(
90
+ (prev: InvitationReducerState, action: InvitationAction): InvitationReducerState => {
90
91
  log('useInvitationStatus', { action });
91
92
  const invitationProps =
92
93
  'invitation' in action
@@ -115,10 +116,10 @@ export const useInvitationStatus = (observable?: CancellableInvitationObservable
115
116
  action.status === Invitation.State.TIMEOUT) && {
116
117
  haltedAt: typeof prev.haltedAt === 'undefined' ? action.haltedAt : prev.haltedAt,
117
118
  }),
118
- } as InvitationReducerState;
119
+ };
119
120
  },
120
121
  null,
121
- (_arg: null) => {
122
+ (_arg: null): InvitationReducerState => {
122
123
  const invitation = observable?.get();
123
124
  return {
124
125
  status: Invitation.State.INIT,
@@ -2,41 +2,37 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React from 'react';
9
7
 
8
+ import { Obj } from '@dxos/echo';
10
9
  import { Input } from '@dxos/react-ui';
11
- import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
12
- import { withTheme } from '@dxos/storybook-utils';
10
+ import { JsonHighlighter } from '@dxos/react-ui-syntax-highlighter';
11
+ import { withTheme } from '@dxos/react-ui/testing';
13
12
 
14
13
  import { useClient } from '../client';
15
14
  import { useSpace } from '../echo';
16
-
17
15
  import { type ClientRepeatedComponentProps, ClientRepeater } from './ClientRepeater';
18
16
 
19
17
  const meta = {
20
18
  title: 'sdk/react-client/ClientRepeater',
21
- decorators: [withTheme],
19
+ decorators: [withTheme()],
22
20
  } satisfies Meta<typeof Input>;
23
21
 
24
22
  export default meta;
25
23
 
26
24
  type Story = StoryObj<typeof meta>;
27
25
 
28
- const JsonPanel = ({ value }: { value: any }) => (
29
- <SyntaxHighlighter language='json'>{JSON.stringify(value, undefined, 2)}</SyntaxHighlighter>
30
- );
26
+ const JsonPanel = ({ value }: { value: any }) => <JsonHighlighter data={value} />;
31
27
 
32
28
  const ClientStory = () => {
33
29
  const client = useClient();
34
30
  return <JsonPanel value={client.toJSON()} />;
35
31
  };
36
32
 
37
- const ClientSpace = ({ spaceKey }: ClientRepeatedComponentProps) => {
33
+ const ClientSpace = ({ spaceId }: ClientRepeatedComponentProps) => {
38
34
  const client = useClient();
39
- const space = useSpace(spaceKey);
35
+ const space = useSpace(spaceId);
40
36
  if (!space?.isOpen) {
41
37
  return null;
42
38
  }
@@ -47,7 +43,11 @@ const ClientSpace = ({ spaceKey }: ClientRepeatedComponentProps) => {
47
43
  <Input.TextInput
48
44
  placeholder='Name'
49
45
  value={space.properties.name}
50
- onChange={(event) => (space.properties.name = event.target.value)}
46
+ onChange={(event) =>
47
+ Obj.update(space.properties, (obj) => {
48
+ obj.name = event.target.value;
49
+ })
50
+ }
51
51
  />
52
52
  </Input.Root>
53
53
  <JsonPanel value={client.toJSON()} />
@@ -2,20 +2,19 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import React, { type FC, useEffect, useRef, useState } from 'react';
5
+ import React, { type FC, useRef, useState } from 'react';
6
6
 
7
- import { Client, type PublicKey } from '@dxos/client';
7
+ import { Client } from '@dxos/client';
8
8
  import { TestBuilder, performInvitation } from '@dxos/client/testing';
9
- import { type TypedObject } from '@dxos/echo-schema';
10
- import { registerSignalsRuntime } from '@dxos/echo-signals/react';
11
- import { faker } from '@dxos/random';
9
+ import { type Type } from '@dxos/echo';
10
+ import { random } from '@dxos/random';
12
11
  import { useAsyncEffect } from '@dxos/react-hooks';
13
12
 
14
13
  import { ClientProvider } from '../client';
15
-
14
+ import { type SpaceId } from '../echo';
16
15
  import { type WithClientProviderProps } from './withClientProvider';
17
16
 
18
- export type ClientRepeatedComponentProps = { id: number; count: number; spaceKey?: PublicKey };
17
+ export type ClientRepeatedComponentProps = { id: number; count: number; spaceId?: SpaceId };
19
18
 
20
19
  export type ClientRepeaterControlsProps = { clients: Client[] };
21
20
 
@@ -26,9 +25,9 @@ export type ClientRepeaterProps<P extends ClientRepeatedComponentProps> = {
26
25
  controls?: FC<ClientRepeaterControlsProps>;
27
26
  count?: number;
28
27
  clients?: Client[];
29
- types?: TypedObject[];
28
+ types?: Type.AnyObj[];
30
29
  args?: Omit<P, 'id' | 'count'>;
31
- } & Pick<WithClientProviderProps, 'createIdentity' | 'createSpace' | 'onSpaceCreated'>;
30
+ } & Pick<WithClientProviderProps, 'createIdentity' | 'createSpace' | 'onCreateSpace'>;
32
31
 
33
32
  /**
34
33
  * Utility component for Storybook stories which sets up clients for n peers.
@@ -48,14 +47,11 @@ export const ClientRepeater = <P extends ClientRepeatedComponentProps>(props: Cl
48
47
  types,
49
48
  createIdentity,
50
49
  createSpace,
51
- onSpaceCreated,
50
+ onCreateSpace,
52
51
  } = props;
53
- useEffect(() => {
54
- registerSignalsRuntime();
55
- }, []);
56
52
 
57
53
  const [clients, setClients] = useState(props.clients ?? []);
58
- const [spaceKey, setSpaceKey] = useState<PublicKey>();
54
+ const [spaceId, setSpaceId] = useState<SpaceId>();
59
55
 
60
56
  const testBuilder = useRef(new TestBuilder());
61
57
  useAsyncEffect(async () => {
@@ -70,9 +66,9 @@ export const ClientRepeater = <P extends ClientRepeatedComponentProps>(props: Cl
70
66
 
71
67
  if (createSpace) {
72
68
  const client = clients[0];
73
- const space = await client.spaces.create({ name: faker.commerce.productName() });
74
- setSpaceKey(space.key);
75
- await onSpaceCreated?.({ client, space }, {});
69
+ const space = await client.spaces.create({ name: random.commerce.productName() });
70
+ setSpaceId(space.id);
71
+ await onCreateSpace?.({ client, space }, {});
76
72
  await Promise.all(clients.slice(1).flatMap((client) => performInvitation({ host: space, guest: client.spaces })));
77
73
  }
78
74
 
@@ -88,7 +84,7 @@ export const ClientRepeater = <P extends ClientRepeatedComponentProps>(props: Cl
88
84
  {Controls && <Controls clients={clients} />}
89
85
  {clients.map((client, index) => (
90
86
  <ClientProvider key={index} client={client}>
91
- <Component id={index} count={clients.length} spaceKey={spaceKey} {...{ ...props.args }} />
87
+ <Component id={index} count={clients.length} spaceId={spaceId} {...{ ...props.args }} />
92
88
  </ClientProvider>
93
89
  ))}
94
90
  </>
@@ -4,16 +4,17 @@
4
4
 
5
5
  import { createContext, useContext } from 'react';
6
6
 
7
- import { type Space } from '@dxos/client/echo';
7
+ import { type Space, type SpaceId } from '@dxos/client/echo';
8
8
  import { raise } from '@dxos/debug';
9
9
 
10
10
  export type ClientStory = {
11
+ index?: number;
12
+ spaceId?: SpaceId;
11
13
  space?: Space;
12
14
  };
13
15
 
14
- // TODO(wittjosiah): Add to multi-client as well.
15
16
  export const ClientStory = createContext<ClientStory | undefined>(undefined);
16
17
 
17
- export const useClientProvider = (): ClientStory => {
18
+ export const useClientStory = (): ClientStory => {
18
19
  return useContext(ClientStory) ?? raise(new Error('Missing ClientStory'));
19
20
  };
@@ -24,7 +24,6 @@ export const createClient = async ({
24
24
  let space: Space | undefined;
25
25
  if (createSpace) {
26
26
  space = await client.spaces.create();
27
- await client.spaces.waitUntilReady();
28
27
  }
29
28
 
30
29
  return { client, space };
@@ -2,35 +2,33 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React from 'react';
7
+ import { expect, within } from 'storybook/test';
9
8
 
10
9
  import { log } from '@dxos/log';
11
- import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
12
- import { render, withLayout, withTheme } from '@dxos/storybook-utils';
10
+ import { JsonHighlighter } from '@dxos/react-ui-syntax-highlighter';
11
+ import { withLayout, withTheme, Loading } from '@dxos/react-ui/testing';
13
12
 
14
13
  import { useClient } from '../client';
15
14
  import { type Space, useSpaces } from '../echo';
16
-
17
15
  import { type WithClientProviderProps, withClientProvider, withMultiClientProvider } from './withClientProvider';
18
16
 
19
17
  const SpaceInfo = ({ space }: { space: Space }) => {
20
18
  const name = space.isOpen ? space.properties.name : '';
21
- return <SyntaxHighlighter language='json'>{JSON.stringify({ id: space.id, name }, null, 2)}</SyntaxHighlighter>;
19
+ return <JsonHighlighter data={{ id: space.id, name }} />;
22
20
  };
23
21
 
24
22
  const DefaultStory = () => {
25
23
  const client = useClient();
26
24
  const spaces = useSpaces();
27
25
  if (!client) {
28
- return null;
26
+ return <Loading data={{ client: !!client }} />;
29
27
  }
30
28
 
31
29
  return (
32
- <div className='flex flex-col min-w-[28rem] divide-y divide-separator border border-separator rounded'>
33
- <SyntaxHighlighter language='json'>{JSON.stringify(client.toJSON(), null, 2)}</SyntaxHighlighter>
30
+ <div className='flex flex-col divide-y divide-separator border border-separator'>
31
+ <JsonHighlighter data={client.toJSON()} />
34
32
  {spaces.map((space) => (
35
33
  <SpaceInfo key={space.id} space={space} />
36
34
  ))}
@@ -40,7 +38,9 @@ const DefaultStory = () => {
40
38
 
41
39
  const meta = {
42
40
  title: 'sdk/react-client/withClientProvider',
43
- render: render(DefaultStory),
41
+ render: DefaultStory,
42
+ decorators: [withTheme()],
43
+ tags: ['test'],
44
44
  } satisfies Meta<typeof DefaultStory>;
45
45
 
46
46
  export default meta;
@@ -53,19 +53,27 @@ const clientProps: WithClientProviderProps = {
53
53
  onInitialized: async (client) => {
54
54
  log.info('onInitialized', { client });
55
55
  },
56
- onSpaceCreated: async ({ client, space }) => {
57
- log.info('onSpaceCreated', { client, space });
56
+ onCreateSpace: async ({ client, space }) => {
57
+ log.info('onCreateSpace', { client, space });
58
58
  },
59
59
  };
60
60
 
61
61
  export const Default: Story = {
62
- decorators: [withClientProvider(clientProps), withTheme, withLayout({ classNames: ['flex gap-2'] })],
62
+ decorators: [withClientProvider(clientProps)],
63
+ parameters: {
64
+ layout: 'centered',
65
+ },
66
+ play: async ({ canvasElement }) => {
67
+ const canvas = within(canvasElement);
68
+ const initializedText = await canvas.findByText(/"initialized": true/i, {}, { timeout: 30_000 });
69
+ await expect(initializedText).toBeVisible();
70
+ },
63
71
  };
64
72
 
65
73
  export const Multiple: Story = {
66
74
  decorators: [
75
+ withTheme(),
76
+ withLayout({ classNames: 'grid grid-cols-3' }),
67
77
  withMultiClientProvider({ ...clientProps, numClients: 3 }),
68
- withTheme,
69
- withLayout({ classNames: ['flex gap-2'] }),
70
78
  ],
71
79
  };