fix(terminal): report the terminal as the focused element

A terminal draws its own glyphs, so nothing outside the window can read
what is on screen. That much is a terminal being a terminal. What is not
is that the window reports no focused element at all.

gpui sets accessibility focus in exactly one place: a `div` that tracks a
focus handle and has an a11y node of its own. The terminal surface tracks
the focus handle and never asks for a role, so it has no node, so
`set_focus` is never reached — and a client asking the window what has
focus is handed the window.

Measured on Windows 11 26200 with a UI Automation probe: the window
answers with `WindowPattern` and nothing else, publishes zero
descendants, and `FocusedElement` is the top-level window, supporting
neither `ValuePattern` nor `TextPattern`. A screen reader has nothing to
say about a tty7 window for the same reason.

It reaches past screen readers. A dictation tool pastes its transcript
and then asks the focused element what it now says, to check the text
arrived. Against tty7 it gets no element to ask, concludes the paste
failed, and hands the transcript back for the user to paste by hand —
while the bytes it sent are already in the pty and the text is on screen.
That is what led here.

The fix is the surface asking for a role: it gets a node, and focus lands
on it. `MultilineTextInput` rather than `Terminal` because `Terminal`
maps to a document that reports itself as not editable, and "is this
something text can be put into" is the question these clients are
actually asking. The node carries no text of its own yet — reading the
grid out is a separate change with a cost per frame, and this one has
none: gpui builds the a11y tree only once something attaches to it, so a
window nobody is inspecting still builds nothing.

The path this fixes is platform-independent; it was verified on Windows,
where the dictation tool that surfaced it runs.
This commit is contained in:
hhdebb
2026-09-11 11:09:36 +08:00
parent a60d992c99
commit 2f63a01f7b
+5
View File
@@ -6820,6 +6820,11 @@ impl Render for TerminalView {
div()
.id("terminal-surface")
// The surface, not the grid inside it, is what carries the role:
// a11y focus is only ever reported for a `div` that tracks a focus
// handle *and* has a node of its own, so a terminal with no role
// here is a window whose focused element is the window.
.role(gpui::Role::MultilineTextInput)
.track_focus(&self.focus_handle)
.key_context(self.key_context())
.size_full()