8. UI, Queries, and Forms
Blueprint UI is an interpreter. It uses declared field, relationship, facet, entity, module, and app semantics; it does not reverse-engineer meaning from column names.
Generic enterprise UX belongs to vyuh_blueprint_ui. CDX packages own controls and view mechanics. vyuh_blueprint_ui maps Blueprint metadata and protocol records into those Studio contracts; it does not introduce a parallel widget system.
The UI hint cascade
Query limits already resolve property-by-property through entity → module → Blueprint → platform defaults:
final blueprint = Blueprint(
name: 'pharma',
version: '1.0.0',
ui: const BlueprintUI(
query: QueryUI(maxSortLevels: 3, maxGroupLevels: 3),
),
modules: [
Module(
// ...
ui: const ModuleUI(
routePrefix: '/ops',
query: QueryUI(maxGroupLevels: 2),
),
),
],
);Describe a field once
static const categoryId = UuidField(
'category_id',
title: 'Area Category',
indexed: true,
db: FieldDb(
queryPatterns: [QueryPattern.filter, QueryPattern.aggregate],
),
reference: UIReferenceHint(
target: EntityRef(module: ModuleRef('ops'), name: 'area_category'),
projection: 'picker',
),
ui: FieldUI(
input: UIFieldInput.createAndUpdate,
widget: UIFieldControl.referencePicker,
formSection: 'hierarchy',
filterable: true,
sortable: true,
order: 40,
optionSearch: UIOptionSearch.auto,
),
);The same declaration drives:
- a searchable reference picker in the editor;
- a linked label in details;
- a projected table column;
- a typed filter operand;
- reference identity on the wire.
Raw UUID entry is not an acceptable reference experience.
Input capability is not storage writability
static const createdBy = ActorField(
'created_by',
ui: FieldUI(input: UIFieldInput.never),
);
static const code = CodeField.configured(
ui: FieldUI(input: UIFieldInput.create),
);UIFieldInput distinguishes never, create-only, update-only, and both. Runtime-managed fields remain excluded even when physically writable.
Derive form values declaratively
static const name = NameField();
static const code = CodeField.configured(
ui: FieldUI(
autofill: UIFieldAutofill(
sourceFields: [name],
transforms: [
UIFieldTransform.trim(),
UIFieldTransform.upperCase(),
UIFieldTransform.replacePattern(
pattern: r'[^A-Z0-9]+',
replacement: '-',
),
UIFieldTransform.replacePattern(pattern: r'^-+|-+$'),
],
override: UIFieldAutofillOverride.untilOverridden,
),
),
);The code follows the name until a person edits it. The editor then preserves the manual value and may expose a relink action.
UIFieldTransformRef('ops.area.code') is the typed escape hatch for a host-registered transform. Keep validation-critical invariants on the server; client autofill is an authoring aid.
Derived and explicit form structures
The app grammar can derive sections from FieldUI.formSection and the owning facet:
const editor = EditorUI(
name: 'area-editor',
title: 'Area',
presentation: EditorPresentation.adaptive,
operations: {EditorOperation.create, EditorOperation.update},
form: FormUI.derived(),
);Or declare a stable custom structure:
const editor = EditorUI(
name: 'area-editor',
title: 'Area',
kind: EditorUIKind.tabs,
presentation: EditorPresentation.dock,
parts: [
EditorPartUI(
name: 'main',
title: 'Area',
form: FormUI.sections(
sections: [
FormSectionUI(
name: 'identity',
title: 'Identity',
fieldRefs: [
ProjectedFieldRef('identity.code'),
ProjectedFieldRef('identity.name'),
],
columns: 2,
),
FormSectionUI(
name: 'hierarchy',
title: 'Hierarchy',
facetRefs: ['hierarchy'],
columns: 2,
),
],
),
),
],
);Explicit sections cannot make a runtime-managed field editable. Input policy still comes from the field.
Query operands follow field semantics
The field registry maps a projected field to its type, labels, relationship, and query capabilities. Therefore:
- enum → compact labeled option picker;
- boolean → semantic Yes/No or Active/Inactive control;
- date/timestamp → date or date-time picker;
- reference → entity picker;
- arrays → multi-value operand;
- numeric/range → typed numeric/range editor;
- text → CDX text control;
- operators with no operand → no value editor.
The client builds a Vyuh/CDX query AST. The server must validate the same field catalog and compile only declared queryable fields. A visible filter that the server cannot execute is a contract error.
The same AST is executable outside the widget tree through EntityQuery<T>. It supports immutable filtering, search, sorting, grouping, fetch windows, one-shot reads, and authoritative subscriptions across HTTP, in-process, cached, test, or generated-client executors. Chapter 18 develops that contract without introducing another filter language.
Reference controls query the standard slim reference projection. That keeps picker reads to identity fields such as id, code, name, and description instead of loading detail-shaped records. Studio's EntityPicker owns the generic selection interaction; Blueprint UI supplies entity, projection, and query context.
Relationship tabs are explicit when EntityUI.relationshipMode: RelationshipFieldUIMode.explicit is used. In that mode, only declared EntityUI.relationships entries appear; storage edges do not become accidental product navigation.
Action placement references the action catalog
ui: EntityUI(
actionPlacements: [
ActionPlacementUI(
placement: ActionPlacementRef.recordHeader,
actions: [
ActionUIRef(
ActionRef(
facet: FacetRef(
entity: EntityRef(module: ModuleRef('ops'), name: 'area'),
name: 'governance',
),
name: 'activate',
),
presentation: UIActionPresentation.icon,
tooltip: 'Activate area',
),
],
),
],
)The facet owns the action. Placements only decide where and how an effective action appears. Permission, policy, state, and context resolution may make the final action hidden, disabled with a reason, or enabled.
Checkpoint
Build an area editor with:
- derived identity and hierarchy sections;
- enum labels;
- a category reference picker;
- create-only code that follows name until overridden;
- at most three sort and group levels;
- header placement for
activate.
Next: Actors, work, and policy.
References: UI Hints · Blueprint UI.