This is an automated email from the ASF dual-hosted git repository.

tbonelee pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zeppelin.git


The following commit(s) were added to refs/heads/master by this push:
     new c2bdaba624 [ZEPPELIN-6564] Route the published paragraph through the 
shared react-mount loader
c2bdaba624 is described below

commit c2bdaba624b56e27ebfd380e994b3f8678571d17
Author: YONGJAE LEE (이용재) <[email protected]>
AuthorDate: Sun Aug 23 17:06:13 2026 +0900

    [ZEPPELIN-6564] Route the published paragraph through the shared 
react-mount loader
    
    ### What is this PR for?
    
    Moves the published paragraph off its own React loader and onto the shared 
`react-mount` directive, and removes the dead Module Federation configuration 
that this exposed.
    
    Today there are two loaders. `published/paragraph.component.ts` injects a 
script tag into `document.head` and reads `window.reactApp` directly, while the 
paragraph footer goes through the `[zeppelin-react-mount]` directive added in 
ZEPPELIN-6428. The two paths differ in load-failure handling, container caching 
and typing. The bespoke path has no fallback at all, so a published paragraph 
whose remote fails to load renders nothing.
    
    Split into four commits.
    
    **1. Remove dead Module Federation configuration**
    
    The shell `webpack.config.js` declares a `reactApp` remote but never calls 
`container.init` and never bundles React. Also removes the 
`GenerateRemoteEntryJson` plugin, which writes a `remoteEntry.json` nothing 
reads, and the unused `<at>angular-architects/module-federation` and 
`ngx-build-plus` devDependencies. As a side effect the hard-coded 
`reactApp<at>http://localhost:3001/remoteEntry.js` no longer ends up in 
production shell bundles.
    
    **2. Make <at>zeppelin/sdk framework-neutral**
    
    The SDK declares `<at>angular/common` and `<at>angular/core` as 
peerDependencies but imports neither, and pins them at `^8.2.9` while the 
project is on Angular 21. It does use `rxjs`, which is not declared. A 
non-Angular consumer, which is what the React remote is, inherits a requirement 
that does not exist and misses one that does.
    
    **3. Add ReactFeatureService as the single flag resolver**
    
    Flag parsing was split. The published paragraph accepted both `?react=true` 
and a bare `?react`, while `notebook.component.ts` required exactly 
`?reactFooter=true`. Unified on the permissive rule so the URLs documented in 
ZEPPELIN-6371 keep working. As a result a bare `?reactFooter` now enables the 
footer as well. Both flags are experimental opt-ins; with no flag the behaviour 
is unchanged.
    
    **4. Route the published paragraph through the shared react-mount loader**
    
    Moves the component onto the directive and makes `./PublishedParagraph` 
satisfy the mount contract. It previously returned a bare unmount function, 
which the directive classified as legacy and for which it made `update` a no-op.
    
    ### What type of PR is it?
    
    Refactoring
    
    ### Todos
    
    None
    
    ### What is the Jira issue?
    
    https://issues.apache.org/jira/browse/ZEPPELIN-6564
    
    ### How should this be tested?
    
    * A production build.
    * The `zeppelin-react` vitest suite, extended with a mount-contract spec 
for `PublishedParagraph` mirroring the existing `ParagraphFooter` one. It 
covers the handle shape, in-place update within the same subtree, the 
empty-state round trip, and `onError` on a render failure.
    * The Playwright suites for the published paragraph and the paragraph 
footer, including the fallback path on both.
    * The Angular shell has no unit test harness (ZEPPELIN-6566, 
ZEPPELIN-6567), so its side is covered by Playwright only.
    
    ### Screenshots (if appropriate)
    
    No
    
    ### Questions:
    
    * Does the license files need to update? No
    * Is there breaking changes for older versions? No
    * Does this needs documentation? Yes
    
    
    Closes #5391 from voidmatcha/mfe-prework.
    
    Signed-off-by: ChanHo Lee <[email protected]>
---
 .../e2e/models/published-paragraph-page.ts         |  10 +-
 .../tests/notebook/paragraph/react-footer.spec.ts  |  30 +
 .../notebook/published/published-paragraph.spec.ts |  30 +-
 zeppelin-web-angular/package-lock.json             | 705 +--------------------
 zeppelin-web-angular/package.json                  |   2 -
 .../projects/zeppelin-react/README.md              |  43 +-
 .../projects/zeppelin-react/package-lock.json      |  14 +-
 .../projects/zeppelin-react/package.json           |   1 +
 .../src/pages/PublishedParagraph.spec.tsx          | 122 ++++
 .../src/pages/PublishedParagraph.tsx               |  43 +-
 .../projects/zeppelin-react/vitest.config.ts       |   8 +
 .../projects/zeppelin-react/webpack.config.js      |  48 +-
 .../projects/zeppelin-sdk/package.json             |   3 +-
 .../pages/workspace/notebook/notebook.component.ts |   6 +-
 .../published/paragraph/paragraph.component.html   |   8 +-
 .../published/paragraph/paragraph.component.ts     | 124 ++--
 .../pages/workspace/published/published.module.ts  |   3 +-
 .../src/app/services/public-api.ts                 |   1 +
 .../src/app/services/react-feature.service.ts      |  70 ++
 .../app/share/react-mount/react-mount-handle.ts    |  12 -
 .../app/share/react-mount/react-mount.directive.ts |  29 +-
 .../react-mount/react-remote-loader.service.ts     |   8 +-
 zeppelin-web-angular/webpack.config.js             |  10 -
 23 files changed, 405 insertions(+), 925 deletions(-)

diff --git a/zeppelin-web-angular/e2e/models/published-paragraph-page.ts 
b/zeppelin-web-angular/e2e/models/published-paragraph-page.ts
index 13293c528e..e63350da02 100644
--- a/zeppelin-web-angular/e2e/models/published-paragraph-page.ts
+++ b/zeppelin-web-angular/e2e/models/published-paragraph-page.ts
@@ -15,15 +15,23 @@ import { navigateToNotebookWithFallback } from '../utils';
 import { BasePage } from './base-page';
 
 export class PublishedParagraphPage extends BasePage {
+  readonly confirmationModal: Locator;
+  readonly angularRenderer: Locator;
+  readonly reactWidget: Locator;
+  readonly reactWidgetOrEmptyState: Locator;
   private readonly errorModalContent: Locator;
   private readonly errorModalOkButton: Locator;
-  readonly confirmationModal: Locator;
 
   constructor(page: Page) {
     super(page);
     this.errorModalContent = this.page.locator('.ant-modal-body', { hasText: 
'Paragraph Not Found' }).last();
     this.errorModalOkButton = page.getByRole('button', { name: 'OK' }).last();
     this.confirmationModal = page.locator('div.ant-modal-confirm').last();
+    // The result count is 0 in both modes, so dynamic-forms is the 
discriminator: it renders only in Angular mode.
+    this.angularRenderer = 
page.locator('zeppelin-notebook-paragraph-dynamic-forms');
+    this.reactWidget = 
page.locator('[data-testid="react-published-paragraph"]');
+    // Without paragraph data the remote mounts an <Empty>, so tests that only 
assert "React took over" accept either.
+    this.reactWidgetOrEmptyState = 
this.reactWidget.or(page.locator('.ant-alert'));
   }
 
   async navigateToNotebook(noteId: string): Promise<void> {
diff --git 
a/zeppelin-web-angular/e2e/tests/notebook/paragraph/react-footer.spec.ts 
b/zeppelin-web-angular/e2e/tests/notebook/paragraph/react-footer.spec.ts
index b82065f9bc..f14c451de7 100644
--- a/zeppelin-web-angular/e2e/tests/notebook/paragraph/react-footer.spec.ts
+++ b/zeppelin-web-angular/e2e/tests/notebook/paragraph/react-footer.spec.ts
@@ -55,6 +55,36 @@ test.describe('React Paragraph Footer', () => {
     await 
expect(page.locator('[data-testid="angular-paragraph-footer"]')).toHaveCount(0);
   });
 
+  test('with a bare reactFooter flag (no value), React footer renders', async 
({ page }) => {
+    const { noteId } = testNotebook;
+
+    await test.step('When I open the notebook with a valueless reactFooter 
flag', async () => {
+      await page.goto(`/#/notebook/${noteId}?reactFooter`);
+      await waitForZeppelinReady(page);
+    });
+
+    await test.step('Then the React footer renders and the Angular one does 
not', async () => {
+      await 
expect(page.locator('[data-testid="react-paragraph-footer-content"]').first()).toBeAttached({
+        timeout: 15000
+      });
+      await 
expect(page.locator('[data-testid="angular-paragraph-footer"]')).toHaveCount(0);
+    });
+  });
+
+  test('with an explicit reactFooter=false, Angular footer renders', async ({ 
page }) => {
+    const { noteId } = testNotebook;
+
+    await test.step('When I navigate with an explicit reactFooter=false', 
async () => {
+      await page.goto(`/#/notebook/${noteId}?reactFooter=false`);
+      await waitForZeppelinReady(page);
+    });
+
+    await test.step('Then the flag disables React and Angular renders', async 
() => {
+      await 
expect(page.locator('[data-testid="angular-paragraph-footer"]').first()).toBeAttached({
 timeout: 15000 });
+      await 
expect(page.locator('[data-testid="react-paragraph-footer"]')).toHaveCount(0);
+    });
+  });
+
   test('reactFooter=true preserves the paragraph query param', async ({ page 
}) => {
     const { noteId, paragraphId } = testNotebook;
 
diff --git 
a/zeppelin-web-angular/e2e/tests/notebook/published/published-paragraph.spec.ts 
b/zeppelin-web-angular/e2e/tests/notebook/published/published-paragraph.spec.ts
index 099212243d..fc9dce1f6f 100644
--- 
a/zeppelin-web-angular/e2e/tests/notebook/published/published-paragraph.spec.ts
+++ 
b/zeppelin-web-angular/e2e/tests/notebook/published/published-paragraph.spec.ts
@@ -156,15 +156,33 @@ test.describe('Published Paragraph', () => {
         await waitForZeppelinReady(page);
       });
 
-      await test.step('Then Angular result component should not be rendered', 
async () => {
-        await 
expect(page.locator('zeppelin-notebook-paragraph-result')).toHaveCount(0, { 
timeout: 10000 });
+      await test.step('Then the Angular renderer should not be used', async () 
=> {
+        await expect(publishedParagraphPage.angularRenderer).toHaveCount(0, { 
timeout: 10000 });
       });
 
       await test.step('And React widget should be mounted in the container', 
async () => {
-        // React mount() renders <div data-testid="react-published-paragraph"> 
or <Empty> (Alert)
-        const reactContent = 
page.locator('[data-testid="react-published-paragraph"], .ant-alert');
-        // JUSTIFIED: compound selector covers React success + error fallback 
(.ant-alert); either may render
-        await expect(reactContent).toBeAttached({ timeout: 15000 });
+        await 
expect(publishedParagraphPage.reactWidgetOrEmptyState).toBeAttached({ timeout: 
15000 });
+      });
+    });
+
+    test('when the remote fails to load, the published paragraph falls back to 
Angular', async ({ page }) => {
+      const { noteId, paragraphId } = testNotebook;
+
+      // Dead remote: every remoteEntry.js request fails, so the mount 
directive's onError fires.
+      await page.route('**/remoteEntry.js', route => route.abort());
+
+      await test.step('When the remote entry is requested and fails', async () 
=> {
+        // Angular is the default renderer, so the assertions below pass even 
if React was never enabled.
+        // Awaiting the request is what proves this is a real fallback.
+        const remoteRequested = page.waitForRequest('**/remoteEntry.js');
+        await 
page.goto(`/#/notebook/${noteId}/paragraph/${paragraphId}?react=true`);
+        await remoteRequested;
+        await waitForZeppelinReady(page);
+      });
+
+      await test.step('Then the Angular renderer takes over and React never 
mounts', async () => {
+        await expect(publishedParagraphPage.angularRenderer).toHaveCount(1, { 
timeout: 15000 });
+        await expect(publishedParagraphPage.reactWidget).toHaveCount(0);
       });
     });
   });
