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

voidmatcha pushed a commit to branch ZEPPELIN-6670-shared-core-contract
in repository https://gitbox.apache.org/repos/asf/zeppelin.git

commit a7d77d3aa8293ffc365b45becf24cdfb1d32f8f5
Author: YONGJAE LEE <[email protected]>
AuthorDate: Mon Aug 31 21:03:32 2026 +0900

    [ZEPPELIN-6670] Add Shared Notebook Core mount contract
---
 zeppelin-web-angular/angular.json                  |  21 +++
 zeppelin-web-angular/eslint.config.js              |  11 +-
 zeppelin-web-angular/package.json                  |   5 +-
 zeppelin-web-angular/pom.xml                       |  24 +++
 .../zeppelin-notebook-core/ng-package.json         |   7 +
 .../projects/zeppelin-notebook-core/package.json   |   8 +
 .../src/host-remote-contract.spec.ts               |  62 +++++++
 .../src/host-remote-contract.ts}                   |  20 +-
 .../src/import-boundary.spec.ts                    | 206 +++++++++++++++++++++
 .../src/public-api.ts}                             |   4 +-
 .../projects/zeppelin-notebook-core/tsconfig.json  |  11 ++
 .../zeppelin-notebook-core/tsconfig.spec.json      |   9 +
 .../projects/zeppelin-react/package-lock.json      |  12 ++
 .../projects/zeppelin-react/package.json           |   1 +
 .../projects/zeppelin-react/src/main.ts            |   1 +
 .../src/{main.ts => notebookCoreContract.ts}       |   4 +-
 .../projects/zeppelin-react/tsconfig.json          |   2 +
 .../tsconfig.notebook-core.dist.json               |   8 +
 .../zeppelin-react/tsconfig.notebook-core.json     |   5 +
 .../projects/zeppelin-react/webpack.config.js      |   1 +
 zeppelin-web-angular/src/tsconfig.spec.json        |   2 +
 zeppelin-web-angular/tsconfig.base.json            |   2 +
 .../main.ts => vitest.notebook-core.config.mts}    |  11 +-
 23 files changed, 421 insertions(+), 16 deletions(-)

diff --git a/zeppelin-web-angular/angular.json 
b/zeppelin-web-angular/angular.json
index fa7d20ec48..0280764be6 100644
--- a/zeppelin-web-angular/angular.json
+++ b/zeppelin-web-angular/angular.json
@@ -177,6 +177,27 @@
         }
       }
     },
