ADR 036 — WebIR vs HIR unification (compare-both)
ADR 036 — WebIR vs HIR unification (compare-both)
Section titled “ADR 036 — WebIR vs HIR unification (compare-both)”Status
Section titled “Status”Accepted (2026-05-11): adopt Option B — HIR semantic core + WebIR (and sibling projections) as typed lowering targets, with explicit work to collapse duplicate emit paths and wire platform capability contracts into packaging/codegen. Option A (fold WebIR into HIR as a single IR type) is rejected for the current product phase.
Context
Section titled “Context”- The compiler pipeline is staged HIR → WebIR → TS/Rust emitters; see Explanation: Compiler lowering phases.
HirModulealready mixes semantic core, app-contract surfaces, migration-only vectors, and mobile primitives; see HIRHirFieldOwnership.- WebIR exists to centralize web UI structure + validation that string emitters duplicated (ADR 012 rationale); reactive views now prefer WebIR TSX when validation passes (
reactive.rs). - Tauri is a packaging + webview shell around the same Axum+Vite/React core; native command lowering is still a stub (
tauri_stub.rs). - Baseline split-brain map: WebIR/HIR split-brain inventory (2026).
Decision drivers (goals)
Section titled “Decision drivers (goals)”| Goal | Implication for IR shape |
|---|---|
| GUI on any platform | Need multiple projections (web UI, HTTP contract, runtime/orchestration, Tauri/mobile manifests) from one semantic program — not one monolithic IR that encodes DOM + HTTP + DB policy in one node type. |
| AI-first | Minimize duplicate pattern-match surfaces and ambiguous emit paths; maximize compiler-verified structure and stable machine-readable artifacts for agents/tests. |
Options compared
Section titled “Options compared”Option A — Full unification (WebIR folded into HIR)
Section titled “Option A — Full unification (WebIR folded into HIR)”- Definition: DOM/style/route contract nodes become native
Hir*variants; removeWebIrModule/lower_hir_to_web_ir/validate_web_iras separate stages. - Pros: One serialized IR type; fewer named pipeline stages.
- Cons: HIR becomes a god-IR for every target (Rust server, React, Tauri, future native GUI). Every typecheck/lint pass pays the cost of UI DOM nodes. Higher regression blast radius for non-UI work. Conflicts with existing projection parity model (
AppContract,RuntimeProjectionalready separate).
Option B — Core + projection (chosen)
Section titled “Option B — Core + projection (chosen)”- Definition: Keep
HirModuleas semantic core; keepWebIrModuleas the web UI projection withvalidate_web_irgates; add/extend contract-driven projections for Tauri/mobile (e.g.runtime-capabilities.v1.yaml) so packaging does not fork from@usesmetadata. - Pros: Preserves separation of concerns; matches shipped triplet parity tests (
projection_parity_test.rs) including a fixture with@back_button; allows GUI-any-platform emitters without polluting HIR with DOM-only invariants. - Cons: Requires discipline: no second “shadow” UI IR; retire legacy string emitters as WebIR coverage reaches parity (M3 in lowering doc).
Scoring rubric (1 = worst, 5 = best)
Section titled “Scoring rubric (1 = worst, 5 = best)”| Criterion | Weight | Option A | Option B | Notes |
|---|---|---|---|---|
| AI-first (single canonical story for agents) | 2× | 2 | 4 | A reduces names but increases HIR complexity agents must reason about. B keeps “semantic vs web projection” as teachable boundary. |
| Maintenance / blast radius | 2× | 2 | 4 | A touches all HIR consumers for every UI tweak. B localizes UI churn to web_ir/* + TS emit. |
| GUI-any-platform extensibility | 2× | 2 | 5 | B maps naturally to new projections (Tauri manifest, mobile plist). A risks encoding platform packs into HIR ad hoc. |
| Migration risk | 1× | 2 | 4 | A is a Big Bang rewrite of lowering + validators + serde snapshots. B is incremental (already underway). |
| Testability | 1× | 3 | 5 | B already has WebIR gates + projection parity; A must recreate equivalent coverage inside HIR tests. |
| Weighted sum (higher better) | — | 20 | 39 | — |
Decision
Section titled “Decision”Adopt Option B as the plan-of-record. Track Option A only as a hypothetical end-state if the team later proves a single IR reduces total complexity after B has eliminated legacy emitters and shrank HirModule migration surface (see GUI-native roadmap Phase 2 primitive collapse).
Consequences
Section titled “Consequences”- Documentation: Lowering explainer + packaging SSOT link this ADR and the split-brain inventory.
- Implementation: Continue WebIR as canonical reactive view;
hir_emitJSX remains compat for non-reactive paths until fully retired. - Platform: Emit machine-readable capability projection beside Tauri hints from
runtime-capabilities.v1.yaml(wired invox-tauri-codegen); future work merges module@usesinto that projection. - Non-goals (this ADR): Changing public
.voxsyntax; deletingHirModulemobile fields without a lowering target in WebIR or a siblingShellIrprojection (separate ADR if needed).
Verification
Section titled “Verification”cargo test -p vox-compiler --test projection_parity_test(bundle + per-projection determinism,@back_button, and bundle fixturecapability_ids+ distinct hashes)cargo test -p vox-tauri-codegencargo test -p vox-compiler --test web_ir_environment_gates_test(or fullweb_ir_lower_emit_testsubset in CI as already configured)cargo run -p vox-arch-check(includesprojection-bundle-*-emit-boundaryforbidden patterns inlayers.toml)