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
7 changes: 7 additions & 0 deletions .changeset/jsonc-fence-extractor-test-only.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
---

Test-only change in `@object-ui/components`: the skill-guide fence extractor in
`skill-guide-data-table-binding.test.tsx` now reads `jsonc` fences as well as
`json`, so retagging a comment-carrying guide example no longer hides it from
every assertion in that file. No published behaviour changes.
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,16 @@ function readGuide(rel: string): string {
return fs.readFileSync(path.join(repoRoot, rel), 'utf8');
}

/** Every fenced ```json block body in a markdown file, in document order. */
/**
* Every fenced ```json OR ```jsonc block body in a markdown file, in document
* order. Both tags are read on purpose: the guides tag their comment-carrying
* examples ```jsonc, and `parseBlock` below already strips those `//` comments
* before parsing. Reading only ```json would let a retag silently hide an
* example from every assertion here — which is a pass, not a failure.
*/
function jsonBlocks(md: string): string[] {
const out: string[] = [];
const fence = /```json\n([\s\S]*?)```/g;
const fence = /```jsonc?\n([\s\S]*?)```/g;
let m: RegExpExecArray | null;
while ((m = fence.exec(md)) !== null) out.push(m[1]);
return out;
Expand Down
22 changes: 11 additions & 11 deletions skills/objectui/guides/schema-expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ 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:

```json
```jsonc
// Evaluated, then dropped -- renders an empty card
{ "type": "card", "props": { "title": "${data.customer.name}" } }

Expand Down Expand Up @@ -167,7 +167,7 @@ name, so calls are case-insensitive.

Each condition field has two forms — a shorthand and an `On` suffix:

```json
```jsonc
{ "hidden": true } // static boolean
{ "hidden": "${data.role !== 'admin'}" } // template expression
{ "hiddenOn": "data.role !== 'admin'" } // raw expression (no ${} needed)
Expand Down Expand Up @@ -325,7 +325,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`.

```json
```jsonc
// ❌ Renders two EMPTY <li>. `children` is not a template — `list` never reads it,
// and `${item.name}` would render literally even if it did.
{
Expand All @@ -352,7 +352,7 @@ descriptor:
section exists to close: binding `list` to ordinary records produces one empty
`<li>` per record — the right number of bullets, no text in any of them.

```json
```jsonc
// ✅ Authored items — `title` and `ordered` are read off the node
{
"type": "list",
Expand All @@ -362,7 +362,7 @@ section exists to close: binding `list` to ordinary records produces one empty
}
```

```json
```jsonc
// ✅ Bound data, already node-shaped: dataSource = { rows: [{ "content": "Ada" }, { "content": "Linus" }] }
{ "type": "list", "bind": "rows" }
```
Expand All @@ -371,7 +371,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:

```json
```jsonc
// ✅ `${data.*}` works inside `body`; there is still no `${item.*}`
{
"type": "list",
Expand All @@ -388,7 +388,7 @@ never against a current element:
accessors (`accessorKey`). Cell values are plain property lookups — never
expressions — and `table` does not read `bind`.

```json
```jsonc
// ✅ `table`: inline rows + column accessors, no per-row scope
{
"type": "table",
Expand Down Expand Up @@ -444,7 +444,7 @@ Expressions are compiled once per unique `(expression, variableNames)` pair and

**Cause:** The field isn't expression-evaluated. Use `content`.

```json
```jsonc
// ❌ Won't evaluate — `value` is read but never templated
{ "type": "text", "value": "${data.total}" }

Expand All @@ -460,7 +460,7 @@ 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.

```json
```jsonc
// ❌ Truthy but not boolean
{ "hidden": "${data.count}" }

Expand All @@ -476,7 +476,7 @@ 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.

```json
```jsonc
// ✅ Works, but renders "Thu Jan 01 1970 …"
{ "type": "text", "content": "${new Date(data.timestamp)}" }

Expand All @@ -489,7 +489,7 @@ the DOM as a locale-dependent blob.

### Object literal in expression

```json
```jsonc
// ❌ Object literals not supported
{ "type": "text", "style": "${{ color: 'red' }}" }

Expand Down
8 changes: 4 additions & 4 deletions skills/objectui/rules/protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ key under `props` never reaches a `ui:*` / `page:*` renderer at all.
**❌ FORBIDDEN:** Adding custom properties not defined in `@objectstack/spec`.

**Example violation:**
```json
```jsonc
{
"type": "data-table",
"fields": [...], // ❌ spec uses "columns"
Expand All @@ -163,7 +163,7 @@ key under `props` never reaches a `ui:*` / `page:*` renderer at all.
```

**✅ CORRECT:**
```json
```jsonc
{
"type": "data-table",
"columns": [...] // ✅ declared by DataTableSchema, read off the node
Expand All @@ -174,7 +174,7 @@ key under `props` never reaches a `ui:*` / `page:*` renderer at all.

When the entire string is a single `${expression}`, the result preserves its type:

```json
```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)
Expand All @@ -184,7 +184,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:

```json
```jsonc
{
"type": "list",
"bind": "customerNames" // Resolved as dataSource.customerNames
Expand Down
2 changes: 1 addition & 1 deletion skills/objectui/rules/styling.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +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:

```json
```jsonc
{
"type": "card",
"className": "bg-red-500", // ✅ User can override styles
Expand Down
Loading