From 6dbef52109607cf81f81b6ae89e77b7ab0868bbe Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 17:07:43 +0000 Subject: [PATCH] docs(api): state every declared BaseSchema member at its declared type The canonical "Common Properties" table in content/docs/api/schema-reference.md narrowed five declared unions to one limb each and omitted five declared members outright. It is the reference page every component page defers to for inherited props, so a reader who checks the authority for a BaseSchema key got a narrower answer than the type gives. Measured against packages/types/src/base.ts and its Zod mirror packages/types/src/zod/base.zod.ts. Their agreement is not assumed: it is held by base-schema-zod-mirror-parity.test.ts, which reads the mirror's own .shape and compares each key against the declaration. Under-stated: label and description are `string | I18nLabel`; ariaLabel is `string | KeyedI18nLabel` -- the KEYED form, deliberately not the inline locale map that label and description carry; visible and disabled each take a predicate expression string as well as a boolean. The expression limb sits on the base key itself, not only on the visibleOn / disabledOn siblings. Omitted entirely: placeholder, style, data, bind, visibleWhen. The three combined cells are split into one row per member. Packing two members with different types into a single `boolean` / `string` cell is what made the error invisible -- the pairing reads as a complete, ordered account, and that appearance of completeness hid the third fact. One row per declared member, in declaration order, makes completeness checkable by reading the table against the interface. hidden really is boolean-only, so that row's type was already correct and the split states it explicitly rather than leaving it to a shared cell. Two notes carry what a cell cannot: a concrete schema may narrow an inherited member and its own declaration wins, and the list is exhaustive for declared members but not for accepted keys -- BaseSchema carries an index signature and its mirror is .passthrough(). Refs objectui#7079. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB --- .../7079-schema-reference-base-props.md | 32 +++++++++++++++++++ content/docs/api/schema-reference.md | 29 +++++++++++++---- 2 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 .changeset/7079-schema-reference-base-props.md diff --git a/.changeset/7079-schema-reference-base-props.md b/.changeset/7079-schema-reference-base-props.md new file mode 100644 index 0000000000..dfcfe3b8e3 --- /dev/null +++ b/.changeset/7079-schema-reference-base-props.md @@ -0,0 +1,32 @@ +--- +--- + +Docs only — the canonical `BaseSchema` "Common Properties" table in +`content/docs/api/schema-reference.md` (objectui#7079). No package source, no +published contract and no runtime behaviour is touched, hence the empty +declaration; `apps/site` is `private: true` and sits in `.changeset/config.json`'s +`ignore` list, so nothing under `content/` ships from this change. + +The table narrowed five declared unions to one limb each and omitted five declared +members outright. Measured against `packages/types/src/base.ts` and its Zod mirror +`packages/types/src/zod/base.zod.ts`, whose agreement is held by +`base-schema-zod-mirror-parity.test.ts` reading the mirror's own `.shape`: +`label` and `description` are `string | I18nLabel`, `ariaLabel` is +`string | KeyedI18nLabel` (the KEYED form, not the inline locale map), and both +`visible` and `disabled` take a predicate expression string as well as a boolean — +the expression limb is on the base key itself, not only on the `visibleOn` / +`disabledOn` siblings the paired cells implied. `placeholder`, `style`, `data`, +`bind` and `visibleWhen` had no row at all. + +The three combined cells (`visible` / `visibleOn`, `hidden` / `hiddenOn`, +`disabled` / `disabledOn`) are split into one row per member. Packing two members +with different types into a single `boolean` / `string` cell is what made the +error invisible: the pairing reads as a complete, ordered account, and that +appearance of completeness is precisely what hid the third fact. One row per +declared member, in declaration order, makes completeness checkable by reading the +table against the interface. + +Nothing in CI reads this table — `check:doc-snippet-types` compiles only `ts` / +`tsx` / `typescript` fences, `check:doc-component-types` judges `type` string +literals and key tables anchored on a `Namespaced key | Bare-name fallback` +header, and no script in the repository parses a Markdown property table. diff --git a/content/docs/api/schema-reference.md b/content/docs/api/schema-reference.md index 7cf2514c4f..0b282bf32a 100644 --- a/content/docs/api/schema-reference.md +++ b/content/docs/api/schema-reference.md @@ -50,21 +50,36 @@ All schema types extend `BaseSchema`. These shared properties are available on e } ``` +One row per declared member, in declaration order, so the list can be checked against `BaseSchema` by reading the two side by side. + | Property | Type | Description | |----------|------|-------------| | `type` | `string` | **Required.** Component type identifier (e.g. `"page"`, `"form"`, `"table"`). | | `id` | `string` | Unique instance identifier. | | `name` | `string` | Component name, used for form fields and data binding. | -| `label` | `string` | Human-readable display label. | -| `description` | `string` | Help text or tooltip content. | +| `label` | `string \| I18nLabel` | Human-readable display label. `I18nLabel` is the spec's **inline locale map** (`string \| Record`, keyed by BCP-47 locale tag such as `en` or `zh-CN`), resolved against the display locale by `resolveI18nLabel`. | +| `description` | `string \| I18nLabel` | Help text or tooltip content. Same inline-locale-map vocabulary and resolver as `label`. | +| `placeholder` | `string` | Hint text for input components. | | `className` | `string` | Tailwind CSS utility classes. | +| `style` | `Record` | Inline CSS styles. Use sparingly — prefer `className`. | +| `data` | `any` | Arbitrary data attached to the node. `any` because the shape is defined by the consuming component rather than by `BaseSchema`. | +| `bind` | `string` | Data-scope path this node draws its rows or value from, resolved by `useDataScope()`. Honoured only by components that call it. | | `body` | `SchemaNode \| SchemaNode[]` | Child components rendered inside this component. | | `children` | `SchemaNode \| SchemaNode[]` | Alias for `body`. | -| `visible` / `visibleOn` | `boolean` / `string` | Control visibility. `visibleOn` accepts expression strings. | -| `hidden` / `hiddenOn` | `boolean` / `string` | Inverse of visible. | -| `disabled` / `disabledOn` | `boolean` / `string` | Control disabled state. | -| `testId` | `string` | Test identifier for automated testing. | -| `ariaLabel` | `string` | Accessibility label. | +| `visible` | `boolean \| string` | Visibility control. Accepts a boolean **or** a predicate expression string — the renderer evaluates this key rather than reading it as a boolean. | +| `visibleWhen` | `string` | Canonical conditional-visibility predicate (ADR-0089); the element is shown when it evaluates truthy. Evaluated **before** `visible` and `visibleOn`, and outranks both. | +| `visibleOn` | `string` | Expression for conditional visibility. **Deprecated** (ADR-0089) — use `visibleWhen`. | +| `hidden` | `boolean` | Inverse of `visible`. Boolean only — unlike `visible`, this key takes no expression. | +| `hiddenOn` | `string` | Expression for conditional hiding. | +| `disabled` | `boolean \| string` | Disabled state. Accepts a boolean **or** a predicate expression string, on the same evaluated path as `visible`. | +| `disabledOn` | `string` | Expression for conditional disabling. | +| `testId` | `string` | Test identifier, rendered as `data-testid`. | +| `ariaLabel` | `string \| KeyedI18nLabel` | Accessibility label, rendered as `aria-label`. `KeyedI18nLabel` is the **keyed** form (`{ key, defaultValue?, params? }`), resolved by `resolveKeyedI18nLabel` — **not** the `I18nLabel` that `label` and `description` carry. The two are structurally confusable and each returns nothing useful for the other's input. | + +Two things the table cannot show in a cell: + +- **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. +- **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. ---