@jskit-ai/shell-web 0.1.94 → 0.1.96
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/package.descriptor.mjs +9 -2
- package/package.json +2 -2
- package/src/client/components/ShellMenuLinkItem.vue +7 -0
- package/src/client/components/ShellSurfaceAwareMenuLinkItem.vue +7 -0
- package/src/client/requestRecovery/runtime.js +51 -6
- package/test/linkItemScaffoldContract.test.js +4 -0
- package/test/provider.test.js +58 -0
- package/test/settingsPlacementContract.test.js +4 -0
package/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
packageVersion: 1,
|
|
3
3
|
packageId: "@jskit-ai/shell-web",
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.96",
|
|
5
5
|
kind: "runtime",
|
|
6
6
|
description: "Web shell layout runtime with outlet-based placement contributions.",
|
|
7
7
|
dependsOn: [],
|
|
@@ -28,6 +28,13 @@ export default Object.freeze({
|
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
30
|
metadata: {
|
|
31
|
+
client: {
|
|
32
|
+
optimizeDeps: {
|
|
33
|
+
exclude: [
|
|
34
|
+
"@jskit-ai/shell-web/client"
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
},
|
|
31
38
|
apiSummary: {
|
|
32
39
|
surfaces: [
|
|
33
40
|
{
|
|
@@ -300,7 +307,7 @@ export default Object.freeze({
|
|
|
300
307
|
dependencies: {
|
|
301
308
|
runtime: {
|
|
302
309
|
"@mdi/js": "^7.4.47",
|
|
303
|
-
"@jskit-ai/kernel": "0.1.
|
|
310
|
+
"@jskit-ai/kernel": "0.1.97"
|
|
304
311
|
},
|
|
305
312
|
dev: {}
|
|
306
313
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/shell-web",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.96",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@mdi/js": "^7.4.47",
|
|
31
|
-
"@jskit-ai/kernel": "0.1.
|
|
31
|
+
"@jskit-ai/kernel": "0.1.97"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"pinia": "^3.0.4",
|
|
@@ -61,6 +61,7 @@ const resolvedIcon = computed(() =>
|
|
|
61
61
|
<template>
|
|
62
62
|
<v-list-item
|
|
63
63
|
v-if="resolvedTarget.href"
|
|
64
|
+
class="shell-menu-link-item"
|
|
64
65
|
:title="props.label"
|
|
65
66
|
:to="resolvedTarget.sameOrigin ? resolvedTarget.href : undefined"
|
|
66
67
|
:href="resolvedTarget.sameOrigin ? undefined : resolvedTarget.href"
|
|
@@ -69,3 +70,9 @@ const resolvedIcon = computed(() =>
|
|
|
69
70
|
:exact="props.exact"
|
|
70
71
|
/>
|
|
71
72
|
</template>
|
|
73
|
+
|
|
74
|
+
<style scoped>
|
|
75
|
+
.shell-menu-link-item {
|
|
76
|
+
min-height: 48px;
|
|
77
|
+
}
|
|
78
|
+
</style>
|
|
@@ -106,6 +106,7 @@ const resolvedIcon = computed(() =>
|
|
|
106
106
|
<template>
|
|
107
107
|
<v-list-item
|
|
108
108
|
v-if="resolvedTarget.href"
|
|
109
|
+
class="shell-menu-link-item"
|
|
109
110
|
:title="props.label"
|
|
110
111
|
:to="resolvedTarget.sameOrigin ? resolvedTarget.href : undefined"
|
|
111
112
|
:href="resolvedTarget.sameOrigin ? undefined : resolvedTarget.href"
|
|
@@ -114,3 +115,9 @@ const resolvedIcon = computed(() =>
|
|
|
114
115
|
:exact="props.exact"
|
|
115
116
|
/>
|
|
116
117
|
</template>
|
|
118
|
+
|
|
119
|
+
<style scoped>
|
|
120
|
+
.shell-menu-link-item {
|
|
121
|
+
min-height: 48px;
|
|
122
|
+
}
|
|
123
|
+
</style>
|
|
@@ -277,29 +277,54 @@ function installRecoverableQueryObserver({
|
|
|
277
277
|
|
|
278
278
|
const reportedErrorUpdateByQueryHash = new Map();
|
|
279
279
|
|
|
280
|
+
function dismissReportedQueryRecovery(queryHash = "") {
|
|
281
|
+
const reported = reportedErrorUpdateByQueryHash.get(queryHash);
|
|
282
|
+
if (!reported) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
reportedErrorUpdateByQueryHash.delete(queryHash);
|
|
287
|
+
if (reported.presentationId && typeof runtime.dismiss === "function") {
|
|
288
|
+
runtime.dismiss(reported.presentationId, {
|
|
289
|
+
presenterId: reported.presenterId
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function dismissReportedQueryRecoveries() {
|
|
295
|
+
for (const queryHash of [...reportedErrorUpdateByQueryHash.keys()]) {
|
|
296
|
+
dismissReportedQueryRecovery(queryHash);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
280
300
|
function inspectQuery(query = null) {
|
|
301
|
+
const queryHash = resolveQueryHash(query);
|
|
281
302
|
if (
|
|
282
303
|
!query ||
|
|
283
304
|
isRequestRecoveryDisabled(query) ||
|
|
284
305
|
!isSafeQueryRecoveryMethod(query) ||
|
|
285
306
|
!isActiveQuery(query)
|
|
286
307
|
) {
|
|
308
|
+
if (queryHash) {
|
|
309
|
+
dismissReportedQueryRecovery(queryHash);
|
|
310
|
+
}
|
|
287
311
|
return;
|
|
288
312
|
}
|
|
289
313
|
|
|
290
314
|
const error = recoverableQueryError(query);
|
|
291
|
-
const queryHash = resolveQueryHash(query);
|
|
292
315
|
if (!error || !queryHash) {
|
|
293
|
-
|
|
316
|
+
if (queryHash) {
|
|
317
|
+
dismissReportedQueryRecovery(queryHash);
|
|
318
|
+
}
|
|
294
319
|
return;
|
|
295
320
|
}
|
|
296
321
|
|
|
297
322
|
const errorUpdateCount = Number(query?.state?.errorUpdateCount || 0);
|
|
298
323
|
const reportKey = `${errorUpdateCount}:${errorMessage(error)}`;
|
|
299
|
-
|
|
324
|
+
const reported = reportedErrorUpdateByQueryHash.get(queryHash);
|
|
325
|
+
if (reported?.reportKey === reportKey) {
|
|
300
326
|
return;
|
|
301
327
|
}
|
|
302
|
-
reportedErrorUpdateByQueryHash.set(queryHash, reportKey);
|
|
303
328
|
|
|
304
329
|
const source = resolveQueryRecoverySource(query);
|
|
305
330
|
const dedupeKey = resolveQueryRecoveryDedupeKey(
|
|
@@ -307,7 +332,7 @@ function installRecoverableQueryObserver({
|
|
|
307
332
|
`shell-web.request-recovery.query.${queryHash}.${errorUpdateCount}`
|
|
308
333
|
);
|
|
309
334
|
const dedupeWindowMs = resolveQueryRecoveryDedupeWindowMs(query);
|
|
310
|
-
runtime.report(error, {
|
|
335
|
+
const result = runtime.report(error, {
|
|
311
336
|
label: resolveQueryRecoveryLabel(query),
|
|
312
337
|
retry: createQueryRetry(queryClient, query),
|
|
313
338
|
source,
|
|
@@ -315,6 +340,17 @@ function installRecoverableQueryObserver({
|
|
|
315
340
|
dedupeKey,
|
|
316
341
|
...(dedupeWindowMs !== null ? { dedupeWindowMs } : {})
|
|
317
342
|
});
|
|
343
|
+
|
|
344
|
+
const presentationId = normalizeText(result?.presentationId || reported?.presentationId);
|
|
345
|
+
const presenterId = normalizeText(result?.decision?.presenterId || reported?.presenterId);
|
|
346
|
+
if (reported?.presentationId && reported.presentationId !== presentationId) {
|
|
347
|
+
dismissReportedQueryRecovery(queryHash);
|
|
348
|
+
}
|
|
349
|
+
reportedErrorUpdateByQueryHash.set(queryHash, {
|
|
350
|
+
reportKey,
|
|
351
|
+
presentationId,
|
|
352
|
+
presenterId
|
|
353
|
+
});
|
|
318
354
|
}
|
|
319
355
|
|
|
320
356
|
try {
|
|
@@ -347,7 +383,7 @@ function installRecoverableQueryObserver({
|
|
|
347
383
|
|
|
348
384
|
return Object.freeze({
|
|
349
385
|
dispose() {
|
|
350
|
-
|
|
386
|
+
dismissReportedQueryRecoveries();
|
|
351
387
|
if (typeof unsubscribe === "function") {
|
|
352
388
|
unsubscribe();
|
|
353
389
|
}
|
|
@@ -427,6 +463,14 @@ function createShellRequestRecoveryRuntime({
|
|
|
427
463
|
};
|
|
428
464
|
}
|
|
429
465
|
|
|
466
|
+
function dismiss(presentationId = "", options = {}) {
|
|
467
|
+
const runtime = errorRuntime();
|
|
468
|
+
if (!runtime || typeof runtime.dismiss !== "function") {
|
|
469
|
+
return 0;
|
|
470
|
+
}
|
|
471
|
+
return runtime.dismiss(presentationId, options);
|
|
472
|
+
}
|
|
473
|
+
|
|
430
474
|
function report(error = null, options = {}) {
|
|
431
475
|
if (!isRecoverableRequestError(error) && options.force !== true) {
|
|
432
476
|
return null;
|
|
@@ -496,6 +540,7 @@ function createShellRequestRecoveryRuntime({
|
|
|
496
540
|
}
|
|
497
541
|
|
|
498
542
|
const api = Object.freeze({
|
|
543
|
+
dismiss,
|
|
499
544
|
dispose,
|
|
500
545
|
install,
|
|
501
546
|
isRecoverableRequestError,
|
|
@@ -151,8 +151,12 @@ test("shell-web generic link items support the expected shared route and icon be
|
|
|
151
151
|
|
|
152
152
|
assert.match(shellMenuSource, /exact:\s*\{/);
|
|
153
153
|
assert.match(shellMenuSource, /:exact="props\.exact"/);
|
|
154
|
+
assert.match(shellMenuSource, /class="shell-menu-link-item"/);
|
|
155
|
+
assert.match(shellMenuSource, /min-height:\s*48px/);
|
|
154
156
|
assert.match(shellSurfaceAwareSource, /exact:\s*\{/);
|
|
155
157
|
assert.match(shellSurfaceAwareSource, /:exact="props\.exact"/);
|
|
158
|
+
assert.match(shellSurfaceAwareSource, /class="shell-menu-link-item"/);
|
|
159
|
+
assert.match(shellSurfaceAwareSource, /min-height:\s*48px/);
|
|
156
160
|
assert.match(shellTabSource, /icon:\s*\{/);
|
|
157
161
|
assert.match(shellTabSource, /resolveMenuLinkIcon/);
|
|
158
162
|
assert.match(shellTabSource, /<v-btn/);
|
package/test/provider.test.js
CHANGED
|
@@ -428,6 +428,64 @@ test("shell request recovery reports active query transport failures with retry
|
|
|
428
428
|
});
|
|
429
429
|
});
|
|
430
430
|
|
|
431
|
+
test("shell request recovery dismisses query presentations after recovery", async () => {
|
|
432
|
+
await withFetchStub({ surfaceAccess: {} }, async () => {
|
|
433
|
+
const queryCache = createQueryCacheDouble();
|
|
434
|
+
const queryClient = {
|
|
435
|
+
getQueryCache() {
|
|
436
|
+
return queryCache;
|
|
437
|
+
},
|
|
438
|
+
async refetchQueries() {}
|
|
439
|
+
};
|
|
440
|
+
const app = createAppDouble({ queryClient });
|
|
441
|
+
const provider = new ShellWebClientProvider();
|
|
442
|
+
provider.register(app);
|
|
443
|
+
await provider.boot(app);
|
|
444
|
+
|
|
445
|
+
const query = {
|
|
446
|
+
queryHash: "[\"project-access\"]",
|
|
447
|
+
queryKey: ["project-access"],
|
|
448
|
+
meta: {
|
|
449
|
+
jskit: {
|
|
450
|
+
requestRecoveryLabel: "Project access",
|
|
451
|
+
requestRecoverySource: "project-access.panel",
|
|
452
|
+
requestRecoveryDedupeKey: "project-access",
|
|
453
|
+
requestRecoveryMethod: "GET"
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
state: {
|
|
457
|
+
status: "error",
|
|
458
|
+
fetchStatus: "idle",
|
|
459
|
+
error: {
|
|
460
|
+
status: 0,
|
|
461
|
+
message: "Network request failed."
|
|
462
|
+
},
|
|
463
|
+
errorUpdateCount: 1
|
|
464
|
+
},
|
|
465
|
+
isActive() {
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
queryCache.emit({ type: "updated", query });
|
|
470
|
+
|
|
471
|
+
const errorStore = app.make("runtime.web-error.presentation-store.client");
|
|
472
|
+
assert.equal(errorStore.getState().channels.banner.length, 1);
|
|
473
|
+
|
|
474
|
+
query.state = {
|
|
475
|
+
status: "success",
|
|
476
|
+
fetchStatus: "idle",
|
|
477
|
+
data: {
|
|
478
|
+
ok: true
|
|
479
|
+
},
|
|
480
|
+
error: null,
|
|
481
|
+
errorUpdateCount: 1
|
|
482
|
+
};
|
|
483
|
+
queryCache.emit({ type: "updated", query });
|
|
484
|
+
|
|
485
|
+
assert.equal(errorStore.getState().channels.banner.length, 0);
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
|
|
431
489
|
test("shell request recovery ignores query transport failures without a safe read method", async () => {
|
|
432
490
|
await withFetchStub({ surfaceAccess: {} }, async () => {
|
|
433
491
|
const queryCache = createQueryCacheDouble();
|
|
@@ -316,6 +316,10 @@ test("shell-web placement topology seeds global actions as a semantic shell plac
|
|
|
316
316
|
});
|
|
317
317
|
|
|
318
318
|
test("shell-web descriptor metadata advertises adaptive shell outlets, default links, and installs the scaffold page", () => {
|
|
319
|
+
assert.deepEqual(descriptor?.metadata?.client?.optimizeDeps?.exclude, [
|
|
320
|
+
"@jskit-ai/shell-web/client"
|
|
321
|
+
]);
|
|
322
|
+
|
|
319
323
|
assert.deepEqual(readClientContainerTokens(), [
|
|
320
324
|
"runtime.web-placement.client",
|
|
321
325
|
"runtime.web-bootstrap.client",
|