Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Purpose

This skill is the common ground for every FrameworX display build, regardless of paradigm. Load it whenever you do display work. Pair with Skill Display Construction — Canvas for pixel-positioned HMI/P&ID displays or with Skill Display Construction — Dashboard for grid-based data displays.

...

What this skill does NOT cover: the equipment cookbook, controls reference, or grid-cell layout — those live in Canvas and Dashboard.

Prerequisites

  • Solution open with at least a minimal UNS (tags created). Displays reference tags; without tags there is nothing meaningful to bind.
  • You know which theme family you are targeting. If you don't, pick Steel (light office) or Onyx (dark control room).

Tools you use over and over

ToolWhen
get_table_schema('DisplaysList')Once at start of session to confirm field names
list_elements('ThemeColors')Once to get the brush catalogue
list_elements('<ElementName>')Before using an element type you have not used this session
list_dynamics() / list_dynamics('<Type>')Before attaching a dynamic you have not used this session
get_objects('DisplaysList', names=['X'], detail='full')Before every modification to an existing display (document-object rule)
write_objects(table_type='DisplaysList', data=[...])After your plan is complete
get_state(target='designer')After every write, to check errorList

Section 1 — Theme-first mental model

The one-sentence rule

Do not hardcode hex colors unless the hex carries process meaning. Use FillTheme, StrokeTheme, ForegroundTheme, BackgroundTheme, BorderBrushTheme properties with semantic brush names from list_elements('ThemeColors').

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.

The 13 theme pairs

Light / Dark pairUse for
Light / DarkDefault office / default control room
Platinum / OnyxRefined corporate office / premium control room
Steel / GraphiteIndustrial office / industrial control room
Pearl / IndigoSoft UI emphasis, OEM branding
Sky / NavyRefreshing / deep contrast
Gold / CoffeeWarm accents for specialty applications
ContrastLight / ContrastDarkAccessibility, outdoor tablets, low-vision

Theme is switched at runtime via @Client.Theme = "Dark". Every properly-themed display reflows automatically.

Pick a theme at the start of the build

Before placing any element, decide which theme family this solution is for:

...

Then use *Theme properties throughout — the displays render correctly under any theme, but look best under the one you designed for.

The brush catalogue

Call list_elements('ThemeColors') for the authoritative list. The 12 brushes you'll use 90% of the time:

BrushMeaningTypical use
PanelBackgroundCard / section backgroundBackground Rectangle inside a zone
PageBackgroundFull-page backgroundThe display-root Background override
ControlBackgroundControl bodyGauge / chart / data-grid background
TextForegroundPrimary textHeadings, values, labels
TextSubtleForegroundMeta textUnits, captions, "UPDATED AT" stamps
TextAccentForegroundHighlighted textSection titles, accent links
AccentBrushThe solution's accent colorActive-state markers, selected-row borders, links
StateGreenRunning / OK stateIndicator fills, live-data values
StateRedStopped / fault stateIndicator fills, alarm text
StateAlarmActive alarm (yellow)Banded gauge danger zones, pulsing elements
OnFill / OffFillHPG two-state fillsStatus-indicator shape fills tied to a boolean
WaterProcess waterPipe 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').

Note on Element*-family brushes: ElementRed, ElementOrange, ElementYellow may not apply consistently on all shape types. If you don't see your intended color, prefer StateRed / ColorAmber / StateAlarm.

How theme properties work on the wire

When you write {"FillTheme": "ElementBlue"} the writer stores it as {"Fill": "theme:ElementBlue"}. Both input forms are equivalent:

...

Use FillTheme / StrokeTheme / ForegroundTheme / BackgroundTheme / BorderBrushTheme for readability — it documents your intent. The writer collapses to the prefixed form. If you mean a hardcoded color, use Fill/Stroke/Foreground etc. directly.

The display-root Background footgun

Every newly created display gets "Background": "#FFFAFAFA" (light gray) baked in. If you're building for a dark theme, this shows through at every gap or transparent edge. Always set the root Background explicitly:

...

Or, for a full-bleed dark background on a dark-themed display, place a full-size Rectangle with FillTheme: "PageBackground" as the first element.

Section 2 — Write mechanics

The canonical display envelope

{
  "Name": "MyDisplay",
  "PanelType": "Canvas",
  "DisplayMode": "Page",
  "Navigate": "true",
  "Size": "1600 x 900",
  "OnResize": "StretchFill",
  "Width": 1600,
  "Height": 900,
  "Background": "theme:PageBackground",
  "Elements": [ /* ... */ ]
}

Non-negotiable rules

  • The identifier field is Name. Never ObjectName.
  • PanelType is required. "Canvas" or "Dashboard". Omitting it silently defaults to Canvas.
  • No JsonFormat wrapper. All properties at the top level.
  • DisplayMode controls window style: "Page" (normal), "Dialog" (modal popup), "Popup" (floating non-modal), "PopupWindow" (separate window — not available on Web, avoid unless explicitly requested).
  • OnResize: "StretchFill" is the sensible default. Use "NoAction" for fixed-pixel displays.
  • DisplaysDraw is NOT a writable table. It is the Designer visual editor UI. Use DisplaysList for all display create/edit operations.

Displays are document objects — read before write

Displays, Symbols, Scripts, Queries, and Reports are document objects: write_objects replaces the entire document on save. Omitted content is deleted.

...

MainPage is predefined in new solutions. Write main content directly into it — no need to create a new display for the landing screen.

Category and MCP-label guard

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 edits.

solution_id on every write

Every write_objects, delete_objects, rename_objects, and designer_action call should include the solution_id from the most recent open_solution or create_solution response. This verifies the session is still connected to the expected solution.

Section 3 — The build loop

This cadence catches errors within the same turn they were introduced, before they accumulate.

  1. PLAN — what am I adding/changing? What tags does it bind to?
  2. READget_objects(detail='full') if modifying an existing display
  3. WRITEwrite_objects(data=[...])
  4. CHECKget_state(target='designer'), look at errorList
  5. FIX — if errorList non-empty, fix and go back to step 3
  6. DONE — move to next display or next task

Reading errorList from get_state

get_state on a display returns compile errors as a list:

...

If errorList is absent or empty, the display compiled clean.

Never use screenshots to self-validate

write_objects success IS the confirmation. The errorList check covers compile correctness. Screenshots are only for sharing visual context with the user — not for the AI to confirm its own work.

Section 4 — Binding syntax

@Tag. is the only binding prefix you'll use in displays

  • @Tag.<path> — the tag's Value. Do NOT append .Value; it's implicit (@Tag.X == @Tag.X.Value).
  • @Tag.<path>/Attr.<member> — a UDT member on the tag.
  • @Tag.<path>.<udt_member> — alternate dot syntax for UDT members.
  • @Dataset.Query.<QueryName> — dataset query result (for DataGrid, ComboBox DataTable source).
  • @Client.Context.<property> — session-scoped client context (AssetName, AssetPath, Theme).
  • @Client.<property> — general client-scoped tags (@Client.Theme, @Client.AlarmPage.Filter).
  • @Now — current time.

Never use @Label. in display-element bindings. @Label. is only for symbol-definition internals.

Tag paths must be FULL paths

Include asset folders in every tag reference: Area1/Line1/Motor1.Speed, not Motor1.Speed.

Inline interpolation — the composite LinkedValue pattern

The single most important text pattern. Instead of two TextBlocks (value + unit) side by side, use ONE with composite LinkedValue:

...

  • "Flow: {@Tag.X} GPM" — label + value + unit
  • "{@Tag.X} NTU" — value + unit
  • "pH: {@Tag.X}" — label + value
  • "Operator: {@Tag.Shift/CurrentOperator}" — label + bound string

Polygon / Polyline / Gridline / Spline silently skip on missing Points

If you write a Polygon without Points, nothing renders and no error is produced — the element is simply invisible.

...

Polygon auto-closes (last point connects back to first). Polyline doesn't. For pipe runs use Gridline (constrains to horizontal/vertical segments — the P&ID convention).

