Builder

What it does

Builder manages:

  • component instances
  • surface data model values
  • template metadata
  • surface serialization into protocol payloads

Data Model

The builder keeps a per-surface data model in self._data_model.

This is the structured payload sent to the client through dataModelUpdate. It is not the same thing as the client stores:

  • the surface data model is the server-built snapshot attached to one surface
  • page/global stores are client-side reactive state used by bindings, visibility rules, and incremental interaction flows

In practice:

  • use the data model when the server is preparing the initial surface state or sending a scoped data refresh for that surface
  • use page/global stores when the client must react immediately without a full rerender

Example:

builder = sdk.ui.Builder()
builder.set_surface("users_main")
builder.set_data("/filters/search", "anna")
builder.set_data("/filters/status", "active")
builder.set_data("/table/page", 0)
builder.set_data("/table/page_size", 25)

This produces a nested data model equivalent to:

{
    "filters": {
        "search": "anna",
        "status": "active",
    },
    "table": {
        "page": 0,
        "page_size": 25,
    },
}

Stores

Store bindings are the reactive client-side layer used by components, conditions, and targeted UI updates.

The common scopes are page, for local UI state on the current route or mounted surface, and global, for shell-level or cross-navigation state.

The practical distinction is simple: the surface data model is something the server prepares and sends, while stores are values the client keeps alive and reacts to over time. If a text field must keep a draft value, a sidebar item must react to the current path, or a visibility rule must toggle immediately after a client-side update, that belongs in a store rather than in the builder data model.

That is why store bindings show up in visual state such as search drafts, selected tabs, current route highlighting, visibility toggles, and optimistic interaction flows. They are the reactive layer of the rendered UI after the surface has already been mounted.

Programmatically, seed store state with set_store(...), not with set_data(...).

Example:

builder = sdk.ui.Builder()
builder.set_store("/draft_title", "", scope="page")
builder.set_store("/current_path", "/users/index", scope="global")

The same idea can be authored programmatically in Python or declared directly in YAML:

sdk.ui.Text(
    "draft_value",
    sdk.ui.bound.store("/draft_title", scope="page", default=""),
)

sdk.ui.Text(
    "active_path",
    sdk.ui.bound.store("/current_path", scope="global", default="/"),
)

Use the builder when you need to create or patch A2UI payloads. Use stores when the rendered UI must stay reactive after mount.

A2UI Mapping

In Democr.ai the split is explicit:

  • dataModelUpdate carries the surface-scoped data model
  • stateUpdate carries client store values
  • @data/... reads from the current surface data model
  • @state/... reads from page/global store

This keeps the A2UI surface data model separate from the client reactive stores.