Dialog builder
The Dialog tab of the Screen Designer opens the Visual Builder — a WYSIWYG editor for the add/edit form. The data model is the same ScreenDialog shape stored in screens.toml, but instead of editing TOML you drag fields onto tabs and tune properties in a side inspector.
A screen without a dialog still works as a read-only / grid-edit table. Build a dialog when the user needs a full form to add or edit rows.
Three-column layout
| Region | What it does |
|---|---|
| Palette (left) | Source of new fields. Two sub-tabs: Dictionary (every entry available on the current connector scope) and Columns (every column the read query returns). A search box narrows the list. Click an entry to add a field card on the canvas; drag an entry to drop it at a specific position. |
| Canvas (center) | The dialog's tabs strip across the top + the field grid below. Click a tab to switch; + Tab adds one. Each field card shows the field name + a one-line summary of its key flags. The selected card has a coloured border; the inspector on the right reflects it. A separate HIDDEN group at the bottom collects fields with hidden = true. |
| Inspector (right) | The properties of the selected field (or a per-tab action editor when a tab action is selected — mutually exclusive). Properties are rendered as a generic form; the Lookup param binds and Conditional rules sections expand inline. |
Building from scratch
Step 1 — Open the Dialog tab
The first time, the canvas is empty with a Create dialog button. Click it to seed dialog.tabs = [{id: 'general', label: 'General', fields: []}] and reveal the three-column layout.
Step 2 — Add the first tab's fields
Two ways:
| Method | When |
|---|---|
| Click a Dictionary entry / column in the Palette | Adds a field card at the end of the current tab's grid. |
| Drag a Dictionary entry / column onto the Canvas | Drops the field card where you released it — useful to insert mid-grid. |
The added field becomes the selected card; the Inspector switches to its properties. Set colspan to widen it (the grid is by default 2 columns; a colspan = 2 field fills the row).
Step 3 — Tune the field properties
In the Inspector:
| Field | What it does |
|---|---|
dd | Dictionary entry the field inherits from. Defaults to the column's name on add. |
colspan | How many grid cells the field spans. Defaults to 1. |
required / hidden / disabled | Per-dialog overrides — leave blank to inherit from the column hint (see Columns). |
| Lookup param binds | Extra :placeholder bindings for a LOOKUP field. Each binding is {param: <placeholder on the lookup query>, source: <field on this dialog>}. The classic case: a role picker narrowed by the application id from another field. |
| Conditional rules | visible_when / required_when / disabled_when — see Conditional fields. |
Step 4 — Add tabs
Click + Tab above the canvas. The new tab is empty; drag fields in from the Palette. Common tab patterns:
| Tab name | What's on it |
|---|---|
| General | Identity + most-used fields. |
| Audit | Created at, created by, updated at, updated by — often hidden_on_add to hide them when inserting. |
| Notes | Free-text fields. |
| Address | A group of related fields (street, city, postcode, country). |
Two flags on each tab control visibility per mode:
| Flag | Effect |
|---|---|
hide_on_add | Tab disappears when adding a new row. |
hide_on_edit | Tab disappears when editing an existing row. |
Step 5 — Reorder fields
Drag a field card within the same tab to reorder. Drag across to a different tab to move it between tabs. The drag is HTML5 native — the drop target highlights while you hover.
Step 6 — Hide a field
Two ways:
| Method | Effect |
|---|---|
Set hidden = true in the Inspector. | The card moves to the HIDDEN group at the bottom of the canvas. The field still binds at save time. |
| Delete the field from the tab. | The field is removed from the dialog entirely. The column is still on the screen; it just doesn't surface in the form. |
The "hide but keep" pattern is useful for technical columns the dialog must submit (e.g. an audit timestamp) but the user shouldn't see.
The three tab kinds
Tabs aren't all the same. Three types live under dialog.tabs, discriminated on the type field:
| Type | Purpose |
|---|---|
form (default) | A grid of editable fields — the case described above. |
nested_form | A child-record form embedded in the tab. The parent's PK binds into a separate read query; if a row comes back the nested form edits it, otherwise it inserts. See Nested tabs. |
nested_table | A related-rows TableView embedded in the tab. Renders another screen's grid scoped to the parent's PK. See Nested tabs. |
The visual builder shows the same tab strip for all three — switching to a nested_form tab swaps the canvas for that tab's nested-form editor; nested_table shows the target-screen picker.
Per-tab actions
Each tab can carry its own action list — buttons that fire from inside the tab footer. Common patterns:
| Pattern | Why per-tab |
|---|---|
| A Recompute button on a calculations tab. | Triggers a stored procedure only when the user is looking at those fields. |
| A Test webhook button on a notification tab. | Only meaningful in that context. |
| A Refresh from source button on a sync tab. | Re-pulls data the tab displays. |
The action editor opens in the Inspector when you click an action's row — the Inspector shows one thing at a time (a field OR an action). Selecting a field clears the action selection and vice versa.
See Actions and lifecycle for the full action types.
Dialog title
The dialog's title sits at the top of the modal when it opens. Set it once in the Inspector's Dialog section (below the field properties when no field is selected). Blank → falls back to the screen's label, then to id.
Common pitfalls
| Mistake | Symptom | Fix |
|---|---|---|
| Field on a tab references a column that doesn't exist in the read query. | The dialog shows the field but the value is always blank, and saving sends no value for it. | Either add the column to the read query or remove the field from the tab. |
lookup_param_binds references a field that's not on the same tab. | The bind resolves to NULL at submit. | Either move the source field onto the same tab or use a cross-tab binding (the engine reads the whole form state, but the inspector's Param dropdown only lists fields visible on the current tab). |
colspan = 3 on a 2-column grid. | The field overflows; layout breaks. | Cap colspan at the tab's cols. |
Two fields with the same name on the same tab. | The save sends two values for the same column; the second wins. | Each field's name must be unique within the dialog. |
hide_on_add = true AND hide_on_edit = true. | The tab never appears. | Either flip one off or drop the tab. |
What's next
- Conditional fields — make a field appear / be required / be locked only under specific form conditions.
- Actions and lifecycle — wire on_load / on_save / on_cancel to extend the dialog's behaviour.
- Nested tabs — embed a child-record form or a related-rows grid.