@noego/wood
v0.4.8
Published
Declarative Electron desktop apps using the noego ecosystem.
Downloads
448
Readme
@noego/wood
Declarative Electron desktop apps using the noego ecosystem.
@noego/wood provides config parsing, operation/view parsing, IPC routing, runtime wiring, code generation, and CLI utilities for building Electron applications with Svelte-based renderer flows.
Install
npm install @noego/woodPeer dependencies used by the package include:
electron >=28svelte ^5playwright ^1.40.0(optional)better-sqlite3 ^11sqlstack@noego/proper
Package Entry Points
The package publishes both ESM and CommonJS builds through exports.
Primary entry points:
@noego/wood@noego/wood/electron@noego/wood/main@noego/wood/client@noego/wood/ipc@noego/wood/navigation@noego/wood/stick@noego/wood/testing@noego/wood/trace-testing@noego/wood/loader
Svelte component exports:
@noego/wood/recursive-render@noego/wood/navigation-shell@noego/wood/components
Core API
The root package exports the main framework surface, including:
parseConfigparseOperationsandparseOperationsFileparseViewsandparseViewsFileWoodRouterSchemaValidatorMiddlewareResolverControllerResolverLoaderRunnerBackendBridgeWindowManagerruntime- code generators such as
generatePreload,generateRuntime,generateRendererApp, andgenerateTypes
Example:
import { parseOperationsFile, parseViewsFile, generateRuntime } from '@noego/wood';
const operations = parseOperationsFile('operations/chat.yaml');
const views = parseViewsFile('views/app.yaml');
const source = generateRuntime({
operations,
views,
});Controller lifecycle
Controllers may optionally implement dispose(): void | Promise<void>.
WoodRouter resolves a controller for each dispatch and calls dispose() in a
finally block after the action completes or throws. Use dispose() to
unsubscribe from streams, unregister listeners, or release other controller-owned
resources.
Tracing
Wood tracing records structured runtime events that describe how a flow ran, not just what it returned. Use traces for async pipelines, renderer/main handoffs, streaming flows, recovery paths, routing decisions, and any behavior where tests need to prove which stages triggered and what each stage produced.
Application code emits traces through TraceProvider:
import { Component, Inject } from '@noego/ioc';
import { TraceProvider, type FrameworkTracer } from '@noego/wood';
@Component()
export class DocumentService {
constructor(
@Inject(TraceProvider) private readonly trace: FrameworkTracer,
) {}
async save(documentId: string, byteLength: number): Promise<void> {
this.trace.info({
source: 'document.service',
type: 'document.save.started',
payload: { documentId, byteLength },
});
// perform the work...
this.trace.info({
source: 'document.service',
type: 'document.save.completed',
payload: { documentId },
});
}
}Use trace.extend(...) when several events share the same context:
const trace = this.trace.extend({
source: 'chat.context',
conversationId,
});
trace.info({
type: 'context.build.started',
payload: { messageCount },
});Trace events are structured records with source, type, level, process,
timestamp, traceSeq, and optional fields such as message,
conversationId, payload, windowId, webContentsId, routeKey, and
harnessId.
Guidelines:
- Use stable event names such as
thing.started,thing.completed, andthing.failed. - Include identifiers needed to correlate the flow, especially
conversationIdfor conversation work. - Keep payloads structured and small: counts, selected route/model names, decision fields, ids, and sizes are usually better than full data blobs.
- Use
infofor normal lifecycle events,warnfor recoverable surprises, anderrorfor failures. - Trace values before and after any stage that can transform, overwrite, skip, or discard data.
CLI
This package includes two executables:
woodstick
wood currently supports:
wood dev
wood buildBoth commands generate project artifacts and pass remaining options through to electron-vite.
Generated client contracts
wood dev and wood build generate ui/generated/wood.d.ts from the
operation manifest. It exports two project-specific contracts:
GeneratedAppmatches@noego/wood/clientgetApp()and injectedAppinstances: controller name -> controller action.GeneratedWoodBridgematches the raw preload bridge path: operation channel segments -> invoke function.WoodRpcApiremains as a backward-compatible alias forGeneratedApp.
Use GeneratedApp when testing renderer services that call the injected client
app:
import type { GeneratedApp } from '../generated/wood';
const app = {
threads: {
updateThreadMode: async () => ({ ok: true }),
},
} satisfies GeneratedApp;This keeps mocks aligned with the production client facade and makes nonexistent methods fail TypeScript instead of failing only at runtime.
Use GeneratedWoodBridge only when typing direct access to the raw preload
bridge shape. Most renderer code should use GeneratedApp instead.
Development
Repository scripts:
npm run build
npm run typecheck
npm testPublishing runs the same checks through prepublishOnly.
Testing
Wood publishes browser-testing helpers from @noego/wood/testing.
import { browser } from '@noego/wood/testing';
const harness = await browser.createHarness({
rootDir: process.cwd(),
componentDir: 'ui',
css: ['ui/app.css'],
bridge: {
operations: {
'settings.get': () => ({ readSpeed: 0 }),
},
},
});
const mounted = await harness.mountComponent('ui/components/StatusPanel.svelte');
await mounted.expect.visible('[data-testid="status-panel"]');
await harness.destroy();The browser harness starts Vite with the Svelte plugin, launches Playwright, installs a controlled Wood bridge fixture, and can mount individual Svelte components, layered Wood trees, or Wood routes from a views config.
Use it for renderer behavior that needs real CSS, browser layout, animation frames, z-index, clipping, or route/controller wiring without booting Electron. Keep pure service and state tests in Node.
See Browser Testing for the full API shape and boundary notes.
Wood also publishes trace-testing helpers from @noego/wood/trace-testing.
TraceRecorder subscribes to the same trace stream as production tracing and
keeps an in-memory buffer for assertions.
import { TraceRecorder } from '@noego/wood/trace-testing';
const traceRecorder = await container.instance(TraceRecorder);
traceRecorder.enable();
await subject.doWork();
const trace = traceRecorder.query();
expect(trace.once({ type: 'thing.started' })).toBe(true);
expect(trace.sequence([
{ type: 'thing.started' },
{ type: 'thing.completed', 'payload.count': 3 },
])).toBe(true);
expect(trace.none({ type: 'thing.failed' })).toBe(true);
traceRecorder.disable();Use trace assertions when the runtime story is part of the contract: branch
selection, model or route choice, repeated work budgets, emitted payload sizes,
failure/recovery paths, or renderer-to-main ordering. TraceRecorder also
provides events() for raw inspection and waitForTrace(...) for async browser
or streaming scenarios.
Publishing
Publishing is handled by the Publish GitHub Actions workflow.
Prerequisites:
- Add an npm automation token as the repository secret
NPM_TOKEN. - Bump
package.jsonandpackage-lock.jsonto a version that is not already published. - Commit the version bump on
main.
Release flow:
npm version patch
git push origin main
git push origin main:releaseThe workflow runs when the release branch is updated. It runs prepublishOnly
through npm publish and publishes the package to npm.
Design Notes
High-level architecture and config format notes live in DESIGN.md.
