@gooddata/sdk-ui-pluggable-host 11.54.0-alpha.4 → 11.54.0-alpha.5

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.
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Registers the URL scope of a remote app's stylesheets: every stylesheet link whose
3
+ * href starts with the directory of `remoteEntryUrl` belongs to `appId`.
4
+ *
5
+ * Idempotent. Registering a different URL for a known app replaces its scope and retires the
6
+ * previous build's links, so the old CSS never comes back with the new build. Must be called
7
+ * before the app's module is loaded so late-injected CSS of deactivated apps is caught by the
8
+ * observer.
9
+ *
10
+ * Refused with a warning when the prefix would swallow host stylesheets: a remote entry at the
11
+ * host page's own origin root, or a prefix covering a scope reserved by
12
+ * `reserveHostStylesheetScope`. A refused app is simply not tracked, so its CSS stays applied
13
+ * across navigation. A root on another origin cannot match a host link and is kept.
14
+ *
15
+ * If one remote ever served two pluggable apps from the same base URL, attribution would
16
+ * over-match; the fix would be the per-expose CSS lists in mf-manifest.json
17
+ * (`exposes[].assets.css`).
18
+ */
19
+ export declare function registerAppStylesheetScope(appId: string, remoteEntryUrl: string): void;
20
+ /**
21
+ * Reserves the URL scope of the host's own remote UI module so no app can claim stylesheets
22
+ * served from the same directory. Must be called before any app registers a scope; the host UI
23
+ * is resolved before an app renders inside it.
24
+ */
25
+ export declare function reserveHostStylesheetScope(remoteEntryUrl: string): void;
26
+ /**
27
+ * Re-enables all stylesheets attributed to the app. Safe to call before any of the
28
+ * app's CSS exists and repeatedly.
29
+ */
30
+ export declare function activateAppStylesheets(appId: string): void;
31
+ /**
32
+ * Disables all stylesheets attributed to the app. Idempotent; a no-op scan for an
33
+ * unknown appId.
34
+ */
35
+ export declare function deactivateAppStylesheets(appId: string): void;
36
+ /**
37
+ * Test-only: clears all registry state and disconnects the head observer.
38
+ */
39
+ export declare function resetStylesheetRegistry(): void;
40
+ //# sourceMappingURL=stylesheetRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stylesheetRegistry.d.ts","sourceRoot":"","sources":["../../src/lib/stylesheetRegistry.ts"],"names":[],"mappings":"AAkJA;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,CAiCtF;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAQvE;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAM1D;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAM5D;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,IAAI,CAQ9C"}
@@ -0,0 +1,207 @@
1
+ // (C) 2026 GoodData Corporation
2
+ const scopePrefixes = new Map();
3
+ const reservedHostPrefixes = new Set();
4
+ const deactivatedApps = new Set();
5
+ let linkLoadStates = new WeakMap();
6
+ let retiredLinks = new WeakSet();
7
+ let observer;
8
+ function deriveScopePrefix(remoteEntryUrl) {
9
+ try {
10
+ const url = new URL(remoteEntryUrl, document.baseURI);
11
+ return url.origin + url.pathname.slice(0, url.pathname.lastIndexOf("/") + 1);
12
+ }
13
+ catch {
14
+ console.error(`[host-runtime/stylesheet-registry] Cannot derive stylesheet scope from remote URL "${remoteEntryUrl}".`);
15
+ return undefined;
16
+ }
17
+ }
18
+ // The longest match wins, so nested directories cannot make ownership depend on registration order.
19
+ function findOwnerOfHref(href) {
20
+ let owner;
21
+ let longestPrefixLength = -1;
22
+ for (const [appId, prefixes] of scopePrefixes) {
23
+ for (const prefix of prefixes) {
24
+ if (href.startsWith(prefix) && prefix.length > longestPrefixLength) {
25
+ owner = appId;
26
+ longestPrefixLength = prefix.length;
27
+ }
28
+ }
29
+ }
30
+ return owner;
31
+ }
32
+ // An app prefix above a reserved host prefix would claim the host's own links.
33
+ function coversReservedHostScope(prefix) {
34
+ for (const reserved of reservedHostPrefixes) {
35
+ if (reserved.startsWith(prefix)) {
36
+ return true;
37
+ }
38
+ }
39
+ return false;
40
+ }
41
+ function isAppStylesheetLink(node) {
42
+ return node instanceof HTMLLinkElement && node.relList.contains("stylesheet");
43
+ }
44
+ // `link.sheet` says whether a sheet is attached right now, not whether one ever loaded:
45
+ // disabling a link detaches it and re-enabling reattaches it asynchronously. So once this
46
+ // registry has disabled a link, only a remembered flag can still be trusted.
47
+ function hasLoaded(link) {
48
+ if (link.sheet) {
49
+ linkLoadStates.set(link, "loaded");
50
+ }
51
+ return linkLoadStates.get(link) === "loaded";
52
+ }
53
+ function setLinkDisabled(link, disabled) {
54
+ if (!disabled) {
55
+ link.disabled = false;
56
+ return;
57
+ }
58
+ if (hasLoaded(link)) {
59
+ link.disabled = true;
60
+ return;
61
+ }
62
+ if (linkLoadStates.get(link) === "awaiting-load") {
63
+ return;
64
+ }
65
+ linkLoadStates.set(link, "awaiting-load");
66
+ // Disabling a link that has never finished loading can suppress its load event, which
67
+ // Vite's preload helper awaits — the pending chunk import would hang forever.
68
+ link.addEventListener("load", () => {
69
+ linkLoadStates.set(link, "loaded");
70
+ // A retired link outlives the scope that owned it, so its owner can no longer be found.
71
+ if (retiredLinks.has(link)) {
72
+ link.disabled = true;
73
+ return;
74
+ }
75
+ const owner = findOwnerOfHref(link.href);
76
+ if (owner && deactivatedApps.has(owner)) {
77
+ link.disabled = true;
78
+ }
79
+ }, { once: true });
80
+ }
81
+ function forEachOwnedLink(appId, callback) {
82
+ const prefixes = scopePrefixes.get(appId);
83
+ if (!prefixes?.size) {
84
+ return;
85
+ }
86
+ document.head.querySelectorAll("link").forEach((link) => {
87
+ if (isAppStylesheetLink(link) && findOwnerOfHref(link.href) === appId) {
88
+ callback(link);
89
+ }
90
+ });
91
+ }
92
+ function handleHeadMutations(mutations) {
93
+ for (const mutation of mutations) {
94
+ mutation.addedNodes.forEach((node) => {
95
+ if (!isAppStylesheetLink(node)) {
96
+ return;
97
+ }
98
+ const owner = findOwnerOfHref(node.href);
99
+ if (owner && deactivatedApps.has(owner)) {
100
+ setLinkDisabled(node, true);
101
+ }
102
+ });
103
+ }
104
+ }
105
+ function ensureObserver() {
106
+ if (observer) {
107
+ return;
108
+ }
109
+ observer = new MutationObserver(handleHeadMutations);
110
+ observer.observe(document.head, { childList: true });
111
+ }
112
+ /**
113
+ * Registers the URL scope of a remote app's stylesheets: every stylesheet link whose
114
+ * href starts with the directory of `remoteEntryUrl` belongs to `appId`.
115
+ *
116
+ * Idempotent. Registering a different URL for a known app replaces its scope and retires the
117
+ * previous build's links, so the old CSS never comes back with the new build. Must be called
118
+ * before the app's module is loaded so late-injected CSS of deactivated apps is caught by the
119
+ * observer.
120
+ *
121
+ * Refused with a warning when the prefix would swallow host stylesheets: a remote entry at the
122
+ * host page's own origin root, or a prefix covering a scope reserved by
123
+ * `reserveHostStylesheetScope`. A refused app is simply not tracked, so its CSS stays applied
124
+ * across navigation. A root on another origin cannot match a host link and is kept.
125
+ *
126
+ * If one remote ever served two pluggable apps from the same base URL, attribution would
127
+ * over-match; the fix would be the per-expose CSS lists in mf-manifest.json
128
+ * (`exposes[].assets.css`).
129
+ */
130
+ export function registerAppStylesheetScope(appId, remoteEntryUrl) {
131
+ if (typeof document === "undefined") {
132
+ return;
133
+ }
134
+ const prefix = deriveScopePrefix(remoteEntryUrl);
135
+ if (!prefix) {
136
+ return;
137
+ }
138
+ if (prefix === `${new URL(document.baseURI).origin}/`) {
139
+ console.warn(`[host-runtime/stylesheet-registry] Not tracking stylesheets of app "${appId}": its remote URL "${remoteEntryUrl}" resolves to the host page origin root, where its CSS cannot be told apart from the host's own.`);
140
+ return;
141
+ }
142
+ if (coversReservedHostScope(prefix)) {
143
+ console.warn(`[host-runtime/stylesheet-registry] Not tracking stylesheets of app "${appId}": its remote URL "${remoteEntryUrl}" covers the host UI module's directory, whose CSS must stay applied.`);
144
+ return;
145
+ }
146
+ const prefixes = scopePrefixes.get(appId);
147
+ if (!prefixes?.has(prefix)) {
148
+ if (prefixes?.size) {
149
+ // A new remote URL for a known app means its previous build is gone. Those links stay
150
+ // in the document, so they must be disabled before the scope stops covering them.
151
+ forEachOwnedLink(appId, (link) => {
152
+ retiredLinks.add(link);
153
+ setLinkDisabled(link, true);
154
+ });
155
+ }
156
+ scopePrefixes.set(appId, new Set([prefix]));
157
+ }
158
+ ensureObserver();
159
+ }
160
+ /**
161
+ * Reserves the URL scope of the host's own remote UI module so no app can claim stylesheets
162
+ * served from the same directory. Must be called before any app registers a scope; the host UI
163
+ * is resolved before an app renders inside it.
164
+ */
165
+ export function reserveHostStylesheetScope(remoteEntryUrl) {
166
+ if (typeof document === "undefined") {
167
+ return;
168
+ }
169
+ const prefix = deriveScopePrefix(remoteEntryUrl);
170
+ if (prefix) {
171
+ reservedHostPrefixes.add(prefix);
172
+ }
173
+ }
174
+ /**
175
+ * Re-enables all stylesheets attributed to the app. Safe to call before any of the
176
+ * app's CSS exists and repeatedly.
177
+ */
178
+ export function activateAppStylesheets(appId) {
179
+ if (typeof document === "undefined") {
180
+ return;
181
+ }
182
+ deactivatedApps.delete(appId);
183
+ forEachOwnedLink(appId, (link) => setLinkDisabled(link, false));
184
+ }
185
+ /**
186
+ * Disables all stylesheets attributed to the app. Idempotent; a no-op scan for an
187
+ * unknown appId.
188
+ */
189
+ export function deactivateAppStylesheets(appId) {
190
+ if (typeof document === "undefined") {
191
+ return;
192
+ }
193
+ deactivatedApps.add(appId);
194
+ forEachOwnedLink(appId, (link) => setLinkDisabled(link, true));
195
+ }
196
+ /**
197
+ * Test-only: clears all registry state and disconnects the head observer.
198
+ */
199
+ export function resetStylesheetRegistry() {
200
+ scopePrefixes.clear();
201
+ reservedHostPrefixes.clear();
202
+ deactivatedApps.clear();
203
+ linkLoadStates = new WeakMap();
204
+ retiredLinks = new WeakSet();
205
+ observer?.disconnect();
206
+ observer = undefined;
207
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"failingStorage.d.ts","sourceRoot":"","sources":["../../src/loader/failingStorage.ts"],"names":[],"mappings":"AAEA,KAAK,aAAa,GAAG,SAAS,GAAG,SAAS,GAAG,YAAY,CAAC;AAE1D;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAC9B,MAAM,EAAE,aAAa,EACrB,GAAG,EAAE,MAAM,IAAI,EACf,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAClC,MAAM,CAiCR"}
@@ -1 +1 @@
1
- {"version":3,"file":"pluggableApplicationsLoader.d.ts","sourceRoot":"","sources":["../../src/loader/pluggableApplicationsLoader.ts"],"names":[],"mappings":"AAEA,OAAO,EACH,KAAK,gCAAgC,EAIxC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,2CAA2C,CAAC;AAG/E,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAOpE;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,SAAS,EAAE,sBAAsB,GAAG,IAAI,CAErF;AAED,wBAAgB,wBAAwB,IAAI,sBAAsB,GAAG,SAAS,CAE7E;AASD;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAgB3C;AAED,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,gCAAgC,GAAG,IAAI,CA2BvF;AAED,wBAAsB,wBAAwB,CAC1C,GAAG,EAAE,gCAAgC,GACtC,OAAO,CAAC,aAAa,CAAC,CAgBxB"}
1
+ {"version":3,"file":"pluggableApplicationsLoader.d.ts","sourceRoot":"","sources":["../../src/loader/pluggableApplicationsLoader.ts"],"names":[],"mappings":"AAEA,OAAO,EACH,KAAK,gCAAgC,EAIxC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,2CAA2C,CAAC;AAI/E,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAOpE;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,SAAS,EAAE,sBAAsB,GAAG,IAAI,CAErF;AAED,wBAAgB,wBAAwB,IAAI,sBAAsB,GAAG,SAAS,CAE7E;AASD;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAgB3C;AAED,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,gCAAgC,GAAG,IAAI,CA2BvF;AAED,wBAAsB,wBAAwB,CAC1C,GAAG,EAAE,gCAAgC,GACtC,OAAO,CAAC,aAAa,CAAC,CAiBxB"}
@@ -1,6 +1,7 @@
1
1
  // (C) 2026 GoodData Corporation
