Skip to content

fix(studio): explain each control the field inspector locks in display-patch mode - #29081

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-tq6682
Nov 2, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-tq6682

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

Fixes #28986

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:Studio 字段检查器在「只能改显示属性」模式(只读基础包的字段,经配对的 .custom 包定制)下,每个被锁住的控件(API 名、类型、必填/唯一、默认值、选项值与颜色、跟踪历史、三个条件规则编辑器、上移/下移、删除等)外面多了一层可 Tab 聚焦的包装:悬停或键盘聚焦时弹出提示,读屏通过 aria-describedby 读到顶部那句「此字段属于已安装的包:你只能修改它的……」。可编辑的标签、描述、帮助文本、占位符、选项标签不加包装、不带原因。
  • 为什么改:feat(studio): add a field to a read-only base object as an extension in its pair #28860 让基础包字段可以写显示补丁后,整栏的只读原因包装(objectui#12303)不再绘制,被锁控件变成灰着却说不出原因,ux-conventions.disabled-control-reason 的回归行由 PASS 变 FAIL;这是 18.3.0 发版前的回归。
  • 风险与回滚:低。只改检查器的渲染:新增的包装只在显示补丁模式下出现,普通可写字段和整栏只读两种情形 DOM 不变(各有对照测试钉住);不新增文案、不碰保存逻辑。代价是该模式下 Tab 序列里多了若干个停靠点(每个被锁控件或一行一组一个)。回滚即还原本 PR。
  • 先看:ui/packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.tsx 的 DisplayPatchLock 与 DisplayPatchReasonContext,然后同文件 OptionsEditor 里选项值与选项工具条的包装;测试 ObjectFieldInspector.displayPatchReason-28986.test.tsx。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.displayPatchReason-28986.test.tsx,预期 1 个文件、16 个测试全绿。
  • 需要你做的:无,席位落地。

What changes

  • One lock, one reason. ObjectFieldInspector gains a module-private DisplayPatchLock. In display-patch mode (BaseFieldDisplayPatchContext on, readOnly off) it wraps a locked control, or one row of them, in a role="group" tab stop with data-disabled-reason. aria-describedby on that wrapper points at the existing whitelist sentence at the top of BASIC (field-base-patch-only, which now carries an id), and a Radix tooltip shows the same sentence on hover and on focus. This is the shape DeclaredActionsBar (objectui#11811) and the read-only rail (objectui#12303) already use. In any other mode the lock renders its children unchanged.
  • What is wrapped: API name, Type, the Required/Unique row, Default value, the picklist source, the related object and the lookup and related-list blocks, the formula editor, the summary block, the numeric and text-length grids, currency, value domain, the Read-only/Hidden/Track history row, the conditional-rules block, Field group, the header's Move up/down pair and the footer's Remove. Inside the options editor, each option's value input and its colour/kind/reorder/remove strip are wrapped, and a malformed row's controls too. Each option's label input is not.
  • The reason is provided above the shell. InspectorShell draws the header and footer itself, so the inspector hands the reason through a context provided around the shell, and the options editor reads it from there too.
  • No new text. The reason is the sentence the inspector already shows ("This field belongs to the installed package: you can change only its …"), which names the condition, so the checklist's specificity clause reads it as specific.
  • StudioDesignSurface.tsx: a comment only, at fieldRailReadOnly, saying where the reason for a base field's locked controls now lives. The rail's read-only wrapper is unchanged.

Tests

  • New ObjectFieldInspector.displayPatchReason-28986.test.tsx mounts the real inspector under the display-patch context, over six fields (text, select with options, lookup, number, formula, currency):
    • every control rendered disabled (attribute, aria-disabled or Radix data-disabled) sits in a [data-disabled-reason] wrapper with tabindex="0" whose aria-describedby resolves to the whitelist sentence;
    • every wrapper contains at least one disabled control (no empty tab stop), and Label, Description, Help text, Placeholder and each option label are enabled with no wrapper and no reason;
    • the card's named controls (API name, Required, Track history, a rule editor, every Move up, Remove, an option value) are each described by the sentence's id;
    • Tab reaches the API name lock and its tooltip shows the sentence; hovering the Required lock shows it too;
    • CONTROL: a writable field draws no lock and nothing disabled; a read-only rail (patch context on or off) draws no inner lock, since the host describes the whole rail.
  • DataPillar.baseObjectExtension-28575.test.tsx (the real Data pillar): the required case now also asserts the Required box's wrapper is a tab stop described by the sentence, and that the Label input has no wrapper.

Acceptance notes

  • Live browser not measured. The checklist row's oracle is the showcase boot in a real browser. This PR is verified in jsdom only. The wrapper is the same tab-stop-plus-tooltip shape the read-only rail uses, which the checklist's QA run measured PASS for hover and keyboard in Chromium; the next QA pass of ux-conventions.disabled-control-reason re-measures it here.
  • Tab stops. One stop per locked control or row, and per option two (value, then the strip). That is the cost of keeping the editable controls undescribed between them.
  • Scope. The external-object locks (ddlLocked from an external binding) keep their own badge and are unchanged.

Verification

New pin · 16/16, and 15 red against main's inspector

pnpm --dir ui exec vitest run --maxWorkers=2 packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.displayPatchReason-28986.test.tsx under os-verify-lock: Test Files 1 passed (1), Tests 16 passed (16).

Reverse check at 97144b76: ObjectFieldInspector.tsx restored from origin/main (ab085a04, DisplayPatchLock count 0 on disk), the new file plus DataPillar.baseObjectExtension-28575.test.tsx run: Tests 15 failed, 11 passed. The 14 display-patch cases and the 28575 required case failed; both CONTROL cases and the rest of 28575 passed. Restored with git checkout HEAD --, blob hash back to 4509f157, git diff HEAD empty.

Every test file naming the field inspector · 61 files

git grep -l -E "ObjectFieldInspector|field-base-patch|BaseFieldDisplayPatchContext|fieldRailReadOnly|data-field-inspector" over ui test files: 61 files in two locked batches at --maxWorkers=2. Test Files 31 passed, Tests 215 passed; Test Files 30 passed, Tests 400 passed. This is a declared narrowing; CI's Test UI runs the rest of app-shell.

Type check and lint
  • turbo run type-check --filter=@object-ui/app-shell (65 tasks, includes tsc -p tsconfig.test.json): exit 0.
  • eslint on the three changed .tsx files: 0 errors, 15 warnings, all on lines that predate this diff (re-indented any casts in the rule editors, and two existing hook warnings).
  • pnpm --dir ui run check:new-line-citations: 0 new citations.
Derived gates

node scripts/pm/dispatch-gates.mjs --commands derived 34 commands against merge base ab085a048 at head 97144b76. All 34 ran and exited 0, including check-sdui-manifest.mjs and its --self-test, check:type-check-debt, check:nul-bytes, check:cross-package-test-inputs, check:test-source-alias and pnpm --dir ui exec vitest run scripts/ (Test Files 169 passed, 2 skipped; Tests 4900 passed, 2 skipped). dispatch-gates --ran reconciled 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. origin/main moved 9 commits since the base, none touching the three changed source files.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu

席位核对 · ACCEPT · 2026-11-02T07:21Z

Head-sha: 97144b76ee23cd1f40c25a41f8d358a7ac35235b

  • Shape:
  • Read in the diff:
    • In display-patch mode, each locked control (or row of them) sits in a module-private DisplayPatchLock: a role=group tab stop with data-disabled-reason, aria-describedby pointing at the existing whitelist sentence (which now carries an id), and a tooltip on hover and focus. That is the Archive pattern of DeclaredActionsBar (objectui#11811).
    • The options editor wraps the value input and the colour, kind, reorder and remove strip, and leaves the label editable. Whitelisted controls (Label, Description, Help text, Placeholder, option labels) are never wrapped.
    • Outside display-patch mode, and on a fully read-only rail (whose host already describes the rail body, objectui#12303), the wrapper renders its children as they are.
  • Gates (check-runs on this head): 44 success, 11 skipped, 0 failed or pending. mergeable_state is clean.
  • Tests (dev): new displayPatchReason-28986 16 of 16; against main's inspector 15 red with the controls green, restored; baseObjectExtension-28575 extended; 61 inspector files 615 of 615; app-shell type-check exit 0. Derived gates 34 of 34.
  • NOT MEASURED: live-browser hover and focus on the showcase (jsdom only), stated in the PR. QA measured the same wrapper shape PASS on the read-only rail.
  • Landing: ordinary queue landing, pr_ready then automerge_enable. Fixes #28986 closes the card on merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

1 participant