From 5718def4fdccfb67e4bf9bc3350cc06c13405538 Mon Sep 17 00:00:00 2001
From: mzxchandra <129460234+mzxchandra@users.noreply.github.com>
Date: Tue, 4 Aug 2026 22:27:50 -0700
Subject: [PATCH] feat(logs): open the workflow from the log details panel
(#6275)
* refactor(logs): centralize workflow-id resolution and editor path
The logs list, its context menu, and the details panel each resolved a
log's workflow id with their own copy of `workflow?.id || workflowId`,
and the list disagreed with the details panel on what counts as a
deleted workflow. Extract `resolveLogWorkflowId` and
`workflowEditorPath` so the three surfaces cannot drift.
`resolveLogWorkflowId` also returns null for Sim agent jobs, which have
no workflow of their own. Only the context menu's "Open Workflow" item
adopts that stricter predicate; cancel and retry keep using the previous
`hasWorkflow` check so their gating is unchanged.
* feat(logs): open the workflow from the log details panel
The workflow name in a log's details panel was static text, so the only
way to reach the workflow was the row's right-click context menu. Make
the label a link to the workflow editor, opening in a new tab so the
log list keeps its filters, scroll position, and open panel.
On hover or keyboard focus the leading workflow icon morphs into
SquareArrowUpRight, reusing the grid-stacked cross-fade already used by
the resource header breadcrumb. Sim agent jobs and deleted workflows
have no reachable workflow and stay static text.
Adds a `group-hover-hover` variant so the morph is gated on a real
hover-capable pointer, matching the existing `hover-hover` variant and
keeping touch devices out of a half-applied hover state.
* refactor(logs): use the existing group-hover variant for the workflow link
Drops the group-hover-hover Tailwind variant this branch added and moves
the details-panel workflow link to plain group-hover:, matching the
variant already used throughout the app. tailwind.config.ts is untouched
by the branch again; all colors, radii, and the focus ring come from
existing design tokens.
---
.../components/log-details/log-details.tsx | 51 ++++++++++++++---
.../log-row-context-menu.tsx | 9 ++-
.../app/workspace/[workspaceId]/logs/logs.tsx | 6 +-
.../[workspaceId]/logs/utils.test.ts | 55 +++++++++++++++++++
.../app/workspace/[workspaceId]/logs/utils.ts | 23 ++++++++
5 files changed, 132 insertions(+), 12 deletions(-)
create mode 100644 apps/sim/app/workspace/[workspaceId]/logs/utils.test.ts
diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/log-details.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/log-details.tsx
index bde89f51df..4990fd6609 100644
--- a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/log-details.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/log-details.tsx
@@ -29,11 +29,13 @@ import {
ChevronUp,
Clipboard,
Search,
+ SquareArrowUpRight,
Workflow,
Wrench,
X,
} from '@sim/emcn/icons'
import { formatDuration } from '@sim/utils/formatting'
+import Link from 'next/link'
import { useParams, useRouter } from 'next/navigation'
import { useQueryState } from 'nuqs'
import { createPortal } from 'react-dom'
@@ -59,8 +61,10 @@ import {
DELETED_WORKFLOW_LABEL,
formatDate,
getDisplayStatus,
+ resolveLogWorkflowId,
StatusBadge,
TriggerBadge,
+ workflowEditorPath,
} from '@/app/workspace/[workspaceId]/logs/utils'
import { useCodeViewerFeatures } from '@/hooks/use-code-viewer'
import { usePermissionConfig } from '@/hooks/use-permission-config'
@@ -317,6 +321,19 @@ export function LogDetailsContent({ log, onActiveTabChange }: LogDetailsContentP
const isWorkflowExecutionLog =
(log.trigger === 'manual' && !!log.duration) || !!log.executionData?.traceSpans
+ /**
+ * The workflow this run belongs to, when it is still reachable. Null for Sim
+ * agent jobs and deleted workflows, which render their label as static text.
+ */
+ const openableWorkflowId = resolveLogWorkflowId(log)
+
+ const workflowLabel =
+ log.trigger === 'mothership'
+ ? log.jobTitle || 'Untitled Job'
+ : openableWorkflowId
+ ? log.workflow?.name || 'Unknown'
+ : DELETED_WORKFLOW_LABEL
+
const hasCostInfo = !!(isWorkflowExecutionLog && log.cost)
const showWorkflowState =
isWorkflowExecutionLog &&
@@ -465,15 +482,31 @@ export function LogDetailsContent({ log, onActiveTabChange }: LogDetailsContentP
{log.trigger === 'mothership' ? 'Job' : 'Workflow'}
-