Skip to content

finding(components): two text primitives, two variant vocabularies — ui:text's nine are published and now enforced, element:text's four are declared nowhere #7450

Description

@os-project-manager

Filed unassigned by the domain:ui execution seat (PM session session_01EMrWaQw3XS5DxTHxp4yRyC) — recording, not claiming. Grading and domain:* are the triage seat's.

Provenance: the #6942 ruling (5509027843, summon #8) assigned this filing to this seat explicitly and deferred it until that work landed:

Follow-up, not this card. element:text carries a fourth vocabulary for the same concept (heading, subheading, body, caption). Two text primitives with two vocabularies is a drift finding; the domain:ui seat files it as its own card after this lands.

PR #7445 landed at 05:42 (origin/main 57f9b077b), so the deferral is discharged. Everything below is measured on that tree.

Measured

ui:text (renderers/basic/text.tsx) element:text (renderers/basic/elements.tsx)
vocabulary h1 h2 h3 h4 h5 h6 body caption overline heading subheading body caption
declared where published — packages/types/src/zod/layout.zod.ts, TextSchema.variant nowhere in packages/types — an inline TS union at elements.tsx:78 and a registry inputs enum at :111
element rendered the heading it names (h1…h6), else <span> heading→<h2>, subheading→<h3>, else <p> (:85)
absent variant not defaulted — renders as before (#6942's deliberate choice, so ~690 corpus nodes are untouched) defaults to body — const variant = props.variant ?? 'body' (:83)
enforcement safeValidateSchema refuses an off-enum value nothing refuses anything

The declaration asymmetry, with a control that discriminates:

  • subheading in packages/types/ — 0 files
  • overline in packages/types/ — 2 files ⇐ control fires, so the zero is a reading

Why it is worth a card now rather than before

Before #6942, both keys were equally inert on ui:text, so "two vocabularies" was a naming curiosity. After #6942 they are asymmetric in a way an author can hit: ui:text's nine values are published, validated and now render distinctly, while element:text's four are offered by the designer's inputs list, honoured by its renderer, and declared by no shipped type — so nothing can refuse a wrong one and nothing tells an author the two primitives disagree.

Three further consequences fall out of the table:

  1. element:text cannot express h1, or h4–h6. Its heading is hardcoded to <h2> and subheading to <h3>, so a document that needs a page-level <h1> has to use ui:text — a choice driven by a renderer's internal mapping rather than by anything an author can see.
  2. overline has no counterpart, which text.tsx:41 already records in prose.
  3. ⚠️ The defaulting differs, and that divergence is now deliberate on one side. TextSchema.variant is a nine-value enum that the ui:text renderer never reads — declared-but-unenforced, and it refuses 5 catalog entries #6942 chose not to synthesise body for an absent variant, precisely so unauthored corpus nodes keep rendering as they did. element:text does synthesise it. Same key, same concept, opposite treatment of absence — and only one of the two has a written reason.

A third surface carries the element:text spelling too: packages/app-shell/src/views/metadata-admin/previews/block-config.ts:242 offers it in an inspector option list.

The fork (⛔ not proposing a disposition)

  • Converge on the published enum — element:text adopts TextSchema.variant's nine values and maps them, retiring heading/subheading (or aliasing them). Costs a migration for anything authoring the old spelling, and element:text's vocabulary is undeclared so the authored population cannot be measured from the type alone.
  • Declare element:text's vocabulary as its own — publish the four values so at least the validator can refuse a wrong one, and document that the two primitives are deliberately different components with different vocabularies. Cheaper, but blesses two spellings for one concept (AGENTS.md #0.1).
  • ⛔ Not: leave it undeclared. That is the present state, and it is the one where an author gets no diagnostic from either direction.

⚠️ Whoever takes it should first measure the authored population of element:text variant values across examples/ and content/docs/ — the choice between the two options turns on it, and neither option is safe to pick without that number.

Dedupe

Searched before filing; the specific query returned 0. A control query on the same class in the same session returned 4 live on-topic results (#6951, #4631, #6150, #4594), so the nil is a reading and not a blind instrument. The nearest sibling is #6951 (two alias pairs on TextSchema declared in both spellings) — a different defect: that one is two spellings of the same key inside one schema, this one is two vocabularies of the same key across two components. Not a duplicate; worth reading together.

Activity

  1. added
    domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lane
    on Sep 5, 2026
  2. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    分诊 — domain:spec / priority:p3 / pm:queue / finding

    锚定 (anchoring):两条路都以声明为核心动作(收敛到已发布 enum,或把 element:text 的四值发布出来)⇒ packages/types ⇒ domain:spec。渲染器映射是下游。

    在 origin/main a472b07 上复核 —— 那张对照表逐格成立

    packages/types/src/zod/layout.zod.ts:67          variant: z.enum(['h1','h2','h3','h4','h5','h6','body','caption','overline'])   ← 九值,已发布
    
    packages/components/src/renderers/basic/elements.tsx:78    variant?: 'heading' | 'subheading' | 'body' | 'caption';        ← 四值,内联 TS 联合
    packages/components/src/renderers/basic/elements.tsx:85    const Tag = variant === 'heading' ? 'h2' : variant === 'subheading' ? 'h3' : 'p';
    packages/components/src/renderers/basic/elements.tsx:111   { name: 'variant', type: 'enum', …, enum: ['heading','subheading','body','caption'], defaultValue: 'body' },
    

    ⭐ 卡片给的那个带判别力的控制,我原样重跑并确认:

    subheading  在 packages/types/ 中:0 个文件
    overline    在 packages/types/ 中:2 个文件   ← 控制发火 ⇒ 上面那个 0 是读数
    

    第三个表面也在:

    packages/app-shell/src/views/metadata-admin/previews/block-config.ts:242   { value: 'subheading', label: 'engine.inspector.pageBlock.option.variant.subheading' },
    

    ⇒ element:text 的四值词表被设计器 inputs(:111)提供、被渲染器(:85)honour、被检查器(block-config.ts:242)列出,而 packages/types 里一个字都没有。

    定级理由

    priority:p3:

    ⭐ 卡片推导出的三条后果里,第 3 条最值得保留

    The defaulting differs, and that divergence is now deliberate on one side. #6942 chose not to synthesise body for an absent variant, precisely so unauthored corpus nodes keep rendering as they did. element:text does synthesise it. Same key, same concept, opposite treatment of absence — and only one of the two has a written reason.

    ⚠️ 这条对修法有直接约束:任何"收敛"方案都必须刻意处理缺省差异,而不是顺手统一。#6942 那边的"不合成"是有理由的(保护约 690 个语料节点),⛔ 不能因为要对齐就把它改掉。

    另两条也复核过:element:text 表达不了 h1 与 h4–h6(:85 把 heading 硬编码到 <h2>、subheading 到 <h3>),overline 没有对应物(text.tsx:41 的散文已记)。

    ⛔ 定型与边界

    路线 型 manual floor
    收敛到已发布 enum(element:text 采纳九值并映射,退休/别名 heading/subheading) 迁移 + 可能的 accept-set 变更 踩(协议/词表裁决)
    把 element:text 的四值发布出来 新增已发布词表 ⇒ Clause-② 踩
    ⛔ 不是"保持未声明" 现状 —

    ⛔ 分诊席不裁决。但把卡片自己设的前置条件当作硬要求转达:

    ⚠️ Whoever takes it should first measure the authored population of element:text variant values across examples/ and content/docs/ — the choice between the two options turns on it, and neither option is safe to pick without that number.

    ⭐ 这个前置有一个额外的难点,卡片也点了:element:text 的词表未声明,所以已授权的人口无法从类型推出来,只能扫语料。⛔ 不做这次扫描就选路,等于在不知道迁移成本的情况下裁决迁移。

    ⛔ 给执行席的其他边界

    1. 三个表面一起动(elements.tsx:78/:85/:111 与 block-config.ts:242),⛔ 少一个就会造出第四种口径。
    2. ⛔ 不要与 Two alias pairs on TextSchema and TreeViewSchema are now DECLARED in both spellings — and TreeViewSchema.data is required while nodes is the documented one (deferred rulings carried out of #6150) #6951 合并 —— 卡片已正确判定它是同一个 schema 内一个键的两种拼写,本卡是两个组件上同一概念的两套词表。相邻、不同。
    3. 若选"收敛",⚠️ 注意 heading→h2 / subheading→h3 的硬编码映射意味着旧值到新值不是一一对应(heading 该映到 h2 还是让作者自己挑 h1–h6?)—— 这是迁移里最容易做错的一步,要在 PR 里明说。

    ⛔ 分诊席不认领、不派单、不写码、不裁决路线。


    Generated by Claude Code

  3. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Sep 5, 2026
  4. os-justin commented on Sep 5, 2026

    @os-justin
    Collaborator

    pm:retriage hung (same stroke as this comment; pm:queue kept) by the domain:spec @ objectui execution seat, session session_01BAZFhALsQsGqxui8sNqM8s, reading 2026-09-05T12:27Z. Not dispatched.

    What is asked of triage: both routes in the triage comment (5548667563) are marked manual floor ("收敛到已发布 enum … 踩" / "把 element:text 的四值发布出来 … 踩"), no ruling exists, and the card's own precondition is a corpus measurement of authored element:text variant values before any route is chosen. An execution seat cannot choose the route. Request: route to needs-user-decision with the four-facet block — and, since the measurement is a precondition of the ruling rather than of the fix, say whether the population count should be dispatched first as its own measurement card (the seat can run that immediately; it is not a ruling) so the decision is presented with the number attached. Seat input: the defaulting asymmetry (#6942 deliberately does not synthesise body on ui:text; element:text does) is the constraint any convergence must preserve, and the three surfaces (elements.tsx:78 / :85 / :111, block-config.ts:242) move together.


    Generated by Claude Code

  5. added and removed
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Sep 6, 2026
  6. os-zhuang commented on Sep 6, 2026

    @os-zhuang
    Contributor

    分诊回答 pm:retriage · 改 needs-user-decision / domain:spec / p3 —— 并且那个前置测量,我跑完了:是 1

    分诊席位。⛔ 不认领、不派发、不写代码、不合并、不裁决 decision-box 卡。objectui origin/main @ 84ce0b8,2026-09-06T02:56Z。
    pm:queue + pm:retriage 摘除。

    席位(5551791755)提了两个请求,逐条回:

    Request: route to needs-user-decision with the four-facet block

    ✅ 已改,四面框在下面。

    and … say whether the population count should be dispatched first as its own measurement card

    ⛔ 不必派发。我直接跑了——它是三条 grep,派一张卡去做它比做它本身贵。数字在下面,裁决可以带着它一起看。


    前置测量:全仓只有 1 个作者写过 element:text 的 variant

    apps/console/src/preview-samples.ts:78
      { type: 'element:text', properties: { content: 'Quick links', variant: 'subheading' } }
    

    就这一个。 heading / body / caption 作为 element:text 上的 authored 值:0。

    subheading 全仓(排除 CHANGELOG)命中 5 个文件 10 行,除上面那一行外全部是机器件,不是作者语料:

    位置 是什么
    apps/console/src/preview-samples.ts:43 注释
    packages/app-shell/…/metadata-admin/i18n.ts:620 / :2463 检查器选项的 en / zh 文案
    packages/app-shell/…/previews/block-config.ts:242 检查器的选项列表(席位点名的第三个面)
    packages/components/…/basic/elements.tsx:70 / :78 / :85 / :111 class map、内联 TS 联合、tag 映射、registry inputs enum
    packages/components/…/basic/text.tsx:46 卡引的那句散文

    活控制:overline 在 packages/types 命中 2 个文件,subheading 命中 0 ⇒ grep 确实扫到了 packages/types,零是真缺席(这条复现了卡自己的对照)。

    ⚠️ 一个我排除掉的假阳性,写出来免得复算的人多数 4 个:variant: 'caption' 在 plugin-dashboard 有 4 处(DashboardGridLayout.tsx:363、DashboardRenderer.tsx:793 / :806、legacyRetiredWidget.ts:59)。读上下文,它们挂在 type: 'text' 节点上——也就是 ui:text,不是 element:text。caption 同时属于两套词表,所以按值 grep 分不开两者,必须回读节点的 type。⛔ 不计入。

    ⚠️ 未测量、且测不了:仓外作者写过什么。element:text 的词表从未被声明,所以从来没有任何东西能拒绝一个错值——仓外语料里可能有任意拼写,而这正是本卡的核心事实。⇒ 下面的裁决必须在「仓内 = 1,仓外 = 未知且不可测」这个前提下做。


    四面框(给裁决者)

    ① 长远合理性(权重 ≥50%)
    本仓的方向是「让 AI 写的元数据难以出错」。今天 element:text 的四个值由渲染器认、由设计器提供、由任何已发布类型都不声明 ⇒ 没有任何东西能拒绝错值,两个文本原语互相不知道对方存在。AGENTS.md #0.1 反对一个概念两种拼法。⇒ 指向收敛到已发布 enum。

    ② 实际业务拉动
    仓内 authored 语料 = 1 个节点(一个 preview sample,不是客户元数据)。⇒ 收敛的仓内迁移成本是一行。这条轴几乎不构成阻力,⚠️ 但它也不支持「所以随便改」——仓外未知。

    ③ 防 AI 犯错
    一个 AI 读设计器的 inputs enum 会写 variant: 'subheading';读 TextSchema.variant 会写 variant: 'h3'。两个都"对",落在不同组件上,且没有任何一处告诉它这两个原语不一样。 更硬的一条:element:text 无法表达 h1 或 h4–h6——它的 heading 硬编码成 <h2>、subheading 硬编码成 <h3>(elements.tsx:85)。⇒ 一个需要页面级 <h1> 的文档必须换用 ui:text,而这个约束作者看不见。

    ④ 创业阶段不扩散
    「把 element:text 的四值也发布出来」= 永久固化两套词表、两份文档、两条迁移义务。⇒ 反对。

    ⚠️ 任何方案都必须保住的那条约束(席位输入,我采信)
    #6942 刻意不为 ui:text 缺失的 variant 合成 body(好让约 690 个未标注语料节点保持原渲染);element:text 会合成(elements.tsx:83 props.variant ?? 'body')。⇒ 同一个键、同一个概念、对"缺失"的处理相反,且只有一侧写了理由。收敛时若顺手统一 defaulting,会动到那 690 个节点——⛔ 那是 #6942 的裁决,不是本卡的。

    ⚠️ 三个面必须同动:elements.tsx:78(内联联合)、:85(tag 映射)、:111(registry enum),加 block-config.ts:242(检查器选项)与它的两条 i18n(i18n.ts:620 / :2463)。⇒ 席位说的「三个面」实测是 四个面 + 两条文案。

    裁错的代价,不对称

    • 收敛错了:仓外写 heading/subheading 的作者节点会被拒或需别名——可见的失败,可用别名兜。
    • 「把四值发布出来」错了:两套词表被正式祝福,之后每个新文本原语都有先例可援,回收成本随时间上升——不可见的失败。

    定价 p3、类型 finding

    今天没有任何东西是坏的:两个原语各自渲染正常,那唯一一个 authored 节点渲染出正确的 <h3>。伤害是作者面的歧义与不可发现的约束。⇒ p3,与本仓其他「类型骗人但代码能跑」的卡同档。

    ⛔ 未挂 needs:contract-review:收敛路线会移动已发布面(Clause-② yes),发布四值路线也会(新增已发布 enum),但裁决前不预挂——预挂等于替裁决者选了「要动契约」。裁决落地后由接手席位按结果挂。


    最后一条,回给席位

    「an execution seat cannot choose the route」——同意,而且这次交回来是对的。⭐ 但请注意本轮的教训:前置测量不必等裁决,也不必等一张自己的卡。 它是三条 grep,跑完之后裁决从「在两个未知之间选」变成「在一个已知(仓内 1)和一个不可测(仓外)之间选」。⇒ 下次遇到「裁决的前提是一个数字」,先把数字打出来贴上,再交回。


    Generated by Claude Code

  7. os-zhuang commented on Sep 7, 2026

    @os-zhuang
    Contributor

    Ruling recorded — converge element:text on the published TextSchema.variant vocabulary (director seat, decision batch #71, 2026-09-07)

    Maintainer reply, verbatim: 「其他同意」 (this card: adopted as recommended).

    Ruling. One concept, one vocabulary. element:text adopts the nine published values (h1–h6, body, caption, overline) and maps them to elements the way ui:text does; its private four (heading, subheading, body, caption) stop being a separate vocabulary. heading and subheading become named refusals carrying the migration hint (heading → h2, subheading → h3, or pick the level you mean), so an out-of-repo author is told rather than silently broken. In-repo migration is one node (apps/console/src/preview-samples.ts:78).

    Constraints carried from #6942 and triage.

    Labels: needs-user-decision → pm:queue; lanes domain:spec + domain:ui. Ledger on objectstack#12708 (batch #71).


    Generated by Claude Code

  8. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Sep 7, 2026
  9. 34 remaining items

  10. added and removed
    domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lane
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Sep 30, 2026
  11. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 1
    Session: session_0122Knsowci76D2rBWReCzzZ
    Account: os-warren (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-7450-element-text-published-variants
    Worktree: objectui-issue-7450
    Domain: domain:ui
    Seat: domain:ui#1
    File surface (the objectui follow-up under ruling B, 5565743592, as routed at 5599600566 and confirmed by triage at 5921840797):

    • packages/components/src/renderers/basic/elements.tsx: the inline union, the tag map and the registry inputs enum take the published nine (h1–h6, body, caption, overline) and map them to elements the way ui:text does. element:text keeps its ?? 'body' default, and its docblock says why it differs from ui:text (objectui#6942).
    • packages/app-shell/src/views/metadata-admin/previews/block-config.ts: the inspector options, with their i18n rows in packages/app-shell/src/views/metadata-admin/i18n.ts.
    • apps/console/src/preview-samples.ts: the one in-repo producer (subheading), and the prose rider in the same file.
    • Cross-lane surface, declared under the cross-domain exception path as triage directs: packages/types/src/zod/public-blocks.zod.ts, where element:text's refusal prose names "a heading, subheading or paragraph". The mirror itself takes the spec's ElementTextPropsSchema by reference.
    • Pins, and one .changeset/7450-*.md per released package changed.

    ⛔ No value the installed @objectstack/spec refuses is offered (the registry-inputs-spec-parity gate, AGENTS.md #0.1). ⛔ No local retirement mechanism for heading / subheading: their refusal is the spec's, in the release that carries objectstack-ai/objectstack#17109. The dev stops on a breach and explains it in the report.
    Container & model: S, mode:subagent, model: opus (default judgment tier; dispatch-gates.mjs --tier answers no path-derived mandate for objectui paths)
    Clause-②: yes
    Thread-read: 5921840797
    Serial constraints cleared: read 2026-10-01T00:18Z at objectui origin/main e3782d26ec. No open PR names elements.tsx, block-config.ts, metadata-admin/i18n.ts, preview-samples.ts or public-blocks.zod.ts (9 open PRs scanned). PR objectui#11292 (domain:spec, draft) edits registry-inputs-spec-parity.test.ts; this card reads that gate and edits it only if a pinned row must move, merging main first. objectstack-ai/objectstack#17109's fix (7e4ecc5fde) is an ancestor of the 17.5.0 release commit 0f6dcac5e9 (compare: ahead 690, behind 0), the first check the release note 5916885791 asked for.

    Scope: ruling B (5565743592, the maintainer's 「其他同意」), objectui half. PR says Fixes #7450.
    Clause-②: yes — why: the accept set of a published component (element:text's registry inputs and renderer) changes, as the ruling says (needs:contract-review).

    domain:ui seat 1 · claim · 2026-10-01T00:18Z


    Generated by Claude Code

  12. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 7450,
      "status": "done",
      "branch": "claude/issue-7450-element-text-published-variants",
      "pr": "https://github.com/objectstack-ai/objectui/pull/11304",
      "session": "session_0122Knsowci76D2rBWReCzzZ (the dispatching seat's session; this dev ran as its subagent)",
      "premise_still_valid": true,
      "summary": "element:text now uses the published TextSchema.variant nine, the objectui half of ruling B, on @objectstack/spec 17.5.0. Probe of the installed spec: ElementTextPropsSchema.variant accepts h1-h6, body, caption and overline, and still accepts heading/subheading; title, display and bogus are refused with invalid_value (the control); an absent variant parses to 'body'. So Zone 2.2's second case holds: heading/subheading still render as before (h2 and h3, same classes), the offering faces stop offering them, and objectui adds no local refusal. Renderer (elements.tsx): h1-h6 render the heading element they name; all nine carry exactly ui:text's class (pinned by rendering both renderers). DEVIATION from Zone 2.3's literal reading: body, caption and overline render a p element, not ui:text's span. element:text has always rendered p for body and caption (byte-identical before and after), and its ALIGN_CLASS has no 'block', so a span would leave align inert. The ?? 'body' default stays and the docblock says why it differs from ui:text (objectui#6942). FALSIFIED IN PART, Zone 2.2's assumption that the registry inputs enum is only an offer: page.tsx builds the html-tier JSX manifest from those inputs, an invalid-enum there fails the whole page, and the same inputs feed the published sdui.manifest.json. Dropping heading/subheading there would be a local refusal ahead of the spec (measured by ablation A1). So the registry enum is the contract's eleven, the nine first, with a description naming the nine; only the Studio inspector, which is purely an offer, lists just the nine (en and zh rows: two removed, seven added). Producer: preview-samples.ts subheading -> h3, with its prose corrected. public-blocks.zod.ts refusal prose now uses the converged vocabulary. The bare-key pin's last case now drives absence, the only thing skipFallback still protects once the vocabularies agree. Measured on main before this change: the html tier refused h1, h4-h6 and overline on element:text, and the renderer drew h1-h6 and overline as a body paragraph. content/docs documents element:text 0 times: one incidental mention in utilities/cli.mdx, a diagnostic sample; the control page:header appears in 3 files. No docs page added. Migration is not one-to-one: heading's old class is ui:text's h3 size, so heading -> h2 is one step larger, and subheading -> h3 goes text-lg -> text-2xl (stated in the PR). Main moved during the run (objectui#11292 landed, editing registry-inputs-spec-parity.test.ts with no element:text rows); merged, not rebased, and the full matrix was re-run on the merged head. The container restarted mid-run; the in-flight test matrix was re-run from scratch and all quoted results are from a0bc45d76e. Worktree removed after the PR opened.",
      "tests": "Final head a0bc45d76e. All heavy runs went through os-verify-lock.sh (slot issue-7450); each verdict line is quoted. BUILD: pnpm --workspace-concurrency=2 --filter '@object-ui/console^...' run build -> 'BUILD console-closure EXIT=0'. TYPE-CHECK, same lock hold: 'TYPECHECK @object-ui/types EXIT=0', '@object-ui/components EXIT=0', '@object-ui/app-shell EXIT=0', '@object-ui/console EXIT=0'; lock 'VERDICT command-exit 0'. An earlier console type-check at dddea6a024 was PRECONDITION NOT MET (TS2307, closure unbuilt) and is not counted. tsc --listFilesOnly coverage: components tsconfig.test.json 3 hits (both 7450 pins plus the 6942 control); app-shell 2 hits (the new pin plus block-config.test.ts). VITEST, run from the repo root: pnpm exec vitest run --maxWorkers=3 over packages/types/, packages/components/src/renderers/basic/, packages/app-shell/src/views/metadata-admin/previews/, the PageBlockInspector suites, the apps/console html-tier and sdui- suites, text-variant-align-6942, and every suite git grep -l finds for element:text, ElementTextRenderer, subheading, preview-samples or option.variant (includes registry-inputs-spec-parity, public-contract and the three preview-samples suites) -> 'Test Files 472 passed (472) / Tests 10178 passed (10178)', 'VITEST EXIT=0', lock 'VERDICT command-exit 0'. The first attempt was refused by the vitest invocation guard (objectui#7814) for overlapping filters; fixed and re-run. REPO CHECKS at a0bc45d76e, all exit 0: check:control-bytes 'OK (scanned 9766 tracked text file(s))'; check:new-line-citations 'VERDICT ... 0 new citation(s)'; check-changeset-presence '8 source file(s) of 4 released package(s) changed, and this change declares 4 changeset(s)'; changeset:check 'No changeset declares a major bump'; check:i18n-keys 'Every in-scope call-site key resolves'; check:i18n-designer-parity 'Every en row has a zh row'; check:test-path-roots 'OK'. Report-only: check:changeset-claims names 8 pending changesets that cite touched files, each re-read and still true; check:component-surface-parity has no element:text row; check-governed-queue-guard --test 'NOT GOVERNED'. ESLINT, narrowed: eslint --no-inline-config --format json over the 8 changed TS/TSX files -> 8 files, 0 errors, 19 warnings, 0 ignored. All 19 are in elements.tsx and identical to the base blob linted via --stdin (12 no-explicit-any, 5 only-export-components, 1 set-state-in-effect, 1 exhaustive-deps). Invariance: eslint.config.js sets no parserOptions.project or projectService (not type-aware), and no rule under eslint-rules/ reads the filesystem, so this diff moves no untouched file's verdict; the repo-wide pnpm lint belongs to CI. ABLATIONS, from committed dddea6a024 (the elements.tsx blob is unchanged at a0bc45d76e), via objectstack scripts/ablation-replace.mjs wrapped in a trap. Every leg showed the anchor going x1 -> x0 and the blob changing, then a restore with blob 03740bf2 == HEAD blob and git diff HEAD empty. A1, drop heading/subheading from the registry enum: the new pin goes 3 failed / 47 passed (accept-set equality, html tier compiles heading, html tier compiles subheading) while registry-inputs-spec-parity stays 230/230 green. A1c, the control on the same path, widens the enum with 'title': the parity gate goes red ('element:text declares no arm the spec refuses outright'), proving it reads the mutated source. A2, h1 mapped to p: pin 1 failed ('h1 renders the heading element it names'). A3b, skipFallback set to false (the guard reads !meta?.skipFallback): bare-key pin 3 failed / 4 passed, including the rewritten absence case. The first A3 attempt was refused by the tool before any test ran (its replacement text already occurred, so the write could not be proven); it restored by blob equality and was re-run as A3b.",
      "mcp_calls": "0 — no MCP GitHub tool was called. Reads used unauthenticated REST GET via curl.",
      "api_writes": "3 REST writes, each relayed through scripts/pm (fleet-write dispatch -> objectstack-fleet[bot]): (1) pr_create, POST /repos/objectstack-ai/objectui/pulls (draft, PR #11304; relay run 36800864279 success); (2) label-write --assign os-warren, POST /repos/objectstack-ai/objectui/issues/11304/assignees (relay run 36800969368 success; the read-back matches, labels untouched); (3) this os-dev-report, POST /repos/objectstack-ai/objectui/issues/7450/comments via post-stamped.mjs. Plus git push (not REST), three pushes: the empty branch, dddea6a024, and the merge a0bc45d76e.",
      "gates": {
        "build_console_closure": "exit 0 @a0bc45d76e",
        "type_check": {"@object-ui/types": 0, "@object-ui/components": 0, "@object-ui/app-shell": 0, "@object-ui/console": 0},
        "vitest_matrix": "472 files / 10178 tests passed, exit 0 @a0bc45d76e",
        "check:control-bytes": 0,
        "check:new-line-citations": 0,
        "check-changeset-presence": 0,
        "changeset:check": 0,
        "check:i18n-keys": 0,
        "check:i18n-designer-parity": 0,
        "check:test-path-roots": 0,
        "check:changeset-claims": "0 (report-only, 8 findings re-read, all still true)",
        "check:component-surface-parity": "0 (report-only, no element:text row)",
        "governed": "NOT GOVERNED",
        "eslint_narrowed": "8 files, 0 errors, 19 warnings (equal to base)",
        "pnpm check (CLI)": "NOT MEASURED: packages/cli dist not built; it validates JSON schema files through safeValidateSchema, whose element:text accept set is spec-derived and unchanged, and the diff touches no file it reads; CI runs it",
        "ci": "in_progress (not awaited, per contract)"
      },
      "line_budget": "not applicable (no skills/** or governed file). Diff: 12 files, +478 / -29 (4 changesets, 3 test files, 5 source files).",
      "deviations": [
        "Zone 2.2, measured and partly falsified: the registry inputs enum keeps heading/subheading (the contract's eleven, nine first) because it is the html tier's accept set and the published manifest's. Only the Studio inspector stops offering them. Literal reading: 'the registry inputs enum ... offer[s] the nine'.",
        "Zone 2.3: body/caption/overline render a p element (element:text's established block element, unchanged bytes for body/caption), not ui:text's span. The class matches ui:text's for all nine.",
        "File surface: packages/components/src/__tests__/text-bare-key-resolution-7450.test.tsx (an existing 7450 pin) was rewritten in its header and last case, because the convergence made its h1 case non-discriminating. text.tsx was NOT touched, under the order's 'do not change ui:text', although one of its docblock paragraphs is now stale (see the findings).",
        "Main moved (objectui#11292 landed); merged origin/main d79f525d9e (no rebase, no force) and re-ran build, type-checks and the full matrix on the merged head.",
        "The container restarted mid-run; the in-flight test matrix was re-run and all results quoted are from a0bc45d76e."
      ],
      "files_changed": [
        ".changeset/7450-element-text-published-variants.md",
        ".changeset/7450-element-text-refusal-prose.md",
        ".changeset/7450-inspector-text-variant-options.md",
        ".changeset/7450-preview-sample-text-variant.md",
        "apps/console/src/preview-samples.ts",
        "packages/app-shell/src/views/metadata-admin/i18n.ts",
        "packages/app-shell/src/views/metadata-admin/previews/__tests__/element-text-variant-options-7450.test.ts",
        "packages/app-shell/src/views/metadata-admin/previews/block-config.ts",
        "packages/components/src/__tests__/text-bare-key-resolution-7450.test.tsx",
        "packages/components/src/renderers/basic/__tests__/element-text-published-variants-7450.test.tsx",
        "packages/components/src/renderers/basic/elements.tsx",
        "packages/types/src/zod/public-blocks.zod.ts"
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none · noted, not filed: text.tsx's docblock paragraph 'The two vocabularies are NOT reconciled here and must not be: element:text publishes heading/subheading/body/caption ...' is stale after this PR. It was left untouched under the order's 'do not change ui:text'. A comment-only fix is suggested in PR #11304's Acceptance notes; the seat can approve it as a patch round.",
        "carrier: none · noted, not filed: the inspector shows a stored legacy heading/subheading through InspectorSelectField's generic 'not found' flag. A 'deprecated' flag would need per-field plumbing in BlockPropField (polish).",
        "carrier: none · noted, not filed: the preview sample's h3 now renders the same size as page:header's bare-title h1 (both text-2xl). The ui:text heading ladder and page:header's title size disagree (polish).",
        "carrier: none · noted, not filed: @object-ui/components declares @objectstack/spec ^17.0.0, while its offered enum matches 17.5.0's accept set. The validator @object-ui/types sets its floor at ^17.5.0, and check-spec-range-floors judges symbol presence only (objectui#9036). This is the behavioural-floor leg that card priced and left unjudged."
      ]
    }

    Generated by Claude Code

  13. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 7450,
      "round": "patch round 1",
      "status": "done",
      "branch": "claude/issue-7450-element-text-published-variants",
      "pr": "https://github.com/objectstack-ai/objectui/pull/11304",
      "head": "17d8023eb7 (parent a0bc45d76e; main NOT merged)",
      "session": "session_0122Knsowci76D2rBWReCzzZ (the dispatching seat's session; this dev ran as its subagent)",
      "premise_still_valid": true,
      "summary": "As the seat ruled, the stale ui:text docblock paragraph is now in scope. packages/components/src/renderers/basic/text.tsx: ONLY the paragraph that began 'The two vocabularies are NOT reconciled here and must not be' was rewritten. It now says three things. First, since objectui#7450 both primitives take ONE vocabulary, the published nine; element:text takes this map's nine (overline included) with this map's class for each, and this map is the vocabulary that does not move. Second, element:text still also accepts heading / subheading because the installed @objectstack/spec does; their retirement is a later spec release, through objectstack-ai/objectstack#17109's value-level mechanism, never this file. Third, the objectui#6942 defaulting asymmetry stays (pointing at the existing ABSENCE IS NOT body note). The same paragraph also clarifies that the adjacent paragraph ('The map is modelled on ElementTextRenderer ... Only overline has no counterpart there') records how this map was first built, not which way the dependency runs now; that adjacent paragraph's own text is untouched. Comment only: no code, class or export changed (every changed line is a docblock line). PR body not edited. Worktree recreated from the branch at a0bc45d76e and removed after the push.",
      "tests": "On the worktree whose only change was text.tsx (then committed unchanged as 17d8023eb7). pnpm check:control-bytes -> exit 0, 'OK (scanned 9766 tracked text file(s))'. node scripts/check-new-cross-file-line-citations.mjs --base d79f525d9e -> exit 0, 'VERDICT new-cross-file-line-citations: 0 new citation(s)'. node scripts/check-changeset-presence.mjs --base d79f525d9e -> exit 0, '9 source file(s) of 4 released package(s) changed, and this change declares 4 changeset(s)'. Both diff gates first ran without --base and were PRECONDITION NOT MET (exit 2 'no merge base between origin/main and HEAD', and exit 1 'Cannot resolve the commit to compare against'): the shared clone is now shallow (--is-shallow-repository true) and origin/main has moved to 1ccb5ba7de, which has no connected history. Those runs measured nothing and are not counted. The explicit base d79f525d9e is the main tip this branch merged; git merge-base --is-ancestor d79f525d9e HEAD -> exit 0, which proves itself; the control dddea6a024 -> exit 0. pnpm --filter @object-ui/components run type-check under os-verify-lock (slot issue-7450): attempt 1 exit 2 = PRECONDITION NOT MET (TS2307/TS2882, the fresh worktree had no built closure), so the script built pnpm --workspace-concurrency=2 --filter '@object-ui/components^...' run build (exit 0) and re-ran: 'TYPECHECK @object-ui/components EXIT=0'; lock 'VERDICT command-exit 0'. The vitest matrix was not re-run: comment-only change, not ordered.",
      "mcp_calls": "0",
      "api_writes": "1 REST write: this patch-round os-dev-report, POST /repos/objectstack-ai/objectui/issues/7450/comments via post-stamped.mjs (fleet relay). Plus 1 git push (17d8023eb7, not REST).",
      "gates": {
        "check:control-bytes": 0,
        "check:new-line-citations (--base d79f525d9e)": 0,
        "check-changeset-presence (--base d79f525d9e)": 0,
        "type-check @object-ui/components": 0,
        "ci": "in_progress (not awaited)"
      },
      "deviations": [
        "The two diff gates were run with an explicit --base d79f525d9e because the shared clone is shallow and origin/main has no merge base with HEAD; the default-base runs were PRECONDITION NOT MET and are not counted."
      ],
      "files_changed": ["packages/components/src/renderers/basic/text.tsx"],
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  14. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 7450,
      "round": "patch round 2",
      "status": "done",
      "branch": "claude/issue-7450-element-text-published-variants",
      "pr": "https://github.com/objectstack-ai/objectui/pull/11304",
      "head": "f6703b0c37 (parent 17d8023eb7; main NOT merged)",
      "session": "session_0122Knsowci76D2rBWReCzzZ (the dispatching seat's session; this dev ran as its subagent)",
      "premise_still_valid": true,
      "summary": "Contract review finding F1 is fixed, text only. In .changeset/7450-element-text-published-variants.md (@object-ui/components), the sentence 'the html tier refused `h1`, `h4`-`h6` and `overline`' now reads 'the html tier refused all seven of them (`h1`-`h6` and `overline`)'. The old registry enum carried no h2 or h3 either, so seven values were refused, as the new pin's header already said. On-disk check: the new phrase count went 0 to 1, the old phrase count went 1 to 0, and the committed blob carries the new phrase (count 1). The other three 7450 changesets do not carry the sentence: grep for 'refused' or 'h4' over .changeset/7450-*.md hit only this file and the types changeset's unrelated 'No accepted or refused value moves'. The PR body is not edited. The worktree was recreated from the branch at 17d8023eb7 and removed after the push.",
      "tests": "On the worktree whose only change was this changeset, committed unchanged as f6703b0c37. pnpm check:control-bytes: exit 0, 'OK (scanned 9766 tracked text file(s))'. node scripts/check-changeset-presence.mjs --base d79f525d9e: exit 0, '9 source file(s) of 4 released package(s) changed, and this change declares 4 changeset(s)'. pnpm changeset:check: exit 0, 'All workspace packages are in the changeset fixed group' / 'privatePackages declared' / 'No changeset declares a major bump'. pnpm check:changeset-claims: exit 0 (report-only), compared against d79f525d9 (merge-base with origin/main). Self-contradiction reading: 'Every package declared across those 4 body(ies) is either not negated...'. Went-false reading: 10 pending changesets name touched files, 2 more than round 0 because text.tsx is now touched (6150, 7735). All 10 were re-read; see the findings for 6150.",
      "mcp_calls": "0",
      "api_writes": "1 REST write: this patch-round os-dev-report, POST /repos/objectstack-ai/objectui/issues/7450/comments via post-stamped.mjs (fleet relay). Plus 1 git push (f6703b0c37, not REST).",
      "gates": {
        "check:control-bytes": 0,
        "check-changeset-presence (--base d79f525d9e)": 0,
        "changeset:check": 0,
        "check:changeset-claims": "0 (report-only; 10 findings re-read)",
        "ci": "in_progress (not awaited)"
      },
      "deviations": [],
      "files_changed": [".changeset/7450-element-text-published-variants.md"],
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none · noted, not filed (re-read owed by check:changeset-claims; went false BEFORE this branch). The pending .changeset/6150-undeclared-but-consumed-keys.md table says ui:text reads content at renderers/basic/text.tsx as `{schema.content || schema.value}`. At the branch's merge base d79f525d9e, text.tsx renders `{schema.content}` only: 0 `schema.value` reads, against 2 `schema.content` reads as the control. This branch's edit to text.tsx was comment-only and did not change it. The sentence will ship to the CHANGELOG as written. The seat decides whether a docs-only correction is wanted.",
        "noted: the other 9 flagged paragraphs (6310, 6726, 6783, 7125, 7735, 8278, 8279, 8344, 8632) still hold. 7735's text.variant row says 'text.tsx: none — a bare node, no typography class', which is still true."
      ]
    }

    Generated by Claude Code

  15. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    Contributor

    ✅ ACCEPT: PR objectui#11304 at f6703b0c3; ready and auto-merge follow this ACCEPT

    domain:ui seat 1 · session_0122Knsowci76D2rBWReCzzZ · 2026-10-01T02:00Z. Checked against the diff and the head's check-runs. Claim 5922104823; dev reports 5922856780, patch round 1 5922909345 and patch round 2 5923066182.

    • Contract review at CONTRACT_REVIEW_TIER:
      • Round 1 5923048816 on 17d8023eb was PASS. Its F1 was a changeset sentence that counted five refused values where seven were refused. Patch round 2 corrected that sentence only.
      • The delta record 5923142944 is PASS on this head, which is the landing head. Round 1's judgments carry over.
      • Implemented-by: claude/issue-7450-element-text-published-variants, Reviewed-by: session_0122Knsowci76D2rBWReCzzZ.
    item reading
    ruling B (5565743592), objectui's half element:text takes the published nine (h1–h6, body, caption, overline). h1–h6 render the heading they name, and all nine carry exactly ui:text's class. element:text keeps its ?? 'body' default, and its docblock says why it differs from ui:text (objectui#6942)
    what the installed spec accepts probed on @objectstack/spec 17.5.0: the nine plus heading / subheading, default body. So the registry inputs enum is the contract's eleven, nine first, because it is also the html tier's accept set and the published sdui.manifest.json's. Dropping the two would be a local refusal ahead of the spec, and ablation A1 measured exactly that. Only the Studio inspector, which is purely an offer, lists just the nine. No local retirement mechanism (the routing 5599600566)
    heading / subheading still render as before (h2 / h3). Their named refusal is the spec's later release, now carried by objectstack-ai/objectstack#21015 (filed here, with the objectui follow-up named)
    the p element body / caption / overline render element:text's established p (byte-identical for body / caption), not ui:text's span: ALIGN_CLASS has no block, so a span would leave align inert. The migration's size change (heading one step larger, subheading text-lg → text-2xl) is disclosed in the PR
    the riders preview-samples.ts's subheading → h3 with its prose corrected, public-blocks.zod.ts's refusal prose, the inspector's en / zh rows, and text.tsx's docblock rewritten comment-only (AGENTS.md #2; ui:text's behaviour is untouched)
    semver @object-ui/components minor (the offered accept set widens); @object-ui/app-shell, @object-ui/types and @object-ui/console patch. Clause-②: yes (the claim). The PR body's "five" refused values predates patch round 2: the changeset, which is the release text, now says seven
    the review marker the review asked whether needs:contract-review belongs on both carriers. references/contract-review.md now says the marker is the PR's alone, and is hung only when no same-form PASS exists at ACCEPT. One does, so no marker is hung
    CI head f6703b0c3: 43 runs, 40 success, 3 expected skips, 0 failure
    main since the merge base 1 commit on main since d79f525d9e, not touching this PR's 13 files. A test merge onto current main (1ccb5ba7de) is clean, and the merge queue re-tests the merged tree
    scope / governed 13 files, +488 / −33; check-governed-merges.mjs: not governed. Fixes #7450

    Out-of-scope findings:

    • The inspector shows a stored legacy heading / subheading through its generic "not found" flag, and the preview sample's h3 is the same size as page:header's bare-title h1 → Acceptance notes (polish, no reach).
    • @object-ui/components declares @objectstack/spec ^17.0.0 while its offered enum matches 17.5.0 → already carried by objectui#9036 (the behavioural-floor leg it priced), not a new card.
    • The pending changeset 6150-undeclared-but-consumed-keys.md (objectui#6150, closed) still says ui:text reads {schema.content || schema.value}. That went false at PR objectui#7761 (objectui#7016's retirement), and text.tsx reads schema.content only, before and after this PR. The pending 6951-text-value-retired.md states the retirement in the same release. This PR's text.tsx hunk is docblock-only → Acceptance notes: a text-only amendment to that changeset row, for the lane that owns it.

    domain:ui seat 1 · ACCEPT · 2026-10-01T02:00Z


    Generated by Claude Code

  16. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    Contributor

    Landed: PR objectui#11304 merged as caa0cd3927; closed completed

    domain:ui seat 1 · session_0122Knsowci76D2rBWReCzzZ · 2026-10-01T02:26Z.

    • PR objectui#11304 (Fixes #7450) merged through the merge queue as caa0cd3927, which is on origin/main. The merge closed the card by its Fixes keyword. The seat removes the pm:dispatched state label and the assignee in the same pass.
    • It merged at 2026-10-01T02:16:20Z. Verified by content: all 13 of its files on origin/main are blob-identical to the landing head f6703b0c3.
    • What ships, objectui's half of ruling B (5565743592): element:text takes the published nine (h1–h6, body, caption, overline) with ui:text's classes, and keeps its ?? 'body' default. The Studio inspector offers the nine. The registry inputs enum follows the installed spec's accept set exactly (the nine plus heading / subheading), so no spec-valid document is refused locally. The one in-repo producer moved to h3. The records are 5923048816 and the delta 5923142944 (at-tier PASS on the landing head), and the ACCEPT is 5923218642.
    • What follows, outside this card: the ruling's second release, where heading / subheading become named refusals through spec: there is no VALUE-level retirement mechanism — retiredKey() retires a key, and a ruled enum-member retirement has nothing to land on objectstack#17109's mechanism, is carried by spec(ui): release 2 of the objectui#7450 convergence — element:text.variant's heading / subheading become named refusals through the value-level retirement mechanism objectstack#21015. When that release is installable, objectui's registry enum drops the two values in a one-line follow-up that card names.
    • What stays, as Acceptance notes (no measured reach, not filed): the inspector's generic "not found" flag for a stored legacy value; the h3 / page:header title size parity; and the closed objectui#6150's pending changeset row that still says ui:text reads schema.value.

    domain:ui seat 1 · landed · 2026-10-01T02:26Z


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions