Skip to content

Commit af4cf71

Browse files
committed
fix(desktop-browser): align recovery interaction paths
1 parent 7ef6aa7 commit af4cf71

5 files changed

Lines changed: 49 additions & 9 deletions

File tree

apps/desktop/src/main/browser-agent/driver.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,14 @@ describe('executeTool', () => {
455455
vi.mocked(contents.loadURL).mockClear()
456456
await driver.handlePanelAction('chat-test', { action: 'reload' })
457457
expect(contents.loadURL).toHaveBeenCalledWith(failedUrl)
458+
459+
vi.mocked(contents.loadURL).mockClear()
460+
await driver.executeTool('chat-test', 'browser_go_back', {})
461+
expect(session.pageIssueForContents(contents)).toBeUndefined()
462+
expect(session.canGoForward(contents)).toBe(true)
463+
464+
await driver.executeTool('chat-test', 'browser_go_forward', {})
465+
expect(contents.loadURL).toHaveBeenCalledWith(failedUrl)
458466
})
459467

460468
it('forces fill availability to replay on scope activation and tab switches', async () => {

apps/desktop/src/main/browser-agent/driver.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1985,19 +1985,20 @@ async function executeToolInner(
19851985
case 'browser_go_forward': {
19861986
invalidateSnapshot()
19871987
const contents = session.requireAutomationTab().view.webContents
1988-
const history = contents.navigationHistory
19891988
assertCurrentExecution()
19901989
let completion: Promise<void>
19911990
if (tool === 'browser_go_back') {
1992-
if (!history.canGoBack()) throw new ToolError('Cannot go back — no earlier history entry.')
1991+
if (!session.canGoBack(contents)) {
1992+
throw new ToolError('Cannot go back — no earlier history entry.')
1993+
}
19931994
completion = waitForLoadComplete(contents, NAVIGATION_TIMEOUT_MS)
1994-
history.goBack()
1995+
session.goBack(contents)
19951996
} else {
1996-
if (!history.canGoForward()) {
1997+
if (!session.canGoForward(contents)) {
19971998
throw new ToolError('Cannot go forward — no later history entry.')
19981999
}
19992000
completion = waitForLoadComplete(contents, NAVIGATION_TIMEOUT_MS)
2000-
history.goForward()
2001+
session.goForward(contents)
20012002
}
20022003
return await navigationResult(contents, completion)
20032004
}

apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-page-issue.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ describe('browserPageIssueCopy', () => {
9090
url: 'http://localhost:3004',
9191
},
9292
onReload,
93+
focusRecovery: true,
9394
})
9495
)
9596
})
@@ -105,4 +106,28 @@ describe('browserPageIssueCopy', () => {
105106
act(() => root.unmount())
106107
container.remove()
107108
})
109+
110+
it('does not move focus when its browser resource is hidden', () => {
111+
const container = document.createElement('div')
112+
const sentinel = document.createElement('button')
113+
document.body.append(container, sentinel)
114+
sentinel.focus()
115+
const root = createRoot(container)
116+
117+
act(() => {
118+
root.render(
119+
createElement(BrowserPageIssueView, {
120+
issue: { kind: 'unresponsive', url: 'https://example.com' },
121+
onReload: vi.fn(),
122+
focusRecovery: false,
123+
})
124+
)
125+
})
126+
127+
expect(document.activeElement).toBe(sentinel)
128+
129+
act(() => root.unmount())
130+
container.remove()
131+
sentinel.remove()
132+
})
108133
})

apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-page-issue.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { CircleAlert, Globe, RefreshCw } from '@sim/emcn/icons'
66
interface BrowserPageIssueProps {
77
issue: BrowserPageIssue
88
onReload: () => void
9+
focusRecovery: boolean
910
}
1011

1112
interface BrowserPageIssueCopy {
@@ -124,13 +125,14 @@ export function browserPageIssueCopy(issue: BrowserPageIssue): BrowserPageIssueC
124125
}
125126
}
126127

127-
export function BrowserPageIssueView({ issue, onReload }: BrowserPageIssueProps) {
128+
/** Replaces a hidden native page and optionally claims renderer focus for keyboard recovery. */
129+
export function BrowserPageIssueView({ issue, onReload, focusRecovery }: BrowserPageIssueProps) {
128130
const headingRef = useRef<HTMLHeadingElement>(null)
129131
const copy = browserPageIssueCopy(issue)
130132

131133
useEffect(() => {
132-
headingRef.current?.focus()
133-
}, [issue])
134+
if (focusRecovery) headingRef.current?.focus()
135+
}, [focusRecovery, issue])
134136

135137
const Icon = issue.kind === 'load-error' ? Globe : CircleAlert
136138

apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-session.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -932,8 +932,11 @@ export function BrowserSession({
932932
// Keep the page's exact captured frame underneath it while it is open so
933933
// pointer events reach the Sim popover instead of the WebContentsView.
934934
useEffect(() => {
935+
if (hasPageIssue) {
936+
void closeOverlay('suggestions')
937+
return
938+
}
935939
if (suggestions.length > 0) {
936-
if (hasPageIssue) return
937940
void requestOverlay('suggestions', () => {})
938941
return
939942
}
@@ -1446,6 +1449,7 @@ export function BrowserSession({
14461449
{pageState?.issue && (
14471450
<BrowserPageIssueView
14481451
issue={pageState.issue}
1452+
focusRecovery={visible}
14491453
onReload={() => sendBrowserPanelAction('reload', {}, scopeId)}
14501454
/>
14511455
)}

0 commit comments

Comments
 (0)