@gears-frontx/react 0.1.0 → 0.2.0-alpha.15

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.
package/CLAUDE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @gears-frontx/react
2
2
 
3
- React bindings and hooks for HAI3 applications. Provides the React integration layer with MFE (Microfrontend) support.
3
+ React bindings and hooks for FrontX applications. Provides the React integration layer with MFE (Microfrontend) support.
4
4
 
5
5
  ## React Layer
6
6
 
@@ -8,51 +8,180 @@ This package is part of the **React Layer (L3)** - it depends only on @gears-fro
8
8
 
9
9
  ## Core Concepts
10
10
 
11
- ### HAI3Provider
11
+ ### FrontXProvider
12
12
 
13
- Wrap your app with HAI3Provider to enable all hooks:
13
+ Wrap your app with FrontXProvider to enable all hooks. A runtime builds exactly one app, once at module level; the provider never creates one. The three compositions below are alternatives: choose one per runtime.
14
14
 
15
15
  ```tsx
16
- import { HAI3Provider } from '@gears-frontx/react';
16
+ import { createFrontX, FrontXProvider, presets } from '@gears-frontx/react';
17
+
18
+ // Alternative 1: the full preset
19
+ const app = createFrontX({ devMode: true }).useAll(presets.full()).build();
17
20
 
18
21
  function App() {
19
22
  return (
20
- <HAI3Provider>
23
+ <FrontXProvider app={app}>
21
24
  <YourApp />
22
- </HAI3Provider>
25
+ </FrontXProvider>
23
26
  );
24
27
  }
25
28
 
26
- // With configuration
27
- <HAI3Provider config={{ devMode: true }}>
28
- <YourApp />
29
- </HAI3Provider>
29
+ ```
30
+
31
+ Alternative 2: a host shell with a custom plugin set (a host typically uses `queryCache()`):
32
+
33
+ ```tsx
34
+ const app = createFrontX().use(effects()).use(queryCache()).build();
35
+ ```
36
+
37
+ Alternative 3: a child MFE app. The canonical bootstrap matches `src/mfe_packages/*/init.ts`: `apiRegistry.register` / `initialize` before `.build()`, `registerSlice` after `.build()` when slices exist:
38
+
39
+ ```tsx
40
+ const app = createFrontX().use(effects()).use(queryCacheShared()).use(mock()).build();
41
+ ```
42
+
43
+ The shared `QueryClient` is created and owned by the `queryCache()` framework plugin at L2.
44
+ `FrontXProvider` resolves that client from the app instance — it does not create its own `QueryClient`.
45
+
46
+ When the host uses `queryCache()` and the child MFE app uses `queryCacheShared()` (with `effects()` and `mock()` on the same chain as in repo MFE inits), both roots join the same shared `QueryClient` while keeping separate React trees. `ThemeAwareReactLifecycle` relies on that shared plugin-owned client through the app instance. If the shared client is missing for a mounted MFE, the lifecycle fails explicitly instead of silently falling back.
47
+
48
+ For separate roots, build each app with the appropriate query-cache plugin (`queryCache()` for the host, `queryCacheShared()` for child MFE shells) so every tree joins the same cache through plugin composition. Host apps should register domains/extensions during bootstrap; **`ExtensionDomainSlot`** is the preferred host-side renderer for screen slots while the framework wires `mount_ext`.
49
+
50
+ ### Data Fetching with Endpoint Descriptors
51
+
52
+ Services define endpoints as descriptors. Components consume them via `useApiQuery` and `useApiMutation`. No manual query keys, no `queryOptions()` calls.
53
+
54
+ ```tsx
55
+ import { useApiQuery, useApiMutation, apiRegistry } from '@gears-frontx/react';
56
+ import { AccountsApiService } from '../api/AccountsApiService';
57
+
58
+ function ProfileScreen({ teamId }: { teamId?: string }) {
59
+ const service = apiRegistry.getService(AccountsApiService);
60
+
61
+ // Read — pass descriptor directly
62
+ const { data, isLoading, error } = useApiQuery(service.getCurrentUser);
63
+
64
+ // Read with params
65
+ const { data: user } = useApiQuery(service.getUser({ id: '123' }));
66
+
67
+ // Write with optimistic update
68
+ const { mutateAsync, isPending } = useApiMutation({
69
+ endpoint: service.updateProfile,
70
+ onMutate: async (variables, { queryCache }) => {
71
+ await queryCache.cancel(service.getCurrentUser);
72
+ const snapshot = queryCache.get(service.getCurrentUser);
73
+ queryCache.set(service.getCurrentUser, (old) => ({
74
+ ...old, user: { ...old.user, ...variables }
75
+ }));
76
+ return { snapshot };
77
+ },
78
+ onError: (_err, _vars, context, { queryCache }) => {
79
+ if (context?.snapshot) {
80
+ queryCache.set(service.getCurrentUser, context.snapshot);
81
+ }
82
+ },
83
+ onSettled: async (_data, _err, _vars, _ctx, { queryCache }) => {
84
+ await queryCache.invalidate(service.getCurrentUser);
85
+ },
86
+ });
87
+
88
+ // Per-endpoint cache override (rare)
89
+ const { data: config } = useApiQuery(service.getConfig, { staleTime: 0 });
30
90
 
31
- // With pre-built app
32
- const app = createHAI3().use(screensets()).build();
33
- <HAI3Provider app={app}>
34
- <YourApp />
35
- </HAI3Provider>
91
+ // Conditional query: stays idle until enabled (not on the suspense hooks).
92
+ // The hook still needs a descriptor on every render, so one is built with a
93
+ // stand-in id; while enabled is false it is not fetched automatically, and
94
+ // refetch would still fetch it, so do not call refetch before teamId is set.
95
+ const { data: team } = useApiQuery(service.getTeam({ id: teamId ?? '' }), { enabled: Boolean(teamId) });
96
+ }
36
97
  ```
