@shipfox/client-workflows 14.0.0 → 15.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/.storybook/preview.tsx +7 -2
  2. package/.turbo/turbo-build.log +1 -1
  3. package/CHANGELOG.md +35 -0
  4. package/dist/components/job-detail/agent-config-failure-callout.d.ts.map +1 -0
  5. package/dist/components/{workflow-run-view → job-detail}/agent-config-failure-callout.js +2 -2
  6. package/dist/components/job-detail/agent-config-failure-callout.js.map +1 -0
  7. package/dist/components/job-detail/job-detail-header.d.ts +8 -0
  8. package/dist/components/job-detail/job-detail-header.d.ts.map +1 -0
  9. package/dist/components/job-detail/job-detail-header.js +111 -0
  10. package/dist/components/job-detail/job-detail-header.js.map +1 -0
  11. package/dist/components/job-detail/job-detail-view.d.ts +16 -0
  12. package/dist/components/job-detail/job-detail-view.d.ts.map +1 -0
  13. package/dist/components/job-detail/job-detail-view.js +405 -0
  14. package/dist/components/job-detail/job-detail-view.js.map +1 -0
  15. package/dist/components/job-detail/job-empty-states.d.ts +10 -0
  16. package/dist/components/job-detail/job-empty-states.d.ts.map +1 -0
  17. package/dist/components/job-detail/job-empty-states.js +195 -0
  18. package/dist/components/job-detail/job-empty-states.js.map +1 -0
  19. package/dist/components/job-detail/job-execution-switcher.d.ts.map +1 -0
  20. package/dist/components/job-detail/job-execution-switcher.js.map +1 -0
  21. package/dist/components/job-detail/job-execution-time-text.d.ts.map +1 -0
  22. package/dist/components/job-detail/job-execution-time-text.js.map +1 -0
  23. package/dist/components/job-detail/job-selection.d.ts +24 -0
  24. package/dist/components/job-detail/job-selection.d.ts.map +1 -0
  25. package/dist/components/job-detail/job-selection.js +66 -0
  26. package/dist/components/job-detail/job-selection.js.map +1 -0
  27. package/dist/components/job-detail/step-attempt-log-panel.d.ts +15 -0
  28. package/dist/components/job-detail/step-attempt-log-panel.d.ts.map +1 -0
  29. package/dist/components/{workflow-run-view → job-detail}/step-attempt-log-panel.js +61 -16
  30. package/dist/components/job-detail/step-attempt-log-panel.js.map +1 -0
  31. package/dist/components/job-graph/job-graph-content.js +1 -1
  32. package/dist/components/job-graph/job-graph-content.js.map +1 -1
  33. package/dist/components/step-list/step-list-model.js +2 -2
  34. package/dist/components/step-list/step-list-model.js.map +1 -1
  35. package/dist/components/step-list/step-list.d.ts +1 -0
  36. package/dist/components/step-list/step-list.d.ts.map +1 -1
  37. package/dist/components/step-list/step-list.js +29 -4
  38. package/dist/components/step-list/step-list.js.map +1 -1
  39. package/dist/components/workflow-run-duration-label.d.ts +9 -4
  40. package/dist/components/workflow-run-duration-label.d.ts.map +1 -1
  41. package/dist/components/workflow-run-duration-label.js +27 -7
  42. package/dist/components/workflow-run-duration-label.js.map +1 -1
  43. package/dist/components/workflow-run-list/workflow-run-row.d.ts.map +1 -1
  44. package/dist/components/workflow-run-list/workflow-run-row.js +6 -5
  45. package/dist/components/workflow-run-list/workflow-run-row.js.map +1 -1
  46. package/dist/components/workflow-run-summary/workflow-run-summary.d.ts.map +1 -1
  47. package/dist/components/workflow-run-summary/workflow-run-summary.js +68 -64
  48. package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -1
  49. package/dist/components/workflow-run-tabs/index.d.ts +0 -3
  50. package/dist/components/workflow-run-tabs/index.d.ts.map +1 -1
  51. package/dist/components/workflow-run-tabs/index.js +0 -3
  52. package/dist/components/workflow-run-tabs/index.js.map +1 -1
  53. package/dist/components/workflow-run-tabs/run-annotations-empty.d.ts +3 -1
  54. package/dist/components/workflow-run-tabs/run-annotations-empty.d.ts.map +1 -1
  55. package/dist/components/workflow-run-tabs/run-annotations-empty.js +2 -2
  56. package/dist/components/workflow-run-tabs/run-annotations-empty.js.map +1 -1
  57. package/dist/components/workflow-run-view/index.d.ts +0 -1
  58. package/dist/components/workflow-run-view/index.d.ts.map +1 -1
  59. package/dist/components/workflow-run-view/index.js +0 -1
  60. package/dist/components/workflow-run-view/index.js.map +1 -1
  61. package/dist/components/workflow-run-view/run-workspace-nav.d.ts +20 -0
  62. package/dist/components/workflow-run-view/run-workspace-nav.d.ts.map +1 -0
  63. package/dist/components/workflow-run-view/run-workspace-nav.js +271 -0
  64. package/dist/components/workflow-run-view/run-workspace-nav.js.map +1 -0
  65. package/dist/components/workflow-run-view/workflow-run-selection.d.ts.map +1 -1
  66. package/dist/components/workflow-run-view/workflow-run-selection.js +1 -14
  67. package/dist/components/workflow-run-view/workflow-run-selection.js.map +1 -1
  68. package/dist/components/workflow-run-view/workflow-run-view.d.ts +10 -8
  69. package/dist/components/workflow-run-view/workflow-run-view.d.ts.map +1 -1
  70. package/dist/components/workflow-run-view/workflow-run-view.js +250 -236
  71. package/dist/components/workflow-run-view/workflow-run-view.js.map +1 -1
  72. package/dist/components/workflow-status/status-visuals.d.ts.map +1 -1
  73. package/dist/components/workflow-status/status-visuals.js +10 -2
  74. package/dist/components/workflow-status/status-visuals.js.map +1 -1
  75. package/dist/components/workflow-status/workflow-status-icon.d.ts +3 -1
  76. package/dist/components/workflow-status/workflow-status-icon.d.ts.map +1 -1
  77. package/dist/components/workflow-status/workflow-status-icon.js +13 -2
  78. package/dist/components/workflow-status/workflow-status-icon.js.map +1 -1
  79. package/dist/core/entities/job-execution.d.ts +9 -0
  80. package/dist/core/entities/job-execution.d.ts.map +1 -1
  81. package/dist/core/entities/job-execution.js +13 -3
  82. package/dist/core/entities/job-execution.js.map +1 -1
  83. package/dist/core/entities/job.d.ts.map +1 -1
  84. package/dist/core/entities/step-attempt.d.ts.map +1 -1
  85. package/dist/core/entities/step.d.ts +2 -0
  86. package/dist/core/entities/step.d.ts.map +1 -1
  87. package/dist/core/entities/step.js +13 -0
  88. package/dist/core/entities/step.js.map +1 -1
  89. package/dist/core/entities/workflow-run-attempt.d.ts.map +1 -1
  90. package/dist/core/entities/workflow-run.d.ts +56 -1
  91. package/dist/core/entities/workflow-run.d.ts.map +1 -1
  92. package/dist/core/entities/workflow-run.js +98 -0
  93. package/dist/core/entities/workflow-run.js.map +1 -1
  94. package/dist/core/workflow-run.d.ts +4 -4
  95. package/dist/core/workflow-run.d.ts.map +1 -1
  96. package/dist/core/workflow-run.js +3 -3
  97. package/dist/core/workflow-run.js.map +1 -1
  98. package/dist/feature.d.ts +20 -16
  99. package/dist/feature.d.ts.map +1 -1
  100. package/dist/feature.js +5 -0
  101. package/dist/feature.js.map +1 -1
  102. package/dist/hooks/api/workflow-runs.d.ts +5 -3
  103. package/dist/hooks/api/workflow-runs.d.ts.map +1 -1
  104. package/dist/hooks/api/workflow-runs.js +5 -4
  105. package/dist/hooks/api/workflow-runs.js.map +1 -1
  106. package/dist/index.d.ts +1 -0
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +1 -0
  109. package/dist/index.js.map +1 -1
  110. package/dist/pages/workflow-job-detail-page.d.ts +11 -0
  111. package/dist/pages/workflow-job-detail-page.d.ts.map +1 -0
  112. package/dist/pages/workflow-job-detail-page.js +66 -0
  113. package/dist/pages/workflow-job-detail-page.js.map +1 -0
  114. package/dist/pages/workflow-run-detail-page.d.ts.map +1 -1
  115. package/dist/pages/workflow-run-detail-page.js +4 -37
  116. package/dist/pages/workflow-run-detail-page.js.map +1 -1
  117. package/dist/routes/inputs.d.ts +47 -34
  118. package/dist/routes/inputs.d.ts.map +1 -1
  119. package/dist/routes/inputs.js +69 -37
  120. package/dist/routes/inputs.js.map +1 -1
  121. package/dist/routes/job-detail.d.ts +10 -0
  122. package/dist/routes/job-detail.d.ts.map +1 -0
  123. package/dist/routes/job-detail.js +27 -0
  124. package/dist/routes/job-detail.js.map +1 -0
  125. package/dist/routes/project-route.d.ts +1 -1
  126. package/dist/routes/run-detail.d.ts.map +1 -1
  127. package/dist/routes/runs.d.ts.map +1 -1
  128. package/dist/routes/workflows.d.ts.map +1 -1
  129. package/dist/tsconfig.test.tsbuildinfo +1 -1
  130. package/package.json +7 -7
  131. package/src/components/{workflow-run-view → job-detail}/agent-config-failure-callout.stories.tsx +1 -1
  132. package/src/components/{workflow-run-view → job-detail}/agent-config-failure-callout.tsx +2 -2
  133. package/src/components/job-detail/job-detail-header.test.tsx +31 -0
  134. package/src/components/job-detail/job-detail-header.tsx +111 -0
  135. package/src/components/job-detail/job-detail-view.stories.tsx +526 -0
  136. package/src/components/job-detail/job-detail-view.tsx +450 -0
  137. package/src/components/job-detail/job-empty-states.test.ts +27 -0
  138. package/src/components/job-detail/job-empty-states.tsx +248 -0
  139. package/src/components/job-detail/job-selection.test.ts +188 -0
  140. package/src/components/job-detail/job-selection.ts +96 -0
  141. package/src/components/{workflow-run-view → job-detail}/step-attempt-log-panel.test.tsx +32 -9
  142. package/src/components/{workflow-run-view → job-detail}/step-attempt-log-panel.tsx +52 -18
  143. package/src/components/job-graph/job-graph-content.tsx +1 -1
  144. package/src/components/step-list/step-list-model.test.ts +1 -1
  145. package/src/components/step-list/step-list-model.ts +2 -2
  146. package/src/components/step-list/step-list.stories.tsx +1 -1
  147. package/src/components/step-list/step-list.test.tsx +33 -0
  148. package/src/components/step-list/step-list.tsx +40 -9
  149. package/src/components/workflow-run-duration-label.tsx +50 -12
  150. package/src/components/workflow-run-list/workflow-run-row.tsx +6 -4
  151. package/src/components/workflow-run-summary/workflow-run-summary.stories.tsx +38 -0
  152. package/src/components/workflow-run-summary/workflow-run-summary.test.tsx +79 -5
  153. package/src/components/workflow-run-summary/workflow-run-summary.tsx +56 -60
  154. package/src/components/workflow-run-tabs/index.ts +0 -3
  155. package/src/components/workflow-run-tabs/run-annotations-empty.tsx +6 -2
  156. package/src/components/workflow-run-view/index.ts +0 -1
  157. package/src/components/workflow-run-view/run-workspace-nav.test.tsx +242 -0
  158. package/src/components/workflow-run-view/run-workspace-nav.tsx +301 -0
  159. package/src/components/workflow-run-view/workflow-run-selection.ts +1 -23
  160. package/src/components/workflow-run-view/workflow-run-view.test.tsx +62 -1136
  161. package/src/components/workflow-run-view/workflow-run-view.tsx +330 -321
  162. package/src/components/workflow-status/status-visuals.ts +5 -2
  163. package/src/components/workflow-status/workflow-status-icon.stories.tsx +1 -0
  164. package/src/components/workflow-status/workflow-status-icon.tsx +26 -1
  165. package/src/core/entities/job-execution.ts +17 -2
  166. package/src/core/entities/step.ts +26 -0
  167. package/src/core/entities/workflow-run-display.test.ts +159 -0
  168. package/src/core/entities/workflow-run.ts +139 -1
  169. package/src/core/workflow-run.ts +16 -1
  170. package/src/feature.ts +5 -0
  171. package/src/hooks/api/workflow-runs.ts +6 -2
  172. package/src/index.ts +1 -0
  173. package/src/page-harness-budget.test.ts +1 -0
  174. package/src/pages/workflow-job-detail-page.test.tsx +539 -0
  175. package/src/pages/workflow-job-detail-page.tsx +84 -0
  176. package/src/pages/workflow-run-detail-page.tsx +3 -43
  177. package/src/pages/workflow-run-pages.test.tsx +77 -332
  178. package/src/routes/inputs.test.ts +56 -6
  179. package/src/routes/inputs.ts +64 -17
  180. package/src/routes/job-detail.tsx +29 -0
  181. package/test/pages.tsx +25 -3
  182. package/test/render.tsx +6 -1
  183. package/tsconfig.build.tsbuildinfo +1 -1
  184. package/dist/components/workflow-run-tabs/run-jobs-list.d.ts +0 -18
  185. package/dist/components/workflow-run-tabs/run-jobs-list.d.ts.map +0 -1
  186. package/dist/components/workflow-run-tabs/run-jobs-list.js +0 -107
  187. package/dist/components/workflow-run-tabs/run-jobs-list.js.map +0 -1
  188. package/dist/components/workflow-run-tabs/run-tab-count.d.ts +0 -6
  189. package/dist/components/workflow-run-tabs/run-tab-count.d.ts.map +0 -1
  190. package/dist/components/workflow-run-tabs/run-tab-count.js +0 -27
  191. package/dist/components/workflow-run-tabs/run-tab-count.js.map +0 -1
  192. package/dist/components/workflow-run-tabs/run-tab-strip.d.ts +0 -8
  193. package/dist/components/workflow-run-tabs/run-tab-strip.d.ts.map +0 -1
  194. package/dist/components/workflow-run-tabs/run-tab-strip.js +0 -82
  195. package/dist/components/workflow-run-tabs/run-tab-strip.js.map +0 -1
  196. package/dist/components/workflow-run-view/agent-config-failure-callout.d.ts.map +0 -1
  197. package/dist/components/workflow-run-view/agent-config-failure-callout.js.map +0 -1
  198. package/dist/components/workflow-run-view/job-card.d.ts +0 -17
  199. package/dist/components/workflow-run-view/job-card.d.ts.map +0 -1
  200. package/dist/components/workflow-run-view/job-card.js +0 -496
  201. package/dist/components/workflow-run-view/job-card.js.map +0 -1
  202. package/dist/components/workflow-run-view/job-execution-switcher.d.ts.map +0 -1
  203. package/dist/components/workflow-run-view/job-execution-switcher.js.map +0 -1
  204. package/dist/components/workflow-run-view/job-execution-time-text.d.ts.map +0 -1
  205. package/dist/components/workflow-run-view/job-execution-time-text.js.map +0 -1
  206. package/dist/components/workflow-run-view/step-attempt-log-panel.d.ts +0 -9
  207. package/dist/components/workflow-run-view/step-attempt-log-panel.d.ts.map +0 -1
  208. package/dist/components/workflow-run-view/step-attempt-log-panel.js.map +0 -1
  209. package/src/components/workflow-run-tabs/run-jobs-list.tsx +0 -151
  210. package/src/components/workflow-run-tabs/run-tab-count.tsx +0 -28
  211. package/src/components/workflow-run-tabs/run-tab-strip.stories.tsx +0 -52
  212. package/src/components/workflow-run-tabs/run-tab-strip.test.tsx +0 -117
  213. package/src/components/workflow-run-tabs/run-tab-strip.tsx +0 -97
  214. package/src/components/workflow-run-view/job-card.stories.tsx +0 -789
  215. package/src/components/workflow-run-view/job-card.test.tsx +0 -115
  216. package/src/components/workflow-run-view/job-card.tsx +0 -614
  217. /package/dist/components/{workflow-run-view → job-detail}/agent-config-failure-callout.d.ts +0 -0
  218. /package/dist/components/{workflow-run-view → job-detail}/job-execution-switcher.d.ts +0 -0
  219. /package/dist/components/{workflow-run-view → job-detail}/job-execution-switcher.js +0 -0
  220. /package/dist/components/{workflow-run-view → job-detail}/job-execution-time-text.d.ts +0 -0
  221. /package/dist/components/{workflow-run-view → job-detail}/job-execution-time-text.js +0 -0
  222. /package/src/components/{workflow-run-view → job-detail}/job-execution-switcher.tsx +0 -0
  223. /package/src/components/{workflow-run-view → job-detail}/job-execution-time-text.tsx +0 -0
