@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
@@ -0,0 +1,405 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // biome-ignore-all lint/a11y/noRedundantRoles: the job log region keeps an explicit role for the public accessibility contract.
3
+ // biome-ignore-all lint/a11y/noNoninteractiveTabindex: the job log region is intentionally keyboard focusable.
4
+ import { ApiError } from '@shipfox/client-api';
5
+ import { QueryLoadError } from '@shipfox/client-ui';
6
+ import { EmptyState } from '@shipfox/react-ui/empty-state';
7
+ import { Icon } from '@shipfox/react-ui/icon';
8
+ import { Skeleton } from '@shipfox/react-ui/skeleton';
9
+ import { TimeTickerProvider } from '@shipfox/react-ui/time-ticker';
10
+ import { Text } from '@shipfox/react-ui/typography';
11
+ import { Link } from '@tanstack/react-router';
12
+ import { useEffect, useRef } from 'react';
13
+ import { workflowJobSearchParams, workflowRunSearchParams } from '#routes/inputs.js';
14
+ import { StepList } from '../step-list/index.js';
15
+ import { WorkflowRunNotFound, WorkflowRunStaleError } from '../workflow-run-view/workflow-run-states.js';
16
+ import { AgentConfigFailureCallout } from './agent-config-failure-callout.js';
17
+ import { JobDetailHeader } from './job-detail-header.js';
18
+ import { CarriedOverStepPanel, emptyStateForJob, emptyStateForMissingExecution, isAgentConfigFailure, jobSucceededSummary, toSelectedAttemptError } from './job-empty-states.js';
19
+ import { resolveWorkflowJobSelection, workflowJobLandingSelection } from './job-selection.js';
20
+ import { StepAttemptLogPanel } from './step-attempt-log-panel.js';
21
+ export function JobDetailView({ workspaceSlug, projectSlug, workflowRunId, jobId, search, query, newerAttempt, newerJob, onSelectionChange }) {
22
+ const rootRef = useRef(null);
23
+ const pageScrollRef = useRef(null);
24
+ const landingSelectionRef = useRef(undefined);
25
+ const hasLoadedData = query.data !== undefined;
26
+ useEffect(()=>{
27
+ if (!jobId || !hasLoadedData) return;
28
+ const heading = rootRef.current?.querySelector('[data-job-heading]');
29
+ // The heading is intentionally the route-change focus target: it announces the new job
30
+ // after rail navigation without putting focus on a decorative status element.
31
+ heading?.focus({
32
+ preventScroll: true
33
+ });
34
+ }, [
35
+ hasLoadedData,
36
+ jobId
37
+ ]);
38
+ if (query.isPending) return /*#__PURE__*/ _jsx(JobDetailSkeleton, {});
39
+ if (query.isError && query.data === undefined) {
40
+ if (query.error instanceof ApiError && query.error.status === 404) {
41
+ return /*#__PURE__*/ _jsx(WorkflowRunNotFound, {});
42
+ }
43
+ return /*#__PURE__*/ _jsx(QueryLoadError, {
44
+ query: query,
45
+ subject: "workflow run",
46
+ icon: "pulseLine"
47
+ });
48
+ }
49
+ if (query.data === undefined) return /*#__PURE__*/ _jsx(JobDetailSkeleton, {});
50
+ const run = query.data;
51
+ const job = run.jobs.find((candidate)=>candidate.id === jobId);
52
+ if (!job) {
53
+ return /*#__PURE__*/ _jsx(JobNotFoundState, {
54
+ workspaceSlug: workspaceSlug,
55
+ projectSlug: projectSlug,
56
+ workflowRunId: workflowRunId,
57
+ search: search
58
+ });
59
+ }
60
+ const resolvedSelection = resolveWorkflowJobSelection({
61
+ job,
62
+ selection: search
63
+ });
64
+ const selectedJobExecution = resolvedSelection.jobExecution;
65
+ const hasExplicitStep = Boolean(search.stepId && resolvedSelection.step);
66
+ const currentLandingSelection = workflowJobLandingSelection(selectedJobExecution);
67
+ if (selectedJobExecution) {
68
+ const frozenLanding = landingSelectionRef.current;
69
+ if (!frozenLanding || frozenLanding.jobId !== job.id || frozenLanding.jobExecutionId !== selectedJobExecution.id) {
70
+ landingSelectionRef.current = {
71
+ jobId: job.id,
72
+ jobExecutionId: selectedJobExecution.id,
73
+ selection: currentLandingSelection,
74
+ hasSelection: currentLandingSelection !== undefined
75
+ };
76
+ } else if (!frozenLanding.hasSelection && currentLandingSelection) {
77
+ landingSelectionRef.current = {
78
+ ...frozenLanding,
79
+ selection: currentLandingSelection,
80
+ hasSelection: true
81
+ };
82
+ }
83
+ } else {
84
+ landingSelectionRef.current = undefined;
85
+ }
86
+ const landingSelection = landingSelectionRef.current?.selection;
87
+ const selectedAttemptId = hasExplicitStep ? resolvedSelection.selectedAttemptId : undefined;
88
+ const runningSelection = runningStepSelection(selectedJobExecution);
89
+ const selectedStepId = resolvedSelection.step?.id ?? landingSelection?.stepId;
90
+ const selectedAttemptForNotice = hasExplicitStep ? resolvedSelection.selectedAttemptId : landingSelection?.attemptId;
91
+ const showRetargetNotice = runningSelection !== undefined && (selectedStepId !== runningSelection.stepId || selectedAttemptForNotice !== runningSelection.attemptId);
92
+ const succeededSummary = !hasExplicitStep && landingSelection === undefined && selectedJobExecution ? jobSucceededSummary(job, selectedJobExecution) : undefined;
93
+ function selectExecution(jobExecutionId) {
94
+ onSelectionChange({
95
+ ...search,
96
+ jobExecutionId,
97
+ stepId: undefined,
98
+ stepAttemptId: undefined
99
+ });
100
+ }
101
+ function selectAttempt(attemptId) {
102
+ if (!selectedJobExecution) return;
103
+ if (!attemptId) {
104
+ onSelectionChange({
105
+ ...search,
106
+ jobExecutionId: selectedJobExecution.id,
107
+ stepId: undefined,
108
+ stepAttemptId: undefined
109
+ });
110
+ return;
111
+ }
112
+ const match = findAttempt(selectedJobExecution, attemptId);
113
+ if (!match) return;
114
+ onSelectionChange({
115
+ ...search,
116
+ jobExecutionId: selectedJobExecution.id,
117
+ stepId: match.step.id,
118
+ stepAttemptId: match.attemptId
119
+ });
120
+ }
121
+ function retargetToRunningStep() {
122
+ if (!runningSelection || !selectedJobExecution) return;
123
+ onSelectionChange({
124
+ ...search,
125
+ jobExecutionId: selectedJobExecution.id,
126
+ stepId: runningSelection.stepId,
127
+ stepAttemptId: runningSelection.attemptId
128
+ });
129
+ }
130
+ return /*#__PURE__*/ _jsx(TimeTickerProvider, {
131
+ intervalMs: 1000,
132
+ reducedMotionIntervalMs: 10_000,
133
+ children: /*#__PURE__*/ _jsxs("div", {
134
+ ref: rootRef,
135
+ className: "flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden",
136
+ children: [
137
+ query.isError ? /*#__PURE__*/ _jsx(WorkflowRunStaleError, {
138
+ query: query
139
+ }) : null,
140
+ newerJob && newerAttempt && newerAttempt > run.runAttempt.attempt ? /*#__PURE__*/ _jsx(NewerAttemptNotice, {
141
+ workspaceSlug: workspaceSlug,
142
+ projectSlug: projectSlug,
143
+ runId: run.id,
144
+ jobId: newerJob.id,
145
+ attempt: newerAttempt
146
+ }) : null,
147
+ /*#__PURE__*/ _jsx("div", {
148
+ ref: pageScrollRef,
149
+ className: "@container min-h-0 flex-1 overflow-auto pb-24",
150
+ children: /*#__PURE__*/ _jsxs("section", {
151
+ "aria-label": `${job.displayName} logs`,
152
+ className: "mx-auto flex w-full max-w-[1120px] flex-col gap-8 px-24",
153
+ children: [
154
+ /*#__PURE__*/ _jsxs("section", {
155
+ className: "min-w-0 overflow-hidden",
156
+ children: [
157
+ /*#__PURE__*/ _jsx(JobDetailHeader, {
158
+ job: job,
159
+ selectedJobExecution: selectedJobExecution,
160
+ onSelectedJobExecutionChange: selectExecution
161
+ }),
162
+ /*#__PURE__*/ _jsx(Text, {
163
+ as: "h2",
164
+ className: "sr-only",
165
+ children: "Logs"
166
+ }),
167
+ selectedJobExecution ? /*#__PURE__*/ _jsx(StepList, {
168
+ job: job,
169
+ jobExecution: selectedJobExecution,
170
+ selectedAttemptId: selectedAttemptId,
171
+ defaultSelectedAttemptId: landingSelection?.attemptId,
172
+ onSelectedAttemptChange: selectAttempt,
173
+ autoSelectActiveAttempt: true,
174
+ emptyState: emptyStateForJob(job, selectedJobExecution),
175
+ showHeader: false,
176
+ className: "rounded-none border-0",
177
+ renderExpandedStep: (context)=>/*#__PURE__*/ _jsx(ExpandedStep, {
178
+ context: context,
179
+ workspaceSlug: workspaceSlug,
180
+ pageScrollRef: pageScrollRef
181
+ })
182
+ }) : /*#__PURE__*/ _jsx(EmptyStateForMissingExecution, {
183
+ job: job
184
+ })
185
+ ]
186
+ }),
187
+ showRetargetNotice && runningSelection ? /*#__PURE__*/ _jsxs("div", {
188
+ role: "status",
189
+ "aria-live": "polite",
190
+ className: "flex min-w-0 items-center justify-between gap-8 rounded-8 border border-border-neutral-base bg-background-components-base px-10 py-8",
191
+ children: [
192
+ /*#__PURE__*/ _jsxs(Text, {
193
+ size: "xs",
194
+ className: "min-w-0 text-foreground-neutral-muted",
195
+ children: [
196
+ "Run moved on to",
197
+ ' ',
198
+ /*#__PURE__*/ _jsx("span", {
199
+ className: "font-code text-foreground-neutral-base",
200
+ children: runningSelection.stepLabel
201
+ }),
202
+ "."
203
+ ]
204
+ }),
205
+ /*#__PURE__*/ _jsx("button", {
206
+ type: "button",
207
+ className: "shrink-0 rounded-4 px-6 py-4 text-xs font-medium text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active",
208
+ onClick: retargetToRunningStep,
209
+ children: "Jump to it"
210
+ })
211
+ ]
212
+ }) : null,
213
+ succeededSummary ? /*#__PURE__*/ _jsx(Text, {
214
+ size: "xs",
215
+ className: "px-8 text-foreground-neutral-muted",
216
+ children: succeededSummary
217
+ }) : null
218
+ ]
219
+ })
220
+ })
221
+ ]
222
+ })
223
+ });
224
+ }
225
+ function ExpandedStep({ context, workspaceSlug, pageScrollRef }) {
226
+ if (context.carriedOver) return /*#__PURE__*/ _jsx(CarriedOverStepPanel, {});
227
+ const selectedAttemptError = toSelectedAttemptError(context.step, context.attemptError) ?? context.step.error;
228
+ return /*#__PURE__*/ _jsxs("section", {
229
+ role: "region",
230
+ tabIndex: 0,
231
+ "aria-label": `${context.stepLabel} output, attempt ${context.attempt}`,
232
+ className: "flex min-w-0 flex-col gap-10 border-t border-border-neutral-base bg-background-neutral-subtle px-16 py-12 outline-none focus-visible:shadow-border-interactive-with-active",
233
+ children: [
234
+ isAgentConfigFailure(context.step, selectedAttemptError) ? /*#__PURE__*/ _jsx(AgentConfigFailureCallout, {
235
+ workspaceSlug: workspaceSlug,
236
+ config: context.step.agentConfig,
237
+ error: selectedAttemptError
238
+ }) : null,
239
+ /*#__PURE__*/ _jsx(StepAttemptLogPanel, {
240
+ stepId: context.stepId,
241
+ attempt: context.attempt,
242
+ attemptStatus: context.attemptStatus,
243
+ attemptStartedAt: context.attemptStartedAt,
244
+ pageScrollRef: pageScrollRef,
245
+ surfaceClassName: "rounded-8"
246
+ })
247
+ ]
248
+ });
249
+ }
250
+ function EmptyStateForMissingExecution({ job }) {
251
+ const emptyState = emptyStateForMissingExecution(job);
252
+ return /*#__PURE__*/ _jsx(EmptyState, {
253
+ className: "min-h-120 px-16 py-20",
254
+ icon: "componentLine",
255
+ title: emptyState.title,
256
+ description: emptyState.description,
257
+ variant: "compact"
258
+ });
259
+ }
260
+ function JobNotFoundState({ workspaceSlug, projectSlug, workflowRunId, search }) {
261
+ return /*#__PURE__*/ _jsxs("div", {
262
+ className: "flex min-h-0 min-w-0 flex-1 flex-col items-center justify-center gap-12 p-24",
263
+ children: [
264
+ /*#__PURE__*/ _jsx(EmptyState, {
265
+ icon: "componentLine",
266
+ title: "Job not found",
267
+ description: "This job is not part of the selected workflow run."
268
+ }),
269
+ /*#__PURE__*/ _jsxs(Link, {
270
+ to: "/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId",
271
+ params: {
272
+ workspaceSlug,
273
+ projectSlug,
274
+ workflowRunId
275
+ },
276
+ search: workflowRunSearchParams({
277
+ runAttempt: search.runAttempt
278
+ }, {
279
+ runAttempt: search.runAttempt
280
+ }),
281
+ className: "inline-flex items-center gap-6 rounded-6 px-8 py-6 text-sm text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active",
282
+ children: [
283
+ /*#__PURE__*/ _jsx(Icon, {
284
+ name: "arrowLeftLine",
285
+ size: 14,
286
+ "aria-hidden": "true"
287
+ }),
288
+ "Back to run summary"
289
+ ]
290
+ })
291
+ ]
292
+ });
293
+ }
294
+ function NewerAttemptNotice({ workspaceSlug, projectSlug, runId, jobId, attempt }) {
295
+ return /*#__PURE__*/ _jsx("div", {
296
+ role: "status",
297
+ className: "border-b border-border-neutral-base px-16 py-8",
298
+ children: /*#__PURE__*/ _jsxs("div", {
299
+ className: "mx-auto flex max-w-[1120px] items-center justify-between gap-8 rounded-8 border border-border-neutral-base bg-background-components-base px-10 py-8",
300
+ children: [
301
+ /*#__PURE__*/ _jsx(Text, {
302
+ size: "xs",
303
+ className: "text-foreground-neutral-muted",
304
+ children: "A newer run attempt is available."
305
+ }),
306
+ /*#__PURE__*/ _jsxs(Link, {
307
+ to: "/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId/jobs/$jobId",
308
+ params: {
309
+ workspaceSlug,
310
+ projectSlug,
311
+ workflowRunId: runId,
312
+ jobId
313
+ },
314
+ search: workflowJobSearchParams({
315
+ runAttempt: attempt
316
+ }),
317
+ className: "shrink-0 rounded-4 px-6 py-4 text-xs font-medium text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active",
318
+ children: [
319
+ "View attempt #",
320
+ attempt
321
+ ]
322
+ })
323
+ ]
324
+ })
325
+ });
326
+ }
327
+ function runningStepSelection(jobExecution) {
328
+ if (!jobExecution) return undefined;
329
+ const steps = [
330
+ ...jobExecution.steps
331
+ ].sort((left, right)=>left.position - right.position || left.id.localeCompare(right.id));
332
+ for(let stepIndex = steps.length - 1; stepIndex >= 0; stepIndex -= 1){
333
+ const step = steps[stepIndex];
334
+ if (!step) continue;
335
+ const attempt = [
336
+ ...step.attempts
337
+ ].sort((left, right)=>left.attempt - right.attempt || left.id.localeCompare(right.id)).reverse().find((candidate)=>candidate.status === 'running');
338
+ if (attempt) return {
339
+ stepId: step.id,
340
+ attemptId: attempt.id,
341
+ stepLabel: step.name || step.key || step.id
342
+ };
343
+ }
344
+ return undefined;
345
+ }
346
+ function findAttempt(jobExecution, attemptId) {
347
+ for (const step of jobExecution.steps){
348
+ const attempt = step.attempts.find((candidate)=>candidate.id === attemptId);
349
+ if (attempt) return {
350
+ step,
351
+ attemptId: attempt.id
352
+ };
353
+ }
354
+ return undefined;
355
+ }
356
+ function JobDetailSkeleton() {
357
+ return /*#__PURE__*/ _jsx("div", {
358
+ className: "min-h-0 min-w-0 flex-1 overflow-auto px-24 pb-24",
359
+ children: /*#__PURE__*/ _jsxs("div", {
360
+ className: "mx-auto max-w-[1120px] overflow-hidden",
361
+ children: [
362
+ /*#__PURE__*/ _jsxs("header", {
363
+ className: "flex items-center gap-12 border-b border-border-neutral-base px-16 py-12",
364
+ children: [
365
+ /*#__PURE__*/ _jsx(Skeleton, {
366
+ className: "size-20 rounded-full"
367
+ }),
368
+ /*#__PURE__*/ _jsx(Skeleton, {
369
+ className: "h-20 w-160 rounded-4"
370
+ }),
371
+ /*#__PURE__*/ _jsx(Skeleton, {
372
+ className: "h-24 w-72 rounded-6"
373
+ }),
374
+ /*#__PURE__*/ _jsx(Skeleton, {
375
+ className: "h-24 w-180 rounded-4"
376
+ })
377
+ ]
378
+ }),
379
+ /*#__PURE__*/ _jsx("div", {
380
+ className: "p-16",
381
+ children: JOB_DETAIL_SKELETON_ROWS.map((row)=>/*#__PURE__*/ _jsxs("div", {
382
+ className: "flex min-h-44 items-center gap-8 border-b border-border-neutral-base last:border-b-0",
383
+ children: [
384
+ /*#__PURE__*/ _jsx(Skeleton, {
385
+ className: "size-14 rounded-full"
386
+ }),
387
+ /*#__PURE__*/ _jsx(Skeleton, {
388
+ className: "h-16 w-180 rounded-4"
389
+ })
390
+ ]
391
+ }, row))
392
+ })
393
+ ]
394
+ })
395
+ });
396
+ }
397
+ const JOB_DETAIL_SKELETON_ROWS = [
398
+ 'step-1',
399
+ 'step-2',
400
+ 'step-3',
401
+ 'step-4',
402
+ 'step-5'
403
+ ];
404
+
405
+ //# sourceMappingURL=job-detail-view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/job-detail/job-detail-view.tsx"],"sourcesContent":["// biome-ignore-all lint/a11y/noRedundantRoles: the job log region keeps an explicit role for the public accessibility contract.\n// biome-ignore-all lint/a11y/noNoninteractiveTabindex: the job log region is intentionally keyboard focusable.\n\nimport {ApiError} from '@shipfox/client-api';\nimport {QueryLoadError} from '@shipfox/client-ui';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {TimeTickerProvider} from '@shipfox/react-ui/time-ticker';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {Link} from '@tanstack/react-router';\nimport {type RefObject, useEffect, useRef} from 'react';\nimport type {Job, JobExecution} from '#core/workflow-run.js';\nimport type {useWorkflowRunAttemptQuery} from '#hooks/api/workflow-runs.js';\nimport {\n type WorkflowJobSearch,\n workflowJobSearchParams,\n workflowRunSearchParams,\n} from '#routes/inputs.js';\nimport {type StepExpandedContext, StepList} from '../step-list/index.js';\nimport {\n WorkflowRunNotFound,\n WorkflowRunStaleError,\n} from '../workflow-run-view/workflow-run-states.js';\nimport {AgentConfigFailureCallout} from './agent-config-failure-callout.js';\nimport {JobDetailHeader} from './job-detail-header.js';\nimport {\n CarriedOverStepPanel,\n emptyStateForJob,\n emptyStateForMissingExecution,\n isAgentConfigFailure,\n jobSucceededSummary,\n toSelectedAttemptError,\n} from './job-empty-states.js';\nimport {\n resolveWorkflowJobSelection,\n type WorkflowJobLandingSelection,\n workflowJobLandingSelection,\n} from './job-selection.js';\nimport {StepAttemptLogPanel} from './step-attempt-log-panel.js';\n\nexport interface JobDetailViewProps {\n workspaceSlug: string;\n projectSlug: string;\n workflowRunId: string;\n jobId: string;\n search: WorkflowJobSearch;\n query: ReturnType<typeof useWorkflowRunAttemptQuery>;\n newerAttempt?: number | undefined;\n newerJob?: Job | undefined;\n onSelectionChange: (selection: WorkflowJobSearch) => void;\n}\n\nexport function JobDetailView({\n workspaceSlug,\n projectSlug,\n workflowRunId,\n jobId,\n search,\n query,\n newerAttempt,\n newerJob,\n onSelectionChange,\n}: JobDetailViewProps) {\n const rootRef = useRef<HTMLDivElement>(null);\n const pageScrollRef = useRef<HTMLDivElement>(null);\n const landingSelectionRef = useRef<FrozenLandingSelection | undefined>(undefined);\n const hasLoadedData = query.data !== undefined;\n\n useEffect(() => {\n if (!jobId || !hasLoadedData) return;\n const heading = rootRef.current?.querySelector<HTMLElement>('[data-job-heading]');\n // The heading is intentionally the route-change focus target: it announces the new job\n // after rail navigation without putting focus on a decorative status element.\n heading?.focus({preventScroll: true});\n }, [hasLoadedData, jobId]);\n\n if (query.isPending) return <JobDetailSkeleton />;\n\n if (query.isError && query.data === undefined) {\n if (query.error instanceof ApiError && query.error.status === 404) {\n return <WorkflowRunNotFound />;\n }\n return <QueryLoadError query={query} subject=\"workflow run\" icon=\"pulseLine\" />;\n }\n\n if (query.data === undefined) return <JobDetailSkeleton />;\n\n const run = query.data;\n const job = run.jobs.find((candidate) => candidate.id === jobId);\n if (!job) {\n return (\n <JobNotFoundState\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n workflowRunId={workflowRunId}\n search={search}\n />\n );\n }\n\n const resolvedSelection = resolveWorkflowJobSelection({job, selection: search});\n const selectedJobExecution = resolvedSelection.jobExecution;\n const hasExplicitStep = Boolean(search.stepId && resolvedSelection.step);\n const currentLandingSelection = workflowJobLandingSelection(selectedJobExecution);\n if (selectedJobExecution) {\n const frozenLanding = landingSelectionRef.current;\n if (\n !frozenLanding ||\n frozenLanding.jobId !== job.id ||\n frozenLanding.jobExecutionId !== selectedJobExecution.id\n ) {\n landingSelectionRef.current = {\n jobId: job.id,\n jobExecutionId: selectedJobExecution.id,\n selection: currentLandingSelection,\n hasSelection: currentLandingSelection !== undefined,\n };\n } else if (!frozenLanding.hasSelection && currentLandingSelection) {\n landingSelectionRef.current = {\n ...frozenLanding,\n selection: currentLandingSelection,\n hasSelection: true,\n };\n }\n } else {\n landingSelectionRef.current = undefined;\n }\n const landingSelection = landingSelectionRef.current?.selection;\n const selectedAttemptId = hasExplicitStep ? resolvedSelection.selectedAttemptId : undefined;\n const runningSelection = runningStepSelection(selectedJobExecution);\n const selectedStepId = resolvedSelection.step?.id ?? landingSelection?.stepId;\n const selectedAttemptForNotice = hasExplicitStep\n ? resolvedSelection.selectedAttemptId\n : landingSelection?.attemptId;\n const showRetargetNotice =\n runningSelection !== undefined &&\n (selectedStepId !== runningSelection.stepId ||\n selectedAttemptForNotice !== runningSelection.attemptId);\n const succeededSummary =\n !hasExplicitStep && landingSelection === undefined && selectedJobExecution\n ? jobSucceededSummary(job, selectedJobExecution)\n : undefined;\n\n function selectExecution(jobExecutionId: string) {\n onSelectionChange({\n ...search,\n jobExecutionId,\n stepId: undefined,\n stepAttemptId: undefined,\n });\n }\n\n function selectAttempt(attemptId: string | undefined) {\n if (!selectedJobExecution) return;\n if (!attemptId) {\n onSelectionChange({\n ...search,\n jobExecutionId: selectedJobExecution.id,\n stepId: undefined,\n stepAttemptId: undefined,\n });\n return;\n }\n\n const match = findAttempt(selectedJobExecution, attemptId);\n if (!match) return;\n onSelectionChange({\n ...search,\n jobExecutionId: selectedJobExecution.id,\n stepId: match.step.id,\n stepAttemptId: match.attemptId,\n });\n }\n\n function retargetToRunningStep() {\n if (!runningSelection || !selectedJobExecution) return;\n onSelectionChange({\n ...search,\n jobExecutionId: selectedJobExecution.id,\n stepId: runningSelection.stepId,\n stepAttemptId: runningSelection.attemptId,\n });\n }\n\n return (\n <TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>\n <div ref={rootRef} className=\"flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden\">\n {query.isError ? <WorkflowRunStaleError query={query} /> : null}\n {newerJob && newerAttempt && newerAttempt > run.runAttempt.attempt ? (\n <NewerAttemptNotice\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n runId={run.id}\n jobId={newerJob.id}\n attempt={newerAttempt}\n />\n ) : null}\n <div ref={pageScrollRef} className=\"@container min-h-0 flex-1 overflow-auto pb-24\">\n <section\n aria-label={`${job.displayName} logs`}\n className=\"mx-auto flex w-full max-w-[1120px] flex-col gap-8 px-24\"\n >\n <section className=\"min-w-0 overflow-hidden\">\n <JobDetailHeader\n job={job}\n selectedJobExecution={selectedJobExecution}\n onSelectedJobExecutionChange={selectExecution}\n />\n <Text as=\"h2\" className=\"sr-only\">\n Logs\n </Text>\n {selectedJobExecution ? (\n <StepList\n job={job}\n jobExecution={selectedJobExecution}\n selectedAttemptId={selectedAttemptId}\n defaultSelectedAttemptId={landingSelection?.attemptId}\n onSelectedAttemptChange={selectAttempt}\n autoSelectActiveAttempt\n emptyState={emptyStateForJob(job, selectedJobExecution)}\n showHeader={false}\n className=\"rounded-none border-0\"\n renderExpandedStep={(context) => (\n <ExpandedStep\n context={context}\n workspaceSlug={workspaceSlug}\n pageScrollRef={pageScrollRef}\n />\n )}\n />\n ) : (\n <EmptyStateForMissingExecution job={job} />\n )}\n </section>\n {showRetargetNotice && runningSelection ? (\n <div\n role=\"status\"\n aria-live=\"polite\"\n className=\"flex min-w-0 items-center justify-between gap-8 rounded-8 border border-border-neutral-base bg-background-components-base px-10 py-8\"\n >\n <Text size=\"xs\" className=\"min-w-0 text-foreground-neutral-muted\">\n Run moved on to{' '}\n <span className=\"font-code text-foreground-neutral-base\">\n {runningSelection.stepLabel}\n </span>\n .\n </Text>\n <button\n type=\"button\"\n className=\"shrink-0 rounded-4 px-6 py-4 text-xs font-medium text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active\"\n onClick={retargetToRunningStep}\n >\n Jump to it\n </button>\n </div>\n ) : null}\n {succeededSummary ? (\n <Text size=\"xs\" className=\"px-8 text-foreground-neutral-muted\">\n {succeededSummary}\n </Text>\n ) : null}\n </section>\n </div>\n </div>\n </TimeTickerProvider>\n );\n}\n\nfunction ExpandedStep({\n context,\n workspaceSlug,\n pageScrollRef,\n}: {\n context: StepExpandedContext;\n workspaceSlug: string;\n pageScrollRef: RefObject<HTMLDivElement | null>;\n}) {\n if (context.carriedOver) return <CarriedOverStepPanel />;\n\n const selectedAttemptError =\n toSelectedAttemptError(context.step, context.attemptError) ?? context.step.error;\n\n return (\n <section\n role=\"region\"\n tabIndex={0}\n aria-label={`${context.stepLabel} output, attempt ${context.attempt}`}\n className=\"flex min-w-0 flex-col gap-10 border-t border-border-neutral-base bg-background-neutral-subtle px-16 py-12 outline-none focus-visible:shadow-border-interactive-with-active\"\n >\n {isAgentConfigFailure(context.step, selectedAttemptError) ? (\n <AgentConfigFailureCallout\n workspaceSlug={workspaceSlug}\n config={context.step.agentConfig}\n error={selectedAttemptError}\n />\n ) : null}\n <StepAttemptLogPanel\n stepId={context.stepId}\n attempt={context.attempt}\n attemptStatus={context.attemptStatus}\n attemptStartedAt={context.attemptStartedAt}\n pageScrollRef={pageScrollRef}\n surfaceClassName=\"rounded-8\"\n />\n </section>\n );\n}\n\nfunction EmptyStateForMissingExecution({job}: {job: Job}) {\n const emptyState = emptyStateForMissingExecution(job);\n return (\n <EmptyState\n className=\"min-h-120 px-16 py-20\"\n icon=\"componentLine\"\n title={emptyState.title}\n description={emptyState.description}\n variant=\"compact\"\n />\n );\n}\n\nfunction JobNotFoundState({\n workspaceSlug,\n projectSlug,\n workflowRunId,\n search,\n}: {\n workspaceSlug: string;\n projectSlug: string;\n workflowRunId: string;\n search: WorkflowJobSearch;\n}) {\n return (\n <div className=\"flex min-h-0 min-w-0 flex-1 flex-col items-center justify-center gap-12 p-24\">\n <EmptyState\n icon=\"componentLine\"\n title=\"Job not found\"\n description=\"This job is not part of the selected workflow run.\"\n />\n <Link\n to=\"/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId\"\n params={{workspaceSlug, projectSlug, workflowRunId}}\n search={\n workflowRunSearchParams(\n {runAttempt: search.runAttempt},\n {runAttempt: search.runAttempt},\n ) as never\n }\n className=\"inline-flex items-center gap-6 rounded-6 px-8 py-6 text-sm text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active\"\n >\n <Icon name=\"arrowLeftLine\" size={14} aria-hidden=\"true\" />\n Back to run summary\n </Link>\n </div>\n );\n}\n\nfunction NewerAttemptNotice({\n workspaceSlug,\n projectSlug,\n runId,\n jobId,\n attempt,\n}: {\n workspaceSlug: string;\n projectSlug: string;\n runId: string;\n jobId: string;\n attempt: number;\n}) {\n return (\n <div role=\"status\" className=\"border-b border-border-neutral-base px-16 py-8\">\n <div className=\"mx-auto flex max-w-[1120px] items-center justify-between gap-8 rounded-8 border border-border-neutral-base bg-background-components-base px-10 py-8\">\n <Text size=\"xs\" className=\"text-foreground-neutral-muted\">\n A newer run attempt is available.\n </Text>\n <Link\n to=\"/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId/jobs/$jobId\"\n params={{workspaceSlug, projectSlug, workflowRunId: runId, jobId}}\n search={workflowJobSearchParams({runAttempt: attempt}) as never}\n className=\"shrink-0 rounded-4 px-6 py-4 text-xs font-medium text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active\"\n >\n View attempt #{attempt}\n </Link>\n </div>\n </div>\n );\n}\n\nfunction runningStepSelection(jobExecution: JobExecution | undefined) {\n if (!jobExecution) return undefined;\n const steps = [...jobExecution.steps].sort(\n (left, right) => left.position - right.position || left.id.localeCompare(right.id),\n );\n for (let stepIndex = steps.length - 1; stepIndex >= 0; stepIndex -= 1) {\n const step = steps[stepIndex];\n if (!step) continue;\n const attempt = [...step.attempts]\n .sort((left, right) => left.attempt - right.attempt || left.id.localeCompare(right.id))\n .reverse()\n .find((candidate) => candidate.status === 'running');\n if (attempt)\n return {stepId: step.id, attemptId: attempt.id, stepLabel: step.name || step.key || step.id};\n }\n return undefined;\n}\n\nfunction findAttempt(jobExecution: JobExecution, attemptId: string) {\n for (const step of jobExecution.steps) {\n const attempt = step.attempts.find((candidate) => candidate.id === attemptId);\n if (attempt) return {step, attemptId: attempt.id};\n }\n return undefined;\n}\n\nfunction JobDetailSkeleton() {\n return (\n <div className=\"min-h-0 min-w-0 flex-1 overflow-auto px-24 pb-24\">\n <div className=\"mx-auto max-w-[1120px] overflow-hidden\">\n <header className=\"flex items-center gap-12 border-b border-border-neutral-base px-16 py-12\">\n <Skeleton className=\"size-20 rounded-full\" />\n <Skeleton className=\"h-20 w-160 rounded-4\" />\n <Skeleton className=\"h-24 w-72 rounded-6\" />\n <Skeleton className=\"h-24 w-180 rounded-4\" />\n </header>\n <div className=\"p-16\">\n {JOB_DETAIL_SKELETON_ROWS.map((row) => (\n <div\n key={row}\n className=\"flex min-h-44 items-center gap-8 border-b border-border-neutral-base last:border-b-0\"\n >\n <Skeleton className=\"size-14 rounded-full\" />\n <Skeleton className=\"h-16 w-180 rounded-4\" />\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n\nconst JOB_DETAIL_SKELETON_ROWS = ['step-1', 'step-2', 'step-3', 'step-4', 'step-5'];\n\ninterface FrozenLandingSelection {\n jobId: string;\n jobExecutionId: string;\n selection: WorkflowJobLandingSelection | undefined;\n hasSelection: boolean;\n}\n"],"names":["ApiError","QueryLoadError","EmptyState","Icon","Skeleton","TimeTickerProvider","Text","Link","useEffect","useRef","workflowJobSearchParams","workflowRunSearchParams","StepList","WorkflowRunNotFound","WorkflowRunStaleError","AgentConfigFailureCallout","JobDetailHeader","CarriedOverStepPanel","emptyStateForJob","emptyStateForMissingExecution","isAgentConfigFailure","jobSucceededSummary","toSelectedAttemptError","resolveWorkflowJobSelection","workflowJobLandingSelection","StepAttemptLogPanel","JobDetailView","workspaceSlug","projectSlug","workflowRunId","jobId","search","query","newerAttempt","newerJob","onSelectionChange","rootRef","pageScrollRef","landingSelectionRef","undefined","hasLoadedData","data","heading","current","querySelector","focus","preventScroll","isPending","JobDetailSkeleton","isError","error","status","subject","icon","run","job","jobs","find","candidate","id","JobNotFoundState","resolvedSelection","selection","selectedJobExecution","jobExecution","hasExplicitStep","Boolean","stepId","step","currentLandingSelection","frozenLanding","jobExecutionId","hasSelection","landingSelection","selectedAttemptId","runningSelection","runningStepSelection","selectedStepId","selectedAttemptForNotice","attemptId","showRetargetNotice","succeededSummary","selectExecution","stepAttemptId","selectAttempt","match","findAttempt","retargetToRunningStep","intervalMs","reducedMotionIntervalMs","div","ref","className","runAttempt","attempt","NewerAttemptNotice","runId","section","aria-label","displayName","onSelectedJobExecutionChange","as","defaultSelectedAttemptId","onSelectedAttemptChange","autoSelectActiveAttempt","emptyState","showHeader","renderExpandedStep","context","ExpandedStep","EmptyStateForMissingExecution","role","aria-live","size","span","stepLabel","button","type","onClick","carriedOver","selectedAttemptError","attemptError","tabIndex","config","agentConfig","attemptStatus","attemptStartedAt","surfaceClassName","title","description","variant","to","params","name","aria-hidden","steps","sort","left","right","position","localeCompare","stepIndex","length","attempts","reverse","key","header","JOB_DETAIL_SKELETON_ROWS","map","row"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { Job, JobExecution, Step, StepError } from '#core/workflow-run.js';
2
+ import type { StepListEmptyState } from '../step-list/index.js';
3
+ export declare function emptyStateForJob(job: Job, jobExecution: JobExecution): StepListEmptyState | undefined;
4
+ export declare function emptyStateForMissingExecution(job: Job): StepListEmptyState;
5
+ export declare function skippedJobDescription(reason: Job['statusReason']): string;
6
+ export declare function CarriedOverStepPanel(): import("react").JSX.Element;
7
+ export declare function toSelectedAttemptError(step: Step, error: Record<string, unknown> | null): StepError | null;
8
+ export declare function isAgentConfigFailure(step: Step, error: StepError | null): boolean;
9
+ export declare function jobSucceededSummary(job: Job, execution: JobExecution): string | undefined;
10
+ //# sourceMappingURL=job-empty-states.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"job-empty-states.d.ts","sourceRoot":"","sources":["../../../src/components/job-detail/job-empty-states.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAO9E,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,uBAAuB,CAAC;AAG9D,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,GAAG,EACR,YAAY,EAAE,YAAY,GACzB,kBAAkB,GAAG,SAAS,CA+DhC;AAED,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,GAAG,GAAG,kBAAkB,CAsD1E;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,GAAG,CAAC,cAAc,CAAC,GAAG,MAAM,CAoBzE;AAiCD,wBAAgB,oBAAoB,gCAUnC;AAED,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GACpC,SAAS,GAAG,IAAI,CAgClB;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,GAAG,OAAO,CAEjF;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAMzF"}