diff --git a/zeppelin-web-angular/package-lock.json 
b/zeppelin-web-angular/package-lock.json
index 661678df45..a92ac782c1 100644
--- a/zeppelin-web-angular/package-lock.json
+++ b/zeppelin-web-angular/package-lock.json
@@ -43,7 +43,6 @@
         "zone.js": "~0.15.1"
       },
       "devDependencies": {
-        "@angular-architects/module-federation": "^21.2.2",
         "@angular-builders/custom-webpack": "^21.0.3",
         "@angular-devkit/build-angular": "^21.2.13",
         "@angular-eslint/builder": "21.4.0",
@@ -78,7 +77,6 @@
         "lint-staged": "^15.5.2",
         "monaco-editor-webpack-plugin": "7.0.1",
         "ng-packagr": "^21.2.3",
-        "ngx-build-plus": "^20.0.0",
         "prettier": "^3.6.2",
         "scandirectory": "8.1.1",
         "style-loader": "^4.0.0",
@@ -314,36 +312,6 @@
         "node": ">=6.0.0"
       }
     },
-    "node_modules/@angular-architects/module-federation": {
-      "version": "21.2.2",
-      "resolved": 
"https://registry.npmjs.org/@angular-architects/module-federation/-/module-federation-21.2.2.tgz";,
-      "integrity": 
"sha512-aM6Oys+RGlUZ+GuVz1gx9LlJNMnb+niEtEsCQ3NmgoIMOZDBlfYnm+jbDO0F1TJWrTOBbAvfbKvJbLHfG25r8Q==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@angular-architects/module-federation-runtime": "~21.2.2",
-        "callsite": "^1.0.0",
-        "node-fetch": "^3.3.2",
-        "semver": "~7.7.1",
-        "word-wrap": "^1.2.5"
-      }
-    },
-    "node_modules/@angular-architects/module-federation-runtime": {
-      "version": "21.2.2",
-      "resolved": 
"https://registry.npmjs.org/@angular-architects/module-federation-runtime/-/module-federation-runtime-21.2.2.tgz";,
-      "integrity": 
"sha512-Akl6fLcD2dYXqxW4pLVgytq7NKQ998g4OUjEgcLQnhqyvlu+qdPRUfSDPlTGUiIY01/J2M9ZXcIWmUJJ5tOT5Q==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "tslib": "^2.3.0"
-      },
-      "peerDependencies": {
-        "@angular/common": "^21.2.0",
-        "@angular/core": "^21.2.0",
-        "@module-federation/enhanced": "^2.2.2",
-        "@module-federation/runtime-core": "^2.2.2"
-      }
-    },
     "node_modules/@angular-builders/common": {
       "version": "5.0.3",
       "resolved": 
"https://registry.npmjs.org/@angular-builders/common/-/common-5.0.3.tgz";,
@@ -5995,278 +5963,6 @@
         }
       }
     },
