Skip to content

Commit 81136e7

Browse files
committed
Merge origin/main (b403bb3) into claude/issue-11340-book-tree-prefilter
Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL Co-authored-by: Claude <noreply@anthropic.com>
2 parents a36dbd7 + b403bb3 commit 81136e7

202 files changed

Lines changed: 2243 additions & 1677 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.changeset/11564-flex-bag-children-list.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,5 @@ The type is the flat `FlexSchema` mirror's own `children` member, by reference,
1616
What does NOT move: every zod face and every runtime path. `FlexBlockSchema` keeps `z.array(z.unknown())` for the list, because `@objectstack/spec`'s page walk already judges each entry there, once, at its real path, on the tolerant and the strict face (objectui#11223). The one entry kind where the faces now differ is a nested list, which the walk passes through unvisited and the TypeScript face refuses; `@object-ui/types`' mirror-parity ledger records the divergence.
1717

1818
**Migration.** Give each list entry its declared node type (or `DeclaredNode`), declare a custom type in `CustomNodeRegistry`, and flatten a nested list into the one list.
19+
20+
⚠️ **Dated note, 2026-10-04 — `BaseSchema` loses its index signature — objectui#8347.** At this change, "Node types that extend `BaseSchema` keep its index signature until objectui#8347 removes it, so a misspelled key on one of them still compiles" held. Later in this same release objectui#8347 removed that signature, so a misspelled key on a node type that extends `BaseSchema` no longer compiles either, in the list exactly as in a single child. `.changeset/8347-baseschema-closed-face.md` states what ships. The rest of this entry is kept as the reading of this change.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@object-ui/types': minor
3+
'@object-ui/plugin-kanban': patch
4+
'@object-ui/plugin-timeline': minor
5+
---
6+
7+
**`BaseSchema` no longer declares `[key: string]: any`** (objectui#8347, executing the objectui#7927 ruling: the TypeScript face is a contract). Every node type extends `BaseSchema`, so a node literal annotated with its node type now refuses a key that no declaration names, a misspelled key included, where it used to type it `any`. The correct spelling compiles as before.
8+
9+
**Clause-②: yes (narrowing)**, shipped as `minor` per this repository's version policy. The removal narrows the TypeScript authoring face of every node type; the `visibleWhen` change below widens both faces to the envelope the spec's own parse writes.
10+
11+
- **What does not move.** The zod faces keep their accept sets for every key but `visibleWhen`: the tolerant mirror is still `.passthrough()`, so `safeValidateSchema` keeps an undeclared key, and the derived strict face refuses it as before. `ComponentRendererProps`, the renderer props type, keeps its own index signature. Nothing a renderer draws changes.
12+
- **The bound.** TypeScript runs its excess-property check only on a fresh object literal. A value that reached its annotation through a variable of a wider type is not re-checked.
13+
- **`PartialSchema<T>` works as written.** With the signature gone, `keyof T` is the literal member union again, so the alias keeps `T`'s declared members, optional, with `type` required. While the signature stood it declared `type` alone (objectui#6397).
14+
- **`BaseSchema.visibleWhen` is the spec's `EvaluatedExpressionInput`**, by reference: a predicate string, or the `{ dialect, source }` envelope. The zod twin takes `EvaluatedExpressionInputSchema`'s verdict without its transform, so a string parses to itself. A dialect-less envelope, an unknown dialect and a blank predicate are refused, as the spec refuses them. Both faces read `string` before, which refused the envelope a spec parse writes into this key.
15+
- **`@object-ui/plugin-kanban`.** `ObjectKanban` reads the `sort` the element data-source gate writes through a read type private to the package. `ObjectKanbanSchema` still declares no `sort` (objectui#8174). Nothing drawn changes.
16+
- **`@object-ui/plugin-timeline`.** `TimelineRenderSchema`, the `schema` prop type of the exported `TimelineRenderer`, gains one optional member: the `onItemClick` slot `ObjectTimeline` composes. That is a one-member optional widening of an exported prop type. `TimelineSchema`, the authoring face, still declares no `onItemClick`. Nothing drawn changes.
17+
18+
**Migration.** Where a literal stops compiling, the key is misspelled (fix it) or not declared on that node type (declare it on the type that reads it, by reference to the `@objectstack/spec` row, or remove it). Do not cast past the error. `props`, the legacy alias of `properties`, is not declared on the TypeScript face; the renderer still reads it, so write `properties`.

‎AGENTS.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,12 @@ Every node in the UI tree follows this shape (`@object-ui/types`):
7474
interface BaseSchema {
7575
type: string; // registry key: 'input', 'grid', 'card'
7676
id?: string; // DOM accessibility / event targeting
77-
props?: Record<string, any>; // element:* config envelope, not a general bag
7877
bind?: string; // data binding path: 'user.address.city'
7978
className?: string; // Tailwind overrides
8079
hidden?: boolean | ExpressionWire; // expression or boolean: "${data.role != 'admin'}"
8180
disabled?: boolean | ExpressionWire; // same wire as hidden
8281
children?: SchemaNode | SchemaNode[]; // layout slots; primitives admitted too
82+
// no index signature: a key no member declares is a compile error (#0.1)
8383
}
8484
```
8585

@@ -90,14 +90,15 @@ interface BaseSchema {
9090
- **#-1 — English-only codebase.** This is an international OSS project. All user-facing text (component labels, buttons, titles, errors), code comments, docs (`README.md`, `docs/*.md`), and console/log messages MUST be English. No Chinese or other non-English in those. *(This rule governs the **codebase**; this instruction file may use Chinese in operational sections.)*
9191
- **#0 — Strict adherence to `@objectstack/spec`.** All schemas/JSON structures/types MUST follow `@objectstack/spec`. Don't invent schema properties — if the spec says `columns`, don't use `fields`. Check the spec before writing any `interface`/`type`.
9292
- **#0.1 — Fix the metadata, not the renderer (contract-first).** Corollary to #0. This is a metadata-driven system: `@objectstack/spec` is the contract between producers and this renderer. When a piece of metadata "doesn't render," ask **first**: *is it spec-compliant? is this the long-term-correct direction?* If the metadata is off-spec, fix it at the **producer** (and have it rejected at authoring/publish) — do **not** add a lenient fallback/alias in the renderer (reading both `columns` and `fields`, coercing a malformed shape, `??`-defaulting around bad input) to make non-compliant metadata "work." A tolerant fallback fossilizes the wrong convention into a second de-facto contract, dilutes the spec, and hides the producer's bug — one strict contract beats N dialects. We own both ends, so Postel's "be liberal in what you accept" does **not** apply (that's for untrusted boundaries). Change the **spec** only when it is genuinely wrong — deliberately, in `@objectstack/spec`, never by accreting renderer-side fallbacks.
93+
- **The TypeScript authoring face is part of the contract (objectui#7927 ruling, executed by objectui#8347).** `BaseSchema` declares no index signature, so a node literal typed with its node type refuses, at the authoring site, a key no declaration names — a misspelling included. When a key you need is refused, this rule applies unchanged: declare it on the node type that reads it, by reference to the spec row, or fix the producer; a key only the host writes at runtime is typed on a package-local hand-off type where its producer and consumer live, never on the authoring face (objectui#6356). ⛔ Never make it compile with `as any`, a cast to `Record<string, unknown>`, a re-added index signature or an open `type: string` arm. Tolerance stays where it is deliberate: renderer props (`ComponentRendererProps` keeps its signature) and the tolerant zod face (`.passthrough()`); the derived strict zod face, `StrictAnyComponentSchema`, refuses an unknown key in metadata that arrives as data. ⚠️ The bound, said per #9: TypeScript excess-checks only a FRESH object literal, so a value that reached its annotation through a wider variable is not re-checked. `packages/types/src/__tests__/base-schema-closed-face-8347.test.ts` pins both directions and that bound.
9394
- **#1 — Protocol-agnostic.** Never hardcode `objectql.find()`. Use the DataSource interface; inject `dataSource` via `<SchemaRendererProvider dataSource={...} />`.
9495
- **#2 — Docs-driven.** For every feature/refactor, update package `README.md` **and** `content/docs/guide/*.md`. Not done until docs reflect the code.
9596
- **#3 — "Shadcn-native" aesthetics.** We are "serializable Shadcn". Follow Shadcn's DOM structure (`CardHeader`/`CardTitle`/`CardContent`). Always expose `className` in schema props so users can override via JSON.
9697
- **#4 — Action system (objectui#7898, objectui#6497, objectui#11183).** Actions are **data, not functions**, and a control that RUNS something is its own NODE TYPE — `action:button` — never a handler key or an event bag on an ordinary node. The block's props live in the node's `properties` bag, which is the spec's `ComponentPropsMap` row for it: `actionType` names the executor the action runner dispatches to, and the row's other keys carry that executor's arguments. ⛔ Never write them flat on the node: the spec's `PageComponentSchema` is `.strict()` (ADR-0089 D3a) and refuses a node-level `actionType` / `target` as mis-layered, and objectui's strict authoring face refuses the same two keys. The runtime reads both spellings through the `SchemaRenderer` `properties` hoist, so the bag costs nothing at render:
9798
```json
9899
{ "type": "action:button", "properties": { "label": "Open details", "actionType": "url", "target": "/users/ada" } }
99100
```
100-
⛔ Never author an `events` bag: `BaseSchema` declares no `events` member and no renderer reads `schema.events` — the node is `.passthrough()`, so one authored there is kept, judged by nothing and run by nothing. `ButtonSchema.onClick` is a runtime slot for a host-supplied function and is refused by name for the same reason.
101+
⛔ Never author an `events` bag: `BaseSchema` declares no `events` member and no renderer reads `schema.events` — a typed literal refuses it (#0.1), but the tolerant zod face is `.passthrough()`, so one that arrives as data is kept, judged by nothing and run by nothing. `ButtonSchema.onClick` is a runtime slot for a host-supplied function and is refused by name for the same reason.
101102
- **#5 — Layout as components.** Treat `Grid`/`Stack`/`Container` as first-class. Layout schemas declare responsive columns on the node as `columns` — a number, or a breakpoint object (`columns: { xs: 1, md: 2, lg: 4 }`); never `cols`, which nothing reads (objectui#4001).
102103
- **#6 — Type safety over magic.** No `any` — use strict generics. Map `"type": "button"` → React component via a central `ComponentRegistry`. **No `eval()` / runtime dynamic imports** to load components (security).
103104
- **#7 — No-Touch zones (Shadcn purity).** `packages/components/src/ui/**/*.tsx` are upstream 3rd-party files overwritten by sync scripts — **never edit their logic/styles**. To change `Button`/`Dialog` behavior: create/edit a wrapper in `packages/components/src/custom/`, import the primitive from `@/ui/...`, and wrap it.

‎content/docs/api/schema-reference.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ One row per declared member, in declaration order, so the list can be checked ag
9696
| `body` | *retired* | ⛔ Refused by name (objectui#6771). `body` was a second child-list spelling `BaseSchema` declared beside `children`; it is now `never` on the TypeScript face and an alias refusal on the Zod mirror, and the refusal names `children`. |
9797
| `children` | `SchemaNode \| SchemaNode[]` | Child components rendered inside this component — the child-list key, and since objectui#6771 the only one. Whether a given node type renders a child list at all is still per component; see the note below. |
9898
| `visible` | `boolean \| string \| { dialect?: string; source: string }` | Visibility control. Accepts a boolean, a predicate expression string, **or** the CEL envelope object (`{ dialect: 'cel', source }` — what `objectstack build` emits for every authored predicate) — the renderer evaluates this key rather than reading it as a boolean. The string-or-envelope half is `ExpressionWire`, the one wire type `visibleWhen` on form fields already carries. |
99-
| `visibleWhen` | `string` | Canonical conditional-visibility predicate (ADR-0089); the element is shown when it evaluates truthy. Evaluated **before** `visible` and `visibleOn`, and outranks both. |
99+
| `visibleWhen` | `string \| { dialect, source }` | Canonical conditional-visibility predicate (ADR-0089); the element is shown when it evaluates truthy. Typed as `@objectstack/spec`'s `EvaluatedExpressionInput`: a predicate string, or the envelope the spec's parse writes (`dialect` is `cel`, `cron` or `template`, and `source` is not blank). Evaluated **before** `visible` and `visibleOn`, and outranks both. |
100100
| `visibleOn` | `string` | Expression for conditional visibility. **Deprecated** (ADR-0089) — use `visibleWhen`. |
101101
| `hidden` | `boolean \| string \| { dialect?: string; source: string }` | Inverse of `visible` — the node is not rendered. Accepts a boolean, a predicate expression string **or** the CEL envelope object (`ExpressionWire`), which the renderer evaluates rather than reading as a boolean; `hiddenOn` remains the sibling spelling. |
102102
| `hiddenOn` | `string` | Expression for conditional hiding. |
@@ -109,7 +109,7 @@ Two things the table cannot show in a cell:
109109

110110
- **A concrete schema may narrow an inherited member, and its own declaration wins.** Many component schemas restate `label`, `description` or `disabled` more narrowly than `BaseSchema` declares them, so the unions above are what a node gets when its own schema does not restate the key. Check the component's own property table before writing a predicate string or a locale map into an inherited slot.
111111
- **⚠️ `body` and `children` WERE two channels, not one key with two spellings — and the second one is retired (objectui#6771).** Each renderer read one, the other, both, or neither, and `SchemaRenderer` strips both out of the props bag it spreads — so writing the channel a renderer did not read rendered an EMPTY element, with no error at authoring time, none at validation time and none at render time. That is the defect objectui#8284 named after seven cards had repaired one page of it each, and it was closed per component by measurement: each schema narrows to the channel its renderer actually reads and **tombstones the other as `never`**, refused by name on both published faces (objectui#9254 for the components that read exactly one channel, objectui#9256 for the ones that read neither). objectui#6771 then closed the class at its source: `children` is the one child-list spelling and `body` is refused on `BaseSchema` itself. ⇒ **whether a node type accepts a child list at all is still per component** — check the component's own section, where a node that renders no children tombstones `children` too.
112-
- **This list is exhaustive for *declared* members, not for *accepted* keys.** `BaseSchema` carries an index signature (`[key: string]: any`) and its Zod mirror is `.passthrough()`, so an undeclared key — a misspelling included — is still accepted by both halves. Absence from this table does not mean a key is rejected.
112+
- **This list is exhaustive for *declared* members; the two faces differ on *accepted* keys.** `BaseSchema` carries no index signature (objectui#8347), so on the TypeScript face an undeclared key, a misspelling included, is a compile error in a typed literal. Its tolerant Zod mirror is still `.passthrough()`, so the rendering face (and `safeValidateSchema`) keeps an undeclared key; the derived strict face, `StrictAnyComponentSchema`, refuses it. Absence from this table does not mean the tolerant face rejects a key.
113113

114114
---
115115

‎content/docs/components/data-display/list.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ required, so the bind-only list above was refused at `items`. The TypeScript fac
4242
<Callout type="warn">
4343
`wrapperClass` is now typed. The renderer has read it all along — it lands on the
4444
wrapper element around the list — but it went undeclared until objectui#7722,
45-
surviving on `BaseSchema`'s index signature and the zod mirror's `.passthrough()`.
45+
surviving on what was then `BaseSchema`'s index signature (objectui#8347 removed it) and the zod mirror's `.passthrough()`.
4646
Declared as `string` on both faces, a **non-string** `wrapperClass` that used to
4747
parse green is now refused by name — on `ListSchema` and through
4848
`safeValidateSchema` alike. Pass the Tailwind class string; a number or an object

‎content/docs/components/form/date-picker.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ interface DatePickerSchema {
4949
<Callout type="warn">
5050
`wrapperClass` is now typed. The renderer has read it all along — it lands on the
5151
wrapper element around the date picker and its label — but it went undeclared until
52-
objectui#7722, surviving on `BaseSchema`'s index signature and the zod mirror's
52+
objectui#7722, surviving on what was then `BaseSchema`'s index signature (objectui#8347 removed it) and the zod mirror's
5353
`.passthrough()`. Declared as `string` on both faces, a **non-string**
5454
`wrapperClass` that used to parse green is now refused by name — on
5555
`DatePickerSchema` and through `safeValidateSchema` alike. Pass the Tailwind class

‎content/docs/components/form/select.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ interface SelectSchema {
2727
<Callout type="warn">
2828
`wrapperClass` is now typed. The renderer has read it all along — it lands on the
2929
wrapper element around the select and its label — but it went undeclared until
30-
objectui#7722, surviving on `BaseSchema`'s index signature and the zod mirror's
30+
objectui#7722, surviving on what was then `BaseSchema`'s index signature (objectui#8347 removed it) and the zod mirror's
3131
`.passthrough()`. Declared as `string` on both faces, a **non-string**
3232
`wrapperClass` that used to parse green is now refused by name — on `SelectSchema`
3333
and through `safeValidateSchema` alike. Pass the Tailwind class string; a number or

‎content/docs/components/form/switch.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ interface SwitchSchema {
2525
<Callout type="warn">
2626
`wrapperClass` is now typed. The renderer has read it all along — it lands on the
2727
wrapper element around the switch and its label — but it went undeclared until
28-
objectui#7722, surviving on `BaseSchema`'s index signature and the zod mirror's
28+
objectui#7722, surviving on what was then `BaseSchema`'s index signature (objectui#8347 removed it) and the zod mirror's
2929
`.passthrough()`. Declared as `string` on both faces, a **non-string**
3030
`wrapperClass` that used to parse green is now refused by name — on `SwitchSchema`
3131
and through `safeValidateSchema` alike. Pass the Tailwind class string; a number or

‎content/docs/components/form/textarea.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ interface TextareaSchema {
2727
<Callout type="warn">
2828
`wrapperClass` is now typed. The renderer has read it all along — it lands on the
2929
wrapper element around the textarea and its label — but it went undeclared until
30-
objectui#7722, surviving on `BaseSchema`'s index signature and the zod mirror's
30+
objectui#7722, surviving on what was then `BaseSchema`'s index signature (objectui#8347 removed it) and the zod mirror's
3131
`.passthrough()`. Declared as `string` on both faces, a **non-string**
3232
`wrapperClass` that used to parse green is now refused by name — on `TextareaSchema`
3333
and through `safeValidateSchema` alike. Pass the Tailwind class string; a number or

0 commit comments

Comments
 (0)