Skip to content

fix(plugin-detail): 声明的展示 type 只能收窄内联编辑,永远不得放宽 (#3355) - #3357

Merged
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-3355-type-gate-narrow
Aug 4, 2026
Merged

fix(plugin-detail): 声明的展示 type 只能收窄内联编辑,永远不得放宽 (#3355)#3357
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-3355-type-gate-narrow

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

Fixes #3355

关联:objectstack-ai/objectstack#5077(上游一家的现场)、下游 yinlianghui/hotcrm-heimao#61(被手工覆写的 rollup)、objectui#3356(同一条链上的 readonly 修复,已合并)。

问题

两个同形的可编辑性门(HeaderHighlight 表头高亮条、DetailSection 详情正文)此前都把「有效类型」收敛成一个值,用的是展示优先级 viewFieldType || objectFieldType。于是作者写在视图条目上的展示 type替换掉对象字段的类型,把对象上 formula / summary / rollup / auto_number 的声明从门的视野里抹掉,平台维护的列因此变成可内联编辑。

这不是假想:报 objectstack#5077 的那家 app 就是这么发的 —— 为了绕过 objectstack#5066 的展示格式问题,他们在条目上写了 { name: 'supply_share', type: 'number' },而 supply_share 是 hook 维护的 rollup。表头 chip 于是可写,值被手工覆写后一直错着,直到一次无关的子行变更重新触发 rollup 才被纠正。

修法(issue 里的 Option A,维护者 2026-08-04 定调)

门改成取两个类型的并集:authored 条目类型 或 对象字段类型任一为计算类型,即不可编辑。渲染器/编辑器的选择保持原有优先级不变,所以展示效果一点不变 —— 变的只是「谁能写」。

规则一句话:声明的展示 type 只能收窄可编辑性,永远不得放宽。 对象 schema 才是「这一列是否由机器维护」的权威;展示层的覆写没有资格授予写权限。收窄仍然有效 —— 在普通列上写 type: 'formula' 依旧能锁死它。

共享而非镜像

新增 isComputedFieldType(viewFieldType, objectFieldType),放在 packages/plugin-detail/src/fieldEnrichment.ts —— 正是两个宿主已经共享、且当初就是为了阻止这类 drift 而建的模块(issue 正文点名了这一点)。计算类型集合 TEXTUAL_REF_FALLBACK_TYPES 一并挪到那里(InlineFieldInput 原样 re-export,包的公开导出名不变),这样「渲染回退」和「两个可编辑性门」读的是同一份定义,不可能再各修各的。

改动文件:

  • packages/plugin-detail/src/fieldEnrichment.ts —— 集合 + 新 helper(带完整的来龙去脉注释)。
  • packages/plugin-detail/src/HeaderHighlight.tsx —— 门改用 helper,传 field.typeobjectDefField?.type 两个值;resolvedType 仍留给渲染器选择用。
  • packages/plugin-detail/src/DetailSection.tsx —— 同上,替掉原来塌缩过的 inlineEditType
  • packages/plugin-detail/src/InlineFieldInput.tsx —— 改为从 fieldEnrichment 导入并 re-export 集合。
  • packages/plugin-detail/src/__tests__/inlineEditTypeNarrowing.test.tsx —— 新增 23 个用例。
  • .changeset/detail-authored-type-narrow-only-editability.md —— patch,正文写明这是行为变更及规则本身。

验证

新测试覆盖两个组件:报告者的原配置({ name: 'supply_share', type: 'number' } 压在 rollup 上,单列一条用例)、四种计算类型的对象字段配 authored number、收窄仍生效(普通列上 authored formula)、普通列对照组、以及 #3356readonly 回归守卫。把 helper 临时改回 #3355 之前的优先级逻辑跑一遍,23 条中 13 条变红(两个组件都有),确认它们是真守卫而不是摆设。

pnpm exec vitest run packages/plugin-detail/src/__tests__/inlineEditTypeNarrowing.test.tsx
  Test Files  1 passed (1)       Tests  23 passed (23)

pnpm exec vitest run packages/plugin-detail/       (整包)
  Test Files  43 passed (43)     Tests  408 passed (408)

pnpm exec turbo run type-check --filter=@object-ui/plugin-detail
  Tasks: 12 successful, 12 total

pnpm exec turbo run lint --filter=@object-ui/plugin-detail
  0 errors, 683 warnings(全部为既有基线;新增文件只有测试里惯例性的 `as any` 警告)

node scripts/check-changeset-no-major.mjs   ->   No changeset declares a `major` bump.

兼容性说明

任何今天依赖「用展示 type 覆写把计算列变成可编辑」的页面,行为会变。issue 作者与我都构造不出这属于合理诉求而非潜在 bug 的场景 —— 这种写入要么被服务端弹回,要么落库并污染数据。需要显式表达「这个 chip 可读不可写」的场景,用 objectui#3356 落地的条目级 readonly 键。


🤖 Generated with Claude Code

https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9


Generated by Claude Code

…ever widens it (#3355)

Both detail-surface editability gates resolved ONE effective type with display
precedence (`viewFieldType || objectFieldType`), so an authored non-computed
`type` erased the object's `formula`/`summary`/`rollup`/`auto_number`
declaration from the gate's view and made a machine-owned column inline
editable.

That is the shipped configuration behind objectstack#5077: the reporter writes
`{ name: 'supply_share', type: 'number' }` purely to fix formatting (a
workaround for objectstack#5066) over a hook-maintained ROLLUP. The header chip
became writable, the rollup was overwritten by hand and stayed corrupted until
an unrelated child-row touch re-fired it (yinlianghui/hotcrm-heimao#61).

The gate now takes the UNION of the two types: non-editable if the authored
entry type OR the object field's type is computed. Renderer/editor selection
keeps the old precedence, so the display is unchanged — only who may write.

- fieldEnrichment: new `isComputedFieldType(viewFieldType, objectFieldType)`,
  the ONE definition both gates call; `TEXTUAL_REF_FALLBACK_TYPES` moved here
  beside it (still re-exported from InlineFieldInput, public name unchanged) so
  the renderer fallback and the two gates read one set.
- HeaderHighlight / DetailSection: gates pass the authored type and the object
  type separately instead of the collapsed `resolvedType`.
- tests: 23 cases across both surfaces — the reporter's exact config named,
  every computed object type under an authored `number`, narrowing still
  working, plain-field controls, and the #3356 `readonly` regression guard.
  13 of them fail against the pre-#3355 precedence logic.

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

vercel Bot commented Aug 4, 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 4, 2026 10:34am

Request Review

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

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

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.47KB 3.09KB
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) 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) 477.09KB 104.71KB
core (index.js) 2.25KB 0.80KB
create-plugin (index.js) 9.28KB 2.98KB
data-objectstack (index.js) 136.23KB 34.75KB
fields (index.js) 226.73KB 55.54KB
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.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.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.98KB 12.37KB
plugin-charts (index.js) 60.54KB 17.13KB
plugin-chatbot (index.js) 180.09KB 42.72KB
plugin-dashboard (index.js) 112.01KB 28.86KB
plugin-designer (index.js) 210.51KB 42.51KB
plugin-detail (index.js) 230.77KB 56.86KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 111.54KB 26.97KB
plugin-gantt (index.js) 162.25KB 39.55KB
plugin-grid (index.js) 185.08KB 49.04KB
plugin-kanban (index.js) 47.89KB 13.18KB
plugin-list (index.js) 104.94KB 25.32KB
plugin-map (index.js) 16.81KB 5.24KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 40.55KB 10.59KB
plugin-timeline (index.js) 25.76KB 7.33KB
plugin-tree (index.js) 8.34KB 2.82KB
plugin-view (index.js) 83.67KB 20.43KB
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.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

@xuyushun441-sys
xuyushun441-sys marked this pull request as ready for review August 4, 2026 10:40
@xuyushun441-sys
xuyushun441-sys added this pull request to the merge queue Aug 4, 2026
Merged via the queue into main with commit bbbde12 Aug 4, 2026
16 checks passed
@xuyushun441-sys
xuyushun441-sys deleted the claude/issue-3355-type-gate-narrow branch August 4, 2026 10:40
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.

An authored display type can WIDEN inline-edit on a computed field — type: 'number' on a rollup unlocks the chip

2 participants