-    "node_modules/@module-federation/bridge-react-webpack-plugin": {
-      "version": "2.5.0",
-      "resolved": 
"https://registry.npmjs.org/@module-federation/bridge-react-webpack-plugin/-/bridge-react-webpack-plugin-2.5.0.tgz";,
-      "integrity": 
"sha512-Ux9XVW//K6K+KHKPdc0Jnc7RtTpZaEXgbVhp5yovtFkCJVt8hEClcTeuI18MvvLiV/q2hUpCU5Wsf9zNaIYStQ==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "@module-federation/sdk": "2.5.0",
-        "@types/semver": "7.5.8",
-        "semver": "7.6.3"
-      }
-    },
-    
"node_modules/@module-federation/bridge-react-webpack-plugin/node_modules/semver":
 {
-      "version": "7.6.3",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz";,
-      "integrity": 
"sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
-      "dev": true,
-      "license": "ISC",
-      "peer": true,
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/@module-federation/cli": {
-      "version": "2.5.0",
-      "resolved": 
"https://registry.npmjs.org/@module-federation/cli/-/cli-2.5.0.tgz";,
-      "integrity": 
"sha512-+czXA6yoiiF9W6+YEOCpQE6zpGZpA89X0oCEz3EaWPTkL4chEbxurjpME8CMnJk9iuFxl167+cBQiQlVBiHGGg==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "@module-federation/dts-plugin": "2.5.0",
-        "@module-federation/sdk": "2.5.0",
-        "commander": "11.1.0",
-        "jiti": "2.4.2"
-      },
-      "bin": {
-        "mf": "bin/mf.js"
-      },
-      "engines": {
-        "node": ">=16.0.0"
-      }
-    },
-    "node_modules/@module-federation/dts-plugin": {
-      "version": "2.5.0",
-      "resolved": 
"https://registry.npmjs.org/@module-federation/dts-plugin/-/dts-plugin-2.5.0.tgz";,
-      "integrity": 
"sha512-q7KDhJ5tn2HrUV7uMuh/L3TaaztUosE+4LAb90sxx0pPPqWRwlpBpxu1REubv5BWXmU1K/Ozn14u6jRbjLVaGA==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "@module-federation/error-codes": "2.5.0",
-        "@module-federation/managers": "2.5.0",
-        "@module-federation/sdk": "2.5.0",
-        "@module-federation/third-party-dts-extractor": "2.5.0",
-        "adm-zip": "0.5.10",
-        "ansi-colors": "4.1.3",
-        "isomorphic-ws": "5.0.0",
-        "node-schedule": "2.1.1",
-        "undici": "7.24.7",
-        "ws": "8.18.0"
-      },
-      "peerDependencies": {
-        "typescript": "^4.9.0 || ^5.0.0",
-        "vue-tsc": ">=1.0.24"
-      },
-      "peerDependenciesMeta": {
-        "vue-tsc": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@module-federation/enhanced": {
-      "version": "2.5.0",
-      "resolved": 
"https://registry.npmjs.org/@module-federation/enhanced/-/enhanced-2.5.0.tgz";,
-      "integrity": 
"sha512-P91tzwyKSCQ6AwirqvAvTqWqmTY79ndpH0uenejFw+bbLpWrjuY0q+iZUXCV/7CSNmqwH2bkA/ssuyZljmcMVQ==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "@module-federation/bridge-react-webpack-plugin": "2.5.0",
-        "@module-federation/cli": "2.5.0",
-        "@module-federation/dts-plugin": "2.5.0",
-        "@module-federation/error-codes": "2.5.0",
-        "@module-federation/inject-external-runtime-core-plugin": "2.5.0",
-        "@module-federation/managers": "2.5.0",
-        "@module-federation/manifest": "2.5.0",
-        "@module-federation/rspack": "2.5.0",
-        "@module-federation/runtime-tools": "2.5.0",
-        "@module-federation/sdk": "2.5.0",
-        "@module-federation/webpack-bundler-runtime": "2.5.0",
-        "schema-utils": "4.3.0",
-        "tapable": "2.3.0",
-        "upath": "2.0.1"
-      },
-      "bin": {
-        "mf": "bin/mf.js"
-      },
-      "peerDependencies": {
-        "typescript": "^4.9.0 || ^5.0.0",
-        "vue-tsc": ">=1.0.24",
-        "webpack": "^5.0.0"
-      },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        },
-        "vue-tsc": {
-          "optional": true
-        },
-        "webpack": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@module-federation/error-codes": {
-      "version": "2.5.0",
-      "resolved": 
"https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-2.5.0.tgz";,
-      "integrity": 
"sha512-sq05/8Gp3csy1nr2/f76K3vLy0/xRqVtP71ibGy8BiLg7h1UxWN7G4EwAKSrPZ4FnsERGeFlIszg5Z+MqlwhFg==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true
-    },
-    "node_modules/@module-federation/inject-external-runtime-core-plugin": {
-      "version": "2.5.0",
-      "resolved": 
"https://registry.npmjs.org/@module-federation/inject-external-runtime-core-plugin/-/inject-external-runtime-core-plugin-2.5.0.tgz";,
-      "integrity": 
"sha512-e2KyTHpesBrPXGHMh4d4+s2xBiNoxbiFJkPRYHMCl81a/Gu+byrMkriZcV4VM/TFvBIlrgOJisVc1nnBI5UDRQ==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "peerDependencies": {
-        "@module-federation/runtime-tools": "2.5.0"
-      }
-    },
-    "node_modules/@module-federation/managers": {
-      "version": "2.5.0",
-      "resolved": 
"https://registry.npmjs.org/@module-federation/managers/-/managers-2.5.0.tgz";,
-      "integrity": 
"sha512-9b5mU/7OYbKrYUJmhZ1kkfeJCZqR7qX6/FWp+oOfZMzUynN7Rb41dwoUs3TdnOKzbZ3CCwtZ2WsR4pF9ZNvuJA==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "@module-federation/sdk": "2.5.0",
-        "find-pkg": "2.0.0"
-      }
-    },
-    "node_modules/@module-federation/manifest": {
-      "version": "2.5.0",
-      "resolved": 
"https://registry.npmjs.org/@module-federation/manifest/-/manifest-2.5.0.tgz";,
-      "integrity": 
"sha512-pmwQCGWjM2oKY7CkR7nEDOfMK0bNFJUifuDxuOB5iOWhU+Rp92UyyBI9IbJAtiISTSFGtuKRy40peJGvQq2VcQ==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "@module-federation/dts-plugin": "2.5.0",
-        "@module-federation/managers": "2.5.0",
-        "@module-federation/sdk": "2.5.0",
-        "find-pkg": "2.0.0"
-      }
-    },
-    "node_modules/@module-federation/rspack": {
-      "version": "2.5.0",
-      "resolved": 
"https://registry.npmjs.org/@module-federation/rspack/-/rspack-2.5.0.tgz";,
-      "integrity": 
"sha512-OAFMpMXuLEQFmWBuC1I7LNDQ8N3CDANXe0YGPWkIPNxKq5Tj/KNfDidmutoYgvXlZKOM4yKBKBsL6Xt/UvtOIw==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "@module-federation/bridge-react-webpack-plugin": "2.5.0",
-        "@module-federation/dts-plugin": "2.5.0",
-        "@module-federation/inject-external-runtime-core-plugin": "2.5.0",
-        "@module-federation/managers": "2.5.0",
-        "@module-federation/manifest": "2.5.0",
-        "@module-federation/runtime-tools": "2.5.0",
-        "@module-federation/sdk": "2.5.0"
-      },
-      "peerDependencies": {
-        "@rspack/core": "^0.7.0 || ^1.0.0 || ^2.0.0-0",
-        "typescript": "^4.9.0 || ^5.0.0",
-        "vue-tsc": ">=1.0.24"
-      },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        },
-        "vue-tsc": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@module-federation/runtime": {
-      "version": "2.5.0",
-      "resolved": 
"https://registry.npmjs.org/@module-federation/runtime/-/runtime-2.5.0.tgz";,
-      "integrity": 
"sha512-dOc7pFEf8aruHBk5hoJLnvwkCa5ELT78q3o9dqcdaa/TT74X5z0FT0BsaGaRBPcse/iP6czK3fWd7RLv5ZKP5g==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "@module-federation/error-codes": "2.5.0",
-        "@module-federation/runtime-core": "2.5.0",
-        "@module-federation/sdk": "2.5.0"
-      }
-    },
-    "node_modules/@module-federation/runtime-core": {
-      "version": "2.5.0",
-      "resolved": 
"https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-2.5.0.tgz";,
-      "integrity": 
"sha512-STmhQ3c6/hunba2FMP6GrHazXU/8GuN7Gk4dOkWNRpnqYIoD8Wx4MNl76j3HdCzBESC7uSMXTniksVaM1+xxyA==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "@module-federation/error-codes": "2.5.0",
-        "@module-federation/sdk": "2.5.0"
-      }
-    },
-    "node_modules/@module-federation/runtime-tools": {
-      "version": "2.5.0",
-      "resolved": 
"https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-2.5.0.tgz";,
-      "integrity": 
"sha512-fR3Na6V78ov3/O17Mev+1vydfmqlYWP4ZNxD/bBkmqKhCO7jMdthNTT02yDljlCyhYl6+X90UJlFhwFle6rIsw==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "@module-federation/runtime": "2.5.0",
-        "@module-federation/webpack-bundler-runtime": "2.5.0"
-      }
-    },
-    "node_modules/@module-federation/sdk": {
-      "version": "2.5.0",
-      "resolved": 
"https://registry.npmjs.org/@module-federation/sdk/-/sdk-2.5.0.tgz";,
-      "integrity": 
"sha512-ScU22XDyV77l50njjzewMpMlNN1CYo0tHS1D6iy+vNKWrHGq8DWVB0vwG8dmvx/WZ4uq+sXgUsQet17MoKsfZw==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "peerDependencies": {
-        "node-fetch": "^2.7.0 || ^3.3.2"
-      },
-      "peerDependenciesMeta": {
-        "node-fetch": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@module-federation/third-party-dts-extractor": {
-      "version": "2.5.0",
-      "resolved": 
"https://registry.npmjs.org/@module-federation/third-party-dts-extractor/-/third-party-dts-extractor-2.5.0.tgz";,
-      "integrity": 
"sha512-5di43LGk2ies86Cj8QyzYr540Ijc+nyPqYziyFotL6Pparnu+uf3b3ERfEyQfBmEcyGk1MpitQIO2J3bd9BcNw==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "find-pkg": "2.0.0",
-        "resolve": "1.22.8"
-      }
-    },
-    "node_modules/@module-federation/webpack-bundler-runtime": {
-      "version": "2.5.0",
-      "resolved": 
"https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-2.5.0.tgz";,
-      "integrity": 
"sha512-UxVad+tNZYkBnZzqJQsZa0pB5gO5cJoCjMumOo3bhzXBJVqHsFupfeHa8Nk7WrRVbJE6zRT9ZHK0s0NDWBMyJw==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "@module-federation/error-codes": "2.5.0",
-        "@module-federation/runtime": "2.5.0",
-        "@module-federation/sdk": "2.5.0"
-      }
-    },
     "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
       "version": "3.0.4",
       "resolved": 
"https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.4.tgz";,
@@ -8163,6 +7859,7 @@
       "integrity": 
"sha512-2MGdy2s2HimsDT444Bp5XnALzNRxuBNc7y0JzyuqKbHBywd4x2NeXyhWXXoxufaCFu5PBc9Qq9jyfjW2Aeh06Q==",
       "dev": true,
       "license": "MIT",
+      "optional": true,
       "peer": true,
       "optionalDependencies": {
         "@rspack/binding-darwin-arm64": "1.7.11",
@@ -8333,6 +8030,7 @@
       "integrity": 
"sha512-rsD9b+Khmot5DwCMiB3cqTQo53ioPG3M/A7BySu8+0+RS7GCxKm+Z+mtsjtG/vsu4Tn2tcqCdZtA3pgLoJB+ew==",
       "dev": true,
       "license": "MIT",
+      "optional": true,
       "peer": true,
       "dependencies": {
         "@module-federation/runtime-tools": "0.22.0",
@@ -8357,6 +8055,7 @@
       "integrity": 
"sha512-xF9SjnEy7vTdx+xekjPCV5cIHOGCkdn3pIxo9vU7gEZMIw0SvAEdsy6Uh17xaCpm8V0FWvR0SZoK9Ik6jGOaug==",
       "dev": true,
       "license": "MIT",
+      "optional": true,
       "peer": true
     },
     "node_modules/@rspack/core/node_modules/@module-federation/runtime": {
@@ -8365,6 +8064,7 @@
       "integrity": 
"sha512-38g5iPju2tPC3KHMPxRKmy4k4onNp6ypFPS1eKGsNLUkXgHsPMBFqAjDw96iEcjri91BrahG4XcdyKi97xZzlA==",
       "dev": true,
       "license": "MIT",
+      "optional": true,
       "peer": true,
       "dependencies": {
         "@module-federation/error-codes": "0.22.0",
@@ -8378,6 +8078,7 @@
       "integrity": 
"sha512-GR1TcD6/s7zqItfhC87zAp30PqzvceoeDGYTgF3Vx2TXvsfDrhP6Qw9T4vudDQL3uJRne6t7CzdT29YyVxlgIA==",
       "dev": true,
       "license": "MIT",
+      "optional": true,
       "peer": true,
       "dependencies": {
         "@module-federation/error-codes": "0.22.0",
@@ -8390,6 +8091,7 @@
       "integrity": 
"sha512-4ScUJ/aUfEernb+4PbLdhM/c60VHl698Gn1gY21m9vyC1Ucn69fPCA1y2EwcCB7IItseRMoNhdcWQnzt/OPCNA==",
       "dev": true,
       "license": "MIT",
+      "optional": true,
       "peer": true,
       "dependencies": {
         "@module-federation/runtime": "0.22.0",
@@ -8402,6 +8104,7 @@
       "integrity": 
"sha512-x4aFNBKn2KVQRuNVC5A7SnrSCSqyfIWmm1DvubjbO9iKFe7ith5niw8dqSFBekYBg2Fwy+eMg4sEFNVvCAdo6g==",
       "dev": true,
       "license": "MIT",
+      "optional": true,
       "peer": true
     },
     
"node_modules/@rspack/core/node_modules/@module-federation/webpack-bundler-runtime":
 {
@@ -8410,6 +8113,7 @@
       "integrity": 
"sha512-aM8gCqXu+/4wBmJtVeMeeMN5guw3chf+2i6HajKtQv7SJfxV/f4IyNQJUeUQu9HfiAZHjqtMV5Lvq/Lvh8LdyA==",
       "dev": true,
       "license": "MIT",
+      "optional": true,
       "peer": true,
       "dependencies": {
         "@module-federation/runtime": "0.22.0",
@@ -8422,6 +8126,7 @@
       "integrity": 
"sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw==",
       "dev": true,
       "license": "MIT",
+      "optional": true,
       "peer": true
     },
     "node_modules/@rtsao/scc": {
@@ -8840,14 +8545,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/@types/semver": {
-      "version": "7.5.8",
-      "resolved": 
"https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz";,
-      "integrity": 
"sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true
-    },
     "node_modules/@types/send": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz";,
@@ -9594,17 +9291,6 @@
         "node": ">=0.8"
       }
     },
-    "node_modules/adm-zip": {
-      "version": "0.5.10",
-      "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz";,
-      "integrity": 
"sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "engines": {
-        "node": ">=6.0"
-      }
-    },
     "node_modules/agent-base": {
       "version": "4.3.0",
       "resolved": 
"https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz";,
@@ -10524,15 +10210,6 @@
         "url": "https://github.com/sponsors/ljharb";
       }
     },
-    "node_modules/callsite": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz";,
-      "integrity": 
"sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==",
-      "dev": true,
-      "engines": {
-        "node": "*"
-      }
-    },
     "node_modules/callsites": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz";,
