Modal sheets

Overview

Every full-screen sheet in the app — the CLI, Git, web-preview and MCP-error viewers, the web-preview URL prompt, the agent file picker and the Inspector's full-screen preview — shares one behaviour: Escape dismisses the sheet, Tab stays inside it, and closing it returns focus to the control that opened it. That behaviour lives in one place (frontend/src/hooks/useModal.js) instead of being re-implemented, slightly differently, in each component.

Usage

Nothing to configure. Open any sheet and:

Key / gestureResult
EscapeDismisses the sheet
TabMoves to the next control inside the sheet; from the last control it wraps to the first
Shift + TabMoves backwards, wrapping from the first control to the last
Tap the backdropDismisses the sheet (each sheet's own overlay handles this)
Tap the close buttonDismisses the sheet

When two sheets are stacked — the Git modal with a commit-confirm sheet over it, the Inspector's full-screen preview over the Inspector — Escape closes only the top one, and the sheet underneath takes the keyboard back. Before this was centralised, each sheet registered its own document-level listener, so one Escape ran both handlers and the outer sheet closed first.

Closing a sheet puts focus back on whatever was focused before it opened, so a keyboard user lands on the button they tapped rather than at the top of the document.

What a sheet deliberately does not do