Skip to content

feat(studio): a Chinese display name derives its identifier by pinyin at every Studio create door - #29734

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

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-tlcgw9

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

Fixes #29690

Clause-②: no
Metadata-protocol: none

Claim: 6593397218 · amendments 6593439786, 6593495761

维护者速读

  • 改了什么:Studio 里所有“由名称生成标识”的新建入口(新建软件包、新建对象等、添加字段、通用新建表单、新建视图、对象/报表/数据集设计器的 Name、字段 API 名、选项值),中文名称现在按词转成不带声调的拼音再生成标识:维修中心 → weixiu_zhongxin,客户等级 → kehu_dengji。生成的标识照常显示、可改。
  • 为什么改:中文管理员用中文起名,过去每个入口都停下来要求手填英文标识,上手头十分钟要被拦好几次。
  • 风险与回滚:拼音字典 pinyin-pro 是按需加载的独立分包,首次遇到汉字才下载;加载失败时退回原来的“无法生成,请手填”。常驻包只多 0.9 KB(预算内)。回滚即还原本 PR,无数据迁移。
  • 先看:romanizeHan.ts(共享规则与加载状态);StudioDesignSurface.tsx 两行(添加字段预览名与实际创建名一致);PackageFormDialog.tsx(软件包 ID 与命名空间)。
  • 两分钟验证:cd ui && pnpm exec vitest run packages/app-shell/src/views/metadata-admin/romanizeHan.test.ts,预期 9 条全绿(含多音字 银行→yinhang、行动→xingdong)。
  • 需要你做的:无,席位落地。

What changes

One shared derivation, ui/packages/app-shell/src/views/metadata-admin/romanizeHan.ts, sits in front of each door's existing sanitizer. Latin text is untouched; each Chinese word becomes its toneless pinyin, then the door applies the rule it always applied (toFieldNameLoose, toFieldNameOrEmpty, slugify, or the package id's toIdSegment). So the namespace prefix, the __c suffix of a customization pair and the taken-name hint are unchanged; they now see weixiu_zhongxin where they used to see an empty string.

  • Doors. New package (com.<org>.weixiu-zhongxin, namespace weixiu_zhongxin); the new-item dialog; Add field, where the popover's preview and the host's created name both go through the helper, so the name previewed is the name created; the generic create form (createDerive.ts); New view; the object, report and dataset designers' Name; a fresh field's API name in the field inspector; and a picklist option's value. That last one was a silent loss: a Chinese option label derived an empty value and the row was never saved.
  • Words, not syllables. Word boundaries come from Intl.Segmenter('zh', { granularity: 'word' }), and pinyin-pro reads a polyphone by its word (银行 → yinhang, 行动 → xingdong). ü is written v (绿色 → lvse). Without Intl.Segmenter, each character's reading is joined by _ (ke_hu_deng_ji).
  • Loading. The dictionary is fetched the first time a door sees a Han character. Until it lands, the door keeps the identifier empty, keeps Create off, and holds back its "not derived" line (romanizerPending). When it lands, a door whose identifier still follows the name fills it in without another keystroke (useHanRomanizer / useWhenRomanizerLands). The fill runs once, on the render where the dictionary landed, so a person who clears the identifier to type their own is never refilled. If the chunk cannot be fetched, the load settles as failed, one console.warn says so, and every door falls back to its "not derived" line and a hand-typed identifier, as before this PR.
  • Japanese. Text containing kana is left alone, so a Japanese name still asks for the identifier. The existing pins that asserted "a Chinese name derives nothing" now assert the same invariant with a kana sample.
  • Package dialog subtitle. The subtitle says "Give it a name to start; everything else is filled in for you." That promise now holds for a Chinese name. It still does not hold for a Japanese one, which opens the advanced section with the hint, as before. The copy is unchanged.

Bundle choice. The client derives the pinyin itself, from pinyin-pro loaded as a lazy chunk. It is the library the server's pinyin search plugin already uses and is already in the lockfile, and it reads polyphones by word. I rejected a server route because it would add a REST route for a create-time preview, and plugin-pinyin-search runs only behind OS_SEARCH_PINYIN_ENABLED. I rejected the collator-sort libraries because they read polyphones wrong.

pnpm --dir ui check:eager-closure:

