Uh oh!
There was an error while loading. Please reload this page.
feat(spec): declare the closed set of expression-bindable text keys (#9599) - #9729
Conversation
…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
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016D9wdJR14KKCxz1WgdAzcw
📓 Docs Drift Check8 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ✅ What this run could not see
Coarse fallback — 115 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin d88c952729c01b7bb9ae3f55783c1e71c25685fa && git checkout d88c952729c01b7bb9ae3f55783c1e71c25685fa
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 28684f081e0983b1f1752364ce69770e66922480 fee0ef51ac5cfdacbea494829fdfe25535464ac8 && git checkout -B drift-repro 28684f081e0983b1f1752364ce69770e66922480 && git merge --no-ff fee0ef51ac5cfdacbea494829fdfe25535464ac8
node scripts/docs-audit/affected-docs.mjs --json 28684f081e0983b1f1752364ce69770e66922480 |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#9599
The spec half of objectui#4795 Direction 1, per the 2026-08-17 ruling's pre-defined terms (verbatim: "the key set is declared in
@objectstack/spec/types as a closed enum, never inferred"), reopened by the 2026-08-18 maintainer ruling. The objectui evaluation-memo half is downstream (it rides objectui#4795, which isBlocked-by:the card this PR closes) — objectui#4795 is NOT addressed here and remains open. Direction 2 (props-envelope merge) stays permanently rejected and is untouched; Direction 3's render-time diagnostic (objectui PR #5129) is unaffected.What is declared
New module
packages/spec/src/ui/expression-bindable-text-keys.zod.ts, exported from the@objectstack/spec/uibarrel (minimal appended region, per the #9392 shared-touch note):EXPRESSION_BINDABLE_TEXT_KEYS— the closed vocabulary, exactly the four ruled keys:title,label,value,description. Runtime-readable constant, not a type-only declaration.ExpressionBindableTextKey(type) andExpressionBindableTextKeySchema(Zod face).EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT— the per-component carriage map, frozen at runtime:statistic→label,value,descriptioncard→title,descriptionbutton→labelexpressionBindableTextKeysFor(componentType)— the mechanical per-type answer the objectui memo consumes instead of hard-coding a twin list. Unlisted types answer the frozen empty set (that IS the contract for them — closed, never inferred), and the lookup is own-property-guarded so prototype-chain names (constructor,toString, …) arriving as author-controlled type strings cannot answer with junk.isExpressionBindableTextKey(key)— membership predicate.Design decisions and their evidence
PM mechanism assumption 1 confirmed:
origin/mainspec has nostatistic/card/buttonliteral anywhere underpackages/spec/src/ui/— spec'sComponentPropsMapcoverspage:*/record:*/element:*/object-*types only, andDashboardWidgetSchemadeliberately refuses objectui-internalcomponentnodes. So the deliverable is the new exported structure (closed enum + component→keys map), exactly the fallback shape the dispatch anticipated.Carriage rows are measured, not inferred. Each row records what that component's renderer already reads back at the node's top level, measured at the
.objectui-shapin (82a9417) and re-verified byte-identical at objectuiorigin/main(6c68b13, 2026-08-18):data-display/statistic.tsxreadsschema.label/schema.value/schema.description;layout/card.tsxreadsschema.title/schema.description;form/button.tsxandaction/action-button.tsxreadschema.label.statistictherefore carrieslabelanddescriptionalongside the motivatingvalue— declaring onlyvaluewould leave a boundstatistic.labelrendering a raw literal and tripping Direction 3's diagnostic with no working top-level channel.Deliberately NOT declared (recorded in the module header): the wider measured surface — 41 renderer files read keys from the closed set at the top level (
alert/empty/dialogtitle+description,badgelabel, form controls'value/label, …). Form-controlvalueis interactive state rather than display text, and each row is its own accept-surface widening that should arrive with its own measurement (startup scope discipline). Rows are additive and spec-first.contentis excluded on purpose — it has its own evaluation leg in the memo, and one key must not have two declared evaluation paths.Consumption path verified: objectui's
@object-ui/reactand@object-ui/typesalready depend on@objectstack/spec^17.0.0, and the new exports were resolved through Node's own ESM resolver against the built dist (the exact path the downstream memo will use) — keys, map, lookup and Zod face all answer correctly.Verification (all at head
fee0ef5)pnpm --filter @objectstack/spec test— 413 files / 10972 tests passed (includes the new 9-test pin file: closed-enum membership both directions, measured rows, frozen shapes, empty-set answers, prototype-chain guard).pnpm --filter @objectstack/spec typecheck— clean (incl. scripts + test-layer debt ledger, no drift).pnpm --filter @objectstack/spec check:generated— all artifacts current (regenerated:api-surface/ui.json,export-origins/ui.json,json-schema.manifest/ui.json, reference docs incl. the newui/expression-bindable-text-keyspage, claimed in the Visualization section next tocomponent).pnpm turbo run build --filter '...@objectstack/spec'(prefix form = downstream consumers) — 71/71 tasks green against the rebuilt spec dist.fee0ef5(dispatch list +node scripts/pm/dispatch-gates.mjsre-derivation on the actual diff, which added the changeset family, docs family, and test-file convention gates): all 27 green, includingcheck:liveness,check:strictness-ledger,check:type-check-debt --re-measure(no ratchet drift),check:quick-reference-counts(the one gate the re-derivation caught that the dispatch list did not: the new reference page moved the UI category page count 16 → 17; heading updated).planned-entries assumption did not apply in this shape.Changeset:
minor(new public contract surface, purely additive).Generated by Claude Code