Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion content/docs/guide/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,12 +243,18 @@ ObjectUI includes a powerful expression engine for dynamic UIs:
```json
{
"type": "button",
"text": "Submit",
"label": "Submit",
"visible": "${form.isValid && !form.isSubmitting}",
"disabled": "${form.isSubmitting}"
}
```

A button's text key is `label`, and `text` is not a `ButtonSchema` key at all. Nothing
refuses the misspelling either: `BaseSchema` is `.passthrough()`, so the validator KEEPS
the unknown key, and the renderer — which reads `schema.label` — never looks at it.
Measured on the node above with `text`: the button renders with an empty `textContent`,
so it appears on screen as a blank rectangle with no text.

### Data Transformations

```json
Expand Down
37 changes: 28 additions & 9 deletions content/docs/guide/layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,13 @@ The `Page` component provides a consistent wrapper for individual pages with opt
"actions": [
{
"type": "button",
"text": "Add Product",
"label": "Add Product",
"variant": "default",
"icon": "plus"
},
{
"type": "button",
"text": "Export",
"label": "Export",
"variant": "outline",
"icon": "download"
}
Expand All @@ -170,6 +170,10 @@ The `Page` component provides a consistent wrapper for individual pages with opt
}
```

`label` is the button's text key — `text` is not a `ButtonSchema` key, and because
`BaseSchema` is `.passthrough()` nothing refuses it: the validator keeps the unknown key
and `button.tsx`, which reads `schema.label`, renders a button with no text.

### Schema API

<!-- doc-snippet: fragment — a SHAPE excerpt, not an expression — the keys carry `?` optional markers and trailing prose comments, so the object literal cannot parse as TypeScript (measured: TS1109 / TS1005 / TS1011) -->
Expand Down Expand Up @@ -516,18 +520,20 @@ Omit `sidebar` and the content fills the width under the top bar.
],
"actions": [
{
"type": "button",
"text": "Edit",
"type": "action:button",
"name": "edit_record",
"label": "Edit",
"variant": "default",
"icon": "pencil",
"onClick": "editRecord"
"actionType": "editRecord"
},
{
"type": "button",
"text": "Delete",
"type": "action:button",
"name": "delete_record",
"label": "Delete",
"variant": "destructive",
"icon": "trash",
"onClick": "deleteRecord"
"actionType": "deleteRecord"
}
],
"body": {
Expand All @@ -539,6 +545,19 @@ Omit `sidebar` and the content fills the width under the top bar.
}
```

A button that RUNS something is an `action:button` node, not a `button` carrying an
`onClick`. `ButtonSchema.onClick` is declared as a runtime slot for a host-supplied
function and the zod mirror refuses it BY NAME — JSON has no function value, and no
handler key consumes a declarative action object. The refusal is not the whole cost:
`onClick` is on `SDUI_DOM_PASS_THROUGH_KEYS`, so an authored string or object is
forwarded to the real DOM listener slot, and React throws the moment anyone clicks.
Measured, React's own error: "Expected `onClick` listener to be a function, instead got
a value of `object` type."

The handler name goes in `actionType`, which `action:button` forwards to the action
runner as the action's type; the runner dispatches to the handler registered under it.
Same spelling as [Record Edit Modes](./record-edit-modes.md).

## Responsive Behavior

The shell has exactly **one** layout breakpoint, at **768px** — Tailwind's `md`, and
Expand Down Expand Up @@ -650,7 +669,7 @@ Place primary actions in page headers:
"type": "page",
"title": "Orders",
"actions": [
{ "type": "button", "text": "New Order", "variant": "default" }
{ "type": "button", "label": "New Order", "variant": "default" }
]
}
```
Expand Down
21 changes: 16 additions & 5 deletions content/docs/guide/schema-rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,15 +252,26 @@ Reference actions in schemas:

```json
{
"type": "button",
"type": "action:button",
"name": "call_api",
"label": "Click Me",
"onClick": {
"actionType": "ajax",
"api": "/api/action"
}
"actionType": "api",
"endpoint": "/api/action",
"method": "POST"
}
```

Three things about the shape this replaces. A declarative action is its own NODE TYPE,
`action:button` — a plain `button` has no authorable handler: `ButtonSchema.onClick` is a
runtime slot for a host-supplied function, refused by name by the zod mirror, and (being
on `SDUI_DOM_PASS_THROUGH_KEYS`) forwarded straight to the DOM listener slot, where React
throws on the first click: "Expected `onClick` listener to be a function, instead got a
value of `object` type." The execution type is `actionType`, and the built-in vocabulary
is `script` | `url` | `modal` | `flow` | `api` | `form` (plus objectui's `navigation`
alias) — anything else must be a handler your host registered on `ActionProvider`. `ajax`
is neither. And the endpoint key is `endpoint`, with `method`; `api` is not a key any
action renderer forwards.

## Performance Optimization

### Lazy Loading
Expand Down