Tests-only takeover handoff

Target: [#15091](https://github.com/pingdotgg/t3code/pull/15091)
Head repository: [pingdotgg/t3code](https://github.com/pingdotgg/t3code)
Original branch: t3code/fix-browser-focus-stealing
Original head: [aec2d97e389f](https://github.com/pingdotgg/t3code/commit/aec2d97e389f40a9041f7fa0398feaccb456ad92)
Author: bmdavis419. Base branch: main. maintainerCanModify: false (same-repository head).
Worktree: /home/ubuntu/wt/terminal-preview-21
Replacement layer: terminal-preview-21/tab-focus
Starting main: [ab6be13d438a](https://github.com/pingdotgg/t3code/commit/ab6be13d438abeace7617e8d789d59cf2e880f42)

Scope and spec
Automated Tab/Shift+Tab must traverse the preview page without moving host composer focus,
including first/last page boundaries and delayed native focus destinations. Continued
host typing, caret editing, and IME composition must remain intact; intentional human
navigation/takeover must work. Tests-only scope: no production changes, agents, dev servers,
browsers, pushes, or comments. Original branch preserved.

Read the full takeover skill, worktree AGENTS.md, gh-stack skill, and original PR diff.
The old renderer PreviewAutomationHosts handler and previewTabFocus helper are absent
on this base. Discarded the original 36-case helper suite by not transplanting it;
there was no file to delete from the replacement layer.

Architecture and test limits
DesktopBrowserHost.relayFor routes page commands through GuestInput.sendPageCommand;
child-session commands go straight to the debugger. For an unfocused guest,
GuestInput checks focusedDocument, translates keys, calls webContents.sendInputEvent,
and evaluates page editing commands before replying. That evaluation does not provide
a host-focus protection handshake or native destination completion signal.
Manager handles human-input IPC and publishes controller state; HostedBrowserWebview
replays webview focus as a synthetic pointerdown to dismiss popups. These paths are
not integrated with sendPageCommand focus protection.

The current Node mocks cannot faithfully reproduce Electron guest/host focus traversal,
delayed native destinations, host selection, or IME. A jsdom test that manually focuses
a toolbar would test its fixture/helper, not the current production input path.
Accordingly, added the explicitly permitted fallback: one parameterized contract guard
with two cases (Tab and Shift+Tab) in existing GuestInput.test.ts. It invokes real
sendPageCommand and rejects dispatching raw native Tab while the guest is unfocused;
it also rejects falling back to focus-routed CDP keys. Both fail on unchanged main
production code. This is evidence of the unsafe input route, NOT a reproduced native
focus loss or proof of correct traversal.

These guards are deliberately conservative: they reject even a future protected native
Tab route until that protection has an observable seam. They can pass if Tab is silently
dropped, and do not assert advancement to a page element. Do not accept a no-op, blanket
Tab rejection, or a synthetic KeyboardEvent without default traversal as a fix.

Suggested minimal seam, if implementation retains native Tab: at the existing desktop
host/renderer boundary, expose a request/acknowledgment that arms host focus protection
before dispatch. Its lifetime must cover delayed native focus and intentional human
release, not merely the CDP receipt. Adapt these two guards to observe protection at
dispatch if that design is chosen; no API or helper was invented/implemented in this
phase. Native integration must still prove the behavior regardless of chosen design.

Counts
Original PR helper suite: 36 -> 2 new contract cases, in one existing module test file.
GuestInput.test.ts: 7 existing + 2 added = 9 (7 pass, 2 expected failures).
Manager.test.ts: 104 existing, unchanged, all pass.
DesktopBrowserHost.test.ts: 4 existing, unchanged, all pass.
Focused baseline: 115 passed across 3 files.
Focused final: 117 total, 115 passed, 2 expected failures across 3 files.
No repository-wide test count or checks claimed.

Commands (all shell invocations began with export PATH="$HOME/.local/bin:$PATH")
Initial `vp test run ...` failed with exit 127: vp not installed globally. Used the
already installed worktree binary; no package installation or environment changes.
From /home/ubuntu/wt/terminal-preview-21:
1. ./node_modules/.bin/vp test run apps/desktop/src/preview/GuestInput.test.ts apps/desktop/src/preview/Manager.test.ts apps/desktop/src/preview/DesktopBrowserHost.test.ts
   Exit 0 before edits: 115 passed. Log: 15091-tests-baseline.log.
2. ./node_modules/.bin/vp fmt apps/desktop/src/preview/GuestInput.test.ts
   Exit 0, one file formatted.
3. ./node_modules/.bin/vp test run apps/desktop/src/preview/GuestInput.test.ts apps/desktop/src/preview/Manager.test.ts apps/desktop/src/preview/DesktopBrowserHost.test.ts --reporter=verbose
   Exit 1 after edits: 115 passed, 2 failed as expected. Log: 15091-tests-red.log.
4. ./node_modules/.bin/vp lint apps/desktop/src/preview/GuestInput.test.ts
   Exit 0. Log: 15091-tests-lint.log.
5. From apps/desktop: ../../node_modules/.bin/tsc-rs --noEmit
   Exit 0. Two existing suggestions in DesktopClerk.test.ts and DesktopWebLinks.test.ts;
   no errors. Log: 15091-tests-typecheck.log.
6. git diff --check
   Exit 0.

Expected red output
 FAIL  apps/desktop/src/preview/GuestInput.test.ts > preview guest input > keeps unfocused Tab off unprotected native focus routes
AssertionError: expected "vi.fn()" to not be called with arguments: [ ObjectContaining{…} ]

Received:

  1st vi.fn() call:

  [
-   ObjectContaining {
+   {
      "keyCode": "Tab",
+     "modifiers": [],
      "type": "rawKeyDown",
    },
  ]

  2nd vi.fn() call:

  [
-   ObjectContaining {
+   {
      "keyCode": "Tab",
-     "type": "rawKeyDown",
+     "modifiers": [],
+     "type": "keyUp",
    },
  ]


Number of calls: 2

 ❯ apps/desktop/src/preview/GuestInput.test.ts:66:50
     64|     // Contract guard only: native Tab can escape at the page boundary…
     65|     // it needs an observable host-focus guard; these mocks cannot pro…
     66|     expect(guest.webContents.sendInputEvent).not.toHaveBeenCalledWith(
       |                                                  ^
     67|       expect.objectContaining({ type: "rawKeyDown", keyCode: "Tab" }),
     68|     );

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/2]⎯

 FAIL  apps/desktop/src/preview/GuestInput.test.ts > preview guest input > keeps unfocused Shift+Tab off unprotected native focus routes
AssertionError: expected "vi.fn()" to not be called with arguments: [ ObjectContaining{…} ]

Received:

  1st vi.fn() call:

  [
-   ObjectContaining {
+   {
      "keyCode": "Tab",
+     "modifiers": [
+       "shift",
+     ],
      "type": "rawKeyDown",
    },
  ]

  2nd vi.fn() call:

  [
-   ObjectContaining {
+   {
      "keyCode": "Tab",
-     "type": "rawKeyDown",
+     "modifiers": [
+       "shift",
+     ],
+     "type": "keyUp",
    },
  ]


Number of calls: 2

 ❯ apps/desktop/src/preview/GuestInput.test.ts:66:50
     64|     // Contract guard only: native Tab can escape at the page boundary…
     65|     // it needs an observable host-focus guard; these mocks cannot pro…
     66|     expect(guest.webContents.sendInputEvent).not.toHaveBeenCalledWith(
       |                                                  ^
     67|       expect.objectContaining({ type: "rawKeyDown", keyCode: "Tab" }),
     68|     );

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/2]⎯


 Test Files  1 failed | 2 passed (3)
      Tests  2 failed | 115 passed (117)
   Start at  04:50:24
   Duration  2.62s (transform 50%, import 27%, tests 15%, setup 8%, worker 1%)

  Transform  transforming modules took 2.17s · 50% of tracked time, re-done on every run
             persist transforms across runs with fsModuleCache: true
             learn more: https://vitest.dev/guide/improving-performance#caching-between-reruns


Native acceptance: BLOCKED / NOT RUN
DISPLAY and WAYLAND_DISPLAY are both unset. No native verification, browser run, UI
screenshot, timing proof, or focus preservation claim is made. Required acceptance:
real Electron production bridge with host composer focused; forward/backward traversal
within page and at last/first focus boundaries; delayed destinations after command reply;
continued typing, caret changes, and active IME; legitimate host/guest human takeover.
Include hidden cross-chat and visible guests. Do not treat a passing Node suite as this
acceptance. Web/mobile changes and extra host tests were not needed for this narrow
contract guard; provider/transport variants share the desktop page input route.

Only apps/desktop/src/preview/GuestInput.test.ts is changed (17 inserted lines).

Local test commit (not pushed): bd08c54d0280e225139ed9f7d81cc1a801c2b431
Title: test(desktop): guard unfocused preview Tab input routes
Post-commit worktree clean; original branch head unchanged. Only target PR linked to thread.
