#15091 takeover — adversarial review round 1 (Opus, read-only)

Target: https://github.com/pingdotgg/t3code/pull/15091 (only this PR)
Head: pingdotgg/t3code t3code/fix-browser-focus-stealing @ aec2d97e389f (unchanged; ls-remote confirms)
Local replacement: terminal-preview-21/tab-focus @ bd08c54d0280 on ab6be13d438a (worktree origin/main)
Remote main is now 25aa9fc4e449 (not fetched; GitHub API rate-limited, so I could not
check whether apps/desktop/src/preview changed since ab6be13). Rebase/recheck before publish.
Feedback refreshed from 15091-feedback-r1.json: nothing new since 2026-10-03 except the
takeover claim comment. Checks there are for aec2d97 against fed41fa (stale vs main).

VERDICT: not worth merging as-is; the takeover is correctly NOT finished. The tests-only
commit is a handoff marker, not a fix, and should not be published in its current form.

CONFIRMED SOURCE FINDINGS
1. Original PR is obsolete (blocker for the original). It edits
   apps/web/src/components/preview/PreviewAutomationHosts.tsx, deleted by #15328
   (ac8e9453ca). Agent keys no longer pass through a renderer press handler: server
   ServerBrowserPage.press -> Playwright keyboard.press -> CDP relay ->
   DesktopBrowserHost.relayFor (DesktopBrowserHost.ts:92-95) -> GuestInput.sendPageCommand.
   Nothing from the original diff ports mechanically.
2. Original guard misses its own headline case (moot, but confirms concern 3). In
   previewTabFocus.ts run(), finally -> settle(); with presses==0 and no focus event yet,
   pendingTraversal is false so settle() releases. A destination focus that arrives only
   after the press reply is unguarded, contrary to the PR body's late-delivery claim.
3. Main does not contain Tab, judging by source. GuestInput.ts:135-136 forwards an unfocused
   guest's Tab as native webContents.sendInputEvent; no code limits where Chromium sends
   focus at the guest's first/last control. Main does not fix the original bug by
   construction. Whether it still reproduces on main is UNVERIFIED (needs native rerun).
4. Injected keys masquerade as human (pre-existing, non-blocking). sendInputEvent produces
   trusted DOM events; PickPreload.ts:178-185 reports every trusted keydown on
   HUMAN_INPUT_CHANNEL; Manager.ts:1429-1443 sets controller=human for 750ms. Today this
   only affects cursor opacity/recording overlay. It disqualifies controller=human as a
   takeover/release signal for any guard. DesktopBrowserHost already tracks agentInputAt,
   which could filter this if ever needed.

ON THE IMPLEMENTATION BLOCKERS
- Restore-after-blur / IME (report 2): concrete for any host-renderer guard, not invented.
  DOM focus events can't veto; blur fires composer blur handlers and Chromium finalizes
  composition on focus change. Holding "never blurred" as mandatory is stricter than the
  original PR promised, but it is the right bar when a design can meet it (below).
- Late focus after reply (3): concrete; confirmed in the original's source (finding 2).
- Provenance (4): confirmed (finding 4).
- Child-session bypass (1): accurate but minor. Playwright sends keyboard input on the page
  session, which does go through sendPageCommand.
- Chromium/Electron internals (5): plausible research, not proof either way.
- Missed option: the report only considered guards in the host renderer and an
  Electron-level veto. It did not consider containing the traversal inside the guest, at
  the existing GuestInput boundary.

DESIGN RECOMMENDATION (investigate natively before building)
Both reproductions in the PR body are boundary cases: Shift+Tab from the first input, and
Tab from the last button. Contain the boundary in the guest, so Chromium never hands focus
to the embedder and the host is never blurred:
 - In GuestInput.sendPageCommand, only on the unfocused path and only for Tab:
   - Fold into the existing pre-dispatch Runtime.evaluate: insert two transient focusable
     sentinels on the guest's top document. Use tabindex=1 as the first child so it is
     first in order even with positive tabindex. Use tabindex=0 as the last child. Make
     them fixed, invisible, and pointer-events:none.
   - Dispatch the native Tab unchanged, so real focus order is used, including shadow DOM
     and frames.
   - Remove the sentinels in the existing post-dispatch evaluate. The comment at
     GuestInput.ts:140 already relies on that evaluate running after the page handles the
     keys.
   - Landing on a sentinel then means "left the page" and focus ends on body, as in a
     real browser.
 - No renderer, contract, IPC, timer, or provenance changes. Desktop-only by nature: web
   and mobile have no webview guests, and the server browser is headless. The focused-guest
   path (a human working in the guest) is unchanged.
 - Known edges to check: a modal <dialog>/inert making sentinels unfocusable (they may need
   to go in the top-layer element), page focus-trap libraries, Tab into a cross-site OOPIF
   inside the guest (frame focus may move WebContents focus on its own: unverified), and
   the page seeing the brief DOM mutation.
 - If native testing shows escapes beyond the main-frame boundary, the fallback is an
   explicit, scoped refusal for unfocused Tab, like CROSS_SITE_FOCUS_MESSAGE. That narrows
   the spec and needs maintainer sign-off. A silent drop is never acceptable.
 - Do not resurrect a renderer focus-restoration guard. That is the original's 176-line
   keyboard heuristic machinery, and it still blurs.

TESTS
The 2 guards (GuestInput.test.ts:55-70) fail on main (rerun: 2 fail, 7 pass). They pass for
a silent drop, and they fail for the recommended design, which still sends native Tab. They
encode a design choice, not behavior: keep them local as a red marker only, and replace them
when a design is chosen. For the sentinel design, one useful non-native test exists: run the
containment script in a real Chromium page in the repo's *.browser.test setup and assert that
Tab at the last control / Shift+Tab at the first never moves document focus out of the page.
That proves the script, not Electron TakeFocus. One test, not 36.

ACCEPTANCE (native Electron, current main rebuild; the bar for publish/PR body)
0. First reproduce on current main: host composer focused in chat B, agent Tab/Shift+Tab
   in hidden chat A at its last/first control. If it doesn't reproduce, close as fixed.
1. Forward and backward traversal across real controls, shadow DOM, same-site and
   cross-site iframes, with repeats at the boundaries. Host activeElement must stay the
   composer, with zero composer blur events (instrument blur, not only final focus).
2. Type into the composer during repeated agent Tabs, with an active IME composition
   in progress: text and caret land where intended.
3. Agent focus state after the boundary is sane: body, no sentinel left in the DOM.
4. A human clicking or Tabbing within the guest, and host Tab navigation, behave natively.
5. Visible guest, hidden cross-chat guest, multiple guests, navigation/remount mid-press.
6. macOS at least; Windows/Linux if available (focus/IME differ).

LIMITS
No native display; nothing run natively; Chromium behavior is from source reasoning only.
Couldn't diff ab6be13..25aa9fc (rate limit). Only GuestInput.test.ts was run.
No edits, commits, pushes, or comments.