eager closure (gz) eager chunks
base 1c1a5d40 2092.9 KB 100 of 2397
this branch before the main merge 2093.8 KB 101 of 2399
final head (after merging main) 2094.3 KB 101 of 2399

The last row's extra 0.5 KB is main's own change in vendor-objectstack, which went from 573.1 to 573.6 KB. The ceiling and baseline are untouched, and headroom is 337.3 KB. The dictionary chunk is assets/esm-Cst8rBvH.js: 286.6 KB raw, 141.7 KB gzipped, and lazy. The helper itself, about 0.9 KB, is in the eager closure because PackageFormDialog is eager. The seat accepted this on the card thread.

Tests

New:

  • romanizeHan.test.ts
    • Before load: text unchanged and pending.
    • After load: weixiu_zhongxin and kehu_dengji through every door sanitizer, plus the polyphone pair, lvse, mixed 维修Center 2号 → weixiu_center_2_hao, and kana unchanged.
    • With Intl.Segmenter absent: ke_hu_deng_ji and yin_hang.
  • romanizeHan.loadFailure.test.tsx
    • The chunk import rejects, the load settles, nothing is pending, and one warning is logged.
    • CreateItemDialog shows its "not derived" line, and a hand-typed id submits.
  • romanizeHan.lands-29690.test.tsx
    • CreateItemDialog stays empty and quiet with Create off while loading, then shows weixiu_zhongxin with Create on.
  • createDerive.romanizerLands-29690.test.tsx
    • The generic create form is pending, then kehu_dengji with nothing underivable.
  • ReportDefaultInspector.romanizerLands-29690.test.tsx
    • The designer Name fills on landing.
    • A cleared Name is not refilled.
  • CreateViewDialog.pinyinName-29690.test.tsx
    • 客户列表 → kehu_liebiao, and the name is editable.
    • A kana label derives nothing.

Extended with a zh-CN case each:

  • CreateItemDialog.notDerived-13389: the namespace prefix gives repairs_weixiu_zhongxin.
  • PackageFormDialog.idDerive-11792: org acme gives com.acme.weixiu-zhongxin and namespace weixiu_zhongxin in the posted manifest; the id stays editable.
  • DataPillar.addFieldPopover-12516: the preview kehu_dengji equals the created field; a second 客户等级 gets the taken-name hint.
  • DataPillar.baseObjectExtension-28575: the preview kehu_dengji__c equals the extension field saved.
  • ResourceEditPage.underivableName-28232
  • Report…/DatasetDefaultInspector.labelLeavesNameEmpty-12789
  • ObjectDefaultInspector
  • ObjectFieldInspector.apiNameSync
  • ObjectFieldInspector.optionLabelFirst-27521: 进行中/已完成 → jinxing_zhong/yi_wancheng saved, and the field parses.

Kana samples replace the Chinese ones in PackageFormDialog.derivedFields-12512, PackageFormDialog.nameOnlyFirstStep-27833 and ResourceEditPage.identifierIssueI18n-28369.

Ablations went through scripts/ablation-replace.mjs. Each one went red and was restored to HEAD:

mutation result
romanizeHan returns its input 8 of 16 red
the Add-field host derives without pinyin 2 of 12 red, preview ≠ created
the landing fill runs every render 2 of 7 red, a cleared Name is refilled

Acceptance notes

  • A fresh field's API name in the field inspector derives on the label keystroke after the dictionary has landed. It does not re-derive on landing by itself: the inspector returns early before its row exists. In practice the dictionary has already landed by then, because a field is created through Add field, which loads it.
  • Add field keeps parity with a Latin label. The derived name is shown as the popover's preview, and the person edits it in the field inspector after creating, as today.
  • No zh/en catalogue rows changed. Every door's existing "not derived" sentence still applies, and only to text the rule cannot read.

Verification

All runs were under os-verify-lock.

Affected tests, type-check, lint
  • turbo-affected app-shell test files: 308 files in 6 batches.
    • The first run's reds were all pins asserting that a Chinese name derives nothing, plus one real bug, the designer Name refilling after it was cleared. Both are fixed above.
    • The new and changed files re-ran 35 of 35 green (259 tests). The field inspector files re-ran 31 of 31 green (324 tests).
  • pnpm exec turbo run type-check --filter=@object-ui/app-shell: 65 of 65 tasks pass.
  • eslint on the changed files: 0 errors.
