12. Extension Points
Blueprint uses a closed typed core for common semantics and named references for behavior that cannot be expressed portably.
Prefer typed vocabulary first
Use:
UIFieldTransform.upperCase(), not a custom “uppercase” callback;Facts.grant(action), not an evaluator for a basic grant check;RelationshipUI, not a custom picker for a normal reference;ActionPlacementUI, not app-local button logic.
This keeps validators and interpreters exhaustive.
Field transform reference
const name = NameField();
const siteId = ReferenceIdField('site_id');
FieldUI(
autofill: UIFieldAutofill(
sourceFields: [name, siteId],
transforms: [
UIFieldTransformRef(
'ops.area.code',
arguments: {'separator': '-'},
),
],
),
)The client binds ops.area.code to a pure transformation function. The ref is dot-qualified, stable, and testable. A missing required binding fails assembly/editor construction instead of silently degrading.
Evaluator reference
Rule(
id: 'ops.area.release.instrument_state',
kind: RuleKind.custom,
test: Facts.evaluator(
HostEvaluatorRef('ops.area.release.instrument_state'),
),
)final runtime = BlueprintRuntimeEngine(
blueprint: blueprint,
evaluators: {
'ops.area.release.instrument_state': instrumentStateEvaluator,
},
);The action record must pin both the symbolic ref and the implementation revision used for the decision.
Client and server extension catalog
vyuh_blueprint declares required imperative seams:
const descriptor = BlueprintDescriptor(
name: 'ops',
title: 'Operations',
version: '1.0.0',
modules: [opsModule],
extensionRefs: [
BlueprintExtensionRef(
ref: 'ops.area.release.instrument_state',
side: BlueprintExtensionSide.server,
kind: BlueprintExtensionKind.ruleEvaluator,
),
BlueprintExtensionRef(
ref: 'ops.area.layout',
side: BlueprintExtensionSide.client,
kind: BlueprintExtensionKind.form,
isRequired: false,
),
],
);Kinds include action handler, rule evaluator, policy resolver, effect handler, route, widget, detail tab, empty state, dashboard widget, report block, form, and custom.
BlueprintTypeCatalog closes the supported Dart declaration types; extension refs close imperative bindings. They answer different questions: “does this runtime understand the declaration type?” and “is the named implementation attached?” Execution readiness requires both checks to pass.
UI attach points
FieldUI(
presentationRef: 'ops.instrument.selector',
// How History and a revision compare the value.
diff: DiffHints(itemKey: 'id', ordered: true),
)
RelationshipUI(
pickerRef: 'ops.area.category-picker',
rendererRef: 'ops.area.category-link',
)
ActionUI(formRef: 'ops.area.retire-form')
EntityUI(
details: [
DetailUI(
name: 'environment',
title: 'Environment',
kind: DetailUIKind.custom,
customWidget: RendererRef('ops.area.environment-monitoring'),
),
],
)The declaration names what is required. vyuh_blueprint_ui client registries bind those refs to Flutter implementations. The domain package remains free of Flutter widgets.
The current client registry is kind-typed:
final bindings = ClientUIBindings(
detailTabs: {
'ops.area.environment-monitoring': (context) =>
EnvironmentMonitoringView(
area: context.row!,
onRefresh: context.refresh,
),
},
// A field's facets by its presentationRef: its editor, its read-only view,
// and, when the defaults will not do, its summary and diff.
fieldFacets: {
'ops.instrument.selector': BlueprintFieldFacets(
edit: (_, field) => InstrumentSelector(
value: field.render.fieldValue,
enabled: field.render.fieldEnabled,
onChanged: field.render.onFieldChanged,
),
view: (_, field) => InstrumentChip(field.value),
),
},
);A field's facets are the same PropertyFacets the property kits use, over a BlueprintFieldContext (the site's ClientRenderContext as render, and the field as a property). They can also be registered by path (fieldFacetsByPath: {'ops.instrument.serial': ...}, the entity type then the field) and by kind (fieldFacetsByKind: {DateKind: ...}). A facet the ref leaves out falls back to the path, the kind, and then Blueprint's own widgets. FieldUI.editorRef and viewRef with fieldEditors and fieldViews still bind, as deprecated aliases.
A field's facets also name and write its value: label (an enum value's label, else its words) and format (the value as text, in the site's format settings). A list, a card, a filter chip, History and a diff all write a field's value with them, so a registration such as fieldFacetsByPath: {'ops.instrument.calibrated_at': BlueprintFieldFacets(format: ...)} changes it everywhere. A report, written on the server, follows the same rules through reportLocaleOf(settings).
ClientRenderContext supplies the protocol client, assembled app, current actor, route, collection, selected row, refresh callback, field state, and the standard record-mutation boundary. A custom surface composes those public contracts instead of bypassing the protocol.
Declared and bound refs are resolved by kind. Declared-but-unbound refs render a deterministic placeholder; bound-but-undeclared refs appear as drift in the DevTools binding report.
Descriptor contributions are also extension
Use EntityDescriptor when an extension changes the effective domain shape: new facets, fields, relationships, actions, aggregates, or UI defaults.
Use a registry ref when the shape is already declared but needs imperative execution or rendering.
| Need | Correct mechanism |
|---|---|
| Add qualification state to user | EntityDescriptor |
| Render a normal enum | built-in FieldUI |
| Call an instrument-specific decision service | evaluator ref |
| Custom multi-part template designer | form/widget ref |
| Add an action from another aspect | contributed facet/action |
| Change tenant threshold | configuration/policy, not an extension |
Extension rules
- Refs are namespaced and stable.
- Required refs are checked at assembly/bootstrap.
- Implementations declare compatible contract versions.
- Server refs cannot execute in a client registry, and vice versa.
- Pure transformations remain pure; side effects use action/effect contracts.
- Runtime decisions capture ref and implementation revision.
- Extensions receive typed context, not unrestricted database access.
- Missing optional UI refs fall back only when the declaration defines a safe built-in fallback.
Checkpoint
Classify each requirement:
- a barcode field editor;
- a site-specific code prefix;
- an external instrument readiness check;
- a new maintenance facet on equipment;
- a custom timeline detail tab.
Choose typed vocabulary, configuration, descriptor, or registry ref for each.
Next: Operations capstone.
References: UI extension vocabulary · Blueprint UI.