+    "zeppelin-notebook-core": {
+      "projectType": "library",
+      "root": "projects/zeppelin-notebook-core",
+      "sourceRoot": "projects/zeppelin-notebook-core/src",
+      "prefix": "lib",
+      "architect": {
+        "build": {
+          "builder": "@angular-devkit/build-angular:ng-packagr",
+          "options": {
+            "tsConfig": "projects/zeppelin-notebook-core/tsconfig.json",
+            "project": "projects/zeppelin-notebook-core/ng-package.json"
+          }
+        },
+        "lint": {
+          "builder": "@angular-eslint/builder:lint",
+          "options": {
+            "lintFilePatterns": ["projects/zeppelin-notebook-core/**/*.ts"]
+          }
+        }
+      }
+    },
     "zeppelin-sdk": {
       "projectType": "library",
       "root": "projects/zeppelin-sdk",
diff --git a/zeppelin-web-angular/eslint.config.js 
b/zeppelin-web-angular/eslint.config.js
index 23b87ff73a..917aef9c69 100644
--- a/zeppelin-web-angular/eslint.config.js
+++ b/zeppelin-web-angular/eslint.config.js
@@ -152,7 +152,11 @@ module.exports = tseslint.config(
   },
   {
     // Library projects publish under the `lib` selector prefix, not 
`zeppelin`.
-    files: ['projects/zeppelin-sdk/**/*.ts', 
'projects/zeppelin-visualization/**/*.ts'],
+    files: [
+      'projects/zeppelin-notebook-core/**/*.ts',
+      'projects/zeppelin-sdk/**/*.ts',
+      'projects/zeppelin-visualization/**/*.ts'
+    ],
     rules: {
       '@angular-eslint/component-selector': ['error', { type: 'element', 
prefix: 'lib', style: 'kebab-case' }],
       '@angular-eslint/directive-selector': ['error', { type: 'attribute', 
prefix: 'lib', style: 'camelCase' }]
@@ -163,8 +167,9 @@ module.exports = tseslint.config(
     // *.spec.ts. Point type-aware linting at the spec program explicitly.
     files: [
       'src/**/*.spec.ts',
-      'projects/zeppelin-{sdk,visualization}/**/*.spec.ts',
+      'projects/zeppelin-{notebook-core,sdk,visualization}/**/*.spec.ts',
       'test/test-setup.ts',
+      'vitest.notebook-core.config.mts',
       'vitest.shell.config.mts'
     ],
     languageOptions: {
@@ -176,7 +181,7 @@ module.exports = tseslint.config(
   },
   {
     // Catch specs that cannot fail, as eslint-plugin-playwright does for e2e.
-    files: ['src/**/*.spec.ts', 
'projects/zeppelin-{sdk,visualization}/**/*.spec.ts'],
+    files: ['src/**/*.spec.ts', 
'projects/zeppelin-{notebook-core,sdk,visualization}/**/*.spec.ts'],
     plugins: { vitest },
     rules: {
       'vitest/expect-expect': 'error',
diff --git a/zeppelin-web-angular/package.json 
b/zeppelin-web-angular/package.json
index 8acdda44a6..10b0123122 100644
--- a/zeppelin-web-angular/package.json
+++ b/zeppelin-web-angular/package.json
@@ -11,7 +11,8 @@
     "build": "npm run build:projects && npm run build:react && npm run 
build:angular",
     "build:angular": "ng build --configuration production",
     "build:react": "cd projects/zeppelin-react && npm run build",
-    "build:projects": "npm run build-project:sdk && npm run build-project:vis",
+    "build:projects": "npm run build-project:sdk && npm run 
build-project:notebook-core && npm run build-project:vis",
+    "build-project:notebook-core": "ng build --project zeppelin-notebook-core",
     "build-project:sdk": "ng build --project zeppelin-sdk",
     "check:websocket-contract": "node --test 
scripts/check-websocket-contract.test.js && node 
scripts/check-websocket-contract.js",
     "build-project:vis": "ng build --project zeppelin-visualization",
@@ -19,6 +20,8 @@
     "lint:fix": "cross-env NODE_OPTIONS='--max-old-space-size=8192' ng lint 
--fix && npm run lint:fix:react && prettier --write 
\"**/*.{ts,tsx,mts,js,json,css,html}\"",
     "lint:react": "cd projects/zeppelin-react && npm run lint",
     "lint:fix:react": "cd projects/zeppelin-react && npm run lint:fix",
+    "typecheck:notebook-core": "tsc -p 
projects/zeppelin-notebook-core/tsconfig.json --noEmit && tsc -p 
projects/zeppelin-notebook-core/tsconfig.spec.json --noEmit && npm run 
build-project:notebook-core && tsc -p 
projects/zeppelin-react/tsconfig.notebook-core.dist.json --noEmit && tsc -p 
projects/zeppelin-react/tsconfig.notebook-core.json --noEmit",
+    "test:notebook-core": "vitest run --config 
vitest.notebook-core.config.mts",
     "test:shell": "vitest run --config vitest.shell.config.mts",
     "test:eslint-rules": "node --test eslint-rules/",
     "e2e": "playwright test",
diff --git a/zeppelin-web-angular/pom.xml b/zeppelin-web-angular/pom.xml
index 3f2fee17ff..6d0123fc7a 100644
--- a/zeppelin-web-angular/pom.xml
+++ b/zeppelin-web-angular/pom.xml
@@ -129,6 +129,30 @@
             </configuration>
           </execution>
 
+          <execution>
+            <id>npm typecheck notebook core</id>
+            <goals>
+              <goal>npm</goal>
+            </goals>
+            <phase>test</phase>
+            <configuration>
+              <skip>${skipTests}</skip>
+              <arguments>run typecheck:notebook-core</arguments>
+            </configuration>
+          </execution>
+
+          <execution>
+            <id>npm test notebook core</id>
+            <goals>
+              <goal>npm</goal>
+            </goals>
+            <phase>test</phase>
+            <configuration>
+              <skip>${skipTests}</skip>
+              <arguments>run test:notebook-core</arguments>
+            </configuration>
+          </execution>
+
           <execution>
             <id>npm e2e</id>
             <goals>
diff --git 
a/zeppelin-web-angular/projects/zeppelin-notebook-core/ng-package.json 
b/zeppelin-web-angular/projects/zeppelin-notebook-core/ng-package.json
new file mode 100644
index 0000000000..b7ef11c43a
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-notebook-core/ng-package.json
@@ -0,0 +1,7 @@
+{
+  "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
+  "dest": "../../dist/zeppelin-notebook-core",
+  "lib": {
+    "entryFile": "src/public-api.ts"
+  }
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-notebook-core/package.json 
b/zeppelin-web-angular/projects/zeppelin-notebook-core/package.json
new file mode 100644
index 0000000000..5431edbe97
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-notebook-core/package.json
@@ -0,0 +1,8 @@
+{
+  "name": "@zeppelin/notebook-core",
+  "version": "0.0.1",
+  "sideEffects": false,
+  "dependencies": {
+    "tslib": "^2.0.0"
+  }
+}
diff --git 
a/zeppelin-web-angular/projects/zeppelin-notebook-core/src/host-remote-contract.spec.ts
 
b/zeppelin-web-angular/projects/zeppelin-notebook-core/src/host-remote-contract.spec.ts
new file mode 100644
index 0000000000..6e1c072877
--- /dev/null
+++ 
b/zeppelin-web-angular/projects/zeppelin-notebook-core/src/host-remote-contract.spec.ts
@@ -0,0 +1,62 @@
+// @vitest-environment node
+
+/*
+ * 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 { describe, expect, it } from 'vitest';
+
+import type {
+  NotebookCorePort,
+  NotebookCoreRemoteProps,
+  NotebookCoreSnapshot,
+  NotebookCoreSnapshotListener
+} from './public-api';
+
+const fakeCorePort = (initialSnapshot: NotebookCoreSnapshot) => {
+  let snapshot = initialSnapshot;
+  const listeners = new Set<NotebookCoreSnapshotListener>();
+  const core: NotebookCorePort = {
+    getSnapshot: () => snapshot,
+    subscribe: listener => {
+      listeners.add(listener);
+      return () => listeners.delete(listener);
+    }
+  };
+
+  return {
+    core,
+    publish: (nextSnapshot: NotebookCoreSnapshot) => {
+      snapshot = nextSnapshot;
+      for (const listener of listeners) {
+        listener();
+      }
+    }
+  };
+};
+
+describe('notebook core host and remote contract', () => {
+  it('lets host and remote share one read-only snapshot source through 
getSnapshot and subscribe', () => {
+    const host = fakeCorePort({ noteId: '2A94M5J1Z', revisionId: null });
+    const remoteProps: NotebookCoreRemoteProps = { core: host.core };
+    const snapshots: unknown[] = [];
+
+    expect(remoteProps.core).toBe(host.core);
+
+    const unsubscribe = remoteProps.core.subscribe(() => 
snapshots.push(remoteProps.core.getSnapshot()));
+    host.publish({ noteId: '2A94M5J1Z', revisionId: 'rev-1' });
+    unsubscribe();
+    host.publish({ noteId: '2A94M5J1Z', revisionId: 'rev-2' });
+
+    expect(snapshots).toEqual([{ noteId: '2A94M5J1Z', revisionId: 'rev-1' }]);
+    expect(remoteProps.core.getSnapshot()).toEqual({ noteId: '2A94M5J1Z', 
revisionId: 'rev-2' });
+  });
+});
diff --git a/zeppelin-web-angular/projects/zeppelin-react/src/main.ts 
b/zeppelin-web-angular/projects/zeppelin-notebook-core/src/host-remote-contract.ts
similarity index 54%
copy from zeppelin-web-angular/projects/zeppelin-react/src/main.ts
copy to 
zeppelin-web-angular/projects/zeppelin-notebook-core/src/host-remote-contract.ts
index ce7edc883f..9a62c63e67 100644
--- a/zeppelin-web-angular/projects/zeppelin-react/src/main.ts
+++ 
b/zeppelin-web-angular/projects/zeppelin-notebook-core/src/host-remote-contract.ts
@@ -10,6 +10,20 @@
  * limitations under the License.
  */
 
-export { ConfigurationTable, mount as mountConfigurationTable } from 
'./pages/ConfigurationTable';
-export { PublishedParagraph, mount } from './pages/PublishedParagraph';
-export { ParagraphFooter, mount as mountParagraphFooter } from 
'./components/paragraph/ParagraphFooter';
+export type NotebookCoreSnapshot = Readonly<{
+  noteId: string;
+  revisionId: string | null;
+}>;
+
+export type NotebookCoreUnsubscribe = () => void;
+
+export type NotebookCoreSnapshotListener = () => void;
+
+export type NotebookCorePort = Readonly<{
+  getSnapshot: () => NotebookCoreSnapshot;
+  subscribe: (listener: NotebookCoreSnapshotListener) => 
NotebookCoreUnsubscribe;
+}>;
+
+export type NotebookCoreRemoteProps = Readonly<{
+  core: NotebookCorePort;
+}>;
diff --git 
a/zeppelin-web-angular/projects/zeppelin-notebook-core/src/import-boundary.spec.ts
 
b/zeppelin-web-angular/projects/zeppelin-notebook-core/src/import-boundary.spec.ts
new file mode 100644
index 0000000000..800ac1c9a4
--- /dev/null
+++ 
b/zeppelin-web-angular/projects/zeppelin-notebook-core/src/import-boundary.spec.ts
@@ -0,0 +1,206 @@
+// @vitest-environment node
+
+/*
+ * 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 { readdirSync, readFileSync, statSync } from 'node:fs';
+import { join, resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+import ts from 'typescript';
+import { describe, expect, it } from 'vitest';
+
+const sourceRoot = fileURLToPath(new URL('.', import.meta.url));
+const zeppelinWebAngularRoot = resolve(sourceRoot, '../../..');
+const reactNotebookCoreBoundaryFiles = [
+  resolve(zeppelinWebAngularRoot, 'projects/zeppelin-react/src/main.ts'),
+  resolve(zeppelinWebAngularRoot, 
'projects/zeppelin-react/src/notebookCoreContract.ts')
+];
+const forbiddenModulePrefixes = [
+  '@angular/',
+  '@zeppelin/sdk',
+  'react',
+  'react-dom',
+  'react-redux',
+  'react-router',
+  'react-router-dom',
+  'rxjs',
+  'axios'
+];
+const forbiddenReactNotebookCoreConsumerModulePrefixes = [
+  '@angular/common/http',
+  '@zeppelin/sdk',
+  'axios',
+  'rxjs/webSocket'
+];
+const forbiddenGlobals = new Set(['fetch', 'WebSocket', 'XMLHttpRequest']);
+const transportGlobalOwners = new Set(['globalThis', 'window']);
+
+const sourceFiles = (dir: string): string[] =>
+  readdirSync(dir).flatMap(entry => {
+    const path = join(dir, entry);
+    if (statSync(path).isDirectory()) {
+      return sourceFiles(path);
+    }
+    return isCheckedSourceFile(path) ? [path] : [];
+  });
+
+const checkedSourceExtensions = ['.ts', '.tsx', '.mts', '.cts'];
+
+const isCheckedSourceFile = (path: string): boolean => {
+  return checkedSourceExtensions.some(extension => path.endsWith(extension)) 
&& !/\.spec\.[cm]?tsx?$/.test(path);
+};
+
+describe('notebook core import boundary', () => {
+  it('stays framework-neutral and transport-neutral', () => {
+    const violations = sourceFiles(sourceRoot).flatMap(path => {
+      const source = readFileSync(path, 'utf8');
+      return findViolations(path, source);
+    });
+
+    expect(violations).toEqual([]);
+  });
+
+  it('keeps the React notebook core adapter independent from Zeppelin 
transport implementations', () => {
+    const violations = reactNotebookCoreBoundaryFiles.flatMap(path => {
+      const source = readFileSync(path, 'utf8');
+      return findViolations(path, source, 
forbiddenReactNotebookCoreConsumerModulePrefixes);
+    });
+
+    expect(formatViolations(violations)).toEqual([]);
+  });
+
+  it('ignores forbidden words in comments and string values', () => {
+    const source = `// React may render this later.\nexport const note = 
'fetch over WebSocket';`;
+
+    expect(findViolations('comment-fixture.ts', source)).toEqual([]);
+  });
+
+  it('rejects static, dynamic and direct transport dependencies', () => {
+    const source = [
+      `import type { OP } from '@zeppelin/sdk';`,
+      `export { useMemo } from 'react';`,
+      `type LeakedMessage = import('@zeppelin/sdk').Message;`,
+      `import { createRoot } from 'react-dom/client';`,
+      `import { Provider } from 'react-redux';`,
+      `const router = () => import('react-router-dom');`,
+      `const load = () => import('rxjs/operators');`,
+      `const request = () => fetch('/api/notebook');`,
+      `const socket = new globalThis.WebSocket('/ws');`,
+      `const xhr = new window.XMLHttpRequest();`
+    ].join('\n');
+
+    expect(findViolations('violation-fixture.ts', source)).toEqual([
+      'violation-fixture.ts: import @zeppelin/sdk',
+      'violation-fixture.ts: import react',
+      'violation-fixture.ts: import @zeppelin/sdk',
+      'violation-fixture.ts: import react-dom/client',
+      'violation-fixture.ts: import react-redux',
+      'violation-fixture.ts: import react-router-dom',
+      'violation-fixture.ts: import rxjs/operators',
+      'violation-fixture.ts: global fetch',
+      'violation-fixture.ts: global WebSocket',
+      'violation-fixture.ts: global XMLHttpRequest'
+    ]);
+  });
+});
+
+const findViolations = (
+  path: string,
+  source: string,
+  forbiddenPrefixes: readonly string[] = forbiddenModulePrefixes
+): string[] => {
+  const sourceFile = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, 
true, getScriptKind(path));
+  const violations: string[] = [];
+
+  const visit = (node: ts.Node): void => {
+    const moduleSpecifier = getModuleSpecifier(node);
+    if (moduleSpecifier && forbiddenPrefixes.some(prefix => 
matchesModulePrefix(moduleSpecifier, prefix))) {
+      violations.push(`${path}: import ${moduleSpecifier}`);
+    }
+
+    const forbiddenGlobal = getForbiddenTransportGlobalName(node);
+    if (forbiddenGlobal) {
+      violations.push(`${path}: global ${forbiddenGlobal}`);
+    }
+
+    ts.forEachChild(node, visit);
+  };
+
+  visit(sourceFile);
+  return violations;
+};
+
+const formatViolations = (violations: readonly string[]): string[] => {
+  return violations.map(violation => 
violation.replace(`${zeppelinWebAngularRoot}/`, ''));
+};
+
+const getScriptKind = (path: string): ts.ScriptKind => {
+  if (path.endsWith('.tsx')) {
+    return ts.ScriptKind.TSX;
+  }
+  return ts.ScriptKind.TS;
+};
+
+const getModuleSpecifier = (node: ts.Node): string | null => {
+  if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && 
node.moduleSpecifier) {
+    return ts.isStringLiteral(node.moduleSpecifier) ? 
node.moduleSpecifier.text : null;
+  }
+  if (ts.isImportTypeNode(node) && ts.isLiteralTypeNode(node.argument) && 
ts.isStringLiteral(node.argument.literal)) {
+    return node.argument.literal.text;
+  }
+  if (ts.isImportEqualsDeclaration(node) && 
ts.isExternalModuleReference(node.moduleReference)) {
+    const expression = node.moduleReference.expression;
+    return expression && ts.isStringLiteral(expression) ? expression.text : 
null;
+  }
+  if (
+    ts.isCallExpression(node) &&
+    node.arguments.length === 1 &&
+    ts.isStringLiteral(node.arguments[0]) &&
+    (node.expression.kind === ts.SyntaxKind.ImportKeyword ||
+      (ts.isIdentifier(node.expression) && node.expression.text === 'require'))
+  ) {
+    return node.arguments[0].text;
+  }
+  return null;
+};
+
+const matchesModulePrefix = (moduleSpecifier: string, prefix: string): boolean 
=> {
+  return moduleSpecifier === prefix || 
moduleSpecifier.startsWith(prefix.endsWith('/') ? prefix : `${prefix}/`);
+};
+
+const isRuntimeIdentifier = (node: ts.Identifier): boolean => {
+  const parent = node.parent;
+  return !(
+    (ts.isPropertyAccessExpression(parent) && parent.name === node) ||
+    (ts.isPropertyAssignment(parent) && parent.name === node) ||
+    (ts.isMethodDeclaration(parent) && parent.name === node) ||
+    (ts.isPropertyDeclaration(parent) && parent.name === node) ||
+    (ts.isTypeReferenceNode(parent) && parent.typeName === node)
+  );
+};
+
+const getForbiddenTransportGlobalName = (node: ts.Node): string | null => {
+  if (ts.isIdentifier(node) && forbiddenGlobals.has(node.text) && 
isRuntimeIdentifier(node)) {
+    return node.text;
+  }
+  if (
+    ts.isPropertyAccessExpression(node) &&
+    ts.isIdentifier(node.name) &&
+    forbiddenGlobals.has(node.name.text) &&
+    ts.isIdentifier(node.expression) &&
+    transportGlobalOwners.has(node.expression.text)
+  ) {
+    return node.name.text;
+  }
+  return null;
+};
diff --git a/zeppelin-web-angular/projects/zeppelin-react/src/main.ts 
b/zeppelin-web-angular/projects/zeppelin-notebook-core/src/public-api.ts
similarity index 66%
copy from zeppelin-web-angular/projects/zeppelin-react/src/main.ts
copy to zeppelin-web-angular/projects/zeppelin-notebook-core/src/public-api.ts
index ce7edc883f..0d82f21dde 100644
--- a/zeppelin-web-angular/projects/zeppelin-react/src/main.ts
+++ b/zeppelin-web-angular/projects/zeppelin-notebook-core/src/public-api.ts
@@ -10,6 +10,4 @@
  * limitations under the License.
  */
 
-export { ConfigurationTable, mount as mountConfigurationTable } from 
'./pages/ConfigurationTable';
-export { PublishedParagraph, mount } from './pages/PublishedParagraph';
-export { ParagraphFooter, mount as mountParagraphFooter } from 
'./components/paragraph/ParagraphFooter';
+export * from './host-remote-contract';
diff --git a/zeppelin-web-angular/projects/zeppelin-notebook-core/tsconfig.json 
b/zeppelin-web-angular/projects/zeppelin-notebook-core/tsconfig.json
new file mode 100644
index 0000000000..6992356b4d
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-notebook-core/tsconfig.json
@@ -0,0 +1,11 @@
+{
+  "extends": "../../tsconfig.base.json",
+  "compilerOptions": {
+    "outDir": "../../out-tsc/lib",
+    "target": "es2020",
+    "declaration": true,
+    "inlineSources": true,
+    "types": []
+  },
+  "exclude": ["**/*.spec.ts"]
+}
diff --git 
a/zeppelin-web-angular/projects/zeppelin-notebook-core/tsconfig.spec.json 
b/zeppelin-web-angular/projects/zeppelin-notebook-core/tsconfig.spec.json
new file mode 100644
index 0000000000..397347a76a
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-notebook-core/tsconfig.spec.json
@@ -0,0 +1,9 @@
+{
+  "extends": "./tsconfig.json",
+  "compilerOptions": {
+    "outDir": "../../out-tsc/spec/notebook-core",
+    "types": ["node"]
+  },
+  "include": ["src/**/*.spec.ts"],
+  "exclude": []
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-react/package-lock.json 
b/zeppelin-web-angular/projects/zeppelin-react/package-lock.json
index c988c10777..d1f92ce00c 100644
--- a/zeppelin-web-angular/projects/zeppelin-react/package-lock.json
+++ b/zeppelin-web-angular/projects/zeppelin-react/package-lock.json
@@ -9,6 +9,7 @@
       "version": "0.0.1",
       "dependencies": {
         "@ant-design/icons": "5.4.0",
+        "@zeppelin/notebook-core": "file:../zeppelin-notebook-core",
         "@zeppelin/sdk": "file:../zeppelin-sdk",
         "ansi-to-react": "6.2.6",
         "antd": "5.21.0",
@@ -52,6 +53,13 @@
         "webpack-dev-server": "6.0.0"
       }
     },
+    "../zeppelin-notebook-core": {
+      "name": "@zeppelin/notebook-core",
+      "version": "0.0.1",
+      "dependencies": {
+        "tslib": "^2.0.0"
+      }
+    },
     "../zeppelin-sdk": {
       "name": "@zeppelin/sdk",
       "version": "0.0.1",
@@ -2733,6 +2741,10 @@
       "dev": true,
       "license": "Apache-2.0"
     },
+    "node_modules/@zeppelin/notebook-core": {
+      "resolved": "../zeppelin-notebook-core",
+      "link": true
+    },
     "node_modules/@zeppelin/sdk": {
       "resolved": "../zeppelin-sdk",
       "link": true
diff --git a/zeppelin-web-angular/projects/zeppelin-react/package.json 
b/zeppelin-web-angular/projects/zeppelin-react/package.json
index c6441af70b..3eb2781ed0 100644
--- a/zeppelin-web-angular/projects/zeppelin-react/package.json
+++ b/zeppelin-web-angular/projects/zeppelin-react/package.json
@@ -15,6 +15,7 @@
   },
   "dependencies": {
     "@ant-design/icons": "5.4.0",
+    "@zeppelin/notebook-core": "file:../zeppelin-notebook-core",
     "@zeppelin/sdk": "file:../zeppelin-sdk",
     "ansi-to-react": "6.2.6",
     "highlight.js": "^9.15.8",
diff --git a/zeppelin-web-angular/projects/zeppelin-react/src/main.ts 
b/zeppelin-web-angular/projects/zeppelin-react/src/main.ts
index ce7edc883f..66f7f95a0a 100644
--- a/zeppelin-web-angular/projects/zeppelin-react/src/main.ts
+++ b/zeppelin-web-angular/projects/zeppelin-react/src/main.ts
@@ -13,3 +13,4 @@
 export { ConfigurationTable, mount as mountConfigurationTable } from 
'./pages/ConfigurationTable';
 export { PublishedParagraph, mount } from './pages/PublishedParagraph';
 export { ParagraphFooter, mount as mountParagraphFooter } from 
'./components/paragraph/ParagraphFooter';
+export type { NotebookCoreRemoteProps } from './notebookCoreContract';
diff --git a/zeppelin-web-angular/projects/zeppelin-react/src/main.ts 
b/zeppelin-web-angular/projects/zeppelin-react/src/notebookCoreContract.ts
similarity index 66%
copy from zeppelin-web-angular/projects/zeppelin-react/src/main.ts
copy to zeppelin-web-angular/projects/zeppelin-react/src/notebookCoreContract.ts
index ce7edc883f..1eceaf420c 100644
--- a/zeppelin-web-angular/projects/zeppelin-react/src/main.ts
+++ b/zeppelin-web-angular/projects/zeppelin-react/src/notebookCoreContract.ts
@@ -10,6 +10,4 @@
  * limitations under the License.
  */
 
-export { ConfigurationTable, mount as mountConfigurationTable } from 
'./pages/ConfigurationTable';
-export { PublishedParagraph, mount } from './pages/PublishedParagraph';
-export { ParagraphFooter, mount as mountParagraphFooter } from 
'./components/paragraph/ParagraphFooter';
+export type { NotebookCoreRemoteProps } from '@zeppelin/notebook-core';
diff --git a/zeppelin-web-angular/projects/zeppelin-react/tsconfig.json 
b/zeppelin-web-angular/projects/zeppelin-react/tsconfig.json
index 3dba52c585..5c4f37847b 100644
--- a/zeppelin-web-angular/projects/zeppelin-react/tsconfig.json
+++ b/zeppelin-web-angular/projects/zeppelin-react/tsconfig.json
@@ -19,6 +19,8 @@
     "baseUrl": "src",
     "paths": {
       "@/*": ["./*"],
+      "@zeppelin/notebook-core": 
["../../zeppelin-notebook-core/src/public-api.ts"],
+      "@zeppelin/notebook-core/*": ["../../zeppelin-notebook-core/src/*"],
       "@zeppelin/sdk": ["../../zeppelin-sdk/src"],
       "@zeppelin/sdk/*": ["../../zeppelin-sdk/src/*"]
     }
diff --git 
a/zeppelin-web-angular/projects/zeppelin-react/tsconfig.notebook-core.dist.json 
b/zeppelin-web-angular/projects/zeppelin-react/tsconfig.notebook-core.dist.json
new file mode 100644
index 0000000000..12ee26106c
--- /dev/null
+++ 
b/zeppelin-web-angular/projects/zeppelin-react/tsconfig.notebook-core.dist.json
@@ -0,0 +1,8 @@
+{
+  "extends": "./tsconfig.notebook-core.json",
+  "compilerOptions": {
+    "paths": {
+      "@zeppelin/notebook-core": ["../../../dist/zeppelin-notebook-core"]
+    }
+  }
+}
diff --git 
a/zeppelin-web-angular/projects/zeppelin-react/tsconfig.notebook-core.json 
b/zeppelin-web-angular/projects/zeppelin-react/tsconfig.notebook-core.json
new file mode 100644
index 0000000000..8516d2cf5f
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-react/tsconfig.notebook-core.json
@@ -0,0 +1,5 @@
+{
+  "extends": "./tsconfig.json",
+  "include": ["src/notebookCoreContract.ts"],
+  "exclude": []
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-react/webpack.config.js 
b/zeppelin-web-angular/projects/zeppelin-react/webpack.config.js
index d7de57b3d9..fc58f1af76 100644
--- a/zeppelin-web-angular/projects/zeppelin-react/webpack.config.js
+++ b/zeppelin-web-angular/projects/zeppelin-react/webpack.config.js
@@ -39,6 +39,7 @@ module.exports = (_env, argv) => {
       modules: ['node_modules', path.resolve(__dirname, '../../node_modules')],
       alias: {
         '@': path.resolve(__dirname, 'src'),
+        '@zeppelin/notebook-core': path.resolve(__dirname, 
'../zeppelin-notebook-core/src/public-api.ts'),
         '@zeppelin/sdk': path.resolve(__dirname, '../zeppelin-sdk/src')
       }
     },
diff --git a/zeppelin-web-angular/src/tsconfig.spec.json 
b/zeppelin-web-angular/src/tsconfig.spec.json
index e4233dd1d4..1a42d73b0c 100644
--- a/zeppelin-web-angular/src/tsconfig.spec.json
+++ b/zeppelin-web-angular/src/tsconfig.spec.json
@@ -6,9 +6,11 @@
   },
   "include": [
     "**/*.spec.ts",
+    "../projects/zeppelin-notebook-core/**/*.spec.ts",
     "../projects/zeppelin-sdk/**/*.spec.ts",
     "../projects/zeppelin-visualization/**/*.spec.ts",
     "../test/test-setup.ts",
+    "../vitest.notebook-core.config.mts",
     "../vitest.shell.config.mts"
   ],
   "exclude": []
diff --git a/zeppelin-web-angular/tsconfig.base.json 
b/zeppelin-web-angular/tsconfig.base.json
index 7fee7c8e79..ee189af234 100644
--- a/zeppelin-web-angular/tsconfig.base.json
+++ b/zeppelin-web-angular/tsconfig.base.json
@@ -6,6 +6,8 @@
       "@zeppelin/*": ["./src/app/*", "./src/environments/*"],
       "@zeppelin/visualization": ["dist/zeppelin-visualization"],
       "@zeppelin/visualization/*": ["dist/zeppelin-visualization/*"],
+      "@zeppelin/notebook-core": ["dist/zeppelin-notebook-core"],
+      "@zeppelin/notebook-core/*": ["dist/zeppelin-notebook-core/*"],
       "@zeppelin/sdk": ["dist/zeppelin-sdk"],
       "@zeppelin/sdk/*": ["dist/zeppelin-sdk/*"]
     },
diff --git a/zeppelin-web-angular/projects/zeppelin-react/src/main.ts 
b/zeppelin-web-angular/vitest.notebook-core.config.mts
similarity index 66%
copy from zeppelin-web-angular/projects/zeppelin-react/src/main.ts
copy to zeppelin-web-angular/vitest.notebook-core.config.mts
index ce7edc883f..6737451fa2 100644
--- a/zeppelin-web-angular/projects/zeppelin-react/src/main.ts
+++ b/zeppelin-web-angular/vitest.notebook-core.config.mts
@@ -10,6 +10,11 @@
  * limitations under the License.
  */
 
-export { ConfigurationTable, mount as mountConfigurationTable } from 
'./pages/ConfigurationTable';
-export { PublishedParagraph, mount } from './pages/PublishedParagraph';
-export { ParagraphFooter, mount as mountParagraphFooter } from 
'./components/paragraph/ParagraphFooter';
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+  test: {
+    environment: 'node',
+    include: ['projects/zeppelin-notebook-core/**/*.spec.ts']
+  }
+});

Reply via email to