37
98
 
99
+ Cache keys are derived automatically by the service — `QueryCache` methods accept
100
+ endpoint descriptors directly (e.g., `queryCache.get(service.getCurrentUser)`).
101
+
102
+ The `queryCache` a mutation callback receives is bound to the cache scope (mock mode, tenant, session) the mutation started under: once that scope changes, its writes (`set`, `remove`, `invalidate`, `invalidateMany`, `cancel`) do nothing and reads still work, so a response or snapshot from the old scope never lands in the new cache. Nothing else is scope-bound: a `useQueryCache()` handle, the raw `QueryClient`, `cache/*` eventBus emits and per-call `mutate(vars, { onSuccess })` options write to whatever scope is current, so a write after an `await` or inside a mutation callback must go through the callback's `queryCache`.
103
+
104
+ ### SSE Streaming with Stream Descriptors
105
+
106
+ Services declare SSE endpoints as stream descriptors. Components consume them via `useApiStream`, which manages the connection lifecycle automatically (connect on mount, disconnect on unmount).
107
+
108
+ ```tsx
109
+ import { useApiStream, apiRegistry } from '@gears-frontx/react';
110
+ import { ChatApiService } from '../api/ChatApiService';
111
+
112
+ function ChatStream() {
113
+ const service = apiRegistry.getService(ChatApiService);
114
+
115
+ // Latest event only (default mode)
116
+ const { data, status, error } = useApiStream(service.messageStream);
117
+
118
+ // Accumulate all events
119
+ const { events, status: streamStatus } = useApiStream(
120
+ service.messageStream,
121
+ { mode: 'accumulate' }
122
+ );
123
+
124
+ // Deferred connection (enabled: false)
125
+ const [active, setActive] = useState(false);
126
+ const { data: msg, disconnect } = useApiStream(
127
+ service.messageStream,
128
+ { enabled: active }
129
+ );
130
+
131
+ if (status === 'connecting') return <Loading />;
132
+ if (status === 'error') return <Error error={error} />;
133
+
134
+ return <div>{data?.text}</div>;
135
+ }
136
+ ```
137
+
138
+ `useApiStream` returns `{ data, events, status, error, disconnect }`:
139
+ - `data` — latest event payload (set in both modes)
140
+ - `events` — all received events when `mode: 'accumulate'`; empty array in `'latest'` mode
141
+ - `status` — connection lifecycle state, `StreamStatus` (see below)
142
+ - `error` — the last connect attempt's error; retained until the next connect attempt resets it, so check `status` rather than `error` truthiness
143
+ - `disconnect()` — close the connection manually
144
+
145
+ #### Status lifecycle
146
+
147
+ `status` is a `StreamStatus`: `'idle' | 'connecting' | 'connected' | 'disconnected' | 'error'`.
148
+
149
+ The TSDoc block on `useApiStream` in `src/hooks/useApiStream.ts` is the detailed and authoritative state-machine reference, including teardown edge cases. This guide summarizes consumer-facing usage and gating.
150
+
151
+ `disconnect()` closes the connection and does not reconnect. There is no reconnect function: to open a new connection, change the descriptor key, change `mode`, or toggle `enabled` back to true.
152
+
153
+ #### The `enabled` option
154
+
155
+ `enabled` (default `true`) gates whether the hook connects at all. While it is false the hook never calls the descriptor's `connect()` and ordinarily holds `status: 'idle'` (calling `disconnect()` while disabled is the one exception — see the TSDoc). Flipping it to true re-runs the connect effect and starts a fresh connection; flipping it back to false tears the connection down and returns `status` to `'idle'`.
156
+
157
+ Tearing down via `enabled` does not clear `data` or `events` — a descriptor-key or `mode` change, or re-enabling, does. A component reading `data` while the stream is disabled still sees the last event from the previous connection.
158
+
159
+ Gating advice, since `'idle'` covers both "not started yet" and "deferred by `enabled`":
160
+
161
+ - Gate the loading state on `'connecting'` alone. Treating `'idle'` as loading renders a permanent spinner for a stream deliberately held at `enabled: false`.
162
+ - Handle `'idle'` as "not started / deferred". A component that both defers and shows loading should key its spinner off its own active flag, not off `'idle'`.
163
+ - Check `status === 'error'` rather than the truthiness of `error` to render failures.
164
+
38
165
  ### Available Hooks
39
166
 
40
- #### useHAI3
167
+ #### useFrontX
41
168
 
42
- Access the HAI3 app instance:
169
+ Access the FrontX app instance:
43
170
 
44
171
  ```tsx
45
- import { useHAI3 } from '@gears-frontx/react';
172
+ import { useFrontX } from '@gears-frontx/react';
46
173
 
47
174
  function MyComponent() {
48
- const app = useHAI3();
175
+ const app = useFrontX();
49
176
 
50
177
  // Access MFE-enabled registry
51
- const extensions = app.screensetsRegistry.getRegisteredExtensions();
178
+ const extensions = app.mfeRegistry.getExtensionsForDomain('screen');
52
179
 
53
- // Access MFE actions
54
- await app.actions.loadExtension({ extensionId: 'home' });
55
- await app.actions.mountExtension({ extensionId: 'home', domainId: 'screen', container });
180
+ // Access MFE actions — fire-and-forget: each dispatches an actions chain
181
+ // through the acceptance-only registry surface and returns nothing to
182
+ // await.
183
+ app.actions.loadExtension('home');
184
+ app.actions.mountExtension('home');
56
185
  }
57
186
  ```
58
187
 
@@ -138,47 +267,78 @@ Access the MFE bridge for child MFEs:
138
267
 
139
268
  ```tsx
140
269
  import { useMfeBridge } from '@gears-frontx/react';
141
- import { HAI3_ACTION_LOAD_EXT, HAI3_SHARED_PROPERTY_THEME } from '@gears-frontx/react';
270
+ import { FRONTX_ACTION_LOAD_EXT, FRONTX_SHARED_PROPERTY_THEME } from '@gears-frontx/react';
142
271
 
143
272
  function MyExtension() {
144
273
  const bridge = useMfeBridge();
145
274
 
146
- // Execute actions chain on parent
147
- await bridge.executeActionsChain({
148
- action: { type: HAI3_ACTION_LOAD_EXT, target: 'screen', payload: { extensionId: 'other' } }
149
- });
275
+ const handleLoad = () => {
276
+ // executeActionsChain is acceptance-only: it returns void, never
277
+ // throws, and never yields anything to await for the chain's own
278
+ // execution.
279
+ bridge.executeActionsChain({
280
+ action: { type: FRONTX_ACTION_LOAD_EXT, target: 'screen', payload: { subject: 'other' } }
281
+ });
282
+ };
150
283
 
151
284
  // Get shared property
152
- const theme = bridge.getProperty(HAI3_SHARED_PROPERTY_THEME);
285
+ const theme = bridge.getProperty(FRONTX_SHARED_PROPERTY_THEME);
153
286
  }
154
287
  ```
