viewDefinition

The UI tree a spec can carry: views, their layout, and the components inside them — plus the evaluated form a renderer receives back. The per-type field lists live in the component catalog; for the engine and the React renderer see the view system reference.


viewDefinition

The viewDefinition is a declarative JSON artifact embedded in ModelSpec that describes component layout, field bindings, visibility rules, and event handlers. It is stored as a raw JsonNode in ModelSpec and parsed by the valem-view module when a view is evaluated.

The definition is renderer-agnostic. The built-in renderer (valem-view-react) is one implementation; any client that can parse EvaluatedView JSON from the REST or console endpoint can implement its own renderer.

Top-level ViewDefinition

"viewDefinition": {
  "renderer":    "builtin",
  "defaultView": "main",
  "views": [ ... ]
}
Field Required Default Description
renderer no "builtin" Reserved for future renderer selection; always "builtin"
defaultView yes id of the view shown when no viewId is specified
views yes Array of ViewSpec objects

ViewSpec

{
  "id":         "main",
  "label":      "Customer Survey",
  "layout":     "vertical",
  "columns":    null,
  "components": [ ... ],
  "onOpen":     null,
  "onClose":    null
}
Field Required Default Description
id yes Unique view identifier within this model
label no Display title for the view
layout no "vertical" Layout mode: "vertical", "horizontal", "grid", "tabs", "wizard"
columns no Column count for "grid" layout
components no [] Array of ComponentSpec objects
onOpen no EventHandler fired when the view becomes active
onClose no EventHandler fired when the view is navigated away from

ComponentSpec

A flat discriminated-union record. The type field identifies the component kind; all other fields are nullable and interpreted only for the component types that use them.

Required fields

Field Description
id Unique component identifier within the view
type Component type discriminator (see catalog below)

Common fields (all component types)

Field Type Description
label string Display label
visible bool | JSONata string | null Visibility; null = inherit from relevant meta
enabled bool | JSONata string | null Interactivity; null = !readOnly
readOnly bool | JSONata string | null Non-editable; null = inherit from readOnly meta
required bool | JSONata string | null Mandatory; null = inherit from required meta
bind $.path string Model field path the component reads from / writes to
placeholder string Input placeholder text
helperText string Helper text shown below the component
tooltip string Tooltip on hover
onChange EventHandler Fired when the bound value changes
onOpen EventHandler Fired when a sub-panel opens
onClose EventHandler Fired when a sub-panel closes

Field value kinds

Every component field is one of three kinds, and putting the wrong kind of value in a field is the most common way a generated view breaks. Know which kind a field is before you fill it in:

Kind Fields What to write
Plain text label, placeholder, helperText, tooltip, legend, alt, addLabel/removeLabel, fromLabel/toLabel, and every options[].label / menuItems[].label / tableColumns[].header / keyValueList items[].label The literal string, shown verbatim. Never wrap it in quotes; never write JSONata here — it is not evaluated. "label": "Weight (kg)", not "label": "\"Weight (kg)\"" (which shows the quote characters).
Path (bind) bind, bindFrom, bindTo, dependsOn, chartX, chartSeries[].field, tableColumns[].field, keyValueList items[].bind A $.path address the component reads its value from. This is the primary way to show a stored or derived value: "bind": "$.bmi".
Expression (JSONata) text, value, delta, caption, trend, and the boolean dynamics visible / enabled / readOnly / required A JSONata expression evaluated against the merged document — subject to the $ rule below for the value fields.

Dynamic expression fields and the $ rule

The boolean dynamics and the display-value expression fields are evaluated differently, and the difference is easy to trip over:

  • visible, enabled, readOnly, required accept a JSON boolean, a JSONata string (always evaluated, no $ required — "visible": "age < 20" works), or null/absent (falls back to meta-cache inheritance, see below).
  • text, value, delta, caption, trend accept a JSONata string or a literal, but the server-side ViewEvaluator only evaluates the string as JSONata when it contains a $. A string with no $ is shown verbatim:

    "text": "bmiCategory"           // no $  → shows the literal word "bmiCategory"
    "text": "$string(bmiCategory)"  // has $ → shows the value of the bmiCategory field
    "caption": "kg/m2"              // no $  → shows the literal text "kg/m2" (correct for fixed text)
    

    To reference a model field from one of these fields, either give the component a bind (simplest, and the value can then be formatted), or wrap the field in a $ function ($string(field), $round(field, 1)). To show fixed literal text, write it plainly with no surrounding quotes and no $ — it falls through to literal. Add \"...\" quotes only for a literal segment spliced into a real $-expression: "text": "$string(bmi) & \" kg/m2\"".

Server vs. built-in UI. The $ rule is a server-side (GET /models/{id}/view, MCP, console) behaviour. The bundled React renderer evaluates the raw spec client-side and will resolve a bare text: "bmiCategory" too, so a spec written without $ appears to work in the built-in UI while rendering literally everywhere else. Write bind or a $-bearing expression so the view renders identically through every renderer. See view-system.md.

Rule of thumb: to show one stored or derived value, prefer "bind": "$.path". Reserve the text/value expression fields for values you compute inline, and always include a $.

className is dead — no evaluator or renderer reads it. It is no longer modelled on the ComponentSpec hierarchy in Java or in the TypeScript mirror. A className in an existing spec is still stored and served verbatim, but nothing acts on it. Don’t rely on it.

Not every common field applies to every type — each component type binds to the ComponentSpec record carrying the fields it uses, so e.g. a separatorLine has no placeholder and a badge has no enabled. See the record table in view-system.md for the exact per-type field sets.

Meta cache inheritance (null defaults)

When a dynamic field is absent or null, the view evaluator looks up the meta cache:

Field Meta cache key Absent default
visible $.bind#relevantfalse means hidden; missing means visible true
readOnly $.bind#read_onlytrue means read-only; missing means editable false
required $.bind#required false
enabled !effectiveReadOnly

This lets metaDerivations drive component visibility and interactivity with zero extra view configuration. For example, if issueCategory has a relevant metaDerivation that evaluates to false (because issueEncountered = false), the component is automatically hidden — no visible expression needed in the view definition.


EventHandler

Defines what happens when a UI event fires. Used by onClick, onChange, onOpen, onClose on ComponentSpec, and onOpen, onClose on ViewSpec.

{
  "mutations": "{'$.status': 'submitted', '$.submittedAt': $now()}",
  "navigate":  "confirmation"
}
Field Description
mutations JSONata expression evaluated against the model state; result must be a {"$.path": value} map
navigate View id to activate after mutations are applied

Both fields are optional (either, neither, or both may be present).


EvaluatedView — REST / Console response

GET /models/{id}/view and GET /models/{id}/view/{viewId} return an EvaluatedView with all dynamic expressions already resolved. This is the renderer-agnostic contract; any client (React, Angular, mobile, CLI) can consume it.

EvaluatedComponent is a sealed interface, not a flat record — each component type serializes as one of 26 concrete records (EvaluatedBasicInput, EvaluatedTextArea, EvaluatedSelectField, EvaluatedDependentSelector, EvaluatedSlider, EvaluatedDateRange, EvaluatedFileUpload, EvaluatedLabel, EvaluatedStaticText, EvaluatedBadge, EvaluatedImage, EvaluatedLink, EvaluatedProgressBar, EvaluatedDataTable, EvaluatedDataChart, EvaluatedKeyValueList, EvaluatedStatTile, EvaluatedJsonViewer, EvaluatedTracePanel, EvaluatedValidationSummary, EvaluatedEffectStatus, EvaluatedContainer, EvaluatedSectionList, EvaluatedButton, EvaluatedMenu, EvaluatedSeparatorLine) carrying only the fields relevant to that type (@JsonInclude(NON_NULL)); the JSON below shows the union of possible fields, not a shape any single component actually emits in full.

Three of them carry a declaration rather than data: explainPanel, auditTimeline and validationSummary evaluate to which path to explain and how many rows to ask for, because the evaluator receives only the merged document, meta cache, expression cache and constants — it has no access to the trace ring buffer, the audit store or the flagged-constraint set. The renderer fetches those itself. See view-system.md.

