@jskit-ai/shell-web 0.1.4

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 (41) hide show
  1. package/package.descriptor.mjs +165 -0
  2. package/package.json +23 -0
  3. package/src/client/components/ShellErrorHost.vue +208 -0
  4. package/src/client/components/ShellLayout.vue +191 -0
  5. package/src/client/components/ShellOutlet.vue +95 -0
  6. package/src/client/components/useShellLayout.js +93 -0
  7. package/src/client/error/index.js +2 -0
  8. package/src/client/error/inject.js +142 -0
  9. package/src/client/error/normalize.js +75 -0
  10. package/src/client/error/policy.js +50 -0
  11. package/src/client/error/presenters.js +89 -0
  12. package/src/client/error/runtime.js +418 -0
  13. package/src/client/error/store.js +176 -0
  14. package/src/client/error/tokens.js +14 -0
  15. package/src/client/index.js +17 -0
  16. package/src/client/navigation/linkResolver.js +117 -0
  17. package/src/client/placement/debug.js +52 -0
  18. package/src/client/placement/index.js +26 -0
  19. package/src/client/placement/inject.js +104 -0
  20. package/src/client/placement/pathname.js +14 -0
  21. package/src/client/placement/registry.js +41 -0
  22. package/src/client/placement/runtime.js +435 -0
  23. package/src/client/placement/surfaceContext.js +290 -0
  24. package/src/client/placement/tokens.js +29 -0
  25. package/src/client/placement/validators.js +210 -0
  26. package/src/client/providers/ShellWebClientProvider.js +352 -0
  27. package/templates/src/App.vue +11 -0
  28. package/templates/src/components/ShellLayout.vue +247 -0
  29. package/templates/src/error.js +13 -0
  30. package/templates/src/pages/console/index.vue +24 -0
  31. package/templates/src/pages/console.vue +20 -0
  32. package/templates/src/pages/home/index.vue +54 -0
  33. package/templates/src/pages/home.vue +20 -0
  34. package/templates/src/placement.js +12 -0
  35. package/test/errorRuntime.test.js +191 -0
  36. package/test/errorStore.test.js +26 -0
  37. package/test/linkResolver.test.js +112 -0
  38. package/test/placementRegistry.test.js +45 -0
  39. package/test/placementRuntime.test.js +374 -0
  40. package/test/provider.test.js +163 -0
  41. package/test/surfaceContext.test.js +184 -0