@@ -10864,17 +10541,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/commander": {
-      "version": "11.1.0",
-      "resolved": 
"https://registry.npmjs.org/commander/-/commander-11.1.0.tgz";,
-      "integrity": 
"sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "engines": {
-        "node": ">=16"
-      }
-    },
     "node_modules/comment-parser": {
       "version": "1.4.1",
       "resolved": 
"https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz";,
@@ -11289,20 +10955,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/cron-parser": {
-      "version": "4.9.0",
-      "resolved": 
"https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz";,
-      "integrity": 
"sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "luxon": "^3.2.1"
-      },
-      "engines": {
-        "node": ">=12.0.0"
-      }
-    },
     "node_modules/cross-env": {
       "version": "10.1.0",
       "resolved": 
"https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz";,
@@ -11636,16 +11288,6 @@
         "lodash": "^4.17.15"
       }
     },
-    "node_modules/data-uri-to-buffer": {
-      "version": "4.0.1",
-      "resolved": 
"https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz";,
-      "integrity": 
"sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">= 12"
-      }
-    },
     "node_modules/data-urls": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz";,
@@ -13298,20 +12940,6 @@
         "node": ">=0.8"
       }
     },
-    "node_modules/expand-tilde": {
-      "version": "2.0.2",
-      "resolved": 
"https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz";,
-      "integrity": 
"sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "homedir-polyfill": "^1.0.1"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/expect-type": {
       "version": "1.4.0",
       "resolved": 
"https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz";,
@@ -13467,30 +13095,6 @@
       "integrity": 
"sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==",
       "license": "MIT"
     },
-    "node_modules/fetch-blob": {
-      "version": "3.2.0",
-      "resolved": 
"https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz";,
-      "integrity": 
"sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/jimmywarting";
-        },
-        {
-          "type": "paypal",
-          "url": "https://paypal.me/jimmywarting";
-        }
-      ],
-      "license": "MIT",
-      "dependencies": {
-        "node-domexception": "^1.0.0",
-        "web-streams-polyfill": "^3.0.3"
-      },
-      "engines": {
-        "node": "^12.20 || >= 14.13"
-      }
-    },
     "node_modules/file-entry-cache": {
       "version": "8.0.0",
       "resolved": 
"https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz";,
@@ -13556,34 +13160,6 @@
         "url": "https://github.com/sponsors/sindresorhus";
       }
     },
-    "node_modules/find-file-up": {
-      "version": "2.0.1",
-      "resolved": 
"https://registry.npmjs.org/find-file-up/-/find-file-up-2.0.1.tgz";,
-      "integrity": 
"sha512-qVdaUhYO39zmh28/JLQM5CoYN9byEOKEH4qfa8K1eNV17W0UUMJ9WgbR/hHFH+t5rcl+6RTb5UC7ck/I+uRkpQ==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "resolve-dir": "^1.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/find-pkg": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/find-pkg/-/find-pkg-2.0.0.tgz";,
-      "integrity": 
"sha512-WgZ+nKbELDa6N3i/9nrHeNznm+lY3z4YfhDDWgW+5P0pdmMj26bxaxU11ookgY3NyP9GC7HvZ9etp0jRFqGEeQ==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "find-file-up": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/find-up": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz";,
@@ -13792,19 +13368,6 @@
         "url": "https://github.com/sponsors/ljharb";
       }
     },
-    "node_modules/formdata-polyfill": {
-      "version": "4.0.10",
-      "resolved": 
"https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz";,
-      "integrity": 
"sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "fetch-blob": "^3.1.2"
-      },
-      "engines": {
-        "node": ">=12.20.0"
-      }
-    },
     "node_modules/forwarded": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz";,
@@ -14093,62 +13656,6 @@
       "dev": true,
       "license": "BSD-2-Clause"
     },
-    "node_modules/global-modules": {
-      "version": "1.0.0",
-      "resolved": 
"https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz";,
-      "integrity": 
"sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "global-prefix": "^1.0.1",
-        "is-windows": "^1.0.1",
-        "resolve-dir": "^1.0.0"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/global-prefix": {
-      "version": "1.0.2",
-      "resolved": 
"https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz";,
-      "integrity": 
"sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "expand-tilde": "^2.0.2",
-        "homedir-polyfill": "^1.0.1",
-        "ini": "^1.3.4",
-        "is-windows": "^1.0.1",
-        "which": "^1.2.14"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/global-prefix/node_modules/ini": {
-      "version": "1.3.8",
-      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz";,
-      "integrity": 
"sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
-      "dev": true,
-      "license": "ISC",
-      "peer": true
-    },
-    "node_modules/global-prefix/node_modules/which": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz";,
-      "integrity": 
"sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
-      "dev": true,
-      "license": "ISC",
-      "peer": true,
-      "dependencies": {
-        "isexe": "^2.0.0"
-      },
-      "bin": {
-        "which": "bin/which"
-      }
-    },
     "node_modules/globals": {
       "version": "14.0.0",
       "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz";,
@@ -14342,20 +13849,6 @@
         "node": "*"
       }
     },
-    "node_modules/homedir-polyfill": {
-      "version": "1.0.3",
-      "resolved": 
"https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz";,
-      "integrity": 
"sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "parse-passwd": "^1.0.0"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/hono": {
       "version": "4.12.23",
       "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.23.tgz";,
@@ -15449,17 +14942,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/is-windows": {
-      "version": "1.0.2",
-      "resolved": 
"https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz";,
-      "integrity": 
"sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/is-wsl": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz";,
@@ -15499,17 +14981,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/isomorphic-ws": {
-      "version": "5.0.0",
-      "resolved": 
"https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz";,
-      "integrity": 
"sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "peerDependencies": {
-        "ws": "*"
-      }
-    },
     "node_modules/istanbul-lib-coverage": {
       "version": "3.2.2",
       "resolved": 
"https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz";,
@@ -15558,6 +15029,7 @@
       "integrity": 
"sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
       "dev": true,
       "license": "MIT",
+      "optional": true,
       "peer": true,
       "bin": {
         "jiti": "lib/jiti-cli.mjs"
@@ -16622,14 +16094,6 @@
         "url": "https://github.com/chalk/slice-ansi?sponsor=1";
       }
     },
-    "node_modules/long-timeout": {
-      "version": "0.1.1",
-      "resolved": 
"https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz";,
-      "integrity": 
"sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true
-    },
     "node_modules/longest": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz";,
@@ -16649,17 +16113,6 @@
         "yallist": "^3.0.2"
       }
     },
-    "node_modules/luxon": {
-      "version": "3.7.2",
-      "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz";,
-      "integrity": 
"sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "engines": {
-        "node": ">=12"
-      }
-    },
     "node_modules/magic-string": {
       "version": "0.30.21",
       "resolved": 
"https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz";,
@@ -17446,21 +16899,6 @@
         "url": "https://opencollective.com/date-fns";
       }
     },
-    "node_modules/ngx-build-plus": {
-      "version": "20.0.0",
-      "resolved": 
"https://registry.npmjs.org/ngx-build-plus/-/ngx-build-plus-20.0.0.tgz";,
-      "integrity": 
"sha512-cm1ZMTACAN3DEqBt/alS84zwVGgL5HAl5Dk/wh7CPyGUBQnLaxiAhjFZ6iykxgSO3e9ebIZmDBvTC480piC1eA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "webpack-merge": "^6.0.0"
-      },
-      "peerDependencies": {
-        "@angular-devkit/build-angular": ">=20.0.0",
-        "@schematics/angular": ">=20.0.0",
-        "rxjs": ">= 6.0.0"
-      }
-    },
     "node_modules/node-addon-api": {
       "version": "7.1.1",
       "resolved": 
"https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz";,
@@ -17469,27 +16907,6 @@
       "license": "MIT",
       "optional": true
     },
-    "node_modules/node-domexception": {
-      "version": "1.0.0",
-      "resolved": 
"https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz";,
-      "integrity": 
"sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
-      "deprecated": "Use your platform's native DOMException instead",
-      "dev": true,
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/jimmywarting";
-        },
-        {
-          "type": "github",
-          "url": "https://paypal.me/jimmywarting";
-        }
-      ],
-      "license": "MIT",
-      "engines": {
-        "node": ">=10.5.0"
-      }
-    },
     "node_modules/node-exports-info": {
       "version": "1.6.0",
       "resolved": 
"https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz";,
@@ -17519,25 +16936,6 @@
         "semver": "bin/semver.js"
       }
     },
-    "node_modules/node-fetch": {
-      "version": "3.3.2",
-      "resolved": 
"https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz";,
-      "integrity": 
"sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "data-uri-to-buffer": "^4.0.0",
-        "fetch-blob": "^3.1.4",
-        "formdata-polyfill": "^4.0.10"
-      },
-      "engines": {
-        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/node-fetch";
-      }
-    },
     "node_modules/node-gyp": {
       "version": "12.3.0",
       "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.3.0.tgz";,
@@ -17625,22 +17023,6 @@
         "node": ">=18"
       }
     },
-    "node_modules/node-schedule": {
-      "version": "2.1.1",
-      "resolved": 
"https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz";,
-      "integrity": 
"sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "cron-parser": "^4.2.0",
-        "long-timeout": "0.1.1",
-        "sorted-array-functions": "^1.3.0"
-      },
-      "engines": {
-        "node": ">=6"
-      }
-    },
     "node_modules/nopt": {
       "version": "9.0.0",
       "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz";,
@@ -18289,17 +17671,6 @@
         "node": ">= 0.10"
       }
     },
-    "node_modules/parse-passwd": {
-      "version": "1.0.0",
-      "resolved": 
"https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz";,
-      "integrity": 
"sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/parse-statements": {
       "version": "1.0.11",
       "resolved": 
"https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz";,
@@ -19241,21 +18612,6 @@
         "url": "https://github.com/sponsors/ljharb";
       }
     },
-    "node_modules/resolve-dir": {
-      "version": "1.0.1",
-      "resolved": 
"https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz";,
-      "integrity": 
"sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "expand-tilde": "^2.0.0",
-        "global-modules": "^1.0.0"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/resolve-from": {
       "version": "4.0.0",
       "resolved": 
"https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz";,
@@ -20326,14 +19682,6 @@
         "node": ">= 14"
       }
     },
-    "node_modules/sorted-array-functions": {
-      "version": "1.3.0",
-      "resolved": 
"https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz";,
-      "integrity": 
"sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true
-    },
     "node_modules/source-map": {
       "version": "0.7.6",
       "resolved": 
"https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz";,
@@ -21496,17 +20844,6 @@
         "url": "https://github.com/sponsors/ljharb";
       }
     },
-    "node_modules/undici": {
-      "version": "7.24.7",
-      "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.7.tgz";,
-      "integrity": 
"sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "engines": {
-        "node": ">=20.18.1"
-      }
-    },
     "node_modules/undici-types": {
       "version": "6.20.0",
       "resolved": 
"https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz";,
@@ -21568,18 +20905,6 @@
         "node": ">= 0.8"
       }
     },
-    "node_modules/upath": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz";,
-      "integrity": 
"sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "engines": {
-        "node": ">=4",
-        "yarn": "*"
-      }
-    },
     "node_modules/update-browserslist-db": {
       "version": "1.2.3",
       "resolved": 
"https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz";,
@@ -22226,16 +21551,6 @@
       "license": "MIT",
       "optional": true
     },
-    "node_modules/web-streams-polyfill": {
-      "version": "3.3.3",
-      "resolved": 
"https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz";,
-      "integrity": 
"sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">= 8"
-      }
-    },
     "node_modules/webidl-conversions": {
       "version": "8.0.1",
       "resolved": 
"https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz";,
diff --git a/zeppelin-web-angular/package.json 
b/zeppelin-web-angular/package.json
index dbf8a7ab08..2324d962bc 100644
--- a/zeppelin-web-angular/package.json
+++ b/zeppelin-web-angular/package.json
@@ -72,7 +72,6 @@
     "zone.js": "~0.15.1"
   },
   "devDependencies": {
-    "@angular-architects/module-federation": "^21.2.2",
     "@angular-builders/custom-webpack": "^21.0.3",
     "@angular-devkit/build-angular": "^21.2.13",
     "@angular-eslint/builder": "21.4.0",
@@ -107,7 +106,6 @@
     "lint-staged": "^15.5.2",
     "monaco-editor-webpack-plugin": "7.0.1",
     "ng-packagr": "^21.2.3",
-    "ngx-build-plus": "^20.0.0",
     "prettier": "^3.6.2",
     "scandirectory": "8.1.1",
     "style-loader": "^4.0.0",
diff --git a/zeppelin-web-angular/projects/zeppelin-react/README.md 
b/zeppelin-web-angular/projects/zeppelin-react/README.md
index f9d1b1d631..293fe0541f 100644
--- a/zeppelin-web-angular/projects/zeppelin-react/README.md
+++ b/zeppelin-web-angular/projects/zeppelin-react/README.md
@@ -46,22 +46,37 @@ The published paragraph was picked as pilot because it's 
read-only and has almos
 ## Architecture
 
 ```
-Angular host (port 4200)          React remote (port 3001)
-┌─────────────────────────┐       ┌─────────────────────────┐
-│  paragraph.component.ts │       │  webpack.config.js      │
-│  loads remoteEntry.js ──┼──────>│  ModuleFederationPlugin  │
-│  calls mount(el, props) │       │  name: 'reactApp'       │
-└─────────────────────────┘       │  exposes:               │
-                                  │    ./PublishedParagraph  │
-                                  └─────────────────────────┘
+Angular host (port 4200)              React remote (port 3001)
+┌───────────────────────────────┐     ┌─────────────────────────┐
+│  [zeppelin-react-mount]       │     │  webpack.config.js      │
+│  ReactRemoteLoaderService ────┼────>│  ModuleFederationPlugin │
+│  loads remoteEntry.js         │     │  name: 'reactApp'       │
+│  calls mount(el, props)       │     │  exposes:               │
+└───────────────────────────────┘     │    ./PublishedParagraph │
+                                      │    ./ParagraphFooter    │
+                                      └─────────────────────────┘
 ```
 
-1. Angular loads `remoteEntry.js` from the React dev server or production 
assets.
+1. `ReactRemoteLoaderService` loads `remoteEntry.js` from the React dev server 
or production assets, once per page.
 2. The script registers `window.reactApp` as a Module Federation container.
-3. Angular calls `container.get('./PublishedParagraph')` to get the module.
-4. The module exports `mount(element, props)`, which calls `createRoot()` and 
renders into the DOM element.
+3. The service calls `container.get('<exposed key>')` and caches the module 
promise.
+4. The `[zeppelin-react-mount]` directive calls `mount(element, props)` 
outside the Angular zone and keeps the returned handle for later `update()` and 
`unmount()` calls.
+5. If the remote fails to load, the directive reports the error to the host, 
which renders its Angular fallback instead.
 
-Append `?react=true` to any published paragraph URL to activate React mode.
+Host components do not touch `window.reactApp` themselves; they bind props to 
the directive and supply an `onError` callback.
+
+## Feature flags
+
+Each React surface is behind a URL query flag, resolved by 
`ReactFeatureService`:
+
+| URL | Result |
+| --- | --- |
+| `?react=true` | enabled |
+| `?react` | enabled |
+| `?react=false` | disabled |
+| flag absent | disabled |
+
+Append `?react=true` to any published paragraph URL, or `?reactFooter=true` to 
a notebook URL, to activate React mode.
 
 ## Setup
 
@@ -146,7 +161,5 @@ export function mount(element: HTMLElement, props: Props): 
ReactMountHandle;
    `exampleFeatureProps` should be a getter on the host component (not
    an inline object literal) so identity is stable when nothing changed.
 
-The legacy `./PublishedParagraph` module returns a bare unmount fn from
-`mount`. The directive tolerates that shape, but new modules should use
-the handle contract.
+Every exposed module must return the handle contract from `mount`. The 
directive assigns the return value straight to its handle, so returning a bare 
unmount function makes the next prop change throw.
 
diff --git a/zeppelin-web-angular/projects/zeppelin-react/package-lock.json 
b/zeppelin-web-angular/projects/zeppelin-react/package-lock.json
index 53b1f40349..757bd13946 100644
--- a/zeppelin-web-angular/projects/zeppelin-react/package-lock.json
+++ b/zeppelin-web-angular/projects/zeppelin-react/package-lock.json
@@ -17,6 +17,7 @@
         "file-saver": "2.0.5",
         "react": "18.3.1",
         "react-dom": "18.3.1",
+        "rxjs": "^7.8.0",
         "xlsx-js-style": "1.2.0"
       },
       "devDependencies": {
@@ -53,8 +54,7 @@
         "tslib": "^2.0.0"
       },
       "peerDependencies": {
-        "@angular/common": "^8.2.9",
-        "@angular/core": "^8.2.9"
+        "rxjs": "^7.0.0"
       }
     },
     "node_modules/@ant-design/colors": {
@@ -8832,6 +8832,15 @@
         "url": "https://github.com/sponsors/sindresorhus";
       }
     },
