|
| 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 | + |
0 commit comments