feat: add render state schema generation#399
Draft
Markionium wants to merge 5 commits into
Draft
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: fe040cbf-edcc-4e6f-b829-f85e08f2d044
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: fe040cbf-edcc-4e6f-b829-f85e08f2d044
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: fe040cbf-edcc-4e6f-b829-f85e08f2d044
There was a problem hiding this comment.
Pull request overview
This PR adds a render-state JSON Schema generation feature to WebUI, exposing it via a new webui schema command and an opt-in webui build --emit-schema output, along with fixture/snapshot-based coverage and documentation/spec updates.
Changes:
- Add CLI support for emitting Draft 2020-12 JSON Schema for render state (
webui schemaandwebui build --emit-schema). - Implement schema inference for state usage across bindings, control flow, collections, component scope, and routed route-chain bundles with
x-webui-routesandx-webui.preferredType. - Add fixtures/snapshots plus example state validation, and update DESIGN + docs to describe the new contract and CLI flags.
Reviewed changes
Copilot reviewed 31 out of 32 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| examples/app/contact-book-manager/data/state.json | Adds missing state field used by templates for example validation. |
| examples/app/commerce/data/state.json | Align example state keys/fields with inferred bindings and schema validation. |
| docs/guide/concepts/state-management/index.md | Documents generating render-state schemas and related inference rules. |
| docs/guide/cli/index.md | Documents webui schema and the --emit-schema build flag. |
| docs/ai.md | Updates AI reference CLI flag table + schema command usage. |
| DESIGN.md | Specifies the render-state JSON Schema contract and routed bundle shape. |
| crates/webui-cli/tests/fixtures/state-schema/routed-shell/settings-page.html | New routed-shell fixture for route-chain schema inference. |
| crates/webui-cli/tests/fixtures/state-schema/routed-shell/index.html | New routed-shell fixture entry. |
| crates/webui-cli/tests/fixtures/state-schema/routed-shell/home-page.html | New routed-shell fixture with nested routes. |
| crates/webui-cli/tests/fixtures/state-schema/routed-shell/detail-page.html | New routed-shell fixture leaf page. |
| crates/webui-cli/tests/fixtures/state-schema/routed-shell/app-shell.html | New routed-shell fixture root shell with routes. |
| crates/webui-cli/tests/fixtures/state-schema/nested-collections/index.html | New nested collections fixture entry. |
| crates/webui-cli/tests/fixtures/state-schema/nested-collections/group-list.html | New nested collections fixture covering nested loops and .length. |
| crates/webui-cli/tests/fixtures/state-schema/component-scope/profile-card.html | New component-scope fixture for attribute/local scope inference. |
| crates/webui-cli/tests/fixtures/state-schema/component-scope/nested-label.html | New component-scope fixture for nested component lookups. |
| crates/webui-cli/tests/fixtures/state-schema/component-scope/index.html | New component-scope fixture entry exercising bindings/conditions/raw. |
| crates/webui-cli/src/main.rs | Wires the new Schema subcommand into the CLI entrypoint. |
| crates/webui-cli/src/commands/state_schema/tests.rs | Adds schema inference snapshot tests + example-state/schema validation. |
| crates/webui-cli/src/commands/state_schema/snapshots/webui__commands__state_schema__tests__snapshots_routes_example_schema_bundle.snap | Snapshot for routed schema bundle output for the Routes example. |
| crates/webui-cli/src/commands/state_schema/snapshots/webui__commands__state_schema__tests__snapshots_routes_declared_in_component_schema_bundle.snap | Snapshot for route discovery in a component-defined routing shell. |
| crates/webui-cli/src/commands/state_schema/snapshots/webui__commands__state_schema__tests__snapshots_nested_collection_schema.snap | Snapshot for nested collections and .length inference. |
| crates/webui-cli/src/commands/state_schema/snapshots/webui__commands__state_schema__tests__snapshots_component_scope_schema.snap | Snapshot for component scope / attribute binding inference. |
| crates/webui-cli/src/commands/state_schema/scope.rs | Implements scope resolution across root/component/loop bindings. |
| crates/webui-cli/src/commands/state_schema/model.rs | Defines inference model + JSON Schema emission for inferred nodes. |
| crates/webui-cli/src/commands/state_schema/inference.rs | Implements protocol walk and type/requiredness inference rules. |
| crates/webui-cli/src/commands/state_schema.rs | Implements webui schema, route-chain bundling, and schema serialization. |
| crates/webui-cli/src/commands/mod.rs | Exposes the new state_schema command module and clap variant. |
| crates/webui-cli/src/commands/build.rs | Adds --emit-schema build output and filename collision checks. |
| crates/webui-cli/README.md | Documents new CLI surface (--emit-schema, webui schema). |
| crates/webui-cli/Cargo.toml | Adds dependencies needed for schema output ordering and snapshots. |
| Cargo.toml | Adds workspace-level insta dependency for JSON snapshot testing. |
| Cargo.lock | Locks new dependencies introduced by schema snapshot testing. |
Comment on lines
+419
to
+427
| while let Some((current_schema, current_value, path)) = pending.pop() { | ||
| let actual_type = json_type(current_value); | ||
| if !schema_accepts_type(current_schema, actual_type) { | ||
| errors.push(format!( | ||
| "{path}: expected {}, found {actual_type}", | ||
| display_schema_type(current_schema) | ||
| )); | ||
| continue; | ||
| } |
Comment on lines
+456
to
+462
| fn schema_accepts_type(schema: &Value, actual: &str) -> bool { | ||
| match schema.get("type") { | ||
| Some(Value::String(expected)) => expected == actual, | ||
| Some(Value::Array(expected)) => expected.iter().any(|value| value.as_str() == Some(actual)), | ||
| _ => true, | ||
| } | ||
| } |
Comment on lines
+481
to
+490
| fn json_type(value: &Value) -> &'static str { | ||
| match value { | ||
| Value::Null => "null", | ||
| Value::Bool(_) => "boolean", | ||
| Value::Number(_) => "number", | ||
| Value::String(_) => "string", | ||
| Value::Array(_) => "array", | ||
| Value::Object(_) => "object", | ||
| } | ||
| } |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: fe040cbf-edcc-4e6f-b829-f85e08f2d044
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: fe040cbf-edcc-4e6f-b829-f85e08f2d044
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 31 out of 32 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
crates/webui-cli/src/commands/state_schema/tests.rs:462
- The schema validation helper treats schemas without a top-level
typeas accepting any value, which makesanyOfand boolean schemas (true/false) effectively unchecked. That can letexample_state_files_match_generated_schemaspass even when a state violates ananyOfconstraint (e.g..lengthunions) or when a subschema isfalse.
fn schema_accepts_type(schema: &Value, actual: &str) -> bool {
match schema.get("type") {
Some(Value::String(expected)) => expected == actual,
Some(Value::Array(expected)) => expected.iter().any(|value| value.as_str() == Some(actual)),
_ => true,
}
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
webui schemato generate a render-state JSON Schema from an existingprotocol.bin.webui build --emit-schemato emit<protocol-stem>.state.schema.jsondirectly from the in-memory build result.x-webui-routes.x-webui.preferredTypemetadata for downstream type generation.Why
The original proof of concept combined schema inference and C# generation in one spike. It also flattened route branches into one state shape, losing the distinction between the state required by individual routes.
This PR separates the language-neutral schema contract so it can be reviewed and evolved independently.
For non-routed applications, the output is a normal object schema.
For routed applications, the output contains one definition per complete route chain:
{ "$defs": { "route": {}, "route.contacts.:id": {} }, "x-webui-routes": { "/": "#/$defs/route", "/contacts/:id": "#/$defs/route.contacts.:id" } }Route definitions remain independent even when two routes currently have the same state shape.
Schema inference
The schema is inferred from how compiled templates use state:
• Plain rendered values and normal attributes accept scalar values.
• Raw values infer strings.
• Dotted paths infer nested objects.
•
<for>collections infer arrays and item shapes.• Condition-only paths are optional because missing condition state evaluates false.
• Boolean conditions infer booleans.
• Ordered comparisons infer numbers.
• Integer literals infer integer .
• Known types propagate across path-to-path equality.
• Component bindings resolve back to parent state paths.
• Complex properties preserve structured values.
• Internal handler signals are excluded.
Broad validation types can include a generation hint without narrowing the JSON Schema contract:
{ "type": ["string", "number", "boolean"], "x-webui": { "preferredType": "string" } }Why
x-webui.preferredTypeexistsJSON Schema validation and DTO generation have slightly different goals.
The renderer is permissive. A normal text binding can render a string, number, or boolean:
{ "type": ["string", "number", "boolean"] }However, generated DTOs should guide developers toward the type normally intended by the template:
Languages such as C# do not have a native
string | number | booleanunion. Generating object would provide little type safety and complicateSystem.Text.Jsonsource generation.The schema therefore keeps the complete runtime-valid type while adding a non-validating annotation:
{ "type": ["string", "number", "boolean"], "x-webui": { "preferredType": "string" } }Standard JSON Schema validators use
typeand ignorex-webui. Host-language generators usepreferredTypeto select a practical DTO type.This keeps the schema accurate for dynamic consumers while producing useful strongly typed APIs for TypeScript, Rust, C#, and future generators.
Future: a WebUI JSON Schema vocabulary
x-webuiis a pragmatic interim annotation. A better long-term design would be to publish a versioned WebUI render-state meta-schema and vocabulary built on JSON Schema Draft 2020-12.That vocabulary could formally define:
Generated schemas could then reference a WebUI-specific
$schemaURI rather than only the generic Draft 2020-12 meta-schema. Standard JSON Schema behavior would remain available, while WebUI-aware validators and generators could also validate the additional metadata.This PR does not define that vocabulary. The current
x-webuishape is intentionally provisional so the schema and generation workflow can be evaluated first.Validation
• Validate every repository example containing both src/index.html and data/state.json .
• Snapshot component scope, nested collections, routed shells, and the complete Routes example.
• Cover nested and control-flow routes, optional and splat paths, nested arrays, complex properties, .length , encoded route references, and literal-looking state keys.
• cargo xtask check
• cd docs && pnpm build
Status and limitations
This remains work in progress:
x-webuiis a provisional annotation. A versioned WebUI JSON Schema vocabulary and meta-schema would be preferable long term.Host-language generation is intentionally excluded from this PR. A stacked follow-up demonstrates TypeScript, Rust, and C# generation on top of this schema contract.