Version compatibility
← Streaming · Documentation · Next: Extensions →
Old installed clients must not crash when BDUI evolves. Layout compatibility is primarily the server's responsibility: clients advertise their vocabulary, and the server returns a compatible tree.
Spec version
The client publishes an integer:
object BduiSpec {
const val CURRENT: Int = 11
}
Spec 2 adds the patch action and $ node roots in patch paths ($id.offer-42/state/liked). Spec 3 replaces alert with dialog (presentation: alert, bottomSheet, tooltip; widget content). Spec 4 moves modifier and onClick out of params onto the widget envelope; any widget may declare onClick. Spec 5 adds LazyRow, weight / background / clip / padding modifiers, Card containerColor, Text fontWeight, and container spacing. Spec 6 adds Scaffold, TopAppBar, FloatingActionButton, openScreen, and popScreen. Spec 7 adds the Chart widget (line, area, bar, pie, donut). Spec 8 adds the Calendar and Review widgets and the validate action; TextField gains error, multiline, minLines and keyboard, and RadioGroup gains onChange. Spec 9 adds the Select dropdown widget. Spec 10 adds ref nodes and over-the-air widget definitions (OTA widgets); servers send refs only to clients reporting spec 10 or higher. Spec 11 adds the Stepper container.
Increment it when adding a new kind of:
- built-in widget or action;
- state/context path prefix;
- HTTP apply mode;
- observer trigger;
- modifier with new semantics;
- incompatible meaning for an existing field.
Do not increment it for an additive optional field, a new optional enum value with an old-client-safe default, or an unknown JSON key that old clients can ignore.
Client capability request
Every BDUI document/stream request sends:
X-Bdui-Spec: 11
BDUI-originated HTTP actions and SSE reconnects include it too. If the backend needs app extensions, the host may additionally advertise registered namespaced widget/action names in a compact capability header or request field.
The backend should use the header to select a compatible widget tree and actions before serialization.
Node behavior
Widget nodes may include:
{
"type": "Carousel",
"minSpec": 5,
"children": []
}
A widget is understood when:
minSpecis absent or not aboveBduiSpec.CURRENT;typeis built in or registered on thisBduiConfig.
Otherwise the node and its subtree are skipped. Siblings continue; log only—no placeholder, crash, or toast.
Use child minSpec only when the block is optional. If losing the block makes the flow unusable, serve a different lower-spec layout.
Action behavior
Actions may also include minSpec:
{
"type": "custom",
"name": "acme:shareOffer",
"minSpec": 5,
"args": { "offerId": "$root.id" }
}
Unknown type, unregistered custom action, or high minSpec is skipped:
- log;
- no-op;
- continue surrounding
sync; - do not run
onErroror error observers because compatibility skip is not execution failure.
The backend returns an older action/tree based on X-Bdui-Spec.
Refusing the view
Root minSpec is different:
{
"id": "root",
"type": "Column",
"minSpec": 6,
"children": []
}
When root minSpec is above the client:
- do not commit the document;
- stop applying it as a snapshot or a
patchthat replaces$root; - ask the host to show its update/incompatible UI in this view's slot;
- keep the previous valid tree if this was a live update.
This rule applies to initial JSON, SSE snapshot, patch that replaces $root, and restored checkpoints.
Evolution rules
| Change | Required behavior |
|---|---|
| New optional param/key | No bump; old client ignores it |
| New enum value | Only no bump when old default is safe; otherwise bump |
| New widget | Bump; server sends old layout or marks optional block minSpec |
| New action/prefix/apply/trigger | Bump; server sends old behavior or omits it. patch is spec 2; dialog is spec 3; envelope modifier/onClick is spec 4 |
HTTP jsMap | Bump. Old clients would ignore the field and apply the unmapped body — do not send jsMap to a lower X-Bdui-Spec; send a pre-shaped payload instead |
| Changed semantics for existing field | Prefer a new field; otherwise bump and require root minSpec |
| New namespaced app widget/action | No spec bump; unregistered clients skip |
| Removing a type | Keep decoder support through deprecation window; server stops sending based on advertised spec |
Never reuse an old field/type name for incompatible behavior.
SSE and generation
The client spec controls vocabulary; generation controls stream ordering. They are separate:
minSpec: can this client understand the payload?generation: is this update newer than the current store?- SSE
id: where should reconnect resume?
An incompatible root snapshot is refused. An incompatible child action/node is skipped without disconnecting.
Checkpoints
A checkpoint stores the client spec/schema with which it was decoded. On restore:
- reject if root
minSpec > CURRENT; - reject when checkpoint schema cannot migrate;
- otherwise apply normal node/action compatibility rules;
- reconnect to obtain current server state.
Server examples
For spec 4:
{
"id": "root",
"type": "Column",
"children": [
{ "type": "Image", "params": { "url": "$root.hero", "contentDescription": "Offer" } }
]
}
For spec 5:
{
"id": "root",
"type": "Column",
"children": [
{
"type": "Carousel",
"children": [
{ "type": "Image", "params": { "url": "$root.hero", "contentDescription": "Offer" } }
]
}
]
}
The two documents may be produced from one server-side model; clients should not carry substitute layout logic.