Skip to content

a destructive Dialog cannot opt its OK button out of autoFocus without replacing the whole footer, which severs the command wiring #119

Description

@woksin

Problem / motivation

This issue is filed from a production Cratis consumer. The complete evidence, costs, options and explicit non-goals follow.

Status: OPEN — filed 2026-08-07. Kind: missing seam. Package: @cratis/components (Dialog) + @cratis/arc.react (CommandDialog).

The row lives in ../CRATIS_UPSTREAM_IMPROVEMENTS.md.

The seam Cratis should provide

Dialog's okCancelFooter renders its OK button with autoFocus: true, and CommandDialog defaults buttons = DialogButtons.OkCancel. There is no prop to say "this dialog is destructive — do not arm its primary button on open."

The only escape is replacing the footer with a custom buttons ReactNode — and that is not a workaround, because it also:

  • flips closable to false,
  • makes onHide a no-op,
  • severs CommandDialog's handleConfirm, so the command never executes.

The ask: an initialFocus / autoFocusPrimary prop on Dialog, honoured by CommandDialog. Which button should take focus, and which dialogs count as destructive, stay the consumer's policy — the seam is only that the choice be expressible without discarding the footer's behaviour.

Where Ada paid the cost

Source/Core/Compliance/Retention/Confirm/ConfirmDialog.tsx — the dialog that authorises irreversible destruction of a person's personal data, and under D82 the only path by which it happens.

An independent review found a one-keystroke destruction path: the row trigger and the dialog's OK carried the same label, a native <button> fires click on keydown, and autoFocus put the mounted dialog's OK under the same held key. Auto-repeat, or the ordinary habit of pressing Enter twice, collapsed a two-step confirmation into one act.

⚠️ The asymmetry is what makes this a seam rather than an Ada mistake. Ada's reversible action — Postpone — was protected for free, because its mandatory comment leaves the OK gated by isCommandFormValid. The irreversible one was not, precisely because it needs no input. The framework protects the dialog that asks for something and arms the one that asks for nothing, which is exactly backwards for a destructive action.

Ada closed it by adding an acknowledgement checkbox purely to create a shut gate. That is a real cost, not a wash: it is a control the user must operate that exists for no domain reason, on a compliance surface where every extra step is friction an advisor will learn to click through — and it is one more thing between an advisor and a deletion they have already decided to make.

The seam vs. Ada's taste

The seam: a dialog should be able to say where initial focus goes without forfeiting closable, onHide and the command execution path.

Ada's taste, which Cratis need not adopt: Ada would additionally like destructive dialogs to require an explicit acknowledgement. That is Ada's policy and Ada can build it — as it now has. The upstream ask is only that the focus choice not be bundled with three unrelated behaviours.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions