feat(frontend): open foreign-workspace runs links in a new tab

When a runs-page link targets a workspace other than the active one,
navigating in the same tab would switch the current tab's workspace. Open
those links in a new tab instead so the current workspace is preserved.

- RunsTable dropdown ("Show run details", "Go to script/flow page"):
  window.open when the job's workspace differs from the active one.
- RunRow parent-job links and RunBadges script link: target=_blank +
  rel=noopener when foreign (the main run link in RunRow was already _blank).

JobDetailHeader already opens its links in new tabs; FlowMetadata is only
used on public /approve pages (not the runs page) so it is left unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Diego Imbert
2026-06-01 21:03:17 +02:00
parent 7fd2c29996
commit 971cf78e17
3 changed files with 48 additions and 12 deletions
@@ -29,11 +29,20 @@
onFilterByConcurrencyKey,
large = false
}: Props = $props()
// Open links to another workspace in a new tab so we don't switch the active
// workspace of the current tab.
let isForeignWorkspace = $derived(
job.workspace_id != undefined && job.workspace_id !== $workspaceStore
)
</script>
{#if job.script_hash && showScriptHash && job.job_kind !== 'aiagent'}
{#if job.job_kind == 'script'}
<a href="{base}/scripts/get/{job.script_hash}?workspace={job.workspace_id ?? $workspaceStore}"
<a
href="{base}/scripts/get/{job.script_hash}?workspace={job.workspace_id ?? $workspaceStore}"
target={isForeignWorkspace ? '_blank' : undefined}
rel={isForeignWorkspace ? 'noopener noreferrer' : undefined}
><Badge color="gray" title={`Script hash: ${job.script_hash}`} {large}
>{truncateHash(job.script_hash)}
</Badge></a
+23 -8
View File
@@ -1,5 +1,6 @@
<script lang="ts">
import { base } from '$lib/base'
import { workspaceStore } from '$lib/stores'
import { goto } from '$lib/navigation'
import type { Job } from '$lib/gen'
import {
@@ -55,6 +56,12 @@
let isExternal = $derived(job && job.id === '-')
// Open links to another workspace in a new tab so we don't switch the active
// workspace of the current tab.
let isForeignWorkspace = $derived(
job?.workspace_id != undefined && job.workspace_id !== $workspaceStore
)
let labelWidth = $state(0)
let isJobRecent = $state(true)
@@ -156,19 +163,27 @@
{/if}
<JobKindIcon size={14} />
</div>
{#snippet text()}
<span>
{#if job && job.job_kind}
{getJobKindDisplayLabel(job.job_kind, job.script_path)}
{/if}
{#if job && job.is_flow_step && job.parent_job}
{#snippet text()}
<span>
{#if job && job.job_kind}
{getJobKindDisplayLabel(job.job_kind, job.script_path)}
{/if}
{#if job && job.is_flow_step && job.parent_job}
<br /> Step of flow
<a href={`${base}/run/${job.parent_job}?workspace=${job.workspace_id}`}>
<a
href={`${base}/run/${job.parent_job}?workspace=${job.workspace_id}`}
target={isForeignWorkspace ? '_blank' : undefined}
rel={isForeignWorkspace ? 'noopener noreferrer' : undefined}
>
{truncateRev(job.parent_job, 10)}
</a>
{:else if job && job.parent_job}
<br /> Parent
<a href={`${base}/run/${job.parent_job}?workspace=${job.workspace_id}`}>
<a
href={`${base}/run/${job.parent_job}?workspace=${job.workspace_id}`}
target={isForeignWorkspace ? '_blank' : undefined}
rel={isForeignWorkspace ? 'noopener noreferrer' : undefined}
>
{truncateRev(job.parent_job, 10)}
</a>
{/if}
@@ -20,6 +20,7 @@
import DropdownMenu, { type Props as DropdownMenuProps } from '../DropdownMenu.svelte'
import { clickOutside, isJobCancelable, isJobReRunnable } from '$lib/utils'
import { goto } from '$lib/navigation'
import { base } from '$lib/base'
import BarsStaggered from '../icons/BarsStaggered.svelte'
interface Props {
@@ -266,24 +267,35 @@
// one, so target the job's own workspace (not the active one).
const jobWorkspace =
(job?.type === 'job' ? job.job.workspace_id : undefined) ?? $workspaceStore
// When the job lives in a different workspace, open in a new tab so we
// don't switch the active workspace of the current tab.
const isForeignWorkspace = jobWorkspace != undefined && jobWorkspace !== $workspaceStore
const navigateTo = (path: string) => {
if (isForeignWorkspace) {
window.open(`${base}${path}`, '_blank')
} else {
goto(path)
}
}
actions.push({
label: 'Show run details',
icon: ExternalLinkIcon,
onClick: () => goto(`/run/${selectedIds[0]}?workspace=${jobWorkspace}`)
onClick: () => navigateTo(`/run/${selectedIds[0]}?workspace=${jobWorkspace}`)
})
if (job?.type === 'job') {
if (job.job.job_kind === 'script') {
actions.push({
label: 'Go to script page',
icon: Code2Icon,
onClick: () => goto(`/scripts/get/${job.job.script_hash}?workspace=${jobWorkspace}`)
onClick: () =>
navigateTo(`/scripts/get/${job.job.script_hash}?workspace=${jobWorkspace}`)
})
}
if (job.job.job_kind === 'flow') {
actions.push({
label: 'Go to flow page',
icon: BarsStaggered,
onClick: () => goto(`/flows/get/${job.job.script_path}?workspace=${jobWorkspace}`)
onClick: () => navigateTo(`/flows/get/${job.job.script_path}?workspace=${jobWorkspace}`)
})
}
}