feat(spec): widen element:text.variant to the published nine — release 1 of 2, additive only - #19019
Conversation
…ive only
`element:text.variant` accepted `heading` / `subheading` / `body` / `caption`
and refused `h1`-`h6` and `overline` with `invalid_value`. objectui's text node
publishes nine values, so the seven it publishes and the spec refuses could not
be authored at all — the authoring gate was the refusing party.
The enum now declares the published nine plus the two spellings it has always
accepted: eleven members, seven newly accepted, nothing refused that was
accepted before. `.optional().default('body')` is kept deliberately, so an
absent `variant` still materialises `'body'` — absence is the one thing a
widening must not move.
Claude-Session: https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh
Co-authored-by: Claude <noreply@anthropic.com>
…iant` moves `api-surface-declarations/ui.txt` gains the seven newly accepted members on `ElementTextPropsSchema` and on `ComponentPropsMap['element:text']`; `content/docs/references/ui/component.mdx`'s property table widens with them. Both produced by `check:generated --fix`, which named exactly these two stale — `check:api-surface`, `check:authorable-surface` and the other 14 were already current, so nothing was regenerated on principle. Plus the changeset: `@objectstack/spec` minor, `Clause-②: yes (widening)`. Claude-Session: https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh Co-authored-by: Claude <noreply@anthropic.com>
…xt-variant-published-nine
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 136 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 0a373f69df093b0b6b509275ca83ed99f7859961 && git checkout 0a373f69df093b0b6b509275ca83ed99f7859961
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 64de4c5764db92b6263b9ef349efb2cdab93e668 3151b9467bd0be1e78a086a0fc820e8599894499 && git checkout -B drift-repro 64de4c5764db92b6263b9ef349efb2cdab93e668 && git merge --no-ff 3151b9467bd0be1e78a086a0fc820e8599894499
node scripts/docs-audit/affected-docs.mjs --json 64de4c5764db92b6263b9ef349efb2cdab93e668 |
Contract reviewServed-tier: 128/128 ① Derived judgmentsIsolated at-tier review; this seat serves below the tier constant and does not self-review. Tier verified by census of the reviewer's own transcript at 128 of 128 assistant turns — a subagent cannot self-attest, its This is a WIDENING, so the two things that had to hold were that nothing previously accepted becomes refused, and that ABSENCE does not move. Both re-derived independently of the implementing report:
Three doors measured, not assumed: the zod schema (above), the metadata authoring rule ( The declared-but-not-delivered window was checked against the ruling, not the report's characterisation. After this lands and before objectui's step 2, an author can write ② Semver level
③ Boundary flagsGoverned surface: 0 of 5 paths, derived by Implemented-by: VERDICT: PASS Non-blocking, carried and not fixed here: Generated by Claude Code |
…ed on the origin/main merge The merge of origin/main (b146102) routed four paths to the os-regen driver, which defers rather than text-merging: the three api-surface-declarations shards automation.txt, data.txt and ui.txt, and the generated reference page content/docs/references/ui/component.mdx. On every one of them the driver kept this branch's side and silently dropped main's, so step 2 of the sanctioned sequence restored main's side into the worktree and this commit re-derives all four from the merged tree. What each side contributed, now both present: ui.txt / component.mdx main's element:text.variant widening to the published nine (#19019), plus this branch's evaluated-slot narrowing data.txt main's transport no-transactions declaration (#18890) and the $orderby dual declaration (#19018), plus this branch's `source: string` narrowing on ConditionalValidationSchema and CrossFieldValidationSchema automation.txt main's structured-region pause/end refusal (#18688), plus this branch's narrowing The two MIXED, deliberately unrouted paths were hand-resolved by git's ordinary three-way merge and verified rather than eyeballed: registry.ts's hand-written remainder (generated regions stripped with the merge script's own awk) is byte-identical across base, both sides and the merge, and its line count is exactly additive (17142 + 121 + 74 = 17337), with both sides' migration entries present by id. component.zod.ts is additive too (3750 + 4 + 45 = 3799) and its single .superRefine() is untouched. Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude <noreply@anthropic.com>
Fixes #17108
Clause-②: yes (widening)
element:text.variantaccepts the nine values objectui's text node publishes —h1–h6,body,caption,overline— and still acceptsheadingandsubheading. Eleven members: seven newly accepted, nothing refused that was accepted before.Release 1 of 2 for the objectui#7450 convergence (director batch #71, 2026-09-07, maintainer verbatim 「其他同意」), split across two releases by the maintainer's decision of 2026-09-09, option B. ⛔ Nothing is retired here.
Landing points, established from evidence rather than assumed
packages/spec/src/ui/component.zod.tsz.enumonElementTextPropsSchema.variant, plus the docblock that records the ruling, the split and the defaulting decisiongit grep "'subheading'"returns one source declaration, at this filepackages/spec/src/ui/component.test.tspackages/spec/api-surface-declarations/ui.txtElementTextPropsSchemaand onComponentPropsMap['element:text']check:generatednamed it stale; regenerated bycheck:generated --fixcontent/docs/references/ui/component.mdxEnumcell.changeset/@objectstack/specminorpreview-samples.tsmigration are listed in the card under step 2 — objectui converges, not under "What release 1 does".git ls-filesfinds noblock-config, nopreview-samplesand noelements.tsxanywhere in this tree; all three are objectui files. Nothing was authored for them here.The defaulting decision, stated rather than inherited
.optional().default('body')is kept. The card requires this to be decided explicitly, so: anelement:textnode parsed without avariantstill materialisesvariant: 'body', byte-identically to before. Absence is the one thing a widening must not move. Theui:textside deliberately does not synthesisebodyfor an absentvariant(objectui#6942, protecting unannotated corpus nodes); that asymmetry is pre-existing, is not this card's to resolve, and is left exactly where it was. Removing the default would refuse nothing at the door but would change what every downstream reader sees for an absent key — a silent behaviour change wearing an additive changeset, which is what the split exists to prevent.Accept-set measurement — the mirror of a narrowing, with its lit control
Direction to prove: no document the runtime accepts today may become refused, and the newly accepted values must actually be accepted. Measured per value, not described.
Door 1 — the Zod schema (
ElementTextPropsSchema.safeParse, against the rebuiltdist):f347c793e)headingsubheadingbodycaption→ accepth1h2h3h4h5h6overline→ refuse,invalid_value"body""body"Accepted after: 11 of 11. Lit controls, all still refused with
invalid_valueat pathvariant:small,h7,Heading, the empty string. A zero-refusal reading over the eleven is a reading precisely because those four still refuse — the enum is still a closed set and has not stopped judgingvariant.Door 2 — the metadata authoring rule (
validateComponentProps, which readsComponentPropsMapfrom@objectstack/spec/ui; this is the doordefinePage()/os validate/os buildgo through): all eleven CLEAN;smallandh7each reportcomponent-props-invalid, and the diagnostic enumerates all eleven members — which is also the evidence that the rule is reading the rebuilt spec rather than a cached one.Door 3 — the TypeScript type, two legs against the rebuilt
.d.ts:variant: 'h1'type-checks, andvariant: 'small'is a type error (its@ts-expect-erroris consumed).tsc --noEmitexit 0.variant: 'h1'assignment under a@ts-expect-errormust be reported unused.tscexit 2,TS2578: Unused '@ts-expect-error' directive. Against the pre-change.d.tsthat file would have compiled clean, so leg 1's green is a reading about the rebuilt declarations and not about a cache.Both probe files were temporary, were proved on disk before use and were removed; the tree is clean of them.
There is a second authoring door, and this release does not move it.
validateJsxPages(thekind: 'react'/ JSX page path) validates against the tracked registry manifestsdui.manifest.json, which is objectui's registry declaration, not the spec's. Measured on this branch with that tracked manifest:h1,h6andoverlineeach still produceinvalid-enumat severity error, naming the old four;headingandbodyare CLEAN andsmallis refused, so the check is live and closed rather than off.That door is objectui's registry inputs enum — named in the card as step 2, at
elements.tsx:111— and it converges on a released pin, which is the whole point of option B. Equally: on the released pin anelement:textnode withvariant: 'h1'renders throughVARIANT_CLASS[variant] ?? VARIANT_CLASS.body, i.e. as body-styledp, until step 2 lands. ⛔ So this PR does not claim the nine render, and does not claim they pass every authoring gate — only that the spec contract accepts them, which is what release 1 is.Gates
Derived from the merge base with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, re-derived after theorigin/mainmerge (identical 107-command set both times), every exit code captured by redirecting to a file before any pipe.dispatch-gates --ranreconciles:107 derived famil(ies) accounted for — 107 run, 0 NOT-MEASURED, a derived zero.PREREQUISITE NOT MET(exit 3, plus one exit-1 refusal of the same class fromcheck:skill-examples) because the workspace was unbuilt. Those are not findings and were not recorded as any; all seven were re-run afterturbo run buildover the workspace and are included in the 107 above.pnpm --filter @objectstack/spec test— 489 files / 14221 tests passed.turbo run typecheckover the repo — 143/143 tasks successful.pnpm lint(eslint . --no-inline-config) — exit 0, whole repo, no narrowing claimed.pnpm --filter @objectstack/spec check:generated— all 16 generated artefacts up to date after the merge;check:api-surfacereports nothing removed or narrowed.origin/mainmerged throughbash scripts/pm/os-regen-merge.sh; no rebase, no force-push, nogen:schemain MERGE state. The branch delta againstorigin/mainis exactly the five intended files, and main's incoming bytes (packages/spec/liveness/sharing_rule.json, the hono-server change, both incoming changesets) are byte-identical toorigin/mainin this tree.Deliberately not here
heading/subheadingbecoming named refusals with migration hints. Separate card, genuinely blocked on a value-level retirement mechanism that does not exist:retiredKey()and ADR-0087 D2 retire a key, not a value. No such mechanism is built here.element:text's renderer, its inspector options, its i18n rows and theui:textside — objectui's, on the released pin.content/docs/releases/**— untouched.Acceptance notes
variant: 'subheading'—packages/platform-objects/src/pages/sys-user.page.ts(four) andexamples/app-showcase/src/ui/pages/page-variables.page.ts(one). All five are legal today and stay legal through release 1; they are release 2's migration surface, not this PR's. Noted, not filed — the release 2 card is the carrier.sdui.manifest.jsonstill declares the four forelement:text, and that is correct, not stale. It is objectui's registry dump, sha-pinned to.objectui-sha; it converges when the pin moves in step 2.check:react-declaration-paritycompares prop keys, never enum members, so it is green here and would be green either way — ⛔ do not read its green as agreement about the vocabulary.GOVERNED_SURFACESregister inscripts/pm/check-governed-merges.mjs: none of the five files is underdocs/adr/,.claude/,skills/,AGENTS.mdorCLAUDE.md.Claude-Session:+Co-authored-by: Claude), per AGENTS.md andcheck:commit-card-trailers, which passed on every push. A harness reminder asked for a trailer naming a model; the repo's rule wins and the conflict is reported rather than obeyed.Generated by Claude Code