MeshWeaver generates UI where the data lives. Instead of shipping large datasets to clients and rendering them in the browser, computations happen server-side and only the rendered components stream across the wire. The result is dramatically lower network traffic and real-time interactivity without sacrificing data security.
Server-side rendering pipeline: computation and data stay in the Hub; only rendered components and JSON patches travel to the browser.
The Data Compression Principle
The design philosophy is easiest to grasp with a concrete example. Suppose you need to display a million-row dataset as a 10 × 10 summary table. The naive approach transfers all one million rows to the client; MeshWeaver transfers only the 100 aggregated numbers:
This pattern applies everywhere — charts, grids, KPI tiles — and becomes especially powerful when data is sensitive or very large.
The Controls Language
Inside a MessageHub, UI is described using the Controls Language: an immutable, declarative API whose objects serialize naturally to JSON.
// Server-side control definition
Controls.Stack
.WithView(Controls.Text("Welcome!"), "Welcome")
.WithView(Controls.Button("Click Me").WithClickAction(OnClick), "Button")
.WithView(Controls.DataGrid(salesData), "Sales")
The resulting JSON streams to the Portal, which renders it as HTML for the browser. Because the control tree is plain data, it round-trips cleanly over any transport and is trivial to version or diff.
Two-Way Data Binding
Rendering is only half the story. MeshWeaver uses a walkie-talkie pattern where both the hub and the Portal hold a live ISynchronizationStream. Changes flow in both directions: control updates push outward to the browser, and user events push inward to the hub.
| Layer | Role |
|---|---|
| MessageHub | Defines controls and owns data; processes click and change events |
| Portal | Holds the server-side ISynchronizationStream; renders controls to HTML |
| Browser | Thin display layer — shows HTML and forwards user events back to the Portal |
Control Lifecycle
The sequence below shows a full round trip from hub to browser and back:
Incremental Updates
After the initial load, only changes travel over the wire. MeshWeaver uses JSON Patch (RFC 6902) for this:
[{"op": "replace", "path": "/areas/counter/Data", "value": 42}]
A counter incrementing once sends a single-operation patch rather than re-sending the entire control tree. This keeps real-time dashboards snappy even under heavy update rates.
When a first frame is slow
Every area open on the owner hub passes three points before the subscriber can see the area:
- Base frame. The host sets an empty store with the "Building layout…" progress marker. The subscriber gets this almost at once.
- Compose. The host has a rendered store carrying a control for the requested area. This interval covers more than the view's inputs: scheduling the render subscription off the hub's turn, the view function and every stream it combines, rendering the control tree, and any other renderers of the same area.
- Delivery. The owner hub's update queue applies that store update and the stream publishes it (
SetCurrentcompares and emits it to subscribers). Then the patch travels to the subscriber.
LayoutAreaHost times all three, once per open, and writes a single line when the requested area first appears in its store:
FIRST FULL FRAME of layout area 'InstallCta' on Northwind after 1012 ms:
base frame at 31 ms, first control composed at 990 ms, then the owner's update queue applied it
(slow-first-frame threshold 1000 ms; -1 = hop not passed)
- Level. The line is
Debugby default, so a healthy open writes nothing to Loki. It isInformationonly when the first full frame took longer thanLayout:SlowFirstFrameThreshold, aTimeSpanthat defaults to one second. A shelf of N cards opens 2N areas, so logging every open atInformationwould cost Loki on every page view, while the slow open is the anomaly an operator needs to see. - Cost. One latch and three timestamps per host, read where the host already passes. No timer, no subscription, and no second line for later updates of the same area.
- Reading it.
- A large gap between the base frame and "composed" is time spent producing the rendered store: the view's inputs, or the scheduling and rendering around them. Narrow it further by timing the view's inputs directly; the line alone does not say which.
- A large gap between "composed" and the total means the owner's update queue or the publication is slow. The delivery mark is taken once the update has committed, so a failed update never reports.
- The clock starts when the owner CONSTRUCTS the host, not when the subscriber asks for the area. Whatever a subscriber measures beyond the total is therefore not transport alone. It also holds the subscribe request's routing, the owner hub's activation, the work before the host was built, the patch's return trip, and the subscriber's own processing. The line does not split these. Measure them separately before calling the remainder transport.
- An area composed inside its own update (
UpdateArea) marks compose inside the update and delivery once it committed, so the two are close together.
The code is FirstFrameTiming (MeshWeaver.Layout). Systemorph/MeshWeaver.Plugins#4399, a Store row that arrived about a second after its base frame on production and in no test rig, is the reason it exists.
Handling User Interactions
User interactions become hub messages. When a button is clicked, the browser sends OnClick to the Portal, which forwards a ClickedEvent to the hub and invokes the registered action. Click handlers are synchronous — compose any follow-up work as an observable and Subscribe:
Controls.Button("Save")
.WithClickAction(context =>
{
// context.Area – which control was clicked
// context.Payload – custom data attached to the event
// context.Hub – hub reference for posting messages
context.Hub.Post(new SaveRequest(data)); // fire-and-forget — no await
return Task.CompletedTask;
})
🚨 Never
async context => await .... Anawaiton a mesh operation inside a click handler runs on the hub's scheduler and deadlocks it. The handler body is synchronous; anything that produces a result is anIObservable<T>chain ending in.Subscribe(...). See AsynchronousCalls.md for the canonical patterns.
Available Controls
MeshWeaver ships a rich control library. The table below lists the most commonly used controls; see the complete controls reference for the full set including advanced layout, charting, and editor controls.
| Control | Purpose |
|---|---|
TextFieldControl |
Text input with validation |
SelectControl |
Dropdown selection |
DataGridControl |
Tabular data display |
ButtonControl |
Clickable actions |
DialogControl |
Modal dialogs |
EditFormControl |
Form containers |
LayoutAreaControl |
Nested layout regions |
Live Example
The cell below runs in the kernel and renders a small stack of controls — the same building blocks used throughout the framework:
MeshWeaver.Layout.Controls.Stack
.WithView(MeshWeaver.Layout.Controls.Markdown("### Controls Language — live demo\nEach `.WithView(...)` call adds a child to this stack."))
.WithView(MeshWeaver.Layout.Controls.Body("A plain text paragraph rendered inside a stack control."))
.WithView(MeshWeaver.Layout.Controls.Button("Click Me"))
Every user-visible string is localized
The portal renders its chrome in the viewer's language (English and German ship today), so text in a control is never a bare literal — it goes through the localization seam as you write it:
Controls.Button(host.Localize("ui.createRelease"))
Because a layout area renders per subscriber, the viewer's language is available on the server
at render time, the same way their display time zone is. Resolution is explicit off
AccessContext.Locale — never ambient CultureInfo.CurrentUICulture, which does not survive the
hub's scheduler hops.
Text attached to a declaration carries its translation next to the English instead, so the two cannot drift:
[Description("Sync direction")]
[Translation("de", "Synchronisierungsrichtung")]
public SyncDirection Direction { get; init; }
Prefer wording that needs no translation at all — a language-neutral glyph (➕, ✏️, 🗑️) plus a translated tooltip beats a translated label. Full reference: Localization.
Why This Architecture?
| Benefit | How it is achieved |
|---|---|
| Bandwidth efficiency | Transfer summaries and patches, never raw datasets |
| Real-time updates | JSON Patch (RFC 6902) for incremental control-tree changes |
| Data security | Sensitive data never leaves the server unnecessarily |
| Single source of truth | All state lives server-side; the browser is a passive renderer |
| Flexibility | Any control can be data-bound; controls compose freely |