Gates

node scripts/pm/dispatch-gates.mjs --commands derived 57 commands on this diff, and --ran reconciled all 57. 56 exit 0. check:dual-build-cjs-loads exits 3, because it needs every workspace package built; it is left to CI. The exit codes are in the os-dev report on the card.


Generated by Claude Code

Every Studio create door that derives a machine name from a display name
now romanises Chinese words to toneless pinyin first (lazy pinyin-pro
chunk, Intl.Segmenter word boundaries), then applies its existing
sanitizer, so prefix, __c and taken-name rules are unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jTMu2EmYV84fwNrBXw
Shared rule (words, polyphones, ü, kana left alone, Segmenter missing),
chunk-load failure and late landing, and a zh-CN case per door; pins that
asserted a Chinese name derives nothing now use a Japanese one.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jTMu2EmYV84fwNrBXw
…lands

The object, report and dataset designers refilled an empty Name from the
Label on every render, so a person who cleared the derived Name to type
their own got the pinyin back. A shared useWhenRomanizerLands hook runs the
fill only on the render where the dictionary landed; the picklist options
editor uses it too. The permission-set banner pin types a Japanese label,
since a Chinese one now derives a Name.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jTMu2EmYV84fwNrBXw
@github-actions github-actions Bot added size/xl dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 3, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y

席位核对 · ACCEPT · 2026-11-03T03:23Z

Head-sha: 40b900e10a2820904f1e1278597a70e674d3c297

  • Shape: draft → main, Fixes #29690 in the body only. 33 files, +1035 / −104 (1139 lines ≤ 3000). No governed path. Clause-②: no and Metadata-protocol: none are line-initial in the body and the changeset, so no review face applies and no contract review is owed.

  • File surface: as claimed and amended twice. 6593439786 adds app-shell/package.json and the lockfile, createDerive.ts, the field inspector's option and API-name doors, and the New view, object, report and dataset designer doors. 6593495761 adds the two StudioDesignSurface.tsx helpers. ResourceEditPage.tsx needed no edit, because its door derives in createDerive.ts.

  • Read in the diff:

    • romanizeHan sits in front of each door's existing sanitizer. The namespace prefix, the __c suffix and the taken-name hint are unchanged.
    • The dictionary loads by dynamic import('pinyin-pro') only when the text holds Han and no kana. While it loads, romanizerPending keeps Create off and holds back the "not derived" line. A failed load settles as failed and falls back to the old hint.
    • useWhenRomanizerLands fills once, on landing, so a person who cleared the identifier is not refilled.
    • Add field's preview and the host's created name go through the same helper.
  • Pins:

    • each door in zh-CN;
    • namespace prefix (repairs_weixiu_zhongxin);
    • preview equals created for kehu_dengji and kehu_dengji__c;
    • taken-name hint;
    • chunk-load failure;
    • polyphones (银行 → yinhang, 行动 → xingdong);
    • Intl.Segmenter absent (ke_hu_deng_ji);
    • late landing.

    Pins that asserted "Chinese derives nothing" keep that invariant with a kana sample. Three ablations each went red.

  • Bundle: check:eager-closure read 2092.9 KB gz on the base and 2094.3 KB gz on the head. Of that, 0.9 KB is the helper, eager because PackageFormDialog is eager; the seat accepted this on the card thread. The other 0.5 KB is main's own vendor-objectstack. Ceiling and baseline are untouched. The dictionary is the lazy chunk esm-*.js, 141.7 KB gz.

  • Accepted gap (stated in the body): a fresh field's API name in the field inspector does not re-derive when the dictionary lands on its own; the next label keystroke derives it. Add field, which loads the dictionary first, is not affected.

  • Gates (check-runs on this head): 48 success, 8 skipped (opt-in or matrix placeholders), 0 failed or pending, including all 16 Test UI shards. All ten required contexts succeeded.

  • Landing: ordinary queue landing, pr_ready then automerge_enable. Fixes #29690 closes the card on merge.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 3, 2026 03:25
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit 11c4eb5 Nov 3, 2026
57 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-tlcgw9 branch November 3, 2026 03:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/xl tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Studio: a Chinese name yields no identifier at any create door; derive one by pinyin, by one rule everywhere

1 participant