diff --git a/package-lock.json b/package-lock.json
index 9bd5fa11fe3..f45eb09aa2c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -117,7 +117,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -290,6 +289,10 @@
"node": ">=18"
}
},
+ "node_modules/@breadboard-ai/opal-frontend": {
+ "resolved": "packages/opal-frontend",
+ "link": true
+ },
"node_modules/@breadboard-ai/types": {
"resolved": "packages/types",
"link": true
@@ -401,7 +404,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=18"
},
@@ -445,7 +447,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -2299,7 +2300,6 @@
"integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^5.0.0",
@@ -2567,7 +2567,6 @@
"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.54.0",
"@typescript-eslint/types": "8.54.0",
@@ -2849,7 +2848,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -4432,7 +4430,6 @@
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -5910,7 +5907,6 @@
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.5.tgz",
"integrity": "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=16.9.0"
}
@@ -8738,8 +8734,7 @@
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/signal-polyfill/-/signal-polyfill-0.2.2.tgz",
"integrity": "sha512-p63Y4Er5/eMQ9RHg0M0Y64NlsQKpiu6MDdhBXpyywRuWiPywhJTpKJ1iB5K2hJEbFZ0BnDS7ZkJ+0AfTuL37Rg==",
- "license": "Apache-2.0",
- "peer": true
+ "license": "Apache-2.0"
},
"node_modules/signal-utils": {
"version": "0.21.1",
@@ -9518,7 +9513,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -10113,7 +10107,6 @@
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
"devOptional": true,
"license": "ISC",
- "peer": true,
"bin": {
"yaml": "bin.mjs"
},
@@ -10238,7 +10231,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz",
"integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==",
"license": "MIT",
- "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
@@ -10255,6 +10247,24 @@
"packages/opal-backend": {
"version": "0.1.0"
},
+ "packages/opal-frontend": {
+ "name": "@breadboard-ai/opal-frontend",
+ "version": "0.1.0",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@lit-labs/signals": "^0.1.3",
+ "lit": "^3.3.2",
+ "signal-polyfill": "^0.2.2",
+ "signal-utils": "^0.21.1"
+ },
+ "devDependencies": {
+ "@google-labs/tsconfig": "^0.0.2",
+ "@types/node": "^22.0.0",
+ "typescript": "^5.8.3",
+ "vite": "^7.2.6",
+ "wireit": "^0.15.0-pre.2"
+ }
+ },
"packages/types": {
"name": "@breadboard-ai/types",
"version": "0.9.0",
@@ -10271,6 +10281,7 @@
"version": "0.4.0",
"license": "Apache-2.0",
"dependencies": {
+ "@breadboard-ai/opal-frontend": "0.1.0",
"@breadboard-ai/types": "0.9.0",
"@breadboard-ai/utils": "^0.1.0",
"@breadboard-ai/visual-editor": "1.30.0",
diff --git a/packages/opal-frontend/.eslintrc b/packages/opal-frontend/.eslintrc
new file mode 100644
index 00000000000..bb987452b0c
--- /dev/null
+++ b/packages/opal-frontend/.eslintrc
@@ -0,0 +1,6 @@
+{
+ "parser": "@typescript-eslint/parser",
+ "parserOptions": {
+ "projectService": true
+ }
+}
diff --git a/packages/opal-frontend/.gitignore b/packages/opal-frontend/.gitignore
new file mode 100644
index 00000000000..849ddff3b7e
--- /dev/null
+++ b/packages/opal-frontend/.gitignore
@@ -0,0 +1 @@
+dist/
diff --git a/packages/opal-frontend/.npmignore b/packages/opal-frontend/.npmignore
new file mode 100644
index 00000000000..7ddb2133685
--- /dev/null
+++ b/packages/opal-frontend/.npmignore
@@ -0,0 +1,2 @@
+.env
+tsconfig.tsbuildinfo
diff --git a/packages/opal-frontend/index.html b/packages/opal-frontend/index.html
new file mode 100644
index 00000000000..fa8c62f71e3
--- /dev/null
+++ b/packages/opal-frontend/index.html
@@ -0,0 +1,10 @@
+
+
+
+
+ Opal Frontend
+
+
+
+
+
diff --git a/packages/opal-frontend/package.json b/packages/opal-frontend/package.json
new file mode 100644
index 00000000000..04204e12ce0
--- /dev/null
+++ b/packages/opal-frontend/package.json
@@ -0,0 +1,115 @@
+{
+ "name": "@breadboard-ai/opal-frontend",
+ "private": true,
+ "version": "0.1.0",
+ "description": "The Opal Frontend",
+ "main": "./dist/src/index.js",
+ "exports": {
+ ".": {
+ "types": "./dist/src/index.d.ts",
+ "default": "./dist/src/index.js"
+ },
+ "./package.json": "./package.json"
+ },
+ "types": "dist/src/index.d.ts",
+ "type": "module",
+ "scripts": {
+ "build": "wireit",
+ "build:vite": "wireit",
+ "build:tsc": "wireit",
+ "dev": "vite",
+ "lint": "wireit"
+ },
+ "wireit": {
+ "build": {
+ "dependencies": [
+ "build:vite"
+ ]
+ },
+ "typescript-files-and-deps": {
+ "files": [
+ "src/**/*.ts",
+ "tsconfig.json",
+ "vite.config.ts",
+ "../../core/tsconfig/base.json",
+ "package.json"
+ ]
+ },
+ "check:tsc": {
+ "command": "tsc -b --pretty --noEmit",
+ "env": {
+ "FORCE_COLOR": "1"
+ },
+ "dependencies": [
+ "typescript-files-and-deps"
+ ],
+ "files": [],
+ "output": []
+ },
+ "build:tsc": {
+ "command": "tsc -b --pretty",
+ "env": {
+ "FORCE_COLOR": "1"
+ },
+ "dependencies": [
+ "typescript-files-and-deps"
+ ],
+ "files": [
+ "dist/tsc/"
+ ]
+ },
+ "build:vite": {
+ "command": "vite build",
+ "env": {
+ "FORCE_COLOR": "1"
+ },
+ "dependencies": [
+ "check:tsc"
+ ],
+ "files": [
+ "vite.config.ts",
+ "*.html"
+ ],
+ "output": [
+ "dist/"
+ ]
+ },
+ "lint": {
+ "command": "eslint --max-warnings=0 'src/**/*.ts'",
+ "files": [
+ "src/**/*.ts",
+ "../../eslint.config.js"
+ ],
+ "output": []
+ }
+ },
+ "repository": {
+ "directory": "packages/opal-frontend",
+ "type": "git",
+ "url": "git+https://github.com/breadboard-ai/breadboard.git"
+ },
+ "files": [
+ "dist/",
+ "index.html"
+ ],
+ "keywords": [],
+ "author": "Google Labs Team",
+ "license": "Apache-2.0",
+ "bugs": {
+ "url": "https://github.com/breadboard-ai/breadboard/issues"
+ },
+ "homepage": "https://github.com/breadboard-ai/breadboard/tree/main/packages/opal-frontend#readme",
+ "devDependencies": {
+ "@google-labs/tsconfig": "^0.0.2",
+ "@types/node": "^22.0.0",
+ "typescript": "^5.8.3",
+ "vite": "^7.2.6",
+ "wireit": "^0.15.0-pre.2"
+ },
+ "dependencies": {
+ "@lit-labs/signals": "^0.1.3",
+ "lit": "^3.3.2",
+ "signal-polyfill": "^0.2.2",
+ "signal-utils": "^0.21.1"
+ }
+}
\ No newline at end of file
diff --git a/packages/opal-frontend/src/index.ts b/packages/opal-frontend/src/index.ts
new file mode 100644
index 00000000000..74f1bbe4176
--- /dev/null
+++ b/packages/opal-frontend/src/index.ts
@@ -0,0 +1,47 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { signal } from "signal-utils";
+import { SignalWatcher } from "@lit-labs/signals";
+import { LitElement, html, css } from "lit";
+import { customElement } from "lit/decorators.js";
+
+/**
+ * @constructor
+ * @extends {LitElement}
+ */
+const SignalWatcherBase = SignalWatcher(LitElement);
+
+@customElement("opal-main")
+/** @extends {LitElement} */
+class OpalMain extends SignalWatcherBase {
+ static override styles = css`
+ :host {
+ display: block;
+ font-family: system-ui, sans-serif;
+ padding: 2rem;
+ }
+ `;
+
+ @signal accessor time = new Date().toLocaleTimeString();
+
+ override connectedCallback() {
+ super.connectedCallback();
+ setInterval(() => {
+ this.time = new Date().toLocaleTimeString();
+ }, 1000);
+ }
+
+ override render() {
+ return html`Hello, Signals! The time is ${this.time}
`;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "opal-main": OpalMain;
+ }
+}
diff --git a/packages/opal-frontend/tsconfig.json b/packages/opal-frontend/tsconfig.json
new file mode 100644
index 00000000000..74e1875bdd2
--- /dev/null
+++ b/packages/opal-frontend/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "nodenext",
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
+ "skipLibCheck": true,
+ "useDefineForClassFields": false,
+ "outDir": "dist/tsc",
+ "moduleResolution": "nodenext",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "types": ["vite/client", "node"]
+ },
+ "include": ["src/**/*.ts"],
+ "extends": "@google-labs/tsconfig/base.json"
+}
diff --git a/packages/opal-frontend/vite.config.ts b/packages/opal-frontend/vite.config.ts
new file mode 100644
index 00000000000..c97782d8127
--- /dev/null
+++ b/packages/opal-frontend/vite.config.ts
@@ -0,0 +1,26 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { defineConfig, type UserConfig } from "vite";
+
+export default defineConfig((): UserConfig => {
+ return {
+ optimizeDeps: { esbuildOptions: { target: "esnext" } },
+ build: {
+ target: "esnext",
+ outDir: "dist/client",
+ },
+ resolve: {
+ dedupe: ["lit"],
+ },
+ server: {
+ port: 3333,
+ watch: {
+ ignored: ["**/.wireit/**"],
+ },
+ },
+ };
+});
diff --git a/packages/unified-server/package.json b/packages/unified-server/package.json
index de1a89e227b..e3cb3d095dd 100644
--- a/packages/unified-server/package.json
+++ b/packages/unified-server/package.json
@@ -37,7 +37,8 @@
},
"build:vite": {
"dependencies": [
- "../visual-editor#build"
+ "../visual-editor#build",
+ "../opal-frontend#build"
]
},
"build:tsc": {
@@ -167,6 +168,7 @@
"dependencies": {
"@breadboard-ai/types": "0.9.0",
"@breadboard-ai/utils": "^0.1.0",
+ "@breadboard-ai/opal-frontend": "0.1.0",
"@breadboard-ai/visual-editor": "1.30.0",
"@google-cloud/storage": "^7.15.2",
"compression": "^1.8.1",