{
  "modelId":    "customer-satisfaction-survey",
  "viewId":     "main",
  "title":      "Survey",
  "layout":     "vertical",
  "components": [
    {
      "id":       "ratingField",
      "type":     "radioField",
      "label":    "Overall Satisfaction",
      "visible":  true,
      "enabled":  true,
      "readOnly": false,
      "required": true,
      "bind":     "$.overallRating",
      "value":    5,
      "options": [
        { "value": "1", "label": "1 — Very dissatisfied" },
        { "value": "5", "label": "5 — Very satisfied" }
      ],
      "placeholder": null,
      "helperText":  null,
      "tooltip":     null,
      "text":        null,
      "components":  null,
      ...
    }
  ]
}

EvaluatedComponent fields

Field Type Description
id string Component identifier
type string Component type
label string Resolved display label
visible boolean Whether the component should be shown
enabled boolean Whether the component is interactive
readOnly boolean Whether the component is non-editable
required boolean Whether the field is mandatory
bind string Bound model path
value JSON value Current value from the merged model document
placeholder string Placeholder text
helperText string Helper text
tooltip string Tooltip
options [{value, label}] Resolved options list
text string Resolved text (for label, staticText, badge, alert, link)
components EvaluatedComponent[] Resolved sub-components (for aggregates)
collapsed boolean Initial fold state (for collapsible, accordion, jsonViewer, the trace panels)
tableColumns ColumnSpec[] Column definitions (for dataTable)
pageSize integer Rows per page (for dataTable)
chartType string Chart type (for dataChart, sparkline)
chartX string X-axis field (for dataChart)
chartSeries ChartSeriesSpec[] Series definitions (for dataChart, sparkline)
items EvaluatedKeyValueItem[] Resolved caption/value rows (for keyValueList) — each {label, bind, value, text, format, currency}
delta / caption / trend string Resolved supporting text (for statTile)
bindFrom / bindTo string The two bound paths (for dateRangeField)
valueFrom / valueTo JSON value The two resolved ends (for dateRangeField)
src / alt / fit string Image source, alternative text, object-fit (for image)
href / target string Anchor destination (for link)
limit / showConstraints integer / boolean How many rows to fetch (for explainPanel, auditTimeline) — a declaration; the rows are not resolved server-side
pathPrefix / maxItems / emptyText string / integer / string Scope and empty state (for validationSummary)
effectId / errorPath / error / showRetry / retryLabel   Effect state (for effectStatus); error is resolved, being ordinary model state
maxDepth integer Truncation depth (for jsonViewer)
menuItems MenuItemSpec[] Navigation items (for menu, stepper, breadcrumb)
orientation string Layout orientation (for menu, stepper)
variant string Visual variant (for button, badge, alert, statTile, validationSummary); unevaluated raw string for badge — see the badge known-gap note above
icon string Icon name
min / max number Range bounds (for sliderField, ratingField, numericStepper, progressBar, gauge)
step number Step increment (for sliderField, ratingField, numericStepper)
allowCustom boolean Accept a value outside options (for the choice types)
toolbar string Editing chrome (for richTextField)
size integer Gap in pixels (for spacer)
accept string MIME filter (for fileUploadField)
multiple boolean Allow multiple files (for fileUploadField)
minFiles / maxFiles integer File-count bounds (for fileUploadField)
minSize / maxSize number Per-file byte-size bounds (for fileUploadField)
allowedMediaTypes string Allowed media types (for fileUploadField)
showValue boolean Display numeric label alongside the bar (for progressBar, gauge)
format string "percent" or "value" (for progressBar); "currency" / "percent" / "number" / "integer" for the numeric inputs and statTile, filled in from the type when unset
currency string ISO-4217 code (for currencyField, statTile, and per keyValueList row)
onClick EventHandler Click handler (passed through unevaluated for client execution)
onChange EventHandler Change handler
onOpen EventHandler Open handler
onClose EventHandler Close handler

Note: EventHandler objects (onClick, onChange, etc.) in EvaluatedComponent are passed through unevaluated — they contain the raw mutations JSONata string and navigate id. The client renderer executes them locally when the event fires.