GUI DSL¶
The GUI DSL is the layout and component system that assembles operator screens. Pages are rendered entirely on the server as HTML with Tailwind CSS. The browser runs a thin TypeScript client that receives differential updates over SignalR and patches the DOM.
Server-rendered architecture¶
graph LR
CT[Component Tree] --> PR[PageRenderer]
PR --> HTML[HTML + Tailwind]
HTML -->|SignalR| Client[Browser — TypeScript Shell]
Client -->|User Events| PR There is no client-side framework (no React, no Blazor). The server owns the component tree, renders full HTML, and pushes incremental updates. This keeps the client simple and eliminates client-side state synchronization.
IComponentServer¶
Every component type implements the IComponentServer interface:
public interface IComponentServer
{
string Render(ComponentNode node, RenderContext context);
Task HandleEventAsync(string eventName, JsonElement payload);
}
Renderreturns an HTML string for the component and its children.HandleEventAsyncprocesses user interactions (clicks, input changes) forwarded from the client.
Component tree¶
The page structure is defined as a tree of ComponentNode records:
[GenerateSerializer]
public record ComponentNode
{
[Id(0)] public string Type { get; init; } = "";
[Id(1)] public Dictionary<string, object> Props { get; init; } = new();
[Id(2)] public List<ComponentNode> Children { get; init; } = [];
}
A typical page tree:
Page
├── Grid (columns: 3)
│ ├── MetricCard (tag: "SumpLevel", unit: "%")
│ ├── MetricCard (tag: "Pump1Speed", unit: "RPM")
│ └── MetricCard (tag: "Pump2Speed", unit: "RPM")
├── SplitView
│ ├── SceneViewport (scene: "pump-station")
│ └── AlarmList (filter: "active")
└── TabGroup
├── Tab (label: "Trends")
│ └── TrendChart (tags: [...])
└── Tab (label: "Events")
└── EventLog (filter: "last-24h")
Component categories¶
Layout¶
| Component | Purpose |
|---|---|
Page | Top-level container with title, breadcrumbs, navigation |
Grid | CSS grid layout with configurable columns and gap |
SplitView | Resizable horizontal or vertical split |
TabGroup | Tabbed content panels |
SCADA¶
| Component | Purpose |
|---|---|
AlarmList | Real-time alarm table with acknowledge/shelve actions |
EventLog | Chronological feed of state changes, commands, and alerts |
SceneViewport | Bridge to the scene graph — renders SVG process graphics |
TrendChart | Historical and real-time tag value charts |
UI primitives¶
| Component | Purpose |
|---|---|
Card | Container with header, body, optional actions |
Gauge | Circular or linear gauge bound to a tag value |
MetricCard | Single tag value with label, unit, and sparkline |
Button | Action button for operator commands |
ChatPanel | AI assistant panel for querying equipment history |
PageRenderer¶
The PageRenderer service composes the full component tree into an HTML page:
- Walk the tree depth-first.
- Call
Render()on each component'sIComponentServerimplementation. - Concatenate the HTML fragments into a complete page with Tailwind utility classes.
- Send the full page on initial load, then differential updates on state changes.
SceneViewport bridge¶
The SceneViewport component bridges the GUI DSL and the scene graph. The GUI DSL handles everything around the viewport — panels, alarm lists, navigation — while the scene graph handles the process visualisation inside it.
┌─────────────────────────────────────────┐
│ Page (GUI DSL) │
│ ┌─────────────────┬───────────────────┐│
│ │ SceneViewport │ AlarmList ││
│ │ ┌─────────────┐ │ (GUI DSL) ││
│ │ │ Scene Graph │ │ ││
│ │ │ (SVG) │ │ ││
│ │ └─────────────┘ │ ││
│ └─────────────────┴───────────────────┘│
│ ┌─────────────────────────────────────┐│
│ │ TrendChart (GUI DSL) ││
│ └─────────────────────────────────────┘│
└─────────────────────────────────────────┘
Differential updates¶
When a tag value changes, the server:
- Identifies which components in the tree depend on that tag.
- Re-renders only those components.
- Sends the HTML fragment and a DOM target selector over SignalR.
- The TypeScript client patches the specific DOM node.
This avoids full-page re-renders and keeps update latency low.