Skip to content

An action's confirm dialog is always titled 「确认操作」 with a 「继续」 button — the action's own label and danger variant never reach it #26580

Closed

Description

@objectstack-fleet

Filing class: ① user-visible defect — reach: public door, measured in a browser on the 18.1.0 Console with objectstack-ai/hotclm main, code read on objectstack main 1c632311. Reader: the UI lane, ui/packages/core/src/actions/ActionRunner.ts (ConfirmationHandler call) and ui/packages/app-shell/src/views/ActionConfirmDialog.tsx.

Business reason

A confirm dialog is the user's last chance before an irreversible step. hotclm's 撤回 (withdraw a draft) moves the contract to 已作废, a terminal state. The dialog's body says so clearly, but its title is 「确认操作」 and the button is 「继续」, in the neutral style. A user who skims reads "continue" and loses the contract.

Measured

  • 撤回 on a draft (hotclm withdraw, confirmText declared): title 「确认操作」, buttons 取消 / 继续 (neutral). The same chrome appears for every declared action with a confirmText.
  • ActionConfirmDialog.tsx:61 / :72 fall back to actionConfirm.title / .confirm whenever options is empty. The runner calls the confirmation handler with the message only, so options is always empty for a metadata action. Record Delete already passes a title, a confirm label and destructive (objectui#11695 / docs(agents): drop the retired "gen:schema wipes gen:openapi" trap from three agent-facing surfaces #11744); declared actions do not.

Expected

Title = the action's label (「撤回」); confirm button = the action's label; destructive styling when the action declares variant: 'danger' (or the spec's equivalent). No new metadata key is needed for the default. (hotclm's withdraw_contract, src/actions/contract-lifecycle.actions.ts:188, does not declare variant: 'danger' today because the dialog would not read it; it will once the dialog does.)

Dedupe: objectstack issues (2,000 most recent) and archived objectui issues grepped for actionConfirm, 确认操作, Continue, confirm dialog — objectui#11695 fixed this for Delete only; objectstack#25637 made confirmText a template slot and did not touch the chrome.


Filed from the hotclm UI/UX review (2026-10-27); maintainer: 「平台的问题应该提交issue到平台」.

Activity

  1. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · priority:p2 · domain:ui · area:records · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-27T09:01Z

    ⛔ Not a claim, ⛔ not a dispatch.

    • p2: the confirm dialog of an irreversible action is titled 「确认操作」 with a neutral 「继续」. Pass the action's own label and its danger variant through ActionRunner's ConfirmationHandler to ActionConfirmDialog (the title is the action label, the confirm button takes the label in the danger style). ⛔ No new authorable key: the action already declares its label and variant.

    Generated by Claude Code

  2. added
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    bugSomething isn't working
    and removed on Oct 27, 2026
  3. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01QsR2jTfvjNTaR29ehLk26T
    Account: zhuangjianguo
    Branch: claude/project-thread-lwevo0 (platform-assigned card-thread branch; a card thread can push only its assigned branch, and the name carries no issue number, so this claim is the identity record)
    Worktree: objectstack-issue-26580
    Domain: domain:ui
    Seat: domain:ui#4
    File surface: ui/packages/core/src/actions/ActionRunner.ts (the confirm call site only), ui/packages/app-shell/src/views/ActionConfirmDialog.tsx if needed, their tests, one root changeset (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate; reason: the maintainer's standing rule 「只认领不需要 fable 的卡」, 2026-10-27T10:39Z), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: ActionRunner (@object-ui/core, this lane) calls the ConfirmationHandler with the message only, so ActionConfirmDialog falls back to generic chrome · the platform path that covers it: the handler's existing options argument, already used by Record Delete · reached by every user confirming a declared action with confirmText (hotclm 撤回)
    Thread-read: 6437776331
    Premise: holds (action, card-reference and work-item surfaces re-read on origin/main c739de75 at 2026-10-27T10:58Z)
    Serial constraints cleared: none: #26459 (seat 2, PR #26604) reads ActionRunner.ts and edits only showcase strings; no open PR touches these files (scan 2026-10-27T10:59Z)

    Dispatched at 2026-10-27T11:01Z to the card thread on that branch; the order lives in the thread's opening message.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim amendment · seat domain:ui#4 · session_01QsR2jTfvjNTaR29ehLk26T · 2026-10-27T11:20Z

    File surface (replaces the claim's line): ui/packages/core/src/actions/ActionRunner.ts (the confirm call site only), ui/packages/app-shell/src/views/ActionConfirmDialog.tsx if needed, ui/scripts/check-action-forward-parity.mjs (four JUSTIFIED table entries only: action:button, action:icon, action:group and action:menu do not forward variant), their tests, one root changeset (stop on breach; explain in the report)

    Why: once the runner reads the action's declared variant, the forward-parity gate expects the four action renderers to forward it too. Their own variant is the @object-ui/types UIActionSchema control vocabulary (destructive, outline, …), consumed at the renderer to paint its control. ActionDef.variant is the spec enum (danger, link, …). Forwarding one into the other is a type error. Each entry names the reason and this card. Whether the two vocabularies should be reconciled is a separate question; it comes back in the report's open questions, ⛔ not built here.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
     "issue": 26580,
     "status": "done",
     "branch": "claude/project-thread-lwevo0",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/26663",
     "session": "session_01QsR2jSVZrd3f8WgEZrkUCu",
     "premise_still_valid": true,
     "summary": "ActionRunner's one confirm call now passes the ConfirmationHandler's existing options bag: title and confirmText = the action's label (localized upstream by useActionTextLocalizer), destructive: true for spec variant 'danger'; an action with neither keeps the one-argument call. ActionConfirmDialog.tsx and useConsoleActionRuntime needed no change (they already forward and render the bag; the dialog maps destructive to buttonVariants({ variant: 'destructive' })). PM assumptions measured: the runner passed the message only (held); the dialog falls back to actionConfirm.title/.confirm (held); Record Delete passes options (held); the spec spells the danger variant `variant: 'danger'` (ActionSchema.variant enum primary|secondary|danger|ghost|link).",
     "files_changed": [
      "ui/packages/core/src/actions/ActionRunner.ts",
      "ui/packages/core/src/actions/__tests__/ActionRunner.confirmNamesAction-26580.test.ts",
      "ui/scripts/check-action-forward-parity.mjs",
      ".changeset/26580-confirm-dialog-names-action.md"
     ],
     "line_budget": "4 files, +152/-8 (git diff --shortstat c739de75..84482207)",
     "deviations": [
      "File surface breach, accepted by the seat (claim amended 6440643307): four JUSTIFIED entries in ui/scripts/check-action-forward-parity.mjs, because the runner now reads action.variant and the four action renderers' variant is the UIActionSchema button-style vocabulary.",
      "Claim lines copied verbatim (Clause-②: no, Metadata-protocol: none); the exported ConfirmationHandler type is unchanged, only its doc comment, so `no` holds as measured."
     ],
     "tests": "core: new ActionRunner.confirmNamesAction-26580.test.ts (5 tests). Full core suite under os-verify-lock at 88581363 after the core^... closure build: Test Files 214 passed (214), Tests 4105 passed | 27 skipped, VERDICT command-exit 0. pnpm --filter @object-ui/core run type-check (src + tsconfig.test.json): exit 0. Ablation via scripts/ablation-replace.mjs (anchor `? this.confirmHandler(confirmMessage, confirmOptions)` 1 to 0, blob 888cfb538c5d to 926b95771ab6): new file Tests 3 failed | 2 passed (CONTROL and cancel stay green as expected); restored blob == HEAD 888cfb538c5d, git diff HEAD empty. Source import, no dist hop. ui scripts suite at 84482207 under the lock after the app-shell^... closure build: Test Files 178 passed | 2 skipped (180), Tests 5065 passed | 2 skipped.",
     "gates": "dispatch-gates --commands at 84482207: 34 commands, all exit 0, captured before any pipe (PR-body gates fed this PR's body via GITHUB_EVENT_PATH; check:type-check-debt under the lock). --ran reconciliation: 34 derived, 34 run, 0 NOT-MEASURED (a DERIVED zero, every line carries an exit code). check-partof-closing-keyword on the final body: exit 0. First sweep (at 88581363) caught `pnpm --dir ui exec vitest run scripts/` red on check-action-forward-parity (variant on 4 surfaces) plus an unbuilt-workspace prerequisite in unit-registry-absence-collision; both green at 84482207. CI check-runs: in_progress at report time.",
     "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
     "mcp_calls": "0",
     "api_writes": "2 — relay pr_create (POST /repos/objectstack-ai/objectstack/pulls + POST /issues/26663/assignees, run 40500596026), this os-dev-report comment (POST /issues/26580/comments via post-stamped). git push is not a REST write.",
     "open_questions": [
      {
       "question": "Two variant vocabularies: action:button/icon/group/menu style with UIActionSchema.variant ('destructive', 'outline', 'default', …) while ActionDef and the spec read 'danger'. A node styled 'destructive' gets a neutral confirm button. Close it how?",
       "options": [
        "A: retire the button-style spelling on the action renderers' authorable surface in favour of the spec enum (danger), with the renderer mapping danger onto its button style",
        "B: map destructive to danger in each renderer's forward",
        "C: leave it; the declared actions the console dispatches carry spec variant already"
       ],
       "recommendation": "A, because the spec declaration should win (spec > implementation) and one spelling makes it harder for AI-authored metadata to pick the one the runner ignores; B is consumer-side tolerance that keeps two spellings; C leaves a silent gap. Needs a maintainer ruling; it touches a published @object-ui/types surface."
      }
     ],
     "out_of_scope_findings": [
      "class: b · reach: named producer needed — the console's declared actions (DeclaredActionsBar, page:header dispatchHeaderAction) carry spec variant and are covered; no producer measured that authors action:* nodes with variant 'destructive' plus confirmText · evidence: ui/packages/types/src/ui-action.ts UIActionSchema.variant vs ActionDef.variant (SpecActionInput['variant']); action-group.tsx and action-menu.tsx read (action.variant as string) === 'destructive' · dedupe words: action variant destructive danger, UIActionSchema variant vocabulary, confirm dialog destructive, action-button variant mapping",
      "carrier: seat (open_questions[0]) · noted, not filed"
     ]
    }

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:uipm:dispatchedpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions