diff --git a/apps/app/src/app/lib/desktop.ts b/apps/app/src/app/lib/desktop.ts index 9fe81b71d..18a0b676e 100644 --- a/apps/app/src/app/lib/desktop.ts +++ b/apps/app/src/app/lib/desktop.ts @@ -50,21 +50,9 @@ declare global { forward?: () => Promise; reload?: () => Promise; setBounds?: (bounds: { x: number; y: number; width: number; height: number }) => Promise; - getState?: () => Promise<{ - url: string; - title: string; - canGoBack: boolean; - canGoForward: boolean; - isLoading: boolean; - } | null>; + getState?: () => Promise<{ url: string; title: string; canGoBack: boolean; canGoForward: boolean; isLoading: boolean } | null>; destroy?: () => Promise; - onStateChange?: (callback: (state: { - url: string; - title: string; - canGoBack: boolean; - canGoForward: boolean; - isLoading: boolean; - }) => void) => () => void; + onStateChange?: (callback: (state: { url: string; title: string; canGoBack: boolean; canGoForward: boolean; isLoading: boolean }) => void) => () => void; }; meta?: { initialDeepLinks?: string[]; diff --git a/apps/app/src/react-app/domains/connections/store.ts b/apps/app/src/react-app/domains/connections/store.ts index 2505c0607..c20e19d4d 100644 --- a/apps/app/src/react-app/domains/connections/store.ts +++ b/apps/app/src/react-app/domains/connections/store.ts @@ -499,14 +499,12 @@ export function createConnectionsStore(options: { } // For chrome-devtools in Electron, resolve the bundled binary so we - // don't need npx/npm at runtime. + // don't need npx/npm at runtime. Only carry over -- prefixed flags + // from the original command (skip npx flags like -y). let resolvedCommand = entry.command; if (slug === CHROME_DEVTOOLS_MCP_ID && isElectronRuntime()) { const bundled = await resolveChromeDevtoolsMcpCommand(); - // Preserve any extra args (e.g. --autoConnect) from the original - const extraArgs = entry.command.filter( - (arg) => arg.startsWith("--") || arg.startsWith("-"), - ); + const extraArgs = entry.command.filter((arg) => arg.startsWith("--")); resolvedCommand = [...bundled, ...extraArgs]; } mcpEntryConfig["command"] = resolvedCommand; @@ -589,7 +587,7 @@ export function createConnectionsStore(options: { } : { type: "local" as const, - command: entry.command!, + command: (mcpEntryConfig["command"] as string[]) ?? entry.command!, enabled: true, ...(mcpEnvironment ? { environment: mcpEnvironment } : {}), }; diff --git a/apps/app/src/react-app/domains/session/browser/browser-panel.tsx b/apps/app/src/react-app/domains/session/browser/browser-panel.tsx index 11ef84668..9d58a11c6 100644 --- a/apps/app/src/react-app/domains/session/browser/browser-panel.tsx +++ b/apps/app/src/react-app/domains/session/browser/browser-panel.tsx @@ -1,13 +1,6 @@ /** @jsxImportSource react */ import { useCallback, useEffect, useRef, useState } from "react"; -import { - ArrowLeft, - ArrowRight, - Globe, - Loader2, - RotateCw, - X, -} from "lucide-react"; +import { ArrowLeft, ArrowRight, Globe, Loader2, RotateCw, X } from "lucide-react"; import { isElectronRuntime } from "../../../../app/utils"; type BrowserState = { @@ -18,119 +11,93 @@ type BrowserState = { isLoading: boolean; }; -type BrowserPanelProps = { - onClose: () => void; -}; +type BrowserPanelProps = { onClose: () => void }; -const EMPTY_STATE: BrowserState = { - url: "", - title: "", - canGoBack: false, - canGoForward: false, - isLoading: false, -}; +const EMPTY_STATE: BrowserState = { url: "", title: "", canGoBack: false, canGoForward: false, isLoading: false }; +const TOOLBAR_HEIGHT = 44; function getElectronBrowser() { if (!isElectronRuntime()) return null; return (window as Window).__OPENWORK_ELECTRON__?.browser ?? null; } +function computeBounds(el: HTMLElement) { + const rect = el.getBoundingClientRect(); + return { + x: Math.round(rect.x), + y: Math.round(rect.y + TOOLBAR_HEIGHT), + width: Math.round(rect.width), + height: Math.round(rect.height - TOOLBAR_HEIGHT), + }; +} + export function BrowserPanel({ onClose }: BrowserPanelProps) { const [state, setState] = useState(EMPTY_STATE); const [urlInput, setUrlInput] = useState(""); const [urlFocused, setUrlFocused] = useState(false); const panelRef = useRef(null); const urlInputRef = useRef(null); + const shownRef = useRef(false); // Subscribe to state changes from the main process useEffect(() => { const browser = getElectronBrowser(); if (!browser) return; - - const unsub = browser.onStateChange((newState: BrowserState) => { - setState(newState); - if (!urlFocused) { - setUrlInput(newState.url); - } + const unsub = browser.onStateChange?.((s: BrowserState) => { + setState(s); + if (!urlFocused) setUrlInput(s.url); }); - - // Get initial state - browser.getState().then((initial: BrowserState | null) => { - if (initial) { - setState(initial); - setUrlInput(initial.url); - } + browser.getState?.().then((s: BrowserState | null) => { + if (s) { setState(s); setUrlInput(s.url); } }); - return unsub; }, [urlFocused]); - // Show the browser view when the panel mounts, hide on unmount. - // Also update bounds when the panel resizes. + // Show the browser view when the panel mounts, keep bounds in sync, hide on unmount. useEffect(() => { const browser = getElectronBrowser(); if (!browser || !panelRef.current) return; - const updateBounds = () => { + const tryShow = () => { if (!panelRef.current) return; - const rect = panelRef.current.getBoundingClientRect(); - // The toolbar is ~44px, leave space at top for it - const toolbarHeight = 44; - const bounds = { - x: Math.round(rect.x), - y: Math.round(rect.y + toolbarHeight), - width: Math.round(rect.width), - height: Math.round(rect.height - toolbarHeight), - }; - browser.setBounds(bounds); + const bounds = computeBounds(panelRef.current); + if (bounds.width < 1 || bounds.height < 1) return; // not laid out yet + if (!shownRef.current) { + browser.show?.(bounds); + shownRef.current = true; + } else { + browser.setBounds?.(bounds); + } }; - // Show with initial bounds - const rect = panelRef.current.getBoundingClientRect(); - const toolbarHeight = 44; - browser.show({ - x: Math.round(rect.x), - y: Math.round(rect.y + toolbarHeight), - width: Math.round(rect.width), - height: Math.round(rect.height - toolbarHeight), - }); + // Initial show (may be zero-dimension if layout hasn't settled) + tryShow(); - // Observe resize - const observer = new ResizeObserver(updateBounds); + const observer = new ResizeObserver(tryShow); observer.observe(panelRef.current); - - // Also update on window resize - window.addEventListener("resize", updateBounds); + window.addEventListener("resize", tryShow); return () => { observer.disconnect(); - window.removeEventListener("resize", updateBounds); - browser.hide(); + window.removeEventListener("resize", tryShow); + browser.hide?.(); + shownRef.current = false; }; }, []); - const navigate = useCallback( - (url?: string) => { - const browser = getElectronBrowser(); - if (!browser) return; - browser.navigate(url ?? urlInput); - }, - [urlInput], - ); + const navigate = useCallback((url?: string) => { + getElectronBrowser()?.navigate?.(url ?? urlInput); + }, [urlInput]); - const handleUrlKeyDown = useCallback( - (e: React.KeyboardEvent) => { - if (e.key === "Enter") { - e.preventDefault(); - navigate(); - urlInputRef.current?.blur(); - } - }, - [navigate], - ); + const handleUrlKeyDown = useCallback((e: React.KeyboardEvent) => { + if (e.key === "Enter") { + e.preventDefault(); + navigate(); + urlInputRef.current?.blur(); + } + }, [navigate]); const browser = getElectronBrowser(); - if (!isElectronRuntime() || !browser) { return (
@@ -141,44 +108,16 @@ export function BrowserPanel({ onClose }: BrowserPanelProps) { return (
- {/* Toolbar */}
- {/* Navigation buttons */} - - - - - {/* URL bar */}
setUrlInput(e.target.value)} onKeyDown={handleUrlKeyDown} - onFocus={() => { - setUrlFocused(true); - urlInputRef.current?.select(); - }} + onFocus={() => { setUrlFocused(true); urlInputRef.current?.select(); }} onBlur={() => setUrlFocused(false)} placeholder="Enter URL..." spellCheck={false} autoComplete="off" />
- - {/* Close button */} -
- {/* WebContentsView renders in this area (managed by Electron main process) */}
diff --git a/apps/app/src/react-app/domains/session/chat/session-page.tsx b/apps/app/src/react-app/domains/session/chat/session-page.tsx index 205b08fe5..744c65d62 100644 --- a/apps/app/src/react-app/domains/session/chat/session-page.tsx +++ b/apps/app/src/react-app/domains/session/chat/session-page.tsx @@ -176,12 +176,9 @@ export function SessionPage(props: SessionPageProps) { const [deleteBusy, setDeleteBusy] = useState(false); const [todoExpanded, setTodoExpanded] = useState(true); const [browserPanelOpen, setBrowserPanelOpen] = useState(false); + const toggleBrowserPanel = useCallback(() => setBrowserPanelOpen((p) => !p), []); const [showDelayedSessionLoadingState, setShowDelayedSessionLoadingState] = useState(false); - const toggleBrowserPanel = useCallback(() => { - setBrowserPanelOpen((prev) => !prev); - }, []); - const selectedSessionTitle = useMemo( () => sessionTitleForId(props.sidebar.workspaceSessionGroups, props.selectedSessionId), [props.selectedSessionId, props.sidebar.workspaceSessionGroups], @@ -350,11 +347,7 @@ export function SessionPage(props: SessionPageProps) { {isElectronRuntime() ? (