Skip to content

fix(studio,timeline,list): 表单设计器解析对象翻译;timeline 认它自己配置的日期字段 (#3134, #3129) - #3175

Merged
os-zhuang merged 5 commits into
mainfrom
claude/v17-render-defects-batch8
Aug 2, 2026
Merged

fix(studio,timeline,list): 表单设计器解析对象翻译;timeline 认它自己配置的日期字段 (#3134, #3129)#3175
os-zhuang merged 5 commits into
mainfrom
claude/v17-render-defects-batch8

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

修复本批次两个渲染缺陷:#3134(Studio 表单布局设计器不解析对象翻译)与 #3129(timeline 不按配置的日期字段分桶)。

#3144 不在本 PR 内 —— 它已于今日 11:08 由另一会话在 #3147 修复并 closed,本 PR 未认领、未触碰。


#3134 —— 表单布局设计器未解析对象字段/分组翻译

根因(与 issue 原文一致)

issue 的归因是对的,且定位精确。ObjectFormDesigner 自称是"最终用户表单的预览",但它直接读原始草稿元数据:

  • 字段卡片读 entry.def.label
  • 分组标题读 g.label || g.key
  • 拖拽浮层读 activeEntry.def.label

而同一对象的其他所有表面(ObjectFormRecordDetailView、数据视图)都先走项目的对象翻译。于是一个已完整翻译的对象,在布局画布上显示 Opportunity Name / Basic Information,点开一层却是 商机名称 / 基本信息

改动

三处显示点改走 useSafeFieldLabel() —— 与运行态表单同一个解析器:fieldLabel() 解析字段卡片与拖拽浮层,sectionLabel() 解析分组标题,无翻译时回落到作者写的元数据 label。查找根是对象 API 名:StudioDesignSurface 现在显式传 objectName={current.name},草稿体尚未命名时也能解析,并保留 draft.name 作为回落。

useSafeFieldLabel(而非 useObjectLabel)是因为设计器也会在无 I18nProvider 的测试/预览里挂载,这时它退化成恒等回落。


#3129 —— timeline 不按配置的日期字段分桶

⚠️ issue 的归因不完整,请以实测为准

issue 说该视图声明了 timeline.startDateField: "start_date",问题"isolated to the timeline path"。这条路径实测是好的。 我按三层做了复现:

验证内容 结果
ObjectTimeline 单体 嵌套 timeline.startDateField + 扁平 startDateField ✅ 正确分桶
ListView 出参 转发的绑定 + $select 投影 $selectstart_date,绑定正确
ListView → 真实 ObjectTimeline 组合 端到端渲染出的分桶表头 Later,非 No date

也就是说:声明 timeline.startDateField 时,渲染路径本来就是对的。照 issue 原文去改渲染器会改错地方。

实测复现到的真正缺陷

把四种产品实际接受的书写形态逐一跑出来,第四种复现了报告的症状:

# 书写形态 $select 分桶(修复前)
A view.timeline.startDateField(spec) start_date Later
B view.options.timeline.startDateField(app-shell 产出) start_date Later
C view.options.timeline.dateField(legacy 别名) start_date Later
D view.timeline.dateField(spec 嵌套 + legacy 键) start_date No date

dateFieldstartDateField#2231 前遗留别名。它并非野生形态:@object-ui/typesListViewTimelineConfig 明确在嵌套配置上声明了它,app-shell 与 plugin-view 两个 ObjectView 读取点也都解析它。真正驱动时间轴的两个读取点却不解析:

  • ObjectTimeline 只在扁平 prop(schema.dateField)上认这个别名,嵌套的 schema.timeline 上不认;
  • ListView 只从 options.timeline 里取,不从 spec 正典的 schema.timeline 取 —— 能力门禁(capability gate)也有同一个缺口,所以这种视图可能连 Timeline 选项都不会被提供。

于是形态 D 一路穿透到调用方的默认值(created_at / due_date)。该字段通常不在投影里,每行都取不到值,dateBucket() 收到 undefined → 全部落进 No date,而配置的日期就原封不动躺在行里。

这同时解释了 issue 里那句"metadata-only 地把两个日期字段加进投影,行为没变":投影里本来就有正确字段,渲染器读的是另一个键 —— 加字段当然没用。这个吻合度是本次归因的主要证据。

改动

两个读取点在它们已经用于 options.timeline.dateField 的同一优先级位置解析该别名;spec 键在两者同时存在时仍然胜出。能力门禁补同一个缺口。ObjectTimelineschema.timeline 类型从 TimelineConfig 收敛到 ListViewTimelineConfig —— 即 types 包一直声明、而渲染器一直没读的那个形状。

仍未证实的部分(如实标注)

若 HotCRM 已发布的 campaign_timeline 确实带的是 startDateField 而非 dateField,那么本 PR 修的就是一个同症状的相邻缺陷,而非该视图的直接病因;那种情况下剩余嫌疑是 viewDef.timeline 根本没走到 app-shell 的 ObjectView(它随后把轴钉死在自己的 'due_date' 默认值上,crm_campaign 没有该字段,症状完全一致)。我没有跑过 HotCRM 实例,也未取得该视图已发布的 JSON(请求挂载 hotcrm 仓库被拒绝),所以这一支属于未实测。要坐实它,需要 crm_campaign/view/campaign_timeline 的已发布视图定义。


验证证据

全部为本分支当前代码的真实运行输出,非誊抄。

pnpm type-check —— 全绿:Tasks: 78 successful, 78 total

受影响包的测试套件:

npx vitest run packages/plugin-timeline packages/plugin-list --maxWorkers=2
  Test Files  21 passed (21)
       Tests  292 passed (292)

npx vitest run packages/app-shell --maxWorkers=2
  Test Files  252 passed (252)
       Tests  2144 passed | 7 skipped (2151)

(app-shell 运行中打印的 DOMException [NetworkError] ... localhost:3000 是既有噪音:部分用例故意打不可达地址,252 个文件全通过。)

#3129 修复前后的分桶实测(同一套探针,仅回退渲染器改动):

修复前                                    修复后
A  buckets = Later                        A  buckets = Later
B  buckets = Later                        B  buckets = Later
C  buckets = Later                        C  buckets = Later
D  buckets = No date   ← 复现              D  buckets = Later

回归测试撤销后的失败条数

两处都验证过"撤掉修复后必须变红":

顺带说明:这两个缺陷当初为什么能带着绿灯发布

ObjectTimeline.test.tsx./renderer 替换成只渲染 item.title 的桩件,因此它那条"uses spec-compliant startDateField property"用例,无论日期绑定有没有解析成功都会通过 —— 日期只存在于真实渲染器输出的分桶表头里。ListView.test.tsx 的 timeline 用例则只断言视图切换器里出现了 Timeline 选项,从未检查转发下去的绑定。所以"整条时间轴全部落进 No date"这件事在既有测试下是完全不可见的。

新增的两套回归测试因此刻意不 mock 渲染器:plugin-timeline 那套读真实渲染器输出的分桶表头,plugin-list 那套用注册表 spy 钉住转发的绑定与 $select

边界

未移除/重命名任何可作者化的 spec key,未改公开导出,无协议破坏性变更。dateField 仍是既有的 deprecated 别名 —— 本 PR 只是让它在两种嵌套下解析一致,spec 键始终优先。


Generated by Claude Code

claude and others added 5 commits August 2, 2026 01:03
)

ObjectFormDesigner read `entry.def.label` / `group.label` straight off the
object draft, so a fully translated object still rendered its English source
labels on the layout canvas while every other surface resolved the project's
object translations. Route field cards (and the drag overlay) through
`fieldLabel()` and section headers through `sectionLabel()` — the same resolver
ObjectForm and RecordDetailView use — with the authored metadata label as the
fallback. StudioDesignSurface passes the object's API name explicitly so the
lookup root survives an unnamed draft body.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gEHJN2NFpS9VMeURvakgD
… nesting (#3129)

`dateField` is the pre-#2231 alias for `startDateField`. `@object-ui/types`
declares it on the nested config and both ObjectView read-sites resolve it, but
ObjectTimeline read it only off the flat prop and ListView only out of
`options.timeline` — including in the capability gate. A view authored as
`timeline: { dateField: … }` therefore fell through to the caller's default
(`created_at` / `due_date`), which the projection does not request, so every
record bucketed into "No date" with the configured date still in the row.

Also drops the reproduction scratch in favour of two real regression suites.
The pre-existing ObjectTimeline test stubs out `./renderer` and asserts only
item titles, so the date binding had no behavioural coverage at all — which is
how an all-"No date" timeline shipped green. The new plugin-timeline suite
renders through the real renderer and reads the bucket headers; the plugin-list
suite spies on the registry to pin the forwarded binding and the $select.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gEHJN2NFpS9VMeURvakgD
… type-check

`ObjectTimelineProps` does not declare `data` (the component reads it off the
rest args), so the regression suite has to apply it untyped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gEHJN2NFpS9VMeURvakgD
@vercel

vercel Bot commented Aug 2, 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 2, 2026 1:31am

Request Review

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

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

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.45KB 3.08KB
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.12KB 3.41KB
auth (LoginForm.js) 17.86KB 5.29KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.43KB 2.09KB
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) 18.38KB 4.49KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 3.65KB 1.42KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.25KB 0.53KB
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) 475.95KB 104.41KB
core (index.js) 2.25KB 0.80KB
create-plugin (index.js) 9.28KB 2.98KB
data-objectstack (index.js) 136.20KB 34.74KB
fields (index.js) 223.45KB 54.66KB
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.46KB 0.96KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 5.37KB 1.72KB
i18n (useObjectLabel.js) 26.14KB 6.07KB
i18n (useSafeTranslation.js) 3.26KB 1.44KB
layout (index.js) 38.44KB 10.66KB
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.05KB 1.53KB
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.90KB 12.35KB
plugin-charts (index.js) 60.53KB 17.12KB
plugin-chatbot (index.js) 180.09KB 42.72KB
plugin-dashboard (index.js) 111.86KB 28.81KB
plugin-designer (index.js) 210.51KB 42.50KB
plugin-detail (index.js) 224.52KB 54.98KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 111.38KB 26.93KB
plugin-gantt (index.js) 162.26KB 39.53KB
plugin-grid (index.js) 184.69KB 48.91KB
plugin-kanban (index.js) 47.82KB 13.18KB
plugin-list (index.js) 104.87KB 25.31KB
plugin-map (index.js) 16.80KB 5.24KB
plugin-markdown (index.js) 13.65KB 4.67KB
plugin-report (index.js) 40.47KB 10.57KB
plugin-timeline (index.js) 25.76KB 7.32KB
plugin-tree (index.js) 8.34KB 2.82KB
plugin-view (index.js) 83.54KB 20.39KB
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
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.46KB 1.21KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 0.20KB 0.18KB
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

@os-zhuang
os-zhuang marked this pull request as ready for review August 2, 2026 01:38
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 2, 2026
Merged via the queue into main with commit 5cb75b3 Aug 2, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the claude/v17-render-defects-batch8 branch August 2, 2026 01:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants