@jskit-ai/shell-web 0.1.162 → 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
@@ -0,0 +1,7 @@
1
+ <script setup>
2
+ import { redirectToChild } from "@jskit-ai/kernel/client/pageRedirects";
3
+
4
+ definePage({
5
+ redirect: redirectToChild("overview")
6
+ });
7
+ </script>
@@ -0,0 +1,8 @@
1
+ <template>
2
+ <section aria-labelledby="reports-overview-title">
3
+ <h2 id="reports-overview-title" class="text-h5 mb-2">Overview</h2>
4
+ <p class="text-body-1 text-medium-emphasis mb-0">
5
+ Your reading and catalogue summary appears here.
6
+ </p>
7
+ </section>
8
+ </template>
@@ -0,0 +1,33 @@
1
+ <script setup>
2
+ import ShellOutlet from "@jskit-ai/shell-web/client/components/ShellOutlet";
3
+ import { RouterView } from "vue-router";
4
+ </script>
5
+
6
+ <template>
7
+ <section class="reports-section d-flex flex-column ga-4">
8
+ <header>
9
+ <p class="text-overline text-medium-emphasis mb-1">Insights</p>
10
+ <h1 class="text-h4 font-weight-medium mb-1">Reports</h1>
11
+ <p class="text-body-2 text-medium-emphasis mb-0">
12
+ Review catalogue activity and completion trends.
13
+ </p>
14
+ </header>
15
+
16
+ <nav aria-label="Report sections">
17
+ <v-tabs density="comfortable">
18
+ <ShellOutlet target="reports:section-nav" />
19
+ </v-tabs>
20
+ </nav>
21
+
22
+ <main class="reports-section__content">
23
+ <RouterView />
24
+ </main>
25
+ </section>
26
+ </template>
27
+
28
+ <style scoped>
29
+ .reports-section,
30
+ .reports-section__content {
31
+ min-width: 0;
32
+ }
33
+ </style>
@@ -0,0 +1,52 @@
1
+ import { createPlacementRegistry } from "@jskit-ai/shell-web/client/placement";
2
+
3
+ const registry = createPlacementRegistry();
4
+ const { addPlacement } = registry;
5
+
6
+ addPlacement({
7
+ id: "catalogue.reports.link",
8
+ target: "shell.primary-nav",
9
+ kind: "link",
10
+ surfaces: ["home"],
11
+ order: 120,
12
+ props: {
13
+ label: "Reports",
14
+ icon: "mdi-chart-box-outline",
15
+ surface: "home",
16
+ scopedSuffix: "/reports",
17
+ unscopedSuffix: "/reports"
18
+ }
19
+ });
20
+
21
+ for (const [id, label, suffix, order] of [
22
+ ["overview", "Overview", "/reports/overview", 100],
23
+ ["activity", "Activity", "/reports/activity", 110]
24
+ ]) {
25
+ addPlacement({
26
+ id: `catalogue.reports.${id}.link`,
27
+ target: "page.section-nav",
28
+ owner: "reports",
29
+ kind: "link",
30
+ surfaces: ["home"],
31
+ order,
32
+ props: {
33
+ label,
34
+ surface: "home",
35
+ scopedSuffix: suffix,
36
+ unscopedSuffix: suffix
37
+ }
38
+ });
39
+ }
40
+
41
+ addPlacement({
42
+ id: "catalogue.sync-status",
43
+ target: "shell.status",
44
+ kind: "component",
45
+ surfaces: ["home"],
46
+ order: 100,
47
+ componentToken: "local.main.ui.sync-status"
48
+ });
49
+
50
+ export default function getPlacements() {
51
+ return registry.build();
52
+ }
@@ -0,0 +1,29 @@
1
+ const placements = [];
2
+
3
+ function addPlacementTopology(value = {}) {
4
+ placements.push(value);
5
+ }
6
+
7
+ addPlacementTopology({
8
+ id: "page.section-nav",
9
+ owner: "reports",
10
+ description: "Navigation between report pages.",
11
+ surfaces: ["home"],
12
+ variants: {
13
+ compact: {
14
+ outlet: "reports:section-nav",
15
+ renderers: { link: "local.main.ui.tab-link-item" }
16
+ },
17
+ medium: {
18
+ outlet: "reports:section-nav",
19
+ renderers: { link: "local.main.ui.tab-link-item" }
20
+ },
21
+ expanded: {
22
+ outlet: "reports:section-nav",
23
+ renderers: { link: "local.main.ui.tab-link-item" }
24
+ }
25
+ }
26
+ });
27
+
28
+ export { addPlacementTopology };
29
+ export default { placements };
@@ -1,17 +1,3 @@
1
- const BOOTSTRAP_PAYLOAD_HANDLER_TAG = "runtime.web-bootstrap.handlers.client";
2
-
3
- function assertTaggableApp(app, context = "bootstrap payload handler registry") {
4
- if (!app || typeof app.singleton !== "function" || typeof app.tag !== "function") {
5
- throw new Error(`${context} requires application singleton()/tag().`);
6
- }
7
- }
8
-
9
- function registerBootstrapPayloadHandler(app, token, factory) {
10
- assertTaggableApp(app, "registerBootstrapPayloadHandler");
11
- app.singleton(token, factory);
12
- app.tag(token, BOOTSTRAP_PAYLOAD_HANDLER_TAG);
13
- }
14
-
15
1
  function normalizeBootstrapPayloadHandler(entry) {
16
2
  if (typeof entry === "function") {
17
3
  return Object.freeze({
@@ -22,47 +8,49 @@ function normalizeBootstrapPayloadHandler(entry) {
22
8
  }
23
9
 
24
10
  if (!entry || typeof entry !== "object" || typeof entry.applyBootstrapPayload !== "function") {
25
- return null;
11
+ throw new TypeError("Bootstrap payload handlers require applyBootstrapPayload().");
12
+ }
13
+
14
+ const handlerId = String(entry.handlerId || "").trim();
15
+ if (!handlerId) {
16
+ throw new TypeError("Bootstrap payload handlers require handlerId.");
26
17
  }
27
18
 
28
19
  return Object.freeze({
29
20
  ...entry,
30
- handlerId: String(entry.handlerId || "anonymous"),
21
+ handlerId,
31
22
  order: Number.isFinite(entry.order) ? Number(entry.order) : 0
32
23
  });
33
24
  }
34
25
 
35
- function resolveBootstrapPayloadHandlers(scope) {
36
- if (!scope || typeof scope.resolveTag !== "function") {
37
- return [];
38
- }
39
-
40
- const rawEntries = scope.resolveTag(BOOTSTRAP_PAYLOAD_HANDLER_TAG);
41
- const queue = Array.isArray(rawEntries) ? [...rawEntries] : [rawEntries];
42
- const entries = [];
26
+ function createBootstrapPayloadHandlerRegistry() {
27
+ const handlers = new Map();
43
28
 
44
- while (queue.length > 0) {
45
- const entry = queue.shift();
46
- if (Array.isArray(entry)) {
47
- queue.push(...entry);
48
- continue;
49
- }
50
- const normalized = normalizeBootstrapPayloadHandler(entry);
51
- if (normalized) {
52
- entries.push(normalized);
29
+ function register(entry) {
30
+ const handler = normalizeBootstrapPayloadHandler(entry);
31
+ if (handlers.has(handler.handlerId)) {
32
+ throw new Error(`Bootstrap payload handler "${handler.handlerId}" is duplicated.`);
53
33
  }
34
+ handlers.set(handler.handlerId, handler);
35
+ return api;
54
36
  }
55
37
 
56
- return entries.sort((left, right) => {
57
- if (left.order !== right.order) {
58
- return left.order - right.order;
59
- }
60
- return left.handlerId.localeCompare(right.handlerId);
61
- });
38
+ function list() {
39
+ return Object.freeze(
40
+ [...handlers.values()].sort((left, right) => {
41
+ if (left.order !== right.order) {
42
+ return left.order - right.order;
43
+ }
44
+ return left.handlerId.localeCompare(right.handlerId);
45
+ })
46
+ );
47
+ }
48
+
49
+ const api = Object.freeze({ register, list });
50
+ return api;
62
51
  }
63
52
 
64
53
  export {
65
- BOOTSTRAP_PAYLOAD_HANDLER_TAG,
66
- registerBootstrapPayloadHandler,
67
- resolveBootstrapPayloadHandlers
54
+ createBootstrapPayloadHandlerRegistry,
55
+ normalizeBootstrapPayloadHandler
68
56
  };
@@ -1,6 +1,5 @@
1
1
  export {
2
- BOOTSTRAP_PAYLOAD_HANDLER_TAG,
3
- registerBootstrapPayloadHandler,
4
- resolveBootstrapPayloadHandlers
2
+ createBootstrapPayloadHandlerRegistry,
3
+ normalizeBootstrapPayloadHandler
5
4
  } from "./bootstrapPayloadHandlerRegistry.js";
6
5
  export { resolveBootstrapErrorStatusCode } from "./bootstrapErrorStatus.js";
@@ -1,7 +1,3 @@
1
- import {
2
- ShellWebClientProvider
3
- } from "./providers/ShellWebClientProvider.js";
4
-
5
1
  export {
6
2
  ShellWebClientProvider
7
3
  } from "./providers/ShellWebClientProvider.js";
@@ -28,11 +24,6 @@ export {
28
24
  useShellRequestRecoveryRuntime
29
25
  } from "./requestRecovery/index.js";
30
26
  export {
31
- BOOTSTRAP_PAYLOAD_HANDLER_TAG,
32
- registerBootstrapPayloadHandler,
33
- resolveBootstrapPayloadHandlers
27
+ createBootstrapPayloadHandlerRegistry,
28
+ normalizeBootstrapPayloadHandler
34
29
  } from "./bootstrap/index.js";
35
-
36
- const clientProviders = Object.freeze([ShellWebClientProvider]);
37
-
38
- export { clientProviders };
@@ -207,8 +207,7 @@ function resolveRenderablePlacement({
207
207
  });
208
208
  }
209
209
 
210
- function resolveContextContributors(app, baseContext = {}, logger) {
211
- const contributors = app.resolveTag("web-placement.context.client");
210
+ function resolveContextContributors(contributors, baseContext = {}, logger) {
212
211
  let merged = {};
213
212
 
214
213
  for (const contributor of ensureArray(contributors)) {
@@ -235,7 +234,7 @@ function resolveContextContributors(app, baseContext = {}, logger) {
235
234
  }
236
235
 
237
236
  function resolvePlacementComponent(
238
- app,
237
+ components,
239
238
  placement,
240
239
  logger,
241
240
  missingTokens,
@@ -251,7 +250,7 @@ function resolvePlacementComponent(
251
250
  return null;
252
251
  }
253
252
 
254
- if (!app.has(componentToken)) {
253
+ if (!components.has(componentToken)) {
255
254
  if (!missingTokens.has(componentToken)) {
256
255
  missingTokens.add(componentToken);
257
256
  logger.warn(
@@ -267,7 +266,7 @@ function resolvePlacementComponent(
267
266
 
268
267
  let component = null;
269
268
  try {
270
- component = app.make(componentToken);
269
+ component = components.get(componentToken);
271
270
  } catch (error) {
272
271
  if (!failedTokens.has(componentToken)) {
273
272
  failedTokens.add(componentToken);
@@ -319,9 +318,9 @@ function shouldIncludePlacement(placement, placementContext, logger) {
319
318
  }
320
319
  }
321
320
 
322
- function createWebPlacementRuntime({ app, logger = null } = {}) {
323
- if (!app || typeof app.resolveTag !== "function" || typeof app.make !== "function" || typeof app.has !== "function") {
324
- throw new Error("createWebPlacementRuntime requires app.resolveTag(), app.has(), and app.make().");
321
+ function createWebPlacementRuntime({ components, contextContributors = [], logger = null } = {}) {
322
+ if (!components || typeof components.has !== "function" || typeof components.get !== "function") {
323
+ throw new Error("createWebPlacementRuntime requires a client component registry.");
325
324
  }
326
325
 
327
326
  const runtimeLogger = createRuntimeLogger(logger);
@@ -438,9 +437,8 @@ function createWebPlacementRuntime({ app, logger = null } = {}) {
438
437
  const baseContext = isRecord(context) ? { ...context } : {};
439
438
  const contextFromRuntime = isRecord(sharedContext) ? sharedContext : {};
440
439
  const contextFromContributors = resolveContextContributors(
441
- app,
440
+ contextContributors,
442
441
  {
443
- app,
444
442
  surface: normalizedSurface,
445
443
  target: normalizedTarget,
446
444
  layoutClass: normalizedLayoutClass,
@@ -455,7 +453,6 @@ function createWebPlacementRuntime({ app, logger = null } = {}) {
455
453
  ...contextFromContributors,
456
454
  ...contextFromRuntime,
457
455
  ...baseContext,
458
- app,
459
456
  surface: normalizedSurface,
460
457
  target: normalizedTarget,
461
458
  layoutClass: normalizedLayoutClass
@@ -503,7 +500,7 @@ function createWebPlacementRuntime({ app, logger = null } = {}) {
503
500
  }
504
501
 
505
502
  const component = resolvePlacementComponent(
506
- app,
503
+ components,
507
504
  renderablePlacement,
508
505
  runtimeLogger,
509
506
  missingTokens,