Skip to content

fix(studio): field inspector API name refuses a taken name and drops the trailing underscore - #27591

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-wom2w0
Oct 29, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-wom2w0

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Bill · project thread

Fixes #27517

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:Studio 字段检查器的「API 名称」输入框改为显示作者实际键入的内容;键入其他字段已用的名称时,给出与「添加字段」弹窗完全相同的拒绝提示,字段保留编辑前的名称;写入的名称按新建对象对话框的规则规范化(末尾标点不再存成下划线)。
  • 为什么改:原来键入已存在的名称会无声吞掉最后一个按键并把半截名称(statu)存进草稿;Estimated Hours! 会存成 estimated_hours_。
  • 风险与回滚:仅 Studio 字段检查器一个输入框与「添加字段」弹窗的一处提示取值(抽成共享函数,行为不变);不改元数据协议;回滚即 revert 本 PR。
  • 先看:ObjectFieldInspector.tsx 的 FieldApiNameInput(键入、拒绝回退到编辑前名称、失焦复位);AddFieldPopover.tsx 的 fieldNameTakenRefusal;新测试文件。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.apiNameRefusal-27517.test.tsx,预期 6 个用例全绿。
  • 需要你做的:无,席位落地。

What changes

  • ObjectFieldInspector — the API name box is now FieldApiNameInput, a box that keeps what the author typed as its own text and still writes the field's name on every keystroke, as before:
    • The text is normalised as the new-object dialog (CreateItemDialog) normalises an identifier: toFieldNameLoose while typing, so an _ can be typed mid-word, and the strict rule (toFieldNameOrEmpty, which is toFieldName without its field placeholder) for the name written. Estimated Hours! saves estimated_hours.
    • A name another field of the object holds is refused with the Add field popover's own sentence. The box keeps showing what was typed (no keystroke dropped), marks itself aria-invalid and points aria-describedby at the refusal, and the field is left carrying the name it had when the edit began, not the last free prefix of the refused name. An emptied box writes no name either.
    • Leaving the box shows the name the field carries (the refusal goes with it).
    • A name arriving from outside the box (another field selected, the Label deriving one) replaces the text; the box's own write echoing back does not.
    • setKey now receives the already-normalised name; its collision guard reads the same takenNames list the box refuses against. The objectui#7615 ownership stamp is unchanged: every keystroke still stamps the author as owner.
  • AddFieldPopover — the duplicate-name refusal is extracted as the exported fieldNameTakenRefusal(name, takenNames, locale) and the popover reads it; its hint logic is otherwise unchanged (same branches, same key engine.studio.data.addFieldApiNameTaken). No second validator, no new string.
  • Root changeset: @object-ui/app-shell patch.

Tests

  • New ObjectFieldInspector.apiNameRefusal-27517.test.tsx (6 cases), typing one character at a time through a host that feeds each patch back and follows the rename with the selection: the refusal equals fieldNameTakenRefusal for the same name; the field keeps estimated_hours; typing past the refusal (status_2) takes the name; blur restores; an emptied box saves nothing; Estimated Hours! saves estimated_hours; repair_ticket still types its underscore.
  • Ablation, one leg per behaviour, each via scripts/ablation-replace.mjs (anchor hit 1 → 0, blob changed, restored, git diff HEAD empty afterwards); app-shell tests import the source, no build in the path:
Leg Mutation Result
rollback refused name written instead of the edit-start name 2 of 6 red (keeps previous name; blur restore)
normaliser loose text written as the name 1 of 6 red (Estimated Hours!)
display box shows the stored name, not the typed text 3 of 6 red (refusal keystroke, emptied box, trailing _)
  • Existing inspector, popover, Data pillar and external-object suites green before the main merge (28 files, 297 tests); the full metadata-admin/ and studio-design/ directories are in the gate run below.

Acceptance notes

  • The Label → API name derivation has the same prefix shape on a collision: deriving Status letter by letter on a new field whose object already has status leaves the derived statu. That path is deriveNameFor, not the API name box, and is outside this card; noted, not changed.
  • The refusal is shown while the author is in the box; leaving the box restores the stored name and drops the refusal, which is the "previous name kept" the card's Expected section describes.
  • Docs-driven rule (ui/AGENTS.md ✨ Set up Copilot instructions #2): no docs page describes the inspector's API name box; nothing to update.

Verification

Gate readings are recorded in the report comment on #27517.


Generated by Claude Code

…ises trailing punctuation

The API name box showed the stored name, so a keystroke whose name another
field held was dropped without a word and the half-typed prefix was saved,
and a trailing punctuation mark saved a trailing underscore. The box now
shows what the author typed, refuses a taken name in the Add field popover's
own sentence (shared as fieldNameTakenRefusal), keeps the name the field had
when the edit began, and writes the strict toFieldNameOrEmpty name.

Claude-Session: https://claude.ai/code/session_01Utp6CYtNGSzW4fU2cRSbut
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Oct 29, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe

席位核对

ACCEPT · head d16e14badad97534906996311a88d43ff8f45f36 · 2026-10-29T14:32Z

  • Shape: draft → main, Fixes #27517 on its own line, no other card number beside a closing keyword. Clause-②: no and Metadata-protocol: none match the claim; @object-ui/app-shell is private: true, so the newly exported fieldNameTakenRefusal is no published surface. No contract review owed (no Clause-②: yes, no narrowing, no spec or governed path).
  • Premise, measured: the triage direction holds in the diff: the refusal is the Add field popover's own sentence, extracted as fieldNameTakenRefusal (AddFieldPopover.tsx), and the written name uses toFieldNameOrEmpty; no second validator. The popover's nameHint rewrite was read branch by branch against main and is equivalent (typed → refusal or null; empty + derived taken → refusal of the derived name; else not-derived hint).
  • Gates (check-runs on the head): 44 success, 11 skipped, 0 failure; Lint & Repo Gates, TypeScript Type Check and Check Changeset each success. check-governed-merges --pr 27591: NOT governed, 314 changed lines.
  • Accepted deviations: the PR's attribution line links the card thread instead of the seat thread (same project, same requester); the dev's commit-trailer note and the re-run of check:new-line-citations are local and change nothing landed.
  • Out of scope: the dev's read-only note (the Label-derived name stops at a free prefix when the full name is taken) stays an Acceptance note: it is the existing Label path, not a silent wrong save, and nothing was filed.
  • Landing: non-governed, all checks green, mergeable_state clean ⇒ the seat lands it (pr_ready + automerge_enable).

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 29, 2026 14:34
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 29, 2026 14:34
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 29, 2026
Merged via the queue into main with commit a4e7014 Oct 29, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-wom2w0 branch October 29, 2026 14:51
os-bill pushed a commit that referenced this pull request Oct 29, 2026
…-ufaqzz

Brings in #27591 (ObjectFieldInspector API-name input, card #27517) and the rest of main; no conflicts.

Claude-Session: https://claude.ai/code/session_01Utp6CbCrKUjGeTReNEmUJx
Co-authored-by: Claude <noreply@anthropic.com>
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/m tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

1 participant