...
write_objects mechanics on DisplaysList (document objects, read-before-write)write_objects → get_state → check errorList → visual checkpoint → fix → move onrespond)...
Tool | When |
|---|---|
| Once at start of session to confirm field names |
| Once to get the brush catalogue |
| Once to get the theme-pair catalogue (Light/Dark, Steel/Graphite, etc.) |
| Before using an element type you have not used this session |
| Before attaching a dynamic you have not used this session |
| Before every modification to an existing display (document-object rule) |
| After your plan is complete |
| After every write, to check |
| At visual checkpoints during a Canvas build, or once at the end of a Dashboardbuild. See §3 for the cap. |
...
Hex is reserved for things with domain meaning that shouldn't change across themes — heat = red, water = blue, alarm = red, batch progress = amber. Everything else should adapt to whichever of the 13 theme pairs the operator is running.
Call list_elements('ThemeNames') for the authoritative list at runtime. The typical usage map:
Light / Dark pair | Use for |
|---|---|
| Default office / default control room |
| Refined corporate office / premium control room |
| Industrial office / industrial control room |
| Soft UI emphasis, OEM branding |
| Refreshing / deep contrast |
| Warm accents for specialty applications |
| Accessibility, outdoor tablets, low-vision |
Theme is switched at runtime via @Client.Theme = "Dark". Every properly-themed display reflows automatically.
...
Call list_elements('ThemeColors') for the authoritative list. The 12 brushes you'll use 90% of the time:
Brush | Meaning | Typical use |
|---|---|---|
| Card / section background | Background |
| Full-page background | The display-root Background override |
| Control body | Gauge / chart / data-grid background |
| Primary text | Headings, values, labels |
| Meta text | Units, captions, "UPDATED AT" stamps |
| Highlighted text | Section titles, accent links |
| The solution's accent color | Active-state markers, selected-row borders, links |
| Running / OK state | Indicator fills, live-data values |
| Stopped / fault state | Indicator fills, alarm text |
| Active alarm (yellow) | Banded gauge danger zones, pulsing elements |
| HPG two-state fills | Status-indicator shape fills tied to a boolean |
| Process water | Pipe runs carrying water/aqueous streams |
Other brushes exist and are valid — ElementBlue, ElementGreen, AlarmHighPriority, ColorCyan, ColorSlate, ColorAmber, ColorTeal, ColorCoral, ColorPurple, ColorDimmed, PopupBackground, DefaultFill, DefaultStroke, DefaultBorder, and more. Check list_elements('ThemeColors').
...
Never send a partial update — you'll silently delete everything you didn't include.
Keep the element list in working memory. During a session that adds, modifies, or removes elements across multiple turns, track what the display holds — same discipline a programmer uses to keep the state of the code they're editing in their head. When you write, every element you intend to keep must be in the payload, and elements you removed must be absent. errorList will not tell you about an element you forgot to drop; only the user will notice it's still on screen.
MainPage is predefined in new MainPage is predefined in new solutions. Write main content directly into it — no need to create a new display for the landing screen.
...
AI-created objects are automatically tagged with the MCP category, which makes them editable / overwritable by subsequent AI writes. Objects without this label are "owned" by the user and produce Skipping existing on write attempts. This is the user-protection mechanism — respect it. When you see Skipping existing, the user can add the MCP label in Designer if they want to allow AI editsthe user can add the MCP label in Designer if they want to allow AI edits.
Exception: Description fields are always writable, even on user-owned objects without the MCP label. If the user asks you to update a description on an object you otherwise can't edit, that specific field will still save.
solution_id on every write...
get_objects(detail='full') if modifying an existing displaywrite_objects(data=[...])get_state(target='designer'), look at errorListerrorList non-empty, fix and go back to step 3errorList and readOnly from get_stateget_state on a display returns compile errors as a list:errors as a list, and also reports whether the Designer is currently read-only:
{
"readOnly": false,
"readOnlyReason": null,{
"errorList": [
{
"ID": 0,
"ErrorCode": "BC30456",
"IsWarning": false,
"Line": 8,
"Column": -1,
"Location": "Uid_41_TTextBlock_LinkedValue_e1",
"ErrorText": "error BC30456: 'Status' is not a member of 'UserType'."
}
]
}...
If errorList is absent or empty, the display compiled clean.
Also check readOnly. If it is true, a subsequent write_objects will fail — check readOnlyReason and surface it to the user before retrying. Common causes: runtime is running, another client has the display open for edit.
errorList is ground truth for compile correctness: bindings resolve, required fields are present, the display will render without crashing. A clean errorList is necessary before you declare the display donerespond as finished.
errorList is blind to visual correctness. None of the following produce an error:
...
Canvas: spatial correctness is the whole point. Coordinate-based composition of pipes, vessels, zone layouts, and P&ID flow is the class of work where authoring blind gets it wrong the most. Take get_screenshot_screenshot(target='display', element='X') at authoring checkpoints:
...
Dashboard: grid reflow is resolved at render time against a specific window size, and most Dashboard mistakes (wrong control choice for the cell purpose, missing Cell.HeaderLink, broken DataGrid → detail wiring) are visible in the structure, not the pixels. Take one get_screenshot at the end, before declaring done, to confirm cells populated and content did not collapse unexpectedly. If that one screenshot reveals a problem, fix and screenshot again; beyond the second screenshot, hand back to the userScreenshots are generally unnecessary for Dashboard — rely on a clean errorList and a clear written summary to the user. If something looks wrong after the user previews it, fix and iterate; don't screenshot speculatively.
Not for iterative self-soothing. Do not take a screenshot after every write just to feel certain. The cost is real and the behavior encourages re-work spirals over clean planning. Screenshots go at checkpoints, not at every turn.
Write a one-paragraph summary when you declare donerespond, regardless of paradigm: which zones or cells exist, what lives in each, which tags drive what. The summary is what the user reads before they look at the display themselves — and it forces you to notice gaps ("I reserved a zone for alarms but never put an AlarmViewer in it") that errorList cannot.
...
The writer normalizes several shapes on save. When you later read the display back, you get the normalized form:
You wrote | Stored as |
|---|---|
|
|
|
|
|
|
|
|
| Flat array OR wrapper — writer accepts both |
Consequence: when round-tripping a display for edits, don't expect to see "Type": "Cylinder" — you'll see a "Path" with auto-generated Data. Edit the Path, or add new Cylinders alongside (they normalize too).
Token | Pixels | Where |
|---|---|---|
xs | 4 | Gap between label and value in a stacked pair |
sm | 8 | Gap between sibling sub-elements within a card |
md | 16 | Gap between sibling cards in a row |
lg | 24 | Card interior padding (content vs card edge) |
xl | 32 | Zone padding (zone background Rectangle vs content) |
2xl | 48 | Major section separator |
3xl | 64 | Display-level margins |
Use FontFamily: "Inter" (or the solution's chosen font) universally.
Role | FontSize | When |
|---|---|---|
Hero | 26–32 | Display title, single-metric hero number |
H1 | 20–22 | Main section headings |
H2 | 16 | Sub-section headings, card titles |
Body | 13–14 | Bound values, primary text |
Meta | 10–11 | Labels, units, captions |
Micro | 9 | Timestamps, very-low-priority meta |
...
...
Element | Min | Recommended |
|---|---|---|
Button | 100×32 | 130×40 |
TextBox / NumericTextBox | 120×28 | 160×32 |
ComboBox | 160×28 | 200×32 |
Slider | 200×28 | 260×32 |
ToggleSwitch | 60×28 | 80×32 |
CircularGauge / RadialGauge | 150×150 | 180×180 |
SemiCircle | 200×120 | 240×140 |
LinearGauge (horizontal) | 260×80 | 300×100 |
CenterValue | 120×120 | 140×140 |
TrendChart | 400×200 | 500×300 |
BarChart | 300×200 | 400×240 |
PieChart | 200×200 | 240×240 |
AlarmViewer | 400×220 | 600×240 |
AssetsTree | 200×300 | 240×400 |
DataGrid | 400×200 | 600×300 |
Wizard symbol (TANK/PUMP/etc.) | 60×60 | 80×80 |
Role | Hex |
|---|---|
Heat / reaction |
|
Cold / water |
|
Running / active |
|
Alarm / warning |
|
Accent / link / data |
|
Text on dark |
|
Meta text |
|
There are many element types — Shapes, Container, Interaction, Gauges, Charts, Viewer, Editors, IndustrialIcons, and the Dashboard-specific Cell. The catalogue also evolves between releases.
Rather than rely on a static list that drifts, always discover at runtime:
list_elements() — every element type, grouped by categorylist_elements('<ElementName>') — the schema for one specific element (valid properties, defaults, notes)list_elements('Wizard') — the 5 Wizard symbolslist_elements('Library/HMI') or subpath — Library symbols (see §7)list_elements('ThemeColors') — brush cataloguelist_elements('ThemeNames') — theme-pair catalogueCanvas and Dashboard skills cover which element types fit each paradigm and how to use them in context
| Role | Hex |
|---|---|
| Heat / reaction | #FFEF4444 (red) |
| Cold / water | #FF38BDF8 (cyan) |
| Running / active | #FF34D399 (green) |
| Alarm / warning | #FFF59E0B (amber) |
| Accent / link / data | #FF38BDF8 or theme AccentBrush |
| Text on dark | #FFF3F4F6 or theme TextForeground |
| Meta text | #FF64748B or theme TextSubtleForeground |
This tells you what exists. Canvas and Dashboard skills cover how to use each category.
| Category | Members | Canvas? | Dashboard? |
|---|---|---|---|
| Shapes | Rectangle, Ellipse, Polygon, Polyline, Path, Gridline, Spline | ? | — |
| First-class auto-shapes | Cylinder, Gear, Arrow, Cloud, Star, Hexagon, Pentagon, Trapezoid | ? | — |
| Container | ShapeGroup, SvgGroup, Group | ? | — |
| Interaction | TextBlock, Label, Button, CheckBox, ComboBox, DataGrid, ListBox, NumericTextBox, PasswordBox, PushButton, RadioButton, Slider, TextBox, ToggleSwitch | ? | ? |
| Gauges | CenterValue, CircularGauge, Compass, DigitalGauge, LinearGauge, RadialGauge, RangeCircular, SemiCircle | ? | ? |
| Charts | BarChart, DigitalMeter, DrillingChart, PieChart, PieChartPlus, Timeline, TrendChart, XYChart | ? | ? |
| Viewer | AlarmViewer, AlarmAreas, AssetsTree, Carousel, ChildDisplay, Expander, FlowPanel, MapsOSM, PdfViewer, ProgressBar, TabControl, WebBlazor, WebBrowser | ? | ? |
| Editors | DatePicker, DateTimePicker, TimePicker, MediaElement, MenuItem, PageSelector | ? | ? |
| Dashboard | Cell | — | ? |
| IndustrialIcons | IndustrialIcon (character-code icon font) | ? | ? |
Call list_elements() for the authoritative, up-to-date catalog of every element type in the current release. When in doubt, always discover at runtime rather than relying on a static list.
...
The complete Wizard catalog is 5 symbols:
SymbolName | What it is | Typical SymbolLabels |
|---|---|---|
| Industrial blower/fan | State, RPM |
| Electric motor | State, RPM |
| Pump (styles selectable in Designer) | State, RPM |
| Storage tank | Level, Alarm |
| Valve (various styles) | State, Position |
Always treat list_elements('Wizard') as the authoritative runtime catalog — if an entry does not appear there, do not attempt to use it.
...
Call list_elements('Library/HMI') or specific subfolders (Library/HMI/Pumps, Library/HMI/Valves, etc.) to discover. Results are limited to 50 per call — when you see truncated: true, drill into specific subfolders to see complete listings. Library symbols auto-import on first reference:
...
Mistake | Fix |
|---|---|
| Declaring done Responding as finished on empty |
responding as finished. |
Taking screenshots after every write | Checkpoints, not reassurance. Canvas: up to 3 per display. Dashboard : one at end: generally unnecessary. |
Losing track of the element list across turns | Keep the display's element list in working memory as you add/modify/remove across the session. An element you forgot to drop will still render; |
Ignoring | Check |
Hardcoding hex without thinking about theme switching | Use |
Polygon / Gridline without Points | Always include |
Using | Field is always |
Using | Visual editor UI, not a writable table. Use |
Omitting | Required — |
Wrapping the envelope in | Properties go at top level, no wrapper |
Partial write on an existing display | Always read-modify-write the complete document |
Using |
|
Setting colors without clearing theme | Set value AND clear theme: |
Calling | The parameter is |
Relying on a static element/symbol list | Always call |
#// Session startup
get_table_schema('DisplaysList')
list_elements('ThemeColors')
list_elements('ThemeNames')// Before first use of a type this session
...
list_elements('<ElementName>') list_dynamics('<DynamicName>')
// Read-modify-write an existing display
...
get_objects('DisplaysList', names=['X'], detail='full') write_objects(table_type='DisplaysList', data=[...]) get_state(target='designer') # // verify errorList empty AND readOnly false
// Visual checkpoint (
...
see Section 3) // Canvas: up to 3 per display (after zones, after equipment, before done)
...
// Dashboard:
...
generally unnecessary get_screenshot(target='display', nameelement='X')
Display envelope template:
...