@trybotster/ui-contract
v0.3.3
Published
Renderer-neutral Botster plugin UI contract
Readme
@trybotster/ui-contract
Renderer-neutral plugin UI contract generated from the authoritative
botster-ui-contract Rust crate in this repository.
The package ships serde-accurate TypeScript declarations, a JSON Schema, and
shared conformance fixtures. Alongside UiNode and action contracts it owns
the renderer-neutral package surface, supported-operation, navigation
discoverability, and package-owned client notice reaction vocabulary. Hub
manifests own admission and registry policy; clients must not copy these
descriptors into daemon-specific mirrors. It is a normal build/protocol
dependency for Botster clients, not an installable marketplace package.
A notice reaction declaration may omit owner. The projected descriptor always
carries the admitted package name. Version one uses subject_scope: "session"
only, severity info | warning | error, ttl_ms in 1000..=60000, and one
top-level RFC 6901 text pointer. resolveNoticeText measures the decoded JSON
string as UTF-8 bytes with a 512-byte ceiling, does not trim or truncate, and
returns a typed error for missing, non-string, empty, or oversized values. The
byte bound is not a JSON Schema keyword.
The host scopes presentation keys to the active Hub, package, and surface.
Clients own the scoped presentation store and renderer policy. Plugin workers
receive one canonical UiActionRequest: form drafts are in values, while
non-form action metadata remains in payload. Accepted results may apply
presentation operations and one validated inline replacement tree. Rejected,
deferred, and error results retain the current tree and presentation state.
Every result must echo the request's request_id, surface_id, action_id,
and node_id exactly. This includes preserving an absent node_id; the Hub
rejects mismatched result identity as invalid_action_result.
UiNode.id is authored identity. The direct UiBindList.item_template root
may retain the 0.2.0 item-relative { "$bind": "@/field" } form. Identity-
bearing descendants below that bound root may use
{ "$kind": "bind_list_descendant_id", "key": "remove" }. Keys are
nonblank, preserve their exact UTF-8 bytes, and are unique across the complete
authored item template. The canonical helper realizes them as
botster-ui-descendant-v1:<row-bytes>:<row><key-bytes>:<key>; consumers must
call the exported helper rather than synthesize IDs locally.
ui.select may produce options either as static select_option children or via
props.options_source with $kind: "entity_options". The entity-options
producer projects string values only from admitted entity families, excludes
values from an optional second family, and sorts with exact UTF-8 byte order.
Shared helpers projectEntityOptions and collectEntityOptionFamilies (Rust
and npm) plus the entity_options_reactive_timeline conformance fixture are
the dual-runtime authority for that projection. Clients walk the rendered body,
issue explicit SubscribeEntities for collected families, and project over the
entity store; Hub admits and serves those families but does not auto-subscribe.
Clients filter rows, resolve the direct root, then realize descendants before
any identity enters renderer, focus, hit, or action state. The new keyed form
is invalid on the item root, outside a bound item template, under
empty_template, or below a literal/absent root. Descendant full-ID $bind
remains invalid. Action request/result node_id remains a literal UiNodeId.
Required field binding is explicit rather than inferred from arbitrary JSON.
Authored validation accepts a structurally valid UiBind for
Button.label, IconButton.label, MenuItem.label, Form.submit_label,
Iframe.src, Iframe.title, and Text.text. The first six fields otherwise
require a nonblank string. Text.text retains its existing presence-only
literal contract, including empty strings, numbers, and null; only its authored
binding sentinel receives structural validation. Other required fields remain
non-bindable. Version 0.3.2 intentionally closes an earlier permissive gap:
required fields outside this seven-field allowlist now reject a binding
sentinel that an earlier authored validator could admit accidentally.
UiNode.validate() and validate_ui_node() are compatible authored-tree
entry points. Rust consumers must call UiNode.validate_realized() or
validate_ui_node_realized() after materialization. Realized validation
rejects unresolved property, payload, list, conditional, and identity bindings
while applying each field's existing literal rules. Hub performs authored
admission and transport only; renderer clients own materialization and the
realized validation boundary.
Rust renderers that also validate capability fallbacks can call
validate_ui_node_realized_with_capabilities() or
UiCapabilitySet.validate_realized_node() for the combined realized-tree and
capability traversal. The npm package publishes DTO declarations, schema, and
fixtures but does not export a JavaScript runtime validator; non-Rust renderers
must enforce the equivalent post-materialization boundary in their own runtime.
The generated JSON Schema can describe the literal-or-binding wire union, but cannot express the complete BindList row context or template-global key uniqueness. Schema validity is therefore necessary but not sufficient; the Rust/Hub validator is authoritative.
Regenerate or check committed assets:
npm run generate
npm run check
npm testAfter the merged artifact is ready, publish manually:
cd packages/ui-contract
npm publish --access public