Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

parity 门的反方向没推到全仓:7 个 block 共 15 个 spec 已声明的顶层键没有任何 inputs 发布,其中 3 个渲染器实读(record:details.hideFields / record:related_list.relationshipValueField / element:text_input.defaultValue) #3808

Closed

Description

@yinlianghui

发现于 #3797 / PR #3806 的实施过程。那一单按判级只推了一个方向(「registry 不得声明 spec 不接受的顶层 input」),PR #3795 的单块版其实有两个方向,另一个是「spec 有的键,inputs 里必须能被作者发现」。反方向没推到全仓,本单是把它补上。未认领,交 PM triage。

为什么反方向同样有害

inputs 是 gen-manifest.ts 序列化进 sdui.manifest.json 与 sdui-intrinsics.d.ts 的发布面。一个 spec 声明了、渲染器也兑现了、但 inputs 不提的键,对读 manifest 的作者(尤其是 AI 作者)就是不存在:它不在设计器面板里,不在生成的 .d.ts 里,写了还会被 sdui-parser 的 manifest 门报 unknown-prop warning。这正是 #3407 的原始抱怨(readonly 早已被兑现,只是 description 没提,作者读不到)。

实测清单(objectui origin/main @ c4c0ac897,pin @objectstack/spec@17.0.0-rc.5)

用 PR #3806 落的门同一套 derivation 反着跑(specTopLevelKeys(type) 减去 inputs 名,aria 已排除 —— 它是无障碍逃生口,record:details / record:activity 都有成文理由不声明):

element:record_picker  displayField, searchFields, filter, multiple, targetVariable
element:text_input     defaultValue, targetVariable
page:card              actions
page:header            icon
page:tabs              type
record:details         hideFields
record:related_list    relationshipValueField, add

这 15 条不是一类东西,triage 时请分开看:

A 类 —— 真缺口:渲染器实读、作者读不到(3 条,最该修)

键 渲染器读点 证据
record:details.hideFields packages/plugin-detail/src/renderers/record-details.tsx:147 spec 那段注释原文说它「由已发布的 sys_user 平台页面书写、由 RecordDetailsRenderer 读取,只是从来没被声明过」(objectstack#5611 已在 spec 侧补声明);本仓 inputs 至今不提
record:related_list.relationshipValueField packages/plugin-detail/src/renderers/record-related-list.tsx:95(schema.relationshipValueField || 'id') 注释原文即写 spec relationshipValueField, default 'id'
element:text_input.defaultValue packages/components/src/renderers/basic/text-input.tsx:73/76/119 用来给绑定的 page 变量播种初值

record:related_list.add 也可能属于这一类,没细查,triage 时一并看。

B 类 —— 声明了但渲染器不读(showSubscriptionToggle 同族,2 条)

page:header.icon 与 page:card.actions:spec 声明、containers.tsx 里没有任何读点。这两条的正确方向不是「补 input」,而是二择一 —— 要么接线,要么按 record:activity.showSubscriptionToggle 的先例(packages/plugin-detail/src/index.tsx:350-360)声明出来并在 description 写明 KNOWN GAP,让双向 parity 成立而不假装可配。不要无脑补 input:那会发布一个平台默默丢掉的键,正是 #3797 修的那个方向。

C 类 —— 已知的、不该动的(5 条)

  • element:record_picker 的 displayField / searchFields / multiple:objectstack#5775 已在上游转成 retiredKey() 墓碑,pin 升上来后它们会从 spec 的接受集消失,本仓正确的做法就是不声明。
  • page:tabs.type:载体冲突,type 是节点分发键,扁平载体里不可授权;正在 objectstack#6776 里定收敛方向。见 PR test(console): registry inputs 与 spec ComponentPropsMap 的 parity 门推到全仓,4 个 OFF-SPEC block 逐块判定 (#3797) #3806 的 body。
  • element:record_picker.targetVariable / element:text_input.targetVariable:spec 自己写明这是「declarative hint」,实际绑定靠 PageVariableSchema 的 source 反查;要不要发布是个判断题,不是漏声明。

反过来还有一条本仓读了、pin 版 spec 没声明、上游已声明的:element:record_picker 的 emptyText / sort / limit(renderers/basic/record-picker.tsx 实读,objectstack#5775 已声明)。pin 升上来后它们会变成新的 A 类缺口,建议和这一单一起收。

建议的落地

在 PR #3806 落的 apps/console/src/__tests__/registry-inputs-spec-parity.test.ts 里加第二个方向的断言,同样运行时从 spec 推导 + 显式豁免名单 + 每条豁免理由(B/C 两类就是豁免的正当来源,理由写清是「不读,按先例处理」还是「载体冲突,见 objectstack#6776」)。这样两个方向同一处、同一套豁免纪律,不会再有一个方向被单独忘掉。

关联:#3797 / PR #3806(正方向,已落)、#3407 / PR #3795(单块双向版)、objectstack#5611、objectstack#5775、objectstack#6776

Activity

  1. yinlianghui commented on Aug 8, 2026

    @yinlianghui
    ContributorAuthor

    分诊判级(objectui 分片 PM,session_01GTRjn8xBqp75dk7kFupVRt):pm:queue,可派,范围 = 正文 A 类三条(record:details.hideFields、record:related_list.relationshipValueField、element:text_input.defaultValue —— 渲染器实读却不可从 manifest 发现的真缺口,补 inputs 声明 + description 从渲染器实际行为取材)。⛔ B 类(spec 声明但渲染器不读)不许无脑补 input —— 那是 showSubscriptionToggle 同族的反向缺陷,若处理须逐条按 #3797 的两向判定另立;C 类不动。派发词须点名 PR #3806 的门与本单的关系:补声明后该键从「反向缺口」变「正向通过」,门的断言自动覆盖。


    Generated by Claude Code

  2. self-assigned this
    on Aug 8, 2026
  3. yinlianghui commented on Aug 8, 2026

    @yinlianghui
    ContributorAuthor

    🔒 认领(objectui 分片 PM,session_01GTRjn8xBqp75dk7kFupVRt)

    即将派发 os-dev 实施。


    Generated by Claude Code

  4. yinlianghui commented on Aug 17, 2026

    @yinlianghui
    ContributorAuthor

    【正文更正 · 来自 #3809 的实施(PR #4967)】本卡 C 类的五条里,有两处读法最终被上游走向证否。第一处 #3809 的 08-08 评论已经更正过,这里补第二处,并把两处放在一起,免得只读本卡的人各拿一半。

    C 类之一(已更正):element:record_picker 的 displayField / searchFields / multiple
    正文写「pin 升上来后它们会从 spec 的接受集消失」—— 错。ADR-0087 D2 退役是把成员换成 z.never().optional(),不删条目,所以 Object.keys(shape) 照样报告它们,pin 升级并不会让本卡为它们写的豁免过期。这三条豁免一直活到 #3809 收窄 specTopLevelKeys 才被 dangling + stale 两道检查点名删除(PR #4967)。

    C 类之二(本次更正):page:tabs.type
    正文把它归为「载体冲突,type 是节点分发键,扁平载体里不可授权;正在 objectstack#6776 里定收敛方向」—— 这个读法把它当成spec 的活键,只是本仓无法发布。实测:objectstack#6776 的收敛方式是直接退役 type 拼法,保留 tabStyle(本仓已发布)。所以它不是「一个无法授权的活键」,而是和 record_picker 三连同一类墓碑,并且在 rc.6 与 17.0.0 GA 两个 pin 上都是墓碑(实测)。它的豁免也在 PR #4967 里被同一道检查点名删除。

    归纳一句给后来者:本卡 C 类当时的划分依据是「为什么不该声明」,而**「上游按名拒绝」这一整类根本不需要豁免** —— 收窄之后这类键自己就离开接受集,反方向不再索要它。所以 C 类里凡是「上游已退役/将退役」的条目,正确处置是等收窄、删豁免,而不是长期挂着。#4967 之后这条已经是自清的:上游此后退役的键一进 shape 就离开接受集,顺带带走覆盖它的豁免。

    本卡的 A 类修复(#3841 落的四个键)与本更正无关,不受影响。


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions