+ Running in standalone mode. The HTTP server is always active. System notifications are
+ not available — notification triggers are logged in-app only.
+
+ >
)}
);
diff --git a/vite.standalone.config.ts b/vite.standalone.config.ts
new file mode 100644
index 00000000..ac24a4d7
--- /dev/null
+++ b/vite.standalone.config.ts
@@ -0,0 +1,115 @@
+/**
+ * Vite build config for the standalone (non-Electron) server.
+ *
+ * Produces a single CJS bundle at dist-standalone/index.cjs that can be
+ * run with `node dist-standalone/index.cjs`.
+ */
+
+import { resolve } from 'path'
+import { defineConfig } from 'vite'
+
+import type { Plugin } from 'vite'
+
+// Node.js built-in modules that should be externalized
+const nodeBuiltins = new Set([
+ 'fs', 'path', 'os', 'events', 'stream', 'util', 'net', 'tls',
+ 'http', 'https', 'crypto', 'zlib', 'url', 'querystring',
+ 'child_process', 'buffer', 'dns', 'dgram', 'assert', 'constants',
+ 'readline', 'string_decoder', 'timers', 'tty', 'worker_threads'
+])
+
+// Packages that must be externalized because they break when bundled
+// (fastify ecosystem uses internal file resolution that doesn't survive bundling)
+const externalPackages = [
+ 'fastify', '@fastify/cors', '@fastify/static'
+]
+
+// Stub native .node addons (ssh2/cpu-features have JS fallbacks)
+function nativeModuleStub(): Plugin {
+ const STUB_ID = '\0native-stub'
+ return {
+ name: 'native-module-stub',
+ resolveId(source) {
+ if (source.endsWith('.node')) return STUB_ID
+ return null
+ },
+ load(id) {
+ if (id === STUB_ID) return 'export default {}'
+ return null
+ }
+ }
+}
+
+// Stub out Electron imports with empty modules
+const electronModules = new Set(['electron', 'electron-updater'])
+
+function electronStub(): Plugin {
+ const ELECTRON_STUB_ID = '\0electron-stub'
+ // Comprehensive stub covering all electron exports used in the codebase
+ const electronStubCode = `
+const noop = () => {};
+const noopClass = class {};
+const handler = { get: () => noop };
+const proxyObj = new Proxy({}, handler);
+export const app = proxyObj;
+export const BrowserWindow = noopClass;
+export const ipcMain = { handle: noop, on: noop, removeHandler: noop };
+export const shell = { openPath: noop, openExternal: noop };
+export const dialog = { showOpenDialog: async () => ({ canceled: true, filePaths: [] }) };
+export const Notification = class { show() {} };
+export default proxyObj;
+`
+ return {
+ name: 'electron-stub',
+ // Use enforce: 'pre' to intercept before Vite's SSR externalization
+ enforce: 'pre',
+ resolveId(source) {
+ if (electronModules.has(source)) return ELECTRON_STUB_ID
+ return null
+ },
+ load(id) {
+ if (id === ELECTRON_STUB_ID) return electronStubCode
+ return null
+ }
+ }
+}
+
+export default defineConfig({
+ plugins: [nativeModuleStub(), electronStub()],
+ resolve: {
+ alias: {
+ '@main': resolve(__dirname, 'src/main'),
+ '@shared': resolve(__dirname, 'src/shared'),
+ '@preload': resolve(__dirname, 'src/preload')
+ }
+ },
+ ssr: {
+ // Force Vite to bundle these instead of externalizing them
+ // (SSR mode externalizes all node_modules by default)
+ noExternal: true
+ },
+ build: {
+ outDir: 'dist-standalone',
+ target: 'node20',
+ ssr: true,
+ rollupOptions: {
+ input: {
+ index: resolve(__dirname, 'src/main/standalone.ts')
+ },
+ output: {
+ format: 'cjs',
+ entryFileNames: '[name].cjs'
+ },
+ external: (id) => {
+ // Externalize Node.js built-ins
+ if (id.startsWith('node:')) return true
+ if (nodeBuiltins.has(id)) return true
+ // Externalize packages that break when bundled
+ if (externalPackages.some(pkg => id === pkg || id.startsWith(pkg + '/'))) return true
+ return false
+ }
+ },
+ minify: false,
+ sourcemap: true
+ }
+})
From ea66c34ce33460157c81fe66630c999257031bd8 Mon Sep 17 00:00:00 2001
From: matt
Date: Mon, 16 Feb 2026 23:12:28 +0900
Subject: [PATCH 2/3] refactor(package): remove unused Remotion scripts and
update HttpServer for improved static file serving
- Removed obsolete Remotion preview and render scripts from package.json.
- Updated HttpServer to enhance static file serving logic, ensuring proper handling of renderer paths in both development and production modes.
- Added support for asarUnpack in package.json to facilitate unpacking of renderer files.
---
package.json | 7 +--
.../services/infrastructure/HttpServer.ts | 58 +++++++++----------
2 files changed, 30 insertions(+), 35 deletions(-)
diff --git a/package.json b/package.json
index e6f6cb72..86e17485 100644
--- a/package.json
+++ b/package.json
@@ -43,9 +43,6 @@
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"test:coverage:critical": "vitest run --coverage --config vitest.critical.config.ts",
- "remotion:preview": "remotion studio remotion/index.ts",
- "remotion:render": "remotion render remotion/index.ts DemoVideo out/demo.mp4",
- "remotion:render:gif": "remotion render remotion/index.ts DemoVideo out/demo.gif --image-format png",
"standalone": "tsx src/main/standalone.ts",
"standalone:build": "electron-vite build && vite build --config vite.standalone.config.ts",
"standalone:start": "node dist-standalone/index.cjs"
@@ -56,7 +53,6 @@
"@dnd-kit/utilities": "^3.2.2",
"@fastify/cors": "^11.2.0",
"@fastify/static": "^9.0.0",
- "@remotion/light-leaks": "4.0.421",
"@tanstack/react-virtual": "^3.10.8",
"date-fns": "^3.6.0",
"electron-updater": "^6.7.3",
@@ -133,6 +129,9 @@
"package.json"
],
"asar": true,
+ "asarUnpack": [
+ "out/renderer/**"
+ ],
"npmRebuild": false,
"extraMetadata": {
"main": "dist-electron/main/index.cjs"
diff --git a/src/main/services/infrastructure/HttpServer.ts b/src/main/services/infrastructure/HttpServer.ts
index 5e0a19cc..8c20ffe0 100644
--- a/src/main/services/infrastructure/HttpServer.ts
+++ b/src/main/services/infrastructure/HttpServer.ts
@@ -24,12 +24,13 @@ const logger = createLogger('Service:HttpServer');
*/
function resolveRendererPath(): string | null {
const candidates = [
- // Electron production paths
- join(__dirname, '../../../out/renderer'),
- join(__dirname, '../../renderer'),
+ // Electron production (asarUnpack): app.asar.unpacked/out/renderer (real filesystem)
+ join(__dirname, '../../out/renderer').replace('app.asar', 'app.asar.unpacked'),
+ // Electron production (asar fallback): app.asar/out/renderer
+ join(__dirname, '../../out/renderer'),
// Standalone: dist-standalone/index.cjs → ../out/renderer
join(__dirname, '../out/renderer'),
- // Fallback: relative to cwd (for standalone bundles)
+ // Fallback: relative to cwd (dev mode, standalone)
join(process.cwd(), 'out/renderer'),
];
@@ -89,38 +90,33 @@ export class HttpServer {
});
}
- // Register static file serving and SPA fallback (production only)
- const isDev = process.env.NODE_ENV === 'development';
- if (!isDev) {
- const rendererPath = resolveRendererPath();
- if (rendererPath) {
- logger.info(`Serving static files from: ${rendererPath}`);
+ // Register static file serving and SPA fallback when renderer output exists.
+ // In dev mode this requires a prior `pnpm build`; in production/standalone it's always present.
+ const rendererPath = resolveRendererPath();
+ if (rendererPath) {
+ logger.info(`Serving static files from: ${rendererPath}`);
- // Cache index.html for SPA fallback
- const indexHtml = readFileSync(join(rendererPath, 'index.html'), 'utf-8');
+ // Cache index.html for SPA fallback
+ const indexHtml = readFileSync(join(rendererPath, 'index.html'), 'utf-8');
- await this.app.register(fastifyStatic, {
- root: rendererPath,
- prefix: '/',
- wildcard: false,
- });
+ await this.app.register(fastifyStatic, {
+ root: rendererPath,
+ prefix: '/',
+ wildcard: false,
+ });
- // Register all API routes BEFORE the not-found handler
- registerHttpRoutes(this.app, services, sshModeSwitchCallback);
+ // Register all API routes BEFORE the not-found handler
+ registerHttpRoutes(this.app, services, sshModeSwitchCallback);
- // SPA fallback: serve index.html for all non-API routes
- this.app.setNotFoundHandler(async (request, reply) => {
- if (request.url.startsWith('/api/')) {
- return reply.status(404).send({ error: 'Not found' });
- }
- return reply.type('text/html').send(indexHtml);
- });
- } else {
- logger.warn('Renderer output directory not found, serving API only');
- registerHttpRoutes(this.app, services, sshModeSwitchCallback);
- }
+ // SPA fallback: serve index.html for all non-API routes
+ this.app.setNotFoundHandler(async (request, reply) => {
+ if (request.url.startsWith('/api/')) {
+ return reply.status(404).send({ error: 'Not found' });
+ }
+ return reply.type('text/html').send(indexHtml);
+ });
} else {
- // Dev mode: no static serving, just API routes
+ logger.warn('Renderer output directory not found (run `pnpm build` first), serving API only');
registerHttpRoutes(this.app, services, sshModeSwitchCallback);
}
From 13d99e5968948aa299c4504515248c4d2c5b2d6b Mon Sep 17 00:00:00 2001
From: matt
Date: Mon, 16 Feb 2026 23:12:51 +0900
Subject: [PATCH 3/3] chore(package): remove unused Remotion dependencies from
package.json
- Deleted obsolete Remotion packages from devDependencies in package.json to streamline the project and reduce unnecessary bloat.
---
package.json | 5 -----
1 file changed, 5 deletions(-)
diff --git a/package.json b/package.json
index 86e17485..ac50ab09 100644
--- a/package.json
+++ b/package.json
@@ -73,10 +73,6 @@
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "^4.6.0",
"@eslint/js": "^9.39.2",
- "@remotion/cli": "^4.0.421",
- "@remotion/google-fonts": "^4.0.421",
- "@remotion/media": "^4.0.421",
- "@remotion/transitions": "^4.0.421",
"@tailwindcss/typography": "^0.5.19",
"@types/hast": "^3.0.4",
"@types/mdast": "^4.0.4",
@@ -109,7 +105,6 @@
"postcss": "^8.4.35",
"prettier": "^3.8.1",
"prettier-plugin-tailwindcss": "^0.7.2",
- "remotion": "^4.0.421",
"tailwindcss": "^3.4.1",
"tsx": "^4.21.0",
"typescript": "^5.9.3",