Skip to content

12. Extension Points

Blueprint uses a closed typed core for common semantics and named references for behavior that cannot be expressed portably.

text
typed declaration
  -> stable symbolic ref
  -> assembly verifies required binding
  -> client/server registry resolves implementation
  -> runtime records ref + implementation version

Prefer typed vocabulary first

Use:

  • UIUpperCaseTransform, not a custom “uppercase” callback;
  • ActorHasGrantCondition, 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

dart
const name = NameField();
const siteId = ReferenceIdField('site_id');

FieldUI(
  derivation: UIFieldDerivation(
    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

dart
Rule(
  id: 'ops.area.release.instrument_state',
  kind: RuleKind.custom,
  condition: EvaluatorCondition(
    ref: 'ops.area.release.instrument_state',
    metadata: {'contract_version': 1},
  ),
)
dart
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:

dart
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.

UI attach points

dart
FieldUI(editorRef: 'ops.instrument.selector')

RelationshipUI(
  pickerRef: 'ops.area.category-picker',
  rendererRef: 'ops.area.category-link',
)

ActionUI(formRef: 'ops.area.retire-form')

EntityUI(
  detailTabRefs: ['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:

dart
final bindings = ClientUIBindings(
  detailTabs: {
    'ops.area.environment-monitoring': (context) =>
        EnvironmentMonitoringView(
          area: context.row!,
          onRefresh: context.refresh,
        ),
  },
  fieldEditors: {
    'ops.instrument.selector': (context) => InstrumentSelector(
      value: context.fieldValue,
      enabled: context.fieldEnabled,
      onChanged: context.onFieldChanged,
    ),
  },
);

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.

NeedCorrect mechanism
Add qualification state to userEntityDescriptor
Render a normal enumbuilt-in FieldUI
Call an instrument-specific decision serviceevaluator ref
Custom multi-part template designerform/widget ref
Add an action from another aspectcontributed facet/action
Change tenant thresholdconfiguration/policy, not an extension

Extension rules

  1. Refs are namespaced and stable.
  2. Required refs are checked at assembly/bootstrap.
  3. Implementations declare compatible contract versions.
  4. Server refs cannot execute in a client registry, and vice versa.
  5. Pure transformations remain pure; side effects use action/effect contracts.
  6. Runtime decisions capture ref and implementation revision.
  7. Extensions receive typed context, not unrestricted database access.
  8. 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.

Blue is the Vyuh Blueprint documentation surface.