155
288
 
156
289
  #### useSharedProperty
157
290
 
158
- Subscribe to shared property changes:
291
+ Subscribe to shared property changes. Two forms: the context form reads the bridge from `MfeContext`, and the options form accepts an explicit bridge and a fallback.
292
+
293
+ **Context form** — `useSharedProperty(propertyTypeId)`. Reads the bridge from `MfeContext` and throws if no `MfeProvider` is mounted. Returns the value (or `undefined` if the host has not published it), updating on every host publish.
159
294
 
160
295
  ```tsx
161
- import { useSharedProperty, HAI3_SHARED_PROPERTY_THEME } from '@gears-frontx/react';
296
+ import { useSharedProperty, FRONTX_SHARED_PROPERTY_THEME } from '@gears-frontx/react';
162
297
 
163
298
  function ThemedComponent() {
164
- const theme = useSharedProperty(HAI3_SHARED_PROPERTY_THEME);
299
+ const theme = useSharedProperty(FRONTX_SHARED_PROPERTY_THEME);
165
300
 
166
301
  return <div style={{ backgroundColor: theme?.primaryColor }}>...</div>;
167
302
  }
168
303
  ```
169
304
 
305
+ **Options form** — `useSharedProperty(propertyTypeId, { bridge, fallback })`. Use it for components that never mount `MfeProvider`, or to show a value until the host publishes one.
306
+
307
+ ```tsx
308
+ import { useSharedProperty } from '@gears-frontx/react';
309
+ import type { ChildMfeBridge } from '@gears-frontx/framework';
310
+
311
+ function StandaloneComponent({ bridge }: { bridge: ChildMfeBridge }) {
312
+ const label = useSharedProperty<string>(LABEL_PROPERTY_ID, {
313
+ bridge,
314
+ fallback: 'Untitled',
315
+ });
316
+
317
+ return <span>{label}</span>;
318
+ }
319
+ ```
320
+
321
+ `UseSharedPropertyOptions<T>` fields:
322
+
323
+ - `bridge` — bridge to read the property from, bypassing `MfeContext`. Pass it when the calling component never mounts `MfeProvider` (for example, a component that receives its bridge as a prop or from a non-React entry point). When omitted, the hook falls back to the bridge from `MfeContext` and throws if no `MfeProvider` is mounted. Changing `bridge` or `propertyTypeId` moves the subscription to the new source and the returned value follows on the same render.
324
+ - `fallback` — value returned while the host has not published the property yet. Only `undefined` is substituted; a published `null` is a real value and comes through as-is. With `fallback` set the hook returns `T` rather than `T | undefined`. It may be passed inline on every render without affecting the subscription.
325
+
326
+ There is no consumer-side validation option on purpose: the host validates every published value against the property's schema before it reaches any MFE (`TypeSystemPlugin.register()` throws on a mismatch and the update is never stored or delivered). `T` is the caller's declaration of that schema, as with `useState<T>`.
327
+
328
+ Both forms read through `useSyncExternalStore`, so the first render shows the host's current value and the result is tearing-protected under concurrent rendering. The TSDoc block on `useSharedProperty` in `src/mfe/hooks/useSharedProperty.ts` is the authoritative reference; this guide summarizes consumer-facing usage.
329
+
170
330
  #### useHostAction
171
331
 
172
332
  Invoke actions on the host application:
173
333
 
174
334
  ```tsx
175
- import { useHostAction, HAI3_ACTION_LOAD_EXT } from '@gears-frontx/react';
335
+ import { useHostAction, FRONTX_ACTION_LOAD_EXT } from '@gears-frontx/react';
176
336
 
177
337
  function MyExtension() {
178
- const loadExtension = useHostAction(HAI3_ACTION_LOAD_EXT);
338
+ const loadExtension = useHostAction(FRONTX_ACTION_LOAD_EXT);
179
339
 
180
340
  const handleClick = () => {
181
- loadExtension({ extensionId: 'other' });
341
+ loadExtension({ subject: 'other' });
182
342
  };
183
343
 
184
344
  return <button onClick={handleClick}>Load Extension</button>;
@@ -254,7 +414,7 @@ import { MfeProvider } from '@gears-frontx/react';
254
414
 
255
415
  function MfeHost() {
256
416
  return (
257
- <MfeProvider bridge={parentBridge}>
417
+ <MfeProvider value={{ bridge: parentBridge, extensionId: 'home', domainId: 'screen' }}>
258
418
  <ExtensionContainer />
259
419
  </MfeProvider>
260
420
  );
@@ -270,17 +430,27 @@ import { ExtensionDomainSlot } from '@gears-frontx/react';
270
430
 
271
431
  function LayoutScreen() {
272
432
  return (
273
- <div>
274
- <ExtensionDomainSlot
275
- domainId="screen"
276
- containerRef={screenContainerRef}
277
- fallback={<Loading />}
278
- />
279
- </div>
433
+ <ExtensionDomainSlot
434
+ registry={registry}
435
+ domainId="screen"
436
+ extensionId="home"
437
+ loadingComponent={<Loading />}
438
+ />
280
439
  );
281
440
  }
282
441
  ```
283
442
 
443
+ `ExtensionDomainSlot` is the preferred host-side renderer for screen MFEs. It
444
+ owns mount/unmount and loading/error UI while host bootstrap handles domain
445
+ registration and shared property setup before slot-driven `mount_ext`.
446
+ The host `QueryClient` is reused by MFE React trees through
447
+ `queryCacheShared()`, not by embedding a `QueryClient` on `MfeMountContext`
448
+ or expecting host React context inside Shadow DOM.
449
+
450
+ When the domain's `ContainerProvider` must point at the same DOM node rendered
451
+ by the slot, pass `containerRef` to `ExtensionDomainSlot` and share that ref
452
+ with `RefContainerProvider`.
453
+
284
454
  #### RefContainerProvider
285
455
 
286
456
  Provide container references for MFE mounting:
