Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions apps/labs/electron/main.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { app, BrowserWindow, shell } from "electron";
import path from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const rendererUrl = process.env.LABS_RENDERER_URL || "";

let mainWindow = null;

function createWindow() {
mainWindow = new BrowserWindow({
width: 1480,
height: 980,
minWidth: 1080,
minHeight: 720,
backgroundColor: "#141211",
titleBarStyle: "hiddenInset",
webPreferences: {
preload: path.join(__dirname, "preload.mjs"),
contextIsolation: true,
nodeIntegration: false,
webSecurity: false,
},
});

mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url).catch(() => undefined);
return { action: "deny" };
});

if (rendererUrl) {
mainWindow.loadURL(rendererUrl).catch(() => undefined);
} else {
mainWindow
.loadFile(path.join(__dirname, "..", "dist", "index.html"))
.catch(() => undefined);
}
}

app.whenReady().then(() => {
createWindow();

app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});

app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
6 changes: 6 additions & 0 deletions apps/labs/electron/preload.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { contextBridge } from "electron";

contextBridge.exposeInMainWorld("openworkLabsDesktop", {
isDesktop: true,
platform: process.platform,
});
19 changes: 19 additions & 0 deletions apps/labs/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<link
rel="icon"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='18' fill='%23121212'/%3E%3Ctext x='50%25' y='54%25' dominant-baseline='middle' text-anchor='middle' font-family='Inter,Arial,sans-serif' font-size='24' font-weight='700' fill='%23ffffff'%3EOW%3C/text%3E%3C/svg%3E"
/>
<title>OpenWork Labs</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
30 changes: 30 additions & 0 deletions apps/labs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "@openwork/labs",
"private": true,
"version": "0.0.1",
"type": "module",
"main": "electron/main.mjs",
"scripts": {
"dev": "node ./scripts/dev.mjs",
"build": "vite build",
"preview": "vite preview --host 0.0.0.0 --port 3340 --strictPort",
"typecheck": "tsc -p tsconfig.json --noEmit",
"start": "electron ./electron/main.mjs"
},
"dependencies": {
"@opencode-ai/sdk": "^1.1.31",
"@tanstack/react-virtual": "^3.13.23",
"react": "19.2.4",
"react-dom": "19.2.4",
"streamdown": "^2.5.0"
},
"devDependencies": {
"@types/node": "^25.4.0",
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"@vitejs/plugin-react": "^5.0.4",
"electron": "^41.1.1",
"typescript": "^5.9.3",
"vite": "^7.1.12"
}
}
Binary file added apps/labs/pr/openwork-labs-template-starter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
84 changes: 84 additions & 0 deletions apps/labs/scripts/dev.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { spawn } from "node:child_process";

const rendererPort = 3340;
const rendererUrl = `http://127.0.0.1:${rendererPort}`;

const children = [];
let shuttingDown = false;

function cleanup(exitCode = 0) {
if (shuttingDown) return;
shuttingDown = true;

for (const child of children) {
if (!child.killed) {
child.kill("SIGTERM");
}
}

setTimeout(() => process.exit(exitCode), 120);
}

function spawnLogged(command, args, env = process.env) {
const child = spawn(command, args, {
stdio: "inherit",
env,
shell: process.platform === "win32",
});
children.push(child);
return child;
}

async function waitForRenderer(url, timeoutMs = 30_000) {
const startedAt = Date.now();
while (Date.now() - startedAt < timeoutMs) {
try {
const response = await fetch(url);
if (response.ok) return;
} catch {
// Ignore until the server is ready.
}
await new Promise((resolve) => setTimeout(resolve, 250));
}

throw new Error(`Timed out waiting for ${url}`);
}

process.on("SIGINT", () => cleanup(0));
process.on("SIGTERM", () => cleanup(0));

const vite = spawnLogged("pnpm", [
"exec",
"vite",
"--host",
"0.0.0.0",
"--port",
String(rendererPort),
"--strictPort",
]);

vite.on("exit", (code) => {
if (!shuttingDown) {
cleanup(code ?? 1);
}
});

try {
await waitForRenderer(rendererUrl);
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
cleanup(1);
}

const electron = spawnLogged(
"pnpm",
["exec", "electron", "./electron/main.mjs"],
{
...process.env,
LABS_RENDERER_URL: rendererUrl,
},
);

electron.on("exit", (code) => {
cleanup(code ?? 0);
});
Loading
Loading