+    "node_modules/rxjs": {
+      "version": "7.8.2",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz";,
+      "integrity": 
"sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "tslib": "^2.1.0"
+      }
+    },
     "node_modules/safe-array-concat": {
       "version": "1.1.4",
       "resolved": 
"https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz";,
@@ -9954,7 +9963,6 @@
       "version": "2.8.1",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz";,
       "integrity": 
"sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
-      "dev": true,
       "license": "0BSD"
     },
     "node_modules/tsyringe": {
diff --git a/zeppelin-web-angular/projects/zeppelin-react/package.json 
b/zeppelin-web-angular/projects/zeppelin-react/package.json
index 3cca715db9..3dd2c0e0ce 100644
--- a/zeppelin-web-angular/projects/zeppelin-react/package.json
+++ b/zeppelin-web-angular/projects/zeppelin-react/package.json
@@ -23,6 +23,7 @@
     "file-saver": "2.0.5",
     "react": "18.3.1",
     "react-dom": "18.3.1",
+    "rxjs": "^7.8.0",
     "xlsx-js-style": "1.2.0"
   },
   "devDependencies": {
diff --git 
a/zeppelin-web-angular/projects/zeppelin-react/src/pages/PublishedParagraph.spec.tsx
 
b/zeppelin-web-angular/projects/zeppelin-react/src/pages/PublishedParagraph.spec.tsx
new file mode 100644
index 0000000000..0999b7a71e
--- /dev/null
+++ 
b/zeppelin-web-angular/projects/zeppelin-react/src/pages/PublishedParagraph.spec.tsx
@@ -0,0 +1,122 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { act } from 'react';
+import { afterEach, describe, expect, it, vi } from 'vitest';
+import { DatasetType } from '@zeppelin/sdk';
+import { mount, PublishedParagraphMountHandle, PublishedParagraphProps } from 
'./PublishedParagraph';
+
+const textResult = (data: string) => ({ type: DatasetType.TEXT, data });
+
+const baseProps: PublishedParagraphProps = {
+  paragraphId: 'paragraph-1',
+  results: [textResult('first output')]
+};
+
+describe('PublishedParagraph mount contract', () => {
+  let host: HTMLElement | null = null;
+  let handle: PublishedParagraphMountHandle | null = null;
+
+  const mountParagraph = (props?: PublishedParagraphProps): void => {
+    host = document.createElement('div');
+    document.body.appendChild(host);
+    act(() => {
+      handle = mount(host as HTMLElement, props);
+    });
+  };
+
+  afterEach(() => {
+    if (handle) {
+      const h = handle;
+      act(() => h.unmount());
+      handle = null;
+    }
+    host?.remove();
+    host = null;
+    vi.restoreAllMocks();
+  });
+
+  it('throws when no element is given', () => {
+    expect(() => mount(null as unknown as HTMLElement, 
baseProps)).toThrow('Mount element is required');
+  });
+
+  it('returns an update/unmount handle and renders the results', () => {
+    mountParagraph(baseProps);
+
+    expect(typeof handle!.update).toBe('function');
+    expect(typeof handle!.unmount).toBe('function');
+
+    const rendered = 
host!.querySelector('[data-testid="react-published-paragraph"]');
+    expect(rendered).not.toBeNull();
+    expect(rendered!.textContent).toContain('first output');
+  });
+
+  it('renders the empty state when the paragraph has no results', () => {
+    mountParagraph({ ...baseProps, results: [] });
+
+    
expect(host!.querySelector('[data-testid="react-published-paragraph"]')).toBeNull();
+    expect(host!.textContent).toContain('No paragraph data found');
+  });
+
+  it('renders the empty state when mounted without props at all', () => {
+    mountParagraph();
+
+    expect(host!.textContent).toContain('No paragraph data found');
+  });
+
+  it('update() re-renders in place with new results', () => {
+    mountParagraph(baseProps);
+    const rootBefore = host!.firstElementChild;
+
+    const h = handle!;
+    act(() => h.update({ ...baseProps, results: [textResult('second output')] 
}));
+
+    const rendered = 
host!.querySelector('[data-testid="react-published-paragraph"]')!;
+    expect(rendered.textContent).toContain('second output');
+    expect(rendered.textContent).not.toContain('first output');
+    // Same host subtree, not a remount. In-place update, which is what the 
host relies on.
+    expect(host!.firstElementChild).toBe(rootBefore);
+  });
+
+  it('update() can move the paragraph back to the empty state', () => {
+    mountParagraph(baseProps);
+
+    const h = handle!;
+    act(() => h.update({ ...baseProps, results: [] }));
+
+    
expect(host!.querySelector('[data-testid="react-published-paragraph"]')).toBeNull();
+    expect(host!.textContent).toContain('No paragraph data found');
+  });
+
+  it('unmount() empties the host element', () => {
+    mountParagraph(baseProps);
+    const h = handle!;
+    handle = null;
+
+    act(() => h.unmount());
+
+    expect(host!.innerHTML).toBe('');
+  });
+
+  it('reports render failures through onError instead of throwing at the 
host', () => {
+    vi.spyOn(console, 'error').mockImplementation(() => undefined);
+    const onError = vi.fn();
+
+    // Truthy with a length but not an array, so results.map() throws during 
render.
+    const malformed = { length: 1 } as unknown as 
PublishedParagraphProps['results'];
+
+    expect(() => mountParagraph({ ...baseProps, results: malformed, onError 
})).not.toThrow();
+
+    expect(host!.innerHTML).toBe('');
+    expect(onError).toHaveBeenCalledTimes(1);
+  });
+});
diff --git 
a/zeppelin-web-angular/projects/zeppelin-react/src/pages/PublishedParagraph.tsx 
b/zeppelin-web-angular/projects/zeppelin-react/src/pages/PublishedParagraph.tsx
index 04ea1024b7..df803619ee 100644
--- 
a/zeppelin-web-angular/projects/zeppelin-react/src/pages/PublishedParagraph.tsx
+++ 
b/zeppelin-web-angular/projects/zeppelin-react/src/pages/PublishedParagraph.tsx
@@ -10,8 +10,8 @@
  * limitations under the License.
  */
 
-import { createRoot } from 'react-dom/client';
-import { Empty } from '@/components';
+import { createRoot, Root } from 'react-dom/client';
+import { Empty, ReactErrorBoundary } from '@/components';
 import { SingleResultRenderer } from '@/templates';
 import { ZeppelinThemeProvider } from '@/theme';
 import type { ParagraphConfigResults, ParagraphIResultsMsgItem } from 
'@zeppelin/sdk';
@@ -22,6 +22,7 @@ export interface PublishedParagraphProps {
   paragraphId: string;
   results?: ParagraphIResultsMsgItem[];
   config?: ParagraphConfigResults;
+  onError?: (error: unknown) => void;
 }
 
 export const PublishedParagraph = ({ results, config }: 
PublishedParagraphProps) => (
@@ -42,22 +43,38 @@ export const PublishedParagraph = ({ results, config }: 
PublishedParagraphProps)
   </ZeppelinThemeProvider>
 );
 
