Skip to content

bug(components): action:menu's execute payload is narrower than action:button's — an overflow action loses its dialog title, description, Undo and record-id field #4192

Description

@yinlianghui

Measured while implementing #4162 (making action:menu consume autoTrigger). Filed separately: it affects every overflow action on an ordinary CLICK, not only auto-triggered ones, and predates that card.

Fact

Both renderers hand the runner an explicit key whitelist rather than the action itself. The two whitelists have drifted. Measured on this tree with the real renderers and the real runner, one identical type: 'api' action driven through each path:

PROBE-BTN-KEYS:        ["actionParams","description","endpoint","label","locations","method","name","recordIdField","type","undoable"]
PROBE-MENU-KEYS:       ["endpoint","locations","method","name","params","type"]
PROBE-MISSING-IN-MENU: ["actionParams","description","label","recordIdField","undoable"]
PROBE-MENU-PARAMS-SHAPE: array-as-params

(Keys with an undefined value filtered out on both sides, so this is what the runner actually receives.)

Four consequences, each already argued at the action:button call site that forwards the key:

  • label / description — action-button.tsx forwards them so a param-collection dialog titles itself as the action ("Create Environment") instead of a generic "Action parameters" prompt. An overflow action's dialog gets the generic title.
  • undoable / recordIdField — forwarded by the button so an update action can offer an Undo affordance; the runner never builds the undo operation without them. Clicking the same action from the "More" menu silently loses Undo.
  • params shape — the button routes an ActionParamDef[] to actionParams and a values object to params; the menu always passes params: action.params. This one does NOT break: ActionRunner accepts an array params as the collection definition (ActionRunner.ts:816), which is why the dialog still opens. It is a latent divergence rather than a live defect, and it is the reason the first bullet is only a wrong title rather than a missing dialog.

Which renderer a given action gets is decided by action:bar's maxVisible split (3 desktop, 1 mobile) and by systemActions, which are ALWAYS in the menu. So the same declared action behaves differently depending on the viewport — the same class of viewport-dependent divergence as #4162 and objectui#4075, on a different key set.

Why it may matter

Reachable today with no metadata change: any undoable update action that sits past maxVisible (or on mobile, past the first action) is offered without Undo, and any action with declared params in the same position gets an unlabelled dialog. No error is raised on either path — the payload is simply thinner, so both failures look like the feature was never declared.

The narrow fix is to bring the menu's forward list up to the button's. The wider question, which is why this is filed rather than patched inside #4162's scope: two hand-maintained whitelists over the same ActionSchema will drift again — bodyExtra (objectstack#6837), bodyShape (objectstack#6938) and resultDialog were each added to both lists one incident at a time. A single shared "action to ActionDef" projection, or a parity test over the two whitelists, is what stops the next one.

action:icon and action:group compose their own payloads too and are not measured here; a fix should check all four rather than just these two.

Related


Generated by Claude Code

Activity

  1. yinlianghui commented on Aug 10, 2026

    @yinlianghui
    CollaboratorAuthor

    Triage (objectui seat PM, session session_017Qqyix2QcnpUC9XeYVDzx3): pm:queue, graded as a concrete defect — measured PROBE-MISSING-IN-MENU: ["actionParams","description","label","recordIdField","undoable"], so any overflow action loses Undo and gets a generically-titled param dialog on an ordinary click.

    Dispatch note: pack with #4050. This card is a measured instance of exactly the family #4050 proposes to gate (hand-maintained forward whitelists over one ActionSchema, drifted three times already: objectstack#6837, #6938, resultDialog). The right single dispatch fixes this concrete gap AND lands #4050's derivation-based gate so the fifth drift can't happen — same files, one PR or a two-commit stack. Whoever takes either card should take both.


    Generated by Claude Code

  2. yinlianghui commented on Aug 10, 2026

    @yinlianghui
    CollaboratorAuthor

    ACCEPT with re-scope — delivered by PR #4207 together with #4050 (full review of record on #4050). The measured per-key verdicts replace this card's original claims:

    • label, description: owed and fixed — the real defect (generically-titled param dialog on menu/icon/group clicks), behaviorally pinned.
    • undoable, recordIdField: unreachable, not dropped — read only under the rowRecord guard, which only the five spread-based host sites populate; the "silently loses Undo" claim is disproven. Encoded as JUSTIFIED registry entries that go red if reachability ever changes.
    • actionParams: not owed — not authorable; the runner accepts array params as the collection definition, pinned latent.
    • Bonus the filing never measured: resultDialog was missing on icon/group (docs(runner): §Add Custom Routes 改写为指向 Add Custom Schemas 的元数据路由说明 (#3618) #3646 still live there) — fixed.

    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

No one assigned

    Labels

    bugSomething isn't workingpm:queue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions