Repository navigation
Commit 6903c93
feat(fields, spec): show a referenceNameVisible lookup's served name as plain text, and offer its field-form row (#29639)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} -->
_Requested by **Steedos** · [project
thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbUkkKyWnKsQpoatsf3hYxiL)_
Fixes #29462
Clause-②: no
Metadata-protocol: none
Claim: 6588482199 (amendments 6588547542, 6590220295)
## 维护者速读
- 改了什么:字段声明 `referenceNameVisible: true` 的 lookup / user
字段,当服务端对读者无权读取的目标返回 `{ id, name, readRefused: true }`
时,列表单元格与记录详情页把名称显示为纯文本:不带链接、不弹悬浮卡、不读取目标记录;多值字段每个元素一个文本标签。字段表单(`field`
元数据表单)新增该开关,只在 lookup 与 user 类型上显示,并补了中日西三语文案。
- 为什么改:服务端半边(#29460,PR #29576)已开始下发名称;按裁决 6574398310 字母
B,控制台要显示名称而不是「受限记录」占位,且不得链接到读者打不开的记录。
- 风险与回滚:只认带 `readRefused: true` 的值;不带该键的字段、可读目标、裸 id
的「受限记录」占位全部不变(#13335 测试原样通过)。回滚即 revert 本 PR。
- 先看:`ui/packages/fields/src/index.tsx` 的 `isReferenceNameOnly` /
`ReferenceNameOnly` 与 `LookupCellRenderer`、`UserCellRenderer`
中的三处分支;`packages/spec/src/data/field.form.ts` 新增的一行。
- 两分钟验证:`pnpm --dir ui exec vitest run
packages/fields/src/__tests__/lookupCell.referenceNameVisible-29462.test.tsx`,预期
11 个用例全部通过。
- 需要你做的:无,席位落地。
## What changes
The console half of `FieldSchema.referenceNameVisible` (director ruling
6574398310, letter B), rendered against the shape PR #29576 serves: on
`$expand`, a `lookup` / `user` field declaring the key answers a target
the reader is refused as `{ id, name, readRefused: true }`, once per
element on `multiple: true`, with the floor `Record #ID` under a
field-level rule on the name column.
| Value the cell receives | Before | Now |
|---|---|---|
| `{ id, name, readRefused: true }` on a `lookup` | a link to the
refused record, with a hover card that reads it | the served `name` as
plain text (`data-slot="reference-name-only"`), no link, no hover card,
no read |
| the same on a `user` | an avatar and the name | the served `name` as
plain text, no avatar |
| an array of those (`multiple: true`) | one linked chip / avatar per
element | one text chip per element; other elements keep their faces |
| an expanded record without the marker | a link | unchanged |
| a bare id whose read answers "not found" (field without the key) | the
restricted-record placeholder | unchanged |
- `isReferenceNameOnly` judges the in-band `readRefused === true` the
server writes, never the shape of the rest, so a readable record
expanded with a narrow projection stays a link.
- The served name is drawn as served: the viewer's policy on the target
object (which is what refused the record) is not applied to it, and the
field's `displayField` does not re-name it.
- A value made only of served names does not ask for the referenced
object's definition either (`useRefObjectSchema` gets no object), so the
cell makes no read at all.
- The record page's field grid (`DetailSection`) draws lookup and user
values through these same cells, so it changes with them;
`plugin-detail` source is untouched.
- **Field form.** `packages/spec/src/data/field.form.ts` gains the
`referenceNameVisible` row ("Show Name When Unreadable"), `visibleWhen:
data.type in ['lookup','user']`. That predicate is also the key's parse
gate: `FieldSchema` refuses the key on every other type, an authored
`false` included. The `omit` ledger entry in
`metadata-form-zod-reconciliation.test.ts` is removed with the row (the
card's one spec edit). The row's label and help text are regenerated
into the `platform-objects` metadata-form bundles and translated for
zh-CN, ja-JP and es-ES.
## Tests
-
`ui/packages/fields/src/__tests__/lookupCell.referenceNameVisible-29462.test.tsx`
(new, 11): served name as text with no anchor, and a recording data
source (find, findOne, getObjectSchema all answering) asked nothing,
after hover and focus; the floor `Record #l1` drawn as served;
`displayField` does not re-name it; `multiple: true` one chip per
element; a mixed array keeps the readable element's link; `user` single
and multiple; CONTROLS: a record without the marker and `readRefused:
'yes'` stay links, a field without the key keeps the restricted
placeholder, an expanded user keeps its avatar.
-
`ui/packages/plugin-detail/src/__tests__/DetailSection.referenceNameVisible-29462.test.tsx`
(new, 5): the real `DetailSection` draws lookup, user and multi-value
user rows as text with no anchor, reads nothing about the refused
targets, and keeps a readable sibling's link.
- `packages/spec/src/data/field-form-reference-name-visible.test.ts`
(new, 3): the row is declared once, its predicate shows it on `lookup`
and `user` only over every `FieldType`, and that set equals the set
`FieldSchema` accepts the key on.
- Unchanged control: `lookupCell.restrictedRecord-13335.test.tsx` green,
unmodified.
- Ablations (`scripts/ablation-replace.mjs`, WRAP mode, each restored to
the HEAD blob), each red: A1 lookup single branch removed, 3 failed; A2
array element branch removed, 2 failed; A3 user single branch removed, 2
failed; A4 user array branch removed, 2 failed; A5 schema read no longer
skipped, 1 failed; A6 the row's predicate widened to `master_detail`, 2
failed.
## Acceptance notes
- Clause-② hint: `dispatch-gates --tier` derives no path mandate
(default tier). It prints a Clause-② SUSPECT hint on
`packages/spec/src/data/field.form.ts` (`packages/spec/src/**`). The row
offers a key `FieldSchema` already accepts on exactly these types (the
accept set does not move), so the claim's `Clause-②: no` is kept; the
seat reads the hint.
- Surface: the claim's eight files plus one count pin the seat added by
amendment 6590220295: `object-lifecycle-panel-echo-decisions.test.ts`
counts metadata-form labels authored in all three locales, 699 → 700
with its comment line, as every new form row does.
- The Studio object designer's field panel is the hand-written
`ObjectFieldInspector`, which does not read `field.form.ts`. A live
probe as the admin (lookup selected in the designer) shows no such
control there. The seat files that control as its own card; this PR
leaves the inspector alone.
- Seen on the live server, not changed here: the same lookup WITHOUT the
key, whose target the member's object gate refuses, draws the unresolved
face (the id with a question mark, inside a link) rather than the
restricted-record placeholder. The record read answers 403 for an
object-gate refusal, and the cell reads only a "not found" answer as
restricted. Reported to the seat.
- Local gates on the merged head `7fb3a29d`: `dispatch-gates --commands`
derives 85 commands; each ran with exit 0 (`check:type-check-debt` and
`pnpm --dir ui exec vitest run scripts/` under the verify lock), and
`--ran` reconciles 85 derived, 85 run, 0 NOT-MEASURED, 0 UNRUN.
Type-checks exit 0: spec, platform-objects, and `turbo run type-check`
for `@object-ui/fields` and `@object-ui/plugin-detail`. Suites: spec
891/891 files (25223 tests) and fields 295 files / plugin-detail 369
files before the main merge; on the merged head platform-objects 110/110
files, spec `src/system/` plus the field-form tests 70/70 files, the
four lookup/detail files 31/31, and `spec check:generated` exit 0. The
repo-wide sweep (`pnpm lint` and the rest of the farm) is CI's.
## Verification
Real backend and the source Console, on head `5c25e7f3` (before the main
merge; this PR's cell and form code is unchanged since). Screenshots and
the scripts are in the project folder (`issue-29462/shots/`,
`live-setup.mjs`, `live-grant.mjs`, `live-shot.cjs`), not on GitHub.
<details><summary><code>pnpm dev:crm -- --fresh</code> plus the Console
Vite dev server, a member who may read the expense but not the payroll
run</summary>
- A scratch package over REST: `rnv_payroll` (`sharingModel: private`,
admin-owned rows) and `rnv_expense` (`public_read`) with `payroll`
(lookup, `referenceNameVisible: true`), `payroll_plain` (the same lookup
without the key) and `payrolls` (`multiple: true`, with the key). A
member created with `admin/create-user`, granted read on `rnv_expense`
only by a package permission set, then impersonated.
- `GET /api/v1/data/rnv_payroll/ID` as the member: 403. `GET
/api/v1/data/rnv_expense/ID?expand=payroll,payroll_plain,payrolls` as
the member: `payroll` = `{ id, name: "Payroll Q3 2026", readRefused:
true }`, `payrolls` = two such values, `payroll_plain` = the bare id. As
the admin, all three are the full records.
- List view (`/apps/com.example.rnv/rnv_expense`) and record page, as
the member: `Payroll (name visible)` reads "Payroll Q3 2026" as plain
text; `Payrolls` shows two text chips "Payroll Q3 2026" and "Payroll Q4
2026"; three `reference-name-only` elements, none inside a link.
Hovering each of them made no request. The only `rnv_payroll` requests
on the page (one record read, one object definition read) and the only
link to `rnv_payroll` belong to `Payroll (no key)`, the control field,
which draws as before.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QsR2jTfRUqsXsgEtuXogw2)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 57958aa commit 6903c93
13 files changed
Lines changed: 607 additions & 17 deletions
File tree
- .changeset
- packages
- lint/src
- platform-objects/src/apps/translations
- spec/src
- data
- system
- ui/packages
- fields/src
- __tests__
- plugin-detail/src/__tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
696 | 696 | | |
697 | 697 | | |
698 | 698 | | |
699 | | - | |
| 699 | + | |
| 700 | + | |
| 701 | + | |
| 702 | + | |
700 | 703 | | |
701 | 704 | | |
702 | 705 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
721 | 721 | | |
722 | 722 | | |
723 | 723 | | |
| 724 | + | |
| 725 | + | |
| 726 | + | |
| 727 | + | |
724 | 728 | | |
725 | 729 | | |
726 | 730 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
721 | 721 | | |
722 | 722 | | |
723 | 723 | | |
| 724 | + | |
| 725 | + | |
| 726 | + | |
| 727 | + | |
724 | 728 | | |
725 | 729 | | |
726 | 730 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
721 | 721 | | |
722 | 722 | | |
723 | 723 | | |
| 724 | + | |
| 725 | + | |
| 726 | + | |
| 727 | + | |
724 | 728 | | |
725 | 729 | | |
726 | 730 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1253 | 1253 | | |
1254 | 1254 | | |
1255 | 1255 | | |
1256 | | - | |
| 1256 | + | |
| 1257 | + | |
| 1258 | + | |
| 1259 | + | |
1257 | 1260 | | |
1258 | 1261 | | |
1259 | 1262 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
721 | 721 | | |
722 | 722 | | |
723 | 723 | | |
| 724 | + | |
| 725 | + | |
| 726 | + | |
| 727 | + | |
724 | 728 | | |
725 | 729 | | |
726 | 730 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
221 | 221 | | |
222 | 222 | | |
223 | 223 | | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
224 | 235 | | |
225 | 236 | | |
226 | 237 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
418 | 418 | | |
419 | 419 | | |
420 | 420 | | |
421 | | - | |
422 | | - | |
423 | | - | |
424 | | - | |
425 | | - | |
426 | | - | |
427 | | - | |
428 | | - | |
429 | | - | |
430 | | - | |
431 | | - | |
432 | 421 | | |
433 | 422 | | |
434 | 423 | | |
| |||
0 commit comments