Skip to content

(ui): confirmations and messages use the app's own dialog; native ones are banned (#585) - #589

Merged
devsuitup merged 10 commits into
mainfrom
feat/585-app-dialogs
Oct 13, 2026
Merged

devsuitup merged 10 commits into
mainfrom
feat/585-app-dialogs

Conversation

@devsuitup

Copy link
Copy Markdown
Owner

Closes #585.

  • New module. public/app-dialogs.js provides appConfirm / appAlert, built on showChoiceDialog.
    • A second dialog is queued (FIFO), not dropped as a cancel.
    • On a destructive confirm, focus goes to the safe button.
    • On close, focus returns to the opener.
  • Every renderer call site is migrated: app.js, resume-guard.js (the injected confirm is now awaited), agents-view.js, sidebar.js, settings-panel.js, viewer-panel.js, file-panel.js (including the async discard check in the unsaved guard), and activity-trace-panel.js.
  • Lint: no-restricted-globals and no-restricted-properties reject confirm, alert and prompt under public/. A test pins the rule.
  • Tests: jsdom tests for the module (queueing, focus, keys) and for the migrated sites. One e2e journey drives a formerly native confirm (Delete), which Playwright used to auto-dismiss.
  • Main process: no native message box remains. The only native dialog left there is the folder picker (main.js:704), which is out of scope.
  • Docs: .ai/contexts/app-dialogs.md and the CHANGELOG.

Note: the unsaved-edit discard check is now async. It touches the area of #479 and #478.

…s are banned (#585)

Native confirm/alert looked foreign, blocked the renderer and were
auto-dismissed by Playwright, so e2e could not drive them. appConfirm
and appAlert (public/app-dialogs.js) build on showChoiceDialog, queue
instead of dropping a second dialog, focus the safe button on a
destructive confirm and give focus back to the opener. Every renderer
call site is migrated, injected confirms in resume-guard are awaited,
and ESLint rejects confirm, alert and prompt under public/.
The scheduled-run notice is an OK-only alert again, continuation prompts
are not styled as dangerous so Enter accepts them, keys follow the
topmost dialog even when focus drifted, global shortcuts wait while a
dialog is open, the unsaved-edits question focuses Save again, Escape in
the diff view is swallowed only when it acted, and Enter on a dangerous
dialog's body does nothing. The lint ban also covers globalThis and
self.
Each dialog routed keys on its own, which left gaps: Enter from the body
reached a dialog underneath, the unsaved-edits guard ignored Escape once
focus drifted, and the terminal still took shortcuts and input under an
open dialog. Every modal now registers on a stack; a single capture-phase
router sends Escape, Enter and Tab to the topmost one, and app shortcuts,
xterm shortcuts and PTY input wait while any modal is open. The unsaved
guard waits for the dialog queue, follow-up questions included.
…stack (#585)

Dropping all xterm output under a modal also dropped its replies to
programs (cursor position, device attributes, colours); typed input is
already stopped by the key router, and opening a modal now takes focus
off the terminal. Resume and remote launch focus their first control,
and the unsaved-edits guard waits only for other questions, opening
above any other modal.
Letting xterm replies through also let typed text and paste reach a
terminal that regained focus under a modal (after F11, or a restored
session opening behind What's new). Focus landing outside the topmost
modal now goes back into it, paste and input on a terminal textarea are
cancelled while a modal is open, and the fullscreen and showSession
paths no longer focus the terminal under a modal. Closing a modal gives
focus back to what had it, or to the active terminal.
…ake focus (#585)

An opener still in the DOM but hidden (the visible session changed under
the modal) took the restore and left focus nowhere. Restore now checks
that focus actually landed on the opener before skipping the terminal
fallback.
The close question asks with replace: true and may carry closeWith. With
choice dialogs queued by the modal stack, replace now cancels the open one
and puts the question at the front of the queue instead of resolving it as
Cancel. Every close path (answer, replace, closeWith) unregisters from the
stack. The tests follow the queue: a second choice dialog waits instead of
answering null, and the native-dialog test waits for the idle question.
closeWith now settles a close question that has not opened yet, so a quit
that arrives first answers yes without waiting for the question ahead of it.
A replace question asked while a non-choice question (session or worktree
deletion) is open opens on top of it instead of waiting, and Escape returns
to the question underneath.
The Hide confirmations these tests drove no longer exist; Archive and Delete
use the choice dialog, which now needs the modal stack loaded.
@devsuitup
devsuitup force-pushed the feat/585-app-dialogs branch from 16dc051 to 2dc54c3 Compare October 13, 2026 10:58
@devsuitup
devsuitup merged commit 1f82265 into main Oct 13, 2026
12 checks passed
@devsuitup
devsuitup deleted the feat/585-app-dialogs branch October 13, 2026 11:07
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.

(ui): replace native confirm/alert with the designed dialog everywhere, and ban them

1 participant