-export const mount = (element: HTMLElement, props?: PublishedParagraphProps) 
=> {
+export interface PublishedParagraphMountHandle {
+  update: (props: PublishedParagraphProps) => void;
+  unmount: () => void;
+}
+
+export const mount = (element: HTMLElement, initialProps?: 
PublishedParagraphProps): PublishedParagraphMountHandle => {
   if (!element) {
     throw new Error('Mount element is required');
   }
 
-  const root = createRoot(element);
+  const root: Root = createRoot(element);
+
+  const renderWith = (props?: PublishedParagraphProps) => {
+    root.render(
+      <ReactErrorBoundary onError={props?.onError}>
+        <PublishedParagraph
+          paragraphId={props?.paragraphId || 'demo-paragraph'}
+          results={props?.results}
+          config={props?.config}
+        />
+      </ReactErrorBoundary>
+    );
+  };
 
-  root.render(
-    <PublishedParagraph
-      paragraphId={props?.paragraphId || 'demo-paragraph'}
-      results={props?.results}
-      config={props?.config}
-    />
-  );
+  renderWith(initialProps);
 
-  return () => {
-    root.unmount();
+  return {
+    update: (newProps: PublishedParagraphProps) => {
+      renderWith(newProps);
+    },
+    unmount: () => {
+      root.unmount();
+    }
   };
 };
diff --git a/zeppelin-web-angular/projects/zeppelin-react/vitest.config.ts 
b/zeppelin-web-angular/projects/zeppelin-react/vitest.config.ts
index 8fde66b75c..ffddd37ae3 100644
--- a/zeppelin-web-angular/projects/zeppelin-react/vitest.config.ts
+++ b/zeppelin-web-angular/projects/zeppelin-react/vitest.config.ts
@@ -10,9 +10,17 @@
  * limitations under the License.
  */
 
+import path from 'path';
 import { defineConfig } from 'vitest/config';
 
 export default defineConfig({
+  // Kept in sync with the `resolve.alias` block in webpack.config.js.
+  resolve: {
+    alias: {
+      '@': path.resolve(__dirname, 'src'),
+      '@zeppelin/sdk': path.resolve(__dirname, '../zeppelin-sdk/src')
+    }
+  },
   test: {
     environment: 'jsdom',
     include: ['src/**/*.spec.{ts,tsx}'],
diff --git a/zeppelin-web-angular/projects/zeppelin-react/webpack.config.js 
b/zeppelin-web-angular/projects/zeppelin-react/webpack.config.js
index aef55f29ac..65a635b8b2 100644
--- a/zeppelin-web-angular/projects/zeppelin-react/webpack.config.js
+++ b/zeppelin-web-angular/projects/zeppelin-react/webpack.config.js
@@ -65,61 +65,19 @@ module.exports = (_env, argv) => {
       ]
     },
     plugins: [
+      // No `shared` scope: the shell bundles no React and never calls 
container.init.
+      // This remote is the only participant, so there is nothing to dedupe 
against. Re-add it once a second exists.
       new ModuleFederationPlugin({
         name: 'reactApp',
         filename: 'remoteEntry.js',
         exposes: {
           './PublishedParagraph': './src/pages/PublishedParagraph',
           './ParagraphFooter': './src/components/paragraph/ParagraphFooter'
-        },
-        shared: {
-          react: {
-            singleton: true,
-            strictVersion: false,
-            requiredVersion: '18.3.1',
-            eager: true
-          },
-          'react-dom': {
-            singleton: true,
-            strictVersion: false,
-            requiredVersion: '18.3.1',
-            eager: true
-          }
         }
       }),
       new HtmlWebpackPlugin({
         template: './src/index.html'
-      }),
-      {
-        apply: compiler => {
-          compiler.hooks.afterEmit.tap('GenerateRemoteEntryJson', () => {
-            const fs = require('fs');
-            const path = require('path');
-
-            const remoteEntryJson = {
-              name: 'zeppelinReact',
-              type: 'module',
-              version: '1.0.0',
-              baseUrl: isProduction ? '/assets/react/' : 
'http://localhost:3001/',
-              exposes: {
-                './PublishedParagraph': './PublishedParagraph.tsx',
-                './ParagraphFooter': './ParagraphFooter.tsx'
-              }
-            };
-
-            const outputDir = path.resolve(__dirname, 'dist');
-            const outputPath = path.resolve(outputDir, 'remoteEntry.json');
-
-            // Ensure directory exists
-            if (!fs.existsSync(outputDir)) {
-              fs.mkdirSync(outputDir, { recursive: true });
-            }
-
-            fs.writeFileSync(outputPath, JSON.stringify(remoteEntryJson, null, 
2));
-            console.log('Generated remoteEntry.json for Native Federation');
-          });
-        }
-      }
+      })
     ],
     output: {
       path: path.resolve(__dirname, 'dist'),
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/package.json 
b/zeppelin-web-angular/projects/zeppelin-sdk/package.json
index 09f5b00a54..b8d9e821bf 100644
--- a/zeppelin-web-angular/projects/zeppelin-sdk/package.json
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/package.json
@@ -5,7 +5,6 @@
     "tslib": "^2.0.0"
   },
   "peerDependencies": {
-    "@angular/common": "^8.2.9",
-    "@angular/core": "^8.2.9"
+    "rxjs": "^7.0.0"
   }
 }
diff --git 
a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.ts 
b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.ts
index 552e0f8c8d..6f5e24974c 100644
--- 
a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.ts
+++ 
b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.ts
@@ -43,6 +43,7 @@ import {
   NgZService,
   NoteStatusService,
   NoteVarShareService,
+  ReactFeatureService,
   SecurityService,
   ThemeService,
   TicketService
@@ -431,7 +432,8 @@ export class NotebookComponent extends 
MessageListenersManager implements OnInit
     private securityService: SecurityService,
     private router: Router,
     private titleService: Title,
-    private themeService: ThemeService
+    private themeService: ThemeService,
+    private reactFeature: ReactFeatureService
   ) {
     super(messageService);
   }
@@ -448,7 +450,7 @@ export class NotebookComponent extends 
MessageListenersManager implements OnInit
     this.activatedRoute.queryParamMap
       .pipe(startWith(this.activatedRoute.snapshot.queryParamMap), 
takeUntil(this.destroy$))
       .subscribe(data => {
-        this.useReactFooter = data.get('reactFooter') === 'true';
+        this.useReactFooter = this.reactFeature.isEnabled('paragraphFooter', 
data);
         this.cdr.markForCheck();
       });
     this.activatedRoute.params.pipe(takeUntil(this.destroy$), 
distinctUntilKeyChanged('noteId')).subscribe(() => {
diff --git 
a/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.html
 
b/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.html
index 78e05a2b2c..aa58141ea6 100644
--- 
a/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.html
+++ 
b/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.html
@@ -9,10 +9,12 @@
 ~ See the License for the specific language governing permissions and
 ~ limitations under the License.
 -->
-@if (useReact) {
-  <div #reactContainer></div>
+@if (useReact && !reactFailed) {
+  @if (paragraph) {
+    <div [zeppelin-react-mount]="'./PublishedParagraph'" 
[reactProps]="reactProps"></div>
+  }
 }
-@if (!useReact) {
+@if (!useReact || reactFailed) {
   <div>
     @if (paragraph) {
       <zeppelin-notebook-paragraph-dynamic-forms
diff --git 
a/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.ts
 
b/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.ts
index b1b05d44a7..94f9af7d94 100644
--- 
a/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.ts
+++ 
b/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.ts
@@ -13,8 +13,6 @@ import {
   ChangeDetectionStrategy,
   ChangeDetectorRef,
   Component,
-  ElementRef,
-  OnDestroy,
   QueryList,
   TemplateRef,
   ViewChild,
@@ -29,13 +27,12 @@ import {
   ParagraphItem,
   ParagraphIResultsMsgItem
 } from '@zeppelin/sdk';
-import { HeliumService, MessageService, NgZService, NoteStatusService } from 
'@zeppelin/services';
-import { RemoteContainer } from '@zeppelin/share';
+import { HeliumService, MessageService, NgZService, NoteStatusService, 
ReactFeatureService } from '@zeppelin/services';
 import { SpellResult } from '@zeppelin/spell';
 import { isNil } from 'lodash';
 import { NzModalService } from 'ng-zorro-antd/modal';
 import { NotebookParagraphResultComponent } from 
'../../share/result/result.component';
-import { environment } from '../../../../../environments/environment';
+import { ReactHostCallbacks, ReactProps } from '../../../../share/react-mount';
 
 @Component({
   selector: 'zeppelin-publish-paragraph',
@@ -44,20 +41,20 @@ import { environment } from 
'../../../../../environments/environment';
   changeDetection: ChangeDetectionStrategy.OnPush,
   standalone: false
 })
-export class PublishedParagraphComponent extends ParagraphBase implements 
Published, OnDestroy {
+export class PublishedParagraphComponent extends ParagraphBase implements 
Published {
   readonly [publishedSymbol] = true;
 
   noteId: string | null = null;
   paragraphId: string | null = null;
   previewCode: string = '';
   useReact = false;
+  // Separate from useReact (which is re-derived from the URL) so a failed 
remote stays degraded.
+  reactFailed = false;
   isLoading = true;
   error: string | null = null;
-  private unmountReact: (() => void) | null = null;
-  private reactScriptLoaded = false;
+  reactProps: ReactProps & ReactHostCallbacks = {};
 
   @ViewChild('codePreviewModal', { static: true }) codePreviewModal!: 
TemplateRef<void>;
-  @ViewChild('reactContainer', { static: false }) reactContainer!: 
ElementRef<HTMLDivElement>;
   @ViewChildren(NotebookParagraphResultComponent)
   notebookParagraphResultComponents!: 
QueryList<NotebookParagraphResultComponent>;
 
@@ -67,13 +64,14 @@ export class PublishedParagraphComponent extends 
ParagraphBase implements Publis
     private heliumService: HeliumService,
     private router: Router,
     private nzModalService: NzModalService,
+    private reactFeature: ReactFeatureService,
     noteStatusService: NoteStatusService,
     ngZService: NgZService,
     cdr: ChangeDetectorRef
   ) {
     super(messageService, noteStatusService, ngZService, cdr);
-    this.activatedRoute.queryParams.subscribe(queryParams => {
-      this.useReact = queryParams.react === 'true' || queryParams.react === '';
+    this.activatedRoute.queryParamMap.subscribe(params => {
+      this.useReact = this.reactFeature.isEnabled('publishedParagraph', 
params);
     });
 
     this.activatedRoute.params.subscribe(params => {
@@ -86,12 +84,6 @@ export class PublishedParagraphComponent extends 
ParagraphBase implements Publis
     });
   }
 
-  ngOnDestroy() {
-    if (this.useReact) {
-      this.cleanupReactWidget();
-    }
-  }
-
   @MessageListener(OP.NOTE)
   getNote(data: MessageReceiveDataTypeMap[OP.NOTE]) {
     const note = data.note;
@@ -101,11 +93,11 @@ export class PublishedParagraphComponent extends 
ParagraphBase implements Publis
         if (!this.paragraph.results) {
           this.showRunConfirmationModal();
         }
-        if (this.useReact) {
+        if (this.useReact && !this.reactFailed) {
           this.setResults(this.paragraph);
+          this.reactProps = this.buildReactProps(this.paragraph);
           this.isLoading = false;
           this.cdr.markForCheck();
-          this.loadReactWidget();
           return;
         }
 
@@ -161,12 +153,24 @@ export class PublishedParagraphComponent extends 
ParagraphBase implements Publis
   }
 
   updateParagraphResult(resultIndex: number, config: ParagraphConfigResult, 
result: ParagraphIResultsMsgItem): void {
+    // In React mode the Angular result components never render, so this query 
is empty.
+    // The remote is refreshed from updateParagraphObjectWhenUpdated instead.
     const resultComponent = 
this.notebookParagraphResultComponents.toArray()[resultIndex];
     if (resultComponent) {
       resultComponent.updateResult(config, result);
     }
   }
 
+  updateParagraphObjectWhenUpdated(newPara: ParagraphItem): void {
+    super.updateParagraphObjectWhenUpdated(newPara);
+    // A run pushes OP.PARAGRAPH, not OP.NOTE, so getNote does not rebuild the 
props.
+    // Do it here, once the base class has merged the new results into 
this.paragraph.
+    if (this.useReact && !this.reactFailed && this.paragraph) {
+      this.reactProps = this.buildReactProps(this.paragraph);
+      this.cdr.markForCheck();
+    }
+  }
+
   private showRunConfirmationModal(): void {
     if (!this.paragraph) {
       return;
@@ -204,75 +208,25 @@ export class PublishedParagraphComponent extends 
ParagraphBase implements Publis
     });
   }
 
-  /**
-   * Loads the React micro-frontend via Webpack Module Federation.
-   *
-   * Flow:
-   * 1. Loads remoteEntry.js once (skips on subsequent calls via 
`reactScriptLoaded` flag).
-   * 2. remoteEntry.js registers `window.reactApp` as a federation container.
-   * 3. `container.get('./PublishedParagraph')` returns a module with a 
`mount(el, props)` function.
-   * 4. `mount()` calls `createRoot()` and renders into the given element.
-   * 5. `mount()` returns an `unmount` function, stored for cleanup in 
`ngOnDestroy`.
-   *
-   * See `projects/zeppelin-react/README.md` for the full guide.
-   * Append `?react=true` to a published paragraph URL to activate.
-   */
-  private loadReactWidget() {
-    if (!this.reactContainer || !this.paragraph) {
-      return;
-    }
-
-    const loadModule = async () => {
-      const container: RemoteContainer | undefined = window.reactApp;
-      if (!container) {
-        throw new Error('window.reactApp not available');
+  private buildReactProps(paragraph: ParagraphItem): ReactProps & 
ReactHostCallbacks {
+    return {
+      paragraphId: this.paragraphId,
+      noteId: this.noteId,
+      results: paragraph.results?.msg,
+      config: paragraph.config?.results,
+      onError: (err: unknown) => {
+        console.error('[PublishedParagraph] React mount failed', err);
+        this.degradeToAngular(paragraph);
       }
-
-      const factory = await container.get<{ mount: (el: HTMLElement, props: 
unknown) => () => void }>(
-        './PublishedParagraph'
-      );
-      const { mount } = factory();
-
-      if (!mount || typeof mount !== 'function') {
-        throw new Error('mount function not found');
-      }
-
-      const mountPoint = this.reactContainer.nativeElement;
-      const props = {
-        paragraphId: this.paragraphId,
-        noteId: this.noteId,
-        results: this.paragraph?.results?.msg,
-        config: this.paragraph?.config?.results
-      };
-
-      this.unmountReact = mount(mountPoint, props);
-    };
-
-    if (this.reactScriptLoaded) {
-      loadModule();
-      return;
-    }
-
-    const script = document.createElement('script');
-    script.src = environment.reactRemoteEntryUrl;
-
-    script.onload = () => {
-      this.reactScriptLoaded = true;
-      loadModule();
     };
-
-    script.onerror = () => {
-      this.error = 'Failed to load React widget';
-      this.cdr.markForCheck();
-    };
-
-    document.head.appendChild(script);
   }
 
-  private cleanupReactWidget() {
-    if (this.unmountReact) {
-      this.unmountReact();
-      this.unmountReact = null;
-    }
+  private degradeToAngular(paragraph: ParagraphItem): void {
+    this.reactFailed = true;
+    this.error = 'Failed to load React widget';
+    // The React branch skipped the Angular init path (getNote), so run it 
before showing the fallback.
+    this.originalText = paragraph.text;
+    this.initializeDefault(paragraph.config, paragraph.settings);
+    this.cdr.markForCheck();
   }
 }
diff --git 
a/zeppelin-web-angular/src/app/pages/workspace/published/published.module.ts 
b/zeppelin-web-angular/src/app/pages/workspace/published/published.module.ts
index 236b9b2ac3..902ec430f4 100644
--- a/zeppelin-web-angular/src/app/pages/workspace/published/published.module.ts
+++ b/zeppelin-web-angular/src/app/pages/workspace/published/published.module.ts
@@ -12,12 +12,13 @@
 
 import { CommonModule } from '@angular/common';
 import { NgModule } from '@angular/core';
+import { ShareModule } from '@zeppelin/share';
 import { WorkspaceShareModule } from '../share/share.module';
 import { PublishedParagraphComponent } from './paragraph/paragraph.component';
 import { PublishedRoutingModule } from './published-ruoting.module';
 
 @NgModule({
   declarations: [PublishedParagraphComponent],
-  imports: [CommonModule, WorkspaceShareModule, PublishedRoutingModule]
+  imports: [CommonModule, ShareModule, WorkspaceShareModule, 
PublishedRoutingModule]
 })
 export class PublishedModule {}
diff --git a/zeppelin-web-angular/src/app/services/public-api.ts 
b/zeppelin-web-angular/src/app/services/public-api.ts
index a554dc9cf4..7a80b4d351 100644
--- a/zeppelin-web-angular/src/app/services/public-api.ts
+++ b/zeppelin-web-angular/src/app/services/public-api.ts
@@ -27,6 +27,7 @@ export * from './note-status.service';
 export * from './note-var-share.service';
 export * from './notebook-repos.service';
 export * from './notebook.service';
+export * from './react-feature.service';
 export * from './runtime-compiler.service';
 export * from './save-as.service';
 export * from './security.service';
diff --git a/zeppelin-web-angular/src/app/services/react-feature.service.ts 
b/zeppelin-web-angular/src/app/services/react-feature.service.ts
new file mode 100644
index 0000000000..0d79bd9e6e
--- /dev/null
+++ b/zeppelin-web-angular/src/app/services/react-feature.service.ts
@@ -0,0 +1,70 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Injectable } from '@angular/core';
+
+export type ReactSurface = 'publishedParagraph' | 'paragraphFooter';
+
+interface ReactSurfaceConfig {
+  queryParam: string;
+  defaultEnabled: boolean;
+}
+
+const SURFACES: Record<ReactSurface, ReactSurfaceConfig> = {
+  publishedParagraph: {
+    queryParam: 'react',
+    defaultEnabled: false
+  },
+  paragraphFooter: {
+    queryParam: 'reactFooter',
+    defaultEnabled: false
+  }
+};
+
+/**
+ * Satisfied by Angular's `ParamMap` and by any `Map`.
+ * Taking the source rather than an already-read value keeps the query-param 
name in SURFACES only,
+ * so renaming it cannot desync a call site.
+ */
+export interface FlagSource {
+  get(name: string): string | null | undefined;
+}
+
+@Injectable({ providedIn: 'root' })
+export class ReactFeatureService {
+  isEnabled(surface: ReactSurface, source?: FlagSource | null): boolean {
+    const config = SURFACES[surface];
+
+    const fromQuery = this.parseFlag(source?.get(config.queryParam));
+    if (fromQuery !== null) {
+      return fromQuery;
+    }
+
+    return config.defaultEnabled;
+  }
+
+  /**
+   * A bare flag (`?react`) or `=true` enables, `=false` disables. Anything 
else, including an absent flag, is unset.
+   */
+  private parseFlag(value: string | null | undefined): boolean | null {
+    if (value === undefined || value === null) {
+      return null;
+    }
+    if (value === 'true' || value === '') {
+      return true;
+    }
+    if (value === 'false') {
+      return false;
+    }
+    return null;
+  }
+}
diff --git 
a/zeppelin-web-angular/src/app/share/react-mount/react-mount-handle.ts 
b/zeppelin-web-angular/src/app/share/react-mount/react-mount-handle.ts
index aefeb58fe3..1285a0c492 100644
--- a/zeppelin-web-angular/src/app/share/react-mount/react-mount-handle.ts
+++ b/zeppelin-web-angular/src/app/share/react-mount/react-mount-handle.ts
@@ -30,15 +30,3 @@ export type ReactMountFn = (element: HTMLElement, props: 
ReactProps & ReactHostC
 export interface ReactExposedModule {
   mount: ReactMountFn;
 }
-
-/**
- * Legacy shape (used by ./PublishedParagraph until its follow-up
- * refactor): mount returns a bare unmount function.
- */
-export type LegacyMountFn = (element: HTMLElement, props: ReactProps) => () => 
void;
-
-export interface LegacyExposedModule {
-  mount: LegacyMountFn;
-}
-
-export type AnyExposedModule = ReactExposedModule | LegacyExposedModule;
diff --git 
a/zeppelin-web-angular/src/app/share/react-mount/react-mount.directive.ts 
b/zeppelin-web-angular/src/app/share/react-mount/react-mount.directive.ts
index 96ff0ee7d0..f981710bff 100644
--- a/zeppelin-web-angular/src/app/share/react-mount/react-mount.directive.ts
+++ b/zeppelin-web-angular/src/app/share/react-mount/react-mount.directive.ts
@@ -12,25 +12,7 @@
 
 import { Directive, ElementRef, Input, NgZone, OnChanges, OnDestroy, 
SimpleChanges } from '@angular/core';
 import { ReactRemoteLoaderService } from './react-remote-loader.service';
-import {
-  AnyExposedModule,
-  ReactExposedModule,
-  ReactHostCallbacks,
-  ReactMountHandle,
-  ReactProps
-} from './react-mount-handle';
-
-const isLegacyModule = (mod: AnyExposedModule, handleOrUnmount: unknown): 
handleOrUnmount is () => void => {
-  void mod;
-  return typeof handleOrUnmount === 'function';
-};
-
-const wrapLegacyHandle = (unmount: () => void): ReactMountHandle => ({
-  update: () => {
-    /* legacy modules don't support updates; no-op */
-  },
-  unmount
-});
+import { ReactExposedModule, ReactHostCallbacks, ReactMountHandle, ReactProps 
} from './react-mount-handle';
 
 @Directive({
   selector: '[zeppelin-react-mount]',
@@ -101,18 +83,13 @@ export class ReactMountDirective implements OnChanges, 
OnDestroy {
     this.loading = true;
     const moduleKey = this.module;
     try {
-      const mod = await this.loader.loadModule<AnyExposedModule>(moduleKey);
+      const mod = await this.loader.loadModule<ReactExposedModule>(moduleKey);
       if (this.destroyed) {
         return;
       }
       this.ngZone.runOutsideAngular(() => {
         try {
-          const returned = (mod as 
ReactExposedModule).mount(this.host.nativeElement, this.latestProps);
-          if (isLegacyModule(mod, returned)) {
-            this.handle = wrapLegacyHandle(returned as unknown as () => void);
-          } else {
-            this.handle = returned as ReactMountHandle;
-          }
+          this.handle = mod.mount(this.host.nativeElement, this.latestProps);
           this.mountedModule = moduleKey;
         } catch (err) {
           this.handle = null;
diff --git 
a/zeppelin-web-angular/src/app/share/react-mount/react-remote-loader.service.ts 
b/zeppelin-web-angular/src/app/share/react-mount/react-remote-loader.service.ts
index 2c903f529a..7e992dc2d4 100644
--- 
a/zeppelin-web-angular/src/app/share/react-mount/react-remote-loader.service.ts
+++ 
b/zeppelin-web-angular/src/app/share/react-mount/react-remote-loader.service.ts
@@ -12,9 +12,9 @@
 
 import { Injectable } from '@angular/core';
 import { environment } from '../../../environments/environment';
-import { AnyExposedModule } from './react-mount-handle';
+import { ReactExposedModule } from './react-mount-handle';
 
-export interface RemoteContainer {
+interface RemoteContainer {
   get<T>(key: string): Promise<() => T>;
   init?: (shareScope: unknown) => Promise<void>;
 }
@@ -28,7 +28,7 @@ declare global {
 @Injectable({ providedIn: 'root' })
 export class ReactRemoteLoaderService {
   private containerPromise: Promise<RemoteContainer> | null = null;
-  private readonly modulePromises = new Map<string, 
Promise<AnyExposedModule>>();
+  private readonly modulePromises = new Map<string, 
Promise<ReactExposedModule>>();
 
   loadContainer(): Promise<RemoteContainer> {
     if (this.containerPromise) {
@@ -90,7 +90,7 @@ export class ReactRemoteLoaderService {
     return this.containerPromise;
   }
 
-  loadModule<T extends AnyExposedModule>(exposedKey: string): Promise<T> {
+  loadModule<T extends ReactExposedModule>(exposedKey: string): Promise<T> {
     const cached = this.modulePromises.get(exposedKey);
     if (cached) {
       return cached as Promise<T>;
diff --git a/zeppelin-web-angular/webpack.config.js 
b/zeppelin-web-angular/webpack.config.js
index 0e73411a6a..03379409c8 100644
--- a/zeppelin-web-angular/webpack.config.js
+++ b/zeppelin-web-angular/webpack.config.js
@@ -11,8 +11,6 @@
  */
 
 const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
-const webpack = require('@angular-devkit/build-angular/node_modules/webpack');
-const ModuleFederationPlugin = webpack.container.ModuleFederationPlugin;
 
 const MONACO_DIR = /monaco-editor[\\/]/;
 
@@ -72,14 +70,6 @@ module.exports = (config, options, targetOptions) => {
   });
 
   config.plugins = config.plugins || [];
-  config.plugins.push(
-    new ModuleFederationPlugin({
-      name: 'shell',
-      remotes: {
-        reactApp: 'reactApp@http://localhost:3001/remoteEntry.js'
-      }
-    })
-  );
   config.plugins.push(
     new MonacoWebpackPlugin({
       languages: [

Reply via email to