Skip to main content

Form conditions

A screen's edit dialog is a flat list of fields. Some fields only make sense when others have a specific value — a Custom SLA that only appears on the Enterprise plan, a Manager email that's only required above a 20% discount. The framework lets you express this directly in the Screens builder, on each field, through three slots:

SlotEffect
Visible whenThe field is rendered only when the expression is true. When false, the field is removed from the dialog and dropped from the save payload.
Required whenThe field gets the required asterisk and save is blocked until the value is provided.
Disabled whenThe field is rendered but greyed out and read-only. Its current value still goes on the save payload.

The three slots are independent — a field can be visible-and-required, visible-and-disabled, or all three.


At a glance

Subscription — edit dialog
Plan
Enterprise ▾
Seats *
250
Custom SLA (visible: plan = enterprise)
Gold — 99.95%
Discount % (disabled: seats < 100)
15
CancelSave

Custom SLA appears only on the Enterprise plan; Discount % is visible on every plan but locked when Seats drops below 100.


Editing a condition

In Settings → Screens, open the screen and click the field on the Fields tab. The field editor on the right exposes the three condition slots, each with an expression editor:

Field editor — Custom SLA
Name
custom_sla
Label
Custom SLA
Type
string ▾
Visible when
plan == 'enterprise'
Required when
— always required when visible —
Disabled when

Each expression slot:

  • Suggests field names of the same screen via auto-complete.
  • Validates the syntax live — a typo (plan = 'enterprise' instead of ==) shows a red underline before save.
  • Refuses cycles at save time (field A visible-when depends on B, B depends on A).

The Test button at the top of the field editor opens a small preview pane (see Testing conditions below).


Expression syntax

The expression language is small and safe — comparable to a SQL WHERE clause but evaluated client-side.

ConstructExample
Field referenceplan, seats, discount_pct. Refers to the current value of the field with that Name in the same dialog.
Literals'enterprise', 5, true, false, null.
Comparisons==, !=, <, <=, >, >=.
Logical&&, ||, !. Parentheses for grouping.
Membershipplan in ['team', 'enterprise'].
Null checkdiscount_pct == null / discount_pct != null.
String predicatesname.startsWith('FR-'), name.endsWith('.pdf'), name.includes('test').
Lengthtags.length > 0 (for Multiple fields).
Session contextsession.user, session.lang, session.roles — the same triple available to queries (see Parameter binding → Session context). session.roles is an array — 'admin' in session.roles.

The language has no function calls beyond the listed predicates and no arithmetic operators other than comparison. The intent is "boolean expression over form state", not a scripting language.


Evaluation order

Conditions are re-evaluated on every field change in the dialog. The framework runs the three slots for every field in parallel; the result is read after every reference resolves. What this means:

  • The order in which fields are listed doesn't matter. A Visible when on field A can reference field B that itself depends on field C — the resolver runs the full pass once per change.
  • Cycles are refused at save — the builder names the cycle and points at the offending fields.
  • Disabled fields keep their value, even if a Visible when later hides them. Toggling visibility off then on doesn't reset the value.
  • Hidden fields drop their value on save. The payload sent to the connector contains only the visible fields. This matters when a connector's INSERT/UPDATE expects NULL for an absent field rather than the previously-typed value.

Interaction with Default

ScenarioBehaviour
Field has Default, becomes visibleThe default is applied the first time the field becomes visible if it has no value yet. Re-hiding and re-showing keeps whatever the user typed in between.
Field has Default, becomes hiddenThe value is not cleared in memory; it is dropped from the save payload because the field is no longer rendered.
Field has no Default, becomes visibleThe field renders empty.
Field has Default ${session.user}, becomes visibleThe session value is substituted at evaluation time (same syntax as query defaults).

Server-side defaults (SEQUENCE, SYSDATE, LOGIN, PASSWORD — see Dictionary) are applied on save and don't interact with conditions.


Server-side enforcement

Conditions are evaluated on the client for live UX and re-evaluated on the server on save to defend against tampering. The save endpoint:

  1. Receives the field payload.
  2. Runs every condition again with the values from the payload.
  3. Refuses the save when a required-and-visible field is missing or when a disabled field is being changed (its value in the payload differs from the value last sent to the client).

So conditions are business rules, not just UX hints. A user opening the network tab and changing the payload directly hits the same validation as the form.


Testing conditions

The field editor's Test button opens a small preview pane: the dialog populated with fixture values from the connector's _test_row (or empty when none is configured); flipping the dependent fields shows the live condition behaviour.

For non-trivial logic, the screen builder also exposes a Test cases tab. Each test case is a { inputs, expected } pair the Run tests button replays:

Test caseInputsExpected
starter-planplan = starter, seats = 5custom_sla = hidden, discount_pct = disabled, manager_email = hidden
enterprise-with-discountplan = enterprise, seats = 250, discount_pct = 25custom_sla = visible, discount_pct = enabled, manager_email = visible+required

The test runner shows a green checkmark per pass, a red diff per fail. Test cases aren't used at runtime — they document the form's logic and survive refactors better than visual checks.


Permissions

session.roles is the row-level switch. A field that only superusers should edit becomes:

Disabled when: 'admin' not in session.roles

The server-side enforcement makes this safe — a regular user editing the network payload still hits the same condition.


Under the hood

Conditions are stored as expression strings on each field of the screen entry. Operators do not edit the underlying TOML by hand; the field editor is the canonical interface and validates every expression before save.


What's next