diff --git a/CHANGELOG.md b/CHANGELOG.md index fd021143..f03d79e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ All notable changes to this project will be documented in this file. - Chat: a "Follow new content while streaming" checkbox (Settings → Chat → Streaming, on by default) turns automatic following off entirely; with it off, the scroll-to-bottom pill now appears as soon as the reply grows past the visible area. - Command palette: rarely used commands (pin session, copy session ID, multi-run launcher, archived sessions, notes, todos, status, theme) are found by typing but stay off the first screen. - Mobile: narrowing a browser window past phone size switches into the mobile layout (and back when widened); the old/new mobile layout setting is gone. +- Browser: an agent opening a page with the browser tool no longer pops the browser panel open (or switches the surface you're on) — the page loads in the background and the rail is where you peek at it. - Usage: the Command Code tile is gone — their official API exposes no usage data, so the tile could only fail. - Auth: an expired OpenChamber login is announced within seconds by a banner with a Log in button, instead of being discovered through failing actions. Sending pauses until login, and a conversation that failed to load reloads itself afterwards. - Chat: a failed send returns your typed prompt to the input — whatever the reason — instead of losing it to an error toast; a mid-send session switch lands it in that session's draft. diff --git a/packages/ui/src/components/layout/ContextPanel.tsx b/packages/ui/src/components/layout/ContextPanel.tsx index 8c24e134..aa52be5d 100644 --- a/packages/ui/src/components/layout/ContextPanel.tsx +++ b/packages/ui/src/components/layout/ContextPanel.tsx @@ -452,10 +452,13 @@ export const ContextPanel: React.FC = () => { // Lets an agent's browser.open create the tab it needs when none is open yet. // Registered from the panel because opening a tab is panel state, not - // something the browser view itself can do before it exists. + // something the browser view itself can do before it exists. Background on + // purpose: an agent working a page must not pop the panel open (or steal + // the active surface) under the user — the tab mounts invisibly, and the + // rail is where the user opens it when curious. React.useEffect(() => { if (!effectiveDirectory) return; - return registerBrowserOpener((url) => openContextBrowser(effectiveDirectory, url)); + return registerBrowserOpener((url) => openContextBrowser(effectiveDirectory, url, { reveal: false })); }, [effectiveDirectory, openContextBrowser]); const reorderContextPanelTabs = useUIStore((state) => state.reorderContextPanelTabs); const setSelectedFilePath = useFilesViewTabsStore((state) => state.setSelectedPath); diff --git a/packages/ui/src/stores/useUIStore.ts b/packages/ui/src/stores/useUIStore.ts index b5254f2e..5eceb243 100644 --- a/packages/ui/src/stores/useUIStore.ts +++ b/packages/ui/src/stores/useUIStore.ts @@ -393,7 +393,9 @@ const touchContextPanelState = (prev?: ContextPanelDirectoryState): ContextPanel const upsertContextPanelTab = ( current: ContextPanelDirectoryState, descriptor: ContextPanelTabDescriptor, + options?: { reveal?: boolean }, ): ContextPanelDirectoryState => { + const reveal = options?.reveal !== false; const nextTab = createContextPanelTab(descriptor); // A real file tab replaces the empty editor placeholder ('file' with no // target) that the rail can open before any file is picked. @@ -418,12 +420,18 @@ const upsertContextPanelTab = ( } : tab)); - const activeTabId = nextTab.id; + // A background upsert (an agent working a page) keeps the panel exactly as + // the user left it: closed stays closed, and whatever tab they were on + // stays active. The tab still exists — panes are kept mounted regardless of + // visibility — so agent control and a later manual open both find it. + const activeTabId = reveal + ? nextTab.id + : current.activeTabId ?? nextTab.id; const clampedTabs = clampContextPanelTabs(tabs, CONTEXT_PANEL_MAX_TABS, activeTabId); return { ...current, - isOpen: true, + isOpen: reveal ? true : current.isOpen, tabs: clampedTabs, activeTabId: resolveActiveContextPanelTabID(clampedTabs, activeTabId), touchedAt: Date.now(), @@ -806,14 +814,14 @@ interface UIStore { toggleContextEditorTree: () => void; setContextEditorTreeWidth: (width: number) => void; openContextSurface: (directory: string, mode: ContextPanelMode) => void; - openContextPanelTab: (directory: string, tab: ContextPanelTabDescriptor) => void; + openContextPanelTab: (directory: string, tab: ContextPanelTabDescriptor, options?: { reveal?: boolean }) => void; openContextDiff: (directory: string, filePath: string, staged?: boolean, scope?: PendingDiffScope | null) => void; openContextFile: (directory: string, filePath: string) => void; openContextFileAtLine: (directory: string, filePath: string, line: number, column?: number) => void; openContextOverview: (directory: string) => void; openContextPlan: (directory: string) => void; openContextPreview: (directory: string, url: string) => void; - openContextBrowser: (directory: string, url?: string) => void; + openContextBrowser: (directory: string, url?: string, options?: { reveal?: boolean }) => void; openNewContextBrowserTab: (directory: string) => void; setContextPanelTabTargetPath: (directory: string, tabID: string, targetPath: string) => void; setActiveContextPanelTab: (directory: string, tabID: string) => void; @@ -1239,7 +1247,7 @@ export const useUIStore = create()( state.openContextPanelTab(normalizedDirectory, { mode }); }, - openContextPanelTab: (directory, tab) => { + openContextPanelTab: (directory, tab, options) => { const normalizedDirectory = normalizeDirectoryPath((directory || '').trim()); if (!normalizedDirectory) { return; @@ -1250,7 +1258,7 @@ export const useUIStore = create()( const current = touchContextPanelState(prev); const byDirectory = { ...state.contextPanelByDirectory, - [normalizedDirectory]: upsertContextPanelTab(current, tab), + [normalizedDirectory]: upsertContextPanelTab(current, tab, options), }; return { contextPanelByDirectory: clampContextPanelRoots(byDirectory, 20) }; @@ -1351,7 +1359,7 @@ export const useUIStore = create()( label: null, }); }, - openContextBrowser: (directory, url = '') => { + openContextBrowser: (directory, url = '', options) => { const normalizedDirectory = normalizeDirectoryPath((directory || '').trim()); if (!normalizedDirectory || isVSCodeRuntime()) return; const targetUrl = typeof url === 'string' && url.trim().length > 0 ? url.trim() : ''; @@ -1360,7 +1368,7 @@ export const useUIStore = create()( targetPath: targetUrl, dedupeKey: targetUrl || 'browser', label: null, - }); + }, options); }, setContextPanelTabTargetPath: (directory, tabID, targetPath) => {