Svelte-mineable features: Phases A, C, E + ADR-032/033 + test-suite cleanup#63
Svelte-mineable features: Phases A, C, E + ADR-032/033 + test-suite cleanup#63brbrainerd merged 32 commits intomainfrom
Conversation
…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>
|
Warning Rate limit exceeded
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 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (25)
📝 WalkthroughWalkthroughAdds .vox.ui module-scope reactive members and typed parametric ChangesCompiler + Codegen + Parser + HIR + Runtime + MCP (single cohesive DAG)
Estimated code review effort 🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
|
…643fc # Conflicts: # docs/src/feed.xml
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>
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (6)
crates/vox-compiler/tests/web_ir_lower_emit_test.rs (1)
1637-1645: ⚡ Quick winConsider accepting an explicit
idparameter to eliminate the implicit caller contract.
link_elementhardcodesDomNodeId(0), which is only valid when this node is pushed to a fresh/emptydom_nodesvec as the first element. This mirrors the fragility that originally caused theDomNodeId(99)bug. The sibling helperelem_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 valueRedundant imports inside function body.
TokenandParseError/ParseErrorClassare already imported at the module level (lines 12-13). These localusestatements 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 valueFix inconsistent indentation.
This line uses 4-space indentation instead of the 12-space indentation used by surrounding fields in the
HirFnstruct 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 valueAdd ownership entries for new
HirModulefields.The
field_ownership_map()method (lines 124-147) enumerates allHirModulefields with their ownership classification, but the newfragmentsandreactive_modulesfields 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 valueSimplify redundant conditional branches.
Both branches of the
if i == 0/elsecondition 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 valueMissing
training_eligiblefield in frontmatter.Per coding guidelines, new documentation pages must include the
training_eligiblefield 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, andtraining_eligiblefields."🤖 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
⛔ Files ignored due to path filters (4)
crates/vox-compiler/tests/snapshots/reactive_smoke_test__parity_page_tsx_data_prop_label_op_s166.snapis excluded by!**/*.snapcrates/vox-compiler/tests/snapshots/state_machine_integration_test__door_terminal_state_machine_emit.snapis excluded by!**/*.snapcrates/vox-compiler/tests/snapshots/state_machine_integration_test__light_state_machine_ts_emit.snapis excluded by!**/*.snapcrates/vox-dashboard/app/src/generated/routes.manifest.tsis excluded by!**/generated/**
📒 Files selected for processing (50)
.claude/settings.local.jsoncontracts/mcp/tool-registry.canonical.yamlcrates/vox-compiler/src/ast/decl/fundecl.rscrates/vox-compiler/src/ast/decl/types.rscrates/vox-compiler/src/ast/decl/ui.rscrates/vox-compiler/src/codegen_ts/emitter.rscrates/vox-compiler/src/codegen_ts/fragment_emit.rscrates/vox-compiler/src/codegen_ts/hir_emit/mod.rscrates/vox-compiler/src/codegen_ts/hir_emit/state_deps.rscrates/vox-compiler/src/codegen_ts/mod.rscrates/vox-compiler/src/codegen_ts/reactive.rscrates/vox-compiler/src/codegen_ts/reactive_module_emit.rscrates/vox-compiler/src/codegen_ts/route_manifest.rscrates/vox-compiler/src/codegen_ts/route_pattern.rscrates/vox-compiler/src/codegen_ts/routes.rscrates/vox-compiler/src/codegen_ts/state_machine_emit.rscrates/vox-compiler/src/hir/lower/decl.rscrates/vox-compiler/src/hir/lower/mod.rscrates/vox-compiler/src/hir/nodes/decl.rscrates/vox-compiler/src/lexer/token.rscrates/vox-compiler/src/lib.rscrates/vox-compiler/src/module.rscrates/vox-compiler/src/parser/descent/decl/head.rscrates/vox-compiler/src/parser/descent/mod.rscrates/vox-compiler/src/parser/descent/tests.rscrates/vox-compiler/tests/route_express_emit_test.rscrates/vox-compiler/tests/web_ir_lower_emit_test.rscrates/vox-corpus/src/corpus/extract_vox/part_ast.rscrates/vox-integration-tests/tests/metamorphic_axiom_test.rscrates/vox-mens/src/training/core.rscrates/vox-orchestrator/src/mcp_tools/code_validator.rscrates/vox-orchestrator/src/mcp_tools/dispatch.rscrates/vox-orchestrator/src/mcp_tools/http_gateway/mod.rscrates/vox-orchestrator/src/mcp_tools/input_schemas.rscrates/vox-orchestrator/src/mcp_tools/params.rscrates/vox-runtime/src/lib.rscrates/vox-runtime/src/state_machine.rscrates/vox-test-harness/src/hir_builders.rsdocs/src/.well-known/llms.txtdocs/src/SUMMARY.mddocs/src/adr/032-vox-ui-reactive-modules.mddocs/src/adr/033-typed-fragment-primitive.mddocs/src/adr/index.mddocs/src/architecture/architecture-index.mddocs/src/architecture/research-index.mddocs/src/architecture/svelte-mineable-features-implementation-plan-2026.mddocs/src/architecture/svelte-vs-react-frameworks-research-2026.mddocs/src/contributors/contribution-loop.mddocs/src/feed.xmldocs/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>
There was a problem hiding this comment.
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
📒 Files selected for processing (13)
crates/vox-compiler/src/codegen_ts/route_manifest.rscrates/vox-compiler/src/fmt/printer.rscrates/vox-compiler/src/hir/lower/decl.rscrates/vox-compiler/src/hir/nodes/decl.rscrates/vox-compiler/src/parser/descent/decl/head.rscrates/vox-compiler/tests/web_ir_lower_emit_test.rscrates/vox-orchestrator/src/mcp_tools/code_validator.rscrates/vox-orchestrator/src/mcp_tools/params.rsdocs/src/adr/032-vox-ui-reactive-modules.mddocs/src/adr/033-typed-fragment-primitive.mddocs/src/contributors/contribution-loop.mddocs/src/feed.xmldocs/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
| /// 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))] |
There was a problem hiding this comment.
🧩 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_toolsRepository: 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" |
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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.
…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>
Summary
Ships three of the seven Svelte-mineable items identified in the Svelte vs React frameworks research — Phase A (
vox_validate_sourceMCP tool with structured autofix), Phase C (segment-aware route conflict detection + typedroutePathbuilder), and Phase E (@reactivedecorator + cross-call dep inference + tier-2 over-track hint surface) — plus drafts ADR-032 (.vox.uireactive 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/workflowtombstone tests against AGENTS.md TASK-2.6 (un-tombstoning), tombstoning the@scheduledtest 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-compilerwent from 7 known failures + 8 ignored to 0 failures + 9 ignored.What landed (per phase)
Phase A — MCP iteration loop (8ae08ed, 423a44e, c04a8b4)
vox_validate_sourceMCP 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 byvox_lsp::typeck_diagnostic_to_lspbut dropped at the MCP boundary).llms.txt.Phase C — Route safety (e0dd30f, 5be659e, 37131fe)
RoutePatternparser +Overlap::{None, Shadowed, Ambiguous}segment-aware analysis (15 unit tests).validate_express_route_emit_inputupgraded from exact-string-matchHashSetto overlap-aware; catches/:a/:bvs/:x/:yambiguity, allows literal-shadows-param.routePathbuilder +KnownRouteunion emitted alongside the existingvoxRoutesmanifest. Authors write<a href={routePath["/users/:id"](userId)}>and get full type-flow on path params.Phase E — Reactive dep inference (5db51fc, a1c6f46, 6691bf8)
@reactivedecorator — opts a freefninto cross-call dep tracking fromderived/effect.// dep_inference.over_trackhint comment emitted in TSX above anyuseMemo/useEffectwhose 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:.vox.uireactive modules — gates Phase D code (~2 weeks once accepted).Cleanup (0fcb734, 4df880e, 277942a, e45629d, f0a3d1b) — orphan-snapshot acceptance across
reactive_smoke/state_machine_integration/web_ir_lower_emit(18 baselines),@scheduledtest tombstoning,actor/workflowtest inversion, inbound-link goldens fix,view_roots-setup gap fix in broken-link tests, contributor doc note. Every fix verified pre-existing on merge-basedcae7e83avia stash test.What this is not
url-decl ↔ routes cross-validation. Phase C slice A bypassed that need by generatingroutePathbuilders directly from route patterns.Ludus / gamify (if applicable)
types added; no Ludus surface touched.teaching_hookchanges.vox_validate_sourcetocontracts/mcp/tool-registry.canonical.yamlplus dispatch (crates/vox-orchestrator/src/mcp_tools/dispatch.rs), input schema (input_schemas.rs), and http_gateway allowlist (http_gateway/mod.rs).VOX_LUDUS_*env vars added.Testing
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@reactivecallee 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_sourcehappy path, lazy-generation guard.route_express_emit_test::validate_*_overlap— 4 new integration tests pinning the segment-aware conflict policy.Data Storage / DB
crates/vox-db/src/schema/manifest.rsnot touched; no baseline version change.Reviewer notes
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
.vox.uireactive modules with generated React provider/hooks and typed fragment declarations emitting typed React components@reactiveopt-in for deeper dependency inference and safer reactive behaviorBug Fixes
Tooling
Documentation
.vox.ui, fragments, and MCP validator integration