Skip to content

feat(spec): declare the closed set of expression-bindable text keys (#9599) - #9729

Merged
os-elon merged 2 commits into
mainfrom
claude/issue-9599-expression-bindable-text-keys
Aug 18, 2026
Merged

feat(spec): declare the closed set of expression-bindable text keys (#9599)#9729
os-elon merged 2 commits into
mainfrom
claude/issue-9599-expression-bindable-text-keys

Conversation

@os-elon

Copy link
Copy Markdown
Collaborator

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 is Blocked-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/ui barrel (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) and ExpressionBindableTextKeySchema (Zod face).
  • EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT — the per-component carriage map, frozen at runtime:
    • statisticlabel, value, description
    • cardtitle, description
    • buttonlabel
  • expressionBindableTextKeysFor(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/main spec has no statistic/card/button literal anywhere under packages/spec/src/ui/ — spec's ComponentPropsMap covers page:* / record:* / element:* / object-* types only, and DashboardWidgetSchema deliberately refuses objectui-internal component nodes. 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-sha pin (82a9417) and re-verified byte-identical at objectui origin/main (6c68b13, 2026-08-18): data-display/statistic.tsx reads schema.label/schema.value/schema.description; layout/card.tsx reads schema.title/schema.description; form/button.tsx and action/action-button.tsx read schema.label. statistic therefore carries label and description alongside the motivating value — declaring only value would leave a bound statistic.label rendering 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-control value is 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. content is 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/react and @object-ui/types already 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 new ui/expression-bindable-text-keys page, claimed in the Visualization section next to component).
  • Consumer sweep: pnpm turbo run build --filter '...@objectstack/spec' (prefix form = downstream consumers) — 71/71 tasks green against the rebuilt spec dist.
  • Full derived gate union re-run at fee0ef5 (dispatch list + node scripts/pm/dispatch-gates.mjs re-derivation on the actual diff, which added the changeset family, docs family, and test-file convention gates): all 27 green, including check: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).
  • Liveness note: no liveness entries were demanded — the module adds no authorable property to any governed metadata-type schema (the gate is registry-rooted and the new exports are a renderer-contract vocabulary, not schema keys), so the gate stayed green with zero new rows; the dispatch's planned-entries assumption did not apply in this shape.

Changeset: minor (new public contract surface, purely additive).


Generated by Claude Code

…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
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests protocol:ui tooling labels Aug 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

8 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ✅

What this run could not see
  • 4 changed file(s) yielded no anchor (packages/spec/api-surface/ui.json, packages/spec/export-origins/ui.json, packages/spec/json-schema.manifest/ui.json, …) — pages documenting those are invisible to this run
  • 3 name(s) were too generic to anchor anything (single lowercase words)

Coarse fallback — 115 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 28684f081e0983b1f1752364ce69770e66922480packageMentionDocs.

Which tree this was computed on

This run read content/docs from d88c952729c01b7bb9ae3f55783c1e71c25685fa — the merge of head fee0ef51ac5cfdacbea494829fdfe25535464ac8 into base 28684f081e0983b1f1752364ce69770e66922480, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@os-elon
os-elon marked this pull request as ready for review August 18, 2026 17:44
@os-elon
os-elon enabled auto-merge August 18, 2026 17:44
@os-elon
os-elon added this pull request to the merge queueAug 18, 2026
Merged via the queue into main with commit 2b292ceAug 18, 2026
27 checks passed
@os-elon
os-elon deleted the claude/issue-9599-expression-bindable-text-keys branch August 18, 2026 18:20
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec: declare the closed set of expression-bindable text keys (title / label / value / description) — the contract half of objectui#4795 Direction 1

2 participants

@os-elon@claude