Skip to content

Commit 2b292ce

Browse files
os-elonclaude
andauthored
feat(spec): declare the closed set of expression-bindable text keys (#9599) (#9729)
* feat(spec): declare the closed set of expression-bindable text keys (#9599) objectui#4795 Direction 1, spec half, per the 2026-08-17 ruling's terms (closed enum, never inferred) reopened by the 2026-08-18 ruling. Exports the four-key closed vocabulary, its Zod face, the measured per-component carriage map (statistic/card/button) and the mechanical per-type lookup the objectui evaluation memo consumes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016D9wdJR14KKCxz1WgdAzcw * docs: bump UI Protocol page count in quick-reference (16 -> 17) (#9599) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016D9wdJR14KKCxz1WgdAzcw --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent b6c7690 commit 2b292ce

13 files changed

Lines changed: 414 additions & 6 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
Declare the closed vocabulary of expression-bindable text keys (objectui#4795 Direction 1, spec half — #9599).
6+
7+
`@objectstack/spec/ui` now exports `EXPRESSION_BINDABLE_TEXT_KEYS` (`title` / `label` / `value` / `description` — a closed enum per the 2026-08-17 maintainer ruling's terms, reopened 2026-08-18), the `ExpressionBindableTextKey` type and `ExpressionBindableTextKeySchema` Zod face, the per-component carriage map `EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT` (`statistic`: `label`/`value`/`description`, `card`: `title`/`description`, `button`: `label` — measured against the objectui renderers' read points at the `.objectui-sha` pin), and the runtime lookup `expressionBindableTextKeysFor(componentType)`. These are consumed by the objectui SchemaRenderer evaluation memo (the downstream half, riding objectui#4795) so the set of top-level text keys the memo evaluates is declared here once, never inferred or hard-coded as a twin list. Purely additive — no existing schema accepts or rejects anything new in this release.

content/docs/getting-started/quick-reference.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ Core business logic and data modeling schemas.
4646
| **[Postgres Driver](/docs/references/data/driver-postgres)** | `driver/postgres.zod.ts` | PostgresConfig | PostgreSQL configuration |
4747
| **[Mongo Driver](/docs/references/data/driver-mongo)** | `driver/mongo.zod.ts` | MongoConfig | MongoDB configuration |
4848

49-
## UI Protocol (11 of 16 schemas)
49+
## UI Protocol (11 of 17 schemas)
5050

5151
Presentation layer - views, forms, dashboards, and themes.
5252

content/docs/references/index.mdx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Protocol Reference
3-
description: Every schema published by @objectstack/spec — 1587 schemas across 14 protocol modules
3+
description: Every schema published by @objectstack/spec — 1588 schemas across 14 protocol modules
44
---
55

66
{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
@@ -32,8 +32,8 @@ counts are sums of the rows they head. Regenerate with
3232
| [Shared Protocol](/docs/references/shared) | 8 | 31 | Primitives used across every protocol — identifiers, HTTP, expressions, error maps, enums. |
3333
| [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. |
3434
| [System Protocol](/docs/references/system) | 36 | 287 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
35-
| [UI Protocol](/docs/references/ui) | 16 | 161 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
36-
| **Total** | **198** | **1587** | 14 protocol modules |
35+
| [UI Protocol](/docs/references/ui) | 17 | 162 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
36+
| **Total** | **199** | **1588** | 14 protocol modules |
3737

3838
---
3939

@@ -364,7 +364,7 @@ The runtime environment — logging, jobs, cache, metrics, notifications, i18n a
364364

365365
## UI Protocol
366366

367-
**Source:** `packages/spec/src/ui/` · **Import:** `@objectstack/spec/ui` · **16 pages, 161 schemas**
367+
**Source:** `packages/spec/src/ui/` · **Import:** `@objectstack/spec/ui` · **17 pages, 162 schemas**
368368

369369
Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer.
370370

@@ -378,6 +378,7 @@ Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI lay
378378
| [`component.zod.ts`](/docs/references/ui/component) | `AIChatWindowProps`, `ElementButtonProps`, `ElementFilterProps`, `ElementFormProps`, `ElementImageProps`, `ElementMetadataViewerProps`, `ElementNumberProps`, `ElementRecordPickerProps`, `ElementTextInputProps`, `ElementTextProps`, `ObjectCalendarProps`, `ObjectFormProps`, `ObjectGridProps`, `ObjectKanbanProps`, `ObjectMasterDetailFormProps`, `ObjectMetricProps`, `PageAccordionProps`, `PageCardProps`, `PageContainerProps`, `PageHeaderProps`, `PageTabsProps`, `RecordActivityProps`, `RecordAlertAction`, `RecordAlertProps`, `RecordChatterProps`, `RecordDetailsProps`, `RecordHighlightsField`, `RecordHighlightsProps`, `RecordHistoryProps`, `RecordPathProps`, `RecordQuickActionsProps`, `RecordReferenceRailProps`, `RecordRelatedListProps`, `ReferenceRailEntry` |
379379
| [`dashboard.zod.ts`](/docs/references/ui/dashboard) | `Dashboard`, `DashboardHeader`, `DashboardHeaderAction`, `DashboardWidget`, `DashboardWidgetOptions`, `GlobalFilter`, `GlobalFilterOptionsFrom`, `WidgetActionType`, `WidgetColorVariant` |
380380
| [`dataset.zod.ts`](/docs/references/ui/dataset) | `Dataset`, `DatasetDimension`, `DatasetMeasure`, `DerivedMeasureOp` |
381+
| [`expression-bindable-text-keys.zod.ts`](/docs/references/ui/expression-bindable-text-keys) | `ExpressionBindableTextKey` |
381382
| [`i18n.zod.ts`](/docs/references/ui/i18n) | `AriaProps`, `I18nLabel`, `InlineLocaleMap` |
382383
| [`notification.zod.ts`](/docs/references/ui/notification) | `NotificationPosition`, `NotificationSeverity`, `NotificationType` |
383384
| [`page.zod.ts`](/docs/references/ui/page) | `ElementDataSource`, `InterfacePageConfig`, `Page`, `PageComponent`, `PageComponentType`, `PageRegion`, `PageType`, `PageVariable` |
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
---
2+
title: Expression Bindable Text Keys
3+
description: Expression Bindable Text Keys protocol schemas
4+
---
5+
6+
{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
7+
8+
Expression-bindable text keys — the CLOSED vocabulary of top-level text keys
9+
a SchemaRenderer evaluation memo evaluates, and the per-component carriage
10+
map that says which component types carry which of them.
11+
12+
## Provenance (not re-litigable here)
13+
14+
objectui#4795 measured the hole: apart from `content`, no top-level text key
15+
on a rendered SDUI node was BOTH evaluated by the renderer's expression memo
16+
AND read back by the component renderer — `statistic.value: '${data.n}'`
17+
rendered the literal `${data.n}`, and the `props`-envelope workaround
18+
rendered blank. The 2026-08-17 maintainer ruling on that card deferred the
19+
fix (Direction 1) behind a restart condition and pre-defined its terms,
20+
verbatim: *"the key set is declared in `@objectstack/spec`/types as a closed
21+
enum, never inferred"*. On 2026-08-18 the maintainer ruled the restart
22+
condition met by product intent (a metadata-authored live dashboard is a
23+
basic requirement), reopening Direction 1 on those terms. This module is the
24+
spec half (objectstack#9599); the objectui half extends the evaluation memo
25+
to CONSUME these exports (it rides objectui#4795) instead of hard-coding a
26+
twin list.
27+
28+
Related, and deliberately out of this module's reach:
29+
30+
- ⛔ Direction 2 (merging the `props` envelope into the node) is
31+
permanently rejected (same 08-17 ruling) — nothing here re-legalizes the
32+
envelope shape the objectui#4786 teaching rewrite retired.
33+
- `content` is NOT a member. It already has its own evaluation leg in the
34+
memo and its own read-back contract; adding it here would give one key
35+
two declared evaluation paths.
36+
- The `properties` / `props` config bags are evaluated per-value by their
37+
own memo legs (objectui#4799 / #5122) — this vocabulary is only about
38+
keys authored at the NODE'S TOP LEVEL.
39+
40+
## The contract
41+
42+
For a component type with a row in
43+
`EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT`, the renderer's
44+
evaluation memo evaluates exactly the listed keys (when the authored value
45+
is an expression-bearing string); every other key stays inert text. For a
46+
component type with NO row, the answer is the empty set — closed and
47+
mechanically answerable in both directions, never inferred from what a
48+
renderer happens to read.
49+
50+
## Why these rows (measured, not inferred)
51+
52+
Rows are a RECORD of what each component renderer already reads back from
53+
the node's top level, measured at the objectui pin (`.objectui-sha`
54+
`82a9417`, re-verified identical at objectui `origin/main` `6c68b13` on
55+
2026-08-18) — declaring a key a renderer does not read back would recreate
56+
the evaluated-but-blank half of the objectui#4795 table:
57+
58+
| type | renderer read points (top level, within the closed set) |
59+
|-------------|----------------------------------------------------------|
60+
| `statistic` | `schema.label`, `schema.value`, `schema.description` (`data-display/statistic.tsx`) |
61+
| `card` | `schema.title`, `schema.description` (`layout/card.tsx`) |
62+
| `button` | `schema.label` (`form/button.tsx`, `action/action-button.tsx`) |
63+
64+
These are the measured motivating cases from objectui#4795 (dashboard
65+
workhorses). Other registered renderers also read keys from this closed set
66+
at the top level (`alert`/`empty`/`dialog` `title`+`description`, `badge`
67+
`label`, form inputs' `value`/`label`, …) — those rows are deliberately NOT
68+
declared yet: form-control `value` is interactive state rather than display
69+
text, and each row is an accept-surface widening that should arrive with its
70+
own measurement, not ride this one (startup scope discipline). Adding a row
71+
is additive and spec-first; do it here, never as a renderer-side inference.
72+
73+
<Callout type="info">
74+
**Source:** `packages/spec/src/ui/expression-bindable-text-keys.zod.ts`
75+
</Callout>
76+
77+
## TypeScript Usage
78+
79+
```typescript
80+
import { ExpressionBindableTextKeySchema } from '@objectstack/spec/ui';
81+
import type { ExpressionBindableTextKey } from '@objectstack/spec/ui';
82+
83+
// Validate data
84+
const result = ExpressionBindableTextKeySchema.parse(data);
85+
```
86+
87+
---
88+
89+
## ExpressionBindableTextKey
90+
91+
One of the closed set of expression-bindable text keys — the top-level node keys a SchemaRenderer evaluation memo may evaluate (objectui#4795 Direction 1; carriage per component type is EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT).
92+
93+
### Allowed Values
94+
95+
* `title`
96+
* `label`
97+
* `value`
98+
* `description`
99+
100+
101+
---
102+

content/docs/references/ui/index.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ This section contains all protocol schemas for the ui layer of ObjectStack.
1414
<Card href="/docs/references/ui/component" title="Component" description="Source: packages/spec/src/ui/component.zod.ts" />
1515
<Card href="/docs/references/ui/dashboard" title="Dashboard" description="Source: packages/spec/src/ui/dashboard.zod.ts" />
1616
<Card href="/docs/references/ui/dataset" title="Dataset" description="Source: packages/spec/src/ui/dataset.zod.ts" />
17+
<Card href="/docs/references/ui/expression-bindable-text-keys" title="Expression Bindable Text Keys" description="Source: packages/spec/src/ui/expression-bindable-text-keys.zod.ts" />
1718
<Card href="/docs/references/ui/i18n" title="I18n" description="Source: packages/spec/src/ui/i18n.zod.ts" />
1819
<Card href="/docs/references/ui/notification" title="Notification" description="Source: packages/spec/src/ui/notification.zod.ts" />
1920
<Card href="/docs/references/ui/page" title="Page" description="Source: packages/spec/src/ui/page.zod.ts" />

content/docs/references/ui/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"component",
1313
"dashboard",
1414
"dataset",
15+
"expression-bindable-text-keys",
1516
"report",
1617
"---Interaction & Layout---",
1718
"responsive",

packages/spec/api-surface/ui.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@
135135
"DateRangePreset (type)",
136136
"DerivedMeasureOp (const)",
137137
"DerivedMeasureOpValue (type)",
138+
"EXPRESSION_BINDABLE_TEXT_KEYS (const)",
139+
"EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT (const)",
138140
"ElementButtonPropsSchema (const)",
139141
"ElementDataSource (type)",
140142
"ElementDataSourceSchema (const)",
@@ -150,6 +152,8 @@
150152
"ElementTextPropsSchema (const)",
151153
"ExpandViewResult (interface)",
152154
"ExpandedViewItem (interface)",
155+
"ExpressionBindableTextKey (type)",
156+
"ExpressionBindableTextKeySchema (const)",
153157
"FieldWidgetProps (type)",
154158
"FieldWidgetPropsParsed (type)",
155159
"FieldWidgetPropsSchema (const)",
@@ -422,8 +426,10 @@
422426
"diagnoseViewMetadata (function)",
423427
"expandViewContainer (function)",
424428
"expandViewContainerWithDiagnostics (function)",
429+
"expressionBindableTextKeysFor (function)",
425430
"isActionParamValuePresent (function)",
426431
"isAggregatedViewContainer (function)",
432+
"isExpressionBindableTextKey (function)",
427433
"isRecordContextBlockType (function)",
428434
"isViewContainerShaped (function)",
429435
"normalizeFilterOperator (function)",

packages/spec/export-origins/ui.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@
135135
"DateRangePreset": "src/data/date-range-presets.ts#DateRangePreset (type)",
136136
"DerivedMeasureOp": "src/ui/dataset.zod.ts#DerivedMeasureOp (const)",
137137
"DerivedMeasureOpValue": "src/ui/dataset.zod.ts#DerivedMeasureOpValue (type)",
138+
"EXPRESSION_BINDABLE_TEXT_KEYS": "src/ui/expression-bindable-text-keys.zod.ts#EXPRESSION_BINDABLE_TEXT_KEYS (const)",
139+
"EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT": "src/ui/expression-bindable-text-keys.zod.ts#EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT (const)",
138140
"ElementButtonPropsSchema": "src/ui/component.zod.ts#ElementButtonPropsSchema (const)",
139141
"ElementDataSource": "src/ui/page.zod.ts#ElementDataSource (type)",
140142
"ElementDataSourceSchema": "src/ui/page.zod.ts#ElementDataSourceSchema (const)",
@@ -150,6 +152,8 @@
150152
"ElementTextPropsSchema": "src/ui/component.zod.ts#ElementTextPropsSchema (const)",
151153
"ExpandViewResult": "src/ui/view.zod.ts#ExpandViewResult (interface)",
152154
"ExpandedViewItem": "src/ui/view.zod.ts#ExpandedViewItem (interface)",
155+
"ExpressionBindableTextKey": "src/ui/expression-bindable-text-keys.zod.ts#ExpressionBindableTextKey (type)",
156+
"ExpressionBindableTextKeySchema": "src/ui/expression-bindable-text-keys.zod.ts#ExpressionBindableTextKeySchema (const)",
153157
"FieldWidgetProps": "src/ui/widget.zod.ts#FieldWidgetProps (type)",
154158
"FieldWidgetPropsParsed": "src/ui/widget.zod.ts#FieldWidgetPropsParsed (type)",
155159
"FieldWidgetPropsSchema": "src/ui/widget.zod.ts#FieldWidgetPropsSchema (const)",
@@ -422,8 +426,10 @@
422426
"diagnoseViewMetadata": "src/ui/view.zod.ts#diagnoseViewMetadata (function)",
423427
"expandViewContainer": "src/ui/view.zod.ts#expandViewContainer (function)",
424428
"expandViewContainerWithDiagnostics": "src/ui/view.zod.ts#expandViewContainerWithDiagnostics (function)",
429+
"expressionBindableTextKeysFor": "src/ui/expression-bindable-text-keys.zod.ts#expressionBindableTextKeysFor (function)",
425430
"isActionParamValuePresent": "src/ui/action-params.zod.ts#isActionParamValuePresent (function)",
426431
"isAggregatedViewContainer": "src/ui/view.zod.ts#isAggregatedViewContainer (function)",
432+
"isExpressionBindableTextKey": "src/ui/expression-bindable-text-keys.zod.ts#isExpressionBindableTextKey (function)",
427433
"isRecordContextBlockType": "src/ui/react-blocks.ts#isRecordContextBlockType (function)",
428434
"isViewContainerShaped": "src/ui/assembled-views.zod.ts#isViewContainerShaped (function)",
429435
"normalizeFilterOperator": "src/ui/view.zod.ts#normalizeFilterOperator (function)",

packages/spec/json-schema.manifest/ui.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"ui/ElementRecordPickerProps",
6161
"ui/ElementTextInputProps",
6262
"ui/ElementTextProps",
63+
"ui/ExpressionBindableTextKey",
6364
"ui/FormButtonConfig",
6465
"ui/FormField",
6566
"ui/FormFieldPublicPicker",

packages/spec/scripts/build-docs.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,12 @@ const SECTION_GROUPS: Record<string, Array<{ section: string; pages: string[] }>
544544
// #5697), and a reader who found the action declaration should find what a
545545
// body receives in the same section rather than under "More".
546546
{ section: 'Apps & Navigation', pages: ['app', 'page', 'view', 'action', 'action-params'] },
547-
{ section: 'Visualization', pages: ['chart', 'dashboard', 'dataset', 'report', 'widget', 'component'] },
547+
// `expression-bindable-text-keys` sits beside `component` deliberately: it
548+
// is the SDUI rendering contract's evaluation vocabulary (objectui#4795
549+
// Direction 1 — which top-level text keys the renderer's expression memo
550+
// evaluates, per component type), and a reader who found the component
551+
// prop schemas should find it in the same section rather than under "More".
552+
{ section: 'Visualization', pages: ['chart', 'dashboard', 'dataset', 'report', 'widget', 'component', 'expression-bindable-text-keys'] },
548553
// `animation` / `dnd` / `keyboard` / `touch` / `offline` left this section at
549554
// #4988: the five `ui/` interaction config modules were retired whole
550555
// (ADR-0049 — no carrier key, nothing parsed them), and their generated

0 commit comments

Comments
 (0)