From 835c777714942ad46fb78e699892538fb06c6d53 Mon Sep 17 00:00:00 2001 From: Mark Polak Date: Wed, 15 Jul 2026 16:43:18 +0200 Subject: [PATCH 1/4] Add render state schema generation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: fe040cbf-edcc-4e6f-b829-f85e08f2d044 --- Cargo.lock | 21 + Cargo.toml | 1 + DESIGN.md | 63 ++ crates/webui-cli/Cargo.toml | 2 + crates/webui-cli/README.md | 20 +- crates/webui-cli/src/commands/build.rs | 179 ++++- crates/webui-cli/src/commands/mod.rs | 3 + crates/webui-cli/src/commands/state_schema.rs | 365 ++++++++++ .../src/commands/state_schema/inference.rs | 419 ++++++++++++ .../src/commands/state_schema/model.rs | 264 +++++++ .../src/commands/state_schema/scope.rs | 99 +++ ...sts__snapshots_component_scope_schema.snap | 63 ++ ...s__snapshots_nested_collection_schema.snap | 105 +++ ...s_declared_in_component_schema_bundle.snap | 114 ++++ ...napshots_routes_example_schema_bundle.snap | 645 ++++++++++++++++++ .../src/commands/state_schema/tests.rs | 394 +++++++++++ crates/webui-cli/src/main.rs | 1 + .../state-schema/component-scope/index.html | 19 + .../component-scope/nested-label.html | 1 + .../component-scope/profile-card.html | 5 + .../nested-collections/group-list.html | 20 + .../nested-collections/index.html | 1 + .../state-schema/routed-shell/app-shell.html | 4 + .../routed-shell/detail-page.html | 1 + .../state-schema/routed-shell/home-page.html | 3 + .../state-schema/routed-shell/index.html | 1 + .../routed-shell/settings-page.html | 1 + docs/ai.md | 17 + docs/guide/cli/index.md | 67 +- docs/guide/concepts/state-management/index.md | 26 + examples/app/commerce/data/state.json | 9 +- .../app/contact-book-manager/data/state.json | 1 + 32 files changed, 2908 insertions(+), 26 deletions(-) create mode 100644 crates/webui-cli/src/commands/state_schema.rs create mode 100644 crates/webui-cli/src/commands/state_schema/inference.rs create mode 100644 crates/webui-cli/src/commands/state_schema/model.rs create mode 100644 crates/webui-cli/src/commands/state_schema/scope.rs create mode 100644 crates/webui-cli/src/commands/state_schema/snapshots/webui__commands__state_schema__tests__snapshots_component_scope_schema.snap create mode 100644 crates/webui-cli/src/commands/state_schema/snapshots/webui__commands__state_schema__tests__snapshots_nested_collection_schema.snap create mode 100644 crates/webui-cli/src/commands/state_schema/snapshots/webui__commands__state_schema__tests__snapshots_routes_declared_in_component_schema_bundle.snap create mode 100644 crates/webui-cli/src/commands/state_schema/snapshots/webui__commands__state_schema__tests__snapshots_routes_example_schema_bundle.snap create mode 100644 crates/webui-cli/src/commands/state_schema/tests.rs create mode 100644 crates/webui-cli/tests/fixtures/state-schema/component-scope/index.html create mode 100644 crates/webui-cli/tests/fixtures/state-schema/component-scope/nested-label.html create mode 100644 crates/webui-cli/tests/fixtures/state-schema/component-scope/profile-card.html create mode 100644 crates/webui-cli/tests/fixtures/state-schema/nested-collections/group-list.html create mode 100644 crates/webui-cli/tests/fixtures/state-schema/nested-collections/index.html create mode 100644 crates/webui-cli/tests/fixtures/state-schema/routed-shell/app-shell.html create mode 100644 crates/webui-cli/tests/fixtures/state-schema/routed-shell/detail-page.html create mode 100644 crates/webui-cli/tests/fixtures/state-schema/routed-shell/home-page.html create mode 100644 crates/webui-cli/tests/fixtures/state-schema/routed-shell/index.html create mode 100644 crates/webui-cli/tests/fixtures/state-schema/routed-shell/settings-page.html diff --git a/Cargo.lock b/Cargo.lock index 9f061686..813dd945 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1574,6 +1574,19 @@ dependencies = [ "libc", ] +[[package]] +name = "insta" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0f8fee8c926415c58d6ae43a08523a26faccb2323f5e6b644fe7dd4ef6b82" +dependencies = [ + "console", + "once_cell", + "serde", + "similar", + "tempfile", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -1816,6 +1829,7 @@ dependencies = [ "clap", "console", "expand-tilde", + "insta", "log", "microsoft-webui", "microsoft-webui-dev-server", @@ -1824,6 +1838,7 @@ dependencies = [ "microsoft-webui-protocol", "microsoft-webui-tokens", "mime_guess", + "serde", "serde_json", "tempfile", "tokio", @@ -2931,6 +2946,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + [[package]] name = "siphasher" version = "1.0.3" diff --git a/Cargo.toml b/Cargo.toml index 4171d0ae..83c00554 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,7 @@ windows-sys = { version = "0.61.2", features = [ # Test dependencies criterion = "0.8.2" +insta = { version = "1.48.0", features = ["json"] } tempfile = "3.27.0" # Build dependencies diff --git a/DESIGN.md b/DESIGN.md index 1ed5dfbd..42fbddfe 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1793,6 +1793,69 @@ function returns `NULL`, call `webui_last_error()` for a human-readable diagnost The CLI specification and usage details are maintained in [crates/webui-cli/README.md](crates/webui-cli/README.md). +### Render State JSON Schema + +`webui schema ` analyzes the compiled entry and writes a +JSON Schema draft 2020-12 document to stdout. `--entry` selects the entry +fragment and `--title` sets the schema title. + +`webui build --emit-schema` runs the same analysis directly against the +in-memory protocol and writes `.state.schema.json` beside the +protocol. Schema generation and serialization complete before build outputs are +written. Emission is opt-in because schemas are build-time tooling artifacts, +not render-time requirements. + +For entries without routes, the output is a normal object schema. Inference +uses the following rules: + +- Plain text and normal attribute values accept JSON scalars: `string`, + `number`, or `boolean`. Complex `:property` bindings remain unconstrained + until child-template usage provides stronger structural evidence. +- Raw signals are strings. +- Dotted paths create nested objects. +- A terminal `.length` accepts the runtime's three valid forms: a string, an + array, or an object with a `length` property. +- `` collections create arrays; loop-item paths define the item schema. +- Ordered predicates infer numbers. Equality predicates infer the type of a + literal operand. +- Condition-only paths are optional because a missing condition value evaluates + false. A path referenced by rendered output or a collection remains required. +- Normal component-attribute source paths are recorded as scalars, but + child-local comparisons do not re-type the parent path. Complex bindings + preserve structural origin through the child template. Static and computed + component-local values do not become root state properties. +- Parser-synthesized `head_end`, `body_start`, and `body_end` signals are not + state. + +For routed entries, the output is a schema bundle. Each complete matched route +chain receives a distinct schema under `$defs`. Top-level `anyOf` references +those definitions, while `x-webui-routes` maps each route pattern to its +definition: + +```json +{ + "$defs": { + "route": {}, + "route.contacts.:id": {} + }, + "anyOf": [ + { "$ref": "#/$defs/route" }, + { "$ref": "#/$defs/route.contacts.:id" } + ], + "x-webui-routes": { + "/": "#/$defs/route", + "/contacts/:id": "#/$defs/route.contacts.:id" + } +} +``` + +Route definitions are not structurally deduplicated. Even routes with identical +current shapes retain independent contracts so future changes do not +unexpectedly couple them. Definition keys are readable route encodings, but +consumers should still use `x-webui-routes` rather than constructing `$defs` +keys. Host-language DTO generation and runtime schema validation are separate +concerns and are not part of `schema`. + ## Example Workflow Examples and end-to-end walkthroughs are maintained in [examples/README.md](examples/README.md) diff --git a/crates/webui-cli/Cargo.toml b/crates/webui-cli/Cargo.toml index 551286c9..bf226ac1 100644 --- a/crates/webui-cli/Cargo.toml +++ b/crates/webui-cli/Cargo.toml @@ -25,6 +25,7 @@ microsoft-webui-dev-server = { path = "../webui-dev-server", version = "0.0.18" clap = { workspace = true } anyhow = { workspace = true } console = { workspace = true } +serde = { workspace = true } serde_json = { workspace = true } actix-web = { workspace = true } awc = { workspace = true } @@ -36,6 +37,7 @@ tokio-stream = { workspace = true } log = { workspace = true } [dev-dependencies] +insta = { workspace = true } tempfile = { workspace = true } microsoft-webui-protocol = { path = "../webui-protocol", version = "0.0.18" } diff --git a/crates/webui-cli/README.md b/crates/webui-cli/README.md index c9175637..081eed54 100644 --- a/crates/webui-cli/README.md +++ b/crates/webui-cli/README.md @@ -1,6 +1,6 @@ # microsoft-webui-cli -Command-line tool for the [WebUI](https://github.com/microsoft/webui) framework — build, serve, and inspect WebUI applications. +Command-line tool for the [WebUI](https://github.com/microsoft/webui) framework - build, serve, inspect, and generate render-state schemas for WebUI applications. ## Install @@ -17,7 +17,7 @@ This installs the `webui` binary. Build a WebUI application into a compiled protocol and CSS files. ```bash -webui build [APP] --out [--entry ] [--css ] [--plugin ] [--asset-file-name-template