@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.
@@ -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.94",
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.95"
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.94",
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.95"
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
- reportedErrorUpdateByQueryHash.delete(queryHash);
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
- if (reportedErrorUpdateByQueryHash.get(queryHash) === reportKey) {
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
- reportedErrorUpdateByQueryHash.clear();
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/);
@@ -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",