Skip to content

feat(console): an action's confirm and param dialogs name their dismiss button (cancelText drawn in place of Cancel) - #28983

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-ewid2r
Nov 2, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-ewid2r

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

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 已在 feat(spec): an action's confirm and param dialogs can name their dismiss button — 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.

Derived gates: 112 derived, 111 run exit 0, 1 NOT MEASURED
  • 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.
Tests
  • 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.

Human Minimum

  • 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.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint is CI's run.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: its generated artifacts are regenerated and committed.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English (the 维护者速读 section is the Chinese summary the repo asks for).

🤖 Generated with Claude Code

https://claude.ai/code/session_01QsR2jQtTxhjJ5tQUg79qSg


Generated by Claude Code

…ext on the dismiss button

Claude-Session: https://claude.ai/code/session_01QsR2jQtTxhjJ5tQUg79qSg
Co-Authored-By: Claude <noreply@anthropic.com>
…s bundles for cancelText; changeset

Claude-Session: https://claude.ai/code/session_01QsR2jQtTxhjJ5tQUg79qSg
Co-Authored-By: Claude <noreply@anthropic.com>
…unts the action form's cancelText row

Claude-Session: https://claude.ai/code/session_01QsR2jQtTxhjJ5tQUg79qSg
Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation protocol:system protocol:ui tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/cli, @objectstack/platform-objects, @objectstack/spec, touching 6 documentable anchor(s). ⚠️ 2 changed file(s) yielded no anchor (packages/spec/liveness/action.json, packages/spec/liveness/state-counts/action.md), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/automation/hooks.mdx (via sys_invitation (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/deployment/self-hosting.mdx (via sys_invitation (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/frontend/plugins/plugin-view.mdx (via cancelText (literal, a string literal in pushActionCancelText; a string literal in sections))
  • content/docs/permissions/system-context.mdx (via sys_invitation (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/protocol/objectui/actions.mdx (via sys_invitation (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects), cancelText (literal, a string literal in pushActionCancelText; a string literal in sections))
  • content/docs/ui/actions.mdx (via sys_invitation (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))

⛔ 3 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-3.mdx (via sys_invitation (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/releases/v18/18-1.mdx (via sys_invitation (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/releases/v18/18-2.mdx (via sys_invitation (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/spec/liveness/action.json, packages/spec/liveness/state-counts/action.md) — pages documenting those are invisible to this run
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 216 client-bound route-ledger rows — the other 156 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 156: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 101 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 177 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 00d0668d380046198dfc9151858b88df463bfd9b → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 6bd81c6aeb45ce93617c163f60d4112426cd1ac3 — the merge of head f566aa2e1c0e895ca3b7d23a2de9626244c2915a into base 00d0668d380046198dfc9151858b88df463bfd9b, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 6bd81c6aeb45ce93617c163f60d4112426cd1ac3 && git checkout 6bd81c6aeb45ce93617c163f60d4112426cd1ac3
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 00d0668d380046198dfc9151858b88df463bfd9b f566aa2e1c0e895ca3b7d23a2de9626244c2915a && git checkout -B drift-repro 00d0668d380046198dfc9151858b88df463bfd9b && git merge --no-ff f566aa2e1c0e895ca3b7d23a2de9626244c2915a

node scripts/docs-audit/affected-docs.mjs --json 00d0668d380046198dfc9151858b88df463bfd9b

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 00d0668d380046198dfc9151858b88df463bfd9b → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu

席位核对 · ACCEPT · 2026-11-02T06:13Z

Head-sha: f566aa2e1c0e895ca3b7d23a2de9626244c2915a

  • Shape:

    • The PR is a draft against main. The first card line is Fixes #28879, and no other card sits beside a closing keyword.
    • 54 files, +1171 / −54, of which 21 are tests. No governed path: check-governed-merges --pr 28983 reports NOT governed, and skills/** is untouched.
    • Clause-②: yes (widening) and Metadata-protocol: none start their lines in the body and the changeset, as claimed (6568809663, amendment 6568954960). The changeset is patch.
  • Read in the diff:

    • The confirm dialog and the param dialog both draw the action's cancelText on the dismiss button, and fall back to "Cancel".
    • The localizer resolves cancelText declared-only, bundle first, and the runner hands it as options.cancelText. Both param handlers resolve it the way they resolve submitText. ActionParamDialog draws it, and the four renderers forward it.
    • UIActionSchema.cancelText derives from the spec (amendment 6568954960).
    • In RecordDetailView, the diff touches only the useObjectLabel() destructure and paramCollectionHandler. It merges cleanly with fix(plugin-detail): the record meta line leaves the owner out only when the strip the page drew carries it #28994 (queued), which edits the same file elsewhere.
    • The extractor scaffolds cancelText beside a declared confirmText or non-empty params, with a test.
    • sys_invitation.cancel_invitation declares cancelText: 'Keep invitation', with zh/ja/es translations regenerated. The platform-objects catalog pin moves from 698 to 699.
    • Liveness and docs flips:
      • The spec liveness row goes from planned to live.
      • The reconciliation omit row is deleted, and action.form.ts offers the key.
      • The "no renderer shows it yet" describes are dropped.
      • The references are regenerated, and both actions.mdx sentences flip, including the stale submitText one.
  • Gates (check-runs on this head): 58 success, 7 skipped, 0 failed or pending. mergeable_state is clean. origin/main 00d0668d was merged in cleanly.

  • Tests (dev):

    Suite Result
    19 new or touched ui files 100 passed
    Touched-module ui sweep 330 files / 3779 tests
    ui types 10456
    spec ui and system 8155
    cli i18n-extract 96
    platform-objects 1503

    Two ablations went red and were restored. Type-check is clean across ui, cli, spec and platform-objects. Derived gates: 111 of 112 exit 0, and check:dual-build-cjs-loads is left to CI's build.

  • Not here: Wanted 5 (inline actions). MetadataTypeActions.tsx passes neither submitText nor cancelText; that is pre-existing and noted in the PR's Acceptance notes. The skills docs are a separate Tier H card.

  • NOT MEASURED: the seat ran no live session.

  • Landing: ordinary queue landing, pr_ready then automerge_enable. Fixes #28879 closes the card on merge.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 2, 2026 06:14
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 2, 2026 06:14
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 2, 2026
Merged via the queue into main with commit 462b4f9 Nov 2, 2026
66 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-ewid2r branch November 2, 2026 06:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:system protocol:ui size/xl tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Action confirm and param dialogs: draw the action's cancelText on the dismiss button, falling back to Cancel; sys_invitation declares "Keep invitation"

1 participant