diff --git a/.claude/rules/destructive-confirmation.md b/.claude/rules/destructive-confirmation.md new file mode 100644 index 0000000000..cfaafa44d8 --- /dev/null +++ b/.claude/rules/destructive-confirmation.md @@ -0,0 +1,95 @@ +# Destructive Action Confirmation Rule + +For **irreversible destructive actions** (permanent deletion, purge, force termination, data wipe), confirmation must be collected through a **modal that requires the user to type a confirmation string** — not a `Popconfirm`, not a plain `Modal.confirm`, not a single-click dialog. + +## Why + +Popconfirm and one-click confirmation dialogs are appropriate for **reversible or low-impact** actions (inactivating, hiding, unassigning, canceling a draft). For actions the user cannot undo, a single misclick has permanent consequences. Requiring the user to type a specific string (typically the resource's name) forces a deliberate pause and prevents accidental destruction. + +This convention was applied project-wide in FR-2479 ("standardize confirmation UX"), which replaced the legacy `PopConfirmWithInput.tsx` with the shared `BAIConfirmModalWithInput` component. + +## Rules + +1. **Irreversible actions → `BAIConfirmModalWithInput`** (from `backend.ai-ui`). The user must type a confirmation string (usually the resource's name) before the OK button enables. Examples: permanently delete a VFolder, terminate a model service endpoint, purge a user, delete an image, delete a resource preset, remove a shell script. +2. **Reversible / low-impact actions → `Popconfirm`** or `App.useApp().modal.confirm({ ... })`. Examples: deactivating (not deleting) a user, canceling an in-progress action, hiding an item, marking inactive. +3. **Never use `Popconfirm` for permanent deletion**, even when the action is guarded server-side. The UX contract is about *user intent*, not backend safety. +4. Do **not** reintroduce `PopConfirmWithInput` or any ad-hoc "modal with a text input" for destructive flows — use the shared `BAIConfirmModalWithInput`. This keeps the copy, layout, danger styling, and accessibility consistent. +5. The confirmation string should be something the user sees on screen and can copy unambiguously (e.g., the resource's `name` or `id`). Avoid opaque tokens. + +## Pattern + +### ❌ Wrong — Popconfirm for permanent deletion + +```tsx + deleteForever(row.id)} +> + - { - modal.confirm({ - title: t('dialog.title.LetsDouble-Check'), - content: t('dialog.ask.DoYouWantToResetChanges'), - onOk: () => { - setScript(''); - }, - }); - }, - danger: true, - }, - ], + { + setScript(''); + setIsResetConfirmOpen(false); }} + onCancel={() => setIsResetConfirmOpen(false)} > -