Repository navigation
fix(ui): object-form drawer and modal saves toast the record-named save message - #28204
Conversation
…and modal saves The object-form block's drawer and modal containers now give a landed write the record-named toast the console's record dialogs and record page give, through one shared builder (recordSaveMessage in @object-ui/i18n) that replaces the two copies in app-shell. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qiby8nyBCmFZV9NNymGG
…derive a name from Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qiby8nyBCmFZV9NNymGG
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qiby8nyBCmFZV9NNymGG
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qiby8nyBCmFZV9NNymGG
…ntainer-save-toast
|
domain:ui#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: At |
|
domain:ui#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract reviewServed-tier: Inputs: card #27453 body and its seven comments (triage 6491491777, devx claim 6499390780 and release 6500073473, retriage ask 6523138910, triage ruling 6523407987 「平台相关的功能,应该尽量先改平台」, ui seat 1 claim 6526202454, the os-dev-report); PR #28204 body, 7-file list and the diff against ① Derived judgments
② Semver level
③ Boundary flags
Should-fix (none blocks the declaration):
Implemented-by: VERDICT: PASS |
|
domain:ui#1 · os-tesla · session_01L9qibzC6RxMfB59EsgAoNN 席位核对ACCEPT · seat 席位意见:在页面里用抽屉或弹窗表单(
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 41096932758 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 41098079932 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 41099798817 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
|
domain:ui#1 · os-tesla · session_01L9qibzC6RxMfB59EsgAoNN Re-queue · 2026-10-30T20:00Z · head
|
domain:ui#1 · os-tesla · session_01L9qibzC6RxMfB59EsgAoNN
Requested by Tesla · project thread
Fixes #27453
Clause-②: yes (widening: @object-ui/i18n gains the public export recordSaveMessage; no accept set moves)
Metadata-protocol: none
维护者速读
object-form块以抽屉或弹窗打开(formType: 'drawer' | 'modal',含 React 页面里formType="drawer"的ObjectForm)保存成功后,现在弹出点名记录的成功提示,如Task "Fix login" updated,与控制台记录对话框一致;提示文案改由@object-ui/i18n新导出的recordSaveMessage统一生成,控制台的两份手抄副本一并换成它。ModalForm、动作弹窗、设计器表单都不经过这个容器,不会双重提示(有用例钉住);带submitHandler的表单不提示;回滚即 revert 本 PR。ui/packages/plugin-form/src/ObjectFormContainer.tsx的useContainerSaveFeedback、ui/packages/i18n/src/recordSaveMessage.ts、AppContent.tsx里改为调用共享构造器的recordSaveMessage。pnpm --dir ui exec vitest run packages/plugin-form/src/__tests__/containerSaveToastNamesRecord-27453.test.tsx,预期 13 个用例全绿。What changes
The triage ruling (6523407987) asked for the console's named-save feedback to become reachable from the platform: the drawer and modal forms raise the same save feedback the record pages do, through one exported helper rather than a second copy of
recordSaveMessage, with no change to the showcase page.@object-ui/i18naddsrecordSaveMessage(t, { label, objectDef, saved, mode }): the record-named message (form.createdNamed/form.updatedNamed), or the object-label one (form.createSuccess/form.updateSuccess) when the ADR-0079 resolver finds nothing nameable (itsRecord #IDfloor). It lives in@object-ui/i18nbecause its four keys live in that package's packs, it already depends on@object-ui/core(wheregetRecordDisplayNamelives), and both@object-ui/plugin-formand@object-ui/app-shellalready depend on it. No new dependency edge.@object-ui/app-shell: the console's record dialog (AppContent'srecordSaveMessage) and the record form page (RecordFormPage'shandleSuccess) each carried their own copy of that logic; both now call the shared builder. Same keys, same floor, same wording; the Console: a save that passes a warning-severity rule shows one toast, the record-named save toast carrying the warning #27999 warning claim andtoastRecordSaveare untouched.@object-ui/plugin-form:ObjectFormContainer, the container theobject-formblock supplies forformType: 'drawer' | 'modal'(ADR-0050 Decision 4), now wraps the author'sonSuccess: when the container itself wrote (it has a data source and nosubmitHandler, increateoreditmode) it toasts the record-named message, then runs the author'sonSuccess. A declaredsuccessMessageis used as written. The object definition is read throughgetObjectSchema, which the adapter has already cached for the form body.DrawerFormandModalFormthemselves are unchanged, so a host that mounts them directly (the console's record dialog,useActionModal, the designer's object and field forms) keeps owning its feedback and is never toasted twice.examples/app-showcaseis unchanged: its task-desk page'safterWritekeeps closing the overlay and reloading, and the toast now comes from the platform. Nopackages/specedit.Tests
ui/packages/plugin-form/src/__tests__/containerSaveToastNamesRecord-27453.test.tsx(new, 13 cases, each of the first six run fordrawerandmodal): edit toastsTask "Fix login" updatedbefore the author'sonSuccess, which still gets the record; create toastsTask "Write release notes" created; a metadata-only block with noonSuccessis toasted; a record with nothing nameable keepsTask created successfully; a declaredsuccessMessageis the toast; asubmitHandlerform is not toasted; a directly mountedDrawerFormwith anonSuccessis not toasted.AppContent.recordSaveToastNamesRecord-26172,AppContent.recordSaveOneToast-27999and everyRecordFormPage.*suite (14 files, 84 tests, all green).Ablation. With
ObjectFormContainer'stoast.success(message)replaced by a no-op throughscripts/ablation-replace.mjs(anchor 1 → 0 on disk), the new suite went 10 red / 3 green: every "toasts" row red, the three "not toasted" rows green, as predicted before the run. Restored by the tool: blob equal toHEAD's,git diff HEADempty.Acceptance notes
DrawerForm/ModalForm. The ruling allowed either the form variants or a React-page toast hook; a hook would have needed the showcase page to call it, which the ruling rules out. InsideDrawerForm/ModalFormthe toast would have fired for every host that mounts them directly and toasts itself (the console's record dialog throughcrud_success,useActionModal), so each would have needed an opt-out.ObjectFormContaineris the one place where theobject-formblock is the caller (ADR-0050 Decision 4), so the feedback sits there and the direct hosts are untouched; the last test row pins that.claimAnswerWarningsin@object-ui/app-shell). The block container lives in@object-ui/plugin-form, below app-shell, and has no claim, so a warning from a drawer/modal save still arrives on the write-warning channel's own toast, as it did before this change. Noted, not filed.successMessageon a drawer/modal block was never shown before. It is now the toast when declared; previously neither container read it.examples/app-showcase): itsafterWritekeeps closing the overlay and bumping the reload counter. Live reproduction against the showcase backend was not run in this container; the behaviour is pinned through the sameObjectFormContainerroute the React page'sObjectFormblock takes.@object-ui/app-shellisprivate: true, so the changeset bumps only@object-ui/i18nand@object-ui/plugin-form; the app-shell edit is a refactor onto the shared builder with no wording change.ModalForm/DrawerFormdirectly and own their feedback:AppContent(record dialog, viacrud_success),useActionModal, the designer'sFieldDesignerandObjectManager. None of them passes throughObjectFormContainer, so none gets a second toast; the last test row pins a directDrawerFormmount. Pages that use the block's container and gain a toast: the showcase task-desk page (drawer edit, modal create) and the showcase renewals page (ObjectForm formType="drawer"). Neither raised a toast of its own before, so neither is doubled.Verification
Head
e8e2bcbd(the branch merged withorigin/mainc48cd2c1; the four incoming commits touch no file underui/).HEAD.pnpm --dir ui exec vitest run packages/plugin-form/ packages/i18n/: 306 files. The first run had 2 failures inresidue-namespaces-3546.test.tsx, which pinsAppContent's fallback spellingt('common.record', { defaultValue: 'Record' }); the spelling is restored inAppContentand that file plusAppContent.recordSaveToastNamesRecord-26172re-ran 52/52 green.pnpm --dir ui exec vitest runonAppContent.recordSaveToastNamesRecord-26172,AppContent.recordSaveOneToast-27999andpackages/app-shell/src/views/RecordFormPage*: 14 files, 84 tests, green.type-check(with a real.d.tsbuild of the@object-ui/app-shell^...closure):@object-ui/i18n,@object-ui/plugin-form,@object-ui/app-shellexit 0.eslint --no-inline-configon the six touched source files: 0 errors.pnpm --dir ui run check:new-line-citations: 0 new.e8e2bcbd, every line run with stdin from /dev/null, exit codes on disk, reconciled withdispatch-gates.mjs --ran: 34 derived, 34 run, 0 NOT-MEASURED.Derived gates (local, head e8e2bcb)
Generated by Claude Code