@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.
Files changed (46) hide show
  1. package/fixtures/adaptive-shell/src/ScreenPage.vue +1 -1
  2. package/package.json +8 -253
  3. package/patterns/application-shell/PATTERN.md +66 -0
  4. package/{templates → patterns/application-shell/example}/expected-existing/src/pages/home/index.vue +8 -8
  5. package/{templates → patterns/application-shell/example}/src/pages/home/index.vue +8 -8
  6. package/{templates → patterns/application-shell/example}/src/pages/home/settings/general/index.vue +1 -1
  7. package/{templates → patterns/application-shell/example}/src/pages/home/settings.vue +8 -8
  8. package/patterns/page-and-placement/PATTERN.md +76 -0
  9. package/patterns/page-and-placement/example/packages/main/src/client/providers/MainClientProvider.js +14 -0
  10. package/patterns/page-and-placement/example/src/components/SyncStatusElement.vue +16 -0
  11. package/patterns/page-and-placement/example/src/pages/home/reports/activity.vue +8 -0
  12. package/patterns/page-and-placement/example/src/pages/home/reports/index.vue +7 -0
  13. package/patterns/page-and-placement/example/src/pages/home/reports/overview.vue +8 -0
  14. package/patterns/page-and-placement/example/src/pages/home/reports.vue +33 -0
  15. package/patterns/page-and-placement/example/src/placement.js +52 -0
  16. package/patterns/page-and-placement/example/src/placementTopology.js +29 -0
  17. package/src/client/bootstrap/bootstrapPayloadHandlerRegistry.js +30 -42
  18. package/src/client/bootstrap/index.js +2 -3
  19. package/src/client/index.js +2 -11
  20. package/src/client/placement/runtime.js +9 -12
  21. package/src/client/providers/ShellWebClientProvider.js +95 -129
  22. package/src/client/requestRecovery/runtime.js +9 -29
  23. package/src/client/runtime/bootstrapRuntime.js +18 -29
  24. package/src/test/adaptiveShellSmoke.js +3 -9
  25. package/test/bootstrapRuntime.test.js +10 -61
  26. package/test/pageAndPlacementPattern.test.js +38 -0
  27. package/test/placementRuntime.test.js +36 -50
  28. package/test/playwrightContract.test.js +2 -12
  29. package/test/provider.test.js +126 -737
  30. package/test/settingsPlacementContract.test.js +75 -126
  31. package/src/server/support/localLinkItemScaffolds.js +0 -80
  32. package/test/bootstrapClaimContract.test.js +0 -76
  33. package/test/linkItemScaffoldContract.test.js +0 -248
  34. /package/{templates → patterns/application-shell/example}/expected-existing/src/App.vue +0 -0
  35. /package/{templates → patterns/application-shell/example}/expected-existing/src/pages/home.vue +0 -0
  36. /package/{templates → patterns/application-shell/example}/src/App.vue +0 -0
  37. /package/{templates → patterns/application-shell/example}/src/components/ShellLayout.vue +0 -0
  38. /package/{templates → patterns/application-shell/example}/src/components/menus/MenuLinkItem.vue +0 -0
  39. /package/{templates → patterns/application-shell/example}/src/components/menus/SurfaceAwareMenuLinkItem.vue +0 -0
  40. /package/{templates → patterns/application-shell/example}/src/components/menus/TabLinkItem.vue +0 -0
  41. /package/{templates → patterns/application-shell/example}/src/error.js +0 -0
  42. /package/{templates → patterns/application-shell/example}/src/pages/home/settings/index.vue +0 -0
  43. /package/{templates → patterns/application-shell/example}/src/pages/home.vue +0 -0
  44. /package/{templates → patterns/application-shell/example}/src/placement.js +0 -0
  45. /package/{templates → patterns/application-shell/example}/src/placementTopology.js +0 -0
  46. /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 { registerBootstrapPayloadHandler } from "../bootstrap/bootstrapPayloadHandlerRegistry.js";
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
- app,
225
+ bootstrapRuntime = null,
226
+ queryClient = null,
227
+ errorRuntime = null,
225
228
  logger = null
