Resolve mobile browser address sync before commit (#3228)

This commit is contained in:
Neil 2026-05-30 19:25:34 -07:00 committed by GitHub
parent 73e17b947c
commit 3dbc38f4f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 89 additions and 3 deletions

View File

@ -47,6 +47,7 @@ import {
type BrowserZoomState
} from './browser-touch-geometry'
import { displayBrowserUrl, normalizeBrowserUrl } from './browser-url'
import { resolveMobileBrowserAddressSync } from './mobile-browser-address-sync'
export type MobileBrowserTab = {
type: 'browser'
@ -135,6 +136,10 @@ export function MobileBrowserPane({
const cachedInitialFrame = peekCachedBrowserFrame(cacheKey)
const [addressValue, setAddressValue] = useState(displayBrowserUrl(tab.url))
const [addressFocused, setAddressFocused] = useState(false)
const [addressSyncState, setAddressSyncState] = useState({
focused: false,
url: tab.url
})
const [keyboardValue, setKeyboardValue] = useState('')
const [frameUri, setFrameUri] = useState<string | null>(cachedInitialFrame?.uri ?? null)
const [frameMetadata, setFrameMetadata] = useState<BrowserScreencastFrameMetadata | null>(
@ -222,11 +227,18 @@ export function MobileBrowserPane({
}
}, [worktreeId])
useEffect(() => {
if (!addressFocused) {
const addressSync = resolveMobileBrowserAddressSync(addressSyncState, {
focused: addressFocused,
url: tab.url
})
if (addressSync.nextState !== addressSyncState) {
setAddressSyncState(addressSync.nextState)
if (addressSync.shouldSyncValue) {
// Why: keep browser stream/goto address updates intact, but avoid a
// stale post-blur paint when the tab URL is the source of truth.
setAddressValue(displayBrowserUrl(tab.url))
}
}, [addressFocused, tab.url])
}
useLayoutEffect(() => {
// Why: gesture and stream handlers need committed values before passive

View File

@ -0,0 +1,54 @@
import { describe, expect, it } from 'vitest'
import { resolveMobileBrowserAddressSync } from './mobile-browser-address-sync'
describe('resolveMobileBrowserAddressSync', () => {
it('syncs the tab URL when the input is not focused', () => {
const result = resolveMobileBrowserAddressSync(
{ focused: false, url: 'https://old.example/' },
{ focused: false, url: 'https://new.example/' }
)
expect(result).toEqual({
nextState: { focused: false, url: 'https://new.example/' },
shouldSyncValue: true
})
})
it('defers tab URL sync while the user is editing', () => {
const result = resolveMobileBrowserAddressSync(
{ focused: false, url: 'https://old.example/' },
{ focused: true, url: 'https://new.example/' }
)
expect(result).toEqual({
nextState: { focused: true, url: 'https://new.example/' },
shouldSyncValue: false
})
})
it('syncs the latest tab URL when editing ends', () => {
const result = resolveMobileBrowserAddressSync(
{ focused: true, url: 'https://new.example/' },
{ focused: false, url: 'https://new.example/' }
)
expect(result).toEqual({
nextState: { focused: false, url: 'https://new.example/' },
shouldSyncValue: true
})
})
it('preserves externally updated address text when focus and tab URL are unchanged', () => {
const previous = { focused: false, url: 'https://new.example/' }
const result = resolveMobileBrowserAddressSync(previous, {
focused: false,
url: 'https://new.example/'
})
expect(result).toEqual({
nextState: previous,
shouldSyncValue: false
})
})
})

View File

@ -0,0 +1,20 @@
export type MobileBrowserAddressSyncState = {
focused: boolean
url: string
}
export type MobileBrowserAddressSyncResult = {
nextState: MobileBrowserAddressSyncState
shouldSyncValue: boolean
}
export function resolveMobileBrowserAddressSync(
previous: MobileBrowserAddressSyncState,
next: MobileBrowserAddressSyncState
): MobileBrowserAddressSyncResult {
const changed = previous.focused !== next.focused || previous.url !== next.url
return {
nextState: changed ? next : previous,
shouldSyncValue: changed && !next.focused
}
}