@@ -302,11 +472,14 @@ function Layout() {
302
472
 
303
473
  ## Key Rules
304
474
 
305
- 1. **Wrap with HAI3Provider** - Required for all hooks to work
475
+ 1. **Wrap with FrontXProvider** - Required for all hooks to work
306
476
  2. **Use hooks for state access** - Don't import selectors directly from @gears-frontx/framework
307
- 3. **Lazy load translations** - Use `useScreenTranslations` for screen-level i18n
308
- 4. **Use MFE hooks for extensions** - `useMfeBridge`, `useSharedProperty`, `useHostAction`, `useDomainExtensions`
309
- 5. **NO Layout components here** - Layout and UI components belong in L4 (user's project via CLI scaffolding)
477
+ 3. **Use endpoint descriptors for data** - `useApiQuery(service.endpoint)` for REST, `useApiStream(service.stream)` for SSE — not `queryOptions()` or manual key factories
478
+ 4. **Service is the cache contract** - The service IS the data layer; cache keys are derived automatically
479
+ 5. **QueryCache uses descriptors** - `queryCache.get(service.endpoint)`, not raw key arrays
480
+ 6. **Lazy load translations** - Use `useScreenTranslations` for screen-level i18n
481
+ 7. **Use MFE hooks for extensions** - `useMfeBridge`, `useSharedProperty`, `useHostAction`, `useDomainExtensions`
482
+ 8. **NO Layout components here** - Layout and UI components belong in L4 (user's project via CLI scaffolding)
310
483
 
311
484
  ## Re-exports
312
485
 
@@ -323,34 +496,45 @@ This allows users to import everything from `@gears-frontx/react` without needin
323
496
  ## Exports
324
497
 
325
498
  ### Components
326
- - `HAI3Provider` - Main context provider
499
+ - `FrontXProvider` - Main context provider
327
500
  - `MfeProvider` - MFE context provider
328
501
  - `ExtensionDomainSlot` - Domain slot renderer
329
502
  - `RefContainerProvider` - Container reference provider
330
503
 
331
504
  ### Hooks
332
- - `useHAI3` - Access app instance
505
+ - `useFrontX` - Access app instance
333
506
  - `useAppDispatch` - Typed dispatch
334
507
  - `useAppSelector` - Typed selector
508
+ - `useApiQuery` - Declarative data fetch from endpoint descriptor; returns `ApiQueryResult<TData>`
509
+ - `useApiMutation` - Declarative mutation with endpoint descriptor and optimistic update support; returns `ApiMutationResult<TData>`
510
+ - `useApiStream` - Declarative SSE streaming from stream descriptor; returns `ApiStreamResult<TEvent>`
511
+ - `useQueryCache` - Restricted query cache access (accepts descriptors or raw keys)
335
512
  - `useTranslation` - Translation utilities
336
513
  - `useScreenTranslations` - Screen translation loading
337
514
  - `useTheme` - Theme utilities
338
515
  - `useMfeBridge` - Access MFE bridge
339
- - `useSharedProperty` - Subscribe to shared property
516
+ - `useSharedProperty` - Subscribe to shared property, via `MfeContext` or an explicit bridge
340
517
  - `useHostAction` - Invoke host action
341
518
  - `useDomainExtensions` - Subscribe to domain extensions
342
519
  - `useRegisteredPackages` - Subscribe to registered GTS packages
343
520
  - `useActivePackage` - Subscribe to active GTS package
344
521
 
345
522
  ### Context
346
- - `HAI3Context` - React context (for advanced use)
523
+ - `FrontXContext` - React context (for advanced use)
347
524
  - `MfeContext` - MFE context (for advanced use)
348
525
 
349
526
  ### Types
350
- - `HAI3ProviderProps`
527
+ - `FrontXProviderProps`
528
+ - `ApiQueryResult<TData>` - Gears FrontX-owned query result type (data, error, isLoading, refetch, etc.)
529
+ - `ApiMutationResult<TData>` - Gears FrontX-owned mutation result type (mutateAsync, isPending, error, reset, etc.)
530
+ - `ApiStreamResult<TEvent>` - Gears FrontX-owned stream result type (data, events, status, error, disconnect)
531
+ - `ApiStreamOptions` - Stream hook options (mode, enabled)
532
+ - `QueryCache` - Restricted cache interface (accepts descriptors or raw keys)
533
+ - `MutationCallbackContext` - Context with queryCache injected into mutation callbacks
351
534
  - `MfeProviderProps`, `ExtensionDomainSlotProps`
535
+ - `UseSharedPropertyOptions<T>` - Options for `useSharedProperty` (bridge, fallback)
352
536
  - `UseTranslationReturn`, `UseThemeReturn`
353
- - All types from @gears-frontx/framework
537
+ - All types from @gears-frontx/framework (including `EndpointDescriptor`, `MutationDescriptor`, `StreamDescriptor`, `StreamStatus`)
354
538
 
355
539
  ## Migration from Legacy API
356
540
 
@@ -378,19 +562,17 @@ function MyComponent() {
378
562
 
379
563
  **NEW**: Mount extension using app actions
380
564
  ```tsx
381
- import { useHAI3 } from '@gears-frontx/react';
565
+ import { useFrontX } from '@gears-frontx/react';
382
566
 
383
567
  function MyComponent() {
384
- const app = useHAI3();
568
+ const app = useFrontX();
385
569
  const containerRef = useRef<HTMLDivElement>(null);
386
570
 
387
- const handleNavigate = async () => {
571
+ const handleNavigate = () => {
388
572
  if (containerRef.current) {
389
- await app.actions.mountExtension({
390
- extensionId: 'home',
391
- domainId: 'screen',
392
- container: containerRef.current,
393
- });
573
+ // Fire-and-forget: dispatches an actions chain through the
574
+ // acceptance-only registry surface and returns nothing to await.
575
+ app.actions.mountExtension('home');
394
576
  }
395
577
  };
396
578
 
@@ -403,16 +585,17 @@ function MyComponent() {
403
585
  }
404
586
  ```
405
587
 
406
- **NEW (Alternative)**: Use ExtensionDomainSlot
588
+ **NEW (Preferred for host screen slots)**: Use ExtensionDomainSlot
407
589
  ```tsx
408
590
  import { ExtensionDomainSlot } from '@gears-frontx/react';
409
591
 
410
592
  function MyComponent() {
411
593
  return (
412
594
  <ExtensionDomainSlot
595
+ registry={app.mfeRegistry}
413
596
  domainId="screen"
414
- containerRef={screenContainerRef}
415
- fallback={<Loading />}
597
+ extensionId="home"
598
+ loadingComponent={<Loading />}
416
599
  />
417
600
  );
418
601
  }