Repository navigation
Commit 0ac6dab
feat(console): name the records a bulk run wrote, hint which required fields to fill, and show CRM expected revenue as money (#28378)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} -->
_Requested by **Tesla** · [project
thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoDdYQJbM1obWY7sMWJcH9sv)_
Part of #27520 (rows 8, 11 and 16; every other row stays on the card)
Clause-②: yes (widening: `ActionResult` gains the optional `fieldErrors`
member, `ActionFieldError` is a new export of `@object-ui/types`, and
`ActionResponseOutcome` gains `fieldErrors`; no accept set moves)
Metadata-protocol: none
## 维护者速读
- **改了什么**:批量动作(如 Tasks 的「Mark Done」)结果弹窗不再只写「Succeeded 2 /
2」,而是列出成功的记录名;动作因必填字段被拒(如草稿发票「Submit for Sign-off」缺 Issued
On)时,提示变成「Issued On is required. Fill in Issued On, then run “Submit for
Sign-off” again.」;CRM 示例的 Expected Revenue 公式声明为货币,与旁边的 Deal Value 同样显示为
`$16,000.00`。
- **为什么改**:18.3.0 RC 走查第 8、11、16
行:用户看不出批量改了哪些记录;被拒后不知道要先去编辑哪个字段;同一表格里两列金额一列带货币一列是普通数字。
- **风险与回滚**:批量名单只在每个失败都对应到具体记录时才显示,服务端只回计数时保持原样;提示只追加在服务端原文之后,只针对
`required` 字段项;`ActionResult` 多一个可选成员(放宽,不收紧)。回滚即 revert 本 PR。
-
**先看**:`ui/packages/app-shell/src/utils/consoleServerAction.ts`(`withRequiredFieldsHint`);`ui/packages/core/src/actions/actionResponse.ts`(`refusalFieldErrors`);`ui/packages/plugin-grid/src/components/BulkActionDialog.tsx`(`succeededRows`)。
- **两分钟验证**:showcase 打开一张无 Issued On 的草稿发票点「Submit for
Sign-off」,预期红色提示以「Fill in Issued On, then run “Submit for Sign-off”
again.」结尾;Tasks → Bulk Actions 视图勾两行点 Mark Done,预期结果弹窗列出两条任务标题。
- **需要你做的**:无,席位落地。
## What changes
**Row 8, bulk result names the records.** `BulkActionDialog`'s result
step now lists the records the run wrote under the "Succeeded N / M"
line, named the way the confirm step names the affected records
(`affectedRecordName`, the object's `nameField`), five at most plus "…
and N more". The list is drawn only when every failure in the result
sits under a record of the run, so the rows left over are exactly the
successes. When a bulk call answered a bare count (`executeBulkBatch`'s
`batch_<offset>` entry), which records failed is unknown and the result
keeps to its count. No server read and no executor change. New key
`grid.bulk.succeededRecords` (the list's accessible name) in all ten
packs.
**Row 11, a required-field refusal says what to fill in.** The action
route answers a refused write with `error.details.fields` (one entry per
failed rule, measured live: `{ field: 'issued_on', code: 'required',
message: 'Issued On is required', label: 'Issued On' }`).
`interpretActionResponse` kept only the text. Now:
- `interpretActionResponse` reads the entries (current envelope
`error.details.fields`, legacy inner envelope `fields`), drops entries
without a string `field`, and returns them as `fieldErrors`;
- `createServerActionHandler` passes them on the failed `ActionResult`
(`fieldErrors?: ActionFieldError[]`, new optional member in
`@object-ui/types`);
- the console wrapper `createConsoleServerActionHandler` follows the
server's sentence with `console.serverAction.fillRequiredThenRetry`
("Fill in {{fields}}, then run “{{action}}” again.") or, for an action
with no label, `fillRequiredFirst`. Only `required` entries on a field
count (`_record` and every other code keep the server's text alone).
Field names are the server's labels, deduplicated and joined with
`validation.formInvalidJoiner`, the key the form's field lists already
join with (`Intl.ListFormat` is outside app-shell's lib target, and a
`unit` list joins zh with no separator). The server's text stays first
and whole; a stop is added only when it has none (a CJK one after CJK
text). The wrapper's `t` now takes interpolation values; both call sites
(`useConsoleActionRuntime`, `RecordDetailView`) pass them through to
i18next as `{ defaultValue, ...values }`.
- An authored `errorMessage` still replaces the whole text in the
runner, hint included, as before.
**Row 16, money formula declares money.**
`examples/app-crm/src/objects/opportunity.object.ts`: `expected_revenue`
declares `returnType: 'currency'`. The renderer reads `returnType` and
never infers money from an expression (`FormulaCellRenderer`,
objectui#11683 / #11748), so the undeclared formula drew the number face
(`16,000`) beside the `Field.currency` Deal Value (`$80,000.00`). The
seat lifted the order's no-examples line for exactly this edit (claim
amendment 6539593257). No renderer change.
## Tests
- New
`ui/packages/plugin-grid/src/__tests__/bulkResultSucceededNames-27520.test.tsx`
(3): a clean run lists both titles under the accessible name; a run with
one refusal lists only the two that landed and keeps the refused one
under its refusal; a `bulkUpdate` answering a bare count names nobody.
- New
`ui/packages/core/src/actions/__tests__/actionRefusalFields-27520.test.ts`
(5): the measured answer yields `fieldErrors`; an answer that also
carries a producer-marked `error.userMessage` keeps them (the hint keys
off the entries, never the text, so it survives a `userMessage`);
field-less entries are dropped; the legacy inner list is read; the
dispatch's failed result carries the entries.
- New
`ui/packages/app-shell/src/utils/__tests__/consoleServerAction.requiredFieldsHint-27520.test.ts`
(6): the hint after the server's sentence; several fields deduplicated
and joined with the pack joiner (zh `、`); a label-less field and a
label-less action; `min` and `_record` entries left alone; the host `t`
receives the values; the server's own stop kept and a CJK stop added.
- Adapted
`ui/packages/i18n/src/__tests__/de-quote-pairing-3876.test.ts`: the de
census counts the new „{{action}}“ span, with the same one-line history
note its earlier rises carry. main's #28310 raised the same pins by one
while this was open, so after merging main (6a3bd4e) they read 103
paired spans and 103 / 103 / 0, both rises noted. `pnpm --dir ui exec
vitest run --maxWorkers=2 packages/i18n/` on the merge head: 93 files
passed (1433 tests, 13 skipped).
- Ablations (one-time, not kept, at 714e376, before the `userMessage`
case was added), through `scripts/ablation-replace.mjs`, each restored
to the HEAD blob with `git diff HEAD` empty: succeeded list never drawn
→ 2 failed of 3; count guard removed → 1 failed of 3; core stops
returning `fieldErrors` → 8 failed of 10 (core + wrapper files); wrapper
returns the refusal unhinted → 5 failed of 6. The suites resolve
`@object-ui/*` to source, so no dist leg applies.
- `pnpm --dir ui exec vitest run --maxWorkers=2 packages/types/
packages/core/ packages/i18n/ packages/plugin-grid/`: at 714e376: 963
files passed, 1 failed (`de-quote-pairing-3876`, 2 tests: its census
counted 101 paired „…“ spans and the new `fillRequiredThenRetry` adds
one). The pins were raised to 102 in 9f34e7d and the file reran 9/9
passed. The new `userMessage` case in e578f93 ran alone, 5/5 passed.
- app-shell, the wrapper and both call sites: `pnpm --dir ui exec vitest
run --maxWorkers=2
packages/app-shell/src/utils/__tests__/consoleServerAction
packages/app-shell/src/hooks/__tests__/useConsoleActionRuntime
packages/app-shell/src/views/RecordDetailView`: 108 files / 761 tests
passed (lock VERDICT command-exit 0). The whole `packages/app-shell/`
run sat 80 minutes under the lock without finishing on this shared box
and was stopped; the rest of app-shell is left to CI.
- `type-check` of `@object-ui/types`, `core`, `i18n`, `plugin-grid`,
`app-shell` (full dts closure built first): all Done, 0 `error TS`.
`@objectstack/example-crm`: `typecheck` exit 0, `test` 7 files / 48
tests passed, `validate` exit 0 (only the pre-existing
`field-no-consumers` warnings on `renewal_of` / `line_total`).
- Dogfood that reads the CRM example's metadata (asked for by the seat
for the example edit), against the built
`examples/app-crm/dist/objectstack.json` that carries `returnType:
'currency'`: `pnpm --filter @objectstack/dogfood exec vitest run
test/install-local-purge-sample-data.dogfood.test.ts
test/wall-environment-metadata-refusal.dogfood.test.ts
test/guest-grants-channel.dogfood.test.ts` → 3 files / 53 tests passed
(lock VERDICT command-exit 0). The other dogfood files read only the
CRM's flows or views, which this PR leaves alone.
## Acceptance notes
- **Row 8's Status half is the example's own declaration**, not touched:
`examples/app-showcase/src/ui/actions/index.ts` `MarkDoneAction` patches
`done` and `progress` on purpose, because `status` is governed by
`task_status_flow` (only `in_review -> done` is legal), and its doc
comment says so.
- **Row 11's "mark fields required for submission" half is not done.**
The rule exists (`issued_on` `requiredWhen: record.status in ['sent',
'paid']`), and the edit form evaluates it on the record as it stands, so
a draft correctly shows Issued On as optional. Saying "required for
Submit for Sign-off" means tying a field's rule to an action's `patch`,
which no spec door declares. The seat files that as a spec card.
- The bulk path runs a promoted object action per record through the
same wrapper, so a required-field refusal there now reads with the hint
under the refused record too.
- The action toast's text comes from `actionErrorDetail`, which reads
`error.message` and never `error.userMessage` (a flow-action refusal can
carry one). That is unchanged here; the hint rides after whichever text
is shown, because it is keyed off `details.fields`. Noted, not filed.
- Row 16 also applies to any author: a money formula with no
`returnType` draws a plain number by rule. `os validate` does not warn
about it. Noted, not filed.
## Verification
<details><summary>Live before/after (showcase and CRM on --fresh stacks,
admin, 1440x900)</summary>
Base `origin/main` b1e1dcf (before) and this branch (after), full dts
build plus `pnpm console:build`, `LANG=en_US.UTF-8 pnpm dev -- --fresh
-p 38611` (showcase) and `pnpm --filter @objectstack/example-crm dev --
--fresh -p 38612`, driven with Playwright.
- Row 8, Tasks → Bulk Actions view, Content backlog + Evidence
collection → Mark Done → Run. Before: dialog text `Mark Done | Succeeded
2 / 2 | Done`. After: `Mark Done | Succeeded 2 / 2 | • Evidence
collection | • Content backlog | Done`.
- Row 11, INV-1002 (draft, no Issued On) → Submit for Sign-off. Wire
both times: `400
{"success":false,"error":{"code":"VALIDATION_FAILED","message":"Issued
On is
required","httpStatus":400,"details":{"fields":[{"field":"issued_on","code":"required","message":"Issued
On is required","label":"Issued On"}]}}}`. Toast before: `Issued On is
required`. After: `Issued On is required. Fill in Issued On, then run
“Submit for Sign-off” again.`
- Row 16, CRM → Opportunities, row Initech — Expansion. Before: Deal
Value `$80,000.00`, Expected Revenue `16,000`. After: `$80,000.00`,
`$16,000.00`.
</details>
<details><summary>Derived gates</summary>
At 6a3bd4e (this branch merged with main 0f29a88): `node
scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 54 families against the diff. All 54
ran with `</dev/null`, exit codes recorded
(`27520-actions-gates-ran-6a3bd4e7.list` in the seat's out folder).
`dispatch-gates --ran`: "54 derived famil(ies) accounted for — 54 run, 0
NOT-MEASURED".
- `pnpm check:type-check-debt` first exited 3 (not measured): the
closure build could not resolve `@objectstack/observability`, a package
main added, because the tree had not been reinstalled after the merge.
After `pnpm install --frozen-lockfile` it reran: exit 0, "0 raw tsc
error(s) total, none above its recorded number". The list records the
rerun.
- `pnpm --dir ui exec vitest run scripts/` ran under the verify lock:
exit 0.
- Also run: `pnpm --dir ui run check:new-line-citations` exit 0; `node
scripts/check-sdui-manifest.mjs` exit 0.
- The same 54 families were derived and all exited 0 at 714e376, before
the last test commit and the merge.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01L9qibzF2YJvme2ABRytQ9o)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent fc5d509 commit 0ac6dab
24 files changed
Lines changed: 479 additions & 14 deletions
File tree
- .changeset
- examples/app-crm/src/objects
- ui/packages
- app-shell/src
- hooks
- utils
- __tests__
- views
- core/src/actions
- __tests__
- i18n/src
- __tests__
- locales
- plugin-grid/src
- __tests__
- components
- types/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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
50 | 53 | | |
51 | 54 | | |
| 55 | + | |
52 | 56 | | |
53 | 57 | | |
54 | 58 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
819 | 819 | | |
820 | 820 | | |
821 | 821 | | |
822 | | - | |
| 822 | + | |
| 823 | + | |
823 | 824 | | |
824 | 825 | | |
825 | 826 | | |
| |||
| 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 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
| 52 | + | |
52 | 53 | | |
53 | 54 | | |
54 | 55 | | |
55 | 56 | | |
56 | 57 | | |
57 | 58 | | |
58 | 59 | | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | | - | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
63 | 65 | | |
64 | | - | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
65 | 71 | | |
66 | | - | |
67 | | - | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
68 | 75 | | |
69 | 76 | | |
70 | 77 | | |
| |||
102 | 109 | | |
103 | 110 | | |
104 | 111 | | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
105 | 143 | | |
106 | 144 | | |
107 | 145 | | |
| |||
221 | 259 | | |
222 | 260 | | |
223 | 261 | | |
224 | | - | |
| 262 | + | |
225 | 263 | | |
226 | 264 | | |
227 | 265 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1829 | 1829 | | |
1830 | 1830 | | |
1831 | 1831 | | |
1832 | | - | |
| 1832 | + | |
| 1833 | + | |
1833 | 1834 | | |
1834 | 1835 | | |
1835 | 1836 | | |
| |||
| 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 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
| 52 | + | |
52 | 53 | | |
53 | 54 | | |
54 | 55 | | |
55 | 56 | | |
56 | 57 | | |
57 | 58 | | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
58 | 64 | | |
59 | 65 | | |
60 | 66 | | |
| |||
119 | 125 | | |
120 | 126 | | |
121 | 127 | | |
| 128 | + | |
122 | 129 | | |
123 | 130 | | |
124 | 131 | | |
125 | 132 | | |
126 | 133 | | |
| 134 | + | |
127 | 135 | | |
128 | 136 | | |
129 | 137 | | |
130 | 138 | | |
131 | 139 | | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
301 | 301 | | |
302 | 302 | | |
303 | 303 | | |
304 | | - | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
305 | 309 | | |
306 | 310 | | |
307 | 311 | | |
| |||
0 commit comments