[00133] Create the rusty-xaml Runtime XAML Parser and Builder Crate - #133
Merged
rorychatt merged 4 commits intoAug 10, 2026
Merged
Conversation
Parses XAML markup into `rusty` widget trees at runtime, where `rusty-ivyml` does the same job at compile time. The vocabulary is a table for the same reason `codegen::shape_for` is: the constructors are not uniform, `List` attaches children with `.item`, and some attributes are consumed by the constructor. Nothing is dropped silently — an unknown element, an unknown attribute, an unparseable value or a child on a leaf is a `XamlError` carrying the element, the attribute and the line it came from. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds the tests that only mean something across several elements: a parsed tree serializing identically to the equivalent builder chain, `.child` and `.item` attachment three deep, `parse_file` against a fixture, and ids and events surviving `assign_ids`. `mapping_covers_every_ivyml_element` scans `rusty-ivyml`'s `shape_for` so an element added to the compile-time vocabulary cannot silently go missing from the runtime one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The example is `rusty/examples/counter.rs` with its UI in XAML, and exists mainly to show where the parse belongs: inside `build`, against a context rebuilt from current state, since a binding is resolved once when the document is parsed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
rorychatt
deleted the
tendril/00133-CreateTheRustyxamlRuntimeXAMLParserAndBuilderCrate
branch
August 10, 2026 09:27
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.
Fixes #116
00133 — Create the rusty-xaml Runtime XAML Parser and Builder Crate
Implements issue #116.
Branch
tendril/00133-CreateTheRustyxamlRuntimeXAMLParserAndBuilderCrate,three commits:
bcf22b8,a6c080b,bd00e0d.Changes
A new leaf crate,
rusty-xaml, that turns XAML markup intorustywidget treesat runtime — the counterpart to
rusty-ivyml, which does the same job at compiletime. A document that only exists once the program is running (read from disk,
from a database, or edited while the app is up) can now become a
rusty::views::Element.StackPanel,Grid,WrapPanel,Border,GroupBox,ItemsControl/ListView,ListViewItem,TextBlock,Button,Badge,TextBox,Image,ProgressBar,Separator,plus
Spacer), each also accepting its Rusty name as an alias. Attributes arePascalCase and map onto the matching builder, with the XAML-specific
translations spelled out (
HorizontalAlignment="Right"→.align(Align::End),IsEnabled="False"→.disabled(true)).{Binding Count}and{Binding Path=Count}resolve against aXamlContext;{}is the literal-brace escape. Resolution happens at parsetime, so a tree is a snapshot — the crate docs say to call
parse_withinsideView::build, which is also the whole of the#[rusty::view]integration.Click="OnIncrement"names a handler theXamlContextsupplies fromRust. The value is a name, never an expression; there is no interpreter here.
attribute, an unparseable value, a child on a leaf, a missing handler, an
unresolved binding — each is a
XamlErrornaming the element, the attribute andthe line and column it came from. Markup that renders half its styling is worse
than markup that refuses to render.
mapping_covers_every_ivyml_elementscansrusty-ivyml/src/codegen.rsat test time, so an element added to thecompile-time vocabulary cannot silently go missing from the runtime one. Same
house style as
every_widget_type_is_mappedinrusty/src/shared/widget_names.rs.rusty-xamlis a leaf crate: it depends onrustyandrustydoes notre-export it, because that would be a dependency cycle. Unlike
rusty-ivyml,which
rustycan re-export precisely because a proc-macro crate does not dependon
rusty. Consumers add a second dependency line, as they already do forrusty-serverandrusty-docs.API Changes
Additive only. No existing signature changed, and no existing test moved.
Workspace:
roxmltree = "0.21"added to[workspace.dependencies];"rusty-xaml"added toworkspace.members.Cargo.lockgained one entry,roxmltree 0.21.1— its only transitive dependency,memchr, was already locked.Deviations from the plan
Seven, each because an assumption the plan made about
rusty's API did not hold.All are resolved in the direction the plan's own rules point;
Verification/CheckResult.mdhas the full reasoning.
rusty,roxmltree,serde_jsonDeserializeOwned, whichserde_jsondoes not re-exportserde.workspace = trueSize::parse_cssaccepts a bare120None— the same fn deserializes the wire formatSize::Pxfallback inas_sizeFontWeight="Bold"→.bold(true)TextBlock::bold(self)takes no argument.bold();Bold/Italicalso accepted as booleans,Falsea no-opBackgroundhas no widget equivalent, so errorContainer::background(Color)existsBorder/Container, error everywhere else;Marginstill errors everywhereHeaderis consumed byCard's constructorCard::new()takes no argumentHeader/Title→Card::title(..)From<std::io::Error>Iovariant carries aPathBuf, which a bareFromcannot supplyparse_file_withDocumentthat could interpret onePosition { line, column }, converted eagerly withtext_pos_atTwo smaller judgement calls in the same spirit:
Columnsis accepted only onGrid.Layouthas acolumnsfield but nocolumnsbuilder, so<StackPanel Columns="2">is anUnknownAttribute.can enumerate a type's builder methods at runtime. Each widget's applier lists
the Rusty spelling next to the XAML one instead (
Gap,Rounded,Border,Disabled,ReadOnly,Max,Color,Direction), which is what the fallbackwas there to provide.
Files Modified
New (
rusty-xaml/, 2541 lines):Cargo.tomlrusty,roxmltree,serde,serde_json, dev-deptokiosrc/lib.rsparse*functions; crate docs on binding snapshots andx:Namesrc/build.rssrc/value.rs{Binding ..}resolution and theas_*coercions; 15 unit testssrc/error.rsXamlError,Position; 4 unit testssrc/context.rsXamlContext; 4 unit teststests/parse.rstests/fixtures/dashboard.xamlexamples/xaml_counter.rscounter.rswith its UI in XAMLModified:
Cargo.toml— one member, one workspace dependency.Cargo.lock—roxmltree 0.21.1.README.md— one row in the crate table.Manual Testing
Beyond the automated gates (807 workspace tests passing, 73 of them new — see
Verification/RustTest.md):PORT=3123 cargo run -p rusty-xaml --example xaml_counterstarts and binds loopback, logging
RUSTY_PORT=3123.curl http://127.0.0.1:3123/returns 404 because this checkout has no built frontend bundle — the
pre-existing
rustyexample behaves identically(
PORT=3124 cargo run -p rusty --example counterlogsRusty server listening on 127.0.0.1:3124and also returns 404 at/), so the new example is on parwith the ones already in the tree. Both processes were killed afterwards.
rusty::server::DEFAULT_BIND_ADDRESSis already127.0.0.1(
rusty/src/server/ws.rs:72), which the example relies on rather than choosingits own bind address. Nothing in this plan listens on
0.0.0.0.rusty-desktopCI job.cargo build -p rusty-desktopandcargo clippy -p rusty-desktop --all-targets -- -D warningswere both run withdefault features, since adding a workspace member affects every member. Clean.
ids_and_events_survive_assign_idsparses a document, runsassign_ids, thendispatches
clickthrough the realEventRegistryby widget id and asserts thecontext's handlers ran. That is the part a browser click would exercise, checked
without one.
No screenshots: the change has no visual surface of its own — a parsed tree
serializes to the same JSON as a hand-built one, which the tests assert directly.
Commits
Created using Ivy Tendril.