@jskit-ai/shell-web 0.1.163 → 0.1.164
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/fixtures/adaptive-shell/src/ScreenPage.vue +1 -1
- package/package.json +8 -253
- package/patterns/application-shell/PATTERN.md +66 -0
- package/{templates → patterns/application-shell/example}/expected-existing/src/pages/home/index.vue +8 -8
- package/{templates → patterns/application-shell/example}/src/pages/home/index.vue +8 -8
- package/{templates → patterns/application-shell/example}/src/pages/home/settings/general/index.vue +1 -1
- package/{templates → patterns/application-shell/example}/src/pages/home/settings.vue +8 -8
- package/patterns/page-and-placement/PATTERN.md +76 -0
- package/patterns/page-and-placement/example/packages/main/src/client/providers/MainClientProvider.js +14 -0
- package/patterns/page-and-placement/example/src/components/SyncStatusElement.vue +16 -0
- package/patterns/page-and-placement/example/src/pages/home/reports/activity.vue +8 -0
- package/patterns/page-and-placement/example/src/pages/home/reports/index.vue +7 -0
- package/patterns/page-and-placement/example/src/pages/home/reports/overview.vue +8 -0
- package/patterns/page-and-placement/example/src/pages/home/reports.vue +33 -0
- package/patterns/page-and-placement/example/src/placement.js +52 -0
- package/patterns/page-and-placement/example/src/placementTopology.js +29 -0
- package/src/client/bootstrap/bootstrapPayloadHandlerRegistry.js +30 -42
- package/src/client/bootstrap/index.js +2 -3
- package/src/client/index.js +2 -11
- package/src/client/placement/runtime.js +9 -12
- package/src/client/providers/ShellWebClientProvider.js +95 -129
- package/src/client/requestRecovery/runtime.js +9 -29
- package/src/client/runtime/bootstrapRuntime.js +18 -29
- package/src/test/adaptiveShellSmoke.js +3 -9
- package/test/bootstrapRuntime.test.js +10 -61
- package/test/pageAndPlacementPattern.test.js +38 -0
- package/test/placementRuntime.test.js +36 -50
- package/test/playwrightContract.test.js +2 -12
- package/test/provider.test.js +126 -737
- package/test/settingsPlacementContract.test.js +75 -126
- package/src/server/support/localLinkItemScaffolds.js +0 -80
- package/test/bootstrapClaimContract.test.js +0 -76
- package/test/linkItemScaffoldContract.test.js +0 -248
- /package/{templates → patterns/application-shell/example}/expected-existing/src/App.vue +0 -0
- /package/{templates → patterns/application-shell/example}/expected-existing/src/pages/home.vue +0 -0
- /package/{templates → patterns/application-shell/example}/src/App.vue +0 -0
- /package/{templates → patterns/application-shell/example}/src/components/ShellLayout.vue +0 -0
- /package/{templates → patterns/application-shell/example}/src/components/menus/MenuLinkItem.vue +0 -0
- /package/{templates → patterns/application-shell/example}/src/components/menus/SurfaceAwareMenuLinkItem.vue +0 -0
- /package/{templates → patterns/application-shell/example}/src/components/menus/TabLinkItem.vue +0 -0
- /package/{templates → patterns/application-shell/example}/src/error.js +0 -0
- /package/{templates → patterns/application-shell/example}/src/pages/home/settings/index.vue +0 -0
- /package/{templates → patterns/application-shell/example}/src/pages/home.vue +0 -0
- /package/{templates → patterns/application-shell/example}/src/placement.js +0 -0
- /package/{templates → patterns/application-shell/example}/src/placementTopology.js +0 -0
- /package/{templates → patterns/application-shell/example}/tests/e2e/adaptive-shell.spec.ts +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getClientAppConfig } from "@jskit-ai/kernel/client";
|
|
2
|
+
import { defineProvider } from "@jskit-ai/kernel/shared/capabilities";
|
|
2
3
|
import {
|
|
3
4
|
createAsyncModuleRecoveryState,
|
|
4
5
|
guardedReloadApp,
|
|
@@ -42,7 +43,7 @@ import { createWebPlacementRuntime } from "../placement/runtime.js";
|
|
|
42
43
|
import { useShellErrorPresentationStore } from "../stores/useShellErrorPresentationStore.js";
|
|
43
44
|
import { buildSurfaceConfigContext } from "../placement/surfaceContext.js";
|
|
44
45
|
import { createShellBootstrapRuntime } from "../runtime/bootstrapRuntime.js";
|
|
45
|
-
import {
|
|
46
|
+
import { createBootstrapPayloadHandlerRegistry } from "../bootstrap/bootstrapPayloadHandlerRegistry.js";
|
|
46
47
|
import { resolveBootstrapErrorStatusCode } from "../bootstrap/bootstrapErrorStatus.js";
|
|
47
48
|
|
|
48
49
|
// Keep this constant for diagnostics, but keep import() below as a literal string so Vite can statically analyze it.
|
|
@@ -221,22 +222,15 @@ function isPullRefreshQuery(query = null) {
|
|
|
221
222
|
}
|
|
222
223
|
|
|
223
224
|
function createShellRefreshRuntime({
|
|
224
|
-
|
|
225
|
+
bootstrapRuntime = null,
|
|
226
|
+
queryClient = null,
|
|
227
|
+
errorRuntime = null,
|
|
225
228
|
logger = null
|
|
226
229
|
} = {}) {
|
|
227
|
-
|
|
228
|
-
throw new Error("createShellRefreshRuntime requires application has()/make().");
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const runtimeLogger = logger || createSharedProviderLogger(app);
|
|
230
|
+
const runtimeLogger = createSharedProviderLogger(logger);
|
|
232
231
|
let refreshQueue = Promise.resolve(null);
|
|
233
232
|
|
|
234
233
|
async function refreshBootstrap(reason) {
|
|
235
|
-
if (!app.has("runtime.web-bootstrap.client")) {
|
|
236
|
-
return false;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const bootstrapRuntime = app.make("runtime.web-bootstrap.client");
|
|
240
234
|
if (!bootstrapRuntime || typeof bootstrapRuntime.refresh !== "function") {
|
|
241
235
|
return false;
|
|
242
236
|
}
|
|
@@ -246,11 +240,6 @@ function createShellRefreshRuntime({
|
|
|
246
240
|
}
|
|
247
241
|
|
|
248
242
|
async function refetchPullQueries() {
|
|
249
|
-
if (!app.has("jskit.client.query-client")) {
|
|
250
|
-
return false;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const queryClient = app.make("jskit.client.query-client");
|
|
254
243
|
if (!queryClient || typeof queryClient.refetchQueries !== "function") {
|
|
255
244
|
return false;
|
|
256
245
|
}
|
|
@@ -275,11 +264,6 @@ function createShellRefreshRuntime({
|
|
|
275
264
|
"Shell refresh failed."
|
|
276
265
|
);
|
|
277
266
|
|
|
278
|
-
if (!app.has("runtime.web-error.client")) {
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const errorRuntime = app.make("runtime.web-error.client");
|
|
283
267
|
if (!errorRuntime || typeof errorRuntime.report !== "function") {
|
|
284
268
|
return;
|
|
285
269
|
}
|
|
@@ -372,12 +356,7 @@ function installVueErrorBridge(vueApp, errorRuntime, logger) {
|
|
|
372
356
|
};
|
|
373
357
|
}
|
|
374
358
|
|
|
375
|
-
function installRouterErrorBridge(
|
|
376
|
-
if (!app.has("jskit.client.router")) {
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
const router = app.make("jskit.client.router");
|
|
359
|
+
function installRouterErrorBridge(router, errorRuntime, logger) {
|
|
381
360
|
if (!router || typeof router.onError !== "function") {
|
|
382
361
|
return;
|
|
383
362
|
}
|
|
@@ -410,28 +389,16 @@ function installRouterErrorBridge(app, errorRuntime, logger) {
|
|
|
410
389
|
}
|
|
411
390
|
|
|
412
391
|
function createShellAsyncModuleRecoveryRuntime({
|
|
413
|
-
|
|
392
|
+
router = null,
|
|
393
|
+
errorRuntime = null,
|
|
414
394
|
logger = null
|
|
415
395
|
} = {}) {
|
|
416
|
-
|
|
417
|
-
throw new Error("createShellAsyncModuleRecoveryRuntime requires application has()/make().");
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
const runtimeLogger = logger || createSharedProviderLogger(app);
|
|
396
|
+
const runtimeLogger = createSharedProviderLogger(logger);
|
|
421
397
|
const state = createAsyncModuleRecoveryState();
|
|
422
398
|
let installedRecovery = null;
|
|
423
399
|
|
|
424
|
-
function errorRuntime() {
|
|
425
|
-
if (!app.has("runtime.web-error.client")) {
|
|
426
|
-
return null;
|
|
427
|
-
}
|
|
428
|
-
const runtime = app.make("runtime.web-error.client");
|
|
429
|
-
return runtime && typeof runtime.report === "function" ? runtime : null;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
400
|
function report(nextState = state) {
|
|
433
|
-
|
|
434
|
-
if (!runtime) {
|
|
401
|
+
if (!errorRuntime || typeof errorRuntime.report !== "function") {
|
|
435
402
|
runtimeLogger.warn(
|
|
436
403
|
{
|
|
437
404
|
label: String(nextState?.label || "")
|
|
@@ -442,7 +409,7 @@ function createShellAsyncModuleRecoveryRuntime({
|
|
|
442
409
|
}
|
|
443
410
|
|
|
444
411
|
try {
|
|
445
|
-
return
|
|
412
|
+
return errorRuntime.report({
|
|
446
413
|
source: "shell-web.async-module-recovery",
|
|
447
414
|
message: String(nextState?.message || "A required app module could not load."),
|
|
448
415
|
cause: nextState?.error || null,
|
|
@@ -503,7 +470,7 @@ function createShellAsyncModuleRecoveryRuntime({
|
|
|
503
470
|
installedRecovery = installAsyncModuleRecoveryHandlers({
|
|
504
471
|
state,
|
|
505
472
|
label: "App module",
|
|
506
|
-
router
|
|
473
|
+
router,
|
|
507
474
|
onNotify: report
|
|
508
475
|
});
|
|
509
476
|
return installedRecovery;
|
|
@@ -524,18 +491,26 @@ function createShellAsyncModuleRecoveryRuntime({
|
|
|
524
491
|
});
|
|
525
492
|
}
|
|
526
493
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
494
|
+
const ShellWebClientProvider = defineProvider({
|
|
495
|
+
id: "shell.web.client",
|
|
496
|
+
requires: {
|
|
497
|
+
components: "client.components",
|
|
498
|
+
loggerInput: "client.logger",
|
|
499
|
+
pinia: "client.pinia",
|
|
500
|
+
queryClient: "client.query",
|
|
501
|
+
router: "client.router",
|
|
502
|
+
surfaceRuntime: "client.surface",
|
|
503
|
+
vueApp: "client.vue"
|
|
504
|
+
},
|
|
505
|
+
provides: {
|
|
506
|
+
shell: "client.shell"
|
|
507
|
+
},
|
|
508
|
+
setup({ components, loggerInput, queryClient, router }) {
|
|
509
|
+
const logger = createSharedProviderLogger(loggerInput);
|
|
510
|
+
const bootstrapHandlers = createBootstrapPayloadHandlerRegistry();
|
|
511
|
+
bootstrapHandlers.register(
|
|
537
512
|
Object.freeze({
|
|
538
|
-
handlerId: "shell.web.bootstrap.
|
|
513
|
+
handlerId: "shell.web.bootstrap.surface-access",
|
|
539
514
|
order: 0,
|
|
540
515
|
applyBootstrapPayload({ payload = {}, placementRuntime, source } = {}) {
|
|
541
516
|
placementRuntime.setContext(
|
|
@@ -543,77 +518,76 @@ class ShellWebClientProvider {
|
|
|
543
518
|
surfaceAccess:
|
|
544
519
|
payload?.surfaceAccess && typeof payload.surfaceAccess === "object" ? payload.surfaceAccess : {}
|
|
545
520
|
},
|
|
546
|
-
{
|
|
547
|
-
source
|
|
548
|
-
}
|
|
521
|
+
{ source }
|
|
549
522
|
);
|
|
550
523
|
},
|
|
551
524
|
handleBootstrapError({ error, placementRuntime, source } = {}) {
|
|
552
525
|
if (resolveBootstrapErrorStatusCode(error) !== 401) {
|
|
553
526
|
return;
|
|
554
527
|
}
|
|
555
|
-
|
|
556
|
-
placementRuntime.setContext(
|
|
557
|
-
{
|
|
558
|
-
surfaceAccess: {}
|
|
559
|
-
},
|
|
560
|
-
{
|
|
561
|
-
source
|
|
562
|
-
}
|
|
563
|
-
);
|
|
528
|
+
placementRuntime.setContext({ surfaceAccess: {} }, { source });
|
|
564
529
|
}
|
|
565
530
|
})
|
|
566
531
|
);
|
|
567
|
-
app.singleton("runtime.web-placement.client", () => createWebPlacementRuntime({ app, logger }));
|
|
568
|
-
app.singleton("runtime.web-bootstrap.client", (scope) =>
|
|
569
|
-
createShellBootstrapRuntime({
|
|
570
|
-
app: scope,
|
|
571
|
-
logger
|
|
572
|
-
})
|
|
573
|
-
);
|
|
574
|
-
app.singleton("runtime.web-refresh.client", (scope) =>
|
|
575
|
-
createShellRefreshRuntime({
|
|
576
|
-
app: scope,
|
|
577
|
-
logger
|
|
578
|
-
})
|
|
579
|
-
);
|
|
580
|
-
app.singleton("runtime.web-async-module-recovery.client", (scope) =>
|
|
581
|
-
createShellAsyncModuleRecoveryRuntime({
|
|
582
|
-
app: scope,
|
|
583
|
-
logger
|
|
584
|
-
})
|
|
585
|
-
);
|
|
586
|
-
app.singleton("runtime.web-request-recovery.client", (scope) =>
|
|
587
|
-
createShellRequestRecoveryRuntime({
|
|
588
|
-
app: scope,
|
|
589
|
-
logger
|
|
590
|
-
})
|
|
591
|
-
);
|
|
592
|
-
app.singleton("runtime.web-error.presentation-store.client", () => createErrorPresentationStore());
|
|
593
|
-
app.singleton("runtime.web-error.client", (scope) =>
|
|
594
|
-
createErrorRuntime({
|
|
595
|
-
presenters: createDefaultMaterialErrorPresenters({
|
|
596
|
-
store: scope.make("runtime.web-error.presentation-store.client")
|
|
597
|
-
}),
|
|
598
|
-
policy: createDefaultErrorPolicy(),
|
|
599
|
-
moduleDefaultPresenterId: "material.snackbar",
|
|
600
|
-
logger
|
|
601
|
-
})
|
|
602
|
-
);
|
|
603
|
-
}
|
|
604
532
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
533
|
+
const errorPresentationStore = createErrorPresentationStore();
|
|
534
|
+
const errorRuntime = createErrorRuntime({
|
|
535
|
+
presenters: createDefaultMaterialErrorPresenters({ store: errorPresentationStore }),
|
|
536
|
+
policy: createDefaultErrorPolicy(),
|
|
537
|
+
moduleDefaultPresenterId: "material.snackbar",
|
|
538
|
+
logger
|
|
539
|
+
});
|
|
540
|
+
const placementRuntime = createWebPlacementRuntime({ components, logger });
|
|
541
|
+
const asyncModuleRecoveryRuntime = createShellAsyncModuleRecoveryRuntime({
|
|
542
|
+
router,
|
|
543
|
+
errorRuntime,
|
|
544
|
+
logger
|
|
545
|
+
});
|
|
546
|
+
const requestRecoveryRuntime = createShellRequestRecoveryRuntime({
|
|
547
|
+
queryClient,
|
|
548
|
+
errorRuntime,
|
|
549
|
+
logger
|
|
550
|
+
});
|
|
551
|
+
const bootstrapRuntime = createShellBootstrapRuntime({
|
|
552
|
+
handlers: bootstrapHandlers,
|
|
553
|
+
placementRuntime,
|
|
554
|
+
requestRecoveryRuntime,
|
|
555
|
+
router,
|
|
556
|
+
logger
|
|
557
|
+
});
|
|
558
|
+
const refreshRuntime = createShellRefreshRuntime({
|
|
559
|
+
bootstrapRuntime,
|
|
560
|
+
queryClient,
|
|
561
|
+
errorRuntime,
|
|
562
|
+
logger
|
|
563
|
+
});
|
|
609
564
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
565
|
+
return {
|
|
566
|
+
shell: Object.freeze({
|
|
567
|
+
asyncModuleRecovery: asyncModuleRecoveryRuntime,
|
|
568
|
+
bootstrap: bootstrapRuntime,
|
|
569
|
+
bootstrapHandlers,
|
|
570
|
+
error: errorRuntime,
|
|
571
|
+
errorPresentationStore,
|
|
572
|
+
placement: placementRuntime,
|
|
573
|
+
refresh: refreshRuntime,
|
|
574
|
+
requestRecovery: requestRecoveryRuntime
|
|
575
|
+
})
|
|
576
|
+
};
|
|
577
|
+
},
|
|
578
|
+
async boot({ loggerInput, pinia, router, surfaceRuntime, vueApp }, { outputs }) {
|
|
579
|
+
const logger = createSharedProviderLogger(loggerInput);
|
|
580
|
+
const {
|
|
581
|
+
asyncModuleRecovery: asyncModuleRecoveryRuntime,
|
|
582
|
+
bootstrap: bootstrapRuntime,
|
|
583
|
+
error: errorRuntime,
|
|
584
|
+
errorPresentationStore,
|
|
585
|
+
placement: placementRuntime,
|
|
586
|
+
refresh: refreshRuntime,
|
|
587
|
+
requestRecovery: requestRecoveryRuntime
|
|
588
|
+
} = outputs.shell;
|
|
614
589
|
asyncModuleRecoveryRuntime.install();
|
|
615
590
|
|
|
616
|
-
const placementRuntime = app.make("runtime.web-placement.client");
|
|
617
591
|
if (placementRuntime && typeof placementRuntime.replacePlacements === "function") {
|
|
618
592
|
const placementTopology = await loadAppPlacementTopology(logger, asyncModuleRecoveryRuntime);
|
|
619
593
|
if (typeof placementRuntime.replacePlacementTopology === "function") {
|
|
@@ -622,9 +596,6 @@ class ShellWebClientProvider {
|
|
|
622
596
|
const placements = await loadAppPlacementDefinitions(logger, asyncModuleRecoveryRuntime);
|
|
623
597
|
placementRuntime.replacePlacements(placements, { source: APP_PLACEMENT_MODULE_SPECIFIER });
|
|
624
598
|
const appConfig = getClientAppConfig();
|
|
625
|
-
const surfaceRuntime = app.has("jskit.client.surface.runtime")
|
|
626
|
-
? app.make("jskit.client.surface.runtime")
|
|
627
|
-
: null;
|
|
628
599
|
const surfaceConfig = buildSurfaceConfigContext(surfaceRuntime, {
|
|
629
600
|
tenancyMode: appConfig?.tenancyMode
|
|
630
601
|
});
|
|
@@ -647,25 +618,16 @@ class ShellWebClientProvider {
|
|
|
647
618
|
applyAppErrorConfig(errorRuntime, errorConfig);
|
|
648
619
|
requestRecoveryRuntime.install();
|
|
649
620
|
|
|
650
|
-
const bootstrapRuntime = app.make("runtime.web-bootstrap.client");
|
|
651
621
|
if (bootstrapRuntime && typeof bootstrapRuntime.initialize === "function") {
|
|
652
622
|
await bootstrapRuntime.initialize();
|
|
653
623
|
}
|
|
654
624
|
|
|
655
|
-
if (!app.has("jskit.client.vue.app")) {
|
|
656
|
-
return;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
const vueApp = app.make("jskit.client.vue.app");
|
|
660
625
|
if (!vueApp || typeof vueApp.provide !== "function" || typeof vueApp.use !== "function") {
|
|
661
626
|
return;
|
|
662
627
|
}
|
|
663
|
-
const pinia = app.make("jskit.client.pinia");
|
|
664
628
|
if (!pinia) {
|
|
665
629
|
throw new Error("ShellWebClientProvider requires Pinia installed in the client app.");
|
|
666
630
|
}
|
|
667
|
-
const errorPresentationStore = app.make("runtime.web-error.presentation-store.client");
|
|
668
|
-
const refreshRuntime = app.make("runtime.web-refresh.client");
|
|
669
631
|
useShellErrorPresentationStore(pinia).attachRuntimeStore(errorPresentationStore);
|
|
670
632
|
|
|
671
633
|
vueApp.provide("jskit.shell-web.runtime.web-placement.client", placementRuntime);
|
|
@@ -679,9 +641,13 @@ class ShellWebClientProvider {
|
|
|
679
641
|
);
|
|
680
642
|
|
|
681
643
|
installVueErrorBridge(vueApp, errorRuntime, logger);
|
|
682
|
-
installRouterErrorBridge(
|
|
644
|
+
installRouterErrorBridge(router, errorRuntime, logger);
|
|
645
|
+
},
|
|
646
|
+
shutdown(_dependencies, { outputs }) {
|
|
647
|
+
outputs.shell.requestRecovery.dispose();
|
|
648
|
+
outputs.shell.asyncModuleRecovery.dispose();
|
|
683
649
|
}
|
|
684
|
-
}
|
|
650
|
+
});
|
|
685
651
|
|
|
686
652
|
export {
|
|
687
653
|
ShellWebClientProvider,
|
|
@@ -235,17 +235,10 @@ function resolveQueryHash(query = null) {
|
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
function installRecoverableQueryObserver({
|
|
238
|
-
|
|
238
|
+
queryClient,
|
|
239
239
|
runtime,
|
|
240
240
|
logger
|
|
241
241
|
} = {}) {
|
|
242
|
-
if (!app?.has?.("jskit.client.query-client")) {
|
|
243
|
-
return Object.freeze({
|
|
244
|
-
dispose() {}
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const queryClient = app.make("jskit.client.query-client");
|
|
249
242
|
const queryCache =
|
|
250
243
|
queryClient && typeof queryClient.getQueryCache === "function"
|
|
251
244
|
? queryClient.getQueryCache()
|
|
@@ -373,25 +366,14 @@ function installRecoverableQueryObserver({
|
|
|
373
366
|
}
|
|
374
367
|
|
|
375
368
|
function createShellRequestRecoveryRuntime({
|
|
376
|
-
|
|
369
|
+
queryClient = null,
|
|
370
|
+
errorRuntime = null,
|
|
377
371
|
logger = null
|
|
378
372
|
} = {}) {
|
|
379
|
-
|
|
380
|
-
throw new Error("createShellRequestRecoveryRuntime requires application has()/make().");
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
const runtimeLogger = logger || createSharedProviderLogger(app);
|
|
373
|
+
const runtimeLogger = createSharedProviderLogger(logger);
|
|
384
374
|
let installedQueryObserver = null;
|
|
385
375
|
let reloadAttempt = 0;
|
|
386
376
|
|
|
387
|
-
function errorRuntime() {
|
|
388
|
-
if (!app.has("runtime.web-error.client")) {
|
|
389
|
-
return null;
|
|
390
|
-
}
|
|
391
|
-
const runtime = app.make("runtime.web-error.client");
|
|
392
|
-
return runtime && typeof runtime.report === "function" ? runtime : null;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
377
|
async function reload({
|
|
396
378
|
label = "App",
|
|
397
379
|
message = REQUEST_RECOVERY_RELOAD_FAILURE_MESSAGE
|
|
@@ -445,11 +427,10 @@ function createShellRequestRecoveryRuntime({
|
|
|
445
427
|
}
|
|
446
428
|
|
|
447
429
|
function dismiss(presentationId = "", options = {}) {
|
|
448
|
-
|
|
449
|
-
if (!runtime || typeof runtime.dismiss !== "function") {
|
|
430
|
+
if (!errorRuntime || typeof errorRuntime.dismiss !== "function") {
|
|
450
431
|
return 0;
|
|
451
432
|
}
|
|
452
|
-
return
|
|
433
|
+
return errorRuntime.dismiss(presentationId, options);
|
|
453
434
|
}
|
|
454
435
|
|
|
455
436
|
function report(error = null, options = {}) {
|
|
@@ -457,9 +438,8 @@ function createShellRequestRecoveryRuntime({
|
|
|
457
438
|
return null;
|
|
458
439
|
}
|
|
459
440
|
|
|
460
|
-
const runtime = errorRuntime();
|
|
461
441
|
const source = normalizeText(options.source, "shell-web.request-recovery");
|
|
462
|
-
if (!
|
|
442
|
+
if (!errorRuntime || typeof errorRuntime.report !== "function") {
|
|
463
443
|
runtimeLogger.warn(
|
|
464
444
|
{
|
|
465
445
|
source,
|
|
@@ -478,7 +458,7 @@ function createShellRequestRecoveryRuntime({
|
|
|
478
458
|
: null;
|
|
479
459
|
|
|
480
460
|
try {
|
|
481
|
-
return
|
|
461
|
+
return errorRuntime.report({
|
|
482
462
|
source,
|
|
483
463
|
message: requestRecoveryMessage(error, options),
|
|
484
464
|
cause: error || null,
|
|
@@ -508,7 +488,7 @@ function createShellRequestRecoveryRuntime({
|
|
|
508
488
|
}
|
|
509
489
|
|
|
510
490
|
installedQueryObserver = installRecoverableQueryObserver({
|
|
511
|
-
|
|
491
|
+
queryClient,
|
|
512
492
|
runtime: api,
|
|
513
493
|
logger: runtimeLogger
|
|
514
494
|
});
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { createProviderLogger
|
|
2
|
-
import { resolveBootstrapPayloadHandlers } from "../bootstrap/bootstrapPayloadHandlerRegistry.js";
|
|
1
|
+
import { createProviderLogger } from "@jskit-ai/kernel/shared/support/providerLogger";
|
|
3
2
|
|
|
4
3
|
const DEFAULT_BOOTSTRAP_PATH = "/api/bootstrap";
|
|
5
4
|
|
|
@@ -32,48 +31,40 @@ function normalizeBootstrapResponseError(response, url) {
|
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
function createShellBootstrapRuntime({
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
handlers,
|
|
35
|
+
placementRuntime,
|
|
36
|
+
requestRecoveryRuntime = null,
|
|
37
|
+
router = null,
|
|
38
|
+
logger,
|
|
37
39
|
fetchImplementation = globalThis.fetch,
|
|
38
40
|
bootstrapPath = DEFAULT_BOOTSTRAP_PATH
|
|
39
41
|
} = {}) {
|
|
40
|
-
if (!
|
|
41
|
-
throw new Error("createShellBootstrapRuntime requires
|
|
42
|
+
if (!handlers || typeof handlers.list !== "function") {
|
|
43
|
+
throw new Error("createShellBootstrapRuntime requires a bootstrap handler registry.");
|
|
42
44
|
}
|
|
43
|
-
if (!
|
|
44
|
-
throw new Error("createShellBootstrapRuntime requires
|
|
45
|
+
if (!placementRuntime || typeof placementRuntime.setContext !== "function") {
|
|
46
|
+
throw new Error("createShellBootstrapRuntime requires a placement runtime.");
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
const runtimeLogger = logger
|
|
48
|
-
const placementRuntime = app.make("runtime.web-placement.client");
|
|
49
|
-
const router = app.has("jskit.client.router") ? app.make("jskit.client.router") : null;
|
|
49
|
+
const runtimeLogger = createProviderLogger(logger);
|
|
50
50
|
let initialized = false;
|
|
51
51
|
let refreshQueue = Promise.resolve();
|
|
52
52
|
|
|
53
|
-
function requestRecoveryRuntime() {
|
|
54
|
-
if (!app.has("runtime.web-request-recovery.client")) {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
const runtime = app.make("runtime.web-request-recovery.client");
|
|
58
|
-
return runtime && typeof runtime.report === "function" ? runtime : null;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
53
|
async function resolveBootstrapRequest(reason = "manual") {
|
|
62
|
-
const
|
|
54
|
+
const payloadHandlers = handlers.list();
|
|
63
55
|
let request = {
|
|
64
56
|
path: bootstrapPath,
|
|
65
57
|
query: {},
|
|
66
58
|
meta: {}
|
|
67
59
|
};
|
|
68
60
|
|
|
69
|
-
for (const handler of
|
|
61
|
+
for (const handler of payloadHandlers) {
|
|
70
62
|
if (typeof handler.resolveBootstrapRequest !== "function") {
|
|
71
63
|
continue;
|
|
72
64
|
}
|
|
73
65
|
|
|
74
66
|
const contribution = normalizeObject(
|
|
75
67
|
await handler.resolveBootstrapRequest({
|
|
76
|
-
app,
|
|
77
68
|
router,
|
|
78
69
|
placementRuntime,
|
|
79
70
|
reason,
|
|
@@ -106,12 +97,11 @@ function createShellBootstrapRuntime({
|
|
|
106
97
|
}
|
|
107
98
|
|
|
108
99
|
async function applyBootstrapPayload(payload, reason = "manual", request = Object.freeze({})) {
|
|
109
|
-
const
|
|
100
|
+
const payloadHandlers = handlers.list();
|
|
110
101
|
const source = `shell-web.bootstrap.${String(reason || "manual").trim() || "manual"}`;
|
|
111
102
|
|
|
112
|
-
for (const handler of
|
|
103
|
+
for (const handler of payloadHandlers) {
|
|
113
104
|
await handler.applyBootstrapPayload({
|
|
114
|
-
app,
|
|
115
105
|
router,
|
|
116
106
|
placementRuntime,
|
|
117
107
|
payload,
|
|
@@ -125,16 +115,15 @@ function createShellBootstrapRuntime({
|
|
|
125
115
|
}
|
|
126
116
|
|
|
127
117
|
async function applyBootstrapError(error, reason = "manual", request = Object.freeze({})) {
|
|
128
|
-
const
|
|
118
|
+
const payloadHandlers = handlers.list();
|
|
129
119
|
const source = `shell-web.bootstrap.${String(reason || "manual").trim() || "manual"}`;
|
|
130
120
|
|
|
131
|
-
for (const handler of
|
|
121
|
+
for (const handler of payloadHandlers) {
|
|
132
122
|
if (typeof handler.handleBootstrapError !== "function") {
|
|
133
123
|
continue;
|
|
134
124
|
}
|
|
135
125
|
|
|
136
126
|
await handler.handleBootstrapError({
|
|
137
|
-
app,
|
|
138
127
|
router,
|
|
139
128
|
placementRuntime,
|
|
140
129
|
error,
|
|
@@ -170,7 +159,7 @@ function createShellBootstrapRuntime({
|
|
|
170
159
|
return applyBootstrapPayload(payload, reason, request);
|
|
171
160
|
} catch (error) {
|
|
172
161
|
await applyBootstrapError(error, reason, request);
|
|
173
|
-
requestRecoveryRuntime
|
|
162
|
+
requestRecoveryRuntime?.report?.(error, {
|
|
174
163
|
label: "App data",
|
|
175
164
|
retry: () => refresh(reason),
|
|
176
165
|
source: "shell-web.bootstrap",
|
|
@@ -14,12 +14,6 @@ async function expectNoHorizontalOverflow(page, expect) {
|
|
|
14
14
|
expect(metrics.scrollWidth).toBeLessThanOrEqual(metrics.clientWidth + 1);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
async function expectGeneratedScreenContract(page, expect) {
|
|
18
|
-
const screen = page.locator(".generated-ui-screen").first();
|
|
19
|
-
|
|
20
|
-
await expect(screen).toBeVisible();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
17
|
async function pullToRefresh(page, expect) {
|
|
24
18
|
await page.evaluate(() => {
|
|
25
19
|
window.scrollTo(0, 0);
|
|
@@ -280,7 +274,7 @@ async function runAdaptiveShellSmokeCase({
|
|
|
280
274
|
await page.setViewportSize({ width: viewport.width, height: viewport.height });
|
|
281
275
|
await page.goto(smokePath);
|
|
282
276
|
await expect(page.locator("body")).toBeVisible();
|
|
283
|
-
await
|
|
277
|
+
await expect(page.getByRole("main").first()).toBeVisible();
|
|
284
278
|
await expectNoHorizontalOverflow(page, expect);
|
|
285
279
|
const drawer = page.getByTestId("jskit-shell-drawer");
|
|
286
280
|
const toggle = page.getByTestId("jskit-shell-nav-toggle");
|
|
@@ -302,7 +296,7 @@ async function runAdaptiveShellSmokeCase({
|
|
|
302
296
|
await expectElementVisibility(page, expect, "jskit-shell-drawer", false);
|
|
303
297
|
|
|
304
298
|
await openCompactDrawer(page, expect);
|
|
305
|
-
await page.
|
|
299
|
+
await page.getByRole("button", { name: "Close navigation menu" }).click();
|
|
306
300
|
await expectElementVisibility(page, expect, "jskit-shell-drawer", false);
|
|
307
301
|
|
|
308
302
|
await openCompactDrawer(page, expect);
|
|
@@ -350,7 +344,7 @@ function runAdaptiveShellSmoke({
|
|
|
350
344
|
throw new Error("runAdaptiveShellSmoke requires Playwright test and expect.");
|
|
351
345
|
}
|
|
352
346
|
|
|
353
|
-
test.describe("
|
|
347
|
+
test.describe("adaptive shell smoke", () => {
|
|
354
348
|
for (const viewport of viewports) {
|
|
355
349
|
test(`${viewport.name} layout has reachable navigation and no horizontal overflow`, async ({ page }) => {
|
|
356
350
|
await runAdaptiveShellSmokeCase({ page, expect, smokePath, viewport });
|