Skip to content

fix(console): Create User marks a missing email on its input, in the user's language - #28649

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

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

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

Requested by Leehom · project thread

Fixes #28289

Clause-②: yes (widening) — @object-ui/core newly exports refusalFieldErrors (reached through the package entry's export * from './actionResponse.js'); no server answer, accept set or spec key changes.
Metadata-protocol: none

维护者速读

  • 改了什么:Console 的「创建用户」对话框里,邮箱留空提交后,服务端返回的 required 字段错误现在直接标在「邮箱」输入框下(手机号登录开启时也标「手机号」),用用户当前语言的「X 为必填项」,不再只在按钮上方显示一句英文。
  • 为什么改:Create User refusal for a missing email answers the generic INVALID_REQUEST, so the console cannot localise it per field #28289 的服务端那一半(PR fix(plugin-auth): Create User with no email answers the field-level VALIDATION_FAILED refusal #28354)已经按字段返回 error.fields,但 Console 读字段错误的那个函数只认 error.details.fields,没读到 error.fields,对话框也从不根据服务端结果标字段,所以非英文用户看到的还是英文。契约评审 6539927716 定了 A:由 Console 用自己的翻译措辞。
  • 风险与回滚:低。只在拒绝结果带有 required 字段条目、且该字段正是对话框里画出的参数时才标字段;其它拒绝照旧显示服务端原文。回滚即 revert 本 PR。
  • 先看:ui/packages/app-shell/src/views/ActionParamDialog.tsx(refusalParamMarks 与 handleSubmit 里的失败分支);ui/packages/core/src/actions/actionResponse.ts(refusalFieldErrors 多读一处 error.fields);ui/packages/app-shell/src/utils/flowResponse.ts(apiActionRefusalResult)。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/hooks/__tests__/useConsoleActionRuntime.createUserRequiredField-28289.test.tsx,预期 6/6 通过(main 上 4 个失败)。
  • 需要你做的:无,席位落地。

What changes

  • @object-ui/core, actions/actionResponse.ts: refusalFieldErrors, the reader behind ActionResult.fieldErrors, now reads a refusal's error.fields as well as error.details.fields and the legacy inner fields. Both envelope members are on the spec's EnhancedApiErrorSchema (fields, and details whose documented example carries details.fields). The data and action doors put a thrown validation failure under details.fields; plugin-auth's Create User builds its own refusal and names the list fields, and the action door relays it whole. The function is now exported, so the api handlers use the same reader the /actions refusals go through (no second spelling).
  • @object-ui/app-shell, utils/flowResponse.ts: new apiActionRefusalResult(res, json, fallback, locale): the failed ActionResult for a non-2xx type: 'api' answer, with error from apiActionFailureMessage (unchanged) and fieldErrors from refusalFieldErrors when the refusal named any. Both api hosts use it: useConsoleActionRuntime (list and page actions, both the action door and the direct fetch) and RecordDetailView (its door branch and its direct fetch).
  • @object-ui/app-shell, views/ActionParamDialog.tsx: on a refused run, refusalParamMarks turns each required entry whose field names a collected param into the dialog's own mark, actionDialog.requiredError with the param's localized label (the same key and rendering the dialog's client-side required check uses, so it reads "邮箱 为必填项" in Chinese). When every entry is marked, the refusal box is not drawn, because it would only repeat the marks in English. An entry the dialog cannot mark (another code, a param it does not draw, an unresolved or carry-over param) keeps the box with the server's text. A refusal with no entries keeps today's display. Typing in a marked input clears its mark, as before. The header's objectui#13161 paragraph no longer says the dialog marks no field.
  • .changeset/28289-create-user-required-field-mark.md: @object-ui/core patch, @object-ui/app-shell patch. Core's files[] ships dist; app-shell is private, and its code ships in the Console bundle, as the other app-shell changesets record.

Not touched, as ruled: the server answer, any error code, packages/spec.

Tests

The wire the dialog reads, measured at the action door

sys_user.create_user is a declared same-origin type: 'api' action, so the Console click goes through POST /api/v1/actions/sys_user/create_user. A throwaway probe on api-action-forward.test.ts's own harness (boot({ answer }), not committed) had the target answer plugin-auth's body for an empty email. The door answered:

PROBE28289 400 {"success":false,"error":{"code":"VALIDATION_FAILED","message":"Email is required","httpStatus":400,"fields":[{"field":"email","code":"required","message":"Email is required"}]}}
Test Files  1 passed (1) · Tests 1 passed | 58 skipped (59) · VERDICT command-exit 0

That is the body the new Console test feeds through its mocked door. Producer check: the server's field names are email and phoneNumber, and sys_user.create_user declares the params email (field-backed, so name is the field name) and phoneNumber: they match.

Red on base a798ae8, green at head

useConsoleActionRuntime.createUserRequiredField-28289.test.tsx: a real ActionRunner, the console runtime's api and param handlers, the real ActionParamDialog, a real I18nProvider in zh, and the mocked door answering the measured body.

Base source (the five changed source files restored from a798ae8f, the new test kept): Test Files 1 failed (1) · Tests 4 failed | 2 passed (6) · VERDICT command-exit 1. Each red case reads Unable to find an element with the text: 邮箱 为必填项. The two controls (a refusal naming no field keeps its box; a created user closes the dialog) pass on base. Restored with git checkout HEAD, git diff HEAD empty.

Head: Test Files 1 passed (1) · Tests 6 passed (6) · VERDICT command-exit 0.

Cases: an empty email marks Email with the Chinese sentence, aria-invalid="true", no refusal box and no English sentence, dialog still open; phone sign-in on marks Email and Phone Number; typing clears the mark; control, a refusal naming no field keeps the server's sentence in the box and marks nothing; control, an entry the dialog cannot mark keeps the box beside the mark it can make; control, success closes the dialog.

actionRefusalFields-28289.test.ts (core): error.fields is read, an /actions refusal carrying it answers fieldErrors, error.details.fields is still read, a refusal naming no field answers none.

Ablations, both through scripts/ablation-replace.mjs

The subjects are src (@object-ui/core is aliased to packages/core/src in ui/vitest.config.mts; the app-shell files are imported by relative path), so no dist is in the path and no rebuild is needed.

  1. Reader: anchor ?? b?.error?.fields deleted from actionResponse.ts (anchor 1 → 0, blob 00c84412e26a → 197eaad93a94): Tests 4 failed | 2 passed (6), VERDICT command-exit 1. Restored: blob 00c84412e26a equal to HEAD, git diff HEAD empty.
  2. Dialog: anchor setErrors(marks); deleted from ActionParamDialog.tsx (anchor 1 → 0, blob 29087c772629 → 79e9f69aba8a): Tests 4 failed | 2 passed (6), VERDICT command-exit 1. Restored: blob equal to HEAD, git diff HEAD empty.
Package suites, typecheck, lint
  • pnpm --dir ui exec vitest run packages/core/: Test Files 228 passed (228) · Tests 4226 passed | 27 skipped (4253), VERDICT command-exit 0.
  • app-shell, narrowed: every test file under packages/app-shell/src and apps/console/src that names ActionParamDialog, useConsoleActionRuntime, flowResponse, RecordDetailView, apiActionDoor, refusalFieldErrors or interpretActionResponse: Test Files 284 passed (284) · Tests 2391 passed (2391), VERDICT command-exit 0. The full 2,029-file app-shell run on two workers was stopped after 34 minutes; CI's Test UI runs the full set.
  • pnpm --filter @object-ui/core run type-check (src and tsconfig.test.json): exit 0. pnpm --filter @object-ui/app-shell run type-check (src and tsconfig.test.json): exit 0.
  • ESLint on the seven touched files, root config with --no-inline-config and the ui/ config: 0 errors under each. Package-wide, the root config reports 22 errors in ui/packages/app-shell files this diff does not touch.

Acceptance notes

  • The runner still toasts a refused run's error (the server's English sentence) as it did before; the dialog no longer repeats it when every entry is marked. Whether the toast should step aside for a fully marked refusal is the runner's call, not changed here.
  • With phone sign-in on, the server sends one required entry for each of Email and Phone Number, so both inputs read "required" although either one satisfies the server. That is the marking the order asked for; a wording of "one of these" would need a second key.
  • The platform carries a refusal's field list in two envelope members (error.details.fields from the data and action doors, error.fields from plugin-auth's own refusals). This PR reads both; it does not choose one.