226
229
  } = {}) {
227
- if (!app || typeof app.has !== "function" || typeof app.make !== "function") {
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(app, errorRuntime, logger) {
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
- app,
392
+ router = null,
393
+ errorRuntime = null,
414
394
  logger = null
415
395
  } = {}) {
416
- if (!app || typeof app.has !== "function" || typeof app.make !== "function") {
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
- const runtime = errorRuntime();
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 runtime.report({
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: app.has("jskit.client.router") ? app.make("jskit.client.router") : null,
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
- class ShellWebClientProvider {
528
- static id = "shell.web.client";
529
-
530
- register(app) {
531
- if (!app || typeof app.singleton !== "function" || typeof app.tag !== "function") {
532
- throw new Error("ShellWebClientProvider requires application singleton()/tag().");
533
- }
534
-
535
- const logger = createSharedProviderLogger(isRecord(app) ? app : null);
536
- registerBootstrapPayloadHandler(app, "shell.web.bootstrap.surfaceAccessHandler", () =>
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.surfaceAccess",
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
- async boot(app) {
606
- if (!app || typeof app.make !== "function" || typeof app.has !== "function") {
607
- throw new Error("ShellWebClientProvider requires application make()/has().");
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
- const logger = createSharedProviderLogger(isRecord(app) ? app : null);
611
- const errorRuntime = app.make("runtime.web-error.client");
612
- const asyncModuleRecoveryRuntime = app.make("runtime.web-async-module-recovery.client");
613
- const requestRecoveryRuntime = app.make("runtime.web-request-recovery.client");
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(app, errorRuntime, logger);
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
- app,
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
- app,
369
+ queryClient = null,
370
+ errorRuntime = null,
377
371
  logger = null
378
372
  } = {}) {
379
- if (!app || typeof app.has !== "function" || typeof app.make !== "function") {
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
- const runtime = errorRuntime();
449
- if (!runtime || typeof runtime.dismiss !== "function") {
430
+ if (!errorRuntime || typeof errorRuntime.dismiss !== "function") {
450
431
  return 0;
451
432
  }
452
- return runtime.dismiss(presentationId, options);
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 (!runtime) {
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 runtime.report({
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
- app,
491
+ queryClient,
512
492
  runtime: api,
513
493
  logger: runtimeLogger
514
494
  });
@@ -1,5 +1,4 @@
1
- import { createProviderLogger as createSharedProviderLogger } from "@jskit-ai/kernel/shared/support/providerLogger";
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
- app,
36
- logger = null,
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 (!app || typeof app.has !== "function" || typeof app.make !== "function" || typeof app.resolveTag !== "function") {
41
- throw new Error("createShellBootstrapRuntime requires application has()/make()/resolveTag().");
42
+ if (!handlers || typeof handlers.list !== "function") {
43
+ throw new Error("createShellBootstrapRuntime requires a bootstrap handler registry.");
42
44
  }
43
- if (!app.has("runtime.web-placement.client")) {
44
- throw new Error("createShellBootstrapRuntime requires shell-web placement runtime.");
45
+ if (!placementRuntime || typeof placementRuntime.setContext !== "function") {
46
+ throw new Error("createShellBootstrapRuntime requires a placement runtime.");
45
47
  }
46
48
 
47
- const runtimeLogger = logger || createSharedProviderLogger(app);
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 handlers = resolveBootstrapPayloadHandlers(app);
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 handlers) {
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 handlers = resolveBootstrapPayloadHandlers(app);
100
+ const payloadHandlers = handlers.list();
110
101
  const source = `shell-web.bootstrap.${String(reason || "manual").trim() || "manual"}`;
111
102
 
112
- for (const handler of handlers) {
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 handlers = resolveBootstrapPayloadHandlers(app);
118
+ const payloadHandlers = handlers.list();
129
119
  const source = `shell-web.bootstrap.${String(reason || "manual").trim() || "manual"}`;
130
120
 
131
- for (const handler of handlers) {
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()?.report?.(error, {
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 expectGeneratedScreenContract(page, expect);
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.locator(".v-navigation-drawer__scrim").click();
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("generated adaptive shell smoke", () => {
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 });