From e8a0dbc969ccac09653513d055f444f525429dd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Titsworth-Morin?= Date: Sat, 4 Apr 2026 12:26:03 +0000 Subject: [PATCH 1/5] fix: remote browser opens in new tab with project chat button - BrowserSidecar component opens Neko URL in a new tab instead of iframe - Add "Remote Browser" button to SessionHeader in project chat view - useBrowserSidecar hook supports session-mode (projectId + sessionId) Co-Authored-By: Claude Opus 4.6 --- apps/web/src/components/BrowserSidecar.tsx | 57 +++++++------------ .../project-message-view/SessionHeader.tsx | 49 +++++++++++++++- apps/web/src/hooks/useBrowserSidecar.ts | 6 +- 3 files changed, 74 insertions(+), 38 deletions(-) diff --git a/apps/web/src/components/BrowserSidecar.tsx b/apps/web/src/components/BrowserSidecar.tsx index cf3b2b59..76b15475 100644 --- a/apps/web/src/components/BrowserSidecar.tsx +++ b/apps/web/src/components/BrowserSidecar.tsx @@ -1,6 +1,6 @@ -import { Alert,Button } from '@simple-agent-manager/ui'; -import { Globe, Loader2, Monitor,X } from 'lucide-react'; -import { type FC, useCallback, useEffect,useState } from 'react'; +import { Alert, Button } from '@simple-agent-manager/ui'; +import { ExternalLink, Globe, Loader2, X } from 'lucide-react'; +import { type FC, useCallback } from 'react'; import { useBrowserSidecar } from '../hooks/useBrowserSidecar'; @@ -19,8 +19,8 @@ interface BrowserSidecarWorkspaceProps { type BrowserSidecarProps = BrowserSidecarSessionProps | BrowserSidecarWorkspaceProps; /** - * BrowserSidecar provides a button to start/stop a Neko remote browser sidecar - * and an iframe to view the browser stream when running. + * BrowserSidecar provides controls to start/stop a Neko remote browser sidecar + * and opens it in a new browser tab when running. * * Supports two modes: * - Session mode: requires projectId + sessionId (used in project chat) @@ -32,7 +32,6 @@ export const BrowserSidecar: FC = (props) => { : { projectId: props.projectId!, sessionId: props.sessionId! }; const { status, isLoading, error, start, stop } = useBrowserSidecar(hookOptions); - const [showViewer, setShowViewer] = useState(false); const handleStart = useCallback(async () => { const opts = { @@ -41,12 +40,20 @@ export const BrowserSidecar: FC = (props) => { devicePixelRatio: window.devicePixelRatio || 1, isTouchDevice: 'ontouchstart' in window || navigator.maxTouchPoints > 0, }; - await start(opts); - setShowViewer(true); + const result = await start(opts); + // Open in new tab once URL is available + if (result?.url) { + window.open(result.url, '_blank', 'noopener,noreferrer'); + } }, [start]); + const handleOpen = useCallback(() => { + if (status?.url) { + window.open(status.url, '_blank', 'noopener,noreferrer'); + } + }, [status?.url]); + const handleStop = useCallback(async () => { - setShowViewer(false); await stop(); }, [stop]); @@ -54,13 +61,6 @@ export const BrowserSidecar: FC = (props) => { const isRunning = sidecarStatus === 'running'; const isStarting = sidecarStatus === 'starting'; - // Reset viewer when sidecar transitions to off or error - useEffect(() => { - if (sidecarStatus === 'off' || sidecarStatus === 'error') { - setShowViewer(false); - } - }, [sidecarStatus]); - return (
{/* Control buttons */} @@ -74,7 +74,7 @@ export const BrowserSidecar: FC = (props) => { aria-label="Start remote browser" >