Repository navigation
Description
Summary
The header chip strip on a record detail page is built from the object's highlightFields. Those chips are inline-editable and the edits save. There is no way to declare a highlight field read-only, so columns that are exclusively maintained by hooks can be overwritten by anyone who clicks on them.
Why there is no authoring knob
highlightFields: z.ZodOptional<z.ZodArray<z.ZodString>>;
// → highlightFields?: string[] | undefinedA plain array of field names. No per-field options at all — no readonly, no disabled, nothing.
The three things an app can reach for, and why each fails
-
View-level
readonlyon the form view's section fields. This works, but only for the form/dialog render path. The detail page's header chips are a different surface and do not consult it. That asymmetry is the reported symptom: the create/edit dialog refuses the edit while the detail page accepts it. -
readonly: trueon the object field.stripReadonlyFieldsdrops caller-supplied readonly keys for any non-isSystemcontext. Our columns are written by cross-object hooks, which inherit the acting user's non-isSystemcontext, so marking them readonly makes the hook's writes silently vanish and the columns never update again. -
Field-level
editable: falsein a permission set.getFieldPermissionshas no super-user bypass —modifyAllRecordsdoes not apply at field level, and field-level entries take precedence over the object-level decision.plugin-security'sdetectForbiddenWritesthen throws 403 for non-isSystemwrites, which kills the same hook write-back. Turning it off for the admin turns it off for the hook.
So an app that has hook-maintained columns and wants them on the header strip has no legal way to protect them.
Reproduce
- Declare a column written only by an
afterInsert/afterUpdatehook on another object. - Put it in
highlightFields, and mark itreadonlyon the form view's section field. - Open the record detail page. The header chip for that column is editable; changing it and saving persists the value, overwriting what the hook computed. The create/edit dialog for the same record refuses the same edit.
Impact
For us these are derived business figures — a customer grade written back by a rating approval, and a supply-share percentage rolled up from two child objects. A user clicking a chip silently replaces a computed value with a typed one, with no indication that the column is machine-owned.
A readonly flag on highlightFields entries — or having the header chips honour the form view's field-level readonly — would close this.
Environment
@objectstack/* protocol ^17.0.0-rc.1, console from @objectstack/console, driver-sql on SQLite.
Filed from a downstream app (hotcrm-heimao), tracked there as issue #61.
Activity
yinlianghui commentedon Aug 4, 2026
Correction + browser-measured reproduction
I re-ran this in a real browser rather than from the type definitions, and the conclusion holds — an app has no way to protect these columns — but two statements in the description are wrong in a way that makes the fix look bigger than it is. Corrected below.
Measured reproduction
Fresh database,
@objectstack/console17.0.0-rc.1, Chromium 1194, localezh-CN, admin user.Double-clicking a header chip puts the whole strip into inline-edit mode with a save bar. Typing a value and clicking save:
After a full page reload the chip shows
99. Read back over REST:{ "share": 99, "our": 6000, "comp": 15000 }supply_shareis defined asour / (our + competitor) × 100, so the correct value is28.57. The three columns no longer reconcile, and nothing self-heals. The rollup only re-fires when a child row changes — touching one child row afterwards restored28.57, but until someone does that, the fabricated number stays on screen and in the database indefinitely.❌ Correction 1 — there is a per-field config path; it just has no
readonlyThe description says highlight entries are "a plain array of field names. No per-field options at all". That is true of the object's
highlightFields, but not of the surface that actually renders them. Therecord:highlightspage component normalizes eachproperties.fields[]entry:So an object entry is accepted, and
typedemonstrably takes effect — we ship one today to work around #5066:That override changes which display renderer the chip picks (verified on screen). So the authoring path exists.
❌ Correction 2 — the chip already checks
readonly; the renderer drops the keyThis is the actionable part. The chip's own editability test reads:
entry.readonlyis already consulted. But the normalize map above copies only{name, label, icon, type}—readonlyis dropped one layer before the check that would honour it. The check can therefore never fire from authored metadata; onlyschemaField.readonlycan, and that is the route the description correctly rules out (stripReadonlyFieldskills the cross-object hook writes, #2948).So this is not a spec change to
highlightFields. Addingreadonly: e?.readonlyto that one normalize map makes the existing check reachable.What I measured that does not work (so nobody re-tries these)
All tested on a live page, reading the rendered DOM for the
cursor-pointer/双击编辑inline-edit affordance:record:highlightsslot{ name: 'supply_share', readonly: true }readonlyis dropped in normalization{ name: 'current_total_score', readonly: true }{ name: 'supply_share', type: 'formula' }{ name: 'last_year_volume', type: 'summary' }{ name: 'last_year_volume', type: 'rollup' }The last three are worth flagging separately:
NON_EDITABLE_TYPEScontainsformula/summary/rollup/auto_number, so an authoredtypeof any of those looks like it should tripisNonEditableType— and it does not. Whatever resolves the effective type for that gate is not reading the authored entry, even though the same authoredtypedoes reach the display-renderer selection. Two consumers of one field, disagreeing. Worth fixing alongside the droppedreadonly.redactFieldsandenforceFieldSecurityon the same component were also considered: both remove the chip rather than lock it, which is not what is wanted for a value whose whole point is being displayed.Suggested fix, smallest first
readonlythrough therecord:highlightsentry normalization — one key, makes the existing check reachable, unblocks every downstream app immediately.type: 'formula'is honoured consistently.readonly, so one declaration covers both surfaces and the dialog/detail-page asymmetry that produced this report disappears.Downstream tracking: yinlianghui/hotcrm-heimao#61.
Generated by Claude Code
xuyushun441-sys commentedon Aug 4, 2026
CLAIM
session_01Ehu85kbvMcrNTUJjwxvLJ9(spec 车道 PM,派发给 os-dev 实施)claude/issue-5077-highlights-readonlyrecord:highlights组件的归一化表把readonly键在既有检查之前丢掉了,且可编辑性门读的有效 type 与渲染器选择不一致。范围 = 实测评论的建议 1 + 2;建议 3(贯通表单视图 field-level readonly)不在本单。Generated by Claude Code
xuyushun441-sys commentedon Aug 4, 2026
结案(2026-08-04)
按维护者定调「按 bug 修,协议零改动」,两半修复均已合入 objectui main:
record:highlights归一化表补传readonly,授权{name, readonly: true}的 chip 从此锁定 inline 编辑(本单标题症状);type永远不能放宽门 —— 对象字段是 formula/summary/rollup/auto_number 时,无论条目写什么 type,chip 都锁死。这是报告方实际上线配置(rollup 上写type:'number'绕 Console:percentdisplay renderer's fixed-width progress bar clips the value in a record-highlights chip (33% renders as3) #5066)触发腐蚀的真实路径;HeaderHighlight 与 DetailSection 两站点共享同一 helper(fieldEnrichment),防 drift。协议未动:
highlightFields仍是纯字符串数组,RecordHighlightsField词表不变。readonly进 spec 词表的事(#5176)已登记为 #5068(SDUI props 闸门)的硬前置,不单独排期 —— 将来谁开闸门,谁必须先落声明,防止本单症状以零诊断形态复发。@yinlianghui 下游 hotcrm-heimao#61:两个修复随下一个 console 发布可验;你们现有的
type:'number'workaround 无需回退(#3357 后它只影响显示渲染,不再打开编辑)。Generated by Claude Code