2
2
  import { isExternalPluggableApplicationRegistryItem, isLocalPluggableApplicationRegistryItem, isRemotePluggableApplicationRegistryItem, } from "@gooddata/sdk-model";
3
3
  import { now } from "../debug.js";
4
+ import { registerAppStylesheetScope } from "../lib/stylesheetRegistry.js";
4
5
  import { loadLocalPluggableApplication } from "./localLoader.js";
5
6
  import { loadRemotePluggableApplication, preloadRemotePluggableApplication } from "./remoteLoader.js";
6
7
  let registeredLifecycle;
@@ -77,6 +78,7 @@ export async function loadPluggableApplication(app) {
77
78
  return loadLocalPluggableApplication(app.id);
78
79
  }
79
80
  if (isRemotePluggableApplicationRegistryItem(app)) {
81
+ registerAppStylesheetScope(app.id, app.remote.url);
80
82
  return loadRemotePluggableApplication(app.remote);
81
83
  }
82
84
  throw new Error(`[host-runtime/loader] Unknown application type for "${JSON.stringify(app)}".`);
@@ -1 +1 @@
1
- {"version":3,"file":"PluggableApplicationRenderer.d.ts","sourceRoot":"","sources":["../../src/ui/PluggableApplicationRenderer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAsE,MAAM,OAAO,CAAC;AAI3G,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,EACH,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,EASxB,MAAM,2CAA2C,CAAC;AAenD,OAAO,qCAAqC,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAkBhD,MAAM,WAAW,kCAAkC;IAC/C,GAAG,EAAE,gCAAgC,CAAC;IACtC,GAAG,EAAE,gBAAgB,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,6FAA6F;IAC7F,iBAAiB,CAAC,EAAE,CAChB,QAAQ,CAAC,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,iBAAiB,EAC/B,YAAY,CAAC,EAAE,OAAO,EACtB,kBAAkB,CAAC,EAAE,OAAO,KAC3B,IAAI,CAAC;IACV,8DAA8D;IAC9D,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,oHAAoH;IACpH,oBAAoB,CAAC,EAAE,CAAC,OAAO,EAAE;QAC7B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;QACvB,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,WAAW,CAAC,EAAE,iBAAiB,CAAC;KACnC,KAAK,IAAI,CAAC;IACX;;;OAGG;IACH,qBAAqB,CAAC,EAAE,SAAS,CAC7B,CAAC,CAAC,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,GAAG,SAAS,CACtG,CAAC;IACF;;;OAGG;IACH,iBAAiB,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC,CAAC;IAC7E;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,EAAE,sBAAsB,KAAK,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IAC7F,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACpE,qBAAqB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;CAClF;AAED,wBAAgB,4BAA4B,CAAC,EACzC,GAAG,EACH,GAAG,EACH,QAAQ,EACR,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACxB,EAAE,kCAAkC,2CAuTpC"}
1
+ {"version":3,"file":"PluggableApplicationRenderer.d.ts","sourceRoot":"","sources":["../../src/ui/PluggableApplicationRenderer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAsE,MAAM,OAAO,CAAC;AAI3G,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,EACH,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,EASxB,MAAM,2CAA2C,CAAC;AAgBnD,OAAO,qCAAqC,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAkBhD,MAAM,WAAW,kCAAkC;IAC/C,GAAG,EAAE,gCAAgC,CAAC;IACtC,GAAG,EAAE,gBAAgB,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,6FAA6F;IAC7F,iBAAiB,CAAC,EAAE,CAChB,QAAQ,CAAC,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,iBAAiB,EAC/B,YAAY,CAAC,EAAE,OAAO,EACtB,kBAAkB,CAAC,EAAE,OAAO,KAC3B,IAAI,CAAC;IACV,8DAA8D;IAC9D,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,oHAAoH;IACpH,oBAAoB,CAAC,EAAE,CAAC,OAAO,EAAE;QAC7B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;QACvB,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,WAAW,CAAC,EAAE,iBAAiB,CAAC;KACnC,KAAK,IAAI,CAAC;IACX;;;OAGG;IACH,qBAAqB,CAAC,EAAE,SAAS,CAC7B,CAAC,CAAC,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,GAAG,SAAS,CACtG,CAAC;IACF;;;OAGG;IACH,iBAAiB,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC,CAAC;IAC7E;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,EAAE,sBAAsB,KAAK,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IAC7F,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACpE,qBAAqB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;CAClF;AAED,wBAAgB,4BAA4B,CAAC,EACzC,GAAG,EACH,GAAG,EACH,QAAQ,EACR,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACxB,EAAE,kCAAkC,2CA4TpC"}
@@ -7,6 +7,7 @@ import { LoadingComponent, useAutoupdateRef } from "@gooddata/sdk-ui";
7
7
  import { bemFactory } from "@gooddata/sdk-ui-kit";
8
8
  import { now } from "../debug.js";
9
9
  import { setActiveAppAttribute } from "../lib/activeAppAttribute.js";
10
+ import { activateAppStylesheets, deactivateAppStylesheets } from "../lib/stylesheetRegistry.js";
10
11
  import { getSecuredRemoteAppValidUntil, validateAppSecurity, } from "../loader/appSecurityValidation.js";
11
12
  import { getAppLifecycleCallbacks, loadPluggableApplication } from "../loader/pluggableApplicationsLoader.js";
12
13
  import { getApplicationHref } from "../loader/routing.js";
@@ -91,6 +92,7 @@ export function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOp
91
92
  mountHandleRef.current = undefined;
92
93
  mountedAppRef.current = undefined;
93
94
  setViewState({ state: "loading" });
95
+ activateAppStylesheets(app.id);
94
96
  // unmount() is synchronous, so calling it inline can re-enter React while the parent render
95
97
  // that swapped this application out is still in progress.
96
98
  if (prevHandle) {
@@ -108,6 +110,7 @@ export function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOp
108
110
  const message = intlRef.current.formatMessage(SECURITY_FAILURE_MESSAGES[securityFailure.kind]);
109
111
  console.error(`[host-runtime/renderer] Refusing to mount app "${app.id}": ${securityFailure.kind}.`, securityFailure);
110
112
  lifecycle?.onLoadFailed?.(app.id, securityFailure.kind);
113
+ deactivateAppStylesheets(app.id);
111
114
  setViewState({ state: "error", message });
112
115
  return;
113
116
  }
@@ -149,6 +152,7 @@ export function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOp
149
152
  const errorMessage = mountError instanceof Error ? mountError.message : "Unknown module loading error.";
150
153
  console.error(`[host-runtime/renderer] Failed to mount app "${app.id}".`, mountError);
151
154
  lifecycle?.onLoadFailed?.(app.id, errorMessage);
155
+ deactivateAppStylesheets(app.id);
152
156
  setViewState({
153
157
  state: "error",
154
158
  message: errorMessage,
@@ -160,6 +164,7 @@ export function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOp
160
164
  if (navigationRequestRef?.current === navigationRequest) {
161
165
  navigationRequestRef.current = undefined;
162
166
  }
167
+ deactivateAppStylesheets(app.id);
163
168
  const handle = mountHandleRef.current;
164
169
  if (handle) {
165
170
  mountHandleRef.current = undefined;
@@ -200,6 +205,7 @@ export function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOp
200
205
  if (navigationRequestRef) {
201
206
  navigationRequestRef.current = undefined;
202
207
  }
208
+ deactivateAppStylesheets(mounted.app.id);
203
209
  handle.unmount();
204
210
  lifecycle?.onUnmounted?.(mounted.app.id);
205
211
  lifecycle?.onLoadFailed?.(mounted.app.id, securityFailure.kind);
@@ -1 +1 @@
1
- {"version":3,"file":"resolveHostUiModule.d.ts","sourceRoot":"","sources":["../../src/ui/resolveHostUiModule.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,aAAa,EAAE,MAAM,2CAA2C,CAAC;AAOtG;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,aAAa,CAAC,CAevF"}
1
+ {"version":3,"file":"resolveHostUiModule.d.ts","sourceRoot":"","sources":["../../src/ui/resolveHostUiModule.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,aAAa,EAAE,MAAM,2CAA2C,CAAC;AAQtG;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,aAAa,CAAC,CAgBvF"}
@@ -1,4 +1,5 @@
1
1
  // (C) 2026 GoodData Corporation
2
+ import { reserveHostStylesheetScope } from "../lib/stylesheetRegistry.js";
2
3
  import { loadRemoteHostUiModule } from "../loader/remoteLoader.js";
3
4
  import { getRemoteRegistry } from "../registry/pluggableApplicationsRegistry.js";
4
5
  import { defaultHostUiModule } from "./DefaultHostUi.js";
@@ -11,6 +12,7 @@ import { defaultHostUiModule } from "./DefaultHostUi.js";
11
12
  export async function resolveHostUiModule(ctx) {
12
13
  const remoteRegistry = getRemoteRegistry(ctx);
13
14
  if (remoteRegistry?.uiModule) {
15
+ reserveHostStylesheetScope(remoteRegistry.uiModule.url);
14
16
  try {
15
17
  return await loadRemoteHostUiModule(remoteRegistry.uiModule);
16
18
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gooddata/sdk-ui-pluggable-host",
3
- "version": "11.54.0-alpha.4",
3
+ "version": "11.54.0-alpha.5",
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.54.0-alpha.4",
33
- "@gooddata/sdk-embedding": "11.54.0-alpha.4",
34
- "@gooddata/sdk-backend-spi": "11.54.0-alpha.4",
35
- "@gooddata/sdk-model": "11.54.0-alpha.4",
36
- "@gooddata/sdk-backend-tiger": "11.54.0-alpha.4",
37
- "@gooddata/sdk-pluggable-application-model": "11.54.0-alpha.4",
38
- "@gooddata/sdk-ui": "11.54.0-alpha.4",
39
- "@gooddata/sdk-ui-application-header": "11.54.0-alpha.4",
40
- "@gooddata/sdk-ui-gen-ai": "11.54.0-alpha.4",
41
- "@gooddata/sdk-ui-kit": "11.54.0-alpha.4",
42
- "@gooddata/sdk-ui-semantic-search": "11.54.0-alpha.4",
43
- "@gooddata/sdk-ui-ext": "11.54.0-alpha.4",
44
- "@gooddata/sdk-ui-theme-provider": "11.54.0-alpha.4",
45
- "@gooddata/util": "11.54.0-alpha.4"
32
+ "@gooddata/sdk-backend-base": "11.54.0-alpha.5",
33
+ "@gooddata/sdk-backend-spi": "11.54.0-alpha.5",
34
+ "@gooddata/sdk-backend-tiger": "11.54.0-alpha.5",
35
+ "@gooddata/sdk-embedding": "11.54.0-alpha.5",
36
+ "@gooddata/sdk-model": "11.54.0-alpha.5",
37
+ "@gooddata/sdk-pluggable-application-model": "11.54.0-alpha.5",
38
+ "@gooddata/sdk-ui": "11.54.0-alpha.5",
39
+ "@gooddata/sdk-ui-application-header": "11.54.0-alpha.5",
40
+ "@gooddata/sdk-ui-ext": "11.54.0-alpha.5",
41
+ "@gooddata/sdk-ui-gen-ai": "11.54.0-alpha.5",
42
+ "@gooddata/sdk-ui-theme-provider": "11.54.0-alpha.5",
43
+ "@gooddata/util": "11.54.0-alpha.5",
44
+ "@gooddata/sdk-ui-semantic-search": "11.54.0-alpha.5",
45
+ "@gooddata/sdk-ui-kit": "11.54.0-alpha.5"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@microsoft/api-documenter": "^7.17.0",
@@ -85,8 +85,8 @@
85
85
  "vite": "8.0.16",
86
86
  "vitest": "4.1.8",
87
87
  "vitest-dom": "0.1.1",
88
- "@gooddata/oxlint-config": "11.54.0-alpha.4",
89
- "@gooddata/eslint-config": "11.54.0-alpha.4"
88
+ "@gooddata/eslint-config": "11.54.0-alpha.5",
89
+ "@gooddata/oxlint-config": "11.54.0-alpha.5"
90
90
  },
91
91
  "peerDependencies": {
92
92
  "react": ">=18.3.1",
@@ -1 +0,0 @@
1
- {"version":3,"file":"failingStorage.d.ts","sourceRoot":"","sources":["../../../src/loader/test/failingStorage.ts"],"names":[],"mappings":"AAEA,KAAK,aAAa,GAAG,SAAS,GAAG,SAAS,GAAG,YAAY,CAAC;AAE1D;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAC9B,MAAM,EAAE,aAAa,EACrB,GAAG,EAAE,MAAM,IAAI,EACf,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAClC,MAAM,CAiCR"}