@@ -0,0 +1,29 @@
1
+ const WEB_PLACEMENT_RUNTIME_CLIENT_TOKEN = "runtime.web-placement.client";
2
+ const WEB_PLACEMENT_CONTEXT_CONTRIBUTOR_TAG = "web-placement.context.client";
3
+ const WEB_PLACEMENT_RUNTIME_INJECTION_KEY = Symbol.for("jskit.shell-web.runtime.web-placement.client");
4
+
5
+ const WEB_PLACEMENT_SURFACE_ANY = "*";
6
+ const DEFAULT_WEB_PLACEMENT_ORDER = 1000;
7
+
8
+ const WEB_PLACEMENT_POSITIONS = Object.freeze([
9
+ "top",
10
+ "top-left",
11
+ "left",
12
+ "bottom-left",
13
+ "bottom",
14
+ "bottom-right",
15
+ "right",
16
+ "top-right",
17
+ "center",
18
+ "primary-menu",
19
+ "secondary-menu"
20
+ ]);
21
+
22
+ export {
23
+ WEB_PLACEMENT_RUNTIME_CLIENT_TOKEN,
24
+ WEB_PLACEMENT_CONTEXT_CONTRIBUTOR_TAG,
25
+ WEB_PLACEMENT_RUNTIME_INJECTION_KEY,
26
+ WEB_PLACEMENT_SURFACE_ANY,
27
+ DEFAULT_WEB_PLACEMENT_ORDER,
28
+ WEB_PLACEMENT_POSITIONS
29
+ };
@@ -0,0 +1,210 @@
1
+ import {
2
+ DEFAULT_WEB_PLACEMENT_ORDER,
3
+ WEB_PLACEMENT_SURFACE_ANY
4
+ } from "./tokens.js";
5
+ import { isRecord, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
6
+
7
+ function isRenderableComponent(value) {
8
+ if (typeof value === "function") {
9
+ return true;
10
+ }
11
+ return isRecord(value);
12
+ }
13
+
14
+ function normalizeSurface(value) {
15
+ const normalized = normalizeText(value).toLowerCase();
16
+ if (!normalized) {
17
+ return WEB_PLACEMENT_SURFACE_ANY;
18
+ }
19
+ if (normalized === WEB_PLACEMENT_SURFACE_ANY) {
20
+ return WEB_PLACEMENT_SURFACE_ANY;
21
+ }
22
+ return normalized;
23
+ }
24
+
25
+ function isValidSurfaceIdToken(value = "") {
26
+ return /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/.test(value);
27
+ }
28
+
29
+ function isValidPlacementHostOrPositionToken(value = "") {
30
+ return /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/.test(value);
31
+ }
32
+
33
+ function normalizePlacementSurface(value, { strict = false, source = "placement" } = {}) {
34
+ const normalized = normalizeText(value).toLowerCase();
35
+ if (!normalized) {
36
+ if (strict) {
37
+ throw new TypeError(`${source} requires surface id or "*".`);
38
+ }
39
+ return "";
40
+ }
41
+
42
+ if (normalized === WEB_PLACEMENT_SURFACE_ANY) {
43
+ return WEB_PLACEMENT_SURFACE_ANY;
44
+ }
45
+
46
+ if (isValidSurfaceIdToken(normalized)) {
47
+ return normalized;
48
+ }
49
+
50
+ if (strict) {
51
+ throw new TypeError(`${source} surface "${normalized}" is invalid.`);
52
+ }
53
+ return "";
54
+ }
55
+
56
+ function toInteger(value, fallback = DEFAULT_WEB_PLACEMENT_ORDER) {
57
+ const numeric = Number(value);
58
+ if (!Number.isFinite(numeric)) {
59
+ return fallback;
60
+ }
61
+ return Math.trunc(numeric);
62
+ }
63
+
64
+ function normalizePlacementHost(value, { strict = false, source = "placement" } = {}) {
65
+ const normalized = normalizeText(value).toLowerCase();
66
+ if (!normalized) {
67
+ if (strict) {
68
+ throw new TypeError(`${source} requires host.`);
69
+ }
70
+ return "";
71
+ }
72
+
73
+ if (!isValidPlacementHostOrPositionToken(normalized)) {
74
+ if (strict) {
75
+ throw new TypeError(`${source} host "${normalized}" is invalid.`);
76
+ }
77
+ return "";
78
+ }
79
+ return normalized;
80
+ }
81
+
82
+ function normalizePlacementPosition(value, { strict = false, source = "placement" } = {}) {
83
+ const normalized = normalizeText(value).toLowerCase();
84
+ if (!normalized) {
85
+ if (strict) {
86
+ throw new TypeError(`${source} requires position.`);
87
+ }
88
+ return "";
89
+ }
90
+
91
+ if (!isValidPlacementHostOrPositionToken(normalized)) {
92
+ if (strict) {
93
+ throw new TypeError(`${source} position "${normalized}" is invalid.`);
94
+ }
95
+ return "";
96
+ }
97
+ return normalized;
98
+ }
99
+
100
+ function normalizePlacementSurfaces(value, { strict = false, source = "placement" } = {}) {
101
+ const candidates = Array.isArray(value) ? value : value === undefined || value === null ? [] : [value];
102
+ if (candidates.length < 1) {
103
+ return Object.freeze([WEB_PLACEMENT_SURFACE_ANY]);
104
+ }
105
+
106
+ const normalized = [];
107
+ const seen = new Set();
108
+ for (const candidate of candidates) {
109
+ const surface = normalizePlacementSurface(candidate, {
110
+ strict,
111
+ source
112
+ });
113
+ if (!surface || seen.has(surface)) {
114
+ continue;
115
+ }
116
+ if (surface === WEB_PLACEMENT_SURFACE_ANY) {
117
+ return Object.freeze([WEB_PLACEMENT_SURFACE_ANY]);
118
+ }
119
+ seen.add(surface);
120
+ normalized.push(surface);
121
+ }
122
+
123
+ if (normalized.length < 1) {
124
+ if (strict) {
125
+ throw new TypeError(`${source} requires at least one valid surface id.`);
126
+ }
127
+ return Object.freeze([WEB_PLACEMENT_SURFACE_ANY]);
128
+ }
129
+
130
+ return Object.freeze(normalized);
131
+ }
132
+
133
+ function normalizePlacementDefinition(value, { strict = false, source = "placement" } = {}) {
134
+ if (!isRecord(value)) {
135
+ if (strict) {
136
+ throw new TypeError(`${source} must be an object.`);
137
+ }
138
+ return null;
139
+ }
140
+
141
+ const id = normalizeText(value.id);
142
+ if (!id) {
143
+ if (strict) {
144
+ throw new TypeError(`${source} requires id.`);
145
+ }
146
+ return null;
147
+ }
148
+
149
+ const host = normalizePlacementHost(value.host, {
150
+ strict,
151
+ source: `${source} "${id}"`
152
+ });
153
+ if (!host) {
154
+ return null;
155
+ }
156
+
157
+ const position = normalizePlacementPosition(value.position, {
158
+ strict,
159
+ source: `${source} "${id}"`
160
+ });
161
+ if (!position) {
162
+ return null;
163
+ }
164
+
165
+ const componentToken = normalizeText(value.componentToken);
166
+ if (!componentToken) {
167
+ if (strict) {
168
+ throw new TypeError(`${source} "${id}" requires componentToken.`);
169
+ }
170
+ return null;
171
+ }
172
+
173
+ const props = isRecord(value.props) ? { ...value.props } : {};
174
+ const when = typeof value.when === "function" ? value.when : null;
175
+ const surfaces = normalizePlacementSurfaces(value.surfaces, {
176
+ strict,
177
+ source: `${source} "${id}"`
178
+ });
179
+
180
+ return Object.freeze({
181
+ id,
182
+ host,
183
+ position,
184
+ surfaces,
185
+ order: toInteger(value.order, DEFAULT_WEB_PLACEMENT_ORDER),
186
+ componentToken,
187
+ props,
188
+ when,
189
+ enabled: value.enabled !== false
190
+ });
191
+ }
192
+
193
+ function definePlacement(value = {}) {
194
+ return normalizePlacementDefinition(value, {
195
+ strict: true,
196
+ source: "placement contribution"
197
+ });
198
+ }
199
+
200
+ export {
201
+ isRecord,
202
+ isRenderableComponent,
203
+ normalizeSurface,
204
+ normalizePlacementSurface,
205
+ normalizePlacementHost,
206
+ normalizePlacementPosition,
207
+ normalizePlacementSurfaces,
208
+ normalizePlacementDefinition,
209
+ definePlacement
210
+ };
@@ -0,0 +1,352 @@
1
+ import {
2
+ CLIENT_MODULE_ROUTER_TOKEN,
3
+ CLIENT_MODULE_SURFACE_RUNTIME_TOKEN,
4
+ CLIENT_MODULE_VUE_APP_TOKEN
5
+ } from "@jskit-ai/kernel/client/moduleBootstrap";
6
+ import { getClientAppConfig } from "@jskit-ai/kernel/client";
7
+ import {
8
+ isRecord,
9
+ shouldRetryTransientQueryFailure,
10
+ transientQueryRetryDelay
11
+ } from "@jskit-ai/kernel/shared/support";
12
+ import { QueryClient, VueQueryPlugin } from "@tanstack/vue-query";
13
+ import {
14
+ createDefaultErrorPolicy
15
+ } from "../error/policy.js";
16
+ import {
17
+ createDefaultMaterialErrorPresenters,
18
+ createMaterialBannerPresenter,
19
+ createMaterialDialogPresenter,
20
+ createMaterialSnackbarPresenter,
21
+ MODULE_DEFAULT_PRESENTER_ID
22
+ } from "../error/presenters.js";
23
+ import {
24
+ createErrorRuntime
25
+ } from "../error/runtime.js";
26
+ import {
27
+ createErrorPresentationStore
28
+ } from "../error/store.js";
29
+ import {
30
+ SHELL_WEB_ERROR_PRESENTATION_STORE_CLIENT_TOKEN,
31
+ SHELL_WEB_ERROR_PRESENTATION_STORE_INJECTION_KEY,
32
+ SHELL_WEB_ERROR_RUNTIME_CLIENT_TOKEN,
33
+ SHELL_WEB_ERROR_RUNTIME_INJECTION_KEY
34
+ } from "../error/tokens.js";
35
+ import {
36
+ WEB_PLACEMENT_RUNTIME_CLIENT_TOKEN,
37
+ WEB_PLACEMENT_RUNTIME_INJECTION_KEY
38
+ } from "../placement/tokens.js";
39
+ import { createWebPlacementRuntime } from "../placement/runtime.js";
40
+ import { buildSurfaceConfigContext } from "../placement/surfaceContext.js";
41
+
42
+ // Keep this constant for diagnostics, but keep import() below as a literal string so Vite can statically analyze it.
43
+ const APP_PLACEMENT_MODULE_SPECIFIER = "/src/placement.js";
44
+ const APP_ERROR_MODULE_SPECIFIER = "/src/error.js";
45
+ const SURFACE_CONTEXT_SOURCE = "shell-web.surface-config";
46
+ const SHELL_WEB_QUERY_CLIENT_TOKEN = "shell.web.query-client";
47
+ const VUE_ERROR_SOURCE = "shell-web.vue.error-handler";
48
+ const ROUTER_ERROR_SOURCE = "shell-web.router.on-error";
49
+
50
+ function createShellWebQueryClient() {
51
+ return new QueryClient({
52
+ defaultOptions: {
53
+ queries: {
54
+ refetchOnWindowFocus: false,
55
+ refetchOnReconnect: true,
56
+ retry: shouldRetryTransientQueryFailure,
57
+ retryDelay: transientQueryRetryDelay
58
+ }
59
+ }
60
+ });
61
+ }
62
+
63
+ function createProviderLogger(app) {
64
+ return Object.freeze({
65
+ warn: (...args) => {
66
+ if (isRecord(app) && typeof app.warn === "function") {
67
+ app.warn(...args);
68
+ return;
69
+ }
70
+ console.warn(...args);
71
+ },
72
+ error: (...args) => {
73
+ if (isRecord(app) && typeof app.error === "function") {
74
+ app.error(...args);
75
+ return;
76
+ }
77
+ console.error(...args);
78
+ }
79
+ });
80
+ }
81
+
82
+ function isMissingDynamicModule(error, moduleSpecifier) {
83
+ const message = String(error?.message || error || "");
84
+ return (
85
+ message.includes(moduleSpecifier) &&
86
+ (message.includes("Cannot find module") ||
87
+ message.includes("Failed to fetch dynamically imported module") ||
88
+ message.includes("ERR_MODULE_NOT_FOUND"))
89
+ );
90
+ }
91
+
92
+ async function loadAppPlacementDefinitions(logger) {
93
+ try {
94
+ const moduleNamespace = await import("/src/placement.js");
95
+ const exported = moduleNamespace?.default;
96
+ const resolved = typeof exported === "function" ? exported() : exported;
97
+ if (Array.isArray(resolved)) {
98
+ return resolved;
99
+ }
100
+
101
+ logger.warn(
102
+ {
103
+ module: APP_PLACEMENT_MODULE_SPECIFIER,
104
+ exportedType: typeof exported
105
+ },
106
+ "App placement module default export did not resolve to an array; using empty list."
107
+ );
108
+ } catch (error) {
109
+ if (isMissingDynamicModule(error, APP_PLACEMENT_MODULE_SPECIFIER)) {
110
+ return [];
111
+ }
112
+
113
+ logger.warn(
114
+ {
115
+ module: APP_PLACEMENT_MODULE_SPECIFIER,
116
+ error: String(error?.message || error || "unknown error")
117
+ },
118
+ "Failed to load app placement module; using empty list."
119
+ );
120
+ }
121
+
122
+ return [];
123
+ }
124
+
125
+ function createErrorConfigToolkit(errorRuntime) {
126
+ return Object.freeze({
127
+ createDefaultErrorPolicy,
128
+ moduleDefaultPresenterId: MODULE_DEFAULT_PRESENTER_ID,
129
+ presenterFactories: Object.freeze({
130
+ createMaterialSnackbarPresenter,
131
+ createMaterialBannerPresenter,
132
+ createMaterialDialogPresenter
133
+ }),
134
+ runtime: errorRuntime
135
+ });
136
+ }
137
+
138
+ async function loadAppErrorConfig(logger, errorRuntime) {
139
+ try {
140
+ const moduleNamespace = await import("/src/error.js");
141
+ const exported = moduleNamespace?.default;
142
+ const toolkit = createErrorConfigToolkit(errorRuntime);
143
+ const resolved = typeof exported === "function" ? await exported(toolkit) : exported;
144
+
145
+ if (isRecord(resolved)) {
146
+ return resolved;
147
+ }
148
+
149
+ logger.warn(
150
+ {
151
+ module: APP_ERROR_MODULE_SPECIFIER,
152
+ exportedType: typeof exported
153
+ },
154
+ "App error module default export did not resolve to an object; using shell-web defaults."
155
+ );
156
+ } catch (error) {
157
+ if (isMissingDynamicModule(error, APP_ERROR_MODULE_SPECIFIER)) {
158
+ return {};
159
+ }
160
+
161
+ logger.warn(
162
+ {
163
+ module: APP_ERROR_MODULE_SPECIFIER,
164
+ error: String(error?.message || error || "unknown error")
165
+ },
166
+ "Failed to load app error module; using shell-web defaults."
167
+ );
168
+ }
169
+
170
+ return {};
171
+ }
172
+
173
+ function applyAppErrorConfig(errorRuntime, errorConfig = {}) {
174
+ const source = isRecord(errorConfig) ? errorConfig : {};
175
+ const configPayload = {};
176
+
177
+ if (Array.isArray(source.presenters) && source.presenters.length > 0) {
178
+ configPayload.presenters = source.presenters;
179
+ }
180
+
181
+ if (Object.prototype.hasOwnProperty.call(source, "policy")) {
182
+ configPayload.policy = source.policy;
183
+ }
184
+
185
+ if (Object.prototype.hasOwnProperty.call(source, "defaultPresenterId")) {
186
+ configPayload.defaultPresenterId = source.defaultPresenterId;
187
+ }
188
+
189
+ if (Object.keys(configPayload).length > 0) {
190
+ errorRuntime.configure(configPayload);
191
+ return;
192
+ }
193
+
194
+ errorRuntime.assertBootReady();
195
+ }
196
+
197
+ function installVueErrorBridge(vueApp, errorRuntime, logger) {
198
+ if (!vueApp || !isRecord(vueApp.config)) {
199
+ return;
200
+ }
201
+
202
+ const previousHandler = typeof vueApp.config.errorHandler === "function" ? vueApp.config.errorHandler : null;
203
+
204
+ vueApp.config.errorHandler = (error, instance, info) => {
205
+ try {
206
+ errorRuntime.report({
207
+ source: VUE_ERROR_SOURCE,
208
+ message: String(error?.message || "Unexpected UI error."),
209
+ cause: error,
210
+ severity: "error",
211
+ channel: "dialog",
212
+ details: {
213
+ info: String(info || "")
214
+ }
215
+ });
216
+ } catch (reportError) {
217
+ logger.error(
218
+ {
219
+ source: VUE_ERROR_SOURCE,
220
+ error: String(reportError?.message || reportError || "unknown error")
221
+ },
222
+ "Shell web error runtime failed to report a Vue error."
223
+ );
224
+ }
225
+
226
+ if (previousHandler) {
227
+ previousHandler(error, instance, info);
228
+ }
229
+ };
230
+ }
231
+
232
+ function installRouterErrorBridge(app, errorRuntime, logger) {
233
+ if (!app.has(CLIENT_MODULE_ROUTER_TOKEN)) {
234
+ return;
235
+ }
236
+
237
+ const router = app.make(CLIENT_MODULE_ROUTER_TOKEN);
238
+ if (!router || typeof router.onError !== "function") {
239
+ return;
240
+ }
241
+
242
+ router.onError((error) => {
243
+ try {
244
+ errorRuntime.report({
245
+ source: ROUTER_ERROR_SOURCE,
246
+ message: String(error?.message || "Navigation failed."),
247
+ cause: error,
248
+ severity: "error",
249
+ channel: "banner",
250
+ dedupeKey: String(error?.message || "navigation-failed"),
251
+ dedupeWindowMs: 2000
252
+ });
253
+ } catch (reportError) {
254
+ logger.error(
255
+ {
256
+ source: ROUTER_ERROR_SOURCE,
257
+ error: String(reportError?.message || reportError || "unknown error")
258
+ },
259
+ "Shell web error runtime failed to report a router error."
260
+ );
261
+ }
262
+ });
263
+ }
264
+
265
+ class ShellWebClientProvider {
266
+ static id = "shell.web.client";
267
+
268
+ register(app) {
269
+ if (!app || typeof app.singleton !== "function") {
270
+ throw new Error("ShellWebClientProvider requires application singleton().");
271
+ }
272
+
273
+ const logger = createProviderLogger(app);
274
+ app.singleton(WEB_PLACEMENT_RUNTIME_CLIENT_TOKEN, () => createWebPlacementRuntime({ app, logger }));
275
+ app.singleton(SHELL_WEB_QUERY_CLIENT_TOKEN, () => createShellWebQueryClient());
276
+ app.singleton(SHELL_WEB_ERROR_PRESENTATION_STORE_CLIENT_TOKEN, () => createErrorPresentationStore());
277
+ app.singleton(SHELL_WEB_ERROR_RUNTIME_CLIENT_TOKEN, (scope) =>
278
+ createErrorRuntime({
279
+ presenters: createDefaultMaterialErrorPresenters({
280
+ store: scope.make(SHELL_WEB_ERROR_PRESENTATION_STORE_CLIENT_TOKEN)
281
+ }),
282
+ policy: createDefaultErrorPolicy(),
283
+ moduleDefaultPresenterId: MODULE_DEFAULT_PRESENTER_ID,
284
+ logger
285
+ })
286
+ );
287
+ }
288
+
289
+ async boot(app) {
290
+ if (!app || typeof app.make !== "function" || typeof app.has !== "function") {
291
+ throw new Error("ShellWebClientProvider requires application make()/has().");
292
+ }
293
+
294
+ const logger = createProviderLogger(app);
295
+ const placementRuntime = app.make(WEB_PLACEMENT_RUNTIME_CLIENT_TOKEN);
296
+ if (placementRuntime && typeof placementRuntime.replacePlacements === "function") {
297
+ const placements = await loadAppPlacementDefinitions(logger);
298
+ placementRuntime.replacePlacements(placements, { source: APP_PLACEMENT_MODULE_SPECIFIER });
299
+ const appConfig = getClientAppConfig();
300
+ const surfaceRuntime = app.has(CLIENT_MODULE_SURFACE_RUNTIME_TOKEN)
301
+ ? app.make(CLIENT_MODULE_SURFACE_RUNTIME_TOKEN)
302
+ : null;
303
+ const surfaceConfig = buildSurfaceConfigContext(surfaceRuntime, {
304
+ tenancyMode: appConfig?.tenancyMode
305
+ });
306
+ const surfaceAccessPolicies =
307
+ appConfig?.surfaceAccessPolicies && typeof appConfig.surfaceAccessPolicies === "object"
308
+ ? appConfig.surfaceAccessPolicies
309
+ : {};
310
+ placementRuntime.setContext(
311
+ {
312
+ surfaceConfig,
313
+ surfaceAccessPolicies
314
+ },
315
+ {
316
+ source: SURFACE_CONTEXT_SOURCE
317
+ }
318
+ );
319
+ }
320
+
321
+ const errorRuntime = app.make(SHELL_WEB_ERROR_RUNTIME_CLIENT_TOKEN);
322
+ const errorConfig = await loadAppErrorConfig(logger, errorRuntime);
323
+ applyAppErrorConfig(errorRuntime, errorConfig);
324
+
325
+ if (!app.has(CLIENT_MODULE_VUE_APP_TOKEN)) {
326
+ return;
327
+ }
328
+
329
+ const vueApp = app.make(CLIENT_MODULE_VUE_APP_TOKEN);
330
+ if (!vueApp || typeof vueApp.provide !== "function" || typeof vueApp.use !== "function") {
331
+ return;
332
+ }
333
+
334
+ vueApp.use(VueQueryPlugin, {
335
+ queryClient: app.make(SHELL_WEB_QUERY_CLIENT_TOKEN)
336
+ });
337
+ vueApp.provide(WEB_PLACEMENT_RUNTIME_INJECTION_KEY, placementRuntime);
338
+ vueApp.provide(SHELL_WEB_ERROR_RUNTIME_INJECTION_KEY, errorRuntime);
339
+ vueApp.provide(
340
+ SHELL_WEB_ERROR_PRESENTATION_STORE_INJECTION_KEY,
341
+ app.make(SHELL_WEB_ERROR_PRESENTATION_STORE_CLIENT_TOKEN)
342
+ );
343
+
344
+ installVueErrorBridge(vueApp, errorRuntime, logger);
345
+ installRouterErrorBridge(app, errorRuntime, logger);
346
+ }
347
+ }
348
+
349
+ export {
350
+ ShellWebClientProvider,
351
+ SHELL_WEB_QUERY_CLIENT_TOKEN
352
+ };
@@ -0,0 +1,11 @@
1
+ <script setup>
2
+ import { RouterView } from "vue-router";
3
+ import ShellErrorHost from "@jskit-ai/shell-web/client/components/ShellErrorHost";
4
+ </script>
5
+
6
+ <template>
7
+ <v-app>
8
+ <RouterView />
9
+ <ShellErrorHost />
10
+ </v-app>
11
+ </template>