Skip to content

fix(plugin-detail,components): 补齐渲染器已兑现的四个 spec 键的 inputs 声明,并把 parity 门的反方向推到全仓 (#3808) - #3841

Merged
yinlianghui merged 2 commits into
mainfrom
claude/issue-3808-inputs-reverse-parity
Aug 8, 2026
Merged

fix(plugin-detail,components): 补齐渲染器已兑现的四个 spec 键的 inputs 声明,并把 parity 门的反方向推到全仓 (#3808)#3841
yinlianghui merged 2 commits into
mainfrom
claude/issue-3808-inputs-reverse-parity

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

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

spec 声明 渲染器读点 inputs 本 PR
record:details.hideFields z.array(z.string())(objectstack#5611) renderers/record-details.tsx:147 ✅ 声明
record:related_list.relationshipValueField ✅ 默认 'id' renderers/record-related-list.tsx:95 ✅ 声明,defaultValue: 'id'
record:related_list.add picker 必填 renderers/record-related-list.tsx:186 / :231 ✅ 声明 + 成员形状写进 description
element:text_input.defaultValue string | number renderers/basic/text-input.tsx:73/76/119 ✅ 声明

四条都是 #3407 的同一失效:manifest 与生成的 .d.ts 不提这个键 → 设计器面板里不存在;sdui-parser/src/validate.ts:74comp.inputs 里找不到它 → 对照写了这个键的作者报 unknown-prop;渲染器照样兑现。三个答案。

element:text_input 不在 PUBLIC_BLOCKS(public-blocks.ts:80 有成文理由),所以它的缺口不在 sdui.manifest.json,而在 JSX 页面编译器的白名单 —— renderers/layout/page.tsx:462getKnownTypes() + 同一份 inputs 现搭那份 manifest。manifest 链实跑(不落盘)确认:

published blocks: 57
PUBLISHED record:details.hideFields:                    block=yes input=yes type=array  desc=492 chars
PUBLISHED record:related_list.relationshipValueField:   block=yes input=yes type=string desc=501 chars
PUBLISHED record:related_list.add:                      block=yes input=yes type=object desc=1075 chars
PUBLISHED element:text_input.defaultValue:              block=NO (non-public tier) input=no
runtime blocks: 505
RUNTIME  record:details.hideFields:                    block=yes input=yes type=array
RUNTIME  record:related_list.relationshipValueField:   block=yes input=yes type=string
RUNTIME  record:related_list.add:                      block=yes input=yes type=object
RUNTIME  element:text_input.defaultValue:              block=yes input=yes type=string
RUNTIME  element:record_picker.filter:                 block=yes input=no      ← 见豁免 #3830

record:related_list.add 的核对结论:A 类,同修

#3808 正文写「也可能属于这一类,没细查」。细查结论是属于,而且是四条里最要紧的一条:add 是构建 junction 指派列表的唯一途径,不声明就等于那个功能只能靠写一个不可发现的键来用。三个成员键渲染器全兑现(RelatedList.tsx:378 / :390 / :724),第四个不兑现。

description 从渲染器取材而非照抄 spec,三处刻意的差异:

  1. picker.labelField 的默认值name(RelatedList.tsx:390),不写 spec .describe() 说的「the object title field」—— 后者没有任何代码实现;
  2. picker.filter 写成 KNOWN GAP:spec 声明「Restrict which records the picker offers」,全仓零读点(RelatedListpicker.object / labelField 传给 record picker 对话框,从不填它的 baseFilter)。按 record:activity.showSubscriptionToggle 先例把 gap 说出口 —— 静默把它列进成员清单,等于告诉作者候选被限定了,而实际上对话框提供全部记录。接线另开 record:related_list.add.picker.filter 全仓零读点:作者限定了 Add 选择器的候选范围,对话框照样提供该对象的全部记录 #3831;
  3. 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.icon containers.tsx:822-1570icon 只出现在每个 action 自己的 action.icon(:1321/:1365)与导航项 item.icon(:604);block 自己的 icon 零读点
page:card.actions PageCardRenderer(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.type 与同文件正方向的 page:tabs.tabStyle 豁免是同一事实的两面:扁平载体里 type 是分发键(SchemaRenderer.tsx:251-270 明确拒绝 hoist),且 validate.tsBASE_PROPS'type',声明了也校验不到

ADR-0087 墓碑(3 条)→ objectstack#5775 + #3809

element:record_pickerdisplayField / 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.targetVariablegrep -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:78ds.filter ?? props.filter:103query.$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 的声明:

× publishes `hideFields`, which the renderer has read all along
× record:details publishes every top-level key its spec props schema declares
× the four keys objectui#3808 declared are discoverable, block by block
AssertionError: expected [ 'hideFields' ] to deeply equal []
AssertionError: record:details does not publish hideFields
 Tests  3 failed | 48 passed (51)

三处一起红:派生的全仓门、按名钉住的那条、块级 parity。这一跑还抓出一个空绿:同文件的「hideFields 只教裸名」断言在声明被删后仍然绿,因为 not.toContain('{')'' 恒真。已加 expect(description).not.toBe('') 前置,并在注释里写明是这次反向验证发现的。

2. 豁免塞无理由条目 → 豁免纪律断言红(预判:恰好一条红。命中)

page:tabs.type 的理由改成不含 issue 号:

× every unpublished-key exemption states a reason and references a tracking issue
AssertionError: expected [ 'page:tabs.type' ] to deeply equal []
 Tests  1 failed | 41 passed (42)

3. 给已声明的键塞豁免 → 陈旧豁免 + 按名钉住 两条红(预判:2 条。命中)

× carries no stale unpublished-key exemption — a published key must lose its entry
× the four keys objectui#3808 declared are discoverable, block by block
AssertionError: expected [ 'record:details.hideFields' ] to not include 'record:details.hideFields'
 Tests  2 failed | 40 passed (42)

第二条是刻意的:它把「用豁免代替声明」这条退路堵死 —— 派生断言本身对「补进豁免名单」也会绿,而那正是本单要排除的动作。

4. public-block-binding-reach 的崩溃守卫 —— 预判红,实测绿,按实情记账

把那里的 add 样本退回 {},预判它会触发新加的崩溃守卫。实测 16 个全绿。原因查清了:renderers/record-related-list.tsx:185 传的是 dataSource={ctx?.dataSource},这个探针不带 RecordContext,于是 RelatedList 收到 dataSource: undefined,:1293add && dataSource 短路,裸取根本到不了。所以那条守卫今天是防御性的、不是承重的,已在注释里如实写明(而不是留一句「它会红」)。守卫的判据本身可用 —— 把它同时加到两个分支上时,它报出了 #3840 里那两个崩溃。

崩溃本身是真的,在兄弟探针里实测到(那个探针带 RecordContext + dataSource):

Component "record:related_list" failed to render
Cannot read properties of undefined (reading 'object')

两个 console 探针的 fixture 处置

两者都按声明自动生成 fixture,type: 'object' 的通用样本是 {}add 一旦成为声明的 object 输入,它们就拿到了 {} —— 而 {} 不是合法的 add(spec 里 picker 必填)。按 fixture 三分法这属于「补声明」:重新拼写暴露出 fixture 本来就不 spec 合法,补上缺失的必填键。

渲染器那处裸取(RelatedList.tsx:1299add.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-parsercheckType 会对 defaultValue={42}type-mismatch warning,而 spec 接受这个值。同族标本(page:header.title / record:alert.title 的内联翻译映射,今天就会被误报)与四条可能方向记入 #3832;本 PR 不在 checkType 侧加任何宽容。

验证

pnpm exec vitest run apps/console/ packages/plugin-detail/ --maxWorkers=2
  Test Files  86 passed (86)
       Tests  774 passed (774)

pnpm exec vitest run packages/components/ --maxWorkers=2
  Test Files  98 passed (98)
       Tests  753 passed (753)

pnpm --filter @object-ui/components --filter @object-ui/plugin-detail --filter @object-ui/console run type-check
  packages/components type-check: Done
  packages/plugin-detail type-check: Done
  apps/console type-check: Done

node scripts/check-control-bytes.mjs
  ✅ OK (scanned 3746 tracked text file(s); skipped 85 binary)
node scripts/check-changeset-fixed.mjs && node scripts/check-changeset-no-major.mjs
  ✅ All workspace packages are in the changeset fixed group.
  ✅ No changeset declares a `major` bump.

eslint(8 个改动文件):0 errors, 45 warnings —— 全部是 plugin-detail/src/index.tsx
第 61-124 行 export 语句上既有的 react-refresh/only-export-components,与本改动无关。

反方向门跑下来 42 条断言全绿(原 12 条 + 新 6 类)。

围栏

本 PR 过程中另立的 issue(均未认领,交 PM triage)

# 类型 一句话
#3829 待决策 page:header.icon / page:card.actions:spec 声明、渲染器零读点,接线 / 声明+KNOWN GAP / 上游退役 三择一
#3830 缺陷 element:record_picker.filter 是第 4 个 A 类缺口,#3808 的三类清单把它漏出了分类
#3831 缺陷 add.picker.filter 全仓零读点,作者限定了候选范围而对话框提供全部记录(baseFilter 是现成槽位)
#3832 缺陷 ComponentInput.type 表达不了 spec 联合类型,page:header.title 的内联翻译映射今天就被 manifest 门误报
#3834 finding targetVariable 全仓零读点,发布与否是未定判断题
#3838 缺陷 写了 add 漏了 add.picker → 整段相关列表变错误卡(RelatedList.tsx:1299 裸取)
#3840 finding object-form / object-master-detail-form 在 binding-reach 探针 fixture 下渲染成错误卡,两分支崩溃守卫因此没法一起开

另在 #3809 补了评论:同一个墓碑盲区在反方向的症状是假红,且 page:card.body 那个标本在 pin 升级时会同时是一个假绿(body)和一个真红(children),一次处置解决两个。


Generated by Claude Code

…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>
@vercel

vercel Bot commented Aug 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Aug 8, 2026 7:42pm

Request Review

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

ℹ️ Console Performance Budget — not measured

This 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.

Step Outcome
Build packages success
Check console performance budget skipped

See the workflow run for details.


📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.66KB 3.13KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.35KB 1.07KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 26.07KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.65KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 482.38KB 106.31KB
core (index.js) 2.96KB 1.13KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 139.61KB 35.99KB
fields (index.js) 230.82KB 56.70KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.65KB 1.06KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 9.48KB 3.27KB
i18n (useObjectLabel.js) 27.59KB 6.63KB
i18n (useSafeTranslation.js) 4.52KB 1.96KB
layout (index.js) 38.53KB 10.71KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 8.75KB 3.06KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 44.98KB 12.37KB
plugin-charts (index.js) 61.04KB 17.31KB
plugin-chatbot (index.js) 180.33KB 42.79KB
plugin-dashboard (index.js) 117.21KB 30.27KB
plugin-designer (index.js) 210.51KB 42.51KB
plugin-detail (index.js) 236.14KB 58.81KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 112.10KB 27.10KB
plugin-gantt (index.js) 162.55KB 39.57KB
plugin-grid (index.js) 187.63KB 49.66KB
plugin-kanban (index.js) 48.30KB 13.28KB
plugin-list (index.js) 105.12KB 25.48KB
plugin-map (index.js) 16.81KB 5.24KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 40.58KB 10.58KB
plugin-timeline (index.js) 25.76KB 7.33KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 84.03KB 20.55KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 19.28KB 6.38KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.02KB 0.55KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 2.71KB 1.34KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…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>

Copy link
Copy Markdown
Collaborator Author

返工记账(实施 agent 自述,便于 review 时核对):首推的 CI 两项红是同一个原因,且是我的流程错误,不是环境问题。

4 条类型错

上一 commit 让 dataCallsFor 返回 { calls, html },但注解还是 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

第 4 条是前 3 条的连带:calls 恢复成 string[] 之后,c 自然推成 string

Bundle Analysis 不是独立失败

.github/workflows/performance-budget.yml 的第一个实质步骤是 pnpm --filter @object-ui/console build,而 console 的 buildtsc && vite build && pnpm build:plugin —— 它在 tsc 就停了,根本没走到打包。所以是同 4 条错的连带,未盲目重跑。

修法

给函数一个命名返回类型,而不是放宽任何东西:interface Mount { calls: string[]; html: string },形状与字段名刻意照抄兄弟探针自己的 Mount(record-block-record-reach.test.tsx:310-313)—— 那个文件从一开始就同时捕获两半,理由正是本文件现在需要 html 的理由:崩溃在调用日志里是隐形的(SchemaRenderer 捕获抛出、画错误卡,崩掉的块只是"没有数据调用")。无 as any,未放宽签名。

我漏了什么(根因)

上一 commit 的 type-check 是在编辑 public-block-binding-reach.test.tsx 之前跑的(时间线:type-check → 全量测试 → 发现 record:related_list 崩溃 → 改两个探针 fixture → 再跑测试),之后没有重跑 type-check,只重跑了 vitest。而且我用的是 pnpm --filter <三包> run type-check 而非 CI 的 pnpm type-check(全仓 turbo 链)。测试绿掩盖了类型红,因为 vitest 走 esbuild 转译、不做类型检查。

本次改用 CI 同命令复验:

pnpm type-check                        → Tasks: 78 successful, 78 total
pnpm --filter @object-ui/console build → exit 0(tsc + vite build + build:plugin 全过)
console 入口 gzip 28.1 KB / 预算 350 KB → PASS(按 workflow 里同一段逻辑本地算的)
vitest(门 + 两个探针 + 三个块级 parity)→ 6 files / 95 tests passed
check-control-bytes.mjs                → ✅ OK(3749 files)
eslint(改动文件)                       → 0 errors(5 warnings 全是既有 no-explicit-any)

行为面零变化:这次只动类型注解与注释,断言、fixture、豁免名单一字未改。


Generated by Claude Code

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 28.1 KB 350 KB
Entry file index-D4iBQgXH.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.66KB 3.13KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.35KB 1.07KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 26.07KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.65KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 482.41KB 106.33KB
core (index.js) 2.96KB 1.13KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 139.61KB 35.99KB
fields (index.js) 230.82KB 56.70KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.65KB 1.06KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 9.48KB 3.27KB
i18n (useObjectLabel.js) 27.59KB 6.63KB
i18n (useSafeTranslation.js) 4.52KB 1.96KB
layout (index.js) 38.53KB 10.71KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 8.75KB 3.06KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 44.98KB 12.37KB
plugin-charts (index.js) 61.04KB 17.31KB
plugin-chatbot (index.js) 180.33KB 42.79KB
plugin-dashboard (index.js) 117.21KB 30.27KB
plugin-designer (index.js) 210.51KB 42.51KB
plugin-detail (index.js) 236.14KB 58.81KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 112.10KB 27.10KB
plugin-gantt (index.js) 162.55KB 39.57KB
plugin-grid (index.js) 187.63KB 49.66KB
plugin-kanban (index.js) 48.30KB 13.28KB
plugin-list (index.js) 105.12KB 25.48KB
plugin-map (index.js) 16.81KB 5.24KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 40.58KB 10.58KB
plugin-timeline (index.js) 25.76KB 7.33KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 84.03KB 20.55KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 19.28KB 6.38KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.02KB 0.55KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 2.71KB 1.34KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Collaborator Author

✅ 验收通过(objectui 分片 PM,session_01GTRjn8xBqp75dk7kFupVRt)—— undraft + auto-merge(返工 1 轮)。

返工核验:修法干净 —— 命名 interface 照抄兄弟探针自身的 Mount 形状(非 as any 非放宽签名),+18/-2 单文件,A 类四键/门/豁免一字未动;Bundle Analysis 红经读 workflow 定义证实为同因连带(Build Console = tsc && vite build,tsc 即停),未盲目重跑;复验用 CI 同命令(全仓 pnpm type-check 78/78)。终态 19/19 全绿含双红转绿。

首推验收要点一并落档(前跳已核):A 类扩为四键(add 核对为 A 并同修,picker.labelField 默认值取渲染器实况而非 spec 散文);反方向门 42 断言 + 9 条带单号豁免 + 陈旧豁免自动红 + 「用豁免代替声明」被封死;四轮反向验证含一处「预判红实测绿」的机制记账(防御性守卫非承重);对 issue 自身三处错述(墓碑不消失/计数)的更正成立。七张衍生单(#3829-#3832/#3834/#3838/#3840)+ #3809 追评立单边界合规。

方法库入账:①「末次编辑之后必须重跑 type-check,且用 CI 同命令(全仓链)」—— vitest esbuild 转译不做类型检查,测试绿掩盖类型红;②豁免名单依赖 #3829/#3830/#3834 保持 open(门只校验单号存在不校验状态)—— 关闭其中任一须同时处理键本身。⚠️ #3830emptyText/sort/limit 建议与 spec pin 提升单同排。


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 8, 2026 19:55
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 8, 2026
Merged via the queue into main with commit aca561a Aug 8, 2026
20 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3808-inputs-reverse-parity branch August 8, 2026 19:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants