Skip to content

feat(vapor): compile Solid and Vue TypeScript views and models to Rust - #428

Draft
HalfSweet wants to merge 44 commits into
pocket-stack:mainfrom
HalfSweet:vue-aot
Draft

HalfSweet wants to merge 44 commits into
pocket-stack:mainfrom
HalfSweet:vue-aot

Conversation

@HalfSweet

@HalfSweet HalfSweet commented Sep 15, 2026

Copy link
Copy Markdown
Member

Summary

Compile Solid TSX and Vue SFC views, plus an admitted TypeScript model, into a native Rust application. With app.aot: true and app.model: "compiled", state seeds, methods, memos, effects and tasks are generated from the TypeScript implementation. The default "rust" mode retains an application-provided Rust model implementing the same generated trait.

The supported application source language is TypeScript. JavaScript is an emitted format for browser and QuickJS execution, not an additional source-language support promise. The compiled model's guest build uses the framework reaction scheduler and task state machines; its native build executes generated Rust without a guest engine.

  • Share typed View IR format 4 between Solid and Vue, while Model IR format 1 records model bodies, dependencies, schedules and coroutine states. The view generator consumes the typed contract without loading model bodies.
  • Support component props and events, mount-owned factory state and arguments, keyed rows, lifecycle rounds, scoped slots, concrete generic specializations, root context, node refs and hardware-neutral relative-axis input.
  • Preserve frame-atomic dispatch, on-demand memo reads, settled render caches, owned-value semantics, fixed-width arithmetic and bounded strings/arrays. Reject unsupported models with source-located diagnostics; there is no fallback to another implementation.
  • Lower async model functions into region-owned tasks with restart/cancellation, boundary readiness, frames/after/until, joins, aggregate waits, typed network requests and animation completion. Cancelling an animation wait drops its listener while preserving the issued motion.
  • Integrate compiled models into the Solid lab, build plans, browser/guest transforms and native host protocol. Both transform passes and model resolution follow the selected presentation entry. Demo gen/ files are ignored and generated before native builds and resource tests.
  • Add hand-authored semantic fixtures, negative cases, interpreter/TypeScript/Rust differential checks, full-view and lifecycle regressions, allocation checks and local fuzz tooling. CI runs the fixed semantic suite; fuzz campaigns and generated seed variants remain local.
  • Check generated output through focused type/structure assertions and native compilation instead of storing complete demo Rust or IR snapshots. Preserve hand-written behavior expectations and minimized regression inputs; core Model IR is regenerated from TypeScript during tests.

TypeScript and native responsibilities

Boundary Contract
Source admission .ts model implementations, .tsx views and typed Vue scripts follow their documented subsets. Arbitrary packages, native promises, timers and dynamic model constructs are not implied by TypeScript support.
Model implementation "compiled" generates the native implementation; "rust" requires handwritten Rust. A .d.ts guest preview supplies defaults and empty methods, not Rust behavior.
View to model Generated Rust calls a Rust trait. The guest and native model are separate instances; they do not share state or exchange TypeScript objects through FFI.
Model to host Ready freezes frame/time/capabilities/deliveries; Cmd carries issued work. Native async entries take a command sink and return (), with source results delivered to awaiting model tasks.
Services and devices A typed net.get declaration does not implement native networking. A host must advertise the service, handle requests/cancellations and queue results. The default Ui declares no services. Physical input and presentation remain host responsibilities.
Memory and numbers Rust AOT uses no_std with alloc. Cap bounds specified storage, not every allocation. Guest 64-bit integer precision remains limited; the zero-allocation claims below apply to selected bounded fixtures.
Targets Board admission checks declared input channels; it does not provide a port or toolchain. The earlier C cartridge compiler lives in the standalone pocket-stack/pocket-vapor repository; its sources and scripts are not restored here.

Documentation

The TypeScript and native code guide covers execution selection, generated code ownership, the Rust trait boundary, service submission and delivery, cancellation, resource limits and packaging. It includes a TypeScript network example checked against all three execution paths.

The model guide, Solid/Vue guides, shared reference, architecture, native contract, build pipeline and README now describe the compiled and handwritten model modes and distinguish TypeScript source support from engine output.

Validation

Validated rebased source: bcff0421276d08cddaf773e86597c261ba9e0a65; mainline base: 53a17f6416c3333f1171141bf996695720101ed2.
Desktop tools: Bun 1.3.14, Rust 1.93.0.

CI=true CARGO_NET_OFFLINE=true bun tools/test.ts --stage=AOT
CI=true CARGO_NET_OFFLINE=true bun test tests/platform-contracts.test.ts tests/modality.test.ts tests/host-build-inputs.test.ts tests/tape-assert.test.ts tests/test-suite.test.ts tests/font-config.test.ts tests/font-archive.test.ts tests/native-source.test.ts
CI=true CARGO_NET_OFFLINE=true bun test --conditions=browser tests/devtools.test.ts
CARGO_NET_OFFLINE=true cargo test --manifest-path engine/core/Cargo.toml
CARGO_NET_OFFLINE=true cargo test --manifest-path engine/Cargo.toml -p pocket_vapor --features model-trace
CARGO_NET_OFFLINE=true cargo test --manifest-path engine/ui-cabi/Cargo.toml
bun tests/contract.ts
git diff --check

The offline flags use the validation machine's populated Cargo cache; omit them to fetch dependencies on a fresh machine.

  • 432 AOT tests passed: 146 frontend/execution checks and 286 model semantics/resource checks. These include both Solid and Vue selected-presentation regressions, native demo compilation, three-way behavior comparison and fixed minimized regressions. Fuzz is excluded under CI=true.
  • 167 upstream integration and DevTools tests passed, preserving modality selection, manifest/build contracts, streamed-font integration, tape assertions and touch recording behavior.
  • 180 Rust tests passed: core 155, model runtime 9 and UI C ABI 16; doc-tests passed.
  • Compiler source type checking, integration type checking with strict null checks, generated-contract drift checks and whitespace checks passed.
  • The Solid lab completed 2,048 frames under the 24 MiB allocation cap; measured peak 125,625 bytes. Bounded primitive/tagged reaction and task fixtures assert zero allocations after mount. Frame timings are recorded without a latency acceptance threshold.

The rebase preserves mainline's removal of the earlier cartridge compiler. Native AOT board checks use a separate input-only profile registry; no retired C compiler, toolchain or board runtime is restored. Demo build output and full-output snapshots remain untracked, and local-only fuzz tests have an explicit exclusion in the expanded test inventory.

Acceptance is exit status 0, exact trace/state/command agreement where asserted, and the stated allocation bounds. Logs and receipts remain under ignored .pocket-build/validation/model-aot/rebase-20260921/; no per-run artifacts are committed.

Prior ESP32 compile/link validation

This compile/link receipt was recorded before the rebase at f6a78cc08a1b3053adb13a34f5e3accb45d8691d. It has not been rerun against the rebased mainline core. The existing ESP-IDF AOT embedding project built the compiled Solid lab. After exporting the project's ESP-IDF and ESP Rust environments:

cd .pocket-build/validation/model-aot/esp32/project
CARGO_NET_OFFLINE=true idf.py -B ../build \
  -DAOT_APP=solid-aot-lab -DAOT_MEMORY_PROFILE=OFF build
  • ESP-IDF: v5.5-dev-1998-gc71d74e2f8-dirty; ESP Rust: 1.97.0-nightly (8ea53bcd7 2026-07-08), LLVM 21.1.3.
  • Firmware: 620,944 bytes in a 3,145,728-byte application partition; build exit status 0 and all 44 native input hashes matched the validated sources.
  • Firmware SHA-256: 9b29944f66c3099058b06f9401eadb5b8542ee1677d97878e09f4da0a39e27f8.
  • No flashing or device-run acceptance is claimed. This establishes compilation and linking, not device frame time, pixels or physical relative-axis input.

@HalfSweet HalfSweet changed the title feat(vapor): add Vue SFC to Rust AOT pipeline feat(vapor): compile Solid and Vue TypeScript views and models to Rust Sep 20, 2026
Fix the decided scope of the Vue AOT rewrite before implementation: SFC input (template plus type-only script), the logic-module contract and its Rust trait shape, TypeScript-to-Rust type mapping with tagged numeric types, the template and expression subset with double-implemented built-ins, the semantics pinned across browser, guest and Rust classes, the per-frame compare-based update model, and the open items.
Logic module is <name>.ts or <name>.d.ts imported without an extension; props borrow and functions return owned values; every v-if group is one generated enum with an Empty variant; blocks expose first_node() instead of marker nodes; one <Text> is one text node formatted into a scratch buffer; v-for reconciles with a key-sequence fast path and a BTreeMap slow path; v-show joins v1 as a DISPLAY prop toggle; Option narrowing and enum match chains in v-if.
…code

