Repository navigation
Commit b1e1dcf
feat(console): a save's toast shows the notice a hook set on the write, one toast per write (#28307)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} -->
_Requested by **Tesla** · [project
thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6Jao7psmQXENcMMMPyS21PpPWj)_
Fixes #28252
Refs #28000
Clause-②: yes (widening: `@object-ui/data-objectstack`'s
`WriteWarningEvent` gains an optional `notice`; `@object-ui/react` gains
the type `RecordSaveFeedback`, `AnswerWarningsEvent` gains an optional
`notice`, and `AnswerWarningClaim.take` / `toastRecordSave` move to it.
Those two were added by #28267 and have not been released, so no
released signature changes)
Metadata-protocol: none
## 维护者速读
-
改了什么:保存记录时,如果服务端钩子给这次保存留了一句话(`notice`,比如「已提交给经理审批」),控制台现在会显示它。记录弹窗(含子表单任务)、整页表单、`object-form`
抽屉/弹窗、表格行内编辑(单行保存和「全部保存」)都在原来的保存提示里把这句话放在标题下面;仍然只弹一个提示。
- 为什么改:#28000 的契约半边(#28247)已经让创建/更新的应答带上
`notice`,但控制台没有任何地方读它,HotCRM「保存即送审」的提示被丢掉了(#28252)。适配器原本只把应答里的记录交出去,`notice`
在那一层就被丢了。
- 风险与回滚:适配器 `create`/`update`
返回值不变,只是写入提醒事件多带一个可选字段;只有提醒的保存仍是成功样式,同时有警告时才是警告样式。没有页面认领的写入(例如记录详情页行内编辑的保存条)由写入提醒通道弹一个
info 提示。回滚即 revert 本 PR。
- 先看:`ui/packages/react/src/record-save-feedback.ts`(`take` 交回
`RecordSaveFeedback`,`toastRecordSave`
的样式选择);`ui/packages/plugin-grid/src/ObjectGrid.tsx`(行内保存新增认领,「全部保存」按行认领再合并成一个提示);`ui/packages/app-shell/src/providers/writeWarningToast.ts`(未被认领时通知的落点)。
- 两分钟验证:`pnpm --dir ui exec vitest run
packages/react/src/record-save-feedback.test.ts
packages/plugin-grid/src/__tests__/inlineEditSaveNotice-28252.test.tsx`,预期
2 个文件全绿。
- 需要你做的:无,席位落地。
## What changes
**Which surfaces show the notice** (the card's should-fix (b)), one
toast per write in every row:
| Surface | Toast |
|:---|:---|
| Record dialog (`AppContent` `crud_success`) and its child-form task |
its record-named message, the notice under it |
| Full-page form (`RecordFormPage`) | its record-named message, the
notice under it |
| `object-form` drawer and modal (`ObjectFormContainer`) | its
record-named message or declared `successMessage`, the notice under it |
| Grid inline edit, per-row save and "Save All" (`ObjectGrid`) | "1 row
saved" / "N rows saved", the notices under it (distinct, one per line) |
| Any write no surface claims (the record page's inline-edit Save bar,
which raises no save toast of its own; kanban, actions, …) | the
write-warning channel's own toast: an info toast with the notice as
written, or the notice leading the "Saved — with warnings" lines |
A notice alone keeps the save toast on the success variant (it reports
an outcome, never a problem); with warnings too, the toast is the
warning variant and the notice leads its lines. The stripped-fields
toast stays its own toast, as before.
**The adapter** (`ui/packages/data-objectstack/src/index.ts`). Measured:
`create` / `update` resolved `result.record` and read only
`droppedFields` and `warnings` off the answer, so `notice` was dropped
there. `notifyDroppedFields` now also reads `notice` (a non-empty
string, else nothing) onto the write-warning event, and emits the event
when the notice is the answer's only news. What the two methods resolve
is unchanged. The batch path is not touched: the spec declares `notice`
on the single-record answers only.
**The claim** (`ui/packages/react/src/record-save-feedback.ts`).
`AnswerWarningsEvent` gains `notice`; `take(saved)` returns
`RecordSaveFeedback` (`warnings`, optional `notice`, the last one parked
winning as on the server); `toastRecordSave(sink, message, feedback)`
titles the toast with the surface's message and puts the notice, then
the warnings, under it.
**The channel**
(`ui/packages/app-shell/src/providers/writeWarningToast.ts`).
`emitWriteWarning` parks when the event carries warnings or a notice,
still synchronously before its first `await`. Unclaimed, the notice is
one toast as the table says. `WriteWarningSink` gains `info`; sonner's
`toast`, which `AdapterProvider` passes, has it. The type is not
exported from the package.
**The surfaces.** `AppContent`, `RecordFormPage` and
`ObjectFormContainer` hand `take`'s result to `toastRecordSave` (the
`crud_success` params carry `notice` beside `warnings`). `ObjectGrid`'s
default row save opens a claim on the row's update for the length of the
save, and "Save All" one per row, read once into the single toast,
released in a `finally`.
## Tests
New:
-
`ui/packages/plugin-grid/src/__tests__/inlineEditSaveNotice-28252.test.tsx`
(3): per-row save shows one success toast with the notice, the channel
nothing; "Save All" over two rows shows one warning toast, the shared
notice once then the warning line; CONTROL: a write the grid did not
make keeps the channel toast after the save.
- Added cells to `onWriteWarning.test.ts` (3: create and update carry
the notice and still resolve the record; empty or non-string notice
emits nothing), `record-save-feedback.test.ts` (3),
`writeWarningToast.test.ts` (5: claimed update and create take it;
unclaimed notice alone is one info toast; beside warnings it leads the
one warnings toast; a released claim shows it),
`RecordFormPage.saveToastCarriesWarnings-27999.test.tsx` (3),
`AppContent.recordSaveOneToast-27999.test.tsx` (4) and
`containerSaveToastCarriesWarnings-28216.test.tsx` (4, drawer and
modal).
- Updated: the existing `take` and `toastRecordSave` assertions read the
new object shape; their meaning is unchanged.
**Ablation** (through `scripts/ablation-replace.mjs`, each mutation
proved on disk and restored to the HEAD blob):
- channel parks on warnings only → 5 notice cells red across
`writeWarningToast`, `RecordFormPage` and `AppContent` (the
notice-with-warning cells stay green, as predicted: the warnings still
open the park);
- adapter drops `notice` from the event → 7 red across `onWriteWarning`
and `writeWarningToast`;
- grid per-row save toasts without `take` → 2 red in the new grid file
(the per-row cell, and its CONTROL, whose count includes the notice the
unread claim hands back).
## Acceptance notes
- The record page's inline-edit Save bar (`InlineEditSaveBar`) raises no
save toast at all today, so it is not a claiming surface; its notice
reaches the user through the channel's info toast, which is one toast
per write. Giving that bar a save toast of its own is outside this card.
- "Save All" joins distinct notices from several rows one per line under
one toast; rows with the same notice show it once.
- Not measured live in a browser against a hook that sets `onNotice`;
the server half that lets sandboxed and flow hooks set it is #28251. The
chain is pinned through the real adapter (`writeWarningToast.test.ts`),
the real `RecordFormPage` / `AppContent` / `ObjectFormContainer` /
`ObjectGrid` save paths in jsdom.
## Verification
<details><summary>Local runs</summary>
Head `4056fd97`, on `main` `6285203d`. `main` has moved 7 commits since;
none touches a file in this diff and `git merge-tree` with `origin/main`
(`53749673`) is clean.
- `pnpm turbo run build --filter='@object-ui/app-shell^...'
--filter='@object-ui/plugin-grid^...' --concurrency=2` (the dependency
closure, under the verify lock) → `Tasks: 64 successful, 64 total`,
VERDICT command-exit 0; then the four touched lower packages rebuilt →
VERDICT command-exit 0.
- `type-check` of `@object-ui/data-objectstack`, `@object-ui/react`,
`@object-ui/plugin-form`, `@object-ui/plugin-grid`,
`@object-ui/app-shell` (each package's `tsc --noEmit`, plus `tsc -p
tsconfig.test.json` where it declares one) → exit 0 each.
- The eight toast files (new grid file, `inlineEditSaveFeedback-12303`,
`onWriteWarning`, `record-save-feedback`, `writeWarningToast`,
`RecordFormPage.saveToastCarriesWarnings-27999`,
`AppContent.recordSaveOneToast-27999`,
`containerSaveToastCarriesWarnings-28216`) → `Test Files 8 passed (8)`,
`Tests 100 passed (100)`.
- Under the verify lock: `pnpm --dir ui exec vitest run packages/react/
packages/data-objectstack/ packages/plugin-form/ packages/plugin-grid/`
plus the 130 app-shell and console test files that name
`writeWarningToast`, `RecordFormPage`, `AppContent`, `toastRecordSave`,
`claimAnswerWarnings`, `ObjectFormContainer` or `onWriteWarning` → `Test
Files 845 passed (845)`, `Tests 8464 passed | 1 skipped (8465)`, VERDICT
command-exit 0. The whole `packages/app-shell/` suite was not run
locally (CI's Test UI runs it).
- eslint on the 14 changed `ts` / `tsx` files → 0 errors. `pnpm --dir ui
run check:new-line-citations` → 0 new citations, exit 0.
</details>
<details><summary>Derived gates</summary>
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` on `4056fd97` → 35 commands, each run with
stdin from /dev/null and its exit code captured before any pipe; `--ran`
reconcile: `35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN`, exit 0.
`check:type-check-debt`'s first run was a lock queue-timeout (exit 3,
NOT MEASURED, behind this branch's own test run); the line below is its
re-run under the lock.
```
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-sdui-manifest.mjs :: exit 0
node scripts/check-sdui-manifest.mjs --self-test :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:error-status-conformance :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:pm-changeset-deadline-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
pnpm --dir ui exec vitest run scripts/ :: exit 0
```
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01L9qibwGdCkvhAHHVnAyVG1)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent be25e2d commit b1e1dcf
15 files changed
Lines changed: 701 additions & 62 deletions
File tree
- .changeset
- ui/packages
- app-shell/src
- console
- __tests__
- providers
- views
- data-objectstack/src
- plugin-form/src
- __tests__
- plugin-grid/src
- __tests__
- react/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
| 17 | + | |
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| |||
763 | 763 | | |
764 | 764 | | |
765 | 765 | | |
766 | | - | |
767 | | - | |
768 | | - | |
| 766 | + | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
| 770 | + | |
| 771 | + | |
| 772 | + | |
769 | 773 | | |
770 | 774 | | |
771 | 775 | | |
| |||
909 | 913 | | |
910 | 914 | | |
911 | 915 | | |
912 | | - | |
| 916 | + | |
913 | 917 | | |
914 | 918 | | |
915 | | - | |
| 919 | + | |
916 | 920 | | |
917 | 921 | | |
918 | 922 | | |
| |||
933 | 937 | | |
934 | 938 | | |
935 | 939 | | |
936 | | - | |
| 940 | + | |
937 | 941 | | |
938 | | - | |
| 942 | + | |
939 | 943 | | |
940 | 944 | | |
941 | 945 | | |
942 | | - | |
| 946 | + | |
943 | 947 | | |
944 | 948 | | |
945 | 949 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
180 | 180 | | |
181 | 181 | | |
182 | 182 | | |
| 183 | + | |
183 | 184 | | |
184 | 185 | | |
185 | 186 | | |
186 | 187 | | |
187 | 188 | | |
| 189 | + | |
188 | 190 | | |
189 | 191 | | |
190 | 192 | | |
191 | 193 | | |
| 194 | + | |
192 | 195 | | |
193 | 196 | | |
194 | 197 | | |
| |||
280 | 283 | | |
281 | 284 | | |
282 | 285 | | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
79 | 79 | | |
80 | 80 | | |
81 | 81 | | |
82 | | - | |
| 82 | + | |
83 | 83 | | |
84 | 84 | | |
| 85 | + | |
85 | 86 | | |
86 | 87 | | |
87 | 88 | | |
| |||
510 | 511 | | |
511 | 512 | | |
512 | 513 | | |
513 | | - | |
| 514 | + | |
514 | 515 | | |
515 | 516 | | |
516 | 517 | | |
| |||
530 | 531 | | |
531 | 532 | | |
532 | 533 | | |
533 | | - | |
| 534 | + | |
534 | 535 | | |
535 | 536 | | |
536 | 537 | | |
| |||
547 | 548 | | |
548 | 549 | | |
549 | 550 | | |
550 | | - | |
| 551 | + | |
551 | 552 | | |
552 | 553 | | |
553 | 554 | | |
| |||
589 | 590 | | |
590 | 591 | | |
591 | 592 | | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
| 626 | + | |
| 627 | + | |
| 628 | + | |
| 629 | + | |
| 630 | + | |
| 631 | + | |
| 632 | + | |
| 633 | + | |
| 634 | + | |
| 635 | + | |
| 636 | + | |
| 637 | + | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
| 641 | + | |
| 642 | + | |
| 643 | + | |
| 644 | + | |
| 645 | + | |
| 646 | + | |
| 647 | + | |
| 648 | + | |
| 649 | + | |
| 650 | + | |
| 651 | + | |
| 652 | + | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
| 659 | + | |
| 660 | + | |
| 661 | + | |
| 662 | + | |
| 663 | + | |
| 664 | + | |
| 665 | + | |
| 666 | + | |
| 667 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
| 59 | + | |
59 | 60 | | |
60 | 61 | | |
61 | 62 | | |
| |||
251 | 252 | | |
252 | 253 | | |
253 | 254 | | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
254 | 263 | | |
255 | 264 | | |
256 | 265 | | |
| |||
261 | 270 | | |
262 | 271 | | |
263 | 272 | | |
264 | | - | |
265 | | - | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
266 | 276 | | |
267 | | - | |
| 277 | + | |
268 | 278 | | |
269 | 279 | | |
270 | 280 | | |
271 | | - | |
272 | | - | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
273 | 284 | | |
274 | | - | |
275 | | - | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
276 | 295 | | |
277 | 296 | | |
278 | 297 | | |
279 | 298 | | |
280 | | - | |
| 299 | + | |
281 | 300 | | |
282 | 301 | | |
283 | 302 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
42 | | - | |
| 42 | + | |
43 | 43 | | |
44 | 44 | | |
| 45 | + | |
45 | 46 | | |
46 | 47 | | |
47 | 48 | | |
| |||
50 | 51 | | |
51 | 52 | | |
52 | 53 | | |
53 | | - | |
| 54 | + | |
54 | 55 | | |
55 | 56 | | |
56 | 57 | | |
| |||
125 | 126 | | |
126 | 127 | | |
127 | 128 | | |
| 129 | + | |
128 | 130 | | |
129 | 131 | | |
130 | 132 | | |
| |||
217 | 219 | | |
218 | 220 | | |
219 | 221 | | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
0 commit comments