fix(plugin-detail,types): record:highlights 尊重条目上声明的 readonly,不再让表头 chip 覆写平台维护的列 - #3356
Merged
Merged
Conversation
…` entry normalization (objectstack#5077)
WIP checkpoint — code + tests complete, changeset/typecheck/lint pending.
HeaderHighlight's editability gate has always consulted `field.readonly`, but
RecordHighlightsRenderer rebuilt each authored entry from a fixed four-key list
({name,label,icon,type}), dropping `readonly` one layer BEFORE the check that
would honour it. The gate could therefore never fire from authored metadata,
leaving hook-maintained columns (rollups, approval-written grades) inline
editable on the detail-page header strip — a user could overwrite a computed
value and it stayed corrupted until an unrelated child-row touch re-fired the
rollup (downstream yinlianghui/hotcrm-heimao#61).
- record-highlights.tsx: copy `readonly` through normalization.
- types: declare `readonly?: boolean` on `HighlightField` and on the
`RecordHighlightsComponentProps.fields[]` entry union, mirroring
`DetailViewField.readonly`; drops an `any` cast in the gate.
- tests: 10 cases pinning the authored-metadata -> chip path.
MEASURED DEVIATION from the issue's 2026-08-04 comment (suggested fix 2): at
HEAD the editability gate ALREADY reads the authored entry type
(`resolvedType = field.type || objectDefField?.type`), the same resolution the
display-renderer selection uses, so an authored `type: 'formula' | 'summary' |
'rollup' | 'auto_number'` already disables inline edit. Verified by probe before
editing; the eight type-gate tests here are therefore PIN tests, not fixes.
REMAINING STEPS: (1) .changeset patch entry; (2) pnpm typecheck + lint on
plugin-detail/types; (3) draft PR. Two open questions for the maintainer are
recorded in the PR body — `readonly` is not declared in @objectstack/spec's
`RecordHighlightsField`, and an authored display `type` can currently WIDEN
editability on a computed object field.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
Also records the two out-of-scope findings filed while measuring: objectstack-ai/objectstack#5176 (spec does not declare `readonly` on `RecordHighlightsField`, so `RecordHighlightsProps.parse()` strips it) and #3355 (an authored display `type` can widen inline-edit on a computed field). Verified: plugin-detail + types type-check Done; 773 tests pass; eslint on the changed files reports 0 errors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
akarma-synetal
pushed a commit
to akarma-synetal/objectui
that referenced
this pull request
Aug 4, 2026
…ever widens it (objectstack-ai#3355) (objectstack-ai#3357) 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 objectstack-ai#3356 `readonly` regression guard. 13 of them fail against the pre-objectstack-ai#3355 precedence logic. Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9 Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
修复 objectstack-ai/objectstack#5077(跨仓,故不用裸
Fixes #)。下游追踪:yinlianghui/hotcrm-heimao#61。这个 PR 是草稿,且是有意不完整的 —— 见下方"需要维护者决策的两点"。objectui 侧的这一半在任何一种决策下都是必需的,先落地以便决策有具体的东西可依附。
问题
详情页表头 chip 由
record:highlights渲染,支持双击行内编辑。HeaderHighlight的可编辑性门 一直 读field.readonly:但
RecordHighlightsRenderer的条目归一化把每个条目按固定四键重建:readonly在那个检查的 上一层 就被丢掉了,所以这个门永远无法从作者写的元数据触发 —— 只有schemaField.readonly能触发,而那条路对本 issue 的场景是死路:把对象字段标readonly会连带让跨对象 hook 自己的回写被stripReadonlyFields剥掉(objectstack-ai/objectstack#2948)。结果:hook 维护的汇总列(rollup、审批回写的等级)在表头 chip 上可以被任何人手工改掉。实测的破坏是持久的 —— 值一直错到某次不相关的子行改动重新触发 rollup 为止。
改动
packages/plugin-detail/src/renderers/record-highlights.tsx—— 归一化时带上readonly。仍然逐键重建而不是展开对象,条目形状保持封闭,未声明的键依旧不会被静默转发。packages/types/src/views.ts/record-components.ts—— 把readonly?: boolean声明到HighlightField和RecordHighlightsComponentProps.fields[]条目联合类型上,与DetailViewField.readonly对齐。packages/plugin-detail/src/HeaderHighlight.tsx—— 既然readonly已是声明键,去掉门里的(field as any)断言。redactFields/enforceFieldSecurity未动:那两个是移除 chip,而这里要的是锁定一个"存在的意义就是被看见"的值。与 issue 评论的实测偏差(重要)
2026-08-04 的浏览器实测评论提出的"建议 2"说:可编辑性门解析有效 type 的来源与显示渲染器不同,所以作者写的
type: 'formula'到不了门上。在 HEAD 上这一条不成立。 编辑前我先跑了探针测量:作者写的
type: 'formula' | 'summary' | 'rollup' | 'auto_number'已经能正确禁用行内编辑。所以本 PR 里那 8 个 type 相关的用例是回归钉(pin tests),不是修复 —— 一旦将来有人再把门和渲染器选择拆成两套 type 解析,它们会立刻失败。评论里"两个消费者对同一个字段意见不一致"的直觉是对的,只是方向反了:真正的洞是作者写的显示 type 会 放宽 门。已另行归档,见下。
需要维护者决策的两点(均已作为未指派 issue 归档)
RecordHighlightsFielddoes not declarereadonly, so the spec silently strips the key the chip gate reads objectstack#5176 —— spec 没有声明readonly。RecordHighlightsField是恰好{name,label?,icon?,type?}四键,非.strict(),所以RecordHighlightsProps.parse()会静默剥掉readonly(已实测)。今天本 PR 能端到端生效,只是因为ComponentPropsMap目前在加载路径上没有消费者、PageComponentSchema.properties是z.record(z.string(), z.unknown())原样透传。协议 15 已经把三个 UI schema 翻成.strict()(ADR-0089 D3a),方向很明确;等 props map 真的接入校验那天,作者写的readonly要么静默消失(chip 悄悄恢复可编辑,本 issue 的破坏原样回来,且没有任何诊断),要么直接 parse 报错。按 AGENTS.md #0.1,渲染器读一个 spec 未声明的键正是要避免的"宽容消费者"。建议在 spec 声明该键,并同步进 blockinputs→sdui.manifest.json,让 AI 作者读得到。typecan WIDEN inline-edit on a computed field —type: 'number'on a rollup unlocks the chip #3355 —— 作者写的显示type会放宽计算字段的编辑门。 对象声明score: { type: 'formula' },作者为了排版写{ name: 'score', type: 'number' },chip 就变成可编辑(实测)。DetailSection有完全相同的形状,所以要改必须两处一起改,否则又会出现fieldEnrichment.ts当初就是为了消除的漂移。这正是上游 app 的真实配置(他们为绕过 Console:percentdisplay renderer's fixed-width progress bar clips the value in a record-highlights chip (33% renders as3) objectstack#5066 给一个 rollup 写了type: 'number'),也就是说实测到的那次数据破坏走的是这条路径。建议改成"只收窄不放宽"。两点都超出本单范围(本单 = 实测评论的建议 1 + 2),故未在此 PR 内修改。
验证
新增用例 10 个(
RecordHighlightsRenderer.readonly.test.tsx),覆盖作者元数据 → chip 的整条路径而非孤立的门。已验证其防回归有效性 —— 临时撤掉归一化里的那一行后:恰好是依赖修复的 2 个失败,8 个 type 钉用例照常通过 —— 与"建议 2 在 HEAD 上已成立"的测量互相印证。
🤖 Generated with Claude Code
https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
Generated by Claude Code