Skip to content

feat(spec): widen element:text.variant to the published nine — release 1 of 2, additive only - #19019

Merged
os-elon-musk merged 3 commits into
mainfrom
claude/issue-17108-text-variant-published-nine
Sep 18, 2026
Merged

os-elon-musk merged 3 commits into
mainfrom
claude/issue-17108-text-variant-published-nine

Conversation

@os-elon-musk

Copy link
Copy Markdown
Collaborator

Fixes #17108

Clause-②: yes (widening)

element:text.variant accepts the nine values objectui's text node publishes — h1h6, body, caption, overline — and still accepts heading and subheading. 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

Path What moved How it was established
packages/spec/src/ui/component.zod.ts the z.enum on ElementTextPropsSchema.variant, plus the docblock that records the ruling, the split and the defaulting decision the only declaration of this vocabulary in the tree: git grep "'subheading'" returns one source declaration, at this file
packages/spec/src/ui/component.test.ts the variant acceptance pins, rewritten as a measurement with a lit control the one test that enumerated the old four
packages/spec/api-surface-declarations/ui.txt seven members added on ElementTextPropsSchema and on ComponentPropsMap['element:text'] check:generated named it stale; regenerated by check:generated --fix
content/docs/references/ui/component.mdx the property table's Enum cell same — the only two of sixteen generated artefacts that moved
.changeset/ one entry, @objectstack/spec minor AGENTS.md post-task step 3

⚠️ Three landing points named in the dispatch are not in this repository and are not release 1's. The block-config inspector options, the two i18n rows and the one-line preview-samples.ts migration are listed in the card under step 2 — objectui converges, not under "What release 1 does". git ls-files finds no block-config, no preview-samples and no elements.tsx anywhere 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: an element:text node parsed without a variant still materialises variant: 'body', byte-identically to before. Absence is the one thing a widening must not move. The ui:text side deliberately does not synthesise body for an absent variant (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 rebuilt dist):

Before (at f347c793e) After
heading subheading body caption → accept all four → accept
h1 h2 h3 h4 h5 h6 overline → refuse, invalid_value all seven → accept
absent → accept, materialises "body" absent → accept, materialises "body"

Accepted after: 11 of 11. Lit controls, all still refused with invalid_value at path variant: 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 judging variant.

Door 2 — the metadata authoring rule (validateComponentProps, which reads ComponentPropsMap from @objectstack/spec/ui; this is the door definePage() / os validate / os build go through): all eleven CLEAN; small and h7 each report component-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:

  • leg 1 — variant: 'h1' type-checks, and variant: 'small' is a type error (its @ts-expect-error is consumed). tsc --noEmit exit 0.
  • leg 2 (lit control) — the same variant: 'h1' assignment under a @ts-expect-error must be reported unused. tsc exit 2, TS2578: Unused '@ts-expect-error' directive. Against the pre-change .d.ts that 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.

⚠️ Where the widening reaches, and where it does not — measured, not argued

There is a second authoring door, and this release does not move it. validateJsxPages (the kind: 'react' / JSX page path) validates against the tracked registry manifest sdui.manifest.json, which is objectui's registry declaration, not the spec's. Measured on this branch with that tracked manifest: h1, h6 and overline each still produce invalid-enum at severity error, naming the old four; heading and body are CLEAN and small is 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 an element:text node with variant: 'h1' renders through VARIANT_CLASS[variant] ?? VARIANT_CLASS.body, i.e. as body-styled p, 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 the origin/main merge (identical 107-command set both times), every exit code captured by redirecting to a file before any pipe.

  • 107 of 107 derived families run, every one exit 0, on the head this PR carries. dispatch-gates --ran reconciles: 107 derived famil(ies) accounted for — 107 run, 0 NOT-MEASURED, a derived zero.
  • Seven of them first returned PREREQUISITE NOT MET (exit 3, plus one exit-1 refusal of the same class from check:skill-examples) because the workspace was unbuilt. Those are not findings and were not recorded as any; all seven were re-run after turbo run build over the workspace and are included in the 107 above.
  • pnpm --filter @objectstack/spec test — 489 files / 14221 tests passed. turbo run typecheck over 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-surface reports nothing removed or narrowed.
  • origin/main merged through bash scripts/pm/os-regen-merge.sh; no rebase, no force-push, no gen:schema in MERGE state. The branch delta against origin/main is 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 to origin/main in this tree.

Deliberately not here

  • Release 2heading / subheading becoming 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 the ui:text side — objectui's, on the released pin.
  • ⛔ objectui#6942's absence semantics.
  • content/docs/releases/** — untouched.

Acceptance notes

  • Five in-repo nodes author variant: 'subheading'packages/platform-objects/src/pages/sys-user.page.ts (four) and examples/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.json still declares the four for element: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-parity compares 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.
  • No governed surface is touched. Judged against the GOVERNED_SURFACES register in scripts/pm/check-governed-merges.mjs: none of the five files is under docs/adr/, .claude/, skills/, AGENTS.md or CLAUDE.md.
  • Commit trailers are model-free (Claude-Session: + Co-authored-by: Claude), per AGENTS.md and check: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

…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>
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Sep 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️ 1 changed file(s) yielded no anchor (packages/spec/api-surface-declarations/ui.txt), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/api-surface-declarations/ui.txt) — pages documenting those are invisible to this run
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 136 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 64de4c5764db92b6263b9ef349efb2cdab93e668packageMentionDocs.

Which tree this was computed on

This run read content/docs from 0a373f69df093b0b6b509275ca83ed99f7859961 — the merge of head 3151b9467bd0be1e78a086a0fc820e8599894499 into base 64de4c5764db92b6263b9ef349efb2cdab93e668, 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 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

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

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: 128/128 CONTRACT_REVIEW_TIER
Head-sha: 3151b9467bd0be1e78a086a0fc820e8599894499

① Derived judgments

Isolated 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 get_session reads the parent.

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:

  • Accept set. At head 11 of 11 accept; at base 4 of 11, with h1h6 and overline refused as invalid_value@variant. The 11-accept reading is lit by four same-subject controls — small, h7, Heading and the empty string all still REFUSE on the same tree, so the enum is still a closed set rather than a field that stopped judging.
  • Absence. safeParse({content:'Test'}) materialises {"content":"Test","variant":"body","align":"left"} byte-identically at base and head. Lit control: a scratch copy with .default('body') changed to .default('h1') read variant:"h1" through the same probe, so the instrument moves when the default moves. This is the reading that matters most for a widening — absence is the one thing it must not touch, and it did not.

Three doors measured, not assumed: the zod schema (above), the metadata authoring rule (validateComponentProps reading the rebuilt dist — 11 of 11 clean, small/h7 refused with all eleven enumerated, which is itself the evidence it read the rebuilt spec and not a cache), and TypeScript (two-leg probe: head leg 1 exit 0 and leg 2 TS2578 Unused; base leg 1 TS2322 and leg 2 exit 0 — both legs flip with the tree).

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 variant: h1, have it accepted, and see it rendered as body while the JSX door still refuses it. That is the state the maintainer ORDERED, not a capability declared and undelivered: objectui#7450 comment 5599600566 rules verbatim 「B — split across two releases. spec widens to the nine first (additive, nothing refused), objectui converges on that released pin, and heading/subheading are retired in a later spec release once out-of-repo authors have had a window.」 Prime Directive 10's corollary arm is satisfied — the carrier objectui#7450 is open and pm:blocked with a Restart-when: naming this card's landing plus the pin move. The JSX door was separately confirmed live and closed rather than switched off: h1/h6/overline each produce jsx-invalid-enum@error, heading/body clean, small refused, and an unknown-tag control fires.

② Semver level

@objectstack/spec minor with line-initial Clause-②: yes (widening) in both the PR body and the changeset, read by the fleet's own readClause2Line (controls: no declaration reads null, a mid-line spelling reads near-miss/inline-key). minor is the stated FLOOR for yes, not merely permitted. major is neither owed nor available: check-changeset-no-major.mjs exit 0. No ADR-0087 entry is owed — that gate is only a breaking changeset's business, and check-adr-0087-registration.mjs exit 0 reports this PR adds no declared-breaking changeset.

③ Boundary flags

Governed surface: 0 of 5 paths, derived by check-governed-merges.mjs --pr 19019, three-dot by construction. Release-owned content/docs/releases/: 0 of 5 delta paths, against a control of 16 tracked files there. Generated artefacts: a fresh build plus regeneration produced 0 differing files against the tracked tree for both api-surface-declarations/ and content/docs/references/, with the tracked-base-vs-head control differing for both — regeneration output, not hand edits. The os-regen merge is clean by the stronger reading: git diff-tree --cc -r names 0 paths, and main moved 0 paths under either os-regen register entry in the window, so the driver's silent-drop precondition was absent. Gate-weakening: none — test lines +47/−5 with no coverage removed (the four old values remain covered), zero .skip/.only/.todo/xit/skipIf in the whole delta against a lit control finding one elsewhere in the tree, and zero ledger, baseline, ratchet, workflow or gate-script paths in the delta.

Implemented-by: claude/issue-17108-text-variant-published-nine
Reviewed-by: session_019srGWGCBBCBHqcDoRZpQRh

VERDICT: PASS

Non-blocking, carried and not fixed here: check:skill-examples refuses an unbuilt tree with exit 1 rather than the prerequisite constant 3, so classifying it as prerequisite-not-met is message-based and the tooling cannot confirm that class from the code — correct in substance here (reproduced verbatim on a pristine unbuilt extract, recorded as NOT MEASURED, re-run to 0 after a build), but it belongs as a note on the dispatch-gates side. The objectui#7450 option-B decision exists on record as a PM seat's transcription rather than a maintainer-authored comment; uncontradicted, and acted on by triage, claim and dispatch alike. The generated reference page now lists eleven members with no note of the pin window, which the changeset covers with "No renderer changes here".


Generated by Claude Code

@os-elon-musk
os-elon-musk marked this pull request as ready for review September 18, 2026 12:22
@os-elon-musk
os-elon-musk added this pull request to the merge queue Sep 18, 2026
Merged via the queue into main with commit 2d235bc Sep 18, 2026
52 checks passed
@os-elon-musk
os-elon-musk deleted the claude/issue-17108-text-variant-published-nine branch September 18, 2026 13:05
os-litant pushed a commit that referenced this pull request Sep 18, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec(ui): widen element:text.variant to the published nine — release 1 of 2 for the objectui#7450 convergence, additive only

2 participants