@@ -12,14 +12,18 @@ import { clsx } from 'clsx';
1212import { TERMINAL_BOTTOM_RADIUS_CLASS } from '../design' ;
1313import { getPlatform } from '../../lib/platform' ;
1414import { readTextFromClipboard } from '../../lib/clipboard' ;
15+ import { isEditableTarget } from '../../lib/dom' ;
1516import {
1617 openAgentBrowserScreenModal ,
1718 registerAgentBrowserScreen ,
19+ type ChromeActions ,
20+ type ChromeSnapshot ,
1821 type ScreenActions ,
1922 type ScreenRegistration ,
2023 type ScreenSnapshot ,
2124 type ScreenState ,
2225} from './agent-browser-screen' ;
26+ import { hostPathDisplay } from './browser-url' ;
2327import {
2428 EDIT_OPS ,
2529 MOUSE_BUTTONS ,
@@ -84,13 +88,7 @@ type StreamStatus = {
8488function tabDisplayTitle ( tab : StreamTab ) : string {
8589 const title = tab . title ?. trim ( ) ;
8690 if ( title ) return title ;
87- try {
88- const parsed = new URL ( tab . url ) ;
89- const path = parsed . pathname === '/' ? '' : parsed . pathname ;
90- return `${ parsed . host } ${ path } ` || tab . url ;
91- } catch {
92- return tab . url || 'untitled' ;
93- }
91+ return hostPathDisplay ( tab . url ) || 'untitled' ;
9492}
9593
9694// Decode a base64 screencast frame to an ImageBitmap (the fallback display path
@@ -397,12 +395,38 @@ export function AgentBrowserPanel({ api, params }: IDockviewPanelProps<AgentBrow
397395 } ;
398396 } , [ wsPort , runAgentBrowser , maybeDisengageSync , publishScreen , screenshotLoop , drawBitmap ] ) ;
399397
400- // --- header title follows the active tab ---
398+ // --- header: persisted title + browser-chrome (URL / key) ---
401399
400+ const activeTab = useMemo ( ( ) => tabs . find ( ( tab ) => tab . active ) ?? tabs [ 0 ] ?? null , [ tabs ] ) ;
401+
402+ // The persisted panel title (door labels, session save) stays the active
403+ // tab's display title; the live browser-chrome header shows the URL instead
404+ // (from the snapshot below) and demotes this title to a tooltip.
402405 useEffect ( ( ) => {
403- const active = tabs . find ( ( tab ) => tab . active ) ?? tabs [ 0 ] ;
404- if ( active ) api . setTitle ( tabDisplayTitle ( active ) ) ;
405- } , [ tabs , api ] ) ;
406+ if ( activeTab ) api . setTitle ( tabDisplayTitle ( activeTab ) ) ;
407+ } , [ activeTab , api ] ) ;
408+
409+ // The browser-chrome snapshot the header reads. Recomputed each render and
410+ // mirrored into a ref so a (re-)registration always seeds the latest; the
411+ // publish effect below pushes it to subscribers only on real changes.
412+ const chromeSnapshot : ChromeSnapshot = {
413+ url : activeTab ?. url ?? '' ,
414+ displayUrl : activeTab ? hostPathDisplay ( activeTab . url ) : '' ,
415+ title : activeTab ?. title ?? null ,
416+ key : params ?. key ?? null ,
417+ } ;
418+ const chromeSnapshotRef = useRef ( chromeSnapshot ) ;
419+ chromeSnapshotRef . current = chromeSnapshot ;
420+
421+ // Native history nav — `back`/`forward`/`reload` issued like tab actions
422+ // (allowlisted in agentBrowserCommand). Stable; reads the live closure via
423+ // the ref so the registered controller never goes stale.
424+ const chromeActions = useMemo < ChromeActions > ( ( ) => ( {
425+ navigate ( url ) { if ( url ) runAgentBrowserRef . current ( [ 'open' , url ] ) ; } ,
426+ back ( ) { runAgentBrowserRef . current ( [ 'back' ] ) ; } ,
427+ forward ( ) { runAgentBrowserRef . current ( [ 'forward' ] ) ; } ,
428+ reload ( ) { runAgentBrowserRef . current ( [ 'reload' ] ) ; } ,
429+ } ) , [ ] ) ;
406430
407431 // --- screen controller: register for the header/modal bridge ---
408432
@@ -437,6 +461,8 @@ export function AgentBrowserPanel({ api, params }: IDockviewPanelProps<AgentBrow
437461 const registration = registerAgentBrowserScreen ( api . id , {
438462 snapshot : computeSnapshot ( ) ,
439463 actions : screenActions ,
464+ chrome : chromeSnapshotRef . current ,
465+ chromeActions,
440466 hostCapable : ! ! getPlatform ( ) . agentBrowserCommand ,
441467 } ) ;
442468 registrationRef . current = registration ;
@@ -446,7 +472,15 @@ export function AgentBrowserPanel({ api, params }: IDockviewPanelProps<AgentBrow
446472 registration . dispose ( ) ;
447473 registrationRef . current = null ;
448474 } ;
449- } , [ api . id , screenActions , computeSnapshot , publishScreen ] ) ;
475+ } , [ api . id , screenActions , chromeActions , computeSnapshot , publishScreen ] ) ;
476+
477+ // Push browser-chrome changes to the header on a channel separate from the
478+ // screen snapshot. Gated on the primitive fields so it fires on real
479+ // tab/status changes, not every frame pulse.
480+ useEffect ( ( ) => {
481+ registrationRef . current ?. updateChrome ( chromeSnapshotRef . current ) ;
482+ // displayUrl is a pure function of url, so url covers it.
483+ } , [ chromeSnapshot . url , chromeSnapshot . title , chromeSnapshot . key ] ) ;
450484
451485 // Persist sync state into the panel params so it round-trips through the
452486 // dockview layout blob (and survives reattach). Skip no-op writes.
@@ -729,6 +763,9 @@ export function AgentBrowserPanel({ api, params }: IDockviewPanelProps<AgentBrow
729763 // contains() check above misses it; without this, typing into the modal's
730764 // Custom W/H/DPI fields would be swallowed and forwarded to the browser.
731765 if ( e . target instanceof Element && e . target . closest ( '[role="dialog"]' ) ) return ;
766+ // Likewise never hijack keystrokes destined for an editable field that
767+ // lives outside the pane — notably the header's URL editor.
768+ if ( isEditableTarget ( e . target ) ) return ;
732769 e . preventDefault ( ) ;
733770 if ( e . type === 'keydown' ) handleKeyDownLike ( e ) ;
734771 else sendKey ( e , 'keyUp' ) ;
0 commit comments