fix(web): preserve composer focus during preview presses - #9530
Conversation
Alternatives consideredI reviewed the two earlier open approaches and then verified the behavior in the real Electron desktop app:
The integrated Electron pass also caught and corrected an earlier guard in this PR: native Tab dispatch can move focus to another connected host button, so restoration must not be conditional on |
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This changes preview press behavior across the web renderer, desktop native focus handling, and the IPC contract, adding nontrivial focus tracking, concurrency coordination, cancellation cleanup, and interruption reporting. The cross-layer runtime behavior is substantially more involved than a small isolated fix and merits human review. You can add or adjust custom eligibility rules. Learn more. |
e7a5459 to
c88499a
Compare
Dismissing prior approval to re-evaluate c88499a
Live T3 Connect validationI also exercised this through the hosted web client against a real connected environment:
Scope note: the hosted web right panel explicitly reports that the Browser surface is only available in the desktop app. This validates the T3 Connect/relay path and hosted-composer behavior, but it does not execute the Electron-specific renderer focus wrapper changed by this PR; that path is covered by the separate integrated Electron before/after pass. The live browser run also exposed a separate preview-driver quirk: Tab did not advance to the intended form field, so the agent corrected the two fields explicitly before the final snapshot. That is distinct from this PR's host-composer focus restoration and is not hidden by the successful final values. |
Prevent preview automation from reclaiming a stale composer or native renderer focus after the user takes control during an awaited press. Report native focus disposition and interruptions through the typed IPC contract, and cover renderer, desktop, and response serialization behavior with focused tests. Model: GPT-5 Harness: Codex CLI
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 6da3508. Configure here.
Treat a successful press as natively restored when the previously focused renderer still owns focus after the operation. This lets the host restore its DOM focus without issuing a redundant native focus call, while different focus owners remain preserved. Model: GPT-5 Harness: Codex CLI
Run preview press key-up and focus-emulation cleanup in an uninterruptible region so cancellation cannot leave Chromium with held input or stale focus emulation. Add deterministic Deferred-backed cancellation coverage at the cleanup boundary. Model: GPT-5 Harness: Codex CLI
|
This PR is superseded by the two focused draft PRs: Both branches are independently based on |

Problem
preview_presstemporarily focuses the preview guest WebContents so Electron can deliver native keyboard input. Restoring the outer renderer afterward did not restore its active DOM element, interrupting users typing in either the current thread or another thread.Fix
Capture the renderer's meaningful active element around
preview_pressand restore it after success or failure while that element remains connected.Restoration is best-effort, avoids scrolling, skips detached elements, and remains scoped to the press path that transfers native focus.
Testing
./node_modules/.bin/vp test run apps/web/src/lib/previewAutomationFocus.test.ts— 4 tests passed./node_modules/.bin/vp lint --report-unused-disable-directives apps/web/src/lib/previewAutomationFocus.ts apps/web/src/lib/previewAutomationFocus.test.ts./node_modules/.bin/vp fmt apps/web/src/lib/previewAutomationFocus.ts apps/web/src/lib/previewAutomationFocus.test.ts --check./node_modules/.bin/vp run -F @t3tools/web typecheckgit diff --checkcomposer-editorto a host button and dropping the next keystroke; verified the fixed branch keepscomposer-editorfocused and accepts the next keystrokeFixes #5792
Implemented with GPT-5.6 Luna and reviewed with GPT-5.6 Sol in T3 Code through the Codex harness.
Note
Medium Risk
Changes native focus/keyboard cleanup and IPC contract for
press, with nuanced restore vs preserve behavior; mistakes could leave stuck keys or steal composer focus, but scope is limited to preview automation press.Overview
Preview automation
pressnow reports whether native focus was restored or preserved, and surfaces human takeover as an explicit interrupted outcome instead of a silentvoid.Contracts add
DesktopPreviewAutomationPressResult(completedwithrestored/preserved, orinterrupted). DesktopPreviewManager.automationPressreturns that disposition: cleanup runs uninterruptibly (key-up and focus emulation off even on fiber interrupt), native restore is skipped when the app is unfocused or focus moved elsewhere, and human control during press still fails withPreviewAutomationControlInterruptedErrorwithout restoring renderer focus. IPC maps that error to{ status: "interrupted" }viaautomationPressResult.On web,
preview_pressruns inside newwithPreviewAutomationFocus, which serializes focus transactions, tracks trusted user focus intent, and restores the prior DOM element only when the desktop result saysfocusDisposition === "restored". Interrupted presses throwPreviewAutomationControlInterruptedHostErrorand serialize toPreviewAutomationControlInterruptedErrorfor automation clients.Breaking:
DesktopPreviewBridge.automation.pressand the preview IPC handler returnDesktopPreviewAutomationPressResultinstead ofvoid.Reviewed by Cursor Bugbot for commit d293589. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Preserve composer focus during preview automation presses
performAutomationPressnow inspects the focusedWebContentsandBrowserWindowduring cleanup and restores the prior renderer only when automation still owns focus or the prior renderer already has it; human-control takeover completes cleanup before re-emitting the interruption.withPreviewAutomationFocusin previewAutomationFocus.ts to serialize concurrent focus transactions, track trusted user focus vs synthetic input viatrackFocusIntent, and restore the prior host element only when the document stays focused and no newer transaction supersedes it.Promise<void>toPromise<DesktopPreviewAutomationPressResult>in ipc.ts, returning either a completed result withfocusDispositionor an interrupted status; the web host converts interrupted results into the newPreviewAutomationControlInterruptedHostError.DesktopPreviewBridge.automation.pressandPreviewManager.automationPressnow return a typedDesktopPreviewAutomationPressFocusDispositionorDesktopPreviewAutomationPressResultinstead ofvoid.Macroscope summarized d293589.