Writer normalizations — what you write ≠ what you read back

The writer normalizes several shapes on save. When you later read the display back, you get the normalized form:

You wroteStored as
{"FillTheme": "ElementBlue"}{"Fill": "theme:ElementBlue"}
{"Type": "Cylinder", Left, Top, Width, Height}{"Type": "Path", "Data": "M 0,20 C ..."}
{"Type": "Hexagon", ...}{"Type": "Polygon", "Points": "50,0 100,25 ..."}
{"Type": "SvgGroup", "SvgContent": "<svg>..."}{"Type": "ShapeGroup", "Children": [...]}
{"Pens": [{TrendPen}]}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).

Section 5 — Spacing and typography tokens

Spacing scale

TokenPixelsWhere
xs4Gap between label and value in a stacked pair
sm8Gap between sibling sub-elements within a card
md16Gap between sibling cards in a row
lg24Card interior padding (content vs card edge)
xl32Zone padding (zone background Rectangle vs content)
2xl48Major section separator
3xl64Display-level margins

Typography ramp

Use FontFamily: "Inter" (or the solution's chosen font) universally.

RoleFontSizeWhen
Hero26–32Display title, single-metric hero number
H120–22Main section headings
H216Sub-section headings, card titles
Body13–14Bound values, primary text
Meta10–11Labels, units, captions
Micro9Timestamps, very-low-priority meta

Minimum sizes (hard floors)

ElementMinRecommended
Button100×32130×40
TextBox / NumericTextBox120×28160×32
ComboBox160×28200×32
Slider200×28260×32
ToggleSwitch60×2880×32
CircularGauge / RadialGauge150×150180×180
SemiCircle200×120240×140
LinearGauge (horizontal)260×80300×100
CenterValue120×120140×140
TrendChart400×200500×300
BarChart300×200400×240
PieChart200×200240×240
AlarmViewer400×220600×240
AssetsTree200×300240×400
DataGrid400×200600×300
Wizard symbol (TANK/PUMP/etc.)60×6080×80

Color hex fallbacks (for process meaning only)

RoleHex
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

Section 6 — Element types overview

This tells you what exists. Canvas and Dashboard skills cover how to use each category.

CategoryMembersCanvas?Dashboard?
ShapesRectangle, Ellipse, Polygon, Polyline, Path, Gridline, Spline?
First-class auto-shapesCylinder, Gear, Arrow, Cloud, Star, Hexagon, Pentagon, Trapezoid?
ContainerShapeGroup, SvgGroup, Group?
InteractionTextBlock, Label, Button, CheckBox, ComboBox, DataGrid, ListBox, NumericTextBox, PasswordBox, PushButton, RadioButton, Slider, TextBox, ToggleSwitch??
GaugesCenterValue, CircularGauge, Compass, DigitalGauge, LinearGauge, RadialGauge, RangeCircular, SemiCircle??
ChartsBarChart, DigitalMeter, DrillingChart, PieChart, PieChartPlus, Timeline, TrendChart, XYChart??
ViewerAlarmViewer, AlarmAreas, AssetsTree, Carousel, ChildDisplay, Expander, FlowPanel, MapsOSM, PdfViewer, ProgressBar, TabControl, WebBlazor, WebBrowser??
EditorsDatePicker, DateTimePicker, TimePicker, MediaElement, MenuItem, PageSelector??
DashboardCell?
IndustrialIconsIndustrialIcon (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.

Section 7 — Symbols (all three sources)

Every symbol uses Type: "Symbol". Not Type: "Pump", not Type: "Valve". One element type, many SymbolName values.

{
  "Type": "Symbol",
  "SymbolName": "Wizard/PUMP",
  "Left": 400, "Top": 300,
  "Width": 80, "Height": 80,
  "SymbolLabels": [
    { "Type": "SymbolLabel", "Key": "State", "LabelName": "State", "LabelValue": "@Tag.Pump1/Running", "FieldType": "Expression" },
    { "Type": "SymbolLabel", "Key": "RPM",   "LabelName": "RPM",   "LabelValue": "@Tag.Pump1/Speed",   "FieldType": "Expression" }
  ]
}

The 5 Wizard symbols

The complete Wizard catalog is 5 symbols:

SymbolNameWhat it isTypical SymbolLabels
Wizard/BLOWERIndustrial blower/fanState, RPM
Wizard/MOTORElectric motorState, RPM
Wizard/PUMPPump (styles selectable in Designer)State, RPM
Wizard/TANKStorage tankLevel, Alarm
Wizard/VALVEValve (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.

Library symbols (~1600)

Call list_elements('Library/HMI') or specific subfolders (Library/HMI/Pumps, Library/HMI/Valves, etc.) to discover. Library symbols auto-import on first reference:

{ "Type": "Symbol", "SymbolName": "Library/HMI/Pumps/CentrifugalPump", "Left": 200, "Top": 200, "Width": 120, "Height": 80 }

Solution symbols (your own)

User-created symbols in the current solution use Solution/ prefix:

{ "Type": "Symbol", "SymbolName": "Solution/MyCustomTank", "Left": 100, "Top": 100, "Width": 80, "Height": 100 }

Sizing

All symbols scale to any proportional size. Default is 65×65 — use 40×40 for compact, 80×80 for medium, 120×120 for large. Maintain aspect ratio; don't stretch asymmetrically.

Binding via SymbolLabels — never via direct properties

SymbolLabels is the ONLY way to push data into a symbol:

  • Key must match a label key defined inside the symbol (case-sensitive).
  • LabelValue uses @Tag.X / @Client.Context.X / @Now / composite strings.
  • FieldType: "Expression" is the default and handles tag paths and expressions.
  • Never use @Label.X in display-element SymbolLabels. @Label. is a symbol-definition internal only.

Section 8 — Layouts and asset navigation (brief)

Layouts

Layout regions: Header, Footer, Menu, Submenu, Content. The Startup layout defines which display loads into each region.

get_table_schema('DisplaysLayouts')
get_objects('DisplaysLayouts', names=['Startup'], detail='full')

Asset navigation

For plant-wide navigation with dynamic content:

...

A single display template can show data for whichever asset the operator selects. See the Dashboard skill for the AssetsTree + ChildDisplay master-detail pattern.

Section 9 — CodeBehind (brief pointer)

CodeBehind is client-side C# or VB.NET code embedded in each display. Lifecycle methods: DisplayOpening(), DisplayIsOpen(), DisplayClosing(), DialogOnOK().

The Contents field format starts with CSharp\r\n or VBdotNet\r\n before the code. Full CodeBehind guidance lives in the Skill Scripts Expressions skill.

Section 10 — Common pitfalls

MistakeFix
Hardcoding hex without thinking about theme switchingUse *Theme properties; reserve hex for process-meaning
Polygon / Gridline without PointsAlways include Points — min 3 for Polygon, 2 for Polyline/Gridline
Using ObjectName instead of NameField is always Name
Using DisplaysDraw as table_typeVisual editor UI, not a writable table. Use DisplaysList
Omitting PanelTypeRequired — "Canvas" or "Dashboard"
Wrapping the envelope in JsonFormatProperties go at top level, no wrapper
Partial write on an existing displayAlways read-modify-write the complete document
Using @Label.X in a display-element binding@Label. is for symbol internals only — use @Tag.
Setting colors without clearing themeSet value AND clear theme: {Fill: '#FF3498DB', FillTheme: ''}
Relying on a static element/symbol listAlways call list_elements() / list_dynamics() to get the authoritative catalog at runtime

Section 11 — Quick reference

# Session startup
get_table_schema('DisplaysList')
list_elements('ThemeColors')

# 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

...

{
  "Name": "MyDisplay",
  "PanelType": "Canvas",
  "DisplayMode": "Page",
  "Navigate": "true",
  "Size": "1600 x 900",
  "OnResize": "StretchFill",
  "Width": 1600,
  "Height": 900,
  "Background": "theme:PageBackground",
  "Elements": []
}

Next skills

After the basics are internalized, load the paradigm-specific skill:

...