v-text joins the v1 directive table (Text only, no children, same SET_TEXT lowering as interpolation); handlers may call emit("name", args); §8 now shows Todo.vue, todo.d.ts and Row.vue next to the Rust the compiler generates for them.
Section 9 now fixes the three-layer code generator (View IR → Rust AST → printer, text only in the printer), committed generated files with a byte-compare drift test so device builds need no Bun, the Rust-side quote!/prettyplease emitter as the alternative behind the same View IR JSON, and the per-layer tests. Open items move to section 10; build.rs mechanics are no longer open.
…type-system rules

Template expression types come from the TypeScript checker over the virtual code @vue/language-core generates, with control-flow narrowing measured on vue-tsc 3.3.11; the compiler's own checker keeps subset admission, numeric tag propagation and the Rust mapping. New §3.2 pins newtypes (__newtype tag), literal-typed constants and discriminated unions as Rust enums with data. §4 and §9 add the display trait, NodeId/StyleId newtypes, the key bound and contract-generated fixtures and mocks. §10 lists v1.1 and v2 items; the auto-mock leaves the open list.
The trait an application implements for a root component is <Name>ViewModel, its generic parameter M, and the TypeScript module beside the SFC is the view-model module; Logic disappears from generated identifiers.
Pocket Vapor keeps its name and the aot execution class; paths for the compiler, the std module, the family spec, the Rust runtime crate, generated code and tests are fixed in §10. withDefaults with literal defaults joins v1; the unannotated-number warning, the family spec with its drift guard and the differential harness are specified; stateful child components (factory + associated type + Default), ActionHandler/AxisHandler input and scoped slots are scheduled with their mechanisms.
The relative-axis ids and units stay in vapor/host/input.ts until AxisHandler lands in v1.1; the v1 family spec holds only what both runtimes implement in v1.
… inject

§7 fixes three cost rules for v1: idle frames issue no update and hosts call invalidate() after out-of-band mutation, update reads only the getters bindings name, text bindings memoize inputs rather than output. §10 schedules unit newtypes and opt-in per-value versions for v1.1 and typed provide/inject for v2.
Fine-grained invalidation stays out of the plan; the compare-based update with the three cost rules is the whole v1 story.
Connect compiled model manifests, shared typed view contracts, factory regions, node refs, frame scheduling, animations and typed service adapters across JavaScript and native hosts.

Add the three-way semantic corpus, task and capacity regressions, generated-program checks, resource validation and the compiled Solid lab. Preserve explicit async command sinks and captured until predicates.
Register every Model AOT test in the default suite and validate affected pull requests. Run the larger generated-program corpus on a nightly schedule and retain failure reproductions as workflow artifacts.
Remove scheduled fuzz runs and exclude the generated fuzz suite from CI entrypoints. Preserve local fuzz commands and fixed semantic regressions, including a maintained independent-effect reordering case.
Ignore demo gen directories and remove generated Rust and styles from the index while retaining local outputs. Generate demo artifacts in tests before native compilation, update the build documentation, and refresh the Vue demo dependency lock.
Detach the request listener without cancelling the core track. Cover both native and guest execution after motion has started, including completion without a resumed model task.
Document source admission, compiled and handwritten models, Rust traits, frame snapshots, owned values, service adapters and native host responsibilities. State TypeScript as the application source contract, distinguish engine output from supported source, and update the Solid, Vue, runtime and build guides.
Remove complete demo Rust/View IR snapshots and 36 redundant Model IR dumps. Assert typed view contracts, compile generated Rust against the handwritten model, and retain hand-authored behavior expectations, three-way differential checks, deterministic emission and minimized regression inputs.
Keep native board input declarations independent of the retired C compiler, preserve explicit local-only fuzz exclusion under the expanded test inventory, and update compiler references to the standalone repository.
Pass the selected build entry through both transform passes, Solid contract resolution and model lowering. Resolve Vue SFC roots from the selected bootstrap and preserve default-entry behavior. Cover both frameworks with executable presentation regressions and retain the recursion-limit schema annotation.
Update the explicit npm files allowlist and assert that packed artifacts include the required AOT modules and pocket_vapor workspace member.
Build the ignored style module before the standalone Model AOT stage. Reset test clocks and dispose task, service and command registrations between cases. Give presentation bundle integration tests an explicit compilation timeout.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant