How it works
MeshWeaver's UI model has three interlocking ideas. Once they click, building rich interfaces is surprisingly straightforward.
1. Controls are immutable records
Every control is a plain C# record. With* methods return new instances — the original is never mutated:
var button1 = Controls.Button("Click me");
var button2 = button1.WithId("myButton"); // button1 is unchanged
button2
This means control definitions are pure data. You can safely share, copy, and compose them without worrying about side effects. A control doesn't render anything until it's placed in a layout area.
2. Rendering is declarative and area-based
Instead of describing how to update the DOM, you describe what each named area should show. Only the affected area re-renders when its data changes — sibling areas are never disturbed:
Controls.Stack
.WithView(Controls.Html("<h1>Dashboard</h1>")) // Static — rendered once
.WithView(liveDataStream.Select(d => ShowData(d))) // Dynamic — updates on each emission
.WithView(Controls.Button("Refresh")) // Static — rendered once
Container structure is fixed; only observable-backed slots re-render.
3. Reactivity comes from IObservable<T>
Pass any IObservable<T> as a view and the content updates automatically. Subscriptions are created and disposed for you as areas mount and unmount:
var counter = Observable.Interval(TimeSpan.FromSeconds(1));
Controls.Stack
.WithView(counter.Select(n => Controls.Label($"Count: {n}")))
No callbacks to wire up. No teardown code to write — the counter below is ticking live.
Live example
Controls.Stack
.WithView(Controls.Html("<b>MeshWeaver UI — live from the kernel</b>"))
.WithView(Controls.Markdown($"Rendered at **{DateTime.Now:HH:mm:ss}** — controls are plain C# records."))
.WithView(Controls.Button("A static button (no action wired here)"))
Where to go next
Browse the full set of controls, layout primitives, and data-binding guides:
| Topic | Description |
|---|---|
| Layout Areas | Named rendering slots — how the hub decides what to show and where |
| Container Controls | Stack, Tabs, Toolbar, Splitter, Layout — composing areas together |
| Layout Grid | CSS-grid-based two-dimensional layouts |
| Data Binding | Two-way reactive binding via JSON Pointers — the contract every backend area must follow |
| Static vs. Dynamic Views | When areas re-render and how to control update frequency |
| Displaying Times | Stored instants are UTC — render them in the viewer's own zone, and which values must NOT be converted |
| DataGrid | Tabular data with sorting, filtering, and row actions |
| Form Input Controls | Checkboxes, switches, date pickers, list selectors, radio groups, node picker — each rendered live |
| Badges, Icons & Status | Badges, icons, progress bars, spacers, menu items, and card skins |
| Navigation Menus | NavMenu, NavGroup, NavLink — collapsible side navigation |
| Code, Diff & Markdown Editors | Monaco-backed source editing, diffs, and markdown authoring |
| Node Cards & Catalogs | Cards, thumbnails, live node collections, and grouped catalogs |
| Mesh Search & Catalogs | One URL-driven Search area per node — group by namespace/type/category, drill down, embed with @@ |
| Configurable Home & Space Pages | Your home / Space Overview is one editable markdown page that embeds regions with @@ |
| Editor | Auto-generated editable forms from C# records |
| Attributes | Declarative style, visibility, and validation annotations |
| Side Panel | Slide-in panels for detail views and settings |
| Reactive Dialogs | Modal dialogs backed by observable state |
| Node Menu | Context menus on mesh nodes |
| React Frontend | The client-side React frontend — same UiControl contract, rendered in the browser over gRPC-web: running it, rendering, theming, chat, testing |
| Custom Blazor Controls | Extend the Blazor portal with your own control — a UiControl subclass + a BlazorView, registered with WithView; ships from core, a pack, or a plugin at runtime |
| Custom React Controls | Extend the React renderer with your own control — server-side $type + a React registry entry |