Skip to content

feat(fields, spec): show a referenceNameVisible lookup's served name as plain text, and offer its field-form row - #29639

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-de5k53
Nov 3, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-de5k53

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

Fixes #29462

Clause-②: no
Metadata-protocol: none

Claim: 6588482199 (amendments 6588547542, 6590220295)

维护者速读

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.

pnpm dev:crm -- --fresh plus the Console Vite dev server, a member who may read the expense but not the payroll run
  • 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.

Generated by Claude Code

…as plain text, and offer its field-form row

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jTfRUqsXsgEtuXogw2
…ield-form row; translate the row

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jTfRUqsXsgEtuXogw2
… no link under a displayField

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jTfRUqsXsgEtuXogw2
…ow among the labels translated in all three locales

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jTfRUqsXsgEtuXogw2
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

5 anchor(s) derived from 2 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see

Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 3482f3d8d2c48ec8d2e3306f1ce26c0364e800c2 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 218b83b6c98d33ff01e54d869da5c7ed4aff4248 — the merge of head 511e63991b63c91a77762b5c708eb31678c2b644 into base 3482f3d8d2c48ec8d2e3306f1ce26c0364e800c2, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 218b83b6c98d33ff01e54d869da5c7ed4aff4248 && git checkout 218b83b6c98d33ff01e54d869da5c7ed4aff4248
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3482f3d8d2c48ec8d2e3306f1ce26c0364e800c2 511e63991b63c91a77762b5c708eb31678c2b644 && git checkout -B drift-repro 3482f3d8d2c48ec8d2e3306f1ce26c0364e800c2 && git merge --no-ff 511e63991b63c91a77762b5c708eb31678c2b644

node scripts/docs-audit/affected-docs.mjs --json 3482f3d8d2c48ec8d2e3306f1ce26c0364e800c2

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

… to 91)

The #7010 corpus pin over METADATA_FORM_REGISTRY counts the new
field.form.ts row's visibleWhen "data.type in ['lookup','user']". It is
an in-list, so the literal-comparison censuses stay at 65 and 18.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jTfRUqsXsgEtuXogw2
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m

席位核对 · ACCEPT · 2026-11-03T00:16Z

Head-sha: 511e63991b63c91a77762b5c708eb31678c2b644

  • Shape: draft → main, Fixes #29462. 13 files, +607 / −17. No governed path: check-governed-merges --pr 29639 reports 0 of 13 paths, NOT governed. Clause-②: no and Metadata-protocol: none are line-initial in the body and in the changeset (patches for @object-ui/fields, @objectstack/spec and @objectstack/platform-objects), as claimed in 6588482199, amended 6588547542, 6590220295 and 6591432476.
  • Contract review: none owed (Clause-②: no).
  • File surface: inside the claim as amended:
    • fields/src/index.tsx
    • the new fields and plugin-detail tests (plugin-detail source unchanged)
    • field.form.ts, the reconciliation entry and the new spec test
    • the four regenerated metadata-forms.generated.ts bundles and the 699 → 700 echo-decisions pin
    • the lint predicate-corpus pin 90 → 91 (validate-predicate-path-refs.test.ts, amendment 3): the first head (7fb3a29d) went red on Test Core (6/8), job 125621243495, and 511e6399 bumps only that pin with the file's comment paragraph. The dev reran @objectstack/lint in full: 7,886 pass, and the in-list leaves the 65 and 18 censuses unchanged
    • the changeset
  • Read in the diff:
    • isReferenceNameOnly matches only readRefused === true with a string name. On such a value, LookupCellRenderer and UserCellRenderer draw ReferenceNameOnly (data-slot="reference-name-only"): plain text, with no link and no hover card.
    • The lookup asks useRefObjectSchema(undefined) when every value is name-only, so no read of the target object goes out.
    • On multiple, each name-only element is a text chip, and the other elements keep their faces.
    • All hooks run before the new early returns, so hook order is stable across value changes.
    • A field without the key keeps today's faces; the objectui#13335 placeholder test stays green as the CONTROL.
  • Tests (dev):
    • New suites: 11 fields cases and 5 plugin-detail cases. CONTROLs cover the 13335 placeholder, a record with no marker, readRefused: 'yes', and the user avatar.
    • Ablations A1–A6 each go red.
    • On the merged head: 85 of 85 gates exit 0. Suites pass for spec (25,223), fields (4,437), plugin-detail (3,241) and platform-objects (1,512).
  • Measured (dev): live on dev:crm, a member refused rnv_payroll sees the payroll names as text on the list and record pages, and hovering makes 0 payroll reads. Shots: /mnt/project-files/issue-29462/shots/.
  • Gates (check-runs on this head): on 511e6399, 56 success, 8 skipped (opt-in and schedule gates), 0 failed, 0 pending. All of Test Core 1–8/8 and Test UI 1–16/16 are green, including the shard that was red on 7fb3a29d.
  • Follow-ups filed by the seat:
  • Landing: ordinary queue landing, pr_ready then automerge_enable.

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.

ui: render a referenceNameVisible lookup's name as plain text, and add its Studio form row (#29171 ruling B)

1 participant