fix(plugin-detail,components): 补齐渲染器已兑现的四个 spec 键的 inputs 声明,并把 parity 门的反方向推到全仓 (#3808) - #3841
Conversation
…rs already honoured, and push the parity gate's reverse direction repo-wide (#3808) PR #3806 landed the repo-wide parity gate in ONE direction ("a block may not declare a top-level input its spec props schema does not accept"). PR #3795's single-block version had two, and the other one — "a key the spec declares must be discoverable from `inputs`" — never made it out of that one block. This adds it, over the same `covered` set and the same exemption discipline, and fixes the four keys it finds. A class, verified at the renderer read sites rather than assumed: - `record:details.hideFields` (`renderers/record-details.tsx:147`) - `record:related_list.relationshipValueField` (`record-related-list.tsx:95`) - `record:related_list.add` (`record-related-list.tsx:186`, `:231`) - `element:text_input.defaultValue` (`text-input.tsx:73/76/119`) All four were spec-declared, renderer-honoured, and absent from `inputs`, which is the objectui#3407 failure on four more keys: the manifest and the generated `.d.ts` omit the key, `sdui-parser`'s prop walk returns `unknown-prop` on an author who writes it, and the renderer honours it anyway. `element:text_input` is non-public so its gap was not in `sdui.manifest.json` at all — it was in the JSX-page compiler's whitelist, which `page.tsx:462` builds from `getKnownTypes()` plus these same `inputs`. Descriptions are derived from what each renderer DOES, not from restating the spec, because the two differ in three places that matter: `hideFields` teaches bare names only (the renderer tolerates entry objects, the spec rejects them); `add.picker.labelField` publishes the renderer's `name` default, not the spec's "object title field"; and `add.picker.filter` is named as a KNOWN GAP rather than documented as a restriction (spec declares it, nothing reads it) following the `record:activity.showSubscriptionToggle` precedent. Nine spec keys stay deliberately unpublished, each with a written reason and an open issue, and a stale-entry test deletes an entry once it stops describing anything: two the renderers do not read at all (#3829), three ADR-0087 tombstones, `page:tabs.type` (#6776 upstream), two `targetVariable` declarative hints (#3834), and `element:record_picker.filter` (#3830 — a real A-class gap that fell out of #3808's own three-class triage, so out of this PR's scope). Two console probes generate fixtures from the declarations, so `add` becoming a declared `object` input handed them `{}` — which is not a valid `add` (the spec makes `picker` required) and crashes `record:related_list` outright (`RelatedList.tsx:1299` dereferences `add.picker.object` where `:378`/`:390` optional-chain it, filed as #3838). Both samples are now spec-valid, and the ledgered branch of `public-block-binding-reach` gained a crash guard because "made no data call" is its pass condition and a crashed block satisfies it. Co-authored-by: Claude <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
ℹ️ Console Performance Budget — not measuredThis run did not produce a console bundle to measure, so there is no pass/fail verdict for the performance budget. This is not a budget violation. Nothing was measured — the numbers a real violation would carry are simply absent.
See the workflow run for details. 📦 Bundle Size Report
Size Limits
|
…llsFor` returning html type-checks (#3808) CI Type Check and Bundle Analysis both went red on four errors in one file, and both from the same cause: the previous commit made `dataCallsFor` return `{ calls, html }` while its annotation still said `Promise<string[]>`. public-block-binding-reach.test.tsx(335,12): TS2353 'calls' does not exist in type 'string[]' public-block-binding-reach.test.tsx(356,15): TS2339 Property 'calls' does not exist on type 'string[]' public-block-binding-reach.test.tsx(356,22): TS2339 Property 'html' does not exist on type 'string[]' public-block-binding-reach.test.tsx(357,37): TS7006 Parameter 'c' implicitly has an 'any' type The fourth is a cascade of the first three — `c` infers as `string` again once `calls` is a `string[]` rather than an error type. Fixed by giving the function a named return type instead of widening anything: a `Mount` interface with `calls: string[]` and `html: string`, deliberately the same shape and field names as the sibling probe's own `Mount` (`record-block-record-reach.test.tsx:310-313`), which has captured both halves from the start for exactly the reason this file now needs the html — a crash is invisible in the call log, because `SchemaRenderer` catches the throw and paints an error card, so a crashed block simply makes no calls. No `as any`, no signature loosening. Bundle Analysis was not an independent failure: its first step is `pnpm --filter @object-ui/console build`, and that build begins with `tsc`, so it was failing on these same four errors before reaching a bundle at all. Verified with the CI commands rather than the narrower per-package ones that missed this — the previous commit's `type-check` run predated these edits and was never repeated, which is precisely how four type errors reached CI: pnpm type-check -> 78 successful, 78 total pnpm --filter @object-ui/console build -> exit 0 (tsc + vite build + build:plugin) console entry gzip 28.1 KB against the 350 KB budget -> PASS vitest, the gate + both probes + three per-block parity files -> 95 passed (6 files) Co-authored-by: Claude <noreply@anthropic.com>
|
返工记账(实施 agent 自述,便于 review 时核对):首推的 CI 两项红是同一个原因,且是我的流程错误,不是环境问题。 4 条类型错上一 commit 让 第 4 条是前 3 条的连带: Bundle Analysis 不是独立失败
修法给函数一个命名返回类型,而不是放宽任何东西: 我漏了什么(根因)上一 commit 的 type-check 是在编辑 本次改用 CI 同命令复验: 行为面零变化:这次只动类型注解与注释,断言、fixture、豁免名单一字未改。 Generated by Claude Code |
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
|
✅ 验收通过(objectui 分片 PM,session_01GTRjn8xBqp75dk7kFupVRt)—— undraft + auto-merge(返工 1 轮)。 返工核验:修法干净 —— 命名 interface 照抄兄弟探针自身的 首推验收要点一并落档(前跳已核):A 类扩为四键( 方法库入账:①「末次编辑之后必须重跑 type-check,且用 CI 同命令(全仓链)」—— vitest esbuild 转译不做类型检查,测试绿掩盖类型红;②豁免名单依赖 #3829/#3830/#3834 保持 open(门只校验单号存在不校验状态)—— 关闭其中任一须同时处理键本身。 Generated by Claude Code |
Fixes #3808
PR #3806 把全仓 parity 门只推了一个方向(「registry 不得声明 spec 不接受的顶层 input」);PR #3795 的单块版有两个,另一个「spec 声明的键必须能从
inputs被发现」没跟出来。本 PR 把反方向补上(同一个covered集合、同一套豁免纪律、同一个文件),并修掉它找出来的 A 类缺口。A 类逐键对照(渲染器读点实读,非按 spec 措辞推断)
pin 版
@objectstack/spec@17.0.0-rc.5,基线origin/main@c85268256。inputsrecord:details.hideFieldsz.array(z.string())(objectstack#5611)renderers/record-details.tsx:147record:related_list.relationshipValueField'id'renderers/record-related-list.tsx:95defaultValue: 'id'record:related_list.addpicker必填renderers/record-related-list.tsx:186/:231element:text_input.defaultValuestring | numberrenderers/basic/text-input.tsx:73/76/119四条都是 #3407 的同一失效:manifest 与生成的
.d.ts不提这个键 → 设计器面板里不存在;sdui-parser/src/validate.ts:74在comp.inputs里找不到它 → 对照写了这个键的作者报unknown-prop;渲染器照样兑现。三个答案。element:text_input不在PUBLIC_BLOCKS(public-blocks.ts:80有成文理由),所以它的缺口不在sdui.manifest.json,而在 JSX 页面编译器的白名单 ——renderers/layout/page.tsx:462用getKnownTypes()+ 同一份inputs现搭那份 manifest。manifest 链实跑(不落盘)确认:record:related_list.add的核对结论:A 类,同修#3808 正文写「也可能属于这一类,没细查」。细查结论是属于,而且是四条里最要紧的一条:
add是构建 junction 指派列表的唯一途径,不声明就等于那个功能只能靠写一个不可发现的键来用。三个成员键渲染器全兑现(RelatedList.tsx:378/:390/:724),第四个不兑现。description 从渲染器取材而非照抄 spec,三处刻意的差异:
picker.labelField的默认值写name(RelatedList.tsx:390),不写 spec.describe()说的「the object title field」—— 后者没有任何代码实现;picker.filter写成 KNOWN GAP:spec 声明「Restrict which records the picker offers」,全仓零读点(RelatedList把picker.object/labelField传给 record picker 对话框,从不填它的baseFilter)。按record:activity.showSubscriptionToggle先例把 gap 说出口 —— 静默把它列进成员清单,等于告诉作者候选被限定了,而实际上对话框提供全部记录。接线另开record:related_list.add.picker.filter全仓零读点:作者限定了 Add 选择器的候选范围,对话框照样提供该对象的全部记录 #3831;hideFields只教裸字段名:渲染器容忍{name}/{field}条目,spec 是z.array(z.string())按值拒绝,教那种拼法就是发布一种契约拒收的方言(与同文件fields同一道围栏)。反方向门:豁免名单全文(9 条,逐条理由 + tracking issue)
aria走全局排除(理由统一:无障碍逃生口,不是布局选项,15 个 block 同一个理由,plugin-detail/src/index.tsx:335-337已成文),并有非空 + 「确实是某个 covered block 的 spec 键」双重非空洞断言。其余逐条:B 类 —— spec 声明、渲染器零读点(2 条,⛔ 不许无脑补 input)→ #3829
page:header.iconcontainers.tsx:822-1570里icon只出现在每个 action 自己的action.icon(:1321/:1365)与导航项item.icon(:604);block 自己的icon零读点page:card.actionsPageCardRenderer(containers.tsx:666-695)只渲染 title / body(或 children)/ footer补 input 会发布一个平台默默丢掉的键 —— 正是 #3797 修的方向;接线是视觉决定(且
page:card.actions会伸进renderers/action/**,PR #3825 刚动过);第三条路是showSubscriptionToggle先例(声明 + KNOWN GAP)。三种形状、一个公开契约,另立 #3829。载体冲突(1 条)→ objectstack#6776
page:tabs.typepage:tabs.tabStyle豁免是同一事实的两面:扁平载体里type是分发键(SchemaRenderer.tsx:251-270明确拒绝 hoist),且validate.ts的BASE_PROPS含'type',声明了也校验不到ADR-0087 墓碑(3 条)→ objectstack#5775 + #3809
element:record_picker的displayField/searchFields/multiple。顺带纠正 #3808 正文的一处错误:它写这三个键「pin 升上来后会从 spec 的接受集消失」。按 #3809 的机制这是错的 —— ADR-0087 D2 的退役是替换成z.never().optional(),不是删条目,Object.keys(shape)照样包含它们。所以这三条豁免不会随 pin 升级自动过期,只会随 #3809 的墓碑识别落地而过期,豁免理由里逐条写明了。同一个盲区在两个方向的症状相反(正方向假绿、反方向假红:门会要求本仓去声明一个按名被拒的键,照做则正方向立刻红),已写进门的 LIMIT 段落并在 #3809 补了评论。
declarative hint(2 条)→ #3834
element:record_picker.targetVariable/element:text_input.targetVariable。grep -rn targetVariable packages/ apps/零命中 —— 不是「读了没用上」,是这个标识符全仓没出现过;真正的绑定是usePageVariableBinding(schema?.id)按组件 id 反查PageVariableSchema.source,与 spec 自己的 describe 一致。发布与否是判断题(发布的风险很具体:只写targetVariable不写变量source的作者会得到一个什么都不写入的输入)。新发现的第 4 个 A 类,超出本单派发范围(1 条)→ #3830
element:record_picker.filter。渲染器实读(record-picker.tsx:78的ds.filter ?? props.filter→:103的query.$filter)、spec 已声明、inputs不提 —— 与本 PR 修的四条完全同形。它在 #3808 的原始 key dump 里出现过,却没有被归入 A / B / C 任何一类(正文自称 15 条、逐行相加实为 13 条,C 类小计写 5 条实为 6 条,掉的就是它),因此落在派发范围之外。按 Prime Directive #10 另开 #3830 + 带理由豁免,而不是在没人为它做过 review 的 PR 里扩面。emptyText/sort/limit(本仓实读、pin 版 spec 未声明、objectstack#5775 已声明)在墓碑那段的注释里记档:pin 一升它们变成新的 A 类,门会报红要它们 —— 这个红是对的、是想要的,就是 pin 升级自带的提醒。反向验证(方向先判后跑,四次)
1. 去掉任一新声明 → 门红(预判:红。命中)
删
record:details.hideFields的声明:三处一起红:派生的全仓门、按名钉住的那条、块级 parity。这一跑还抓出一个空绿:同文件的「
hideFields只教裸名」断言在声明被删后仍然绿,因为not.toContain('{')对''恒真。已加expect(description).not.toBe('')前置,并在注释里写明是这次反向验证发现的。2. 豁免塞无理由条目 → 豁免纪律断言红(预判:恰好一条红。命中)
把
page:tabs.type的理由改成不含 issue 号:3. 给已声明的键塞豁免 → 陈旧豁免 + 按名钉住 两条红(预判:2 条。命中)
第二条是刻意的:它把「用豁免代替声明」这条退路堵死 —— 派生断言本身对「补进豁免名单」也会绿,而那正是本单要排除的动作。
4.
public-block-binding-reach的崩溃守卫 —— 预判红,实测绿,按实情记账把那里的
add样本退回{},预判它会触发新加的崩溃守卫。实测 16 个全绿。原因查清了:renderers/record-related-list.tsx:185传的是dataSource={ctx?.dataSource},这个探针不带 RecordContext,于是RelatedList收到dataSource: undefined,:1293的add && dataSource短路,裸取根本到不了。所以那条守卫今天是防御性的、不是承重的,已在注释里如实写明(而不是留一句「它会红」)。守卫的判据本身可用 —— 把它同时加到两个分支上时,它报出了 #3840 里那两个崩溃。崩溃本身是真的,在兄弟探针里实测到(那个探针带 RecordContext + dataSource):
两个 console 探针的 fixture 处置
两者都按声明自动生成 fixture,
type: 'object'的通用样本是{}。add一旦成为声明的object输入,它们就拿到了{}—— 而{}不是合法的add(spec 里picker必填)。按 fixture 三分法这属于「补声明」:重新拼写暴露出 fixture 本来就不 spec 合法,补上缺失的必填键。record-block-record-reach.test.tsx:样本补成{ picker: { object: PROBE_CHILD_OBJECT } }。头注释里这是该文件记录的第五次「a plausible value for EVERY input is not a plausible CONFIGURATION」;public-block-binding-reach.test.tsx:同样补成 spec 合法值,并给 ledgered 分支加崩溃守卫 —— 那一支的通过条件是「没有数据调用」,而崩掉的块恰好满足它,所以本 PR 若只补样本会留下一类空绿。守卫刻意只加在 ledgered 分支:object-form/object-master-detail-form在这个 fixture 下确实会渲染成错误卡(reading 'map'),但它们发出了真实数据调用、断言是挣来的,那是既有状态,记入 [finding]object-form与object-master-detail-form在 binding-reach 探针的自动 fixture 下渲染成错误卡(reading 'map')—— 断言仍成立,但两个分支的崩溃守卫因此没法一起开 #3840,不靠加宽守卫顺手裹进本 PR。渲染器那处裸取(
RelatedList.tsx:1299取add.picker.object,而同文件:378/:390是可选链)按 AGENTS.md #0.1 没有在本 PR 加宽容 —— 修法有三条(一致化守卫 / 显式诊断 / 产出端校验),定的是诊断契约与校验时机,另立 #3838。element:text_input.defaultValue的一处刻意收窄spec 是联合
string | number,ComponentInput.type是单一粗类型、表达不了。选了'string'(文本输入的常态,且 DOM 值本来就String(...)强转),number arm 写进 description 并有断言钉住。这是真收窄:sdui-parser的checkType会对defaultValue={42}报type-mismatchwarning,而 spec 接受这个值。同族标本(page:header.title/record:alert.title的内联翻译映射,今天就会被误报)与四条可能方向记入 #3832;本 PR 不在checkType侧加任何宽容。验证
反方向门跑下来 42 条断言全绿(原 12 条 + 新 6 类)。
围栏
content/docs/releases/**;⛔ 未触components/renderers/action/**(PR fix(components): 动作面成员动作的 visible 门按「已声明」判定,visible: false 不再渲染 (#3812) #3825 在闸);未改containers.tsx(B 类只进豁免)。packages/types的 mirror(RecordDetailsComponentProps缺hideFields那半落在 Migrate the remaining ListView legacy vocabulary to spec-canonical keys, and audit ObjectView/DetailView (#2231 phases 4–5) #2890 /record:details的layout发布了auto|custom语义,渲染器唯一的读点只认 spec 已退役的inline|compact—— auto/custom 从未被实装 #3818 的完成范围里,渲染器走(schema as any)读,本 PR 无需类型面改动)。@object-ui/plugin-detail+@object-ui/componentspatch(未声明major)。本 PR 过程中另立的 issue(均未认领,交 PM triage)
page:header.icon/page:card.actions:spec 声明、渲染器零读点,接线 / 声明+KNOWN GAP / 上游退役 三择一element:record_picker.filter是第 4 个 A 类缺口,#3808 的三类清单把它漏出了分类add.picker.filter全仓零读点,作者限定了候选范围而对话框提供全部记录(baseFilter是现成槽位)ComponentInput.type表达不了 spec 联合类型,page:header.title的内联翻译映射今天就被 manifest 门误报findingtargetVariable全仓零读点,发布与否是未定判断题add漏了add.picker→ 整段相关列表变错误卡(RelatedList.tsx:1299裸取)findingobject-form/object-master-detail-form在 binding-reach 探针 fixture 下渲染成错误卡,两分支崩溃守卫因此没法一起开另在 #3809 补了评论:同一个墓碑盲区在反方向的症状是假红,且
page:card.body那个标本在 pin 升级时会同时是一个假绿(body)和一个真红(children),一次处置解决两个。Generated by Claude Code