Skip to content

Svelte-mineable features: Phases A, C, E + ADR-032/033 + test-suite cleanup#63

Merged
brbrainerd merged 32 commits intomainfrom
claude/amazing-bohr-f643fc
May 3, 2026
Merged

Svelte-mineable features: Phases A, C, E + ADR-032/033 + test-suite cleanup#63
brbrainerd merged 32 commits intomainfrom
claude/amazing-bohr-f643fc

Conversation

@brbrainerd
Copy link
Copy Markdown
Collaborator

@brbrainerd brbrainerd commented May 3, 2026

Summary

Ships three of the seven Svelte-mineable items identified in the Svelte vs React frameworks researchPhase A (vox_validate_source MCP tool with structured autofix), Phase C (segment-aware route conflict detection + typed routePath builder), and Phase E (@reactive decorator + cross-call dep inference + tier-2 over-track hint surface) — plus drafts ADR-032 (.vox.ui reactive modules) and ADR-033 (typed parametric fragment primitive) ahead of their respective phases. Net: 18 commits across compiler, MCP orchestrator, contracts, docs, and tests.

Concurrent housekeeping cleared seven pre-existing test failures by accepting orphaned snapshot baselines from the 2026-05-01 insta migration, inverting two stale actor/workflow tombstone tests against AGENTS.md TASK-2.6 (un-tombstoning), tombstoning the @scheduled test per ADR-028, and adding a contributor doc note on the snapshot-baseline workflow so the orphan pattern doesn't recur. cargo test -p vox-compiler went from 7 known failures + 8 ignored to 0 failures + 9 ignored.

What landed (per phase)

  • Phase A — MCP iteration loop (8ae08ed, 423a44e, c04a8b4)

    • New vox_validate_source MCP tool — text-in/text-out validation; AI assistants iterate against the compiler without writing intermediate files.
    • DiagnosticInfo.{code,fixes} plumbed end-to-end (autofix data was already produced by vox_lsp::typeck_diagnostic_to_lsp but dropped at the MCP boundary).
    • User-facing how-to with Claude Desktop + Cursor wiring snippets (docs/src/how-to/how-to-mcp-vox-validate.md); surfaced in llms.txt.
  • Phase C — Route safety (e0dd30f, 5be659e, 37131fe)

    • RoutePattern parser + Overlap::{None, Shadowed, Ambiguous} segment-aware analysis (15 unit tests).
    • validate_express_route_emit_input upgraded from exact-string-match HashSet to overlap-aware; catches /:a/:b vs /:x/:y ambiguity, allows literal-shadows-param.
    • Typed routePath builder + KnownRoute union emitted alongside the existing voxRoutes manifest. Authors write <a href={routePath["/users/:id"](userId)}> and get full type-flow on path params.
  • Phase E — Reactive dep inference (5db51fc, a1c6f46, 6691bf8)

    • New @reactive decorator — opts a free fn into cross-call dep tracking from derived / effect.
    • Single-level cross-call analyzer with self-reference guard (no whole-program escape analysis; conservative under-tracking otherwise).
    • Tier-2 // dep_inference.over_track hint comment emitted in TSX above any useMemo / useEffect whose body calls visible in-module fns missing @reactive. Names the offending callees so AI/human readers see the gap.
  • ADRs drafted (301fa77, af64ac6) — both status roadmap, awaiting acceptance:

    • ADR-032 .vox.ui reactive modules — gates Phase D code (~2 weeks once accepted).
    • ADR-033 typed parametric fragment primitive — gates Phase F code; Phase F is itself gated on Phase 6 (TASK-6.1) primitives stabilizing.
  • Cleanup (0fcb734, 4df880e, 277942a, e45629d, f0a3d1b) — orphan-snapshot acceptance across reactive_smoke/state_machine_integration/web_ir_lower_emit (18 baselines), @scheduled test tombstoning, actor/workflow test inversion, inbound-link goldens fix, view_roots-setup gap fix in broken-link tests, contributor doc note. Every fix verified pre-existing on merge-base dcae7e83a via stash test.

What this is not

  • Not Phase D, F, or G code. Those need ADR-032/ADR-033 acceptance and (for Phase F) the Phase 6 primitive surface to land first. The drafted ADRs document the design choices for whoever picks them up.
  • Not a Phase B (rescoped) start. Phase B is folded into the Phase 6 (TASK-6.1) typed-primitive design per the implementation plan.
  • No url-decl ↔ routes cross-validation. Phase C slice A bypassed that need by generating routePath builders directly from route patterns.

Ludus / gamify (if applicable)

Testing

cargo test -p vox-compiler                       # 281 lib + ~120 integration, all green
cargo test -p vox-orchestrator --lib             # 5 new code_validator tests pass; 10 http_gateway tests pass
cargo run -p vox-doc-pipeline -- --check         # all doctest fences + index drift checks pass
cargo build -p vox-compiler                      # zero warnings
cargo build -p vox-integration-tests --tests     # zero warnings

Highlights from the new test surface:

  • codegen_ts::route_pattern::tests::* — 15 tests covering parse, identical-literals, disjoint, literal-shadows-param, two-param ambiguity, wildcard absorption (one-sided + two-sided), length-mismatch, root-vs-empty.
  • codegen_ts::hir_emit::state_deps::tests::cross_call_recursion_* — 2 tests covering @reactive callee descent + self-reference guard.
  • codegen_ts::reactive::tests::derived_calling_{reactive,non_reactive}_callee_* — 4 tests covering Phase E end-to-end + Phase E tier-2 hint surface.
  • codegen_ts::route_manifest::tests::route_path_builder_* — 5 tests covering typed builder emit (literal, param, wildcard, empty cases).
  • mcp_tools::code_validator::tests::* — 5 tests covering autofix-data extraction, validate_source happy path, lazy-generation guard.
  • route_express_emit_test::validate_*_overlap — 4 new integration tests pinning the segment-aware conflict policy.

Data Storage / DB

  • N/A — crates/vox-db/src/schema/manifest.rs not touched; no baseline version change.

Reviewer notes

  • 18 commits stacked; recommend reviewing per-commit rather than as a single squash. Each commit is self-contained, has its own test coverage, and verifies the relevant suite green.
  • Pre-existing failures fixed are independently verifiable — every cleanup commit message names the pre-existing failure it addresses and notes the merge-base stash test that confirmed the failure predated this work.
  • ADR-032 and ADR-033 are the longest-tail items. They drive multi-week Phase D + Phase F implementations. Worth the most thorough review.
  • The orphan-snapshot pattern is now documented in contribution-loop.md. Future PRs touching snapshot tests should follow the new "After merging a snapshot-touching PR" section to avoid re-accumulating the cleanup debt.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Module-scope .vox.ui reactive modules with generated React provider/hooks and typed fragment declarations emitting typed React components
    • @reactive opt-in for deeper dependency inference and safer reactive behavior
    • Generated typed route builders and KnownRoute union for safer routing
    • React-friendly state-machine hooks emission
  • Bug Fixes

    • Improved route overlap detection with ambiguity reporting
  • Tooling

    • New vox_validate_source tool with stable diagnostic codes and structured autofix suggestions
  • Documentation

    • ADRs and how-to guide for .vox.ui, fragments, and MCP validator integration

AI Assistant and others added 19 commits May 2, 2026 23:43
…icInfo

Phase A of the Svelte-mineable features plan. Two MCP-side gaps closed:

1. New `vox_validate_source` tool — validates Vox source passed as a string
   (no filesystem read), enabling AI assistants to iterate against the compiler
   without writing intermediate files. Wired through dispatch, input_schemas,
   http_gateway allowlist, and the canonical tool-registry YAML.

2. `DiagnosticInfo` now carries `code` and `fixes` fields. The autofix data
   (`{ label, replacement, range }`) was already being produced by
   `vox_lsp::typeck_diagnostic_to_lsp` and stuffed into `Diagnostic.data`,
   but was being dropped at the MCP boundary. The conversion now extracts it.
   Both `vox_validate_file` and the new `vox_validate_source` benefit.

Refactored the heuristic pre-validation guard (TODO/macro detection) into a
shared helper so both tools apply the same checks consistently.

Tests: 5 new unit tests in code_validator covering parse_fix happy path,
parse_fix missing-fields rejection, lsp_diagnostics_to_info code+fixes
extraction, validate_source diagnostics for malformed source, and
validate_source short-circuit on TODO patterns. All pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase C foundation. Pure utility module — not yet wired into routes.rs.

Today's route conflict detection at codegen_ts/routes.rs:87 builds a
HashSet<(Method, String)> of literal path strings, so /users/:id and
/users/me are not flagged as overlapping. This module ships the parser and
overlap analysis the next slice will integrate.

API:
- Segment::{Literal(String), Param(String), Wildcard}
- RoutePattern::parse(&str) — tolerates redundant slashes; recognizes
  ":name" params and "*" wildcards.
- RoutePattern::overlap_with(other) -> Overlap::{None, Shadowed, Ambiguous}.

Wildcard semantics: one-side wildcard yields Shadowed (the other side is
more specific); two-side wildcard yields Ambiguous. Literal-vs-Literal
mismatch short-circuits to None. Literal-vs-Param yields Shadowed when
remaining segments overlap.

Tests: 15 unit tests covering identical-literals, disjoint-literals,
literal-shadows-param (both directions), two-param ambiguity, wildcard
absorption (one-sided + two-sided), length-mismatch, and root-vs-empty.
All pass.

Integration into routes.rs (replacing the HashSet check, emitting
routes.overlap.shadowed / routes.overlap.unresolvable_precedence
diagnostics) is the next slice and is intentionally NOT in this commit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… ADR-032

Three new docs grounding the GUI roadmap with verified codebase facts:

1. svelte-vs-react-frameworks-research-2026.md — Comparative analysis of
   Svelte 5/6, Next.js 15/16, and TanStack Start v1.0 against Vox's actual
   React-emit baseline. Confirms Vox should NOT add Svelte as an emit target
   but identifies seven mineable items (M1–M7) that extend existing surfaces:
   reactivity outside `component { }`, cross-call auto-dep inference, more
   directive families on `on:*`-style attributes, `.vox.ui` reactive modules,
   MCP tool polish, route segment-aware overlap + typed `href`, and
   reactive-class state-machine instances. SvelteBench/vox-bench deferred
   (no defined consumer for the metric).

2. svelte-mineable-features-implementation-plan-2026.md — Phased plan
   (Phases A–G) with concrete file changes, scope estimates, and explicit
   ADR triggers. Critical alignment note: JSX is being deprecated in favor
   of Phase 6 typed semantic primitives (TASK-6.1), so Phase B is rescoped
   as "binding conventions on primitives" not standalone JSX directive work.
   Phase A is now shipped (commit 8ae08ed); Phase C foundation shipped
   (commit e0dd30f).

3. ADR-032 .vox.ui reactive modules — gates Phase D. Documents the suffix
   convention, the file-discovery wire-up surface (no single dispatch point
   today; commits to a `vox_compiler::module::FileKind::from_path` helper),
   and the Phase D / Phase E coupling around cross-module reactive
   read-tracking. Status: roadmap.

Doc-pipeline indices regenerated; --check passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase C integration. Wires the RoutePattern utility from the previous
commit into validate_express_route_emit_input.

Replaces the HashSet<(Method, String)> exact-match check with pairwise
RoutePattern overlap analysis. Conflict policy:

- Empty path → error (unchanged).
- Same method, Overlap::Ambiguous → error (subsumes the old "duplicate
  literal path" case; also catches /:a/:b vs /:x/:y which the old check
  missed).
- Same method, Overlap::Shadowed (e.g. /users/me + /users/:id) → allowed;
  the more specific route wins by source order. (Routing a structured
  routes.overlap.shadowed info diagnostic through the typeck channel is
  a follow-up.)
- Different methods → no conflict (GET /users/:id and POST /users/:id
  coexist normally).

Tests: 4 new integration tests in route_express_emit_test exercising the
two-param ambiguity case, literal-shadowing-param (allowed), cross-method
overlap (allowed), and disjoint literal prefixes (allowed). The original
duplicate-route test is updated to match the new "ambiguous" wording —
identical literals are now correctly classified as Overlap::Ambiguous.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…hase E)

Phase E foundation slice. The decorator and the analyzer extension that
consumes it ship together; wiring them into reactive.rs callsites is the
next slice (so this commit is observable but not yet behaviour-changing
for any existing reactive component).

Lexer / AST / HIR plumbing:
- New `@reactive` token (`Token::AtReactive`) with Display arm.
- New `is_reactive: bool` field on AST `FnDecl` and HIR `HirFn`, lowered
  one-to-one. Default false everywhere; populated by the parser when
  `@reactive` precedes `fn`. Two FnDecl construction sites updated
  (head.rs:775 fn parser; mod.rs:247 synthetic main wrapper); four HirFn
  construction sites in hir/lower/decl.rs got the explicit `false`;
  vox-test-harness's `hir_fn` builder default is also `false`.

Analyzer extension (codegen_ts/hir_emit/state_deps.rs):
- New `extract_state_deps_with_callees(expr, state_names, reactive_callees)`
  variant. The callees map is `name -> body` of `@reactive` free functions
  visible to the caller. When the walker encounters a `HirExpr::Call` whose
  callee resolves to a name in the map, it descends one level into the
  body so reads of reactive bindings through the call participate in
  dependency tracking.
- Bounded per the "K-complexity" guidance: single-level recursion only;
  a `visited: HashSet<String>` prevents infinite loops on direct or
  mutual recursion; functions without `@reactive` are not descended into
  (conservative under-tracking, opt-in extension); no whole-program escape
  analysis.
- The original `extract_state_deps()` is preserved as a thin wrapper
  passing an empty callees map — every existing caller's behavior is
  byte-identical.

Tests: 2 new state_deps unit tests cover the cross-call descent finding
state through a `@reactive` callee body, and the self-reference recursion
guard. All 279 vox-compiler unit tests + 8 route_express integration tests
pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… emit

Phase E wiring slice — turns the @reactive decorator into observable
behavior for emitted reactive components.

Changes:
- generate_reactive_component (reactive.rs:702) builds a HashMap of
  @reactive-annotated free functions in the module (name → body) and
  passes it to extract_state_deps_with_callees at the two `derived` and
  `effect` dep-array call sites (lines 767/776, now 776/787).
- Top-level decl dispatch in parser/descent/mod.rs (4 sites: skip-recovery
  list, async-fn dispatch, top-level fn dispatch, pub-fn dispatch) gains
  Token::AtReactive arms so `@reactive fn name() { ... }` parses at module
  scope. Without this, `@reactive` was only recognized inside the
  `parse_fn_decl` body.
- Re-exports `extract_state_deps_with_callees` from
  codegen_ts/hir_emit/mod.rs (alongside `extract_state_deps`).

Tests: 2 new end-to-end unit tests in reactive.rs prove the wiring:
- derived calling a `@reactive` free function gets the underlying state
  dep in its useMemo dep array (`[count]`).
- The same source without `@reactive` gets `[]` — conservative
  under-tracking by design (opt-in policy preserved).

Full vox-compiler unit suite: 281 passed (was 279, +2).

Pre-existing reactive_smoke_test snapshot failures (37 .snap.new files
without matching baselines) predate this change — verified by re-running
the failing test against a stash of just the wiring edits.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ap.new)

The 2026-05-01 snapshot migration (commit fdfe71d) replaced 341
`assert!(contains())` calls with 71 `insta::assert_snapshot!` calls and
configured CI to auto-generate `.snap` files via `INSTA_UPDATE=new`. CI
uploads them as artifacts for developers to commit, but the artifacts were
never committed, leaving the entire reactive_smoke_test suite reporting
0/42 passing locally — every new test produced a `.snap.new` and reported
"snapshot assertion failed" on first run.

Spot-checked all 13 .snap.new files before promotion: each contains
plausible idiomatic React/TSX emit (e.g., `useMemo(() => count * 2,
[count])`, `data-vox-island="DataChart"`, `import "./Box.css"`). No
suspicious tombstone, error placeholder, or empty fixture. Promoted via
straight rename `.snap.new` → `.snap` (equivalent to `cargo insta accept`).

Iterated the accept→test cycle 4 rounds because each successful run
exposed previously-skipped tests, and the file's `REACTIVE_SMOKE_SERIAL`
mutex was being poisoned by the first failing test, cascading into ~28
spurious "mutex poisoned" panics in unrelated tests.

