Skip to content

feat(confirmations): ask in a modal instead of an action toast - #199

Merged
Artmann merged 2 commits into
mainfrom
feat/confirm-modal
Sep 18, 2026
Merged

Artmann merged 2 commits into
mainfrom
feat/confirm-modal

Conversation

@Artmann

@Artmann Artmann commented Sep 18, 2026

Copy link
Copy Markdown
Owner

Deleting a connection, a worksheet selection, or an environment asked in a sonner toast carrying a Delete action. A toast is a poor place to ask a question: it dismisses itself, it never takes focus, it is not announced as a dialog, and it is gone by the time the user looks back at it.

All three now go through one AlertDialog.

What changed

  • src/app/components/ui/alert-dialog.tsx — the Radix primitive, written by hand in the shape ui/popover.tsx already establishes: one @radix-ui/react-alert-dialog import and the app's own tokens (bg-panel, text-text, z-(--z-portal)). The shadcn CLI was tried first and is not usable in this repository — see the friction entry in the second commit.
  • src/app/components/ConfirmDialogProvider.tsx — one dialog for the whole app, asked for imperatively. useConfirm() takes the same shape the action toast did (a title, a description, and the callback the confirm button runs), so no caller keeps "is the dialog open" state of its own and the worksheet explorer still confirms a whole selection in one question. Closing drops the options rather than flipping an open flag, so the callback — and the selection it closes over — is released as soon as the question is answered.
  • The three call sites (DatabaseExplorer, WorksheetExplorer, SettingsScreen) call useConfirm(). Their success and error toasts are untouched; only the question moved.
  • Provider mounted in renderer.tsx and in test-utils.tsx.

One test-infra fix

A Radix modal sets pointer-events: none on <body> and restores it when the last layer closes. A test that leaves the dialog open is unmounted rather than closed, so the restore never runs and the style leaks to the next test in the file, which then fails on user.click with "the element has pointer-events: none" — pointing at whatever it clicked rather than at the dialog.

vitest.setup.ts clears it on the way into each test. beforeEach rather than afterEach, because vitest runs afterEach hooks in reverse registration order, so one registered there fires before Testing Library's cleanup — that is, before the unmount that leaks the style.

Verification

yarn typecheck, yarn lint and yarn format:check are clean. yarn test passes 1607 tests across 129 files, including six new ConfirmDialogProvider tests covering confirm, Cancel, Escape, and the missing-provider assertion; the existing delete tests now click Cancel rather than walking away from the question.

Not verified: the visual check in the running app. yarn start exits as soon as it is not attached to an interactive terminal — the Vite dev server dies with it and the window loads nothing — so it could not be driven from a background shell. Worth a look at the modal in yarn start before merging, particularly opening one from inside the connection editor overlay (the --z-portal vs --z-overlay check).

🤖 Generated with Claude Code

https://claude.ai/code/session_0156WamccrozzRDnhxs9nRH7

@Artmann
Artmann changed the base branch from main to feat/environment-labels-ui September 18, 2026 04:31
@github-actions

github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

React Doctor found no new issues. 🎉

Reviewed by React Doctor for commit f93738e.

Base automatically changed from feat/environment-labels-ui to main September 18, 2026 04:41
Deleting a connection, a worksheet selection, or an environment asked in a
sonner toast carrying a Delete action. A toast is a poor place to ask: it
dismisses itself, it never takes focus, it is not announced as a dialog, and
the question is gone by the time the user looks back at it.

All three now go through one AlertDialog. `useConfirm()` takes the same shape
the action toast did -- a title, a description and the callback the confirm
button runs -- so no caller keeps "is the dialog open" state of its own, and
the worksheet explorer still confirms a whole selection in one question. The
success and error toasts are unchanged; only the question moved.

The primitive is written by hand rather than generated: the shadcn CLI writes
to a literal `@/` directory here and pulls in the umbrella `radix-ui` package,
so `ui/alert-dialog.tsx` follows `ui/popover.tsx` instead -- one
`@radix-ui/react-alert-dialog` import and the app's own tokens.

A Radix modal sets `pointer-events: none` on `<body>` and restores it when the
last layer closes. A test that leaves the dialog open is unmounted rather than
closed, so the restore never runs and the style leaks to the next test in the
file, which then fails on `user.click` pointing at whatever it clicked.
`vitest.setup.ts` clears it on the way into each test -- `beforeEach`, because
vitest runs `afterEach` hooks in reverse registration order and one registered
there would fire before the unmount that leaks it.
…ectory

`npx shadcn@latest add alert-dialog` put both files under a literal `./@/`
directory, silently rewrote this repository's `button.tsx` inside it, and added
`cn` and the umbrella `radix-ui` to package.json. Nothing landed in `src/`.
@Artmann
Artmann merged commit d448bbb into main Sep 18, 2026
9 checks passed
@Artmann
Artmann deleted the feat/confirm-modal branch September 18, 2026 04:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant