Skip to content

fix(app-shell): name the page block inspector's JSON editor by its label - #27254

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-qresy5
Oct 28, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-qresy5

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread
Fixes #27178

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:Studio 页面设计器的块检查器里,JSON 编辑框(InspectorJsonField)的标签现在用 htmlFor 指向它的 textarea(id 由 React.useId() 生成)。
  • 为什么改:原来标签和输入框没有关联,读屏器只读出一个没有名字的文本框,测试也无法按标签找到这个字段。
  • 风险与回滚:只加了 label 与 textarea 的 id 配对,不改写入的数据和提交逻辑;回滚即 revert 本 PR。
  • 先看:PageBlockInspector.tsx 中的 InspectorJsonField;新测试 PageBlockInspector.jsonFieldLabel-27178.test.tsx。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.jsonFieldLabel-27178.test.tsx,预期 2/2 通过。
  • 需要你做的:无,席位落地。

What changes

InspectorJsonField in PageBlockInspector.tsx mints an id with React.useId() and pairs it: Label htmlFor={id} and the textarea's id={id}. This is the same scheme FilterRulesField and ColorPropField in the same file already use. The id is minted inside the component, not derived from the label or prop name, because renderField also draws this editor inside array rows, where a caller-derived id would repeat.

Every door that draws the editor gets the pairing, because they all go through the one component: a curated kind: 'json' prop (for example record:related_list.add, element:button.action), the JSON fallback for a stored value in another union arm (sort stored as a string, columns as ListColumn objects), and the generic "Advanced" editor for object or array props. There was no aria-describedby or error wiring on the field to keep; the invalid-JSON message is unchanged.

Tests

New PageBlockInspector.jsonFieldLabel-27178.test.tsx, on the real inspector inside a host that applies every patch:

  • getByLabelText('Add existing records (picker)') returns the add textarea, its accessible name is that label, and typing JSON and blurring writes add in the stored shape.
  • With sort: 'subject' stored, getByLabelText('Sort') returns the JSON fallback textarea showing "subject", and the Sort and add editors resolve to two different textareas with two different ids.

At bc69c4ad: every PageBlockInspector* test file in the directory, 13 files, 86 tests passed. pnpm --filter @object-ui/app-shell run type-check (both the source and the test tsconfig): exit 0. eslint on the two changed files: exit 0, no new warning.

Ablation (drop the htmlFor / id pair)

Through scripts/ablation-replace.mjs, on the committed fix: anchor hits 1 to 0, blob e552a33e to 1da05b77. Both new tests turn red with the card's own wording: Found a label with the text of: Add existing records (picker), however no form control was found associated to that label and the same for Sort. Restore: blob back to the HEAD blob e552a33e, git diff HEAD empty.

Acceptance notes

  • Changeset: none. @object-ui/app-shell is private: true, so the PR carries skip-changeset.
  • The jsonEditorUnder helper in PageBlockInspector.relatedListProps-27054.test.tsx says the label "is a sibling, not a for". That comment is now out of date; the helper still works because the label and the textarea share a parent. Left as is (outside this card's file surface).
  • Three other labels in the same file still name no control: FieldListField's label, and the string-list and array labels in renderField. Each labels a group of rows, so the fix there is a group name (role="group" with aria-labelledby), not this for pairing. Reported to the seat as a finding; not changed here.

Verification

Derived gates at e781cf8f (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, 24 families), every one run locally, exit codes captured before any pipe: all 24 exit 0, including node scripts/check-sdui-manifest.mjs and its --self-test, and pnpm --dir ui exec vitest run scripts/ under the verify lock. dispatch-gates --ran: 24 derived, 24 run, 0 NOT-MEASURED. dispatch-gates --tier: no path-derived mandate. 109 changed lines, under the 3000 threshold; no governed path. Every family CI runs is read from this PR's check-runs.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic.
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: its generated artifacts are regenerated and committed.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JQwxbW6EicqdfE9onssvrp


Generated by Claude Code

InspectorJsonField drew its Label beside the textarea with no for/id
pairing, so every curated JSON prop exposed an unnamed text box. Mint the
id with React.useId() and pair Label htmlFor with the textarea id.

Claude-Session: https://claude.ai/code/session_01JQwxbW6EicqdfE9onssvrp
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · seat domain:ui#5 · session_01JQwxbXGpVJHZTXXerMoY4C · head e781cf8f · 2026-10-28T13:16Z

席位意见:Studio 页面块检查器里的 JSON 编辑框原先没有可访问名称:标签和文本框没有关联,屏幕阅读器读到的是一个无名文本框。现在用 React.useId() 把 Label htmlFor 和 textarea id 配对,与同文件其他行做法一致。

  1. Shape: Fixes #27178; 2 files, +108/−1: PageBlockInspector.tsx +6/−1 (InspectorJsonField only: one useId(), htmlFor, id), one new test file (102 lines). skip-changeset: @object-ui/app-shell is private: true.
  2. Premise measured: the order's mechanism held. The seat read the diff: the id is minted per instance rather than derived from the label or prop name, because renderField also draws this editor inside array rows, where a derived id would repeat. The existing invalid/error wiring is untouched.
  3. Gates / check-runs: PR check-runs on e781cf8f: 45 success, 19 skipped (path-filtered), 0 failure; mergeable_state clean. Dev: 24 derived, 24 run, 0 NOT-MEASURED; 13 PageBlockInspector files / 86 tests green; app-shell type-check exit 0. Ablation (drop the pair): both new pins (add, Sort) turn red with the card's wording, restored.
  4. Accepted deviations: none. The PR body's "108 changed lines" is corrected to 109 by the seat's body amendment (relay issue_patch).
  5. Out of scope: the Columns, string-list and array editor labels in the same file name no group. Filed as Studio page block inspector: the Columns, string-list and array prop labels name no group, so their rows have no accessible group name #27256 (role="group" + aria-labelledby). A stale helper comment in PageBlockInspector.relatedListProps-27054.test.tsx was noted and not filed. Skills-docs: none — a11y wiring only.
  6. Landing: not Tier H (no governed path, 109 lines); ready for landing (ready + auto-merge).

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 28, 2026 13:17
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 28, 2026 13:18
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 28, 2026
Merged via the queue into main with commit 7858c1d Oct 28, 2026
65 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-qresy5 branch October 28, 2026 13:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Studio page block inspector: InspectorJsonField's Label is not associated with its textarea, so curated JSON props have no accessible name

2 participants