fix(apps): make a covered elicitation dialog inert

Only the top modal owned the focus trap, but every lower `Modal.Content`
remained an exposed `aria-modal` dialog with reachable controls and a live
iframe — so browse navigation could land in a dialog that is visually covered
and that itself claims the rest of the page is inert.

Non-top contents are now `inert`: out of the accessibility tree and out of
focus order, without unmounting, so each app keeps its bridge and its handshake
and is live the moment it becomes top.

Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-23 00:02:37 -04:00
commit beaaad4982

View file

@ -216,6 +216,12 @@ describe("AppElicitationHost (#1854)", () => {
await flushAsync();
expect(screen.getAllByTestId("app-elicitation")).toHaveLength(2);
// The covered dialog is inert: out of the a11y tree and out of focus
// order, but still mounted — its app keeps its bridge and handshake.
const [lower, top] = screen.getAllByTestId("app-elicitation");
expect(lower.hasAttribute("inert")).toBe(true);
expect(top.hasAttribute("inert")).toBe(false);
await user.keyboard("{Escape}");
// Exactly one request is dismissed — the topmost, which is the last one.
expect(onFail).toHaveBeenCalledTimes(1);