fix: render extension dialogs inline with keyboard nav, stacking, and expiry (#462) - #676
fix: render extension dialogs inline with keyboard nav, stacking, and expiry (#462)#676mufflegyro wants to merge 1 commit into
Conversation
… expiry (agegr#462) Extension dialogs (ctx.ui.select/confirm/input/editor) were rendered as a full-window overlay that hid the chat context, ignored timeout/expiresAt, offered no keyboard support for select, and used a single state slot that could not express composed flows. - Inline cards: dialogs now render as cards in the conversation transcript (message stream and new-session state) instead of a dimming overlay, so the conversation stays readable while answering. Drop aria-modal; the card is non-modal by design. - Keyboard navigation: select supports Arrow keys, Home/End, Enter to pick, and Esc to cancel (input/editor already handled Esc; the global stop-agent Esc shortcut now yields to focused extension dialogs). - Dialog stack: unanswered requests queue instead of clobbering each other, so composed flows (select -> input) and concurrent requests resolve in order (deduped by id against SSE replays on reconnect). - timeout/expiresAt: dialogs that carry a deadline show a countdown and auto-cancel (extension await resolves with its default value, matching the daemon-side timeout and TUI behavior). - Drop stale dialogs when switching sessions. Verified with a playwright E2E against a real extension command: keyboard select -> input flow round-trips answers, Esc cancels, countdown auto-cancels, queued dialogs resolve sequentially, and SSE reconnect replays only one card.
|
Closing this PR in favor of the existing #612 changes and 2356904 on main. #612 already provides collapsible dialogs that keep the composer and Stop accessible, plus server-driven dialog closure on timeout/cancellation. The new commit adds arrow-key and Home/End navigation with visible native button focus, scrolls the focused option into view, handles Escape inside the dialog without aborting the agent, and displays a localized countdown from the server's expiresAt value. Expiry remains owned by the server through extension_ui_closed. The inline layout and preemptive dialog stack are not being adopted here. Review reproduced cancellation leaking from an expired request into an unrelated queued request, loss of a queued dialog's draft, and keyboard selection moving offscreen. Concurrent request queuing can be addressed separately; sequential select-to-input flows already work without a stack. Validation: TypeScript, ESLint, and all 891 tests pass. The browser suite passes at 1280px and 390px, including real extension commands for navigation, Escape without agent abort, draft preservation across collapse/expand, countdown updates, and answering a new request after server-side expiry. These checks are now retained in the E2E suite. |
Closes #462
Summary
ctx.ui.select()/confirm()/input()/editor()dialogs were rendered as a full-window overlay that hid the chat context, ignored thetimeout/expiresAtfields in the protocol, offered no keyboard support forselect, and used a single state slot that couldn't express composed flows. This PR implements all three proposed steps (P0 + P1 + P2) from the issue.Changes
P1 — Inline cards in the transcript
position:absolute; inset:0overlay. The conversation stays readable while answering; no overlay, no click-outside needed.aria-modal="true": the card is intentionally non-modal (role="dialog"+aria-labelinstead).P0 — Keyboard navigation + Esc
selectsupports Arrow Up/Down (and Left/Right), Home/End, Enter to pick the highlighted option, and Esc to cancel. Hover syncs the active option.hooks/useKeyboardShortcuts.ts), so Esc cancels the dialog instead of aborting the run.P2 — Dialog stack + timeout/expiresAt
hooks/useAgentSession.ts).timeout/expiresAtshow a live countdown ("expires in Ns") and auto-cancel at the deadline, resolving the extension's await with its default value — matching the daemon-side timeout and the TUI's auto-dismiss. Stale dialogs are dropped when switching sessions.Verification
tsc --noEmit,eslint ., and the full test suite (844 tests) pass.timeoutshows the countdown, auto-dismisses, and notifies the extension;Notes
ExtensionCustomPanel(custom UI method) is intentionally left as an overlay — out of scope for this issue.maxHeight: 260with internal scroll (replaces the oldflex: 1overlay cap); the card ismaxWidth: 560,width: 100%.