@astrale-os/shell-react 0.4.0-beta.17 → 0.4.0-beta.18

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.
@@ -1,5 +1,7 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
- import { createContext, useContext, useEffect, useMemo, useState } from 'react';
2
+ import { createContext, useContext, useEffect, useState } from 'react';
3
+ import { GraphContext } from '../graph/graph.context.js';
4
+ import { resolveGraphStoreScope, retainStore, storeFor } from '../graph/store.js';
3
5
  import { ShellContext } from '../session/session.context.js';
4
6
  export const IdentityContext = createContext(null);
5
7
  IdentityContext.displayName = 'AstraleIdentityContext';
@@ -19,22 +21,34 @@ export function ActAs({ client, fallback, children }) {
19
21
  const [state, setState] = useState({ status: 'pending' });
20
22
  useEffect(() => {
21
23
  let active = true;
24
+ let release;
22
25
  setState({ status: 'pending' });
23
- void client.auth.whoami().then((self) => {
24
- if (active)
25
- setState({ status: 'ready', self });
26
- }, (error) => {
27
- if (active)
28
- setState({ status: 'failed', error });
29
- });
26
+ void (async () => {
27
+ try {
28
+ const self = await client.auth.whoami();
29
+ if (!active)
30
+ return;
31
+ const scope = await resolveGraphStoreScope(client, self);
32
+ if (!active)
33
+ return;
34
+ const entry = storeFor(client, scope);
35
+ release = retainStore(entry);
36
+ setState({ status: 'ready', binding: { kernel: client, self }, store: entry.store });
37
+ }
38
+ catch (error) {
39
+ if (active)
40
+ setState({ status: 'failed', client, error });
41
+ }
42
+ })();
30
43
  return () => {
31
44
  active = false;
45
+ release?.();
32
46
  };
33
47
  }, [client]);
34
- const value = useMemo(() => (state.status === 'ready' ? { kernel: client, self: state.self } : null), [client, state]);
35
- if (state.status === 'failed')
48
+ if (state.status === 'failed' && state.client === client)
36
49
  throw state.error;
37
- if (value === null)
50
+ if (state.status !== 'ready' || state.binding.kernel !== client) {
38
51
  return fallback === undefined ? null : _jsx(_Fragment, { children: fallback });
39
- return _jsx(IdentityContext.Provider, { value: value, children: children });
52
+ }
53
+ return (_jsx(IdentityContext.Provider, { value: state.binding, children: _jsx(GraphContext.Provider, { value: state.store, children: children }) }));
40
54
  }
@@ -183,7 +183,7 @@ function combine(snapshots) {
183
183
  return 'ready';
184
184
  }
185
185
  function stableIdentity(value) {
186
- const seen = new WeakSet();
186
+ const ancestors = new WeakSet();
187
187
  const encode = (input) => {
188
188
  if (input === null)
189
189
  return ['null'];
@@ -198,17 +198,22 @@ function stableIdentity(value) {
198
198
  }
199
199
  return [typeof input, input];
200
200
  }
201
- if (seen.has(input))
201
+ if (ancestors.has(input))
202
202
  throw new TypeError('Query input cannot contain cycles.');
203
- seen.add(input);
204
- if (Array.isArray(input))
205
- return ['array', input.map(encode)];
206
- return [
207
- 'object',
208
- Object.fromEntries(Object.keys(input)
209
- .sort()
210
- .map((key) => [key, encode(input[key])])),
211
- ];
203
+ ancestors.add(input);
204
+ try {
205
+ if (Array.isArray(input))
206
+ return ['array', input.map(encode)];
207
+ return [
208
+ 'object',
209
+ Object.fromEntries(Object.keys(input)
210
+ .sort()
211
+ .map((key) => [key, encode(input[key])])),
212
+ ];
213
+ }
214
+ finally {
215
+ ancestors.delete(input);
216
+ }
212
217
  };
213
218
  return JSON.stringify(encode(value));
214
219
  }
@@ -227,19 +227,27 @@ export function retainRefreshScheduler(store, identity, refresh, interval, when)
227
227
  };
228
228
  }
229
229
  function schedulePoll(scheduler, immediate = false) {
230
- if (scheduler.timer !== undefined)
231
- clearTimeout(scheduler.timer);
232
230
  const subscribers = [...scheduler.subscribers.values()];
233
231
  if (subscribers.length === 0)
234
232
  return;
235
233
  const interval = Math.min(...subscribers.map((subscriber) => subscriber.interval));
234
+ const delay = immediate ? 0 : interval;
235
+ const dueAt = Date.now() + delay;
236
+ // Observer changes may advance a refresh, but must not postpone pending work.
237
+ if (scheduler.timer !== undefined) {
238
+ if (scheduler.dueAt !== undefined && scheduler.dueAt <= dueAt)
239
+ return;
240
+ clearTimeout(scheduler.timer);
241
+ }
242
+ scheduler.dueAt = dueAt;
236
243
  scheduler.timer = setTimeout(() => {
237
244
  scheduler.timer = undefined;
245
+ scheduler.dueAt = undefined;
238
246
  const visible = typeof document === 'undefined' || document.visibilityState !== 'hidden';
239
247
  const eligible = [...scheduler.subscribers.values()].filter((subscriber) => subscriber.when === 'always' || visible);
240
248
  eligible.forEach((subscriber) => void subscriber.refresh().catch(() => { }));
241
249
  schedulePoll(scheduler);
242
- }, immediate ? 0 : interval);
250
+ }, delay);
243
251
  }
244
252
  function positivePage(input) {
245
253
  if (!Number.isSafeInteger(input) || input < 1) {
@@ -1,8 +1,8 @@
1
1
  import type { GraphApi } from '@astrale-os/sdk/client';
2
2
  import type { GraphStore as GraphStoreValue } from '@astrale-os/sdk/client/store';
3
- import type { Shell } from '@astrale-os/shell';
3
+ import type { KernelClient } from '@astrale-os/shell';
4
4
  interface StoreEntry {
5
- readonly shell: Shell;
5
+ readonly client: KernelClient;
6
6
  readonly graph: GraphApi;
7
7
  readonly scope: string;
8
8
  readonly store: GraphStoreValue;
@@ -15,9 +15,9 @@ export interface GraphStoreScope {
15
15
  readonly authority: string;
16
16
  readonly schemaRevision: string;
17
17
  }
18
- type StoreIdentity = Awaited<ReturnType<Shell['kernel']['auth']['whoami']>>;
19
- export declare function resolveGraphStoreScope(shell: Shell, knownIdentity?: StoreIdentity | null): Promise<GraphStoreScope>;
20
- export declare function storeFor(shell: Shell, scope: GraphStoreScope): StoreEntry;
18
+ type StoreIdentity = Awaited<ReturnType<KernelClient['auth']['whoami']>>;
19
+ export declare function resolveGraphStoreScope(client: KernelClient, knownIdentity?: StoreIdentity | null): Promise<GraphStoreScope>;
20
+ export declare function storeFor(client: KernelClient, scope: GraphStoreScope): StoreEntry;
21
21
  export declare function retainStore(entry: StoreEntry): () => void;
22
22
  /** The exact caller/schema-scoped GraphStore for the current View subtree. */
23
23
  export declare function useGraphStore(): GraphStoreValue;
@@ -2,39 +2,39 @@ import { GraphStore } from '@astrale-os/sdk/client/store';
2
2
  import { useContext } from 'react';
3
3
  import { GraphContext } from './graph.context.js';
4
4
  const entries = new WeakMap();
5
- export async function resolveGraphStoreScope(shell, knownIdentity) {
5
+ export async function resolveGraphStoreScope(client, knownIdentity) {
6
6
  const [identity, snapshot] = await Promise.all([
7
7
  knownIdentity === undefined
8
- ? shell.kernel.auth.whoami().catch(() => null)
8
+ ? client.auth.whoami().catch(() => null)
9
9
  : Promise.resolve(knownIdentity),
10
- shell.kernel.snapshot(),
10
+ client.snapshot(),
11
11
  ]);
12
12
  return Object.freeze({
13
13
  authority: identity === null ? 'anonymous' : `${identity.iss}\u0000${identity.sub}\u0000${identity.id}`,
14
14
  schemaRevision: snapshot.publication.schema.revision,
15
15
  });
16
16
  }
17
- export function storeFor(shell, scope) {
18
- const current = entries.get(shell);
17
+ export function storeFor(client, scope) {
18
+ const current = entries.get(client);
19
19
  const scopeKey = `${scope.authority}\u0000${scope.schemaRevision}`;
20
- if (current !== undefined && current.graph === shell.kernel.graph && current.scope === scopeKey) {
20
+ if (current !== undefined && current.graph === client.graph && current.scope === scopeKey) {
21
21
  return current;
22
22
  }
23
- const supplied = shell.kernel.store;
23
+ const supplied = client.store;
24
24
  const created = {
25
- shell,
26
- graph: shell.kernel.graph,
25
+ client,
26
+ graph: client.graph,
27
27
  scope: scopeKey,
28
28
  // A supplied Store is trusted only for the KernelClient's first declared scope. A later
29
29
  // authority/revision/GraphApi replacement gets a new Store and cannot inherit old memory.
30
30
  store: current === undefined && supplied !== undefined
31
31
  ? supplied
32
- : new GraphStore({ graph: shell.kernel.graph }),
32
+ : new GraphStore({ graph: client.graph }),
33
33
  owned: current !== undefined || supplied === undefined,
34
34
  retained: 0,
35
35
  generation: 0,
36
36
  };
37
- entries.set(shell, created);
37
+ entries.set(client, created);
38
38
  if (current !== undefined && current.retained === 0 && current.owned)
39
39
  current.store.close();
40
40
  return created;
@@ -52,8 +52,8 @@ export function retainStore(entry) {
52
52
  queueMicrotask(() => {
53
53
  if (entry.retained !== 0 || entry.generation !== generation || !entry.owned)
54
54
  return;
55
- if (entries.get(entry.shell) === entry)
56
- entries.delete(entry.shell);
55
+ if (entries.get(entry.client) === entry)
56
+ entries.delete(entry.client);
57
57
  entry.store.close();
58
58
  });
59
59
  };
@@ -45,10 +45,10 @@ export function ShellProvider(props) {
45
45
  await primeUser(shell);
46
46
  if (generation !== ref.generation)
47
47
  return;
48
- const storeScope = await resolveGraphStoreScope(shell, self);
48
+ const storeScope = await resolveGraphStoreScope(shell.kernel, self);
49
49
  if (generation !== ref.generation)
50
50
  return;
51
- const graph = storeFor(shell, storeScope);
51
+ const graph = storeFor(shell.kernel, storeScope);
52
52
  dispatch({
53
53
  type: 'ready',
54
54
  runtime: { shell, self, storeScope, store: graph.store, ownsStore: graph.owned },
@@ -105,7 +105,7 @@ export function ShellProvider(props) {
105
105
  useEffect(() => {
106
106
  if (shell === null || store === null || storeScope === null)
107
107
  return;
108
- return retainStore(storeFor(shell, storeScope));
108
+ return retainStore(storeFor(shell.kernel, storeScope));
109
109
  }, [shell, store, storeScope]);
110
110
  if (state.status === 'error') {
111
111
  if (errorFallback !== undefined)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/shell-react",
3
- "version": "0.4.0-beta.17",
3
+ "version": "0.4.0-beta.18",
4
4
  "description": "Astrale shell-react — the React face of the shell (provider, hooks, view components)",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -31,7 +31,7 @@
31
31
  "zod": "4.4.3"
32
32
  },
33
33
  "devDependencies": {
34
- "@astrale-os/sdk": "0.5.0-beta.131",
34
+ "@astrale-os/sdk": "0.5.0-beta.136",
35
35
  "@testing-library/dom": "^10.4.0",
36
36
  "@testing-library/react": "^16.3.0",
37
37
  "@types/node": "^26.2.0",
@@ -46,7 +46,7 @@
46
46
  "vitest": "4.1.10"
47
47
  },
48
48
  "peerDependencies": {
49
- "@astrale-os/sdk": ">=0.5.0-beta.131 <1.0.0",
49
+ "@astrale-os/sdk": ">=0.5.0-beta.136 <1.0.0",
50
50
  "react": ">=18",
51
51
  "react-dom": ">=18"
52
52
  },
@@ -2,8 +2,10 @@ import type { Identity } from '@astrale-os/sdk/auth'
2
2
  import type { KernelClient } from '@astrale-os/shell'
3
3
  import type { ReactElement, ReactNode } from 'react'
4
4
 
5
- import { createContext, useContext, useEffect, useMemo, useState } from 'react'
5
+ import { createContext, useContext, useEffect, useState } from 'react'
6
6
 
7
+ import { GraphContext } from '../graph/graph.context.js'
8
+ import { resolveGraphStoreScope, retainStore, storeFor } from '../graph/store.js'
7
9
  import { ShellContext } from '../session/session.context.js'
8
10
 
9
11
  export type NodeValue = Identity
@@ -40,31 +42,44 @@ export interface ActAsProps {
40
42
  export function ActAs({ client, fallback, children }: ActAsProps): ReactElement | null {
41
43
  const [state, setState] = useState<
42
44
  | { readonly status: 'pending' }
43
- | { readonly status: 'ready'; readonly self: Identity }
44
- | { readonly status: 'failed'; readonly error: unknown }
45
+ | {
46
+ readonly status: 'ready'
47
+ readonly binding: IdentityBinding
48
+ readonly store: ReturnType<typeof storeFor>['store']
49
+ }
50
+ | { readonly status: 'failed'; readonly client: KernelClient; readonly error: unknown }
45
51
  >({ status: 'pending' })
46
52
 
47
53
  useEffect(() => {
48
54
  let active = true
55
+ let release: (() => void) | undefined
49
56
  setState({ status: 'pending' })
50
- void client.auth.whoami().then(
51
- (self) => {
52
- if (active) setState({ status: 'ready', self })
53
- },
54
- (error: unknown) => {
55
- if (active) setState({ status: 'failed', error })
56
- },
57
- )
57
+ void (async () => {
58
+ try {
59
+ const self = await client.auth.whoami()
60
+ if (!active) return
61
+ const scope = await resolveGraphStoreScope(client, self)
62
+ if (!active) return
63
+ const entry = storeFor(client, scope)
64
+ release = retainStore(entry)
65
+ setState({ status: 'ready', binding: { kernel: client, self }, store: entry.store })
66
+ } catch (error) {
67
+ if (active) setState({ status: 'failed', client, error })
68
+ }
69
+ })()
58
70
  return () => {
59
71
  active = false
72
+ release?.()
60
73
  }
61
74
  }, [client])
62
75
 
63
- const value = useMemo<IdentityBinding | null>(
64
- () => (state.status === 'ready' ? { kernel: client, self: state.self } : null),
65
- [client, state],
76
+ if (state.status === 'failed' && state.client === client) throw state.error
77
+ if (state.status !== 'ready' || state.binding.kernel !== client) {
78
+ return fallback === undefined ? null : <>{fallback}</>
79
+ }
80
+ return (
81
+ <IdentityContext.Provider value={state.binding}>
82
+ <GraphContext.Provider value={state.store}>{children}</GraphContext.Provider>
83
+ </IdentityContext.Provider>
66
84
  )
67
- if (state.status === 'failed') throw state.error
68
- if (value === null) return fallback === undefined ? null : <>{fallback}</>
69
- return <IdentityContext.Provider value={value}>{children}</IdentityContext.Provider>
70
85
  }
@@ -251,7 +251,7 @@ function combine(snapshots: readonly StoreSnapshot[]): GraphResourceState {
251
251
  }
252
252
 
253
253
  function stableIdentity(value: unknown): string {
254
- const seen = new WeakSet<object>()
254
+ const ancestors = new WeakSet<object>()
255
255
  const encode = (input: unknown): unknown => {
256
256
  if (input === null) return ['null']
257
257
  if (typeof input !== 'object') {
@@ -264,17 +264,21 @@ function stableIdentity(value: unknown): string {
264
264
  }
265
265
  return [typeof input, input]
266
266
  }
267
- if (seen.has(input)) throw new TypeError('Query input cannot contain cycles.')
268
- seen.add(input)
269
- if (Array.isArray(input)) return ['array', input.map(encode)]
270
- return [
271
- 'object',
272
- Object.fromEntries(
273
- Object.keys(input as Record<string, unknown>)
274
- .sort()
275
- .map((key) => [key, encode((input as Record<string, unknown>)[key])]),
276
- ),
277
- ]
267
+ if (ancestors.has(input)) throw new TypeError('Query input cannot contain cycles.')
268
+ ancestors.add(input)
269
+ try {
270
+ if (Array.isArray(input)) return ['array', input.map(encode)]
271
+ return [
272
+ 'object',
273
+ Object.fromEntries(
274
+ Object.keys(input as Record<string, unknown>)
275
+ .sort()
276
+ .map((key) => [key, encode((input as Record<string, unknown>)[key])]),
277
+ ),
278
+ ]
279
+ } finally {
280
+ ancestors.delete(input)
281
+ }
278
282
  }
279
283
  return JSON.stringify(encode(value))
280
284
  }
@@ -176,6 +176,7 @@ interface PollSubscriber {
176
176
  interface PollScheduler {
177
177
  readonly subscribers: Map<symbol, PollSubscriber>
178
178
  timer: ReturnType<typeof setTimeout> | undefined
179
+ dueAt?: number
179
180
  onVisibility?: () => void
180
181
  }
181
182
 
@@ -344,22 +345,27 @@ export function retainRefreshScheduler(
344
345
  }
345
346
 
346
347
  function schedulePoll(scheduler: PollScheduler, immediate = false): void {
347
- if (scheduler.timer !== undefined) clearTimeout(scheduler.timer)
348
348
  const subscribers = [...scheduler.subscribers.values()]
349
349
  if (subscribers.length === 0) return
350
350
  const interval = Math.min(...subscribers.map((subscriber) => subscriber.interval))
351
- scheduler.timer = setTimeout(
352
- () => {
353
- scheduler.timer = undefined
354
- const visible = typeof document === 'undefined' || document.visibilityState !== 'hidden'
355
- const eligible = [...scheduler.subscribers.values()].filter(
356
- (subscriber) => subscriber.when === 'always' || visible,
357
- )
358
- eligible.forEach((subscriber) => void subscriber.refresh().catch(() => {}))
359
- schedulePoll(scheduler)
360
- },
361
- immediate ? 0 : interval,
362
- )
351
+ const delay = immediate ? 0 : interval
352
+ const dueAt = Date.now() + delay
353
+ // Observer changes may advance a refresh, but must not postpone pending work.
354
+ if (scheduler.timer !== undefined) {
355
+ if (scheduler.dueAt !== undefined && scheduler.dueAt <= dueAt) return
356
+ clearTimeout(scheduler.timer)
357
+ }
358
+ scheduler.dueAt = dueAt
359
+ scheduler.timer = setTimeout(() => {
360
+ scheduler.timer = undefined
361
+ scheduler.dueAt = undefined
362
+ const visible = typeof document === 'undefined' || document.visibilityState !== 'hidden'
363
+ const eligible = [...scheduler.subscribers.values()].filter(
364
+ (subscriber) => subscriber.when === 'always' || visible,
365
+ )
366
+ eligible.forEach((subscriber) => void subscriber.refresh().catch(() => {}))
367
+ schedulePoll(scheduler)
368
+ }, delay)
363
369
  }
364
370
 
365
371
  function positivePage(input: number): number {
@@ -1,6 +1,6 @@
1
1
  import type { GraphApi } from '@astrale-os/sdk/client'
2
2
  import type { GraphStore as GraphStoreValue } from '@astrale-os/sdk/client/store'
3
- import type { Shell } from '@astrale-os/shell'
3
+ import type { KernelClient } from '@astrale-os/shell'
4
4
 
5
5
  import { GraphStore } from '@astrale-os/sdk/client/store'
6
6
  import { useContext } from 'react'
@@ -8,7 +8,7 @@ import { useContext } from 'react'
8
8
  import { GraphContext } from './graph.context.js'
9
9
 
10
10
  interface StoreEntry {
11
- readonly shell: Shell
11
+ readonly client: KernelClient
12
12
  readonly graph: GraphApi
13
13
  readonly scope: string
14
14
  readonly store: GraphStoreValue
@@ -17,7 +17,7 @@ interface StoreEntry {
17
17
  generation: number
18
18
  }
19
19
 
20
- const entries = new WeakMap<Shell, StoreEntry>()
20
+ const entries = new WeakMap<KernelClient, StoreEntry>()
21
21
 
22
22
  /** Authority/schema identity. Session ids and Publication ETags are deliberately absent. */
23
23
  export interface GraphStoreScope {
@@ -25,17 +25,17 @@ export interface GraphStoreScope {
25
25
  readonly schemaRevision: string
26
26
  }
27
27
 
28
- type StoreIdentity = Awaited<ReturnType<Shell['kernel']['auth']['whoami']>>
28
+ type StoreIdentity = Awaited<ReturnType<KernelClient['auth']['whoami']>>
29
29
 
30
30
  export async function resolveGraphStoreScope(
31
- shell: Shell,
31
+ client: KernelClient,
32
32
  knownIdentity?: StoreIdentity | null,
33
33
  ): Promise<GraphStoreScope> {
34
34
  const [identity, snapshot] = await Promise.all([
35
35
  knownIdentity === undefined
36
- ? shell.kernel.auth.whoami().catch(() => null)
36
+ ? client.auth.whoami().catch(() => null)
37
37
  : Promise.resolve(knownIdentity),
38
- shell.kernel.snapshot(),
38
+ client.snapshot(),
39
39
  ])
40
40
  return Object.freeze({
41
41
  authority:
@@ -44,28 +44,28 @@ export async function resolveGraphStoreScope(
44
44
  })
45
45
  }
46
46
 
47
- export function storeFor(shell: Shell, scope: GraphStoreScope): StoreEntry {
48
- const current = entries.get(shell)
47
+ export function storeFor(client: KernelClient, scope: GraphStoreScope): StoreEntry {
48
+ const current = entries.get(client)
49
49
  const scopeKey = `${scope.authority}\u0000${scope.schemaRevision}`
50
- if (current !== undefined && current.graph === shell.kernel.graph && current.scope === scopeKey) {
50
+ if (current !== undefined && current.graph === client.graph && current.scope === scopeKey) {
51
51
  return current
52
52
  }
53
- const supplied = shell.kernel.store
53
+ const supplied = client.store
54
54
  const created: StoreEntry = {
55
- shell,
56
- graph: shell.kernel.graph,
55
+ client,
56
+ graph: client.graph,
57
57
  scope: scopeKey,
58
58
  // A supplied Store is trusted only for the KernelClient's first declared scope. A later
59
59
  // authority/revision/GraphApi replacement gets a new Store and cannot inherit old memory.
60
60
  store:
61
61
  current === undefined && supplied !== undefined
62
62
  ? supplied
63
- : new GraphStore({ graph: shell.kernel.graph }),
63
+ : new GraphStore({ graph: client.graph }),
64
64
  owned: current !== undefined || supplied === undefined,
65
65
  retained: 0,
66
66
  generation: 0,
67
67
  }
68
- entries.set(shell, created)
68
+ entries.set(client, created)
69
69
  if (current !== undefined && current.retained === 0 && current.owned) current.store.close()
70
70
  return created
71
71
  }
@@ -81,7 +81,7 @@ export function retainStore(entry: StoreEntry): () => void {
81
81
  const generation = ++entry.generation
82
82
  queueMicrotask(() => {
83
83
  if (entry.retained !== 0 || entry.generation !== generation || !entry.owned) return
84
- if (entries.get(entry.shell) === entry) entries.delete(entry.shell)
84
+ if (entries.get(entry.client) === entry) entries.delete(entry.client)
85
85
  entry.store.close()
86
86
  })
87
87
  }
@@ -61,9 +61,9 @@ export function ShellProvider(props: ShellProviderProps): ReactElement | null {
61
61
  if (generation !== ref.generation) return
62
62
  await primeUser(shell)
63
63
  if (generation !== ref.generation) return
64
- const storeScope = await resolveGraphStoreScope(shell, self)
64
+ const storeScope = await resolveGraphStoreScope(shell.kernel, self)
65
65
  if (generation !== ref.generation) return
66
- const graph = storeFor(shell, storeScope)
66
+ const graph = storeFor(shell.kernel, storeScope)
67
67
  dispatch({
68
68
  type: 'ready',
69
69
  runtime: { shell, self, storeScope, store: graph.store, ownsStore: graph.owned },
@@ -117,7 +117,7 @@ export function ShellProvider(props: ShellProviderProps): ReactElement | null {
117
117
  const storeScope = state.runtime?.storeScope ?? null
118
118
  useEffect(() => {
119
119
  if (shell === null || store === null || storeScope === null) return
120
- return retainStore(storeFor(shell, storeScope))
120
+ return retainStore(storeFor(shell.kernel, storeScope))
121
121
  }, [shell, store, storeScope])
122
122
 
123
123
  if (state.status === 'error') {