@gooddata/sdk-ui-pluggable-host 11.57.0-alpha.5 → 11.57.0-alpha.7

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 +1 @@
1
- {"version":3,"file":"chunkReloadGuard.d.ts","sourceRoot":"","sources":["../../src/lib/chunkReloadGuard.ts"],"names":[],"mappings":"AAsBA;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IAClC,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,sFAAsF;IACtF,UAAU,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,IAAI,EAAE,qBAAqB,KAAK,IAAI,CAAC;AAoC7E;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,wBAAwB,GAAG,SAAS,GAAG,IAAI,CAEhG;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,UAAU,CAAC;AAEhD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CA2BzD;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,IAAI,IAAI,CAkBjD;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACnC,4DAA4D;IAC5D,GAAG,EAAE,MAAM,CAAC;IACZ,+CAA+C;IAC/C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,EAClC,GAAG,EACH,UAAuB,EACvB,eAAe,GAClB,EAAE,sBAAsB,GAAG,IAAI,CAsE/B"}
1
+ {"version":3,"file":"chunkReloadGuard.d.ts","sourceRoot":"","sources":["../../src/lib/chunkReloadGuard.ts"],"names":[],"mappings":"AAkCA;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IAClC,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,sFAAsF;IACtF,UAAU,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,IAAI,EAAE,qBAAqB,KAAK,IAAI,CAAC;AAsC7E;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,wBAAwB,GAAG,SAAS,GAAG,IAAI,CAEhG;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,UAAU,CAAC;AAEhD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CA4DzD;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,IAAI,IAAI,CAwBjD;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACnC,4DAA4D;IAC5D,GAAG,EAAE,MAAM,CAAC;IACZ,+CAA+C;IAC/C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,EAClC,GAAG,EACH,UAAuB,EACvB,eAAe,GAClB,EAAE,sBAAsB,GAAG,IAAI,CAsE/B"}
@@ -1,9 +1,21 @@
1
1
  // (C) 2026 GoodData Corporation
2
2
  const RELOAD_FLAG_KEY = "gd-chunk-reload-guard";
3
3
  const RELOAD_LOOP_WINDOW_MS = 30_000;
4
+ /**
5
+ * How long one recovery attempt speaks for any further stale chunks in the same document.
6
+ *
7
+ * A redeploy usually takes several imports away at once, and each failure raises its own event.
8
+ * Without a bound, a user who answers the unload prompt with "stay" is asked again immediately,
9
+ * once per failed import. The window is short on purpose: it must cover a burst of simultaneous
10
+ * failures without reaching the next thing the user does, so that someone who saves and carries
11
+ * on still gets their recovery.
12
+ */
13
+ const RELOAD_COALESCE_WINDOW_MS = 1_000;
4
14
  let preloadErrorHandlerInstalled = false;
5
15
  let versionWatcherInstalled = false;
6
16
  let staleChunkReloadListener;
17
+ let lastReloadAttemptAt;
18
+ let isReloadAttemptUnresolved = false;
7
19
  function readReloadFlag() {
8
20
  try {
9
21
  const raw = sessionStorage.getItem(RELOAD_FLAG_KEY);
@@ -79,10 +91,31 @@ export function reloadForStaleChunks(reason) {
79
91
  }
80
92
  const currentHash = getCurrentHash();
81
93
  const previous = readReloadFlag();
82
- if (previous?.hash === currentHash && Date.now() - previous.at < RELOAD_LOOP_WINDOW_MS) {
94
+ // A redeploy strips many chunks at once, so the failures arrive in a burst. One attempt
95
+ // speaks for all of them; without this the user is asked to leave once per failed import.
96
+ //
97
+ // An attempt stays unresolved while the browser's unload prompt is up, because that prompt
98
+ // blocks this thread: elapsed time alone would count the seconds the user spends reading it
99
+ // and expire the window exactly when the queued events are about to drain.
100
+ if (isReloadAttemptUnresolved) {
101
+ return;
102
+ }
103
+ if (lastReloadAttemptAt !== undefined && Date.now() - lastReloadAttemptAt < RELOAD_COALESCE_WINDOW_MS) {
104
+ return;
105
+ }
106
+ // Still running after an attempt means the navigation was cancelled — by a `beforeunload`
107
+ // handler the user answered with "stay". The flag is then this document's own, and must not
108
+ // stand in for a reload that never happened. The loop guard is unaffected: it protects the
109
+ // case where a reload DID replace the document, whose fresh module state starts undefined.
110
+ const staleFlagIsOurOwn = lastReloadAttemptAt !== undefined;
111
+ if (!staleFlagIsOurOwn &&
112
+ previous?.hash === currentHash &&
113
+ Date.now() - previous.at < RELOAD_LOOP_WINDOW_MS) {
83
114
  console.error(`[host-runtime/chunk-reload-guard] Skipping reload for "${reason}" — already reloaded for the same build (${currentHash}) within ${RELOAD_LOOP_WINDOW_MS}ms.`);
84
115
  return;
85
116
  }
117
+ isReloadAttemptUnresolved = true;
118
+ lastReloadAttemptAt = Date.now();
86
119
  writeReloadFlag(currentHash);
87
120
  console.warn(`[host-runtime/chunk-reload-guard] Reloading page due to: ${reason}`);
88
121
  try {
@@ -95,6 +128,13 @@ export function reloadForStaleChunks(reason) {
95
128
  const target = new URL(window.location.href);
96
129
  target.searchParams.set(STALE_CHUNK_RELOAD_PARAM, String(Date.now()));
97
130
  window.location.replace(target.toString());
131
+ // Reached only if the navigation did not happen — a `beforeunload` the user answered with
132
+ // "stay". This runs after that prompt closes, since the prompt blocks the thread, so the
133
+ // coalescing window starts from the moment the user is back rather than from the attempt.
134
+ window.setTimeout(() => {
135
+ isReloadAttemptUnresolved = false;
136
+ lastReloadAttemptAt = Date.now();
137
+ }, 0);
98
138
  }
99
139
  /**
100
140
  * Installs a window listener for `vite:preloadError`. When fired (e.g. a chunk has
@@ -120,6 +160,12 @@ export function installPreloadErrorHandler() {
120
160
  // error from asPluggableApp instead of a preload failure. If the loop guard
121
161
  // suppresses the reload, the original rejection still gives the caller a
122
162
  // truthful failure to handle.
163
+ //
164
+ // Reload synchronously, from inside the dispatch. Deferring it — to a timer or even a
165
+ // microtask — hands control back to the failing import first, and a rejected `React.lazy`
166
+ // with no error boundary above it unmounts the tree that installed the page's
167
+ // `beforeunload` guard. The reload would then discard unsaved work unasked, which is the
168
+ // whole thing this guard exists to prevent.
123
169
  reloadForStaleChunks(`vite:preloadError (${preloadEvent.payload?.message ?? "unknown"})`);
124
170
  });
125
171
  }
@@ -1 +1 @@
1
- {"version":3,"file":"HostChrome.d.ts","sourceRoot":"","sources":["../../src/ui/HostChrome.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAuC,KAAK,SAAS,EAAwB,MAAM,OAAO,CAAC;AAElG,OAAO,EACH,KAAK,iBAAiB,EAEtB,KAAK,gCAAgC,EACxC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACH,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACxB,MAAM,2CAA2C,CAAC;AA6BnD,OAAO,mBAAmB,CAAC;AAK3B,OAAO,0CAA0C,CAAC;AAClD,OAAO,6CAA6C,CAAC;AACrD,OAAO,sDAAsD,CAAC;AAC9D,OAAO,0DAA0D,CAAC;AAiBlE,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,gBAAgB,CAAC;IACtB,oBAAoB,EAAE,gCAAgC,EAAE,CAAC;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,YAAY,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC1C;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,2FAA2F;IAC3F,gBAAgB,CAAC,EAAE,CACf,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,iBAAiB,EAC/B,YAAY,CAAC,EAAE,OAAO,EACtB,kBAAkB,CAAC,EAAE,OAAO,KAC3B,IAAI,CAAC;IACV;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB;AAED,wBAAgB,UAAU,CAAC,EACvB,GAAG,EACH,oBAAoB,EACpB,QAAQ,EACR,UAAU,EACV,SAAS,EAAE,UAAU,EACrB,aAAa,EACb,YAAmB,EACnB,YAAoB,EACpB,UAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,QAAQ,GACX,EAAE,gBAAgB,2CA8OlB"}
1
+ {"version":3,"file":"HostChrome.d.ts","sourceRoot":"","sources":["../../src/ui/HostChrome.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAuC,KAAK,SAAS,EAAwB,MAAM,OAAO,CAAC;AAElG,OAAO,EACH,KAAK,iBAAiB,EAEtB,KAAK,gCAAgC,EACxC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACH,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACxB,MAAM,2CAA2C,CAAC;AA8BnD,OAAO,mBAAmB,CAAC;AAK3B,OAAO,0CAA0C,CAAC;AAClD,OAAO,6CAA6C,CAAC;AACrD,OAAO,sDAAsD,CAAC;AAC9D,OAAO,0DAA0D,CAAC;AAiBlE,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,gBAAgB,CAAC;IACtB,oBAAoB,EAAE,gCAAgC,EAAE,CAAC;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,YAAY,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC1C;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,2FAA2F;IAC3F,gBAAgB,CAAC,EAAE,CACf,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,iBAAiB,EAC/B,YAAY,CAAC,EAAE,OAAO,EACtB,kBAAkB,CAAC,EAAE,OAAO,KAC3B,IAAI,CAAC;IACV;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB;AAED,wBAAgB,UAAU,CAAC,EACvB,GAAG,EACH,oBAAoB,EACpB,QAAQ,EACR,UAAU,EACV,SAAS,EAAE,UAAU,EACrB,aAAa,EACb,YAAmB,EACnB,YAAoB,EACpB,UAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,QAAQ,GACX,EAAE,gBAAgB,2CAiPlB"}
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useCallback, useMemo } from "react";
4
4
  import { BackendProvider, resolveLocale } from "@gooddata/sdk-ui";
5
5
  import { AppHeaderNotifications } from "@gooddata/sdk-ui-application-header";
6
- import { AppHeader, DocumentHeader, ToastsCenterContextProvider, generateHeaderAccountMenuItems, generateHeaderStaticHelpMenuItems, } from "@gooddata/sdk-ui-kit";
6
+ import { AppHeader, DocumentHeader, ToastsCenterContextProvider, generateHeaderAccountMenuItems, generateHeaderCommonHelpMenuItems, generateHeaderStaticHelpMenuItems, } from "@gooddata/sdk-ui-kit";
7
7
  import { defaultHeaderTheme } from "@gooddata/sdk-ui-theme-provider";
8
8
  import defaultLogoUrl from "../assets/logo-white.svg";
9
9
  import { getAppLifecycleCallbacks } from "../loader/pluggableApplicationsLoader.js";
@@ -58,12 +58,15 @@ export function HostChrome({ ctx, resolvedApplications, pathname, onNavigate, on
58
58
  telemetry: shellTelemetry,
59
59
  });
60
60
  const { menuItemsGroups, messages: appMessages } = useMemo(() => buildAppMenu(resolvedApplications, ctx, pathname, ctx.preferredLocale), [resolvedApplications, ctx, pathname]);
61
- // The default help menu links exclusively to GoodData resources (documentation, university,
62
- // community, Slack). When white-labeling is enabled (the "Hide links to GoodData documentation"
63
- // setting) those must not leak into the branded header, so the host renders no default help
64
- // menu. Apps may still supply their own help items via headerOptions.
61
+ // The default help menu links exclusively to GoodData resources (product documentation,
62
+ // university, community, Slack). When white-labeling is enabled (the "Hide links to GoodData
63
+ // documentation" setting) those must not leak into the branded header, so the host renders no
64
+ // default help menu. An app that needs app-specific links replaces the whole menu through
65
+ // headerOptions; an app that pushes nothing gets these product-wide links. (LX-2960)
65
66
  const helpMenuItems = useMemo(() => headerOptions?.helpMenuItems ??
66
- (ctx.whiteLabeling?.enabled ? [] : generateHeaderStaticHelpMenuItems()), [headerOptions, ctx.whiteLabeling?.enabled]);
67
+ (ctx.whiteLabeling?.enabled
68
+ ? []
69
+ : [...generateHeaderCommonHelpMenuItems(), ...generateHeaderStaticHelpMenuItems()]), [headerOptions, ctx.whiteLabeling?.enabled]);
67
70
  const accountMenuItems = useMemo(() => generateHeaderAccountMenuItems(ctx.workspacePermissions ?? {}, features.workspaceId, ctx.settings), [ctx.workspacePermissions, features.workspaceId, ctx.settings]);
68
71
  const handleMenuItemClick = useCallback((item, e) => {
69
72
  e?.preventDefault();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gooddata/sdk-ui-pluggable-host",
3
- "version": "11.57.0-alpha.5",
3
+ "version": "11.57.0-alpha.7",
4
4
  "description": "GoodData SDK runtime for hosting pluggable applications — registry, loader, routing, platform context, default UI chrome",
5
5
  "license": "MIT",
6
6
  "author": "GoodData Corporation",
@@ -29,20 +29,20 @@
29
29
  "dependencies": {
30
30
  "@module-federation/runtime": "2.6.0",
31
31
  "lodash-es": "^4.17.23",
32
- "@gooddata/sdk-backend-base": "11.57.0-alpha.5",
33
- "@gooddata/sdk-backend-spi": "11.57.0-alpha.5",
34
- "@gooddata/sdk-backend-tiger": "11.57.0-alpha.5",
35
- "@gooddata/sdk-embedding": "11.57.0-alpha.5",
36
- "@gooddata/sdk-model": "11.57.0-alpha.5",
37
- "@gooddata/sdk-pluggable-application-model": "11.57.0-alpha.5",
38
- "@gooddata/sdk-ui": "11.57.0-alpha.5",
39
- "@gooddata/sdk-ui-application-header": "11.57.0-alpha.5",
40
- "@gooddata/sdk-ui-ext": "11.57.0-alpha.5",
41
- "@gooddata/sdk-ui-gen-ai": "11.57.0-alpha.5",
42
- "@gooddata/sdk-ui-kit": "11.57.0-alpha.5",
43
- "@gooddata/sdk-ui-semantic-search": "11.57.0-alpha.5",
44
- "@gooddata/util": "11.57.0-alpha.5",
45
- "@gooddata/sdk-ui-theme-provider": "11.57.0-alpha.5"
32
+ "@gooddata/sdk-backend-base": "11.57.0-alpha.7",
33
+ "@gooddata/sdk-backend-spi": "11.57.0-alpha.7",
34
+ "@gooddata/sdk-backend-tiger": "11.57.0-alpha.7",
35
+ "@gooddata/sdk-embedding": "11.57.0-alpha.7",
36
+ "@gooddata/sdk-model": "11.57.0-alpha.7",
37
+ "@gooddata/sdk-pluggable-application-model": "11.57.0-alpha.7",
38
+ "@gooddata/sdk-ui-application-header": "11.57.0-alpha.7",
39
+ "@gooddata/sdk-ui": "11.57.0-alpha.7",
40
+ "@gooddata/sdk-ui-ext": "11.57.0-alpha.7",
41
+ "@gooddata/sdk-ui-gen-ai": "11.57.0-alpha.7",
42
+ "@gooddata/sdk-ui-kit": "11.57.0-alpha.7",
43
+ "@gooddata/sdk-ui-semantic-search": "11.57.0-alpha.7",
44
+ "@gooddata/sdk-ui-theme-provider": "11.57.0-alpha.7",
45
+ "@gooddata/util": "11.57.0-alpha.7"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@microsoft/api-documenter": "7.30.10",
@@ -66,8 +66,8 @@
66
66
  "happy-dom": "20.12.0",
67
67
  "jiti": "2.6.1",
68
68
  "npm-run-all": "4.1.5",
69
- "oxfmt": "0.66.0",
70
- "oxlint": "1.82.0",
69
+ "oxfmt": "0.68.0",
70
+ "oxlint": "1.83.0",
71
71
  "oxlint-plugin-eslint": "1.82.0",
72
72
  "oxlint-tsgolint": "7.0.2001",
73
73
  "react": "19.1.1",
@@ -75,14 +75,14 @@
75
75
  "react-intl": "10.1.25",
76
76
  "react-router": "7.18.1",
77
77
  "rolldown": "1.0.3",
78
- "sass": "1.104.0",
78
+ "sass": "1.104.1",
79
79
  "tslib": "2.8.1",
80
80
  "typescript": "7.0.2",
81
81
  "vite": "8.0.16",
82
82
  "vitest": "4.1.8",
83
83
  "vitest-dom": "0.1.1",
84
- "@gooddata/eslint-config": "11.57.0-alpha.5",
85
- "@gooddata/oxlint-config": "11.57.0-alpha.5"
84
+ "@gooddata/eslint-config": "11.57.0-alpha.7",
85
+ "@gooddata/oxlint-config": "11.57.0-alpha.7"
86
86
  },
87
87
  "peerDependencies": {
88
88
  "react": ">=18.3.1",