Project structure
A scaffolded app (ow create my-app) looks like this:
my-app/
├── app/
│ ├── main.ts # Node main process (sidecar)
│ └── workers/ # optional Node workers (compiled to dist/workers)
├── native/ # optional: your C++ modules (*.cpp → *.owm)
├── public/ # static assets copied verbatim by Vite
├── src/
│ ├── renderer.ts # UI entry point (runs in the WebView)
│ ├── style.css
│ ├── ow.d.ts # types for window.ow (injected by the kernel)
│ └── owear-native.d.ts # generated types for @owear/native
├── index.html # the document the WebView loads
├── vite.config.ts
├── package.json
└── tsconfig.json
The renderer (src/, index.html)
This is a normal Vite app. It is loaded inside the OS WebView. The kernel injects
window.ow into every document, which is how the UI reaches native code directly
— there is no ipcRenderer and no round trip through Node.
// src/renderer.ts
const files = await window.ow.invoke<string[]>('fs', 'readDir', '/tmp')
src/ow.d.ts declares the window.ow surface so TypeScript knows about it.
Note that it is a type file — the object itself is injected at runtime.
The main process (app/main.ts)
Optional but present in the template. It runs on a real Node process ("sidecar")
and uses @owear/core, whose API mirrors Electron:
import { app, BrowserWindow, Menu, Tray } from '@owear/core'
Use it for:
- creating and orchestrating windows;
- native menus and tray icons (they need the main process in the SDK);
- Node-only code (database drivers,
child_process, native addons); - exposing privileged operations to the renderer via
app.handle.
If app/main.ts does not exist, the kernel opens a window at OW_START_URL
directly (see Dev and build).
Native modules (native/)
Drop .cpp files here to add native functions to your app. Each file becomes a
.owm module, callable from the renderer.
// native/files.cpp
#include <ow/Module.h>
static void readText(const ow_request_t* req, ow_response_t* res) {
ow::Module::RespondOk(res, "\"hello from C++\"");
}
OW_MODULE_BEGIN(files, "1.0.0")
OW_FN(readText)
OW_MODULE_END()
import { files } from '@owear/native'
const text = await files.readText()
See Native modules. The Vite plugin compiles these
for you in dev and build, and generates owear-native.d.ts.
Configuration files
| File | Purpose |
|---|---|
vite.config.ts |
Adds @owear/vite-plugin; sets base: './' for app:// |
package.json |
Scripts dev/build call the ow CLI |
tsconfig.json |
TypeScript for both renderer and main |
owear.bridge.ts |
Optional: app ↔ installer contract (see Installers) |
owear.pack.json |
Optional: packaging overrides (modes.default, etc.) |
What lives outside the project
ow dev and ow build keep intermediate artifacts in .owear/ (git-ignored):
the compiled main process, compiled workers, app bundles, and installer staging.
Next steps
- Dev and build — what each command produces.
- Main process — the Node sidecar in depth.