OpenScene is an Electron, React, TypeScript, and Vite desktop app. The app is split into three strict surfaces:
src/main/: Electron main process. Owns desktop capture selection, OS permissions, local file access, project storage, FFmpeg export jobs, local TTS jobs, app menu wiring, and shell open or reveal actions.src/preload/: typed bridge. Exposes the narrowwindow.videoToolAPI and never exposes rawipcRenderer.src/renderer/: React UI. Owns capture controls, project timeline editing, Program Monitor preview, narration UI, and editor state.src/shared/: IPC names, validators, timeline models, export types, and provider seam types shared across main, preload, renderer, and tests.
npm install
npm run dev
npm run typecheck
npm test
npm run buildnpm run build runs typecheck first, then builds Electron main, preload, and renderer output into out/. It does not package an installer.
- Tests live in
tests/**/*.test.tsand run in Node through Vitest. - Main process tests usually cover stores, validators, job logic, FFmpeg argument boundaries, and IPC services without launching Electron.
- Renderer behavior lives under
src/renderer/src/, with timeline editor logic split across hooks, model helpers, and components. - Shared timeline logic and validators should stay in
src/shared/when both renderer and main process need the contract.
- Keep
contextIsolation: true,nodeIntegration: false,sandbox: true,webSecurity: true, and blocked window navigation. - File paths, FFmpeg executable paths, FFmpeg argv, voice sample paths, and generated output paths stay in the main process. The renderer gets typed job status and open or reveal actions.
- OpenScene stores recordings, projects, imported assets, voice profiles, TTS output, and exports locally under Electron
userDataunless an explicit local override exists. - No cloud upload, analytics, account system, crash reporting, auto-update, or provider network calls are implemented.
- Future hybrid AI support must follow
docs/hybrid-ai-editor-direction.md: local models are user-configured, connected services are selected and authorized per operation, and provider adapters remain behind typed seams until a separately reviewed implementation adds them.
Do not rename these persisted or public identifiers while rebranding docs or UI:
window-loom-themewindow-loom-editor-layoutwindow-loom-editor-shortcutsapplication/x-window-loom-timelineVIDEO_TOOL_*window.videoToollocal_qwen
Left behind by the OpenVideo → OpenScene rename, for the same reason the
window-loom-* names above survived the one before it — renaming a key does not
migrate what is stored under it, it orphans it:
openvideo-*renderer storage keys (theme-adjacent preferences, model selection, workspace tab, reasoning effort, agent chat layout)openvideo.— the mobile keystore prefix. Renaming this loses every API key the user has stored.openvideo-mcp-server— the MCP server name, which clients match ontech.theorvane.openvideo— the Electron appId. It identifies an installed application: changing it orphans every existing install and silently breaks auto-update for anyone already on an earlier version.executableName: openvideoand theopenvideo-${version}artifact namesOpenVideoMcpServer/getOpenVideoMcpDefinition— source identifiers
- Prefer typed shared contracts over ad hoc IPC payloads.
- Treat Program Monitor as best-effort preview. FFmpeg MP4 export is the supported final output for saved local timelines.
- Keep local Qwen TTS as a user-configured local wrapper. The app must not download models or promise model compatibility.
- Preserve consent boundaries for voice samples. Samples must be user-owned or authorized, stored locally, and deletable from the app store.
- When editing docs, use
OpenScenefor the product name and keep compatibility identifiers exactly as written above.
The desktop app and mobile/ are two front ends over the same editing rules.
A feature is not finished on one of them.
- Put the rule in
src/shared/first. Anything that decides an outcome — what a trim does, what plays at a moment, what an export composites, what a model costs — is a pure function there, imported by both. Neither surface reimplements a rule. A project that behaves one way on a laptop and another on a phone is the failure this exists to prevent, and it is discovered by users, not by tests. - Ship the mobile screen in the same pull request. Not a follow-up issue:
a feature that lands on the desktop alone leaves the shared core with a caller
on one side only, and the second caller is what proves the seam was drawn in
the right place. It has found real mistakes —
NodeJS.Platformin shared code, and image adapters returning a NodeBuffer— each caught by the mobile typecheck and by nothing else. - Say so when a surface genuinely cannot have it. Some things are honestly platform-bound: window capture has no phone equivalent, and Android export is not written yet. Those stay visible and disabled with the reason, in the UI and in the pull request. Silence reads as an oversight; a stated limit reads as a decision.
- Verify on both.
npm run typecheck && npm test && npm run buildat the root,npm run typecheckinmobile/, and the screen exercised on a development client. The mobile typecheck is not optional — it is the only check that compiles the shared core against a non-Node environment. - Do not reach for a native module to avoid sharing. Native code is for what only the platform can do — AVFoundation and Media3 for rendering, the keystore for secrets. It is not a place to put a rule that both surfaces need.
- Agent Skills Location:
.agents/skills/api-to-typemcp/SKILL.md(Integratesapi-to-typemcpskill for converting OpenAPI/Swagger specifications or API docs into TypeMCP MCP projects). - TypeMCP Server & Tools: OpenScene internal process capabilities (AI video generation, TTS speech synthesis, job status tracking) are declared as standard TypeMCP tools (
@theorvane/type-mcp) insrc/main/openVideoMcpServer.tsand exposed over IPC (window.videoTool.mcpGetTools/mcpExecuteTool).
Every change after the initial repository bootstrap follows this sequence. Never commit directly to protected dev or main.
- Inspect open GitHub Issues and pull requests, then create or update one focused GitHub Issue before branching.
- Put the issue number in the branch name:
<type>/<issue-number>-<short-description>, such asfeat/12-tool-compilerorchore/1-strict-workspace-baseline. - Branch from the current
origin/devunless a documented stacked pull request requires another base. - Implement one coherent issue only. Use conventional commits in the format
type(scope): subject. - Push the branch and open a pull request against
devwithCloses #<issue-number>in the body. - Run and report fresh verification evidence. Obtain specification and code-quality review before merging.
- Squash merge only after CI and review pass. Verify the issue closes and
devcontains the merged commit. - Promote vetted
devto release-onlymainthrough a separate reviewed release pull request. Verifymaincontains the release commit before publication. - The version in
package.jsonis the release decision. Bump it ondevbefore promoting: the push tomaintagsv<version>, creates arelease/v<version>branch, packages macOS, Windows, and Linux on their own runners, and publishes the release with every artifact attached. Promoting without a bump republishes nothing, by design.