diff --git a/scripts/check-skill-examples.mjs b/scripts/check-skill-examples.mjs
index be765652f2..0480b44ed8 100644
--- a/scripts/check-skill-examples.mjs
+++ b/scripts/check-skill-examples.mjs
@@ -460,7 +460,13 @@ export const MARKED_FLOOR = new Map([
// gate's reach, and the guide already carried five UNMARKED `vitest` fences
// beside them. ⛔ Not a precedent for unmarking a fence that reds.
['ts', 13],
- ['json', 39],
+ // 39 -> 70 (objectui#7474): the 8 multi-document listings under
+ // `skills/objectui/` were split into one fence per document and the two
+ // `...` elisions were replaced by real values, so every one of the 70
+ // json/jsonc fences in the corpus now parses under its own tag and carries
+ // the marker. Raising the number is the ADD direction this list documents:
+ // the population measured 39 marked / 46 parsing before, 70 / 70 after.
+ ['json', 70],
]);
/**
diff --git a/skills/objectui/guides/schema-expressions.md b/skills/objectui/guides/schema-expressions.md
index 3773413fc1..ef6b3a624c 100644
--- a/skills/objectui/guides/schema-expressions.md
+++ b/skills/objectui/guides/schema-expressions.md
@@ -29,16 +29,26 @@ full boundary tables -- the evaluated fields, the raw ones, `visible` over
-- are in [`rules/protocol.md`](../rules/protocol.md), which is the anchor for
this rule. The four cases that decide most schemas:
+
```jsonc
// Evaluated, then dropped -- renders an empty card
{ "type": "card", "props": { "title": "${data.customer.name}" } }
+```
+
+```jsonc
// `card` declares `title`, so on the node it is evaluated AND read
{ "type": "card", "title": "${data.customer.name}" }
+```
+
+```jsonc
// `text` does not declare `value` -- renders the literal "${data.customer.name}"
{ "type": "text", "value": "${data.customer.name}" }
+```
+
+```jsonc
// `content` is evaluated on every component type
{ "type": "text", "content": "${data.customer.name}" }
```
@@ -167,10 +177,19 @@ name, so calls are case-insensitive.
Each condition field has two forms — a shorthand and an `On` suffix:
+
+```jsonc
+{ "hidden": true } // static boolean
+```
+
+
```jsonc
-{ "hidden": true } // static boolean
-{ "hidden": "${data.role !== 'admin'}" } // template expression
-{ "hiddenOn": "data.role !== 'admin'" } // raw expression (no ${} needed)
+{ "hidden": "${data.role !== 'admin'}" } // template expression
+```
+
+
+```jsonc
+{ "hiddenOn": "data.role !== 'admin'" } // raw expression (no ${} needed)
```
The `On` variants accept raw expressions without `${}` wrapping — the entire string is the expression.
@@ -203,8 +222,13 @@ The `On` variants accept raw expressions without `${}` wrapping — the entire s
### Disabled patterns
+
```json
{ "disabled": "${form.isSubmitting}" }
+```
+
+
+```json
{ "disabledOn": "!data.canPerformAction || data.isLocked" }
```
@@ -325,6 +349,7 @@ what reaches the screen.
`list` is the component authors reach for first, and it is **data-as-nodes**:
the array it renders *is* the node list. It never reads `children`.
+
```jsonc
// ❌ Renders two EMPTY
. `children` is not a template — `list` never reads it,
// and `${item.name}` would render literally even if it did.
@@ -352,6 +377,7 @@ descriptor:
section exists to close: binding `list` to ordinary records produces one empty
`` per record — the right number of bullets, no text in any of them.
+
```jsonc
// ✅ Authored items — `title` and `ordered` are read off the node
{
@@ -362,6 +388,7 @@ section exists to close: binding `list` to ordinary records produces one empty
}
```
+
```jsonc
// ✅ Bound data, already node-shaped: dataSource = { rows: [{ "content": "Ada" }, { "content": "Linus" }] }
{ "type": "list", "bind": "rows" }
@@ -371,6 +398,7 @@ Only `body` entries go back through `SchemaRenderer`, so they are the one place
inside a list where expressions are evaluated at all — against the host scope,
never against a current element:
+
```jsonc
// ✅ `${data.*}` works inside `body`; there is still no `${item.*}`
{
@@ -388,6 +416,7 @@ never against a current element:
accessors (`accessorKey`). Cell values are plain property lookups — never
expressions — and `table` does not read `bind`.
+
```jsonc
// ✅ `table`: inline rows + column accessors, no per-row scope
{
@@ -444,13 +473,20 @@ Expressions are compiled once per unique `(expression, variableNames)` pair and
**Cause:** The field isn't expression-evaluated. Use `content`.
+
```jsonc
// ❌ Won't evaluate — `value` is read but never templated
{ "type": "text", "value": "${data.total}" }
+```
+
+```jsonc
// ❌ Worse — evaluated inside the envelope, then discarded: renders nothing
{ "type": "text", "props": { "value": "${data.total}" } }
+```
+
+```jsonc
// ✅ Evaluated and read
{ "type": "text", "content": "Total: ${data.total}" }
```
@@ -460,10 +496,14 @@ Expressions are compiled once per unique `(expression, variableNames)` pair and
**Cause 1:** `visible` is also set and takes priority.
**Cause 2:** Expression returns a non-boolean truthy value — use explicit comparison.
+
```jsonc
// ❌ Truthy but not boolean
{ "hidden": "${data.count}" }
+```
+
+```jsonc
// ✅ Explicit boolean
{ "hidden": "${data.count > 0}" }
```
@@ -476,23 +516,34 @@ is **not** blocked — measured through the schema path, it returns a real
`Date`. Prefer a formula function anyway: a `Date` object stringifies into
the DOM as a locale-dependent blob.
+
```jsonc
// ✅ Works, but renders "Thu Jan 01 1970 …"
{ "type": "text", "content": "${new Date(data.timestamp)}" }
+```
+
+```jsonc
// ✅ Preferred — formatted, and stable across locales
{ "type": "text", "content": "${DATEFORMAT(data.timestamp, 'YYYY-MM-DD')}" }
+```
+
+```jsonc
// ❌ Rejected — any constructor other than Date / RegExp
{ "type": "text", "content": "${new Function('return 1')()}" }
```
### Object literal in expression
+
```jsonc
// ❌ Object literals not supported
{ "type": "text", "style": "${{ color: 'red' }}" }
+```
+
+```jsonc
// ✅ Use className
{ "type": "text", "className": "text-red-500" }
```
diff --git a/skills/objectui/rules/protocol.md b/skills/objectui/rules/protocol.md
index 5ea19e06fe..51463cd724 100644
--- a/skills/objectui/rules/protocol.md
+++ b/skills/objectui/rules/protocol.md
@@ -154,19 +154,21 @@ key under `props` never reaches a `ui:*` / `page:*` renderer at all.
**❌ FORBIDDEN:** Adding custom properties not defined in `@objectstack/spec`.
**Example violation:**
+
```jsonc
{
"type": "data-table",
- "fields": [...], // ❌ spec uses "columns"
+ "fields": [{ "header": "Name", "accessorKey": "name" }], // ❌ spec uses "columns"
"customProp": "value" // ❌ not in spec
}
```
**✅ CORRECT:**
+
```jsonc
{
"type": "data-table",
- "columns": [...] // ✅ declared by DataTableSchema, read off the node
+ "columns": [{ "header": "Name", "accessorKey": "name" }] // ✅ declared by DataTableSchema, read off the node
}
```
@@ -174,9 +176,18 @@ key under `props` never reaches a `ui:*` / `page:*` renderer at all.
When the entire string is a single `${expression}`, the result preserves its type:
+
+```jsonc
+"${data.count}" // → returns number 42, not string "42"
+```
+
+
+```jsonc
+"${data.isActive}" // → returns boolean true, not string "true"
+```
+
+
```jsonc
-"${data.count}" // → returns number 42, not string "42"
-"${data.isActive}" // → returns boolean true, not string "true"
"Count: ${data.count}" // → returns string "Count: 42" (mixed template)
```
@@ -184,6 +195,7 @@ When the entire string is a single `${expression}`, the result preserves its typ
The `bind` field is NOT expression-evaluated. It's a path string resolved by `useDataScope()`, and only a component that calls that hook reads it:
+
```jsonc
{
"type": "list",
diff --git a/skills/objectui/rules/styling.md b/skills/objectui/rules/styling.md
index 06654f354a..60b888c8a9 100644
--- a/skills/objectui/rules/styling.md
+++ b/skills/objectui/rules/styling.md
@@ -167,6 +167,7 @@ const buttonVariants = cva(
**Every component must accept `className` on its schema node** to allow JSON-level style overrides. Like every other key, it is read off the node itself — not out of a `props` envelope, which the renderers never read:
+
```jsonc
{
"type": "card",