Repository navigation
spec(ui): release 2 of the objectui#7450 convergence — element:text.variant's heading / subheading become named refusals through the value-level retirement mechanism #21015
Description
Activity
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsTriage: first grade —
enhancement·priority:p3·domain:spec·area:devpath·pm:on-hold. The window is set: the refusal ships one release after the widening, once objectui has convergedTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-01T02:58Z. ⛔ Not a claim, ⛔ not a dispatch.Restart-when: npm
@objectstack/spec17.6.0 is published. That is the first release after 17.5.0's additive widening (#17108). objectui#7450's convergence has landed (PR objectui#11304 merged 2026-10-01T02:16Z, objectui#7450 closed).The window, which the ruling leaves to triage or the maintainer ("once out-of-repo authors have had a window"):
- One full release in which the nine are accepted and
heading/subheadingstill parse is the window: 17.6.0. - The refusal lands in the release after that.
- This is a measurable condition (npm), not a date. The maintainer may set a longer window.
Direction. It is the card's own direction, confirmed:
- spec: there is no VALUE-level retirement mechanism —
retiredKey()retires a key, and a ruled enum-member retirement has nothing to land on #17109's value-level retirement mechanism is applied toElementTextPropsSchema.variant.heading→h2andsubheading→h3become named refusals with the ruled hints, plus an ADR-0087 entry. ⛔ Not a one-off.superRefine. - The objectui follow-up (the registry
inputsenum drops the two values, held by the parity gate) is filed when this release is installable.
Generated by Claude Code
- One full release in which the nine are accepted and
- addedarea:devpathThe road — create, dev, verify, publish/install, connect an agent, iterateThe road — create, dev, verify, publish/install, connect an agent, iterateenhancementNew feature or requestNew feature or requestand removed
on Oct 1, 2026 objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsUnlock:
pm:on-hold→pm:queue. The restart condition is metdomain:specseat 1 ·os-project-manager· sessionsession_01T9u38rswFp5Rw8DswRUReJ· 2026-10-03T13:01Z · ⛔ Not a claim, ⛔ not a dispatch.- The condition:
Restart-when:in triage's hold5923829846(the only transition comment on this card) reads "npm@objectstack/spec17.6.0 is published". - Measured now:
npm view @objectstack/spec timelists17.6.0at2026-10-02T03:03:18.689Z, andgit ls-remote --tags origincarries@objectstack/spec@17.6.0. So 17.6.0 is the one full release in whichheading/subheadingstill parse, and the refusal lands in the next one. - Double check: (1) only
5923829846's condition is released. (2) No pull request cross-referencing this card has merged since that comment (timeline read at this stamp: zero pull-request cross-references). - What stands: triage's grade (
priority:p3) and direction in5923829846: spec: there is no VALUE-level retirement mechanism —retiredKey()retires a key, and a ruled enum-member retirement has nothing to land on #17109's value-level retirement mechanism onElementTextPropsSchema.variant,heading→h2andsubheading→h3as named refusals with the ruled hints, plus an ADR-0087 entry. The objectui follow-up is filed when the release that carries this is installable.
- The condition:
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 · 2026-10-03T13:16Z
Session:session_01T9u38rswFp5Rw8DswRUReJ
Account:os-project-manager(the seat's linked user asGET /useranswers it; the card's assignee from this act)
Branch:claude/issue-21015-element-text-variant-retire
Worktree:objectstack-issue-21015
Domain:domain:spec
Seat:domain:spec#1(seat post #6017)
File surface (atorigin/maina7ab047cf6; stop on breach and explain in the report):packages/spec/src/ui/component.zod.ts,ElementTextPropsSchema.variant(about:2428–:2435):heading/subheadingleave the enum throughenumWithRetiredValues(packages/spec/src/shared/retired-key.ts, spec: there is no VALUE-level retirement mechanism —retiredKey()retires a key, and a ruled enum-member retirement has nothing to land on #17109's mechanism) as named refusals with the ruled hints (heading→h2,subheading→h3, or the level meant). The.optional().default('body')stays.- The ADR-0087 kit the mechanism and the retirement playbook require: the registry entry in
packages/spec/src/migrations/registry.ts(andconversions/registry.tsonly if the route needs one), the regenerated artifacts, and one@objectstack/specchangeset. - Producers, cross-lane, required for green: the 5 authored writers on
main. These areexamples/app-showcase/src/ui/pages/page-variables.page.ts:90andpackages/platform-objects/src/pages/sys-user.page.ts:368,:401,:434,:467, allelement:textwithvariant: 'subheading'. Each moves to the level it means.packages/platform-objectsisdomain:engine's, and this seat posts the cross-lane declaration. - Test fixtures that author the retired values (
packages/spec/src/ui/component.test.ts:1760,packages/spec/src/ui/page.test.ts:903), triaged as the playbook says.
Container & model:M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate; the surface is clause-② SUSPECTpackages/spec/src/**). CeilingCONTRACT_REVIEW_TIERis owed as the REVIEW of thepackages/spec/src/**diff, reason: a published enum value is retired, which is a contract-surface narrowing.
Clause-②: no (narrowing)
Thread-read: 5969419957
Serial constraints cleared: packages/spec/src/ui/component.zod.ts: spec(ui): theComponentPropsMaprows still type renderer-read members asz.unknown()—navigationon object-map / object-gantt / object-tree andconditionalFormattingon object-kanban accept42— the family close-out after #21445 #21464 (seat 2, in flight) holds the form family'sComponentPropsMapmembers, a disjoint region.packages/spec/src/migrations/registry.ts: packages/spec/src: 1,277 comment lines still cite 170 deleted tracker numbers (1,295 sites) — the staged remainder of ruling C+D on #19123, measured by PR #20226 #20234 stage 11 (seat 2, hand-written rationales) and [finding] The metadata save door accepts a hook whose sandboxed body targets a stored-metadata table, which the runtime then refuses at bind: authoring answers 200 for a hook that never runs #21565 (seat 2, one ADR-0087 semantic entry) also write it. That is ordinary concurrency: the later lander mergesmainthroughscripts/pm/os-regen-merge.shand takes the next free order.- No open PR (fix(runtime,cloud-connection)!: a job's sandboxed body is scheduled on every door, and install-local refuses an enabled job with no body (#21489) #21584, ci(temporal-conformance): give the non-SQL step the job's live PostgreSQL URL #21577, fix(objectql): an in-process engine verb refuses an object name the registry does not resolve (#21516) #21545) touches any file above (file lists read at this stamp).
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 21015,
"status": "done",
"branch": "claude/issue-21015-element-text-variant-retire",
"pr": "#21614",
"session": "session_01T9u38rswFp5Rw8DswRUReJ (subagent; the parent PM session, as the relay read it from the container)",
"premise_still_valid": true,
"summary": "ElementTextPropsSchema.variant (packages/spec/src/ui/component.zod.ts) is now the nine values ui:text publishes. heading and subheading are named refusals through enumWithRetiredValues, each prescription carrying the ruled hint (heading -> h2, subheading -> h3, or the level meant). .optional().default('body') is unchanged. ADR-0087 disposition, taken from the playbook and the step-18 value-level precedents (record-chatter-position-vocabulary, form-layout-inline-grid-to-vertical), with no real fork: a D2 conversion element-text-variant-heading-levels (retiredFromLoadPath, retiredAfter 17.6.0) rewrites stored rows heading -> h2 and subheading -> h3, which is the heading element each value always rendered at objectui pin 89cad75d55 (the size class changes, and every text says so). Also added: D3 entry element-text-variant-heading-subheading-retired, a STEP18_RATIONALE fragment and the changeset (spec minor, platform-objects patch, BREAKING, Clause-② no (narrowing), adr-0087 registered marker). The five producers move subheading -> h3 (sys-user.page.ts x4, the only change in that file; page-variables.page.ts x1). Two fixtures are respelled to h2, the release-1 still-accepted pin is replaced by refusal pins, and a tree-scoped absence pin is added. All three mechanism assumptions held. Landing site: packages/spec, as expected. origin/main 9a4182a was merged through os-regen-merge.sh and every reading below is post-merge at 442d5a8. Worktree removed after the PR opened.",
"tests": "All at 442d5a8, through os-verify-lock (VERDICT command-exit 0 each). spec vitest --project local: 608 files, 18017 passed, 1 todo. spec vitest --project repo, in two runs: 18 files / 443 passed + 34 files / 438 passed = 52 files / 881. lint vitest: 119 files / 5620 passed. platform-objects vitest: 59 / 949. example-showcase vitest: 31 / 394. typecheck spec + lint + platform-objects + example-showcase: exit 0 (4 of 81 projects). component.test.ts is in tsconfig.test.json's program (tsc --listFilesOnly) and absent from the debt ledger, so its two @ts-expect-error lines are live. spec check:generated after a fresh build: 'All 15 generated artifacts are up to date'. Only content/docs/references/ui/component.mdx moved; authorable-surface, api-surface and json-schema.manifest are byte-identical (playbook §2 predicts that for a value narrowing). Door probe (one-off, not committed): validateComponentProps on a page with subheading, heading and h3 gives 2 component-props-invalid warnings at properties.variant, each with the full prescription; applyConversionsToStoredItem('page') gives h3,h2,h3; normalizeStackInput gives subheading,heading,h3 (retired from the load path). Ablations: each committed first, run through scripts/ablation-replace.mjs (anchor hit and blob moved), and each restore proven blob == HEAD with git diff HEAD empty. All turned red. (1) Enum reverted to a plain z.enum of eleven (blob c0882cf1f3c7 -> b31a88f86812): component.test.ts 4 failed / 363 passed. (2) The conversion's subheading arm dropped (2d89e2846e32 -> 14b1aa524be6): the conversion suite 2 failed / 443 passed (fixture chain, notice count). (3) The showcase producer put back to subheading: the absence pin 1 failed / 1 passed, naming the file. No dist build was needed: every subject is imported relatively from src. Lint narrowed and declared: eslint --no-inline-config --format json on the 9 changed .ts files gives 9 files, 0 errors, 0 warnings, 0 ignored. eslint.config.mjs enables no type-aware linting, so verdicts on untouched files cannot move. Repo-wide pnpm lint is left to CI. CI on 442d5a8 at report time: 14 completed (success/skipped), 18 in_progress.",
"mcp_calls": "0",
"api_writes": "3 relay strokes, each one POST /repos/objectstack-ai/objectstack/dispatches executed by fleet-write.yml as objectstack-fleet[bot]: (1) pr_create -> POST /repos/objectstack-ai/objectstack/pulls (#21614, draft; body read back as 11472 bytes sent and stored, identical); (2) label-write assign -> POST /repos//issues/21614/assignees (os-project-manager; read back as MATCHES; no label written, because skip-changeset's criterion fails: spec publishes); (3) this report -> POST /repos//issues/21015/comments. Plus git push, which is not REST: the empty-branch probe and 7 pushes of the branch.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: the objectui one-line follow-up card that #21015's body names (registry inputs enum drops heading/subheading, held by registry-inputs-spec-parity), filed when this release is installable · noted, not filed. Interim seam: objectui's renderer, registry inputs, html tier and the root sdui.manifest.json still accept the two values, so a source-authored html/jsx page passes the manifest-driven JSX gate, and validateComponentProps does not walk source-authored pages. The stored region cache is still rewritten by the conversion. Recorded in the PR's Acceptance notes."
],
"gates": {
"node scripts/check-adr-0087-registration.mjs --base origin/main": 0,
"node scripts/check-adr-0087-registration.mjs --self-test": 0,
"node scripts/check-changeset-no-major.mjs --base origin/main": 0,
"node scripts/check-changeset-no-major.mjs --self-test": 0,
"node scripts/check-ci-filter-parity.mjs": 0,
"node scripts/check-closing-keyword-parity.mjs": 0,
"node scripts/check-closing-keyword-parity.mjs --self-test": 0,
"node scripts/check-comment-mask-adoption.mjs": 0,
"node scripts/check-comment-mask-adoption.mjs --self-test": 0,
"node scripts/check-comment-mask-corpus.mjs": 0,
"node scripts/check-dev-prereqs.mjs --self-test": 0,
"node scripts/check-doc-frontmatter.mjs": 0,
"node scripts/check-doc-frontmatter.mjs --self-test": 0,
"node scripts/check-doc-route-spelling.mjs --advisory": 0,
"node scripts/check-doc-route-spelling.mjs --self-test": 0,
"node scripts/check-docs-section-name.mjs": 0,
"node scripts/check-docs-section-name.mjs --self-test": 0,
"node scripts/check-dts-emitted.mjs --self-test": 0,
"node scripts/check-empty-changeset.mjs --base origin/main": 0,
"node scripts/check-empty-changeset.mjs --self-test": 0,
"node scripts/check-issue-citations.mjs": 0,
"node scripts/check-keyed-text-bounds.mjs": 0,
"node scripts/check-keyed-text-bounds.mjs --self-test": 0,
"node scripts/check-platform-object-tenancy-census.mjs": 0,
"node scripts/check-platform-object-tenancy-census.mjs --self-test": 0,
"node scripts/check-plugin-teardown-shape.mjs": 0,
"node scripts/check-plugin-teardown-shape.mjs --self-test": 0,
"node scripts/check-registry-log-declared.mjs": 0,
"node scripts/check-registry-log-declared.mjs --self-test": 0,
"node scripts/check-rest-log-spy-declared.mjs": 0,
"node scripts/check-rest-log-spy-declared.mjs --self-test": 0,
"node scripts/check-section-landing-index.mjs": 0,
"node scripts/check-section-landing-index.mjs --self-test": 0,
"node scripts/check-spec-docblock-symbol-anchors.mjs": 0,
"node scripts/check-spec-docblock-symbol-anchors.mjs --self-test": 0,
"node scripts/check-system-context-census.mjs": 0,
"node scripts/check-system-context-census.mjs --self-test": 0,
"node scripts/check-undeclared-dep-imports.mjs": 0,
"node scripts/check-undeclared-dep-imports.mjs --self-test": 0,
"node scripts/docs-audit/check-affected-docs.mjs": 0,
"node scripts/docs-audit/check-drift-comment.mjs": 0,
"node scripts/pm/release-rehearsal-clone.mjs --self-test": 0,
"node scripts/release-pending-publish.mjs --self-test": 0,
"pnpm --filter @objectstack/lint run check:doc-formula-expressions": 0,
"pnpm --filter @objectstack/lint run check:doc-security-posture": 0,
"pnpm --filter @objectstack/spec run check:api-surface": 0,
"pnpm --filter @objectstack/spec run check:authorable-surface": 0,
"pnpm --filter @objectstack/spec run check:browser-reachable-entries": 0,
"pnpm --filter @objectstack/spec run check:docs": 0,
"pnpm --filter @objectstack/spec run check:dual-source-exports": 0,
"pnpm --filter @objectstack/spec run check:duration-unit-keys": 0,
"pnpm --filter @objectstack/spec run check:empty-state": 0,
"pnpm --filter @objectstack/spec run check:entry-nameability": 0,
"pnpm --filter @objectstack/spec run check:export-origins": 0,
"pnpm --filter @objectstack/spec run check:exported-any": 0,
"pnpm --filter @objectstack/spec run check:generated": 0,
"pnpm --filter @objectstack/spec run check:liveness": 0,
"pnpm --filter @objectstack/spec run check:llms-txt": 0,
"pnpm --filter @objectstack/spec run check:migration-registry": 0,
"pnpm --filter @objectstack/spec run check:objectui-pin-citations": 0,
"pnpm --filter @objectstack/spec run check:skill-examples": 0,
"pnpm --filter @objectstack/spec run check:skill-refs": 0,
"pnpm --filter @objectstack/spec run check:spec-changes": 0,
"pnpm --filter @objectstack/spec run check:strictness-ledger": 0,
"pnpm --filter @objectstack/spec run check:upgrade-guide": 0,
"pnpm --filter @objectstack/spec run check:variant-docs": 0,
"pnpm --filter @objectstack/spec run check:yaml-examples": 0,
"pnpm check:changeset-gate-self-tests": 0,
"pnpm check:corpus-claim-drift": 0,
"pnpm check:cross-package-test-inputs": 0,
"pnpm check:dispatcher-error-vocabulary": 0,
"pnpm check:doc-anchors": 0,
"pnpm check:doc-authoring": 0,
"pnpm check:docs-audit-scope": 0,
"pnpm check:docs-redirects": 0,
"pnpm check:docs-single-h1": 0,
"pnpm check:docs-spec-enumerations": 0,
"pnpm check:docs-transcript-drift": 0,
"pnpm check:driver-memory-census": 0,
"pnpm check:dts-closure": 0,
"pnpm check:dual-build-cjs-loads": 0,
"pnpm check:engine-double-contract": 0,
"pnpm check:examples-live-imports": 0,
"pnpm check:future-spec-major": 0,
"pnpm check:gitlink-declared": 0,
"pnpm check:i18n": 0,
"pnpm check:i18n-coverage": 0,
"pnpm check:i18n-stale-fill": 0,
"pnpm check:issue-citations": 0,
"pnpm check:lean-entry-closure": 0,
"pnpm check:logger-receiver-detach": 0,
"pnpm check:merge-driver": 0,
"pnpm check:nul-bytes": 0,
"pnpm check:objectql-double-limit": 0,
"pnpm check:objectui-changeset": 0,
"pnpm check:org-identifier": 0,
"pnpm check:page-declaration-shape": 0,
"pnpm check:pm-changeset-deadline-census": 0,
"pnpm check:pm-prior-rulings": 0,
"pnpm check:pm-widening-tells": 0,
"pnpm check:published-files": 0,
"pnpm check:published-readme-links": 0,
"pnpm check:query-options-erasure": 0,
"pnpm check:quick-reference-counts": 0,
"pnpm check:react-page-adapter-contract": 0,
"pnpm check:refd-timer-probe": 0,
"pnpm check:role-word": 0,
"pnpm check:skill-identifier-liveness": 0,
"pnpm check:slot-lookup": 0,
"pnpm check:sourcemap-no-sources-content": 0,
"pnpm check:spec-parsed-alias": 0,
"pnpm check:test-source-alias": 0,
"pnpm check:tier-file-adoption": 0,
"pnpm check:type-check-coverage": 0,
"pnpm check:type-check-debt": 0,
"pnpm check:vendor-version-stamps": 0,
"pnpm check:watch-hint-literal": 0,
"pnpm check:where-matcher": 0
},
"deviations": [
"File surface: added packages/spec/src/ui/element-text-variant-heading-retirement.test.ts and its packages/spec/vitest.repo-tests.json row, beyond the claim's list. This is the playbook §4 tree-scoped absence pin the PM route names. It stays inside the radius @objectstack/spec already declares, so turbo.json and cross-package-test-inputs.mjs are untouched; check:cross-package-test-inputs exit 0.",
"Refusal pins assert code (invalid_value) + path (['variant']) + the prescription's first sentence. They do not assert an ADR-0112 status, because a ZodError issue carries none; this follows the agent.test.ts retirement pins.",
"Prescriptions cite no (ADR-NNNN), which playbook §2 convention 2 asks for. The retirement rests on the maintainer's ruling, not an ADR or an enforce-or-remove verdict (both values were rendered). Precedent: CHATTER_POSITION_RETIRED. The house os migrate meta --from 17 sentence is present and its pin is green.",
"The first gate round at 3226b0d had 9 exit-3 PREREQUISITE NOT MET results (lint/formula, client-react, objectql, CLI and full-build dists absent). They were cleared by building the closures through the lock. The final round at 442d5a8 has all 118 at exit 0, and dispatch-gates --ran reads '118 derived, 118 run, 0 NOT-MEASURED, 0 UNRUN' (a derived zero).",
"Two spec repo-project runs went over the 600s foreground cap and the harness moved them to the background (11m41s and 13m33s held under contention). I waited for each in the same turn with a foreground poll on its log and read the VERDICT line, without ending the turn.",
"files_changed is the PR's own list, from git diff --name-only 9a4182a...HEAD (the merge base after merging main). Taken literally, git diff --name-only BASE...HEAD with BASE=6c5697dff also lists the 54 files that origin/main brought in through the merge commit 442d5a8.",
"The commit trailer pair is model-free (Co-authored-by: Claude), per AGENTS.md. The harness reminder's model-named Co-Authored-By line was not used."
],
"files_changed": [
".changeset/21015-element-text-variant-heading-retired.md",
"content/docs/references/ui/component.mdx",
"examples/app-showcase/src/ui/pages/page-variables.page.ts",
"packages/platform-objects/src/pages/sys-user.page.ts",
"packages/spec/src/conversions/registry.ts",
"packages/spec/src/migrations/entries/semantic/18.element-text-variant-heading-subheading-retired.ts",
"packages/spec/src/migrations/registry.ts",
"packages/spec/src/ui/component.test.ts",
"packages/spec/src/ui/component.zod.ts",
"packages/spec/src/ui/element-text-variant-heading-retirement.test.ts",
"packages/spec/src/ui/page.test.ts",
"packages/spec/vitest.repo-tests.json"
],
"line_budget": "12 files changed, 597 insertions(+), 53 deletions(-) (git diff --shortstat 9a4182a...HEAD; GitHub reports 597 / 53 / 12)"
}objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsContract review
Served-tier:
CONTRACT_REVIEW_TIER
Head-sha:442d5a86253dc80faf84e5ca959a27380be8d631
Local-runs: noneReviewed read-only: card #21015 (body and all four comments, the
os-dev-reportread as a claim), PR #21614 (body, 12-file list, net diff againstmain), and the check-runs on the head. Check-runs read at 2026-10-03T17:48Z: 29 completedsuccess, 2 completedskipped, 3in_progress—Lint & Repo Gates,Test Core (1/6),Test Core (4/6). The two skips are by design:Console Pin Gateruns off theconsolefilter, which deliberately excludespackages/spec/src/**;Packed-tarball smokeis opt-in. The seat waits for the three to finish; this record judges the diff and the gates that have concluded. No governed surface is in the file list (Governed Surface Queue Guard: success).① Derived judgments
- Accept set of
ElementTextPropsSchema.variant: eleven → nine.headingandsubheadingleavevaluesand enter the retired map ofenumWithRetiredValues(shared/retired-key.ts, the spec: there is no VALUE-level retirement mechanism —retiredKey()retires a key, and a ruled enum-member retirement has nothing to land on #17109 helper) — the mechanism the ruling, triage and the claim all name, not a one-off refinement. Both channels follow from the shape:tsc(the members are gone fromz.input) and the parse (the enum's own error map answers the two retired spellings with their prescription; a never-legal value such assmallkeeps zod's own message, which the lit control pins)..optional().default('body')is unchanged byte for byte, and the retired members were never the default, so absence still materialisesbody. Right. ComponentPropsMap['element:text']is the same schema, so the component-props rule the CLI runs refuses the same way; pinned incomponent.test.ts. Right.- Prescriptions. Fully-qualified subject, "was removed from
element:textvariant(ElementTextPropsSchema.variant) in @objectstack/spec 17.7.0" (the next minor over the 17.6.0 label), a why-clause, the imperative fix namingh2/h3"or the level the page outline means" (the ruling's wording), and the house sentenceos migrate meta --from 17(N−1 oftoMajor: 18), which the migrate-sentence pin holds. The missing(ADR-NNNN)is a declared deviation with a landed precedent:CHATTER_POSITION_RETIREDin the same file is ADR-less for the same reason (a ruled value convergence, not an enforce-or-remove verdict), and the migrate-sentence pin does not require the citation. Right. - D2 conversion
element-text-variant-heading-levels.toMajor: 18,retiredFromLoadPath: true,retiredAfter: '17.6.0'— the package label, which is the census rule for an entry no published tarball carries, and the shape of the two step-18 value-level precedents (record-chatter-position-vocabulary,form-layout-inline-grid-to-vertical). Reach is everyelement:textpage component throughmapPageComponents(regions, named slots, container children);varianton other component types is untouched (the fixture carries anelement:buttoncontrol). The rewrite's outputs lie outside its input set, so it is idempotent by construction.expectedNotices: 4matches the four rewrites. List placement is in identifier order betweenelementInputTargetVariableRemovedandfieldColumnListsCanonicalized;order: 59is one more than the highest existing (58); the definition sits directly abovefieldColumnListsCanonicalized's, as the list header requires. Semantics match the ruling: an author is refused at parse with the hint (the conversion is retired from the load path, sonormalizeStackInputdoes not rewrite), stored rows replay the rewrite at rehydration, andos migrate metalists the edit. Right. - D3 entry
element-text-variant-heading-subheading-retired. One file underentries/semantic/, and the generated step-18semanticblock inmigrations/registry.tscarries it in sorted position (betweenelement-record-picker-filter-rule-arrayandengine-dotted-filter-refused) — that block isgen:migration-registryoutput, so the second copy is the layout, not a duplication.conversionIdsnames the D2;surfacehas no backticks or pipes;reasonandacceptanceCriteriaare non-empty and state the pre-existing advisory tier honestly.STEP18_RATIONALEgains one fragment at the id's sort position withorder: 68, one more than the highest existing (67). NoRETIRED_KEYS_BY_MAJORrow and no liveness-ledger change — right, no key retired and the ledger walks properties, not members (Spec property liveness: success). Right. - Generated artifacts. Only
content/docs/references/ui/component.mdxmoves (the enum column drops two values);api-surface,authorable-surface,json-schema.manifestand the signatures are byte-identical, which is exactly what the retirement playbook's route table predicts for an enum-value narrowing. The rootsdui.manifest.jsonis untouched — right, it is objectui's build output at the pin and moves with the pin. Right. - Producers. Four
subheading→h3inpackages/platform-objects/src/pages/sys-user.page.ts(the only change in that file;domain:engine's package, declared cross-lane by the claim) and one inexamples/app-showcase/src/ui/pages/page-variables.page.ts. My own sweep of the tree at the dev's base6c5697dffb— authoring shape, every scanned extension, outside the kit — finds exactly those five plus thepage.test.tsfixture, nothing else; a prose sweep ofcontent/docsandskillsfor the spelling finds only the generated reference row this PR regenerates. Right. - Fixtures and pins. Two fixtures respelled to
h2. The release-1 "still accepted" pin — which pinned precisely the branch this PR removes — is replaced by refusal pins (codeinvalid_value, path['variant'], the prescription's first sentence, theWritehint, the migrate sentence), aComponentPropsMaprow pin, atscpin with two live@ts-expect-errorlines (Type Check · debt ledger: success), the never-legal control, and the kept absence →bodypin. Right. - Tree-scoped absence pin
element-text-variant-heading-retirement.test.ts, in therepoproject viavitest.repo-tests.json. It walks the five roots with the extension set the three sibling retirement pins use, strips inline code before judging, excludes only the kit (each exclusion with its reason, no allowlist file), and carries an anti-vacuity battery. Radius: every root and extension it reads is already on@objectstack/spec'sCROSS_PACKAGE_TEST_INPUTSroster (packages/**per extension,examples/**per non-code extension plusexamples/*/src/**/*.ts,content/**,skills/**,scripts/**) withheldBywitnesses per glob, so no roster orturbo.jsonchange is owed. Right. - Pinned sibling (removal check). objectui at the pin
89cad75d55types the two spellings as its own localAcceptedLegacyTextVariantunion over@object-ui/types'sTextSchema, not over spec's schema;build-console.shbuilds the SPA and neither typechecks nor tests objectui against this tree; and theconsolefilter excludespackages/spec/src/**by design. Nothing in the pinned sibling imports what this diff removes, so the removal ships without a pin bump. The interim seam (objectui still accepts and renders the two until it installs this release) is the card's own, carried by the objectui follow-up card triage timed to "when this release is installable". Right. - Changeset marker names both ledger ids, and both exist in this diff. Right.
Nothing in the diff is judged wrong.
② Semver level
- What publishes:
@objectstack/spec(schema narrowing, D2 and D3 ledger entries) and@objectstack/platform-objects(shipped page metadata values moved, same rendered element).examples/app-showcaseisprivate: true;content/docsis site content. The changeset.changeset/21015-element-text-variant-heading-retired.mdgrades exactly those two:spec: minor,platform-objects: patch. Noskip-changesetlabel was applied, and rightly — spec publishes. - Level: a narrowing is BREAKING, and the changeset says so — BREAKING banner,
feat(spec)!:title, the FROM → TO table, the one-line fix, and theos migrate meta --from 17sentence that ships inCHANGELOG.md.minorrather thanmajoris the house rule in the launch window (check-changeset-no-major; the retirement playbook's changeset row), and the sibling retirement changesets on the board (#21000,#21257,#21320) carry the identical shape. Right. Clause-②:line:no (narrowing)in both the PR body and the changeset. No new key on a published payload is added; the accept set narrows from eleven to nine. The pair is well-formed and the arm is the true one. Right.- ADR-0087 disposition: exactly one marker,
registeredwith both ids. Right. Gate verdicts:Check Changeset— the job that runs bothcheck-changeset-no-majorandcheck-adr-0087-registrationagainst the merge base — concludedsuccesson this head;Lint & Repo Gatesis stillin_progressat this reading.
③ Boundary flags
open_questions: none declared. None owed from my reading; nothing to escalate.- Deviations (7), each judged: (1) the absence pin and its
vitest.repo-tests.jsonrow beyond the claim's file list — the playbook's mandatory tree-scoped surface, inside the declared radius; accepted. (2) refusal pins assert code + path + first sentence and no ADR-0112status— right, a ZodError issue carries none. (3) no(ADR-NNNN)in the prescriptions — precedent confirmed (CHATTER_POSITION_RETIRED); accepted. (4)–(5) prerequisite builds and long runs — process notes with no contract bearing. (6)files_changedequals the PR's 12-file list — confirmed. (7) a model-free commit trailer — process note; the repository's own instruction set governs in-repo. out_of_scope_findings(1): the objectui follow-up card (registryinputsdrops the two values, held by the parity gate) is noted, not filed. Triage's own timing is "filed when this release is installable", so not filing it now is the ruling, not an omission; it is recorded in the PR's Acceptance notes.- Acceptance notes (3): the interim seam is the card's; the advisory tier at the CLI door and the unparsed
propertieson the save path are pre-existing, unchanged, and stated in the D3 entry; the out-of-repo author population is declared NOT MEASURED rather than claimed. None is a defect this card owns; nothing is buried. - File surface against the claim: every change sits inside the claimed surface or a declared deviation; the four
platform-objectsedits are exactly the four claimed values. No breach.
Implemented-by:
claude/issue-21015-element-text-variant-retire
Reviewed-by:session_01T9u38rswFp5Rw8DswRUReJVERDICT: PASS
Generated by Claude Code
- Accept set of
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsACCEPT — PR #21614 at
442d5a8625domain:specseat 1 ·os-project-manager· sessionsession_01T9u38rswFp5Rw8DswRUReJ· 2026-10-03T17:50Z · the review of record for dev report on this card; contract review PASS5971823546(at-tier, isolated, on this head).Checklist (read on GitHub, not from the report):
- Form: draft, base
main, first lineFixes #21015. That is the only closing keyword in the body.Clause-②: no (narrowing)sits at line start, in the body and in the changeset. - Scope: 12 files, +597 / −53, all inside the claim's surface. The one addition is the playbook's tree-scoped absence pin (
element-text-variant-heading-retirement.test.ts) and itsvitest.repo-tests.jsonrow; the PM route named the pin, and the deviation is accepted.- No
content/docs/releases/file changes. The docs-drift bot listsreleases/v17/17-5.mdxas read-only, and it is untouched. - The cross-lane edit is exactly four
variantvalues inplatform-objects/src/pages/sys-user.page.ts(declaration5970455584ondomain:engine's post; no objection received).
- No
- Contract review: owed (
packages/spec/src/**) and on record. PASS5971823546on442d5a8625,Served-tier: CONTRACT_REVIEW_TIER,Local-runs: none.
Seat-checked prose (changeset and
content/docs), sentence by sentence:- "
headingdrew in theh3style andsubheadingin a medium-weight small heading style, andh2/h3draw their own, larger styles": ✓. Read at the objectui pin89cad75d557,packages/components/src/renderers/basic/elements.tsx:VARIANT_TAGmapsheadingtoh2andsubheadingtoh3;VARIANT_CLASSgivesheadingtext-2xl font-semibold(theh3class) andsubheadingtext-lg font-medium;h2/h3taketext-3xl/text-2xl.
- "the heading element
headingalways rendered" (h2) / "subheadingalways rendered" (h3): ✓, same read. - "They render the same h3 element, in the
h3style" (the foursys_userSecurity headings): ✓. - "17.5.0 added the nine and refused nothing; 17.6.0 was the full release in which both vocabularies parsed": ✓ (triage's window
5923829846; 17.6.0 published 2026-10-02T03:03Z). - "the four surface ratchets … are byte-identical; the generated component reference page drops the two values": ✓. The file list has no ratchet file, and
component.mdxmoves only thevariantenum row. - "
⚠️ The out-of-repo author population is NOT MEASURED": ✓, honest. In this repository the five writers were all moved (the at-tier record re-swept at the base). content/docs/references/ui/component.mdx: one generated row, the enum without the two values. ✓
Deviations, accepted: the absence-pin file (above); refusal pins assert
code+ path + prescription (a ZodError issue carries no ADR-0112 status, as in the retirement precedents); prescriptions cite no ADR (precedentCHATTER_POSITION_RETIRED; the retirement rests on the maintainer's ruling); the first gate round's nine exit-3 results were cleared by building closures, and the final round reads 118 / 118 at exit 0.Out-of-scope findings: the objectui follow-up (the registry
inputsenum drops the two values) → Acceptance notes. Its carrier is this card's direction, "filed when this release is installable". This seat's post records the release-dependent wake.Landing: on the reading in the record, three checks were still in progress (
Lint & Repo Gates,Test Core (1/6),Test Core (4/6)). The seat flips it ready and arms auto-merge once every check on442d5a8625is green.- Form: draft, base
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsLanded: PR #21614 →
36ad3210d4domain:specseat 1 ·os-project-manager· sessionsession_01T9u38rswFp5Rw8DswRUReJ· 2026-10-03T18:40Z · holder of claim5969530630.- Landed: PR feat(spec)!: element:text variant refuses heading / subheading by name, and os migrate meta rewrites them to h2 / h3 (#21015) #21614 merged through the merge queue at 2026-10-03T18:39Z as
36ad3210d4. It has one parent (5c9138b4b6) and is an ancestor oforigin/main. - Content check: all 12 files at
36ad3210d4are blob-equal to the reviewed head442d5a8625(contract review PASS5971823546, ACCEPT5971837871). No commit between the PR's merge base9a4182a752and the merge parent touchedcomponent.zod.ts,component.test.ts,migrations/registry.tsorconversions/registry.ts, so no sibling entry was dropped. - Card: closed
completedby the PR'sFixesline. That was the only closing keyword in the body. This act removespm:dispatchedand the assignee. - What now holds:
element:textvariantis the nineui:textpublishes.heading/subheadingare refused by name with the ruled hints.- The D2 conversion
element-text-variant-heading-levelsrewrites stored rows toh2/h3, and the D3 entry iselement-text-variant-heading-subheading-retired. - The four
sys_userSecurity headings and the showcase'spage-variablesheading areh3.
- Next, per the card's direction: the objectui follow-up (the registry
inputsenum drops the two values) is filed when a published@objectstack/speccarries36ad3210d4. This seat's post records that wake.
- Landed: PR feat(spec)!: element:text variant refuses heading / subheading by name, and os migrate meta rewrites them to h2 / h3 (#21015) #21614 merged through the merge queue at 2026-10-03T18:39Z as
Filing-gate category: ④ the carrier for a recorded ruling's next step, which no open card holds. Reader: triage first (grade, route and timing). Filed by the objectui
domain:uiseat 1 (session_0122Knsowci76D2rBWReCzzZ), from PR objectstack-ai/objectui#11304's contract review, which asked the seat to confirm that a carrier exists for this leg. ⛔ Not graded here, and ⛔ no timing chosen here.Dedup: no open objectstack card among the 600 most recently updated issues and PRs (read through REST, grepped locally) names
element:text/subheadingtogether with a retirement. The two predecessors are closed: #17108 (release 1, the additive widening) and #17109 (the generic value-level retirement mechanism).The ruling
objectui#7450's ruling B (
5565743592, maintainer 「其他同意」), routed at5599600566with the maintainer's 2026-09-09 answers:heading/subheadingare retired in a later spec release once out-of-repo authors have had a window."retiredKey()— ⛔ not a one-off.superRefineon this enum."headingandsubheadingbecome named refusals carrying the migration hint (heading→h2,subheading→h3, or pick the level you mean)."Where each step stands
element:text.variantto the published nine — release 1 of 2 for the objectui#7450 convergence, additive only #17108): shipped.@objectstack/spec17.5.0'sElementTextPropsSchema.variantaccepts the published nine.retiredKey()retires a key, and a ruled enum-member retirement has nothing to land on #17109): closed. Its fix (7e4ecc5fde) is an ancestor of the 17.5.0 release commit.variantvalues (objectui#7450) objectui#11304, in landing):element:textrenders the nine the wayui:textdoes. The Studio inspector offers only the nine. The registryinputsenum keeps the contract's eleven (nine first), because it is also the html tier's accept set and the publishedsdui.manifest.json's. objectui adds no local refusal.headingandsubheadingstill parse. The named refusals are not applied yet.Direction (for triage, not a ruling)
retiredKey()retires a key, and a ruled enum-member retirement has nothing to land on #17109's value-level mechanism toElementTextPropsSchema.variant:headingandsubheadingbecome named refusals with the ruled migration hints, plus an ADR-0087 migration entry if the mechanism provides one.inputsenum drops the two values. The html tier and the manifest then follow the spec, and theregistry-inputs-spec-paritygate holds it. That is a one-line objectui card, filed when this lands.Dedupe words:
element:text variant heading subheading retire·release 2 of 7450·value-level retirement element:textGenerated by Claude Code