Repository navigation
fix(plugin-form): a drawer or modal object-form save that answers warnings shows one toast, the record-named message carrying them - #28267
Conversation
…hat answers warnings Move the answer-warning claim and the record save toast from app-shell's write-warning channel into @object-ui/react, so the object-form drawer and modal container can claim its own write's warnings like the record page and the record dialogs do, with one claim registry for every surface. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibvQRyTQaTC3jT6F19i
|
domain:ui#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: |
|
domain:ui#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract reviewServed-tier: Inputs: card #28216 (body, triage 6530250373 and 6533339668, claim 6533291658, os-dev-report 6534234747, claim amendment 6534268411), the S1 should-fix of PASS record 6528599404 on #28204 that filed it, the PR body, the 10-file diff against merge base f40b777 (main has moved 100 files since; none is in this diff, and GitHub reports the head mergeable, clean), the head's check-runs (read 2026-10-30T22:18Z: check-runs on 0bd8234: 60 total, 45 success (Auto Label, Build & E2E UI, Build UI Deps, Check Changeset, Check PR Size, Dogfood Regression Gate, Governed Surface Queue Guard, Lint & Repo Gates, Lint UI, Lint · CI, release & engine gates, Lint · ESLint, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · repo & tooling gates, Lint · scoped · query-options erasure, Lint · scoped · slot lookup & corpus, Lint · scoped · tooling self-tests, Live E2E UI, PR-body guards (claim · duplicate · Part-of · single-writer path), Test Core, Test UI, Test UI (all 16), Type Check UI, Type Check · consumer gates, Type Check · debt ledger, Type Check · source gates, Type Check · workspace, TypeScript Type Check, filter); 15 skipped path-filtered (18.x upgrade harness (opt-in), Auto Label, Build Core, Build Docs, Check PR Size, Dogfood Regression Gate (matrix shard/3), Dogfood Verify CLI, Packed-tarball smoke (opt-in), Plan Test Core shards, Schedule gate · skip when a green push run covers the commit, Temporal Conformance (live PG + MySQL), Test Core (matrix shard/8), UI Published Artifacts); 0 failure, 0 in progress). Author ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
|
domain:ui#1 · os-tesla · session_01L9qibzC6RxMfB59EsgAoNN 席位核对ACCEPT · seat 席位意见:页面里的抽屉或弹窗表单(
|
Requested by Tesla · project thread
Fixes #28216
Refs #27453
Clause-②: yes (widening:
@object-ui/reactgains the exportsclaimAnswerWarnings,parkAnswerWarnings,answerWarningLines,toastRecordSaveand the typesAnswerWarningsEvent,AnswerWarningScope,AnswerWarningClaim,RecordSaveSink; they were internal to@object-ui/app-shell, which exported none of them, so nothing is removed)Metadata-protocol: none
维护者速读
object-form抽屉或弹窗保存记录时,如果服务端带回提醒(例如一条警告级的校验规则),现在只弹一个提示:写着记录名的「任务 "Fix login" 已更新」,提醒逐行附在里面;不再另外弹一条「已保存,有提醒」。和记录页、控制台记录弹窗的表现一致。@object-ui/react,只有一份实现;记录页和记录弹窗只改了 import,它们原有测试一条未改、全绿。容器只在打开时认领自己那次写入,用submitHandler的容器不认领。回滚即 revert 本 PR。ui/packages/react/src/record-save-feedback.ts(搬下来的认领注册表与新的parkAnswerWarnings);ui/packages/plugin-form/src/ObjectFormContainer.tsx(useContainerSaveFeedback里的认领与toastRecordSave);ui/packages/app-shell/src/providers/writeWarningToast.ts(emitWriteWarning改走parkAnswerWarnings)。pnpm --dir ui exec vitest run packages/plugin-form/src/__tests__/containerSaveToastCarriesWarnings-28216.test.tsx packages/react/src/record-save-feedback.test.ts,预期 2 个文件、17 个测试全绿。What changes
One home for the claim (
ui/packages/react/src/record-save-feedback.ts). The claim registry the record page and the record dialogs use (#27999) lived in@object-ui/app-shell's write-warning channel, which@object-ui/plugin-formcannot import (app-shell depends on plugin-form). It moves, unchanged in behaviour, to@object-ui/react, which both packages already depend on, beside the data-invalidation bus, the other module-level seam between writers and readers. Moved as they were:claimAnswerWarnings, the matching rule,answerWarningLines,toastRecordSaveand its sink type. New:parkAnswerWarnings(adapter, ev, show), the channel's side of the same registry, which parks an event's warnings on the claim that names it and answers whether one did. The event type is structural (AnswerWarningsEvent) because@object-ui/reactsits below@object-ui/data-objectstack;WriteWarningEventsatisfies it.Why this home and not the other options the order lists: an injected seam (the container accepting a claim from its host) would keep the registry in app-shell but leave a page block outside the Console with two toasts, and needs a context type in a lower package anyway;
@object-ui/i18n(next torecordSaveMessage) would put a write-coordination registry in a translation package;@object-ui/coreholds no React-facing runtime seams.@object-ui/reactis the lowest package all three save surfaces and the channel share with a precedent of this shape. No dependency edge is added:reactdepends oncore,i18n,typesand@objectstack/specas before.The channel (
ui/packages/app-shell/src/providers/writeWarningToast.ts).emitWriteWarningparks throughparkAnswerWarnings, still synchronously before its firstawait, and toasts the warnings itself only when no claim took them. The stripped-fields toast is never claimed, as before. Its claim half is deleted, not re-exported, so there is one spelling.The callers (
RecordFormPage.tsx,AppContent.tsx). Imports re-pointed to@object-ui/react; nothing else changes. Their #27999 tests are untouched and green.The container (
ui/packages/plugin-form/src/ObjectFormContainer.tsx).useContainerSaveFeedbackopens a claim while the container is open (schema.open !== false) and makes the write itself (an adapter, nosubmitHandler,createoreditwith arecordId): a create of its object, or an update of its record. On success it takes its write's warnings first, before the label lookup awaits, and raisestoastRecordSave: the record-named message (or a declaredsuccessMessage) on the warning variant with the warnings one per line, the success variant when there are none. Closing or unmounting releases the claim, and anything it still parks (a write it did not make) gets the channel's toast. The effect is keyed on primitives and the adapter, asAppContent's is (ui/AGENTS.md #10).Tests
New:
ui/packages/plugin-form/src/__tests__/containerSaveToastCarriesWarnings-28216.test.tsx(14, drawer and modal each): edit and create show one warning-variant toast naming the record with the warning, and the channel shows nothing; a declaredsuccessMessagecarries them too; CONTROL: warnings about another record keep the channel toast and the save toast stays the plain success one; CONTROL: asubmitHandlercontainer claims nothing; CONTROL: a closed container claims nothing; closing the container shows what its claim still parks. The channel is stood in for byparkAnswerWarnings, called inside the write as the adapter emits its event (plugin-form cannot import app-shell).ui/packages/react/src/record-save-feedback.test.ts(3): park on a matching claim and take distinct lines; CONTROL: no park for another adapter, record or object, or after release;toastRecordSavepicks the variant.Updated:
writeWarningToast.test.tsimportsclaimAnswerWarningsfrom@object-ui/react; its assertions are unchanged.Ablation (through
scripts/ablation-replace.mjs, mutation proved on disk, restored to the HEAD blob): the container's claim effect returning before it claims → the new file 8 failed / 6 passed (the four one-toast rows per form type red, the controls green, as predicted before the run); restored, blob4f849d2d= HEAD.Acceptance notes
DrawerForm/ModalFormsubmit path in jsdom.Verification
Local runs
Head
0bd8234e, onmainf40b777d.mainhas moved 6 commits since; none touches a file in this diff andgit merge-treewithorigin/mainis clean.pnpm turbo run build --filter='@object-ui/app-shell^...' --concurrency=2(the dependency closure, under the verify lock) →Tasks: 64 successful, 64 total, VERDICT command-exit 0.type-checkof@object-ui/react,@object-ui/plugin-form,@object-ui/app-shell(tsc --noEmitandtsc -p tsconfig.test.json) → exit 0 each.pnpm --dir ui exec vitest run packages/react/ packages/plugin-form/plus the 129 app-shell and console test files that namewriteWarningToast,RecordFormPage,AppContent,toastRecordSave,claimAnswerWarningsorObjectFormContainer→Test Files 474 passed (474),Tests 4903 passed | 1 skipped (4904), VERDICT command-exit 0.containerSaveToastNamesRecord-27453,writeWarningToast,RecordFormPage.saveToastCarriesWarnings-27999,AppContent.recordSaveOneToast-27999) → 6 files, 65 tests passed.ts/tsxfiles underui/→ 0 errors.pnpm --dir ui run check:new-line-citations→ 0 new citations, exit 0.Derived gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon0bd8234e→ 34 commands, each run with stdin from /dev/null and its exit code captured before any pipe;--ranreconcile:34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN, exit 0.Generated by Claude Code