Repository navigation
Commit 4bc963c
feat(components): the edit form names the fields a visible update action would require, before it is pressed (#28459)
<!-- claude-projects-attribution: {"github_login":"os-sales"} -->
_Requested by **Sales** · [project
thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4Uu4iEg7dMGWavC1JjYSAwAZj)_
Fixes #28382
Clause-②: yes (widening: `@object-ui/i18n` gains the
`form.requiredForAction` key in all ten locales; no accept set moves)
Metadata-protocol: none
Branch note: `claude/project-thread-3lhyz5` is the branch the platform
assigned to this card thread (claim 6546347620 names it), not the repo's
`claude/issue-N-slug` convention.
## 维护者速读
- **改了什么**:编辑记录时,如果某个可见的「update + 静态
patch」动作执行后会让一个空字段变成必填,表单就在该字段下写一行提示,例如 showcase 草稿发票的「Issued On」下显示
“Required to run “Submit for Sign-off”.”。
- **为什么改**:服务端在动作的 patch 合并后的记录上跑 `requiredWhen`,而表单只看当前记录,所以第一次点「Submit
for Sign-off」总会被拒(`Issued On is required`),事前没有任何提示。
- **风险与回滚**:纯提示,不拦保存、不改字段的必填状态、不改服务端;只在编辑表单生效,读对象 schema
走已有的数据源缓存。回滚即撤销本 PR。
- **先看**:`ui/packages/components/src/renderers/form/form.tsx` 的
`actionOutcomes` 块和 `renderFieldDescription`
调用处;`ui/packages/components/src/renderers/form/actionRequirements.ts`(哪些动作计入)。
- **两分钟验证**:`cd ui && pnpm exec vitest run
packages/components/src/renderers/form/__tests__/form-required-for-action-28382.test.tsx`,预期
12 条全过;或在 showcase 打开一张 draft 发票点 Edit,Issued On 下出现该提示。
- **需要你做的**:无,席位落地。
## What changes
Measured on `main` `f91ab5a0` before any code, with the showcase
(`objectstack dev --seed-admin`) and the console (`vite`, proxied):
| | On `main` | This PR |
|:--|:--|:--|
| `POST /api/v1/actions/showcase_invoice/showcase_submit_signoff` on
draft INV-1004 (`issued_on` empty) | `400 VALIDATION_FAILED`, "Issued On
is required" | unchanged: the server stays the source of truth |
| Edit Invoice dialog on the same draft | "Issued On" shows nothing |
"Issued On" shows "Required to run “Submit for Sign-off”." under the
control |
- **`actionRequirements.ts` (new, in the form renderer's directory).**
`patchingUpdateActions` keeps a declared action only when its whole
write is known before it runs: `operation: 'update'`, a non-empty static
`patch`, no `params` (an empty list is fine), and at least one location
(a locationless action is placed nowhere, per `actionRendersAt`).
`usePatchingUpdateActions` reads the object's actions once per object
through the data source the renderer already has (`getObjectSchema`, the
read every form host makes and the adapter caches), on edit forms only.
- **`form.tsx`.** `actionOutcomes` is the record each kept action would
leave: `{ ...ruleRecord, ...patch }`, the merge the server makes. An
action is dropped when it is switched off, when the caller lacks its
`requiredPermissions` (the `action:bar` gate), or when its `visible`
predicate does not hold over the live record. That predicate fails
closed and quietly, because the action's own button reports its fault.
In `renderFormField`, a field with a `requiredWhen` that is not required
now, is editable and is empty gets one line per action whose record
makes it required. The line sits inside the field's `FormDescription`,
so the control's `aria-describedby` announces it.
- Nothing about Save changes: the field gets no asterisk and no
`aria-required`, and the submit-time required check reads the same live
verdict as before.
- **i18n.** `form.requiredForAction` in all ten locales, plus the
renderer's provider-less fallback row (byte-identical to `en`).
## Tests
- `form-required-for-action-28382.test.tsx` (new, 12 tests) through the
real `form` renderer with a data source in `SchemaRendererContext`: the
note on an edit form and its `aria-describedby` delivery; Save still
writes with the field empty; the note goes when the field is filled; it
gives way to the asterisk when the record as it stands requires the
field; a create form reads nothing; an action whose `visible` does not
hold, or that collects `params`, adds nothing; two actions give two
lines and an unrelated patch none; and `patchingUpdateActions` unit
cases.
- Ablation (fix committed first; `scripts/ablation-replace.mjs`, each
leg restored with `git diff HEAD` empty):
- `actionOutcomes.length > 0 &&` to `< 0`: 5 tests red (every case that
expects a note).
- `visible !== true &&` to `false &&`: 1 red, the `visible` skip case.
- `!required &&` to `true &&`: 1 red, the asterisk case.
- `packages/components/src/renderers/form/` + `packages/i18n/` (one
run): 177 of 178 files passed; the only red was the German quote census
below (2 tests). After moving it, `packages/i18n/` reran: 94/94 files,
1438 passed, 13 skipped.
- Moved pin:
`ui/packages/i18n/src/__tests__/de-quote-pairing-3876.test.ts`. The
German value „{{action}}“ is one more matched pair, so its census goes
from 103 to 104 (both literals), with the history comment extended.
- `turbo run type-check --filter=@object-ui/components
--filter=@object-ui/i18n`: exit 0. eslint on each changed file: 0
errors. `form.tsx` has the same warning count as on `main` (58).
## Acceptance notes
- Heads-up only, as the card asks: Save is never blocked, and the
server's refusal stays the source of truth for the action.
- The renderer reads the actions itself, not through a new `FormSchema`
key, so every form host (modal, drawer, page, tabbed, wizard) gets the
note with no host or `@object-ui/types` change. This is the same pattern
the renderer already uses for the record-currency used codes.
- Not covered, by design: an action that collects `params` (the dialog's
values are unknown before the press), and any non-declarative action
(`api` / `flow` / `script` bodies). The optional "say what is missing on
the button" half of the card is not done here: the button lives in the
record header, which another card holds.
- Live check: the after screenshot shows the note under "Issued On" on
the showcase's Edit Invoice dialog.
## Verification
- Gates: `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 34 commands; on head `c56ed27d` all
34 ran with exit 0, and `--ran` reconciles 34 derived, 34 run, 0
NOT-MEASURED (the full list is in the os-dev-report on the card). `node
scripts/check-sdui-manifest.mjs` exit 0. `pnpm --dir ui
check:i18n-keys`, `check:i18n-dead-keys` and `check:i18n-drift` exit 0.
- No open pull request touches
`ui/packages/components/src/renderers/form/**` or
`ui/packages/i18n/src/locales/**` at this head.
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(not touched)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes. (per-file eslint locally;
the repo-wide run is CI's)
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed. (not touched)
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English. (the 维护者速读 section is
the seat's required Chinese summary)
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01L6bhgXCPFJQiBaT5tcJurf)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01L6bhgXCPFJQiBaT5tcJurf
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 8e2d56d commit 4bc963c
15 files changed
Lines changed: 513 additions & 5 deletions
File tree
- .changeset
- ui/packages
- components/src/renderers/form
- __tests__
- i18n/src
- __tests__
- locales
| 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 | |
|---|---|---|---|
| |||
| 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 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 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 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 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 | + | |
0 commit comments