@@ -1,29 +1,29 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ApiError } from '@shipfox/client-api';
3
3
  import { QueryLoadError } from '@shipfox/client-ui';
4
4
  import { EmptyState } from '@shipfox/react-ui/empty-state';
5
5
  import { RelativeTimeProvider } from '@shipfox/react-ui/relative-time';
6
- import { Tabs, TabsContent, TabsContents } from '@shipfox/react-ui/tabs';
6
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@shipfox/react-ui/select';
7
7
  import { toast } from '@shipfox/react-ui/toast';
8
8
  import { useNavigate } from '@tanstack/react-router';
9
- import { useEffect, useId, useRef, useState } from 'react';
10
- import { resolveJobExecution } from '#core/workflow-run.js';
9
+ import { useEffect } from 'react';
11
10
  import { withoutWorkflowRunSelectionSearch } from '#core/workflow-run-url-state.js';
12
11
  import { useCancelWorkflowRunMutation, useRerunWorkflowRunMutation, useWorkflowRunAttemptQuery } from '#hooks/api/workflow-runs.js';
13
- import { WORKFLOW_RUN_TABS } from '#routes/inputs.js';
12
+ import { workflowJobSearchParams, workflowRunSearchParams } from '#routes/inputs.js';
14
13
  import { JobGraph } from '../job-graph/index.js';
15
14
  import { WorkflowRunSummary } from '../workflow-run-summary/index.js';
16
- import { RunAnnotationSummaryLine, RunAnnotationsEmpty, RunJobsList, RunTabStrip } from '../workflow-run-tabs/index.js';
17
- import { WorkflowSourceContent, WorkflowSourcePanel } from '../workflow-source-panel/index.js';
15
+ import { RunAnnotationSummaryLine, RunAnnotationsEmpty } from '../workflow-run-tabs/index.js';
16
+ import { WorkflowSourceContent } from '../workflow-source-panel/index.js';
17
+ import { RunWorkspaceNav } from './run-workspace-nav.js';
18
18
  import { resolveWorkflowRunSelection } from './workflow-run-selection.js';
19
19
  import { WorkflowRunContentSkeleton, WorkflowRunNotFound, WorkflowRunSkeleton, WorkflowRunStaleError } from './workflow-run-states.js';
20
20
  /**
21
- * Renders the run for `workflowRunId`, keeping the local tab strip mounted while the run
22
- * request is pending so polling never inserts navigation under the user's cursor.
23
- */ export function WorkflowRunView({ projectId, workspaceSlug, projectSlug, workflowRunId, selection, onSelectionChange, tab, onTabChange, annotationSummary }) {
21
+ * The persistent run workspace. Run-level sections and dedicated job routes share the same
22
+ * header and job index, while the all-jobs Summary always opens on the dependency graph.
23
+ */ export function WorkflowRunView({ projectId, workspaceSlug, projectSlug, workflowRunId, runAttempt, selection, tab, annotationSummary, activeJobId, jobSearch, jobContent }) {
24
24
  const runQuery = useWorkflowRunAttemptQuery({
25
25
  workflowRunId,
26
- runAttempt: selection?.runAttempt
26
+ runAttempt: selection?.runAttempt ?? runAttempt
27
27
  });
28
28
  const rerunMutation = useRerunWorkflowRunMutation(projectId);
29
29
  return /*#__PURE__*/ _jsx(RelativeTimeProvider, {
@@ -34,79 +34,59 @@ import { WorkflowRunContentSkeleton, WorkflowRunNotFound, WorkflowRunSkeleton, W
34
34
  projectSlug: projectSlug,
35
35
  query: runQuery,
36
36
  rerunMutation: rerunMutation,
37
+ runAttempt: runAttempt,
37
38
  selection: selection,
38
- onSelectionChange: onSelectionChange,
39
39
  tab: tab,
40
- onTabChange: onTabChange,
41
- annotationSummary: annotationSummary
40
+ annotationSummary: annotationSummary,
41
+ activeJobId: activeJobId,
42
+ jobSearch: jobSearch,
43
+ jobContent: jobContent
42
44
  })
43
45
  })
44
46
  });
45
47
  }
46
- function RunViewContent({ workspaceSlug, projectSlug, query, rerunMutation, selection, onSelectionChange, tab, onTabChange, annotationSummary }) {
48
+ function RunViewContent({ workspaceSlug, projectSlug, query, rerunMutation, runAttempt, selection, tab, annotationSummary, activeJobId, jobSearch, jobContent }) {
47
49
  const navigate = useNavigate();
48
- const [selectedJobId, setSelectedJobId] = useState();
49
- const [selectedJobExecutionId, setSelectedJobExecutionId] = useState();
50
- const [localTab, setLocalTab] = useState(tab ?? 'summary');
51
- const [sourcePanelOpen, setSourcePanelOpen] = useState(false);
52
- const [sourceFocus, setSourceFocus] = useState(null);
53
- const sourcePanelId = useId();
54
- // The step Source button that last opened the panel, so Escape / Close returns focus to it.
55
- const lastSourceTriggerRef = useRef(null);
56
- const selectionControlled = selection !== undefined;
57
- const tabControlled = onTabChange !== undefined;
58
- const activeTab = tabControlled ? tab ?? 'summary' : localTab;
59
- const sourceAvailable = query.data?.sourceSnapshot !== null && query.data?.sourceSnapshot !== undefined;
60
- const cancelMutation = useCancelWorkflowRunMutation(query.data);
61
- useEffect(()=>{
62
- if (!sourceAvailable) {
63
- setSourcePanelOpen(false);
64
- setSourceFocus(null);
65
- }
66
- }, [
67
- sourceAvailable
68
- ]);
69
- // If a refetch drops the focused step or its location, close the panel so it never points at
70
- // an unmounted Source button.
71
- useEffect(()=>{
72
- if (!sourceFocus) return;
73
- const stillLocated = query.data?.jobs.some((job)=>job.jobExecutions.some((execution)=>execution.steps.some((step)=>step.id === sourceFocus.stepId && step.sourceLocation)));
74
- if (!stillLocated) {
75
- setSourceFocus(null);
76
- setSourcePanelOpen(false);
77
- }
78
- }, [
79
- sourceFocus,
80
- query.data
81
- ]);
82
50
  const runData = query.data;
83
- const resolvedSelection = selectionControlled && runData ? resolveWorkflowRunSelection({
51
+ const activeSection = runWorkspaceSection(tab);
52
+ const cancelMutation = useCancelWorkflowRunMutation(runData);
53
+ const sourceSnapshot = runData?.sourceSnapshot ?? null;
54
+ const resolvedSelection = runData && selection ? resolveWorkflowRunSelection({
84
55
  run: runData,
85
- selection: selection
56
+ selection
86
57
  }) : undefined;
87
- const hasExplicitJobSelection = Boolean(selection?.jobId || selection?.jobExecutionId || selection?.stepId || selection?.stepAttemptId);
88
- const selectedJob = selectionControlled ? hasExplicitJobSelection ? resolvedSelection?.job : undefined : runData?.jobs.find((job)=>job.id === selectedJobId) ?? runData?.jobs.at(0);
89
- const selectedJobExecution = selectionControlled ? resolvedSelection?.jobExecution : selectedJob ? resolveJobExecution(selectedJob, selectedJobExecutionId) : undefined;
90
- const selectedAttemptId = selectionControlled ? resolvedSelection?.selectedAttemptId ?? null : undefined;
91
- // Explicit per-step focus wins; fall back to the URL-selected step so deep links still
92
- // pre-highlight source in the Source tab.
93
- const highlightedLineRange = sourceFocus?.location ?? resolvedSelection?.step?.sourceLocation ?? null;
94
- const sourceSnapshot = runData?.sourceSnapshot ?? null;
95
- function changeTab(nextTab) {
96
- if (!tabControlled) {
97
- setLocalTab(nextTab);
98
- } else if (activeTab !== nextTab) {
99
- onTabChange?.(nextTab);
100
- }
101
- if (nextTab !== 'jobs') {
102
- setSourcePanelOpen(false);
103
- setSourceFocus(null);
58
+ const highlightedLineRange = resolvedSelection?.step?.sourceLocation ?? null;
59
+ useEffect(()=>{
60
+ const hasLegacyJobSelection = Boolean(selection?.jobId || selection?.jobExecutionId || selection?.stepId || selection?.stepAttemptId);
61
+ if (activeJobId || activeSection !== 'summary' || !hasLegacyJobSelection || !resolvedSelection?.job || !runData || !workspaceSlug || !projectSlug) {
62
+ return;
104
63
  }
105
- }
106
- function handleTabChange(nextTab) {
107
- if (!WORKFLOW_RUN_TABS.some((value)=>value === nextTab)) return;
108
- changeTab(nextTab);
109
- }
64
+ void navigate({
65
+ to: '/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId/jobs/$jobId',
66
+ params: {
67
+ workspaceSlug,
68
+ projectSlug,
69
+ workflowRunId: runData.id,
70
+ jobId: resolvedSelection.job.id
71
+ },
72
+ search: workflowJobSearchParams({
73
+ jobExecutionId: selection?.jobExecutionId,
74
+ stepId: selection?.stepId,
75
+ stepAttemptId: selection?.stepAttemptId,
76
+ runAttempt: selection?.runAttempt
77
+ }),
78
+ replace: true
79
+ });
80
+ }, [
81
+ activeJobId,
82
+ activeSection,
83
+ navigate,
84
+ projectSlug,
85
+ resolvedSelection?.job,
86
+ runData,
87
+ selection,
88
+ workspaceSlug
89
+ ]);
110
90
  async function rerun(mode) {
111
91
  if (!runData || !workspaceSlug || !projectSlug) {
112
92
  toast.error('Could not start re-run from this route.');
@@ -131,206 +111,240 @@ function RunViewContent({ workspaceSlug, projectSlug, query, rerunMutation, sele
131
111
  toast.error(error instanceof ApiError ? error.message : 'Could not start re-run');
132
112
  }
133
113
  }
134
- function selectJob(jobId, source = 'pointer') {
135
- if (source === 'pointer') changeTab('jobs');
136
- if (!selectionControlled) {
137
- setSelectedJobId(jobId);
138
- setSelectedJobExecutionId(undefined);
139
- return;
140
- }
141
- // Keyboard roving focus is an interaction within the Summary graph. Keep that panel mounted
142
- // and let JobGraphView own the visual selection so focus is not moved to a different tab.
143
- if (source === 'keyboard') return;
144
- onSelectionChange?.(jobId ? {
145
- jobId,
146
- runAttempt: selection?.runAttempt
147
- } : {
148
- runAttempt: selection?.runAttempt
149
- });
150
- }
151
- function selectJobExecution(jobExecutionId) {
152
- if (!selectionControlled) {
153
- setSelectedJobExecutionId(jobExecutionId);
154
- return;
155
- }
156
- if (!selectedJob) return;
157
- onSelectionChange?.({
158
- jobId: selectedJob.id,
159
- jobExecutionId,
160
- runAttempt: selection?.runAttempt
161
- });
162
- }
163
- function selectAttempt(attemptId) {
164
- if (!selectionControlled || !selectedJob || !selectedJobExecution) return;
165
- if (!attemptId) {
166
- onSelectionChange?.({
167
- jobId: selectedJob.id,
168
- jobExecutionId: selectedJobExecution.id,
169
- runAttempt: selection?.runAttempt
170
- });
171
- return;
172
- }
173
- const match = findAttemptSelection(selectedJobExecution, attemptId);
174
- if (!match) return;
175
- onSelectionChange?.({
176
- jobId: selectedJob.id,
177
- jobExecutionId: selectedJobExecution.id,
178
- stepId: match.stepId,
179
- stepAttemptId: match.attemptId,
180
- runAttempt: selection?.runAttempt
114
+ function selectGraphJob(jobId, source = 'pointer') {
115
+ if (source !== 'pointer' || !jobId || !runData || !workspaceSlug || !projectSlug) return;
116
+ void navigate({
117
+ to: '/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId/jobs/$jobId',
118
+ params: {
119
+ workspaceSlug,
120
+ projectSlug,
121
+ workflowRunId: runData.id,
122
+ jobId
123
+ },
124
+ search: workflowJobSearchParams({
125
+ runAttempt: selection?.runAttempt ?? runAttempt ?? runData.runAttempt.attempt
126
+ })
181
127
  });
182
128
  }
183
- function openStepSource(stepId, location, trigger) {
184
- setSourceFocus({
185
- stepId,
186
- location
129
+ function selectAnnotationJob(jobId) {
130
+ if (!runData || !workspaceSlug || !projectSlug) return;
131
+ const nextSearch = {
132
+ ...selection,
133
+ tab: 'annotations'
134
+ };
135
+ if (jobId) nextSearch.jobId = jobId;
136
+ else delete nextSearch.jobId;
137
+ delete nextSearch.jobExecutionId;
138
+ delete nextSearch.stepId;
139
+ delete nextSearch.stepAttemptId;
140
+ void navigate({
141
+ to: '/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId',
142
+ params: {
143
+ workspaceSlug,
144
+ projectSlug,
145
+ workflowRunId: runData.id
146
+ },
147
+ search: workflowRunSearchParams(nextSearch, nextSearch)
187
148
  });
188
- lastSourceTriggerRef.current = trigger;
189
- setSourcePanelOpen(true);
190
- }
191
- function closeSourcePanel() {
192
- const trigger = lastSourceTriggerRef.current;
193
- setSourcePanelOpen(false);
194
- // Defer so focus lands after the panel unmounts; clear the focus only after focusing so the
195
- // opener button is still expanded (force-visible) on return.
196
- window.setTimeout(()=>{
197
- if (trigger?.isConnected) trigger.focus();
198
- setSourceFocus(null);
199
- }, 0);
200
149
  }
201
150
  function cancelRun() {
202
151
  cancelMutation.mutate(undefined, {
203
- onError: (error)=>{
204
- toast.error(cancelErrorMessage(error));
205
- }
152
+ onError: (error)=>toast.error(cancelErrorMessage(error))
206
153
  });
207
154
  }
208
155
  const fatalError = query.isError && runData === undefined;
209
- const tabState = fatalError ? query.error instanceof ApiError && query.error.status === 404 ? /*#__PURE__*/ _jsx(WorkflowRunNotFound, {}) : /*#__PURE__*/ _jsx(QueryLoadError, {
156
+ const loadingOrError = fatalError ? query.error instanceof ApiError && query.error.status === 404 ? /*#__PURE__*/ _jsx(WorkflowRunNotFound, {}) : /*#__PURE__*/ _jsx(QueryLoadError, {
210
157
  query: query,
211
158
  subject: "workflow run",
212
159
  icon: "pulseLine"
213
160
  }) : query.isPending || runData === undefined ? /*#__PURE__*/ _jsx(WorkflowRunContentSkeleton, {}) : null;
214
- return /*#__PURE__*/ _jsxs(_Fragment, {
161
+ return /*#__PURE__*/ _jsxs("div", {
162
+ className: "flex min-h-0 min-w-0 flex-1 flex-col",
215
163
  children: [
216
- /*#__PURE__*/ _jsxs(Tabs, {
217
- value: activeTab,
218
- onValueChange: handleTabChange,
219
- className: "flex min-w-0 flex-1 flex-col gap-0",
164
+ runData ? /*#__PURE__*/ _jsx(WorkflowRunSummary, {
165
+ workspaceSlug: workspaceSlug,
166
+ projectSlug: projectSlug,
167
+ run: runData,
168
+ cancelling: cancelMutation.isPending,
169
+ onCancel: cancelRun,
170
+ rerunPending: rerunMutation.isPending,
171
+ onRerun: (mode)=>void rerun(mode),
172
+ latestAttempt: runData.latestAttempt
173
+ }) : /*#__PURE__*/ _jsx(WorkflowRunSkeleton, {}),
174
+ runData && query.isError ? /*#__PURE__*/ _jsx(WorkflowRunStaleError, {
175
+ query: query
176
+ }) : null,
177
+ /*#__PURE__*/ _jsxs("div", {
178
+ "data-run-workspace-layout": true,
179
+ className: "flex min-h-0 min-w-0 flex-1 flex-col border-t border-border-neutral-base min-[768px]:flex-row",
220
180
  children: [
221
- runData ? /*#__PURE__*/ _jsx(WorkflowRunSummary, {
181
+ runData && workspaceSlug && projectSlug ? /*#__PURE__*/ _jsx(RunWorkspaceNav, {
222
182
  workspaceSlug: workspaceSlug,
223
183
  projectSlug: projectSlug,
224
184
  run: runData,
225
- cancelling: cancelMutation.isPending,
226
- onCancel: cancelRun,
227
- rerunPending: rerunMutation.isPending,
228
- onRerun: (mode)=>void rerun(mode),
229
- latestAttempt: runData.latestAttempt
230
- }) : /*#__PURE__*/ _jsx(WorkflowRunSkeleton, {}),
231
- runData && query.isError ? /*#__PURE__*/ _jsx(WorkflowRunStaleError, {
232
- query: query
233
- }) : null,
234
- /*#__PURE__*/ _jsx(RunTabStrip, {
235
- jobCount: runData?.jobs.length,
236
- jobsFailed: runData?.jobs.filter((job)=>job.status === 'failed').length,
185
+ activeSection: activeSection,
186
+ currentJobId: activeJobId,
187
+ jobSearch: jobSearch,
237
188
  annotationSummary: annotationSummary
238
- }),
239
- /*#__PURE__*/ _jsxs(TabsContents, {
240
- className: "min-h-0 flex-1 overflow-auto bg-background-neutral-background pb-24 pt-16",
189
+ }) : /*#__PURE__*/ _jsx(RunWorkspaceNavSkeleton, {}),
190
+ /*#__PURE__*/ _jsx("div", {
191
+ "data-run-workspace-content": true,
192
+ className: "flex min-h-0 min-w-0 flex-1 flex-col bg-background-neutral-base",
193
+ children: runData && jobContent ? jobContent : runData ? /*#__PURE__*/ _jsx(RunSectionContent, {
194
+ section: activeSection,
195
+ run: runData,
196
+ annotationSummary: annotationSummary,
197
+ workspaceSlug: workspaceSlug,
198
+ projectSlug: projectSlug,
199
+ selection: selection,
200
+ selectedJobId: selection?.jobId,
201
+ onSelectGraphJob: selectGraphJob,
202
+ onSelectAnnotationJob: selectAnnotationJob,
203
+ sourceSnapshot: sourceSnapshot,
204
+ highlightedLineRange: highlightedLineRange
205
+ }) : /*#__PURE__*/ _jsx("div", {
206
+ className: "min-h-0 flex-1 overflow-auto p-24",
207
+ children: loadingOrError
208
+ })
209
+ })
210
+ ]
211
+ })
212
+ ]
213
+ });
214
+ }
215
+ function RunSectionContent({ section, run, annotationSummary, workspaceSlug, projectSlug, selection, selectedJobId, onSelectGraphJob, onSelectAnnotationJob, sourceSnapshot, highlightedLineRange }) {
216
+ if (section === 'summary') {
217
+ return /*#__PURE__*/ _jsx("section", {
218
+ "aria-label": "All jobs summary",
219
+ className: "min-h-0 flex-1 overflow-auto pb-24 pt-16",
220
+ children: /*#__PURE__*/ _jsx("div", {
221
+ className: "mx-auto flex w-full max-w-[1120px] flex-col gap-16 px-24",
222
+ children: /*#__PURE__*/ _jsx(JobGraph, {
223
+ run: run,
224
+ selectedJobId: selectedJobId,
225
+ onSelectedJobChange: onSelectGraphJob,
226
+ className: "min-h-160 overflow-hidden"
227
+ })
228
+ })
229
+ });
230
+ }
231
+ if (section === 'annotations') {
232
+ const selectedJob = run.jobs.find((job)=>job.id === selectedJobId);
233
+ return /*#__PURE__*/ _jsx("section", {
234
+ "aria-label": "Run annotations",
235
+ className: "min-h-0 flex-1 overflow-auto pb-24 pt-16",
236
+ children: /*#__PURE__*/ _jsxs("div", {
237
+ className: "mx-auto flex w-full max-w-[1120px] flex-col gap-16 px-24",
238
+ children: [
239
+ /*#__PURE__*/ _jsxs("div", {
240
+ className: "flex flex-wrap items-center justify-between gap-8",
241
241
  children: [
242
- /*#__PURE__*/ _jsx(TabsContent, {
243
- value: "summary",
244
- tabIndex: -1,
245
- className: "mx-auto flex w-full max-w-[1120px] flex-col gap-16 px-24 outline-none",
246
- children: runData ? /*#__PURE__*/ _jsx(JobGraph, {
247
- run: runData,
248
- selectedJobId: selectedJob?.id,
249
- onSelectedJobChange: selectJob
250
- }) : tabState
251
- }),
252
- /*#__PURE__*/ _jsx(TabsContent, {
253
- value: "jobs",
254
- tabIndex: -1,
255
- className: "mx-auto w-full max-w-[1120px] px-24 outline-none",
256
- children: runData ? /*#__PURE__*/ _jsx(RunJobsList, {
257
- jobs: runData.jobs,
258
- selectedJobId: selectedJob?.id,
259
- onSelectedJobChange: selectJob,
260
- workspaceSlug: workspaceSlug,
261
- selectedJobExecution: selectedJobExecution,
262
- selectedAttemptId: selectedAttemptId,
263
- onSelectedJobExecutionChange: selectJobExecution,
264
- onSelectedAttemptChange: selectionControlled ? selectAttempt : undefined,
265
- sourcePanelId: sourcePanelId,
266
- sourceAvailable: sourceAvailable,
267
- focusedSourceStepId: sourceFocus?.stepId ?? null,
268
- onOpenStepSource: openStepSource
269
- }) : tabState
270
- }),
271
- /*#__PURE__*/ _jsx(TabsContent, {
272
- value: "annotations",
273
- tabIndex: -1,
274
- className: "mx-auto flex w-full max-w-[1120px] flex-col gap-16 px-24 outline-none",
275
- children: runData ? /*#__PURE__*/ _jsxs(_Fragment, {
276
- children: [
277
- /*#__PURE__*/ _jsx(RunAnnotationSummaryLine, {
278
- summary: annotationSummary,
279
- workspaceSlug: workspaceSlug,
280
- projectSlug: projectSlug,
281
- workflowRunId: runData.id,
282
- search: selection
283
- }),
284
- /*#__PURE__*/ _jsx(RunAnnotationsEmpty, {})
285
- ]
286
- }) : tabState
242
+ /*#__PURE__*/ _jsx(RunAnnotationSummaryLine, {
243
+ summary: annotationSummary,
244
+ workspaceSlug: workspaceSlug,
245
+ projectSlug: projectSlug,
246
+ workflowRunId: run.id,
247
+ search: selection
287
248
  }),
288
- /*#__PURE__*/ _jsx(TabsContent, {
289
- value: "source",
290
- keepMounted: sourceSnapshot !== null,
291
- tabIndex: -1,
292
- className: "mx-auto flex min-h-full w-full max-w-[1120px] px-24 outline-none",
293
- children: runData ? sourceSnapshot ? /*#__PURE__*/ _jsx(WorkflowSourceContent, {
294
- source: sourceSnapshot,
295
- highlightedLineRange: highlightedLineRange,
296
- scrollHighlightedIntoView: true
297
- }) : /*#__PURE__*/ _jsx(EmptyState, {
298
- className: "min-h-160 w-full",
299
- icon: "fileDamageLine",
300
- title: "Source snapshot unavailable",
301
- description: runData.isTemporary ? 'Temporary runs do not capture workflow source.' : 'This run was created before workflow source snapshots were captured.'
302
- }) : tabState
249
+ /*#__PURE__*/ _jsx(AnnotationJobFilter, {
250
+ jobs: run.jobs,
251
+ selectedJobId: selectedJob?.id,
252
+ onSelect: onSelectAnnotationJob
303
253
  })
304
254
  ]
255
+ }),
256
+ /*#__PURE__*/ _jsx(RunAnnotationsEmpty, {
257
+ jobName: selectedJob?.displayName
305
258
  })
306
259
  ]
307
- }),
308
- /*#__PURE__*/ _jsx(WorkflowSourcePanel, {
309
- id: sourcePanelId,
260
+ })
261
+ });
262
+ }
263
+ return /*#__PURE__*/ _jsx("section", {
264
+ "aria-label": "Workflow source",
265
+ className: "min-h-0 flex-1 overflow-auto pb-24 pt-16",
266
+ children: /*#__PURE__*/ _jsx("div", {
267
+ className: "mx-auto flex min-h-full w-full max-w-[1120px] px-24",
268
+ children: sourceSnapshot ? /*#__PURE__*/ _jsx(WorkflowSourceContent, {
310
269
  source: sourceSnapshot,
311
- open: sourcePanelOpen && sourceAvailable,
312
- onClose: closeSourcePanel,
313
270
  highlightedLineRange: highlightedLineRange,
314
271
  scrollHighlightedIntoView: true
272
+ }) : /*#__PURE__*/ _jsx(EmptyState, {
273
+ className: "min-h-160 w-full",
274
+ icon: "fileDamageLine",
275
+ title: "Source snapshot unavailable",
276
+ description: run.isTemporary ? 'Temporary runs do not capture workflow source.' : 'This run was created before workflow source snapshots were captured.'
277
+ })
278
+ })
279
+ });
280
+ }
281
+ const ALL_ANNOTATION_JOBS = 'all-jobs';
282
+ function AnnotationJobFilter({ jobs, selectedJobId, onSelect }) {
283
+ const sortedJobs = [
284
+ ...jobs
285
+ ].sort((left, right)=>left.position - right.position || left.id.localeCompare(right.id));
286
+ return /*#__PURE__*/ _jsxs(Select, {
287
+ value: selectedJobId ?? ALL_ANNOTATION_JOBS,
288
+ onValueChange: (value)=>onSelect(value === ALL_ANNOTATION_JOBS ? undefined : value),
289
+ children: [
290
+ /*#__PURE__*/ _jsx(SelectTrigger, {
291
+ size: "small",
292
+ "aria-label": "Filter annotations by job",
293
+ className: "w-full min-[480px]:w-240",
294
+ children: /*#__PURE__*/ _jsx(SelectValue, {})
295
+ }),
296
+ /*#__PURE__*/ _jsxs(SelectContent, {
297
+ align: "end",
298
+ children: [
299
+ /*#__PURE__*/ _jsx(SelectItem, {
300
+ value: ALL_ANNOTATION_JOBS,
301
+ children: "All jobs"
302
+ }),
303
+ sortedJobs.map((job)=>/*#__PURE__*/ _jsx(SelectItem, {
304
+ value: job.id,
305
+ children: job.displayName
306
+ }, job.id))
307
+ ]
315
308
  })
316
309
  ]
317
310
  });
318
311
  }
312
+ function RunWorkspaceNavSkeleton() {
313
+ return /*#__PURE__*/ _jsx("aside", {
314
+ "aria-label": "Loading run navigation",
315
+ className: "hidden w-240 shrink-0 border-r border-border-neutral-base bg-background-neutral-background p-12 min-[768px]:block",
316
+ children: /*#__PURE__*/ _jsxs("div", {
317
+ className: "flex flex-col gap-16",
318
+ children: [
319
+ /*#__PURE__*/ _jsx("div", {
320
+ className: "h-32 rounded-4 bg-background-components-subtle"
321
+ }),
322
+ /*#__PURE__*/ _jsxs("div", {
323
+ className: "flex flex-col gap-6",
324
+ children: [
325
+ /*#__PURE__*/ _jsx("div", {
326
+ className: "h-16 w-64 rounded-4 bg-background-components-subtle"
327
+ }),
328
+ /*#__PURE__*/ _jsx("div", {
329
+ className: "h-32 rounded-4 bg-background-components-subtle"
330
+ }),
331
+ /*#__PURE__*/ _jsx("div", {
332
+ className: "h-32 rounded-4 bg-background-components-subtle"
333
+ })
334
+ ]
335
+ })
336
+ ]
337
+ })
338
+ });
339
+ }
340
+ function runWorkspaceSection(tab) {
341
+ return tab === 'annotations' || tab === 'source' ? tab : 'summary';
342
+ }
319
343
  function cancelErrorMessage(error) {
320
344
  if (error instanceof ApiError && error.code === 'run-already-finished') {
321
345
  return 'This workflow run has already finished.';
322
346
  }
323
347
  return 'Could not cancel workflow run.';
324
348
  }
325
- function findAttemptSelection(jobExecution, attemptId) {
326
- for (const step of jobExecution.steps){
327
- const attempt = step.attempts.find((candidate)=>candidate.id === attemptId);
328
- if (attempt) return {
329
- stepId: step.id,
330
- attemptId: attempt.id
331
- };
332
- }
333
- return undefined;
334
- }
335
349
 
336
350
  //# sourceMappingURL=workflow-run-view.js.map