Skip to content

fix(plugin-detail): record:details 的 sections 说明改为 spec 的对象形,不再教已退役的 Section IDs (#3807) - #3820

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-3807-record-details-sections-desc
Aug 8, 2026
Merged

fix(plugin-detail): record:details 的 sections 说明改为 spec 的对象形,不再教已退役的 Section IDs (#3807)#3820
yinlianghui merged 1 commit into
mainfrom
claude/issue-3807-record-details-sections-desc

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #3807

record:detailssections input description 教的是 spec 已经退役的「Section IDs」形状。inputs 不是文档而是发布出去的编写契约(gen-manifest.tssdui.manifest.json 保存门 + parser 白名单 + sdui-intrinsics.d.ts),而 pin 版 @objectstack/spec@17.0.0-rc.5RecordDetailsProps.sections 是对象数组 —— objectstack#5611 把 z.array(z.string()) 那条拼法删掉而不是 union 进来(既无 producer 也无 consumer,一种形状而不是两套事实契约)。照旧说明写 sections: ['contact_info','address'] 的作者,四层都拿不到诊断,最后得到一张没有报错的空白详情页(layout: 'custom' 时 sections 是正文的唯一来源)。

做法沿用 #3407 / PR #3795record:highlights.fields 上确立的范式:description 从 spec 形状派生着写,并配一条两方向都在运行时从 spec schema 取期望值的 parity 测试。

三方对照:spec 成员键 ↔ 新 description ↔ 渲染器实读

spec 侧全部实读自装好的 node_modules/@objectstack/spec@17.0.0-rc.5(不是 objectstack 仓库的 HEAD),渲染器侧实读自 origin/main 5147d93packages/plugin-detail/src/renderers/record-details.tsx

spec sections[] 成员 新 description 怎么写 渲染器实读点
name? string,snake_case,「resolves objects.{object}._sections.{name}.label」 「stable snake_case identifier and the i18n anchor —— 标题走 objects.{object}._sections.{name}.label,没写 name 的 section 在所有 locale 下都显示 authored label」 sectionLabel(objectName, s.name, rawTitle ?? s.name);键约定在 packages/i18n/src/useObjectLabel.ts:419
label? I18nLabel,「omit for an untitled, borderless section」 「section heading;省略即无标题、无边框」 s.title ?? s.labelpickLocalizedshowBorder: s.showBorder ?? (translatedTitle ? true : false)
columns? int 1-4,「Omitted → the renderer derives the width」 「本 section 的字段栅格宽度(1-4),省略则由渲染器推导」 ...s 透传进 synthesized section → DetailSection.tsx:190 applyDetailAutoLayout(visibleFields, section.columns)
fields string[],必填,「in order」 「必填,本 section 按序渲染的字段名」 dropHidden(normaliseList(filterList(s.fields)))
—— string 元素已被删除 —— 明确写出「a bare section-id string is NOT accepted」并给出后果 没有任何 string 分支;字符串上 s.name / s.label / s.fieldsundefined → 该 section 零字段

顶层 parity:spec 顶层键 = columns / layout / sections / fields / hideFields / aria;本 block 声明 4 个,全部在 spec 内,所以反方向(声明 spec 不接受的顶层键)本来就绿,PR #3806 的全仓门实跑仍绿。hideFields 未声明属 #3808 的范围,本单不夹带;aria 的省略是既有决定(无障碍逃生口,不是布局选择),同文件下方已有说明。

故意不写进 description 的三个键:渲染器另外还认 s.title / s.showBorder / s.hideEmpty,但 spec 的 section 对象没有声明它们 —— safeParse({ sections: [{ label:'Contact', fields:['phone'], title:'T', showBorder:true, hideEmpty:false }] }) 成功但条目只剩 { label, fields }(实跑,测试里钉住了)。发布它们等于教作者写契约会丢弃的键,与同文件下方「顶层 readonly 不声明」是同一条理由;渲染器容忍不等于可以教。

围栏内核对:同处的 fields(:250)没有同漂,故不改

  • 语义一致:spec fields 的 describe 是 Explicit field list to display (optional, overrides highlightFields),input 写的是 Explicit field list (overrides highlightFields) —— 同一件事,没有退役形状、也没有漏掉的成员键。
  • 没有成员形状可发布:spec 侧是 z.array(z.string()),元素是纯字符串(测试里用 arrayElement(...)shapeKeys 为空钉住)。
  • 渲染器的 normaliseField 容忍 {name} / {field} 条目,但 spec 按值拒(safeParse({ fields: [{ name:'phone' }] }).success === false,实跑)。按契约优先,这种容忍不写进说明,否则就是发布第二套事实契约;测试里把「fields 说明不出现成员形状」也钉住了。

测试

新增 packages/plugin-detail/src/__tests__/recordDetailsInputs.spec-parity.test.ts(PR #3795 那条的 sibling,7 条):前提守卫(spec 真的只收对象形、退役拼法真的被按值拒)、每个 spec 成员键都能从说明里发现、说明不再教 section-id 拼法、渲染器独有的三个键被 parse 剥掉且不得出现在说明里、不声明 spec 不接受的顶层 input、fields 不发布成员形状。期望值全部运行时从 spec schema 派生。

一处值得写下来的细节:「不得出现」这个方向用的是词边界而不是子串 —— 第一次实跑就红在 title,因为说明里的 untitled 内含 title。判 key 是否被「教」要用 \b;正方向(每个 spec 键都能被发现)保留子串匹配(与 PR #3795 一致,那边的假阳性只会放过一个确实提到了该键的说明)。

反向验证(方向先判后跑)

先判:把 description 退回 'Section IDs to show (required when layout is "custom")',应当恰好两条红 —— 派生的「成员键可发现」(四个键全部 undocumented)和显式的「不再教 section-id 拼法」;其余五条绿,因为它们的判据(spec 前提、parse 剥键行为、顶层 parity、fields)与这段文本无关。

实跑一致:

 FAIL  … > every spec section member key is discoverable from the `sections` description
AssertionError: expected [ 'name', 'label', 'columns', 'fields' ] to deeply equal []
+   "name",
+   "label",
+   "columns",
+   "fields",

 FAIL  … > the `sections` description no longer teaches the retired section-id spelling
AssertionError: expected 'Section IDs to show (required when la…' not to match /section ids/i
+ Received: "Section IDs to show (required when layout is \"custom\")"

 Test Files  1 failed (1)
      Tests  2 failed | 5 passed (7)

恢复后 7 passed (7)

其他实跑

  • pnpm vitest run packages/plugin-detail --maxWorkers=2Test Files 57 passed (57) / Tests 475 passed (475)(输出里的 ECONNREFUSED 127.0.0.1:3000 是既有 fixture 噪声,非本改动)
  • pnpm --filter @object-ui/plugin-detail type-checktsc --noEmit && tsc -p tsconfig.typetests.json 无输出即通过
  • pnpm vitest run apps/console/src/__tests__/registry-inputs-spec-parity.test.ts21 passed(PR test(console): registry inputs 与 spec ComponentPropsMap 的 parity 门推到全仓,4 个 OFF-SPEC block 逐块判定 (#3797) #3806 的全仓 inputs × spec 门,消费半径内)
  • manifest 生成链实跑(manifestFromConfigs + generateDts,即 scripts/gen-manifest.ts / dev/manifest-dump.tsx 用的同一对,不落盘):record:details 在 public tier,新说明整段出现在 components['record:details'].inputs[sections].description。附一句诚实的边界:generateDts 只发 sections?: unknown[]、不带 JSDoc,所以这段说明的落点是 manifest(以及读 manifest 的作者/AI),不是 intrinsics 的类型注释。
  • node scripts/check-control-bytes.mjsOK (scanned 3737 tracked text file(s));另对三个改动文件 grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]' 零命中
  • pnpm --filter @object-ui/plugin-detail lint0 errors(707 条既有 warning,新测试文件零 warning,index.tsx 的 4 条 warning 都在 26/42/46/52 行,与本改动无关)

Changeset:.changeset/record-details-sections-description-3807.md(@object-ui/plugin-detail patch,与 PR #3795 同档位;authoring 面 description 修正,无运行时行为改动)。

顺带发现(未夹带,已另开 unassigned issue)


Generated by Claude Code

…Section IDs (#3807)

`inputs` 是发布出去的编写契约(gen-manifest.ts → sdui.manifest.json 保存门 +
sdui-intrinsics.d.ts),而 record:details.sections 的说明写的是
`Section IDs to show (required when layout is "custom")` —— 17.x 以前的形状。
pin 版 @objectstack/spec@17.0.0-rc.5 的 RecordDetailsProps.sections 是对象数组
`{ name?, label?, columns?, fields }`;objectstack#5611 把 z.array(z.string())
那条拼法删掉而不是 union 进来(既无 producer 也无 consumer)。

照旧说明写 `sections: ['contact_info','address']` 的作者四层都拿不到诊断:
manifest 门只看顶层键名 + 粗类型(字符串数组是合法 array),上游
validateComponentProps 是 advisory 级,spec 只在真走 parse 的路径上才拒,而
RecordDetailsRenderer 对每个条目读 s.name / s.label / s.fields —— 字符串上三者
全 undefined,该 section 一个字段都不渲染。layout: 'custom' 时 sections 是正文
唯一来源,结果就是一张没有报错的空白详情页。

新说明逐键派生自 spec 各成员的 .describe() 与渲染器实读:fields 必填按序;
label 是标题(省略即无标题无边框);name 是 snake_case 稳定标识与 i18n 锚点
(sectionLabel → objects.{object}._sections.{name}.label,useObjectLabel.ts:419);
columns(1-4)是本 section 的字段栅格宽度(DetailSection 的
applyDetailAutoLayout(visibleFields, section.columns)),省略则由渲染器推导;
并明确写出字符串条目不被接受。渲染器另外还认的 title / showBorder / hideEmpty
故意不写进说明:spec 的 section 对象没声明它们,parse 时静默剥掉,发布它们等于
教作者写契约丢弃的键(与本文件下方"顶层 readonly 不声明"同一条理由)。

recordDetailsInputs.spec-parity.test.ts 是 PR #3795(record:highlights)那条
sibling,两个方向都在运行时从 spec schema 派生:每个 spec 成员键都能从说明里
发现;本 block 不声明 spec 不接受的顶层 input。另外三条把这次的判断钉住 ——
退役拼法真的被 spec 按值拒(safeParse 红)、对象形保留、渲染器独有的三个 section
键确实会被 parse 剥掉且不得出现在说明里(词边界匹配,因为 untitled 内含 title)。

围栏内核对结论:同处 fields 的说明与 spec 的
"Explicit field list to display (optional, overrides highlightFields)" 语义一致,
元素是纯字符串、没有成员形状可发布,故不改;渲染器对 {name}/{field} 条目的容忍
不是第二套契约(spec 按值拒),测试里也钉了这一点。hideFields 未声明属 #3808。

仅说明文本变化,无运行时行为改动。

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 5:46pm

Request Review

@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-dOJWzkVi.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) 481.72KB 106.05KB
core (index.js) 2.96KB 1.13KB
create-plugin (index.js) 9.85KB 3.18KB
data-objectstack (index.js) 139.51KB 35.97KB
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) 233.78KB 57.92KB
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.74KB 49.69KB
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。

核验:净 diff 3 文件;description 逐键派生自实装 spec(含 string 元素被拒的实跑证据),渲染器独有键(title/showBorder/hideEmpty)故意不发布并被测试钉住 —— 与 #3795 同一契约优先姿态,且 RENDERER_ONLY_SECTION_KEYS 用 spec 运行时过滤防钉住过时禁令,设计正确;fields 核对「未同漂不改」三点论证成立(渲染器容忍 ≠ 契约,不发布第二方言)。7 条 spec 推导测试,反向验证 2 红精确命中,词边界假阳性(untitled 含 title)中途修正如实记录;manifest 携带经实跑 gen 链证实,含「落点是 manifest 非 .d.ts JSDoc」的诚实边界。19/19 CI 全绿;changeset patch 同 #3795 档位。

衍生:#3818(layout 发布 auto|custom 但渲染器读退役词表,三条互斥路线)、#3819(Studio sections 设计器缺 name 锚点)归分诊席判级;#3808(hideFields 等 A 类)在本 PR 落 main 后解锁可派。


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 8, 2026 17:53
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 8, 2026
Merged via the queue into main with commit 4178d5a Aug 8, 2026
20 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3807-record-details-sections-desc branch August 8, 2026 17:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants