docs/waves/engine_host_frame_migration_plan.md
Engine-Host Frame Interface Migration Plan
Status: planning-ready
Last updated: 2026-03-03
Purpose
Define a deterministic, multi-target rendering boundary where the engine emits structured frame data and hosts render it, without engine-generated HTML or host DOM-coupled assumptions.
This plan is intentionally scoped to interface and rendering-boundary migration only.
Scope
In scope:
- engine output model migration from
RenderListtoEngineFrame(additive first) - engine input model migration from key-only calls to typed
EngineInputEvent(additive first) - host adapter updates for:
- WASM browser harness (
engine-wasm/host-sample) - Waves Tauri frontend (
browser/frontend)
- WASM browser harness (
- contract-source and type-generation alignment across Rust and TypeScript
- parity and determinism validation across native + wasm targets
Out of scope:
- protocol stack redesign
- broad runtime feature expansion unrelated to interface migration
- GPU renderer implementation (only compatibility posture)
Current Baseline (Observed)
- Engine already emits structured draw commands (
RenderList,DrawCmd::Text|Link). - WASM host sample already consumes draw commands and paints to Canvas2D.
- Tauri frontend currently converts draw commands into HTML string lines (
innerHTML) for viewport presentation. - Input flow is key-driven (
up/down/enter) with host-triggered back and timer ticks; no click/scroll event boundary exists yet. - Type contracts are partly centralized:
- generated host contracts from Rust (
ts-rs) inbrowser/contracts/generated/* - handwritten engine wasm contract in
engine-wasm/contracts/wml-engine.ts
- generated host contracts from Rust (
Target Architecture
Engine Output Boundary
Engine returns a frame object with enough data for renderer independence:
EngineFrameviewportdrawCommands- optional
hitRegions(for deterministic click mapping) - optional
metadata(diagnostics/version/feature flags)
Engine Input Boundary
Engine accepts typed input events:
EngineInputEventKeyPressClickScroll- optional future events (
Softkey,TextCommit)
Host Responsibilities
- renderer implementation selection (Canvas2D now, future GPU later)
- mapping host/native/browser input into
EngineInputEvent - transport, windowing, and side-effect policy remain host-owned
Engine Responsibilities
- deterministic layout, navigation, focus, and hit-testing
- deterministic frame generation (host-agnostic)
- zero DOM/HTML assumptions
Compatibility Strategy
Use additive migration with compatibility shims:
- Introduce new frame/input API while preserving existing
render()andhandleKey()paths. - Migrate hosts to new APIs incrementally.
- Keep regression parity tests on both APIs until cutover.
- Remove legacy API only after parity gate is green.
Contract-First Plan
Primary contract evolution sequence:
- Define canonical Rust-side interface types (
EngineFrame,DrawCommand,EngineInputEvent,EngineCommand). - Generate TypeScript bindings from Rust-owned types for host usage.
- Keep
engine-wasm/contracts/wml-engine.tsaligned as contract surface until fully generator-backed. - Update
browser/contracts/generated/engine-host.tsgeneration pipeline for new methods and shapes.
Contract guardrails:
- no host-specific fields in engine-owned frame/input contracts
- serialization-only target glue at adapters
- contract changes require docs and parity-test updates in same change
Migration Phases
Phase F0: Contracts and Adapters (No Behavior Change)
- define frame/input types
- add engine API methods:
renderFrame() -> EngineFramehandleInput(event: EngineInputEvent)
- keep legacy methods with wrappers:
render()handleKey()
Phase F1: Host Renderer Migration
- update WASM host sample to consume
EngineFramepath - replace Tauri frontend viewport HTML rendering with Canvas2D renderer adapter
- keep presenter/session/timeline behavior unchanged outside rendering surface
Phase F2: Input Event Surface Expansion
- add click and scroll routing from hosts to engine
- implement deterministic hit testing in engine frame/hit-region model
- keep keyboard path behavior identical
Phase F3: Internal Engine Boundary Cleanup
- split current combined layout+draw generation into:
- layout phase
- paint phase
- preserve draw ordering and focus determinism across targets
Phase F4: Cutover and Legacy Removal
- require parity suite green on native + wasm for frame/input paths
- remove legacy
RenderListand key-only API surfaces - finalize docs and coverage mappings
Determinism and Parity Gates
Must remain deterministic across native and wasm:
loadDeckContexthandleInput(KeyPress)renderFrame- script invocation side effects
- back-stack/focus transitions
- timer-driven transitions
Parity-critical coverage must include:
- snapshot parity for frame command sequence
- focus and navigation parity under identical input traces
- click hit-target parity using frame/hit-region mapping fixtures
Risks and Mitigations
- Risk: contract drift between handwritten and generated TS types.
- Mitigation: make Rust-owned contract generation authoritative and add CI drift checks.
- Risk: renderer-dependent text metrics affecting determinism.
- Mitigation: keep engine layout in logical units (rows/cols) and avoid host-measured line wrapping.
- Risk: click semantics diverge by target host.
- Mitigation: engine-owned hit-region identifiers in frame output; hosts send coordinates only.
- Risk: migration stalls due to dual-path complexity.
- Mitigation: strict phase gates and explicit legacy removal criteria.
Documentation Update Requirements Per Phase
Each migration phase PR must update:
engine-wasm/contracts/wml-engine.tsengine-wasm/README.mdbrowser/contracts/generated/engine-host.ts(generated output)docs/waves/CONTRACT_REQUIREMENTS_MAPPING.mddocs/waves/SPEC_TEST_COVERAGE.mddocs/waves/ENGINE_HOST_FRAME_WORK_ITEMS.mdstatus
Acceptance Criteria (Program-Level)
Migration is complete when:
- Engine no longer exposes legacy
RenderList/handleKeyAPI in primary host path. - Both hosts consume
EngineFrameandEngineInputEvent. - Tauri viewport rendering no longer depends on HTML line injection.
- Native/wasm parity suite covers frame/input critical paths and passes.
- Contract and docs drift checks enforce boundary consistency in CI.