After: vox-compiler full test suite is 281 lib + 42 reactive_smoke + 8
route_express + … all green. One pre-existing failure in
`golden_vox_examples_test` (web_ir_validate.route.unreachable on
blog_fullstack `/edit`) confirmed to predate this change via stash check
— flagged for separate triage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The Web IR validator at crates/vox-compiler/src/web_ir/validate.rs:526
emits `web_ir_validate.route.unreachable` for any non-root route in the
`routes { }` block that has no literal `<a href="…">` or `<… to="…">`
inbound link in the rendered DOM ("may be unreachable via dynamic
navigation"). Two goldens declared routes with no inbound link:

- blog_fullstack.vox: BlogHome view didn't link to /edit (PostEditor).
- web_routing_fullstack.vox: Home view didn't link to /section (Section).

Validator behavior is correct (real apps either link to their routes
literally or get caught by this check). Fix is in the goldens — both
home views now include a literal `<a href="…">` to the secondary route,
modeling realistic navigation shape.

Brings golden_vox_examples_test from 0/1 to 1/1. Combined with the prior
snapshot cleanup, the only known-failing test in vox-compiler is now
test_ir_emission_includes_scheduled_jobs_in_web_ir — independently
verified to predate this session (fails identically on merge-base
dcae7e8). That failure is downstream of ADR-028 durability deprecation
removing the @scheduled web_ir lowering path; out of scope here.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…baselines

Three pre-existing test failures on merge-base dcae7e8 fixed:

1. test_ir_emission_includes_scheduled_jobs_in_web_ir — ADR-028 removed
   `@scheduled` from the public grammar; pipeline::run_frontend_str now
   rejects it early via check_adr028_reserved_keywords (pipeline.rs:139)
   and returns an empty HIR. The test asserts the pre-ADR behavior. Marked
   #[ignore] with a tombstone comment pointing at ADR-028 and the path to
   restore it (when a real scheduler runtime ships).

2. actor_is_tombstoned + workflow_is_tombstoned — per AGENTS.md §Grammar
   Unification, TASK-2.6 Path A (commit 080b3f8) UN-tombstoned `actor`
   and `workflow`; both now parse cleanly and lower to HirFn with a
   DurabilityKind. Inverted both tests to assert the documented current
   behavior (no Tombstoned ParseErrorClass on parse). The remaining
   tombstone tests for `@component` and `http` keep their expected-error
   semantics — those surfaces stay retired.

3. state_machine_integration_test snapshots — same orphaned-.snap.new
   pattern as the prior reactive_smoke cleanup (commit 0fcb734):
   2 baselines from the 2026-05-01 insta migration that were never
   committed. Spot-checked content (correct discriminated-union TS emit
   for Door/Light state machines), promoted via .snap.new → .snap rename.

Remaining pre-existing vox-compiler failures (independently verified on
merge-base): 4 in web_ir_lower_emit_test (route broken-link, counter
view, op_s126/s219 web_ir preview parity). Out of scope for this cycle.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three more pre-existing failures fixed:

1. web_ir_lowering_validates_and_emits_counter_view, op_s126_fixture_pack_d2,
   op_s219_final_web_ir_parity_fixture_preview_literal — same orphaned
   .snap.new pattern as the prior reactive_smoke (commit 0fcb734) and
   state_machine (commit 4df880e) cleanups. 3 baselines from the
   2026-05-01 insta migration spot-checked (correct WebIR preview TSX
   output), promoted via .snap.new → .snap rename.

2. web_ir_validate_route_broken_link_is_error — real test bug. The
   broken-link validator at validate.rs:493 only walks DOM nodes
   reachable from a declared view_root (the "orphan / detached nodes
   must not count as route references" comment). The test pushed the
   link into m.dom_nodes but never registered a view_root, so the
   validator never traversed it, never produced the broken_link
   diagnostic, and the test panicked at "expected broken_link".

   The companion test web_ir_validate_route_matching_link_is_ok had
   the same setup gap but passed by accident — its assertion
   ("no broken_link") is trivially satisfied when the validator never
   inspects the link. Both tests now register the link as a view_root
   so they actually exercise the validator.

   Also pinned link_element's id to DomNodeId(0) so it can be addressed
   directly (the BFS uses the id field as the array index into
   dom_nodes; the previous id 99 + array position 0 mismatch would
   have masked any reachability test).

Result: cargo test -p vox-compiler is now 0 failures.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three separate cleanup commits in 2026-05 (commits 0fcb734, 4df880e,
e45629d) were needed to drain orphan .snap.new files accumulated since
the 2026-05-01 insta migration (commit fdfe71d). Root cause: CI runs
with INSTA_UPDATE=unseen and uploads new snapshots as the
`insta-snapshots` artifact, but no one downloaded + committed the
artifact after merging snapshot-touching PRs. Every later contributor
then saw snapshot tests fail locally on tests they didn't touch.

Documents the gap in contribution-loop.md "After merging a
snapshot-touching PR" section: two recovery options (run locally before
merge / commit artifact after merge), what an orphan looks like
(`tests/snapshots/*.snap.new` with no sibling `.snap`), and the
spot-check + accept workflow when cleanup is needed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Surfaces conservative under-tracking from the cross-call dep analyzer
(commit 5db51fc) so AI/human readers of generated TSX see why a
useMemo/useEffect dep array might miss reactive reads through a call.

state_deps.rs:
- New `DepAnalysis` struct returning both `deps` and `unannotated_calls`
  (sorted, deduped lists).
- New `extract_state_deps_with_diagnostics(expr, state_names,
  reactive_callees, visible_fn_names) -> DepAnalysis` — the dep walker
  now also flags every Call(Ident(name)) where `name` is in
  `visible_fn_names` but not in `reactive_callees` (i.e., known in-module
  fn missing `@reactive`). Method calls / stdlib calls / unknown
  identifiers stay silent — only in-module fns where the author can
  meaningfully add `@reactive` are reported.
- Existing `extract_state_deps` and `extract_state_deps_with_callees`
  retained as thin wrappers; behavior byte-identical.
- Internal `collect_deps_and_calls` is now the single recursive walker;
  removed the back-compat `collect_deps` shim that became unused once
  every recursion site was updated to pass the new params.

reactive.rs:
- generate_reactive_component now builds `visible_fn_names` from
  `hir.functions` and calls the diagnostics-aware variant for both
  `derived` and `effect` dep computation.
- New `emit_dep_inference_hints(out, owner, unannotated)` helper writes
  a `// dep_inference.over_track` comment line above the useMemo /
  useEffect when the analysis reports any unannotated callees:

    // dep_inference.over_track: `doubled` calls [`opaque`] which lack
    // `@reactive` — reactive reads inside those bodies will not trigger
    // re-runs. Add `@reactive` to the callee(s) to opt in to cross-call
    // dep tracking.
    const doubled = useMemo(() => opaque(count), [count]);

Tests: 2 new reactive end-to-end tests prove the hint fires on
non-@reactive callees and stays silent when every call is annotated.
Full vox-compiler suite green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…nifest.ts

Adds a typed URL surface to the framework-agnostic route manifest emit
so authors can replace hand-formatted href strings with type-checked
builder calls.

Generated alongside the existing `voxRoutes: VoxRoute[]` export:

    export type KnownRoute = "/" | "/edit" | "/users/:id";

    export const routePath = {
      "/": (): string => "/",
      "/edit": (): string => "/edit",
      "/users/:id": (id: string): string => `/users/${id}`,
    } as const;

Author writes `<a href={routePath["/users/:id"](userId)}>` — full
type-flow on path params; typo'd routes ("/usrs/:id") fail at the
KnownRoute gate.

Reuses the segment-aware RoutePattern parser from the Phase C
foundation (commit e0dd30f). Wildcards are exposed as identity (no
substitution; rest-segment semantics are a separate decision); they
still appear in KnownRoute so authors can reference them.

Tests: 5 new unit tests in route_manifest::tests covering KnownRoute
union emit, typed param builder, literal identity, wildcard identity,
and the empty-routes-no-emit case. Full vox-compiler suite green.

Companion to Phase E tier-2 (commit 6691bf8): both shipped in one
session because they're independent and isolated. The remaining
Phase C work (url-decl ↔ routes cross-validation) is the next slice.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…E shipped

Reconciles the Svelte-mineable features implementation plan with what's
actually landed across the 2026-05-02→05-03 work:

- Phase A — vox_validate_source + autofix surfacing: SHIPPED.
- Phase C — RoutePattern foundation, segment-aware overlap integration in
  validate_express_route_emit_input, and the typed routePath builder
  (KnownRoute union + per-pattern URL-builder map): SHIPPED.
- Phase E — @reactive decorator, cross-call dep inference, end-to-end
  wiring in reactive emit, and the dep_inference.over_track tier-2 hint
  comment surface: SHIPPED.
- Phase D — ADR-032 drafted; awaiting acceptance.
- Phases B, F, G unchanged (deferred / pending).

Also documents the slice-A bypass: emitting routePath builders directly
from route patterns in routes.manifest.ts removed the need for
url-decl ↔ routes cross-validation. Explicit `url` decls remain a
general-purpose typed-URL construct independent of `routes { }`; the
deferred slice B work is documented as no-longer-needed.

Plus shipped-this-session note covering the housekeeping: ADR-032 draft,
three orphan-snapshot cleanups (reactive_smoke, state_machine,
web_ir_lower_emit), two stale-test fixes (ADR-028 @scheduled tombstone,
TASK-2.6 actor/workflow un-tombstoning inversion), inbound-link goldens
fix, view_roots setup gap fix in the broken-link tests, and the
contributor doc note on the snapshot-baseline workflow that triggered
the cleanups.

Net: vox-compiler test suite went from 7 known failures to 0.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Documents the Phase F design ahead of implementation, while Phase F code
is still gated on TASK-6.1 (Phase 6 typed semantic primitives) per the
implementation plan.

ADR-033 covers:

- Syntax: new `fragment` bare-keyword scope with body shape mirroring
  `view:` (single markup expression). Per AGENTS.md grammar policy,
  `fragment` qualifies as a new scope-opening keyword (not a decorator)
  because its scope rules and validation differ structurally from `fn`.

- Type system: `Fragment[(T1, T2, …)]` as the type of a parametric
  fragment value. Empty arg list valid. Three new diagnostic codes:
  fragment.required_prop_missing, fragment.arg_arity_mismatch,
  fragment.arg_type_mismatch.

- Render shape: JSX-shaped `<RenderFragment of={row} args={(item,)} />`.
  Decorator-shaped `@render` rejected because it doesn't compose inside
  for-loops, conditionals, and other fragments.

- Lowering: typed React function components (Fragment[(Item,)] →
  (args: { item: Item }) => React.ReactElement); compile-time
  validation removes any need for fragment-specific runtime machinery.

- Coexistence with Phase 6 primitives: orthogonal axes (authoring vs
  emission). Fragment bodies can use Phase 6 primitives; Phase 6
  primitives can accept Fragment[…] props.

Seven alternatives explicitly considered + rejected (slot, block,
template, decorator-shaped @Fragment, decorator-shaped @render, fn-as-
fragment value without keyword, Svelte's {#snippet} literal syntax).
Four open questions documented (local-fragment scope inside component,
recursion, render-shorthand, .vox.ui exports).

Status: roadmap (drafted, awaiting acceptance). Sibling to ADR-032.

Phase G prep verified: vox-runtime/src/ has no existing state-machine
module; Phase G adds a new file there. Implementation plan's existing
"helpers land in vox-runtime (no vox-stdlib exists)" note is correct;
no plan update needed beyond marking ADR-033 as drafted.

Doc-pipeline indices regenerated; --check passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Closes the Phase A documentation loop. Step-by-step how-to for:
- Claude Desktop (claude_desktop_config.json snippet, paths for macOS
  and Windows, restart instructions).
- Cursor (Settings → Features → MCP snippet).

Documents the diagnostic shape AI clients should consume — `code`
(stable identifier; classify by this, never by message text) and
`fixes` (machine-applicable {label, replacement, range}). Walks
through the canonical iterate-against-the-compiler loop with a
deliberately-buggy <img> example and shows how the assistant
converges in two round-trips instead of guessing.

Includes the most useful diagnostic-code namespaces (a11y, route,
routes.overlap, dep_inference.over_track, island) with brief
"triggered when" explanations so AI prompts can reference codes
directly.

Comparison table for the three exposed tools (vox_validate_source,
vox_validate_file, vox_compiler::ast_inspect) and when to pick each.
Three troubleshooting entries covering the common failure modes
(server-not-running, fixes-empty, repeated-bad-suggestions).

Surfaces this how-to from the well-known/llms.txt agent-discovery
path and cross-links to the comparative research, the implementation
plan, and the canonical tool registry.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-complete

Two micro-updates closing the Phase A loop:

1. llms.txt — new "AI Coding-Assistant Integration" section pointing
   at the how-to-mcp-vox-validate page so AI clients discovering the
   repo via the .well-known path land directly on the assistant-wiring
   instructions.

2. Implementation plan Phase A row — was marked "✅ Shipped" describing
   only the code commit. Now reads "✅ Code + docs complete" and links
   the how-to commit alongside the code commit. Distinguishes Phase A
   from the other "✅ Shipped" rows (C, E) which had no equivalent
   user-facing docs gap.

Doc pipeline --check passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pre-push hook caught a missing field on the HirFn literal in
vox-integration-tests/tests/metamorphic_axiom_test.rs that I missed
when adding is_reactive in commit 5db51fc. Same one-line additive
fix pattern as the hir_builders + hir/lower/decl.rs sites updated in
that commit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…hims

Phase E tier-2 (commit 6691bf8) made `extract_state_deps_with_diagnostics`
the active codegen path; the original `extract_state_deps` and
`extract_state_deps_with_callees` shims are no longer called from
reactive.rs. Their tests in state_deps::tests still exercise them.

Two cleanups:
- Drop the dead imports from reactive.rs and the dead re-exports from
  hir_emit/mod.rs (only extract_state_deps_with_diagnostics is reachable
  from the rest of the crate now).
- Mark both shims #[allow(dead_code)] with a comment noting the tests
  keep them honest. They stay public-API for any future caller that
  doesn't need the diagnostics surface.

Net: no behavior change; just stops `cargo build -p vox-compiler` from
emitting three new warnings.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

Warning

Rate limit exceeded

@brbrainerd has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 34 minutes and 40 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4d8f282b-deb1-49b1-b5ae-b218d2630719

📥 Commits

Reviewing files that changed from the base of the PR and between 68b7ad5 and 55d9ee8.

📒 Files selected for processing (25)
  • crates/vox-compiler/src/ast/decl/types.rs
  • crates/vox-compiler/src/ast/decl/ui.rs
  • crates/vox-compiler/src/codegen_ts/emitter.rs
  • crates/vox-compiler/src/codegen_ts/fragment_emit.rs
  • crates/vox-compiler/src/codegen_ts/hir_emit/mod.rs
  • crates/vox-compiler/src/codegen_ts/mod.rs
  • crates/vox-compiler/src/codegen_ts/reactive.rs
  • crates/vox-compiler/src/codegen_ts/reactive_module_emit.rs
  • crates/vox-compiler/src/codegen_ts/routes.rs
  • crates/vox-compiler/src/fmt/printer.rs
  • crates/vox-compiler/src/hir/lower/mod.rs
  • crates/vox-compiler/src/hir/nodes/decl.rs
  • crates/vox-compiler/src/lexer/token.rs
  • crates/vox-compiler/src/parser/descent/decl/head.rs
  • crates/vox-compiler/src/parser/descent/mod.rs
  • crates/vox-compiler/src/parser/descent/tests.rs
  • crates/vox-compiler/tests/web_ir_lower_emit_test.rs
  • crates/vox-corpus/src/corpus/extract_vox/part_ast.rs
  • crates/vox-mens/src/training/core.rs
  • crates/vox-orchestrator/src/mcp_tools/code_validator.rs
  • docs/src/SUMMARY.md
  • docs/src/adr/032-vox-ui-reactive-modules.md
  • docs/src/adr/033-typed-fragment-primitive.md
  • docs/src/architecture/architecture-index.md
  • docs/src/feed.xml
📝 Walkthrough

Walkthrough

Adds .vox.ui module-scope reactive members and typed parametric fragment declarations; introduces @reactive for bounded cross-call dep inference with diagnostics/hints; emits fragment and reactive-module TSX artifacts; upgrades route overlap detection to segment-aware analysis; adds in-memory MCP tool vox_validate_source with structured diagnostics/fixes; and implements a runtime reactive state machine type.

Changes

Compiler + Codegen + Parser + HIR + Runtime + MCP (single cohesive DAG)

Layer / File(s) Summary
Lexer / Tokens
crates/vox-compiler/src/lexer/token.rs
Added Token::AtReactive and Token::Fragment token variants and Display strings.
File-kind discovery
crates/vox-compiler/src/lib.rs, crates/vox-compiler/src/module.rs
New module submodule and FileKind enum with from_path and allows_module_scope_reactive_members for .vox.ui.
Parser: top-level decls & functions
crates/vox-compiler/src/parser/descent/mod.rs, crates/vox-compiler/src/parser/descent/decl/head.rs, crates/vox-compiler/src/parser/descent/tests.rs
Parser gains file_kind mode; parses fragment declarations; parses module-scope reactive members into Decl::ReactiveModule; recognizes @reactive pre-attribute and sets FnDecl.is_reactive; added parser tests.
AST: decl types
crates/vox-compiler/src/ast/decl/fundecl.rs, crates/vox-compiler/src/ast/decl/types.rs, crates/vox-compiler/src/ast/decl/ui.rs
Added FnDecl.is_reactive; new Decl::Fragment and Decl::ReactiveModule variants; introduced FragmentDecl and ReactiveModuleDecl structs.
HIR nodes & lowering
crates/vox-compiler/src/hir/nodes/decl.rs, crates/vox-compiler/src/hir/lower/mod.rs, crates/vox-compiler/src/hir/lower/decl.rs
HIR gains fragments and reactive_modules collections and HirFn.is_reactive; lowering maps FragmentHirFragmentDecl and ReactiveModuleHirReactiveModule, propagating is_reactive.
Formatter / Printer
crates/vox-compiler/src/fmt/printer.rs
Printer emits fragment and reactive module source forms (print arms and helpers).
Fragment TSX emission
crates/vox-compiler/src/codegen_ts/fragment_emit.rs, crates/vox-compiler/src/codegen_ts/mod.rs, crates/vox-compiler/src/codegen_ts/emitter.rs
Added FRAGMENTS_FILENAME, emit_fragment_decls(hir) producing fragments.tsx with typed React function components; integrated into emitter pipeline.
Reactive-module TSX emission
crates/vox-compiler/src/codegen_ts/reactive_module_emit.rs, crates/vox-compiler/src/codegen_ts/mod.rs, crates/vox-compiler/src/codegen_ts/emitter.rs
Added emit_reactive_modules(hir) producing <PascalName>Provider.tsx files with Context/Provider/hook; emits state/useMemo/useEffect wiring; integrated into emitter.
Cross-call dependency analysis
crates/vox-compiler/src/codegen_ts/hir_emit/state_deps.rs, crates/vox-compiler/src/codegen_ts/hir_emit/mod.rs
Replaced simple state-read walker with extract_state_deps_with_diagnostics and DepAnalysis (deps + unannotated_calls); supports one-level descent into @reactive callees, visited set to bound recursion, and new tests.
Reactive TSX emission with hints
crates/vox-compiler/src/codegen_ts/reactive.rs
Compute reactive_callees and visible_fn_names; call extract_state_deps_with_diagnostics for derived/effect; emit // dep_inference.over_track hint when unannotated in-module calls exist; added Phase E tests.
Route pattern & overlap detection
crates/vox-compiler/src/codegen_ts/route_pattern.rs, crates/vox-compiler/src/codegen_ts/routes.rs
New Segment, RoutePattern, Overlap with parse() and overlap_with(); validator now uses segment-aware overlap analysis and errors on Ambiguous only; added tests.
Route manifest builders
crates/vox-compiler/src/codegen_ts/route_manifest.rs
Added emit_route_path_builder producing KnownRoute union and routePath typed builders for params/literals/wildcards; tests added.
State-machine React hook emission
crates/vox-compiler/src/codegen_ts/state_machine_emit.rs
Emit React import and use<Name>StateMachine hook (useState/useCallback + reducer call) alongside generated reducers; added test.
Runtime reactive state machine
crates/vox-runtime/src/lib.rs, crates/vox-runtime/src/state_machine.rs
New state_machine module and ReactiveStateMachine<S,E> with new, state, send, Clone, and tests.
MCP validator tool: params & diagnostics
crates/vox-orchestrator/src/mcp_tools/params.rs
Added ValidateSourceParams (in-memory source + optional virtual_path); extended DiagnosticInfo with optional code and fixes: Vec<FixInfo>; added FixInfo.
MCP validator tool: implementation
crates/vox-orchestrator/src/mcp_tools/code_validator.rs
Added pre_validation_guard (detect lazy-generation/unsupported syntax); lsp_diagnostics_to_info + parse_fix to extract stable code and structured autofix suggestions; updated validate_file/validate_source; tests for parsing/fixes/early-fail.
MCP dispatch / gateway / registry / schemas
crates/vox-orchestrator/src/mcp_tools/dispatch.rs, crates/vox-orchestrator/src/mcp_tools/http_gateway/mod.rs, crates/vox-orchestrator/src/mcp_tools/input_schemas.rs, contracts/mcp/tool-registry.canonical.yaml
Added static dispatch arm for vox_validate_source, added to default allowed tools, added input schema mapping, and registered tool in canonical MCP registry.
Corpus / training / tooling updates
crates/vox-corpus/src/corpus/extract_vox/part_ast.rs, crates/vox-mens/src/training/core.rs
Corpus extraction and training construct tags include reactive_module and fragment.
Tests & harness updates
crates/vox-test-harness/src/hir_builders.rs, crates/vox-integration-tests/..., crates/vox-compiler/tests/*
Test fixtures updated to initialize is_reactive; route and web_ir tests adjusted for overlap/reachability; added many unit/integration tests covering new parser, HIR, codegen, deps, and MCP validator behavior.
Docs & ADRs & how-to
docs/src/adr/032-*.md, docs/src/adr/033-*.md, docs/src/architecture/*, docs/src/how-to/how-to-mcp-vox-validate.md, docs/src/.well-known/llms.txt, docs/src/SUMMARY.md, docs/src/contributors/contribution-loop.md, docs/src/feed.xml
Added ADRs 032/033, architecture research and implementation plan docs (Phases A–G), MCP-how-to for vox_validate_source, LLMS entry, feed/SUMMARY updates, and contribution notes.
Local config
.claude/settings.local.json
Extended allowed Bash(...) permissions with git checkout --theirs and a grep for HTML tags in reactive codegen file.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰
I parsed the meadow, token by token,
marked @reactive trails at dawn;
Fragments bloom as TSX rabbits hop—
Providers, hooks, and routes march on.
A tiny state-machine hums: release, then run.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/amazing-bohr-f643fc

AI Assistant and others added 10 commits May 3, 2026 03:18
The implementation-plan commit-hash references in the Phase row table
used a placeholder org (`anthropics/vox`) that doesn't exist on GitHub.
Link checker (CI: check-links) flagged them as 404s.

Four occurrences swapped from `https://github.com/anthropics/vox` →
`https://github.com/vox-foundation/vox`. Hash-as-link-text behavior
preserved; readers click through to the repo root rather than the per-
commit URL, but the hash is searchable from there.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…covery

Per user authorization to proceed with all remaining Svelte-mineable
implementation phases:

ADR-032 (.vox.ui reactive modules) → status: current. Phase D code work
begins now.

ADR-033 (typed parametric fragment primitive) → status: current. Phase F
foundation slices begin now; codegen still gated on Phase 6 (TASK-6.1)
typed semantic primitives stabilizing per the ADR's revised status note.

Foundation slice for Phase D: new vox_compiler::module::FileKind enum
(Source / ReactiveModule / Unknown) + FileKind::from_path helper. This
is the single dispatch point ADR-032 commits to — every CLI entry that
hands a file path to the parser will route it through this helper instead
of duplicating extension-matching logic. The helper handles the
".vox.ui" / ".vox" precedence correctly (longer suffix wins) and
ignores parent directory names.

Six unit tests cover plain .vox, reactive-module suffix, unknown
extensions, directory-component handling, and the suffix-precedence
case. Doctest in the module-level docs.

No existing call sites switched yet — that's the next Phase D slice
(parser/AST/HIR/codegen). This commit is the pure utility layer the
rest of Phase D builds on.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Parser-layer slice for ADR-032 .vox.ui reactive modules. Lets the
compiler parse a .vox.ui source file with module-scope state / derived /
effect / on-mount / on-cleanup members into a typed AST node; HIR
lowering and codegen come in subsequent slices.

AST:
- New ReactiveModuleDecl in ast/decl/ui.rs — { name, members, span }.
  Members re-uses Vec<ReactiveMemberDecl>.
- New Decl::ReactiveModule variant + span match arm in
  ast/decl/types.rs. Two downstream exhaustive matches updated:
  vox-corpus/extract_vox/part_ast.rs and vox-mens/training/core.rs
  (both classify as `reactive_module`).

Parser:
- New parse_with_kind(tokens, FileKind) entry point. Original
  parse(tokens) is now a thin wrapper defaulting to FileKind::Source —
  every existing caller is byte-identical.
- Parser struct gains a file_kind field; new() defaults to Source.
- parse_decl checks self.file_kind.allows_module_scope_reactive_members()
  before its main match, dispatching to parse_reactive_module_decl when
  the next token is State / Derived / Effect / On in a ReactiveModule
  file.
- parse_reactive_module_decl absorbs consecutive reactive members into
  one synthetic ReactiveModuleDecl. Inner parsers re-use the existing
  parse_state_decl / parse_derived_decl / parse_reactive_block helpers,
  so the inner grammar matches what already works inside `component`.
- Module name is left empty by the parser; codegen will fill it from
  the source file basename in the next slice.

Tests: 3 new parser tests in parser::descent::tests covering
- module-scope state in a regular .vox source is a parse error
- module-scope state in a .vox.ui source parses into ReactiveModuleDecl
- module-scope derived + effect alongside state parse cleanly

Full lib suite: 297 passing (+3), 0 failing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase F slice 1 for ADR-033 typed parametric fragment primitive: lexer
token, AST node, top-level parser dispatch. Codegen and HIR lowering
are gated on Phase 6 (TASK-6.1) typed-primitive stabilization per the
ADR's status note — this slice ships the syntactic surface so authors
can start writing fragment declarations and the parser accepts them
into a typed AST node, with the actual emission decision deferred.

Lexer:
- New Token::Fragment recognized for the `fragment` keyword + Display
  arm.

AST:
- New FragmentDecl in ast/decl/ui.rs — { name, params, body, span }.
  Params re-uses the existing Param type; body is parsed as a generic
  Expr (codegen will validate the markup shape later).
- New Decl::Fragment(FragmentDecl) variant + span match arm.

Parser:
- New top-level Token::Fragment dispatch in parse_decl.
- New parse_fragment_decl in parser/descent/decl/head.rs: expects
  `fragment Ident(params) { expr }` and returns Decl::Fragment. Params
  re-use parse_params; body re-uses parse_expr.

Two downstream exhaustive matches updated for the new variant
(vox-corpus extract_vox classification and vox-mens training tagging).
Both classify as `fragment`.

Tests: 2 new parser tests in parser::descent::tests covering
- fragment with no params parses; name and empty params verified
- fragment with typed params parses; param names/types verified

Full lib suite green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase G slice 1 for the reactive-class state-machine pattern in the
Svelte-mineable features plan. The compiler's `state_machine` keyword
already emits typed states + events + a pure reducer fn stub at
codegen_ts/state_machine_emit.rs; what was missing was a runtime
instance pattern an emitted `useFooStateMachine(initial)` hook (in a
`component { }`) or a top-level reactive-class instance (in a `.vox.ui`
module per ADR-032) could be built on top of.

This slice ships the helper; the codegen change to actually emit a
hook backed by it is the next slice (touches state_machine_emit.rs).

API:
- `ReactiveStateMachine<S, E>` — Arc<Mutex<S>> for state, Arc<dyn Fn> for
  the reducer. Cheap to clone (both fields Arc'd) so the same machine
  can be shared across observers.
- `state() -> S` — clone-on-read; expected to be cheap for state-machine
  variant enums.
- `send(event: &E) -> S` — applies the reducer, replaces the state,
  returns the new value.
- `Reducer<S, E>` type alias = `Arc<dyn Fn(&S, &E) -> S + Send + Sync>`.

Tests: 5 unit tests covering construction, dispatch, return-value,
clone-shares-state-via-Arc, and multi-send composition. Doctest in the
struct docs.

Implementation plan updated: D / F / G rows now show 🟡 partial-shipped
status with commit hashes; ADRs 032 and 033 marked as accepted in the
"Needs ADR?" column.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…643fc

# Conflicts:
#	crates/vox-compiler/tests/ir_emission_test.rs
#	crates/vox-compiler/tests/snapshots/reactive_smoke_test__box_tsx_css_import.snap
#	crates/vox-compiler/tests/snapshots/reactive_smoke_test__clicky_tsx_classname_onclick.snap
#	crates/vox-compiler/tests/snapshots/reactive_smoke_test__counter_tsx_function_and_usememo_op_s205.snap
#	crates/vox-compiler/tests/snapshots/reactive_smoke_test__counter_tsx_reactive_smoke.snap
#	crates/vox-compiler/tests/snapshots/reactive_smoke_test__counter_tsx_with_web_ir_view_on.snap
#	crates/vox-compiler/tests/snapshots/reactive_smoke_test__island_mount_ast_z.snap
#	crates/vox-compiler/tests/snapshots/reactive_smoke_test__island_mount_hir_fragment_z.snap
#	crates/vox-compiler/tests/snapshots/reactive_smoke_test__panel_tsx_island_mount_attrs.snap
#	crates/vox-compiler/tests/snapshots/reactive_smoke_test__panel_tsx_web_ir_datachartisland.snap
#	crates/vox-compiler/tests/snapshots/reactive_smoke_test__parity_page_tsx_island_mount_classic_route.snap
#	crates/vox-compiler/tests/snapshots/reactive_smoke_test__parity_page_tsx_island_mount_op_s161.snap
#	crates/vox-compiler/tests/snapshots/reactive_smoke_test__v_tsx_usestate_k_op_s074.snap
#	crates/vox-compiler/tests/snapshots/reactive_smoke_test__vox_islands_meta_datachart.snap
#	crates/vox-compiler/tests/snapshots/web_ir_lower_emit_test__counter_view_tsx_webir_emit.snap
#	crates/vox-compiler/tests/snapshots/web_ir_lower_emit_test__op_s126_t_component_view_tsx.snap
#	crates/vox-compiler/tests/snapshots/web_ir_lower_emit_test__op_s219_hi_literal_view_tsx.snap
#	crates/vox-compiler/tests/tombstone_test.rs
#	docs/src/feed.xml
#	examples/golden/blog_fullstack.vox
#	examples/golden/web_routing_fullstack.vox
Phase F is now end-to-end functional. Authors write
`fragment Row(item: str, idx: int) { text() { item } }` and the codegen
emits a typed React function component into `fragments.tsx`:

  export interface RowArgs {
    item: string;
    idx: number;
  }
  export function Row({ item, idx }: RowArgs): React.ReactElement {
    return (text({ children: item }));
  }

Consumer components reference the fragment by name; the compiler will
validate `<RenderFragment of={Row} args={(item, idx)} />` at the call
site so the runtime needs no fragment-specific machinery.

Phase 6 (TASK-6.1) typed semantic primitives landed on main during the
prior merge cycle, unblocking the codegen gate noted in ADR-033 — so
this slice ships in the same session as the parser skeleton instead of
waiting on a separate primitive-stabilization milestone.

HIR:
- New HirFragmentDecl in hir/nodes/decl.rs — { name, params, body, span }.
- New HirModule.fragments: Vec<HirFragmentDecl> field.
- Lowering in hir/lower/mod.rs: AST Decl::Fragment lowered with the
  existing param + expr helpers. ReactiveModule arm wired to a no-op
  (Phase D codegen still pending in a follow-up slice).

Codegen:
- New crates/vox-compiler/src/codegen_ts/fragment_emit.rs with
  emit_fragment_decls(hir) and FRAGMENTS_FILENAME constant.
- Emits the React import + per-fragment Args interface + typed function.
- Wired into emitter::generate.

Tests: 6 unit + e2e tests in codegen_ts::fragment_emit::tests cover
empty-hir, args-interface emit, paramless signature, React import +
header, one-function-per-fragment, and the lex→parse→lower→generate
end-to-end path producing fragments.tsx.

Full vox-compiler lib suite: 326 passing (+6), 0 failing.

Implementation plan Phase F row updated to ✅ Shipped.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…der+hook

Phase D end-to-end functional. A `.vox.ui` source like:

  state count: int = 0
  derived doubled = count * 2

now compiles into `CounterProvider.tsx` (basename → PascalCase) with:

  export interface CounterValue {
    count: unknown;
    set_count: (v: unknown) => void;
    doubled: unknown;
  }
  const CounterContext = createContext<CounterValue | null>(null);
  export function CounterProvider({ children }) { ... }
  export function useCounter(): CounterValue { ... }

per ADR-032's lowering policy.

HIR:
- New HirReactiveModule { name, members, span } in hir/nodes/decl.rs.
- New HirModule.reactive_modules: Vec<HirReactiveModule> field.
- Lowering in hir/lower/mod.rs (was a no-op stub) now mirrors
  `lower_reactive_component` member-by-member: state / derived / effect /
  on-mount / on-cleanup all reuse the existing HirReactiveMember enum.

Codegen:
- New `crates/vox-compiler/src/codegen_ts/reactive_module_emit.rs` with
  `emit_reactive_modules(hir) -> Vec<(filename, content)>` and
  `pascalize_module_name` helper (snake_case / kebab-case / dot.case →
  PascalCase; empty → indexed fallback).
- Each module emits one `<Name>Provider.tsx` containing the React imports,
  typed Value interface, Context, Provider component (mirroring
  reactive.rs in-component lowering for state/derived/effect/on-mount/
  on-cleanup), and the `use<Name>()` hook with a clear "called outside
  Provider" error.
- Wired into `emitter::generate` so the file appears alongside
  `fragments.tsx` / `state_machines.ts` in the standard codegen output.

Tests: 4 unit tests in `codegen_ts::reactive_module_emit::tests`
covering empty-hir, provider+hook emit with state init verified,
empty-name fallback, and pascalize name conversion.

Full vox-compiler lib suite: 330 passing (+4), 0 failing.

Implementation plan Phase D row updated to ✅ Shipped end-to-end.
Remaining sub-slice: CLI build entry point should pass the source
basename through `parse_with_kind` so the parser fills `name` instead
of leaving it empty (codegen currently falls back to indexed default).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase G end-to-end functional. The runtime helper from
vox-runtime/src/state_machine.rs (commit fc06677) now has a React
counterpart: each `state_machine` decl emits, alongside the existing
discriminated-union types and pure reducer, a typed React hook:

  export function useLightStateMachine(initial: Light): {
    state: Light;
    send: (event: LightEvent) => void;
  } {
    const [state, setState] = useState<Light>(initial);
    const send = useCallback((event: LightEvent) => {
      setState((prev) => lightReducer(prev, event));
    }, []);
    return { state, send };
  }

The hook owns useState of the current state and a memoized send()
running the existing reducer. Mirrors the runtime-side
ReactiveStateMachine<S, E> helper for the React side, so both
component-host and out-of-component (.vox.ui module) consumers can
share the same conceptual state-machine instance pattern.

Codegen:
- emit_state_machine_decls now adds `import { useState, useCallback }
  from "react"` to the file header (state_machines.ts becomes a TSX-
  consumable module — JSX-free, but uses React hooks).
- New emit_react_hook(sm, out) helper called per state machine after
  the reducer emit.

Tests: 1 new lib unit test test_emit_react_hook_alongside_reducer
covers the import line, hook signature, send callback shape, and
reducer invocation. 2 state_machine_integration_test snapshots
re-accepted to absorb the new emit shape (Door/Light goldens). Full
vox-compiler suite: 331 passing (+1 lib + 2 snapshots), 0 failing.

Implementation plan Phase G row updated to ✅ Shipped end-to-end.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (6)
crates/vox-compiler/tests/web_ir_lower_emit_test.rs (1)

1637-1645: ⚡ Quick win

Consider accepting an explicit id parameter to eliminate the implicit caller contract.

link_element hardcodes DomNodeId(0), which is only valid when this node is pushed to a fresh/empty dom_nodes vec as the first element. This mirrors the fragility that originally caused the DomNodeId(99) bug. The sibling helper elem_node (line 1714) already uses the safer pattern:

♻️ Proposed refactor
-fn link_element(href: &str) -> DomNode {
+fn link_element(id: u32, href: &str) -> DomNode {
     DomNode::Element {
-        id: DomNodeId(0),
+        id: DomNodeId(id),
         tag: "a".to_string(),
         attrs: vec![("href".to_string(), href.to_string())],
         children: vec![],
         span: None,
     }
 }

Call sites then become link_element(0, "/home"), making the contract explicit in the call.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vox-compiler/tests/web_ir_lower_emit_test.rs` around lines 1637 -
1645, The helper link_element currently hardcodes DomNodeId(0) which makes
callers implicitly rely on it being the first element; change link_element to
accept an explicit id parameter (either usize or DomNodeId) and construct the
node using that id instead of DomNodeId(0), following the same pattern used by
elem_node; update all call sites in the tests to pass the intended id (e.g.,
link_element(0, "/home") or link_element(DomNodeId(0), "/home")) so the
DomNodeId contract is explicit and no longer tied to dom_nodes being empty.
crates/vox-compiler/src/parser/descent/decl/head.rs (1)

376-380: 💤 Low value

Redundant imports inside function body.

Token and ParseError/ParseErrorClass are already imported at the module level (lines 12-13). These local use statements are unnecessary.

♻️ Remove redundant imports
     pub(crate) fn parse_reactive_module_decl(&mut self) -> Result<crate::ast::decl::Decl, ()> {
         use crate::ast::decl::{
             EffectDecl, OnCleanupDecl, OnMountDecl, ReactiveMemberDecl, ReactiveModuleDecl,
         };
-        use crate::lexer::token::Token;
-        use crate::parser::error::{ParseError, ParseErrorClass};

         let start = self.span();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vox-compiler/src/parser/descent/decl/head.rs` around lines 376 - 380,
Remove the redundant local imports of Token and ParseError/ParseErrorClass
inside the function body: keep the needed local use for EffectDecl,
OnCleanupDecl, OnMountDecl, ReactiveMemberDecl, ReactiveModuleDecl but delete
Token and ParseError/ParseErrorClass from that inner use block so the code uses
the module-level imports instead; update the local use statement in head.rs
accordingly to only include the decl types.
crates/vox-compiler/src/hir/lower/decl.rs (1)

466-466: 💤 Low value

Fix inconsistent indentation.

This line uses 4-space indentation instead of the 12-space indentation used by surrounding fields in the HirFn struct literal.

🔧 Proposed fix
-            is_reactive: false,
+                    is_reactive: false,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vox-compiler/src/hir/lower/decl.rs` at line 466, The field initializer
"is_reactive: false," inside the HirFn struct literal is misindented (4 spaces)
compared to the other fields (12 spaces); update the indentation of the
"is_reactive" line to align with the surrounding fields in the HirFn struct
literal so all field entries use the same 12-space indentation.
crates/vox-compiler/src/hir/nodes/decl.rs (1)

78-87: 💤 Low value

Add ownership entries for new HirModule fields.

The field_ownership_map() method (lines 124-147) enumerates all HirModule fields with their ownership classification, but the new fragments and reactive_modules fields are not included. This could cause confusion for tooling or migration logic that relies on this map being exhaustive.

♻️ Proposed fix to add missing ownership entries
             ("components", HirFieldOwnership::SemanticCore),
             ("url_decls", HirFieldOwnership::SemanticCore),
+            ("fragments", HirFieldOwnership::SemanticCore),
+            ("reactive_modules", HirFieldOwnership::SemanticCore),
         ]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vox-compiler/src/hir/nodes/decl.rs` around lines 78 - 87, The
HirModule field_ownership_map() is missing ownership entries for the newly added
fields fragments and reactive_modules; update the HirModule::field_ownership_map
(the method named field_ownership_map on the HirModule type) to include
ownership classifications for fragments: Vec<HirFragmentDecl> and
reactive_modules: Vec<HirReactiveModule> consistent with the other Vec fields
(add corresponding entries mapping "fragments" and "reactive_modules" to the
appropriate Ownership variant used by the map), and run tests/formatting to
ensure the map remains exhaustive and compiles.
crates/vox-compiler/src/codegen_ts/route_manifest.rs (1)

196-202: 💤 Low value

Simplify redundant conditional branches.

Both branches of the if i == 0 / else condition emit identical format strings. This appears intentional for future extensibility but currently adds unnecessary branching.

Proposed simplification
-    for (i, p) in deduped.iter().enumerate() {
-        if i == 0 {
-            s.push_str(&format!("\n  | \"{p}\""));
-        } else {
-            s.push_str(&format!("\n  | \"{p}\""));
-        }
+    for p in &deduped {
+        s.push_str(&format!("\n  | \"{p}\""));
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vox-compiler/src/codegen_ts/route_manifest.rs` around lines 196 - 202,
The loop over deduped with enumerate contains an unnecessary if/else because
both branches call s.push_str(&format!("\n  | \"{p}\"")); simplify by removing
the if i == 0 check and always call s.push_str(&format!("\n  | \"{p}\"")) for
each p in deduped (use the existing variables deduped, p, s, and the enumerate
loop context) so the output remains identical but the branching is eliminated.
docs/src/adr/033-typed-fragment-primitive.md (1)

1-8: 💤 Low value

Missing training_eligible field in frontmatter.

Per coding guidelines, new documentation pages must include the training_eligible field in frontmatter.

Proposed fix
 ---
 title: "ADR 033 — Typed parametric fragment primitive"
 description: "Introduces a `fragment` bare-keyword scope for typed parametric markup blocks that are passable as props, callable multiple times with different arguments, and validated against consumer prop interfaces at compile time. Drafted ahead of Phase F implementation; deferred until the Phase 6 (TASK-6.1) typed semantic primitives stabilize."
 category: "architecture"
 status: "current"
 last_updated: "2026-05-03"
+training_eligible: true
 schema_type: "TechArticle"
 ---

As per coding guidelines: "Frontmatter for new documentation pages must include title, description, category, status, last_updated, and training_eligible fields."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/src/adr/033-typed-fragment-primitive.md` around lines 1 - 8, The
frontmatter for this ADR is missing the required training_eligible key; update
the YAML frontmatter block (the keys 'title', 'description', 'category',
'status', 'last_updated') by adding a 'training_eligible' field set to true or
false as appropriate (e.g., training_eligible: true) so the document adheres to
the required frontmatter schema.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/vox-compiler/src/ast/decl/types.rs`:
- Around line 172-181: The pretty-printer's print_decl() currently swallows
Decl::Fragment and Decl::ReactiveModule due to a catch-all arm; update
print_decl() in fmt/printer.rs to add explicit match arms for
Decl::Fragment(FragmentDecl) and Decl::ReactiveModule(ReactiveModuleDecl) that
invoke the printer's fragment and reactive-module printing logic (e.g., call the
existing helpers or add small helpers like print_fragment_decl(&mut self, decl)
and print_reactive_module_decl(&mut self, decl) if none exist) so these variants
are emitted instead of dropped.

In `@crates/vox-orchestrator/src/mcp_tools/code_validator.rs`:
- Around line 68-90: pre_validation_guard is currently using broad substring
matches causing false positives; narrow these by removing the "// TODO" check
and only flag "macro" and "operator" when they appear as code at the start of a
line (not inside comments/strings). Update pre_validation_guard to (1) keep the
specific token checks for "todo!()" and "unimplemented!()", (2) drop the "//
TODO" substring check, and (3) scan text line-by-line, trim_start each line and
only treat lines that start_with("macro_rules!") or start_with("macro ") or
start_with("operator ") as triggers (this avoids matching those words inside
comments or strings). Use the same ToolResult::<R>::err_with_remediation
branches and retain function name pre_validation_guard to locate and modify the
logic.
- Around line 61-64: The current parse_fix implementation uses unchecked casts
like start.get("line")?.as_u64()? as u32 which silently truncates out-of-range
values; update parse_fix to defensively convert the u64 fields to u32 using a
fallible conversion (e.g. u32::try_from(...).ok() or similar) for start_line,
start_col, end_line, end_col so that any out-of-range or malformed values cause
parse_fix to return None (skip the fix) instead of producing truncated ranges.

In `@crates/vox-orchestrator/src/mcp_tools/params.rs`:
- Around line 422-425: The ValidateSourceParams::source field lacks an
upper-bound schema constraint; add a schemars length(max = 131072) attribute (or
a higher cap if you expect >128KB) to the source field's attributes so the
generated JSON Schema and runtime validation advertise/enforce a safe maximum
for AI-generated payloads; update the #[schemars(...)] line on the
ValidateSourceParams struct to include max = 131072 alongside the existing min =
1.

In `@docs/src/contributors/contribution-loop.md`:
- Line 110: The markdown link '../../../.github/workflows/ci.yml' in
contribution-loop.md points outside the mdBook source tree and results in a dead
anchor; update the link to either a path that stays inside the book source
(e.g., point to a copy or excerpt of the relevant workflow stored under
docs/src/) or replace it with a permanent GitHub permalink to the workflow file
on the repository (use the raw or blob URL) so the anchor remains valid in the
built book.

In `@docs/src/how-to/how-to-mcp-vox-validate.md`:
- Around line 1-7: The frontmatter block at the top of the document is missing
the required status field; update the YAML frontmatter (the block containing
title, description, category, last_updated, training_eligible) to include a
status entry (e.g., status: "draft" or "published") so the page contains title,
description, category, status, last_updated, and training_eligible as required
by the docs guidelines.

---

Nitpick comments:
In `@crates/vox-compiler/src/codegen_ts/route_manifest.rs`:
- Around line 196-202: The loop over deduped with enumerate contains an
unnecessary if/else because both branches call s.push_str(&format!("\n  |
\"{p}\"")); simplify by removing the if i == 0 check and always call
s.push_str(&format!("\n  | \"{p}\"")) for each p in deduped (use the existing
variables deduped, p, s, and the enumerate loop context) so the output remains
identical but the branching is eliminated.

In `@crates/vox-compiler/src/hir/lower/decl.rs`:
- Line 466: The field initializer "is_reactive: false," inside the HirFn struct
literal is misindented (4 spaces) compared to the other fields (12 spaces);
update the indentation of the "is_reactive" line to align with the surrounding
fields in the HirFn struct literal so all field entries use the same 12-space
indentation.

In `@crates/vox-compiler/src/hir/nodes/decl.rs`:
- Around line 78-87: The HirModule field_ownership_map() is missing ownership
entries for the newly added fields fragments and reactive_modules; update the
HirModule::field_ownership_map (the method named field_ownership_map on the
HirModule type) to include ownership classifications for fragments:
Vec<HirFragmentDecl> and reactive_modules: Vec<HirReactiveModule> consistent
with the other Vec fields (add corresponding entries mapping "fragments" and
"reactive_modules" to the appropriate Ownership variant used by the map), and
run tests/formatting to ensure the map remains exhaustive and compiles.

In `@crates/vox-compiler/src/parser/descent/decl/head.rs`:
- Around line 376-380: Remove the redundant local imports of Token and
ParseError/ParseErrorClass inside the function body: keep the needed local use
for EffectDecl, OnCleanupDecl, OnMountDecl, ReactiveMemberDecl,
ReactiveModuleDecl but delete Token and ParseError/ParseErrorClass from that
inner use block so the code uses the module-level imports instead; update the
local use statement in head.rs accordingly to only include the decl types.

In `@crates/vox-compiler/tests/web_ir_lower_emit_test.rs`:
- Around line 1637-1645: The helper link_element currently hardcodes
DomNodeId(0) which makes callers implicitly rely on it being the first element;
change link_element to accept an explicit id parameter (either usize or
DomNodeId) and construct the node using that id instead of DomNodeId(0),
following the same pattern used by elem_node; update all call sites in the tests
to pass the intended id (e.g., link_element(0, "/home") or
link_element(DomNodeId(0), "/home")) so the DomNodeId contract is explicit and
no longer tied to dom_nodes being empty.

In `@docs/src/adr/033-typed-fragment-primitive.md`:
- Around line 1-8: The frontmatter for this ADR is missing the required
training_eligible key; update the YAML frontmatter block (the keys 'title',
'description', 'category', 'status', 'last_updated') by adding a
'training_eligible' field set to true or false as appropriate (e.g.,
training_eligible: true) so the document adheres to the required frontmatter
schema.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d115cdbf-525a-4ec5-8364-8ca292f029d6

📥 Commits

Reviewing files that changed from the base of the PR and between ef0f76f and eeee059.

⛔ Files ignored due to path filters (4)
  • crates/vox-compiler/tests/snapshots/reactive_smoke_test__parity_page_tsx_data_prop_label_op_s166.snap is excluded by !**/*.snap
  • crates/vox-compiler/tests/snapshots/state_machine_integration_test__door_terminal_state_machine_emit.snap is excluded by !**/*.snap
  • crates/vox-compiler/tests/snapshots/state_machine_integration_test__light_state_machine_ts_emit.snap is excluded by !**/*.snap
  • crates/vox-dashboard/app/src/generated/routes.manifest.ts is excluded by !**/generated/**
📒 Files selected for processing (50)
  • .claude/settings.local.json
  • contracts/mcp/tool-registry.canonical.yaml
  • crates/vox-compiler/src/ast/decl/fundecl.rs
  • crates/vox-compiler/src/ast/decl/types.rs
  • crates/vox-compiler/src/ast/decl/ui.rs
  • crates/vox-compiler/src/codegen_ts/emitter.rs
  • crates/vox-compiler/src/codegen_ts/fragment_emit.rs
  • crates/vox-compiler/src/codegen_ts/hir_emit/mod.rs
  • crates/vox-compiler/src/codegen_ts/hir_emit/state_deps.rs
  • crates/vox-compiler/src/codegen_ts/mod.rs
  • crates/vox-compiler/src/codegen_ts/reactive.rs
  • crates/vox-compiler/src/codegen_ts/reactive_module_emit.rs
  • crates/vox-compiler/src/codegen_ts/route_manifest.rs
  • crates/vox-compiler/src/codegen_ts/route_pattern.rs
  • crates/vox-compiler/src/codegen_ts/routes.rs
  • crates/vox-compiler/src/codegen_ts/state_machine_emit.rs
  • crates/vox-compiler/src/hir/lower/decl.rs
  • crates/vox-compiler/src/hir/lower/mod.rs
  • crates/vox-compiler/src/hir/nodes/decl.rs
  • crates/vox-compiler/src/lexer/token.rs
  • crates/vox-compiler/src/lib.rs
  • crates/vox-compiler/src/module.rs
  • crates/vox-compiler/src/parser/descent/decl/head.rs
  • crates/vox-compiler/src/parser/descent/mod.rs
  • crates/vox-compiler/src/parser/descent/tests.rs
  • crates/vox-compiler/tests/route_express_emit_test.rs
  • crates/vox-compiler/tests/web_ir_lower_emit_test.rs
  • crates/vox-corpus/src/corpus/extract_vox/part_ast.rs
  • crates/vox-integration-tests/tests/metamorphic_axiom_test.rs
  • crates/vox-mens/src/training/core.rs
  • crates/vox-orchestrator/src/mcp_tools/code_validator.rs
  • crates/vox-orchestrator/src/mcp_tools/dispatch.rs
  • crates/vox-orchestrator/src/mcp_tools/http_gateway/mod.rs
  • crates/vox-orchestrator/src/mcp_tools/input_schemas.rs
  • crates/vox-orchestrator/src/mcp_tools/params.rs
  • crates/vox-runtime/src/lib.rs
  • crates/vox-runtime/src/state_machine.rs
  • crates/vox-test-harness/src/hir_builders.rs
  • docs/src/.well-known/llms.txt
  • docs/src/SUMMARY.md
  • docs/src/adr/032-vox-ui-reactive-modules.md
  • docs/src/adr/033-typed-fragment-primitive.md
  • docs/src/adr/index.md
  • docs/src/architecture/architecture-index.md
  • docs/src/architecture/research-index.md
  • docs/src/architecture/svelte-mineable-features-implementation-plan-2026.md
  • docs/src/architecture/svelte-vs-react-frameworks-research-2026.md
  • docs/src/contributors/contribution-loop.md
  • docs/src/feed.xml
  • docs/src/how-to/how-to-mcp-vox-validate.md

Comment thread crates/vox-compiler/src/ast/decl/types.rs
Comment thread crates/vox-orchestrator/src/mcp_tools/code_validator.rs Outdated
Comment thread crates/vox-orchestrator/src/mcp_tools/code_validator.rs
Comment thread crates/vox-orchestrator/src/mcp_tools/params.rs
Comment thread docs/src/contributors/contribution-loop.md Outdated
Comment thread docs/src/how-to/how-to-mcp-vox-validate.md
CRITICAL — fmt/printer.rs silently dropped Fragment + ReactiveModule.
Added explicit print_fragment_decl and print_reactive_module_decl arms
so `vox fmt` round-trips the new declarations instead of erasing them
through the catch-all `_ => {}` arm.

MAJOR
- pre_validation_guard substring patterns were too broad and produced
  false positives on legitimate code (a comment containing "macro " or
  "operator ", or a routine "// TODO" marker, would be rejected with
  UNSUPPORTED_SYNTAX or LAZY_GENERATION_DETECTED). Narrowed:
  * Dropped "// TODO" from the lazy-generation guard entirely (todo
    markers in human code aren't skeleton-code indicators).
  * "macro " and "operator " checks now anchor on trim_start of each
    line, so the words inside comments / string literals don't trigger.
    "macro_rules!" stays as a global substring (no legitimate Vox use).
- ValidateSourceParams.source missing length(max=…) — added 131_072
  (128 KiB) cap to match the convention for other large-text params
  in this file.
- how-to-mcp-vox-validate.md missing required `status` frontmatter
  field — added `status: "current"`.

MINOR
- parse_fix used `as u32` casts that silently truncate u64 → defensive
  `u32::try_from(...).ok()?` so corrupt JSON ranges are dropped instead
  of producing truncated fixes.
- contribution-loop.md link to .github/workflows/ci.yml escaped the
  mdBook source tree → swapped to a GitHub permalink.

NITS
- link_element test helper hardcoded DomNodeId(0); now takes an explicit
  id parameter, both call sites updated.
- Removed redundant local imports of Token and ParseError* from
  parse_reactive_module_decl (already imported at module scope).
- Fixed misindented `is_reactive: false` line (4-space → 12-space) in
  hir/lower/decl.rs.
- Added `fragments` and `reactive_modules` ownership entries to
  HirModule::field_ownership_map.
- Collapsed redundant if/else in route_manifest.rs route_path_builder
  (both branches emitted the same format string).
- ADR-032 and ADR-033 frontmatter were missing `training_eligible`;
  added `training_eligible: true` to both.

Verification:
- vox-compiler lib suite: 331 passing, 0 failing.
- vox-compiler full crate: 0 failing.
- vox-orchestrator lib build: clean. Lib *test* build is broken on a
  pre-existing main issue (vox_populi::transport::serve_with_listener
  missing) — not addressed here; out of scope for the review.
- Doc pipeline --check passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/vox-compiler/src/hir/nodes/decl.rs`:
- Around line 147-148: Semantic projection is dropping fields marked as
SemanticCore: the entries ("fragments", HirFieldOwnership::SemanticCore) and
("reactive_modules", HirFieldOwnership::SemanticCore) are never carried into the
SemanticHirModule by to_semantic_hir(), causing those declarations to be lost;
fix by either moving these fields out of SemanticCore or by adding corresponding
fields to SemanticHirModule and populating them inside to_semantic_hir() (trace
the source HIR fields, convert as needed, and assign to the new
SemanticHirModule fields); update any constructors/serializers that construct
SemanticHirModule so fragments and reactive_modules are preserved.

In `@crates/vox-orchestrator/src/mcp_tools/params.rs`:
- Around line 423-426: The schemars attribute enforces max characters not bytes,
so add an explicit UTF-8 byte-length check in validate_source() (in
code_validator.rs) before the pre_validation_guard call: if params.source.len()
> 131_072 then return a
ToolResult::<ValidateResponse>::err_with_remediation(...) with the same
remediation/error shape you use elsewhere; check params.source (not schema) and
use the 131_072 byte limit to enforce the intended 128 KiB cap.

In `@docs/src/adr/032-vox-ui-reactive-modules.md`:
- Line 5: The frontmatter status is incorrectly set to "current" while the ADR
still describes unimplemented slices and pending wiring; update the frontmatter
`status` value (and the related `accepted`/status metadata instance later in the
frontmatter) from "current"/"accepted" to "roadmap" so the document reflects an
unshipped feature plan, and ensure any other frontmatter entries that duplicate
the status are kept consistent with `status: roadmap`.

In `@docs/src/adr/033-typed-fragment-primitive.md`:
- Line 5: The ADR frontmatter has an incorrect status: change the YAML "status"
value from "current" to "roadmap" (or "research" if this is purely evidence) to
match the body text that defers implementation; update any other inconsistent
mentions (e.g., the acceptance language at the top and the
draft/awaiting-acceptance reference around the body) so the document
consistently uses "status: roadmap" and corresponding roadmap wording; ensure
the frontmatter and the body (including the text referenced near Line 14 and
Line 163) all use the same unshipped status label.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 279db246-9b0b-49c0-881a-bb26257d7bcd

📥 Commits

Reviewing files that changed from the base of the PR and between eeee059 and 68b7ad5.

📒 Files selected for processing (13)
  • crates/vox-compiler/src/codegen_ts/route_manifest.rs
  • crates/vox-compiler/src/fmt/printer.rs
  • crates/vox-compiler/src/hir/lower/decl.rs
  • crates/vox-compiler/src/hir/nodes/decl.rs
  • crates/vox-compiler/src/parser/descent/decl/head.rs
  • crates/vox-compiler/tests/web_ir_lower_emit_test.rs
  • crates/vox-orchestrator/src/mcp_tools/code_validator.rs
  • crates/vox-orchestrator/src/mcp_tools/params.rs
  • docs/src/adr/032-vox-ui-reactive-modules.md
  • docs/src/adr/033-typed-fragment-primitive.md
  • docs/src/contributors/contribution-loop.md
  • docs/src/feed.xml
  • docs/src/how-to/how-to-mcp-vox-validate.md
✅ Files skipped from review due to trivial changes (3)
  • crates/vox-compiler/src/hir/lower/decl.rs
  • crates/vox-compiler/src/codegen_ts/route_manifest.rs
  • docs/src/contributors/contribution-loop.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • crates/vox-compiler/tests/web_ir_lower_emit_test.rs
  • crates/vox-orchestrator/src/mcp_tools/code_validator.rs
  • docs/src/how-to/how-to-mcp-vox-validate.md

Comment thread crates/vox-compiler/src/hir/nodes/decl.rs
Comment on lines +423 to +426
/// Vox source code to validate. Capped at 131_072 bytes (128 KiB) so a runaway
/// or adversarial AI agent cannot submit an arbitrarily large payload that the
/// compiler pipeline would happily allocate and process.
#[schemars(length(min = 1, max = 131_072))]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C3 'ValidateSourceParams|pre_validation_guard|source\.len\(\)|131_072|128 KiB' crates/vox-orchestrator/src/mcp_tools

Repository: vox-foundation/vox

Length of output: 7056


Add explicit runtime byte-length validation for the source parameter.

The #[schemars(length(max = 131_072))] constraint enforces a limit on Unicode code points, not bytes. JSON Schema's maxLength counts characters; a 131,072-character string with multi-byte UTF-8 characters can exceed 128 KiB in actual bytes. Add a runtime byte check in validate_source() (line 168 in code_validator.rs) before the pre_validation_guard call to enforce the intended byte limit and protect against oversized payloads:

if params.source.len() > 131_072 {
    return ToolResult::<ValidateResponse>::err_with_remediation(...);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vox-orchestrator/src/mcp_tools/params.rs` around lines 423 - 426, The
schemars attribute enforces max characters not bytes, so add an explicit UTF-8
byte-length check in validate_source() (in code_validator.rs) before the
pre_validation_guard call: if params.source.len() > 131_072 then return a
ToolResult::<ValidateResponse>::err_with_remediation(...) with the same
remediation/error shape you use elsewhere; check params.source (not schema) and
use the 131_072 byte limit to enforce the intended 128 KiB cap.

title: "ADR 032 — `.vox.ui` reactive modules"
description: "Introduces a `.vox.ui` file-suffix convention that allows module-scope `state` / `derived` / `effect` / `on mount` / `on cleanup` reactive members. Lowers to a generated React context+provider+hook in TSX emit. Addresses the gap that today's reactive members live only inside `component { }` blocks, with no story for shared state across components."
category: "architecture"
status: "current"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Status metadata is ahead of the document’s own rollout state.

Line 5 marks this page as current, and Line 14 marks it accepted, but the section still describes future slices and pending wiring. This should stay roadmap until the planned slices are actually complete.

Suggested edit
-status: "current"
+status: "roadmap"
@@
-Accepted (2026-05-03). Phase D code work begins immediately; first slice ships the `FileKind::from_path` helper, parser allowance for module-scope reactive members in `.vox.ui` files, and a minimal context+provider+hook emit. Subsequent slices wire cross-module reactive imports into the Phase E dep analyzer per the §"Read-tracking interaction" note.
+Proposed (2026-05-03). Phase D code work begins immediately; first slice ships the `FileKind::from_path` helper, parser allowance for module-scope reactive members in `.vox.ui` files, and a minimal context+provider+hook emit. Subsequent slices wire cross-module reactive imports into the Phase E dep analyzer per the §"Read-tracking interaction" note.

As per coding guidelines, Use status: research for evidence documentation pages and status: roadmap for unshipped feature plans in frontmatter.

Also applies to: 14-15

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/src/adr/032-vox-ui-reactive-modules.md` at line 5, The frontmatter
status is incorrectly set to "current" while the ADR still describes
unimplemented slices and pending wiring; update the frontmatter `status` value
(and the related `accepted`/status metadata instance later in the frontmatter)
from "current"/"accepted" to "roadmap" so the document reflects an unshipped
feature plan, and ensure any other frontmatter entries that duplicate the status
are kept consistent with `status: roadmap`.

title: "ADR 033 — Typed parametric fragment primitive"
description: "Introduces a `fragment` bare-keyword scope for typed parametric markup blocks that are passable as props, callable multiple times with different arguments, and validated against consumer prop interfaces at compile time. Drafted ahead of Phase F implementation; deferred until the Phase 6 (TASK-6.1) typed semantic primitives stabilize."
category: "architecture"
status: "current"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Status is internally inconsistent with deferred implementation language.

Line 5/Line 14 mark this ADR as current+accepted, while the body says implementation is deferred behind Phase 6 and Line 163 references draft/awaiting-acceptance status. Align this to roadmap/proposed until it is actually shipped and accepted.

Suggested edit
-status: "current"
+status: "roadmap"
@@
-Accepted (2026-05-03). Phase F code work begins with a foundation slice: lexer token, AST/HIR node, parser production.
+Proposed (2026-05-03). Phase F code work begins with a foundation slice: lexer token, AST/HIR node, parser production.
@@
-- [ADR 032: `.vox.ui` reactive modules](032-vox-ui-reactive-modules.md) — sibling ADR for the other major Phase D/E surface; same status (drafted, awaiting acceptance).
+- [ADR 032: `.vox.ui` reactive modules](032-vox-ui-reactive-modules.md) — sibling ADR for the other major Phase D/E surface; same roadmap status until full shipment/acceptance.

As per coding guidelines, Use status: research for evidence documentation pages and status: roadmap for unshipped feature plans in frontmatter.

Also applies to: 14-14, 163-163

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/src/adr/033-typed-fragment-primitive.md` at line 5, The ADR frontmatter
has an incorrect status: change the YAML "status" value from "current" to
"roadmap" (or "research" if this is purely evidence) to match the body text that
defers implementation; update any other inconsistent mentions (e.g., the
acceptance language at the top and the draft/awaiting-acceptance reference
around the body) so the document consistently uses "status: roadmap" and
corresponding roadmap wording; ensure the frontmatter and the body (including
the text referenced near Line 14 and Line 163) all use the same unshipped status
label.

AI Assistant and others added 2 commits May 3, 2026 16:55
…643fc

# Conflicts:
#	.claude/settings.local.json
#	crates/vox-compiler/src/ast/decl/ui.rs
#	crates/vox-compiler/src/codegen_ts/reactive.rs
#	crates/vox-compiler/src/parser/descent/decl/head.rs
#	crates/vox-compiler/src/parser/descent/mod.rs
#	docs/src/architecture/architecture-index.md
#	docs/src/feed.xml
- SemanticHirModule.to_semantic_hir() now carries fragments and reactive_modules.
- vox_validate_source enforces a 131072-byte runtime cap (schemars maxLength counts code points, not bytes).
- ADR-032 and ADR-033 Status sections rewritten to match shipped state.
- Stale 'drafted, awaiting acceptance' cross-reference in ADR-033 fixed.

Verified: vox-compiler 328 lib tests pass, doc pipeline --check passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@brbrainerd brbrainerd merged commit 250f1f4 into main May 3, 2026
7 of 34 checks passed
@brbrainerd brbrainerd deleted the claude/amazing-bohr-f643fc branch May 3, 2026 23:37
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