fix(lint): move the doc preview guest-focus handoff into its own hook (#28110)

HtmlDocPreview.tsx reached 403 lines on main (#27608 + #27875), over the
400-line limit for .tsx, so lint and the pre-commit hook fail on any
branch that merges main. The focus handoff effect moves unchanged into
use-doc-preview-guest-focus.ts.

Co-authored-by: Orca Worker <orca-worker@localhost>
This commit is contained in:
OrcaWin
2026-10-12 16:58:22 -07:00
committed by GitHub
co-authored by Orca Worker
parent 845b723258
commit 5e83ca0edd
2 changed files with 82 additions and 54 deletions
@@ -34,12 +34,11 @@ import {
import { DocPreviewToolbar } from './doc-preview-toolbar'
import { buildDocPreviewShareControl } from './doc-preview-artifact-share'
import { useDocPreviewWebviewHistory } from './doc-preview-webview-history'
import { useDocPreviewGuestFocus } from './use-doc-preview-guest-focus'
import { useDocPreviewGuestTools } from './use-doc-preview-guest-tools'
type PreviewState = 'loading' | 'ready' | 'unavailable'
/** Frames a preview keeps offering focus to a guest that is still attaching. */
const GUEST_FOCUS_FRAMES = 10
const MAX_ASSET_FAILURES = 50
export function HtmlDocPreview({
@@ -301,58 +300,13 @@ export function HtmlDocPreview({
.recordWorkspaceDocVisit({ kind: 'workspace-doc', worktreeId, filePath }, null)
}, [filePath, worktreeId])
// Why the guest is handed focus: a preview has no address bar to make the usual handoff, so a
// surfaced document would otherwise look active while its keyboard and link input land elsewhere.
useEffect(() => {
if (!holdsGuestFocus || state !== 'ready') {
return
}
let frameId = 0
let attempts = 0
let claimedOnly = false
const focusGuest = (): void => {
const webview = webviewRef.current
attempts += 1
// Why a re-offer yields: it is a handoff for focus nothing else wanted, and the reader
// pressing a tab lands here first. Taking it back would fight them for the keyboard, which
// is what shut the tab strip while a preview was open.
if (
claimedOnly &&
document.activeElement !== document.body &&
document.activeElement !== webview
) {
return
}
try {
webview?.focus()
} catch {
// Why swallowed: WebViewElement.focus() reads null internals once the guest is destroyed.
return
}
// Why retried: the guest takes focus only once it is attached and laid out, a frame or two
// after it reports ready.
if (document.activeElement !== webview && attempts < GUEST_FOCUS_FRAMES) {
frameId = window.requestAnimationFrame(focusGuest)
}
}
const offerFocus = (yieldToOtherClaims: boolean): void => {
window.cancelAnimationFrame(frameId)
attempts = 0
claimedOnly = yieldToOtherClaims
frameId = window.requestAnimationFrame(focusGuest)
}
// Why assertive: the reader just made this preview their surface, so the handoff is the point.
offerFocus(false)
// Why re-offered on the window's own focus: another app taking the front takes focus out of the
// guest, and coming back puts it on the embedder. Nothing hands it on, so the route out of the
// preview would stay shut until something remounted it.
const reofferFocus = (): void => offerFocus(true)
window.addEventListener('focus', reofferFocus)
return () => {
window.removeEventListener('focus', reofferFocus)
window.cancelAnimationFrame(frameId)
}
}, [holdsGuestFocus, previewId, remintCount, state])
useDocPreviewGuestFocus({
webviewRef,
holdsGuestFocus,
ready: state === 'ready',
previewId,
remintCount
})
// Why: a grant is pinned to the owner ids resolved when it was minted, so after a pairing or
// SSH reconnect the old one reads nothing and reloading the guest would just refetch the
@@ -0,0 +1,74 @@
import { useEffect, type MutableRefObject } from 'react'
/** Frames a preview keeps offering focus to a guest that is still attaching. */
const GUEST_FOCUS_FRAMES = 10
/**
* Hands a ready preview's guest the keyboard. A preview has no address bar to make the usual
* handoff, so a surfaced document would otherwise look active while its keyboard and link input
* land elsewhere. `remintCount` re-runs the handoff when a re-mint replaces the guest.
*/
export function useDocPreviewGuestFocus({
webviewRef,
holdsGuestFocus,
ready,
previewId,
remintCount
}: {
webviewRef: MutableRefObject<Electron.WebviewTag | null>
holdsGuestFocus: boolean
ready: boolean
previewId: string
remintCount: number
}): void {
useEffect(() => {
if (!holdsGuestFocus || !ready) {
return
}
let frameId = 0
let attempts = 0
let claimedOnly = false
const focusGuest = (): void => {
const webview = webviewRef.current
attempts += 1
// Why a re-offer yields: it is a handoff for focus nothing else wanted, and the reader
// pressing a tab lands here first. Taking it back would fight them for the keyboard, which
// is what shut the tab strip while a preview was open.
if (
claimedOnly &&
document.activeElement !== document.body &&
document.activeElement !== webview
) {
return
}
try {
webview?.focus()
} catch {
// Why swallowed: WebViewElement.focus() reads null internals once the guest is destroyed.
return
}
// Why retried: the guest takes focus only once it is attached and laid out, a frame or two
// after it reports ready.
if (document.activeElement !== webview && attempts < GUEST_FOCUS_FRAMES) {
frameId = window.requestAnimationFrame(focusGuest)
}
}
const offerFocus = (yieldToOtherClaims: boolean): void => {
window.cancelAnimationFrame(frameId)
attempts = 0
claimedOnly = yieldToOtherClaims
frameId = window.requestAnimationFrame(focusGuest)
}
// Why assertive: the reader just made this preview their surface, so the handoff is the point.
offerFocus(false)
// Why re-offered on the window's own focus: another app taking the front takes focus out of the
// guest, and coming back puts it on the embedder. Nothing hands it on, so the route out of the
// preview would stay shut until something remounted it.
const reofferFocus = (): void => offerFocus(true)
window.addEventListener('focus', reofferFocus)
return () => {
window.removeEventListener('focus', reofferFocus)
window.cancelAnimationFrame(frameId)
}
}, [holdsGuestFocus, previewId, ready, remintCount, webviewRef])
}