diff --git a/src/renderer/src/components/sidebar/WorktreeCardIssueDetailSection.tsx b/src/renderer/src/components/sidebar/WorktreeCardIssueDetailSection.tsx index 76fa707c30b..4cc4ab7837b 100644 --- a/src/renderer/src/components/sidebar/WorktreeCardIssueDetailSection.tsx +++ b/src/renderer/src/components/sidebar/WorktreeCardIssueDetailSection.tsx @@ -7,7 +7,7 @@ import { DropdownMenuTrigger } from '@/components/ui/dropdown-menu' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' -import { CircleDot, Copy, Ellipsis, ExternalLink, MonitorUp, Pencil } from 'lucide-react' +import { CircleDot, Copy, Ellipsis, ExternalLink, Globe, MonitorUp, Pencil } from 'lucide-react' import { translate } from '@/i18n/i18n' import { WorktreeCardDetailSection, @@ -22,6 +22,7 @@ type WorktreeCardIssueDetailSectionProps = { issueMenuOpen: boolean onIssueMenuOpenChange: (open: boolean) => void onCopyIssueLink?: () => void + onOpenIssueInBrowser?: (url: string) => void onEditIssue?: (event: React.MouseEvent) => void onOpenGitHubIssueInOrca?: (event: React.MouseEvent) => void } @@ -31,6 +32,7 @@ export function WorktreeCardIssueDetailSection({ issueMenuOpen, onIssueMenuOpenChange, onCopyIssueLink, + onOpenIssueInBrowser, onEditIssue, onOpenGitHubIssueInOrca }: WorktreeCardIssueDetailSectionProps): React.JSX.Element | null { @@ -71,7 +73,7 @@ export function WorktreeCardIssueDetailSection({ )} actions={ <> - {issue.url && onCopyIssueLink && ( + {issue.url && (onCopyIssueLink || onOpenIssueInBrowser) && ( {issueMenuOpen ? ( moreActionsTrigger @@ -84,10 +86,25 @@ export function WorktreeCardIssueDetailSection({ )} - - - {translate('auto.components.sidebar.WorktreeCardMeta.copyLink', 'Copy link')} - + {onOpenIssueInBrowser && ( + { + onOpenIssueInBrowser(issue.url!) + }} + > + + {translate( + 'auto.components.sidebar.WorktreeCardMeta.openInOrcaBrowser', + 'Open in Orca browser' + )} + + )} + {onCopyIssueLink && ( + + + {translate('auto.components.sidebar.WorktreeCardMeta.copyLink', 'Copy link')} + + )} )} diff --git a/src/renderer/src/components/sidebar/WorktreeCardMeta.interaction.test.tsx b/src/renderer/src/components/sidebar/WorktreeCardMeta.interaction.test.tsx index 2b2cabb5947..d172df1bac3 100644 --- a/src/renderer/src/components/sidebar/WorktreeCardMeta.interaction.test.tsx +++ b/src/renderer/src/components/sidebar/WorktreeCardMeta.interaction.test.tsx @@ -116,7 +116,10 @@ describe('WorktreeCardDetailsHover interactions', () => { writeClipboardText.mockResolvedValue(undefined) }) - function renderHover(onUnlinkReview = vi.fn()): ReturnType { + function renderHover( + onUnlinkReview = vi.fn(), + onOpenReviewInBrowser?: () => void + ): ReturnType { container = document.createElement('div') root = createRoot(container) act(() => { @@ -130,6 +133,7 @@ describe('WorktreeCardDetailsHover interactions', () => { onEditComment={vi.fn()} onOpenReviewInOrca={vi.fn()} onUnlinkReview={onUnlinkReview} + onOpenReviewInBrowser={onOpenReviewInBrowser} > Linked PR @@ -288,6 +292,49 @@ describe('WorktreeCardDetailsHover interactions', () => { ).toBe('false') }) + it('opens the review URL in Orca browser and leaves existing actions independent', () => { + const onOpenReviewInBrowser = vi.fn() + const onUnlinkReview = renderHover(vi.fn(), onOpenReviewInBrowser) + + act(() => { + interactionMocks.onHoverOpenChange?.(true) + interactionMocks.onReviewMenuOpenChange?.(true) + }) + + const browserButton = Array.from(container.querySelectorAll('button')).find((button) => + button.textContent?.includes('Open in Orca browser') + ) + + act(() => { + browserButton?.dispatchEvent(new MouseEvent('click', { bubbles: true })) + }) + + expect(onOpenReviewInBrowser).toHaveBeenCalledWith('https://github.com/acme/orca/pull/456') + expect(onUnlinkReview).not.toHaveBeenCalled() + expect(container.querySelector('[data-hover-open]')?.getAttribute('data-hover-open')).toBe( + 'false' + ) + }) + + it('preserves repeated-click behavior by forwarding each browser action', () => { + const onOpenReviewInBrowser = vi.fn() + renderHover(vi.fn(), onOpenReviewInBrowser) + + act(() => { + interactionMocks.onReviewMenuOpenChange?.(true) + }) + const browserButton = Array.from(container.querySelectorAll('button')).find((button) => + button.textContent?.includes('Open in Orca browser') + ) + + act(() => { + browserButton?.dispatchEvent(new MouseEvent('click', { bubbles: true })) + browserButton?.dispatchEvent(new MouseEvent('click', { bubbles: true })) + }) + + expect(onOpenReviewInBrowser).toHaveBeenCalledTimes(2) + }) + it('reports clipboard failures without unlinking the review', async () => { writeClipboardText.mockRejectedValueOnce(new Error('clipboard unavailable')) const onUnlinkReview = renderHover() @@ -310,4 +357,43 @@ describe('WorktreeCardDetailsHover interactions', () => { expect(onUnlinkReview).not.toHaveBeenCalled() expect(toastMocks.error).toHaveBeenCalledWith('Failed to copy link') }) + + it('passes a linked issue URL to the embedded-browser action', () => { + const onOpenIssueInBrowser = vi.fn() + container = document.createElement('div') + root = createRoot(container) + act(() => { + root.render( + + Linked issue + + ) + }) + + act(() => { + interactionMocks.onHoverOpenChange?.(true) + interactionMocks.onReviewMenuOpenChange?.(true) + }) + const browserButton = Array.from(container.querySelectorAll('button')).find((button) => + button.textContent?.includes('Open in Orca browser') + ) + + act(() => { + browserButton?.dispatchEvent(new MouseEvent('click', { bubbles: true })) + }) + + expect(onOpenIssueInBrowser).toHaveBeenCalledWith('https://github.com/acme/orca/issues/5518') + }) }) diff --git a/src/renderer/src/components/sidebar/WorktreeCardMeta.test.tsx b/src/renderer/src/components/sidebar/WorktreeCardMeta.test.tsx index 1e207973911..aa816fdd05d 100644 --- a/src/renderer/src/components/sidebar/WorktreeCardMeta.test.tsx +++ b/src/renderer/src/components/sidebar/WorktreeCardMeta.test.tsx @@ -130,6 +130,7 @@ describe('WorktreeCardDetailsHover', () => { onEditIssue={vi.fn()} onEditComment={vi.fn()} onOpenReviewInOrca={vi.fn()} + onOpenReviewInBrowser={vi.fn()} onUnlinkReview={vi.fn()} > Linked PR @@ -143,10 +144,12 @@ describe('WorktreeCardDetailsHover', () => { expect(moreActionsIndex).toBeGreaterThan(-1) expect(markup).toContain('More PR actions') expect(markup).toContain('Copy link') + expect(markup).toContain('Open in Orca browser') expect(markup).toContain('Unlink PR') expect(moreActionsIndex).toBeLessThan(openInOrcaIndex) expect(openInOrcaIndex).toBeLessThan(viewOnGitHubIndex) expect(markup).not.toContain('aria-label="Unlink PR"') + expect(markup.indexOf('Open in Orca browser')).toBeLessThan(markup.indexOf('Copy link')) expect(markup.indexOf('Copy link')).toBeLessThan(markup.indexOf('Unlink PR')) }) @@ -166,6 +169,7 @@ describe('WorktreeCardDetailsHover', () => { onEditIssue={vi.fn()} onEditComment={vi.fn()} onOpenGitHubIssueInOrca={vi.fn()} + onOpenIssueInBrowser={vi.fn()} > Linked issue @@ -179,6 +183,8 @@ describe('WorktreeCardDetailsHover', () => { expect(moreActionsIndex).toBeGreaterThan(-1) expect(copyLinkIndex).toBeGreaterThan(-1) + expect(markup).toContain('Open in Orca browser') + expect(markup.indexOf('Open in Orca browser')).toBeLessThan(copyLinkIndex) expect(editIssueIndex).toBeGreaterThan(-1) expect(moreActionsIndex).toBeLessThan(editIssueIndex) expect(copyLinkIndex).toBeLessThan(editIssueIndex) @@ -203,6 +209,7 @@ describe('WorktreeCardDetailsHover', () => { onEditIssue={vi.fn()} onEditComment={vi.fn()} onUnlinkReview={vi.fn()} + onOpenReviewInBrowser={vi.fn()} > Linked MR @@ -211,6 +218,53 @@ describe('WorktreeCardDetailsHover', () => { expect(markup).toContain('aria-label="More MR actions"') expect(markup).toContain('Unlink MR') expect(markup).toContain('View on GitLab') + expect(markup).toContain('Open in Orca browser') + }) + + it('hides the embedded-browser action when a linked review has no URL', () => { + const markup = renderToStaticMarkup( + + Linked PR + + ) + + expect(markup).not.toContain('Open in Orca browser') + }) + + it('keeps the embedded-browser action provider-neutral for unsupported review URLs', () => { + const markup = renderToStaticMarkup( + + Linked review + + ) + + expect(markup).toContain('Open in Orca browser') }) it('displays Linear issue details with link', () => { diff --git a/src/renderer/src/components/sidebar/WorktreeCardMeta.tsx b/src/renderer/src/components/sidebar/WorktreeCardMeta.tsx index 071d857aed2..3c288f6eb94 100644 --- a/src/renderer/src/components/sidebar/WorktreeCardMeta.tsx +++ b/src/renderer/src/components/sidebar/WorktreeCardMeta.tsx @@ -70,8 +70,10 @@ export function WorktreeCardDetailsHover({ onEditIssue, onEditComment, onOpenGitHubIssueInOrca, + onOpenIssueInBrowser, onOpenLinearIssueInOrca, onOpenReviewInOrca, + onOpenReviewInBrowser, onUnlinkReview, onOpenAutomation, onOpenAutomationRun, @@ -212,6 +214,14 @@ export function WorktreeCardDetailsHover({ onOpenGitHubIssueInOrca={ onOpenGitHubIssueInOrca ? dismissAndRun(onOpenGitHubIssueInOrca) : undefined } + onOpenIssueInBrowser={ + onOpenIssueInBrowser && issue?.url + ? (url: string) => { + closeHover() + onOpenIssueInBrowser(url) + } + : undefined + } /> {linearIssue && ( @@ -305,6 +315,9 @@ export function WorktreeCardDetailsHover({ reviewMenuOpen={reviewMenuOpen} onReviewMenuOpenChange={handleReviewMenuOpenChange} onOpenReviewInOrca={onOpenReviewInOrca} + onOpenReviewInBrowser={ + onOpenReviewInBrowser && review?.url ? onOpenReviewInBrowser : undefined + } onCopyReviewLink={review?.url ? handleCopyReviewLink : undefined} onUnlinkReview={onUnlinkReview} closeHover={closeHover} diff --git a/src/renderer/src/components/sidebar/WorktreeCardReviewDetailSection.tsx b/src/renderer/src/components/sidebar/WorktreeCardReviewDetailSection.tsx index 257ccd70c70..59edfde10bd 100644 --- a/src/renderer/src/components/sidebar/WorktreeCardReviewDetailSection.tsx +++ b/src/renderer/src/components/sidebar/WorktreeCardReviewDetailSection.tsx @@ -7,7 +7,7 @@ import { DropdownMenuTrigger } from '@/components/ui/dropdown-menu' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' -import { Copy, Ellipsis, ExternalLink, MonitorUp, Unlink } from 'lucide-react' +import { Copy, Ellipsis, ExternalLink, Globe, MonitorUp, Unlink } from 'lucide-react' import { translate } from '@/i18n/i18n' import { WorktreeCardDetailSection, @@ -24,6 +24,7 @@ type WorktreeCardReviewDetailSectionProps = { onReviewMenuOpenChange: (open: boolean) => void onOpenReviewInOrca?: (event: React.MouseEvent) => void onCopyReviewLink?: () => void + onOpenReviewInBrowser?: (url: string) => void onUnlinkReview?: () => void closeHover: () => void } @@ -34,6 +35,7 @@ export function WorktreeCardReviewDetailSection({ onReviewMenuOpenChange, onOpenReviewInOrca, onCopyReviewLink, + onOpenReviewInBrowser, onUnlinkReview, closeHover }: WorktreeCardReviewDetailSectionProps): React.JSX.Element | null { @@ -78,7 +80,7 @@ export function WorktreeCardReviewDetailSection({ )} actions={ <> - {(onCopyReviewLink || onUnlinkReview) && ( + {(onCopyReviewLink || onOpenReviewInBrowser || onUnlinkReview) && ( )} + {onOpenReviewInBrowser && ( + { + closeHover() + onOpenReviewInBrowser(review.url!) + }} + > + + {translate( + 'auto.components.sidebar.WorktreeCardMeta.openInOrcaBrowser', + 'Open in Orca browser' + )} + + )} {onCopyReviewLink && ( { diff --git a/src/renderer/src/components/sidebar/use-worktree-card-secondary-details.ts b/src/renderer/src/components/sidebar/use-worktree-card-secondary-details.ts index e5ffe899c8d..111f1fa95f6 100644 --- a/src/renderer/src/components/sidebar/use-worktree-card-secondary-details.ts +++ b/src/renderer/src/components/sidebar/use-worktree-card-secondary-details.ts @@ -1,6 +1,9 @@ import React, { useCallback } from 'react' +import { toast } from 'sonner' import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import { translate } from '@/i18n/i18n' +import { openWorkspaceBrowserTab } from '@/lib/workspace-browser-tab-open' import { hasWorktreeCardDetails } from './WorktreeCardMeta' import { usePromptCacheCountdownStartedAt } from './CacheTimer' import { useWorktreeAgentRows } from './useWorktreeAgentRows' @@ -141,6 +144,34 @@ export function useWorktreeCardSecondaryDetails({ }, [hoverReview, openTaskPage, repo] ) + const openLinkedUrlInBrowser = useCallback( + (url: string): void => { + void openWorkspaceBrowserTab({ + workspaceId: worktree.id, + url, + intent: { kind: 'url' } + }).catch((error: unknown) => { + toast.error( + error instanceof Error + ? error.message + : translate('auto.lib.workspace.browser.tab.open.urlFailed', 'Unable to open URL.') + ) + }) + }, + [worktree.id] + ) + const handleOpenIssueInBrowser = useCallback( + (url: string): void => { + openLinkedUrlInBrowser(url) + }, + [openLinkedUrlInBrowser] + ) + const handleOpenReviewInBrowser = useCallback( + (url: string): void => { + openLinkedUrlInBrowser(url) + }, + [openLinkedUrlInBrowser] + ) const hoverReviewProvider = hoverReview?.provider const hasExplicitLinkedReview = (hoverReviewProvider === 'github' && worktree.linkedPR !== null) || @@ -214,7 +245,9 @@ export function useWorktreeCardSecondaryDetails({ showInlineAgentList, compactInlineAgentRows, handleOpenGitHubIssueInOrca, + handleOpenIssueInBrowser, handleOpenReviewInOrca, + handleOpenReviewInBrowser, hasExplicitLinkedReview, handleUnlinkReview, handleOpenLinearIssueInOrca, diff --git a/src/renderer/src/components/sidebar/worktree-card-meta-types.ts b/src/renderer/src/components/sidebar/worktree-card-meta-types.ts index 54637f7a50a..c35ad3c9fa5 100644 --- a/src/renderer/src/components/sidebar/worktree-card-meta-types.ts +++ b/src/renderer/src/components/sidebar/worktree-card-meta-types.ts @@ -57,8 +57,10 @@ export type WorktreeCardDetailsHoverProps = WorktreeCardMetaBadgesProps & { onEditIssue?: (event: React.MouseEvent) => void onEditComment?: (event: React.MouseEvent) => void onOpenGitHubIssueInOrca?: (event: React.MouseEvent) => void + onOpenIssueInBrowser?: (url: string) => void onOpenLinearIssueInOrca?: (event: React.MouseEvent) => void onOpenReviewInOrca?: (event: React.MouseEvent) => void + onOpenReviewInBrowser?: (url: string) => void onUnlinkReview?: () => void onOpenAutomation?: (event: React.MouseEvent) => void onOpenAutomationRun?: (event: React.MouseEvent) => void diff --git a/src/renderer/src/components/sidebar/worktree-card-parent-content.tsx b/src/renderer/src/components/sidebar/worktree-card-parent-content.tsx index 4cf122b0519..147e6615a9d 100644 --- a/src/renderer/src/components/sidebar/worktree-card-parent-content.tsx +++ b/src/renderer/src/components/sidebar/worktree-card-parent-content.tsx @@ -44,9 +44,11 @@ export function WorktreeCardParentContent({ handleEditIssue, handleEditComment, handleOpenGitHubIssueInOrca, + handleOpenIssueInBrowser, linearIssue, handleOpenLinearIssueInOrca, handleOpenReviewInOrca, + handleOpenReviewInBrowser, handleOpenAutomation, handleOpenAutomationRun, hasExplicitLinkedReview, @@ -98,10 +100,14 @@ export function WorktreeCardParentContent({ ? handleOpenGitHubIssueInOrca : undefined } + onOpenIssueInBrowser={ + hoverIssue && 'url' in hoverIssue && hoverIssue.url ? handleOpenIssueInBrowser : undefined + } onOpenLinearIssueInOrca={linearIssue?.url ? handleOpenLinearIssueInOrca : undefined} onOpenReviewInOrca={ hoverReview?.url && hoverReview.provider === 'github' ? handleOpenReviewInOrca : undefined } + onOpenReviewInBrowser={hoverReview?.url ? handleOpenReviewInBrowser : undefined} onOpenAutomation={affiliateListMode ? undefined : handleOpenAutomation} onOpenAutomationRun={affiliateListMode ? undefined : handleOpenAutomationRun} onUnlinkReview={ diff --git a/src/renderer/src/components/sidebar/worktree-card-presentation.tsx b/src/renderer/src/components/sidebar/worktree-card-presentation.tsx index de5c72f43b2..a5b708a9046 100644 --- a/src/renderer/src/components/sidebar/worktree-card-presentation.tsx +++ b/src/renderer/src/components/sidebar/worktree-card-presentation.tsx @@ -57,8 +57,10 @@ export function buildWorktreeCardPresentation(card: WorktreeCardController) { handleEditIssue, handleEditComment, handleOpenGitHubIssueInOrca, + handleOpenIssueInBrowser, handleOpenLinearIssueInOrca, handleOpenReviewInOrca, + handleOpenReviewInBrowser, handleOpenAutomation, handleOpenAutomationRun, hasExplicitLinkedReview, @@ -167,12 +169,18 @@ export function buildWorktreeCardPresentation(card: WorktreeCardController) { ? handleOpenGitHubIssueInOrca : undefined } + onOpenIssueInBrowser={ + metaIssue && 'url' in metaIssue && metaIssue.url + ? handleOpenIssueInBrowser + : undefined + } onOpenLinearIssueInOrca={linearIssue?.url ? handleOpenLinearIssueInOrca : undefined} onOpenReviewInOrca={ metaReview?.url && metaReview.provider === 'github' ? handleOpenReviewInOrca : undefined } + onOpenReviewInBrowser={metaReview?.url ? handleOpenReviewInBrowser : undefined} onOpenAutomation={affiliateListMode ? undefined : handleOpenAutomation} onOpenAutomationRun={affiliateListMode ? undefined : handleOpenAutomationRun} // Why: compact mode hides the metadata badge row, so title hover carries the explicit-link affordance. @@ -231,10 +239,14 @@ export function buildWorktreeCardPresentation(card: WorktreeCardController) { onOpenGitHubIssueInOrca={ metaIssue && 'url' in metaIssue && metaIssue.url ? handleOpenGitHubIssueInOrca : undefined } + onOpenIssueInBrowser={ + metaIssue && 'url' in metaIssue && metaIssue.url ? handleOpenIssueInBrowser : undefined + } onOpenLinearIssueInOrca={linearIssue?.url ? handleOpenLinearIssueInOrca : undefined} onOpenReviewInOrca={ metaReview?.url && metaReview.provider === 'github' ? handleOpenReviewInOrca : undefined } + onOpenReviewInBrowser={metaReview?.url ? handleOpenReviewInBrowser : undefined} onOpenAutomation={affiliateListMode ? undefined : handleOpenAutomation} onOpenAutomationRun={affiliateListMode ? undefined : handleOpenAutomationRun} // Why: branch lookup can surface a review without persisted metadata; only unlink when explicitly linked. diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 31b048c7d1c..042adb66254 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -5415,6 +5415,7 @@ "eace1d2cf6": "neutral", "dbe2d18972": "More {{value0}} actions", "ae76907ca6": "Unlink {{value0}}", + "openInOrcaBrowser": "Open in Orca browser", "ad25c3ff05": "View on {{value0}}", "2c67730e07": "Open in Orca", "e42941631a": "View on Linear", diff --git a/src/renderer/src/lib/workspace-browser-tab-open.test.ts b/src/renderer/src/lib/workspace-browser-tab-open.test.ts index 307156f40f0..59ce6bac8d8 100644 --- a/src/renderer/src/lib/workspace-browser-tab-open.test.ts +++ b/src/renderer/src/lib/workspace-browser-tab-open.test.ts @@ -104,6 +104,31 @@ describe('openWorkspaceBrowserTab', () => { expect(mocks.createRemote).not.toHaveBeenCalled() }) + it('creates a client tab without activating it when requested', async () => { + const createBrowserTab = vi.fn() + const sshHost = toSshExecutionHostId('ssh-target') + mocks.state = { + ...ownerState(sshHost), + createBrowserTab, + defaultBrowserSessionProfileId: 'focused-profile', + defaultBrowserSessionProfileIdByHostId: { [sshHost]: 'ssh-profile' } + } + + await openWorkspaceBrowserTab({ + workspaceId: WORKSPACE_ID, + url: 'https://github.com/acme/orca/pull/456', + intent: { kind: 'url' }, + focusOnCreate: false, + selectWorktree: false + }) + + expect(createBrowserTab).toHaveBeenCalledWith( + WORKSPACE_ID, + 'https://github.com/acme/orca/pull/456', + expect.objectContaining({ activate: false }) + ) + }) + it('fails closed when the asserted SSH browser route is opted out or belongs to another host', async () => { const sshHost = toSshExecutionHostId('ssh-target') mocks.state = { @@ -164,6 +189,32 @@ describe('openWorkspaceBrowserTab', () => { expect(createBrowserTab).not.toHaveBeenCalled() }) + it('stages a runtime tab without selecting the worktree or focusing the browser', async () => { + mocks.state = { + ...ownerState(toRuntimeExecutionHostId('hub-a')), + ...browserCapableRuntime('hub-a'), + createBrowserTab: vi.fn(), + defaultBrowserSessionProfileId: 'client-profile', + defaultBrowserSessionProfileIdByHostId: {} + } + + await openWorkspaceBrowserTab({ + workspaceId: WORKSPACE_ID, + url: 'https://gitlab.com/acme/orca/-/merge_requests/77', + intent: { kind: 'url' }, + focusOnCreate: false, + selectWorktree: false + }) + + expect(mocks.createRemote).toHaveBeenCalledWith( + expect.objectContaining({ + focusOnCreate: false, + selectWorktree: false, + url: 'https://gitlab.com/acme/orca/-/merge_requests/77' + }) + ) + }) + it('waits for host registration before reconciling an asserted runtime link', async () => { const createBrowserTab = vi.fn() mocks.state = { diff --git a/src/renderer/src/lib/workspace-browser-tab-open.ts b/src/renderer/src/lib/workspace-browser-tab-open.ts index 9fed023ac4d..d159d47aab3 100644 --- a/src/renderer/src/lib/workspace-browser-tab-open.ts +++ b/src/renderer/src/lib/workspace-browser-tab-open.ts @@ -25,6 +25,10 @@ export type OpenWorkspaceBrowserTabRequest = { targetGroupId?: string url: string intent: WorkspaceBrowserTabIntent + /** Keep the caller's current terminal/task surface selected while creating the tab. */ + focusOnCreate?: boolean + /** Keep the caller's current workspace selected while creating the tab. */ + selectWorktree?: boolean expectedRuntimeEnvironmentId?: string expectedSshConnectionId?: string } @@ -180,7 +184,7 @@ function createClientBrowserTab( ): void { try { state.createBrowserTab(request.workspaceId, request.url, { - activate: true, + activate: request.focusOnCreate !== false, browserRuntimeEnvironmentId: null, focusAddressBar: false, sessionProfileId: @@ -284,7 +288,8 @@ export async function openWorkspaceBrowserTab( ...(expectedEnvironmentId !== null ? { waitForRegistration: true } : {}), // Why: the tab is opened from this workspace's tab bar, so surface that // workspace — otherwise a background worktree looks like nothing happened. - selectWorktree: true, + ...(request.focusOnCreate !== undefined ? { focusOnCreate: request.focusOnCreate } : {}), + selectWorktree: request.selectWorktree !== false, stagedTitle: presentation.title, stagedFocusAddressBar: false, failureLogMode: 'operation-only'