- Do not run
bun run build. You may run tests (for typescript). - Do not install Rust packages (
cargo install,cargo add) or build the native addon, update only thecargo.toml. - To build the addon use the tool
execute("build-native-addon"). - To run clippy use the tool
execute("clippy-native-addon"). - To run fmt use the tool
execute("fmt-native-addon"). - After every new feature or fix remember to run both the linter, fmt and the native addon build.
- The
native-window-ipcpackage must remain pure TypeScript with zero runtime dependencies. - Always fix all rust warnings
Bun workspace monorepo providing native OS webview windows for Bun/Node.js. The native addon is built with Rust + napi-rs. The typed IPC layer is pure TypeScript.
packages/
native-window/ # Rust napi-rs native addon
src/
lib.rs # Module entry: init(), pumpEvents()
window.rs # NativeWindow class (#[napi])
window_manager.rs # Global state, Command enum, command queue
options.rs # WindowOptions struct (#[napi(object)])
events.rs # Event callback type aliases
platform/
macos.rs # macOS: WKWebView + AppKit
windows.rs # Windows: WebView2 + Win32
index.ts # TS entry point + NativeWindow wrapper class
native-window.d.ts # TS type declarations for the native addon
native-window-ipc/ # Pure TypeScript typed IPC layer
index.ts # Host-side: createChannel, createWindow, getClientScript
client.ts # Webview-side: createChannelClient
tests/
channel.test.ts # Unit tests (vitest)
samples/
src/basic.ts # Raw IPC example
src/typed-ipc.ts # Typed IPC channel example
| Task | Command | Working Directory |
|---|---|---|
| Install deps | bun install |
repo root |
| Build addon (release) | bun run build |
packages/webview |
| Build addon (debug) | bun run build:debug |
packages/webview |
| Format Rust | cargo fmt |
packages/webview |
| Lint Rust | cargo clippy |
packages/webview |
| Run all tests | vitest run |
packages/ipc |
| Run single test by name | vitest run -t "pattern" |
packages/ipc |
| Run sample | bun samples/src/basic.ts |
repo root |
- Framework:
vitest. Usesdescribe/test/expect/mock. - Tests live in
packages/ipc/tests/channel.test.ts. - Tests use a mock
NativeWindow— no native addon or macOS/Windows required. - Run a single test:
vitest run -t "test name pattern"(e.g.vitest run -t "send() encodes"). - Test names use present-tense verb phrases:
"send() encodes messages with $ch envelope". - When adding IPC features, add corresponding tests in
channel.test.ts.
- 2-space indentation, double quotes, always semicolons, trailing commas in multi-line lists.
- No formatter (Prettier/ESLint) is configured; follow existing style manually.
import typefor type-only imports (enforced byverbatimModuleSyntax: true).- Named imports only — no default imports.
- Order: external packages first, then relative imports.
- Use
.tsextension in relative imports withinnative-window-ipc.
camelCasefor functions, variables, parameters.PascalCasefor types, interfaces, classes._prefix for private fields and methods:_native,_closed,_handleClose().
- Explicit return types on all public and private functions.
- Type inference for local variables when type is obvious from context.
- Named exports only — no default exports. Use
export typefor type-only re-exports. - Constrained generics:
<T extends EventMap>, key types:keyof T & string.
try/catchwith barecatch {}(no error binding) when error is unused.- Return
nullfrom parse/decode failures. - Guard clauses with early
returnfor invalid input. - Type guard functions (
data is Envelope) for type narrowing. - Prefer
??over||, use?.for optional chaining.
- JSDoc
/** */with@exampleon all public exports. @internaltag on private implementation details.- Section headers:
// ── Section ──────────────────────────(em-dash style). - Subsection headers in classes:
// ---- Section ----. - Inline
//comments explain the "why", not the "what".
- 4-space indentation (rustfmt defaults). Run
cargo fmtbefore committing. - No
rustfmt.toml— uses all defaults.
- Group: std → external crates →
crate::locals, separated by blank lines. - Glob imports allowed for napi prelude (
use napi::bindgen_prelude::*) and platform APIs.
snake_casefor functions, methods, variables, fields.PascalCasefor types, structs, enums, enum variants.UPPER_SNAKE_CASEfor constants/statics.
- Return
napi::Result<()>from all#[napi]functions. - Create errors with
napi::Error::from_reason("descriptive message"). - Use
?operator for propagation,.ok_or_else(|| ...)for Option → Result. unwrap_or_default()for sensible defaults. Nounwrap()orexpect()in production.let _ =to explicitly discard results.
#[napi(constructor)]onnew(),#[napi(getter)]on getters.#[napi(object)]on structs mapping to JS objects (with#[derive(Debug, Clone)]).#[napi(ts_args_type = "...")]for explicit TS callback signatures.- Optional fields use
Option<T>with manualDefaultimpl.
///doc comments on all public items.- Section headers:
// ---- Section ----. - Inline
//comments for behavior explanation.
- Use
#[cfg(target_os = "...")]blocks, notif cfg!(). - Always handle the unsupported-platform case with a descriptive error.
thread_local!withRefCellfor global mutable state.- Accessor functions (e.g.
with_manager) to hide thread-local boilerplate. try_borrow()with deferred processing for reentrant access.
- Command queue: JS calls enqueue
Commandvariants.pumpEvents()drains and executes them on the main thread, then pumps the OS event loop. - IPC envelope: Messages use
{$ch, p}JSON format overpostMessage/onMessage. - Client injection: A minified client script is auto-injected into the webview and re-injected on page navigation.
New window method:
- Add command variant in
src/window_manager.rs(Commandenum). - Add
#[napi]method insrc/window.rs. - Implement in
src/platform/macos.rsandsrc/platform/windows.rs. - Update
native-window.d.tswith the type declaration.
New IPC event type:
- Add callback type alias in
src/events.rs. - Add field to
WindowEventHandlers. - Add
#[napi]handler registration method insrc/window.rs. - Update
native-window.d.ts.
New IPC channel feature:
- Implement in
packages/ipc/index.ts(host) and/orclient.ts(webview). - Add tests in
tests/channel.test.ts. - Keep the package pure TypeScript — no native or platform imports.