Verification

  • Every reading above and below is at head cd271105.
  • Derived gate list: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on this diff gave 34 commands. Each ran with its exit code captured before any pipe: all 34 exit 0. --ran reconciliation: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. check:type-check-debt first answered exit 3 (PREREQUISITE NOT MET: its closure build queued behind this branch's own test run on the shared lock); re-run under the lock it answered OK, 0 raw tsc errors, exit 0.
  • pnpm check:new-line-citations (from ui/): 0 new citations.
  • origin/main moved 13 commits past the base a798ae8f; none touches a file this diff changes, so the branch was not merged with it.

Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

席位核对 · ACCEPT · PR #28649 · 2026-11-01T12:05Z

Head-sha: db536a519babf055650eb1bd4ceeee35c14b1d0b

维护者速读(席位终稿)

  • 改了什么:Console「创建用户」对话框里邮箱留空提交后,服务端返回的 required 字段错误现在标在「邮箱」输入框下(手机号登录开启时也标「手机号」),用用户当前语言的「X 为必填项」,不再只显示一句英文。
  • 为什么改:Create User refusal for a missing email answers the generic INVALID_REQUEST, so the console cannot localise it per field #28289 的服务端那一半(PR fix(plugin-auth): Create User with no email answers the field-level VALIDATION_FAILED refusal #28354)已按字段返回 error.fields,但 Console 读字段错误的函数只认 error.details.fields,对话框也不按服务端结果标字段。契约复核 6539927716 定了由 Console 用自己的翻译措辞。
  • 风险与回滚:低。只在拒绝结果带 required 条目且该字段正是对话框里的参数时才标字段;其它拒绝照旧显示服务端原文。@object-ui/core 多导出一个 refusalFieldErrors(公开面 +1,故 Clause-②: yes (widening))。回滚即 revert。
  • 先看:ActionParamDialog.tsx 的 refusalParamMarks 与失败分支;actionResponse.ts 的 refusalFieldErrors 多读 error.fields;flowResponse.ts 的 apiActionRefusalResult。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/hooks/__tests__/useConsoleActionRuntime.createUserRequiredField-28289.test.tsx → 6/6(main 上 4 红)。
  • 需要你做的:无。席位按常设授权落地。
  • 席位意见:只读已上线的字段错误形状,不动服务端;席位复核(Clause-②: yes 申报档)通过。可落。

核对六项

  1. 形态:draft → main;Fixes #28289,本 PR 交付卡剩下的 Console 一半,合并即关卡正确;其余闭合关键词零命中(已扫)。文件 8 个,在认领申报面内(ui/packages/core 的读取器属「复用已有逐字段读取器」一项)。
  2. 前提实测:红先在 base 上 4/6 红;两处消融各自转红,复原后 blob 回 HEAD。dev 实测动作门把 Create User 的 error.fields 原样转达。
  3. 门禁:本 head check-runs 44 success、11 skipped(路径未命中)、0 失败或待跑。dev 本地:34/34 派生门禁 exit 0,--ran 0 NOT-MEASURED;改申报后 changeset 与 ADR-0087 门在新 head 复跑皆 exit 0。
  4. 接受的偏差:app-shell 全套 2,029 文件本地超 34 分钟,dev 缩到 284 文件,全量由 CI Test UI 覆盖,接受;未合 main(新提交 13 个,均不触本 PR 文件),接受。申报由 no 改为 yes (widening)(新导出),席位要求,已改。
  5. 范围外去向:无。Metadata-protocol: none,无 Skills-docs: 欠账。
  6. 落地条款:Clause-②: yes (widening),非 spec 路径、非收窄 ⇒ 车道席位 TIER_DEFAULT 复核,即本 ACCEPT:新导出 refusalFieldErrors 只多读 spec EnhancedApiErrorSchema.fields,丢弃无字符串 field 的条目,不猜;无服务端、接受集或 spec 键变化。check-governed-merges --pr 28649:NOT governed,370 行。落地授权,维护者原话:「按照 pm-dispatch 规则复核过的都授权」(2026-10-25T11:35Z)。

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 1, 2026 12:06
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 1, 2026 12:06
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 1, 2026
Merged via the queue into main with commit 9da2f4b Nov 1, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-2v6d35 branch November 1, 2026 12:23
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 tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create User refusal for a missing email answers the generic INVALID_REQUEST, so the console cannot localise it per field

1 participant