mirror of
https://github.com/stablyai/orca.git
synced 2026-09-25 08:02:31 +00:00
Open linked reviews in Orca browser (#17360)
* feat(sidebar): open linked reviews in Orca browser * test(e2e): match paired window reveal assertion * feat(sidebar): focus linked browser tabs * ci: retry checks after cancelled rerun
This commit is contained in:
@@ -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) && (
|
||||
<DropdownMenu modal={false} open={issueMenuOpen} onOpenChange={onIssueMenuOpenChange}>
|
||||
{issueMenuOpen ? (
|
||||
moreActionsTrigger
|
||||
@@ -84,10 +86,25 @@ export function WorktreeCardIssueDetailSection({
|
||||
</Tooltip>
|
||||
)}
|
||||
<DropdownMenuContent align="end" className="w-40">
|
||||
<DropdownMenuItem onSelect={onCopyIssueLink}>
|
||||
<Copy className="size-3.5" />
|
||||
{translate('auto.components.sidebar.WorktreeCardMeta.copyLink', 'Copy link')}
|
||||
</DropdownMenuItem>
|
||||
{onOpenIssueInBrowser && (
|
||||
<DropdownMenuItem
|
||||
onSelect={() => {
|
||||
onOpenIssueInBrowser(issue.url!)
|
||||
}}
|
||||
>
|
||||
<Globe className="size-3.5" />
|
||||
{translate(
|
||||
'auto.components.sidebar.WorktreeCardMeta.openInOrcaBrowser',
|
||||
'Open in Orca browser'
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{onCopyIssueLink && (
|
||||
<DropdownMenuItem onSelect={onCopyIssueLink}>
|
||||
<Copy className="size-3.5" />
|
||||
{translate('auto.components.sidebar.WorktreeCardMeta.copyLink', 'Copy link')}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
|
||||
@@ -116,7 +116,10 @@ describe('WorktreeCardDetailsHover interactions', () => {
|
||||
writeClipboardText.mockResolvedValue(undefined)
|
||||
})
|
||||
|
||||
function renderHover(onUnlinkReview = vi.fn()): ReturnType<typeof vi.fn> {
|
||||
function renderHover(
|
||||
onUnlinkReview = vi.fn(),
|
||||
onOpenReviewInBrowser?: () => void
|
||||
): ReturnType<typeof vi.fn> {
|
||||
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}
|
||||
>
|
||||
<span>Linked PR</span>
|
||||
</WorktreeCardDetailsHover>
|
||||
@@ -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(
|
||||
<WorktreeCardDetailsHover
|
||||
issue={{
|
||||
number: 5518,
|
||||
title: 'Agent monitor issue',
|
||||
state: 'open',
|
||||
url: 'https://github.com/acme/orca/issues/5518',
|
||||
labels: []
|
||||
}}
|
||||
linearIssue={null}
|
||||
review={null}
|
||||
comment={null}
|
||||
onOpenIssueInBrowser={onOpenIssueInBrowser}
|
||||
>
|
||||
<span>Linked issue</span>
|
||||
</WorktreeCardDetailsHover>
|
||||
)
|
||||
})
|
||||
|
||||
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')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -130,6 +130,7 @@ describe('WorktreeCardDetailsHover', () => {
|
||||
onEditIssue={vi.fn()}
|
||||
onEditComment={vi.fn()}
|
||||
onOpenReviewInOrca={vi.fn()}
|
||||
onOpenReviewInBrowser={vi.fn()}
|
||||
onUnlinkReview={vi.fn()}
|
||||
>
|
||||
<span>Linked PR</span>
|
||||
@@ -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()}
|
||||
>
|
||||
<span>Linked issue</span>
|
||||
</WorktreeCardDetailsHover>
|
||||
@@ -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()}
|
||||
>
|
||||
<span>Linked MR</span>
|
||||
</WorktreeCardDetailsHover>
|
||||
@@ -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(
|
||||
<WorktreeCardDetailsHover
|
||||
issue={null}
|
||||
linearIssue={null}
|
||||
review={{
|
||||
provider: 'github',
|
||||
number: 456,
|
||||
title: 'Loading PR...',
|
||||
state: 'open'
|
||||
}}
|
||||
comment={null}
|
||||
onOpenReviewInBrowser={vi.fn()}
|
||||
>
|
||||
<span>Linked PR</span>
|
||||
</WorktreeCardDetailsHover>
|
||||
)
|
||||
|
||||
expect(markup).not.toContain('Open in Orca browser')
|
||||
})
|
||||
|
||||
it('keeps the embedded-browser action provider-neutral for unsupported review URLs', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<WorktreeCardDetailsHover
|
||||
issue={null}
|
||||
linearIssue={null}
|
||||
review={{
|
||||
provider: 'unsupported',
|
||||
number: 12,
|
||||
title: 'Review from an unsupported provider',
|
||||
state: 'open',
|
||||
url: 'https://code.example.test/reviews/12',
|
||||
status: 'neutral',
|
||||
updatedAt: '2026-05-17T00:00:00.000Z',
|
||||
mergeable: 'UNKNOWN'
|
||||
}}
|
||||
comment={null}
|
||||
onOpenReviewInBrowser={vi.fn()}
|
||||
>
|
||||
<span>Linked review</span>
|
||||
</WorktreeCardDetailsHover>
|
||||
)
|
||||
|
||||
expect(markup).toContain('Open in Orca browser')
|
||||
})
|
||||
|
||||
it('displays Linear issue details with link', () => {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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) && (
|
||||
<DropdownMenu
|
||||
modal={false}
|
||||
open={reviewMenuOpen}
|
||||
@@ -95,6 +97,20 @@ export function WorktreeCardReviewDetailSection({
|
||||
</Tooltip>
|
||||
)}
|
||||
<DropdownMenuContent align="end" className="w-40">
|
||||
{onOpenReviewInBrowser && (
|
||||
<DropdownMenuItem
|
||||
onSelect={() => {
|
||||
closeHover()
|
||||
onOpenReviewInBrowser(review.url!)
|
||||
}}
|
||||
>
|
||||
<Globe className="size-3.5" />
|
||||
{translate(
|
||||
'auto.components.sidebar.WorktreeCardMeta.openInOrcaBrowser',
|
||||
'Open in Orca browser'
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{onCopyReviewLink && (
|
||||
<DropdownMenuItem
|
||||
onSelect={() => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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={
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user