Repository navigation
Commit 462b4f9
feat(console): an action's confirm and param dialogs name their dismiss button (
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} -->
_Requested by **Steedos** · [project
thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbCvT3geLANnf5WgDVV6kTX7)_
Fixes #28879
Refs #28519
Clause-②: yes (widening)
Metadata-protocol: none
File surface: the card's Wanted 1-4 list (claim 6568809663), plus two
mechanical consequences the seat added in amendment 6568954960
(`ui/packages/types/src/ui-action.ts`, and the one `RecordDetailView`
`useObjectLabel()` destructure line outside `paramCollectionHandler`).
## 维护者速读
- 改了什么:动作弹出的确认框和参数框,其「取消」按钮现在显示动作自己声明的 `cancelText`;没声明时仍是「Cancel」。翻译包里的
`_actions.ACTION.cancelText` 优先,其次是作者写的多语言文本,最后是作者写的字符串。设置 → 邀请里的「Cancel
Invitation」确认框,取消按钮现在写「Keep invitation」(中/日/西已翻译)。Studio 的动作表单可以填这个键,`os
i18n extract` 会为带 `confirmText` 或参数的动作生成它的翻译位。
- 为什么改:`cancelText` 已在 #28859 写进
spec,但控制台一直画「Cancel」;动作本身就叫「取消……」时,两个按钮分不清哪个是保留。
- 风险与回滚:只改按钮文字,确认与取消的逻辑不变;没写 `cancelText` 的动作完全不变。回滚即 revert 本 PR。
- 先看:`ui/packages/core/src/actions/ActionRunner.ts` 的 `confirmOptions`(把
`cancelText`
交给确认框);`ui/packages/react/src/hooks/useActionTextLocalizer.ts` 新增的
`cancelText` 解析;`packages/spec/liveness/action.json` 的 `cancelText` 行。
- 两分钟验证:`pnpm --dir ui exec vitest run
packages/core/src/actions/__tests__/ActionRunner.confirmCancelText-28879.test.ts
packages/app-shell/src/views/ActionConfirmDialog.cancelText-28879.test.tsx
packages/app-shell/src/views/ActionParamDialog.cancelText-28879.test.tsx`,预期
3 个文件全绿。
- 需要你做的:无,席位落地。
## What changes
**The confirm path.** `ActionDef` declares `cancelText?:
SpecActionInput['cancelText']` (derived) and `ACTION_DEF_KEYS` lists it.
`useActionTextLocalizer` resolves it only when the action declares one:
the authored `I18nLabel` is collapsed to the active language, then
offered as the fallback to `useObjectLabel().actionCancelText`, so the
bundle's `objects.OBJECT._actions.ACTION.cancelText`
(`globalActions.ACTION.cancelText` for an object-less action) wins.
`ActionRunner#confirmOptions` hands the resolved non-empty string to the
confirm handler as `options.cancelText`, which `ActionConfirmDialog`
already draws over `actionConfirm.cancel`. The runner has no language,
so it passes on a string only; a surface that skips the localizer keeps
the host's "Cancel".
**The param path.** `ParamDialogState` gains `cancelText?: string`. Both
param-collection handlers
(`useConsoleActionRuntime#paramCollectionHandler`,
`RecordDetailView#paramCollectionHandler`) resolve it exactly as they
resolve `submitText`, and `ActionParamDialog` draws it on the dismiss
button, `actionDialog.cancel` when absent or empty.
**The resolver.** `@object-ui/i18n` `useObjectLabel().actionCancelText`,
the sibling of `actionSubmitText` on the same `actionSuffixes` node rule
(a bound action never reads `globalActions`). The member-count pin in
`useObjectLabel-identity-5564.test.tsx` goes from 29 to 30.
**Types and forwarding.** `UIActionSchema.cancelText?:
SpecAction['cancelText']` (amendment 6568954960). `action:button`,
`action:icon`, `action:group` and `action:menu` forward `cancelText` to
the runner; `action-forward-precedence.test.tsx` lists it after
`submitText`.
**`os i18n extract`** (`pushActionCancelText`). Scaffolds `cancelText`
under an action's translation root for an action that declares
`confirmText` or a non-empty `params`, the population `translateAction`
overlays it onto, and no slot for any other action.
**First consumer.** `sys_invitation`'s `cancel_invitation` declares
`cancelText: 'Keep invitation'`. The platform-objects bundles regenerate
(`node scripts/check-i18n-bundles.mjs --write`); zh-CN 保留邀请, ja-JP
招待を保持, es-ES Conservar invitación are hand-translated, as is the new
Studio action-form row.
**Spec rows.** `liveness/action.json` `cancelText` flips `planned` →
`live` with the read points above and drops `authorWarn` / `authorHint`;
`state-counts/action.md` regenerates. The `action` / `cancelText` omit
row leaves `metadata-form-zod-reconciliation.test.ts`, and
`action.form.ts` gains a plain `cancelText` row ("Cancel button label"),
not gated, because the parse refuses the key on an action that opens
neither dialog. "(declared ahead of its console reader: no renderer
shows it yet)" leaves the `ActionSchema.cancelText` describe and "no
renderer shows it yet" leaves the translation slot's;
`content/docs/references/**` regenerates. In `actions.mdx` both "The
console does not draw it yet" sentences flip, the `cancelText` one and
the stale `submitText` one.
## Why
`ActionSchema.cancelText` landed spec-first in #28859 with no reader, so
an author who wrote it still saw "Cancel". On Setup → Invitations,
**Cancel Invitation** asked with two buttons reading **Cancel** and
**Cancel Invitation**.
## Tests
- `ActionRunner.confirmCancelText-28879.test.ts` (5): the bag carries
`cancelText` beside the label; a label-less action still hands it on;
control, no key without it; `''` hands nothing; dismissing stops the
run.
- `ActionConfirmDialog.cancelText-28879.test.tsx` (3) and
`ActionParamDialog.cancelText-28879.test.tsx` (4): the label is drawn,
the fallback is Cancel (also for `''` on the param dialog), and the
labelled button dismisses.
- `useActionTextLocalizer.cancelText-28879.test.tsx` (6): authored
string, bundle first, inline map collapsed, the global node for an
object-less action, nothing for an undeclared key even with a bundle
entry, a nameless action's map collapsed.
- `useConsoleActionRuntime.cancelText-28879.test.tsx` (5) and
`RecordDetailView.paramCancelText-28879.test.tsx` (5), one per param
reader.
- `useObjectLabel-actionCancelText-28879.test.tsx` (5): the object node,
the global node, no global read for a bound action (with a control), the
miss answers.
- `RecordDetailView.paramRuntimeParity-6431.test.tsx`: `cancelText`
joins `DIALOG_READS`, and the two runtimes write the same value.
- `packages/cli/test/i18n-extract-action-cancel-text.test.ts` (7):
top-level, global and inline actions; the seed-less slot; no slot
without `confirmText` or `params`; bundle seed and coverage gating; one
fixture through `ActionSchema` → extract →
`ObjectTranslationDataSchema`.
- Sibling pins that mock `useObjectLabel` partially gain an
`actionCancelText` passthrough (seven `useConsoleActionRuntime.*`
files), and `object-lifecycle-panel-echo-decisions.test.ts` counts the
new action-form row (698 → 699).
## Acceptance notes
- Two lines outside the claim's first file surface, both added by
amendment 6568954960: `ui/packages/types/src/ui-action.ts` (the
renderers' `schema.cancelText` does not type-check without it), and in
`RecordDetailView.tsx` the `useObjectLabel()` destructure gains
`actionCancelText` (the handler cannot call the hook). Nothing else in
`RecordDetailView.tsx` moves.
- `cancelText` is not interpolated: it is a button label, like
`submitText`.
- Wanted 5 (inline actions) and the two skills files are out of scope,
as the card says.
- `metadata-admin/MetadataTypeActions.tsx`, a third `ActionParamDialog`
consumer (Setup metadata-type actions), passes neither `submitText` nor
`cancelText`, so its buttons keep Confirm / Cancel. The card names the
two business-action handlers only. Noted, not filed.
## Verification
Readings at head `f566aa2e` (after merging `origin/main` at `00d0668d`)
unless a line says otherwise.
<details><summary>Derived gates: 112 derived, 111 run exit 0, 1 NOT
MEASURED</summary>
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 112 families at `e389f7f7` (the
pre-merge head; the merge brought nothing that overlaps the diff). Each
ran with stdin closed and its exit captured before any pipe.
- `dispatch-gates.mjs --ran`: 112 accounted, 111 run, 1 NOT-MEASURED:
`pnpm check:dual-build-cjs-loads` exit 3 (PREREQUISITE NOT MET, needs a
whole-workspace build), left to CI.
- `check:skill-examples` and `check:i18n-coverage` first exited 3 on
unbuilt prerequisites; after building `client-react` and the
i18n-coverage closure both exit 0 (`check-i18n-coverage: OK (14
config(s), 426 baselined untranslated string(s), none new)`).
- At `f566aa2e`: `node scripts/check-sdui-manifest.mjs` exit 0; `node
scripts/check-i18n-bundles.mjs` exit 0; `pnpm --filter @objectstack/spec
check:generated` "All 14 generated artifacts are up to date" after a
spec rebuild; ui `check:new-line-citations` 0 new; `check-nul-bytes`
exit 0.
</details>
<details><summary>Tests</summary>
- ui, at `f566aa2e`: the 18 new or touched test files plus
`RecordDetailView.childWriteRefreshesParent-12244.test.tsx` (the one
overlap with main) → 19 files, 100 passed.
- ui, at `e389f7f7`: `packages/core/src/actions/`, `packages/i18n/`,
`packages/components/src/renderers/action/`, every
`useActionTextLocalizer*`, `useConsoleActionRuntime*`,
`ActionParamDialog*`, `ActionConfirmDialog*` and `RecordDetailView*`
test → 330 files, 3779 passed, 13 skipped. `packages/types/` → 386
files, 10456 passed. `pnpm --dir ui exec vitest run scripts/` → 169
passed, 2 skipped, exit 0 (includes `check:action-forward-parity`).
- ui type-check (turbo, `@object-ui/app-shell`, `components`, `core`,
`i18n`, `react`, `types`) → 70/70 tasks, exit 0.
- spec: `vitest run src/ui/ src/system/` → 238 files, 8155 passed;
`scripts/liveness/` → 12 files, 359 passed; `typecheck` exit 0.
- cli: `vitest run --project unit test/i18n-extract` → 12 files, 96
passed; `typecheck` exit 0. The integration tier is declared to CI.
- platform-objects: `test` → 108 files, 1503 passed; `typecheck` exit 0.
- Ablation (`scripts/ablation-replace.mjs`, committed first): deleting
the `cancelText` spread in `confirmOptions` reds
`ActionRunner.confirmCancelText-28879` (2 failed | 3 passed); replacing
`state.cancelText || t('actionDialog.cancel')` with
`t('actionDialog.cancel')` reds `ActionParamDialog.cancelText-28879` (2
failed | 2 passed). Both restored, blob == HEAD, `git diff HEAD` empty.
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(Only a describe, a form row, a liveness row and a test ledger row
change there.)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` is CI's run.
- [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.
- [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 Chinese summary the repo asks for).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01QsR2jQtTxhjJ5tQUg79qSg
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QsR2jQtTxhjJ5tQUg79qSg)_
---------
Co-authored-by: Claude <noreply@anthropic.com>cancelText drawn in place of Cancel) (#28983)1 parent c708de6 commit 462b4f9
54 files changed
Lines changed: 1171 additions & 54 deletions
File tree
- .changeset
- content/docs
- protocol/objectui
- references
- data
- kernel
- system
- ui
- packages
- cli
- src/utils
- test
- platform-objects/src
- apps/translations
- identity
- spec
- liveness
- state-counts
- src
- system
- ui
- ui/packages
- app-shell/src
- hooks
- __tests__
- views
- components/src/renderers/action
- __tests__
- core/src/actions
- __tests__
- i18n/src
- __tests__
- react/src/hooks
- __tests__
- types/src
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
538 | 538 | | |
539 | 539 | | |
540 | 540 | | |
541 | | - | |
| 541 | + | |
542 | 542 | | |
543 | 543 | | |
544 | 544 | | |
| |||
558 | 558 | | |
559 | 559 | | |
560 | 560 | | |
561 | | - | |
| 561 | + | |
562 | 562 | | |
563 | 563 | | |
564 | 564 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
491 | 491 | | |
492 | 492 | | |
493 | 493 | | |
494 | | - | |
| 494 | + | |
495 | 495 | | |
496 | 496 | | |
497 | 497 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
336 | 336 | | |
337 | 337 | | |
338 | 338 | | |
339 | | - | |
| 339 | + | |
340 | 340 | | |
341 | 341 | | |
342 | 342 | | |
| |||
0 commit comments