@shipfox/client-workflows 16.0.0 → 17.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 (180) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-type.log +1 -0
  3. package/CHANGELOG.md +20 -0
  4. package/dist/components/identifier/identifier.js +2 -2
  5. package/dist/components/identifier/identifier.js.map +1 -1
  6. package/dist/components/job-detail/agent-config-failure-callout.d.ts.map +1 -1
  7. package/dist/components/job-detail/agent-config-failure-callout.js +1 -1
  8. package/dist/components/job-detail/agent-config-failure-callout.js.map +1 -1
  9. package/dist/components/job-detail/job-context-panel.d.ts +6 -0
  10. package/dist/components/job-detail/job-context-panel.d.ts.map +1 -0
  11. package/dist/components/job-detail/job-context-panel.js +224 -0
  12. package/dist/components/job-detail/job-context-panel.js.map +1 -0
  13. package/dist/components/job-detail/job-detail-header.d.ts +3 -1
  14. package/dist/components/job-detail/job-detail-header.d.ts.map +1 -1
  15. package/dist/components/job-detail/job-detail-header.js +54 -48
  16. package/dist/components/job-detail/job-detail-header.js.map +1 -1
  17. package/dist/components/job-detail/job-detail-view.d.ts +1 -1
  18. package/dist/components/job-detail/job-detail-view.d.ts.map +1 -1
  19. package/dist/components/job-detail/job-detail-view.js +80 -44
  20. package/dist/components/job-detail/job-detail-view.js.map +1 -1
  21. package/dist/components/job-detail/job-empty-states.d.ts.map +1 -1
  22. package/dist/components/job-detail/job-empty-states.js +19 -10
  23. package/dist/components/job-detail/job-empty-states.js.map +1 -1
  24. package/dist/components/job-detail/job-execution-switcher.js +4 -4
  25. package/dist/components/job-detail/job-execution-switcher.js.map +1 -1
  26. package/dist/components/job-detail/json-code.d.ts +14 -0
  27. package/dist/components/job-detail/json-code.d.ts.map +1 -0
  28. package/dist/components/job-detail/json-code.js +93 -0
  29. package/dist/components/job-detail/json-code.js.map +1 -0
  30. package/dist/components/job-detail/step-attempt-log-panel.d.ts.map +1 -1
  31. package/dist/components/job-detail/step-attempt-log-panel.js +9 -6
  32. package/dist/components/job-detail/step-attempt-log-panel.js.map +1 -1
  33. package/dist/components/job-detail/step-troubleshooting.d.ts +18 -0
  34. package/dist/components/job-detail/step-troubleshooting.d.ts.map +1 -0
  35. package/dist/components/job-detail/step-troubleshooting.js +493 -0
  36. package/dist/components/job-detail/step-troubleshooting.js.map +1 -0
  37. package/dist/components/job-graph/job-graph-content.d.ts.map +1 -1
  38. package/dist/components/job-graph/job-graph-content.js +5 -4
  39. package/dist/components/job-graph/job-graph-content.js.map +1 -1
  40. package/dist/components/job-graph/job-node.js +3 -3
  41. package/dist/components/job-graph/job-node.js.map +1 -1
  42. package/dist/components/step-list/index.d.ts +1 -0
  43. package/dist/components/step-list/index.d.ts.map +1 -1
  44. package/dist/components/step-list/index.js.map +1 -1
  45. package/dist/components/step-list/step-list-model.d.ts.map +1 -1
  46. package/dist/components/step-list/step-list-model.js +5 -0
  47. package/dist/components/step-list/step-list-model.js.map +1 -1
  48. package/dist/components/step-list/step-list.d.ts +5 -1
  49. package/dist/components/step-list/step-list.d.ts.map +1 -1
  50. package/dist/components/step-list/step-list.js +117 -82
  51. package/dist/components/step-list/step-list.js.map +1 -1
  52. package/dist/components/workflow-run-duration-label.js +1 -1
  53. package/dist/components/workflow-run-duration-label.js.map +1 -1
  54. package/dist/components/workflow-run-list/job-status-strip.js +6 -6
  55. package/dist/components/workflow-run-list/job-status-strip.js.map +1 -1
  56. package/dist/components/workflow-run-list/workflow-run-filter-menu.js +1 -1
  57. package/dist/components/workflow-run-list/workflow-run-filter-menu.js.map +1 -1
  58. package/dist/components/workflow-run-list/workflow-run-filters.js +4 -4
  59. package/dist/components/workflow-run-list/workflow-run-filters.js.map +1 -1
  60. package/dist/components/workflow-run-list/workflow-run-list-states.js +6 -6
  61. package/dist/components/workflow-run-list/workflow-run-list-states.js.map +1 -1
  62. package/dist/components/workflow-run-list/workflow-run-list-view.js +1 -1
  63. package/dist/components/workflow-run-list/workflow-run-list-view.js.map +1 -1
  64. package/dist/components/workflow-run-list/workflow-run-row.js +7 -7
  65. package/dist/components/workflow-run-list/workflow-run-row.js.map +1 -1
  66. package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js +1 -1
  67. package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js.map +1 -1
  68. package/dist/components/workflow-run-summary/workflow-run-summary.d.ts +0 -1
  69. package/dist/components/workflow-run-summary/workflow-run-summary.d.ts.map +1 -1
  70. package/dist/components/workflow-run-summary/workflow-run-summary.js +8 -13
  71. package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -1
  72. package/dist/components/workflow-run-tabs/index.d.ts +1 -1
  73. package/dist/components/workflow-run-tabs/index.d.ts.map +1 -1
  74. package/dist/components/workflow-run-tabs/index.js.map +1 -1
  75. package/dist/components/workflow-run-tabs/run-annotation-count-chip.js +2 -2
  76. package/dist/components/workflow-run-tabs/run-annotation-count-chip.js.map +1 -1
  77. package/dist/components/workflow-run-tabs/run-annotation-list.d.ts +9 -2
  78. package/dist/components/workflow-run-tabs/run-annotation-list.d.ts.map +1 -1
  79. package/dist/components/workflow-run-tabs/run-annotation-list.js +28 -19
  80. package/dist/components/workflow-run-tabs/run-annotation-list.js.map +1 -1
  81. package/dist/components/workflow-run-tabs/run-annotation-summary-line.d.ts.map +1 -1
  82. package/dist/components/workflow-run-tabs/run-annotation-summary-line.js +5 -5
  83. package/dist/components/workflow-run-tabs/run-annotation-summary-line.js.map +1 -1
  84. package/dist/components/workflow-run-view/run-workspace-nav.js +7 -7
  85. package/dist/components/workflow-run-view/run-workspace-nav.js.map +1 -1
  86. package/dist/components/workflow-run-view/workflow-run-states.js +6 -6
  87. package/dist/components/workflow-run-view/workflow-run-states.js.map +1 -1
  88. package/dist/components/workflow-run-view/workflow-run-view.d.ts.map +1 -1
  89. package/dist/components/workflow-run-view/workflow-run-view.js +72 -12
  90. package/dist/components/workflow-run-view/workflow-run-view.js.map +1 -1
  91. package/dist/core/entities/job-execution.d.ts +7 -0
  92. package/dist/core/entities/job-execution.d.ts.map +1 -1
  93. package/dist/core/entities/job-execution.js.map +1 -1
  94. package/dist/core/entities/job.d.ts +9 -0
  95. package/dist/core/entities/job.d.ts.map +1 -1
  96. package/dist/core/entities/job.js.map +1 -1
  97. package/dist/core/entities/step-attempt.d.ts +29 -0
  98. package/dist/core/entities/step-attempt.d.ts.map +1 -1
  99. package/dist/core/entities/step-attempt.js.map +1 -1
  100. package/dist/core/entities/step.d.ts +3 -1
  101. package/dist/core/entities/step.d.ts.map +1 -1
  102. package/dist/core/entities/step.js.map +1 -1
  103. package/dist/core/run-annotation.d.ts +6 -0
  104. package/dist/core/run-annotation.d.ts.map +1 -1
  105. package/dist/core/run-annotation.js.map +1 -1
  106. package/dist/core/workflow-run.d.ts +1 -1
  107. package/dist/core/workflow-run.d.ts.map +1 -1
  108. package/dist/core/workflow-run.js.map +1 -1
  109. package/dist/hooks/api/annotations.d.ts +63 -0
  110. package/dist/hooks/api/annotations.d.ts.map +1 -0
  111. package/dist/hooks/api/annotations.js +145 -0
  112. package/dist/hooks/api/annotations.js.map +1 -0
  113. package/dist/hooks/api/step-attempt-detail.d.ts +14 -0
  114. package/dist/hooks/api/step-attempt-detail.d.ts.map +1 -0
  115. package/dist/hooks/api/step-attempt-detail.js +43 -0
  116. package/dist/hooks/api/step-attempt-detail.js.map +1 -0
  117. package/dist/hooks/api/workflow-run-mapper.d.ts +9 -2
  118. package/dist/hooks/api/workflow-run-mapper.d.ts.map +1 -1
  119. package/dist/hooks/api/workflow-run-mapper.js +47 -0
  120. package/dist/hooks/api/workflow-run-mapper.js.map +1 -1
  121. package/dist/pages/project-workflows-page.js +19 -19
  122. package/dist/pages/project-workflows-page.js.map +1 -1
  123. package/dist/pages/workflow-run-list-page.js +1 -1
  124. package/dist/pages/workflow-run-list-page.js.map +1 -1
  125. package/dist/tsconfig.test.tsbuildinfo +1 -1
  126. package/package.json +10 -10
  127. package/src/components/identifier/identifier.tsx +2 -2
  128. package/src/components/job-detail/agent-config-failure-callout.tsx +5 -1
  129. package/src/components/job-detail/job-context-panel.test.tsx +72 -0
  130. package/src/components/job-detail/job-context-panel.tsx +237 -0
  131. package/src/components/job-detail/job-detail-header.tsx +52 -46
  132. package/src/components/job-detail/job-detail-view.stories.tsx +555 -53
  133. package/src/components/job-detail/job-detail-view.tsx +87 -45
  134. package/src/components/job-detail/job-empty-states.tsx +23 -10
  135. package/src/components/job-detail/job-execution-switcher.tsx +5 -5
  136. package/src/components/job-detail/json-code.tsx +120 -0
  137. package/src/components/job-detail/step-attempt-log-panel.test.tsx +1 -0
  138. package/src/components/job-detail/step-attempt-log-panel.tsx +17 -6
  139. package/src/components/job-detail/step-troubleshooting.stories.tsx +120 -0
  140. package/src/components/job-detail/step-troubleshooting.test.tsx +337 -0
  141. package/src/components/job-detail/step-troubleshooting.tsx +537 -0
  142. package/src/components/job-graph/job-graph-content.tsx +4 -3
  143. package/src/components/job-graph/job-node.tsx +3 -3
  144. package/src/components/step-list/index.ts +1 -0
  145. package/src/components/step-list/step-list-model.ts +5 -0
  146. package/src/components/step-list/step-list.test.tsx +75 -2
  147. package/src/components/step-list/step-list.tsx +73 -20
  148. package/src/components/workflow-run-duration-label.tsx +1 -1
  149. package/src/components/workflow-run-list/job-status-strip.tsx +6 -6
  150. package/src/components/workflow-run-list/workflow-run-filter-menu.tsx +1 -1
  151. package/src/components/workflow-run-list/workflow-run-filters.tsx +4 -4
  152. package/src/components/workflow-run-list/workflow-run-list-states.tsx +6 -6
  153. package/src/components/workflow-run-list/workflow-run-list-view.tsx +1 -1
  154. package/src/components/workflow-run-list/workflow-run-row.tsx +7 -7
  155. package/src/components/workflow-run-summary/workflow-run-attempt-switcher.tsx +1 -1
  156. package/src/components/workflow-run-summary/workflow-run-summary.test.tsx +1 -1
  157. package/src/components/workflow-run-summary/workflow-run-summary.tsx +11 -11
  158. package/src/components/workflow-run-tabs/index.ts +5 -1
  159. package/src/components/workflow-run-tabs/run-annotation-count-chip.tsx +2 -2
  160. package/src/components/workflow-run-tabs/run-annotation-list.tsx +25 -10
  161. package/src/components/workflow-run-tabs/run-annotation-summary-line.tsx +5 -5
  162. package/src/components/workflow-run-view/run-workspace-nav.tsx +7 -7
  163. package/src/components/workflow-run-view/workflow-run-states.tsx +6 -6
  164. package/src/components/workflow-run-view/workflow-run-view.tsx +114 -14
  165. package/src/core/entities/job-execution.ts +7 -0
  166. package/src/core/entities/job.ts +9 -0
  167. package/src/core/entities/step-attempt.ts +33 -0
  168. package/src/core/entities/step.ts +3 -1
  169. package/src/core/run-annotation.ts +6 -0
  170. package/src/core/workflow-run.ts +4 -0
  171. package/src/hooks/api/annotations.test.ts +84 -0
  172. package/src/hooks/api/annotations.ts +240 -0
  173. package/src/hooks/api/step-attempt-detail.ts +67 -0
  174. package/src/hooks/api/workflow-run-mapper.ts +46 -0
  175. package/src/hooks/api/workflow-runs.test.tsx +61 -1
  176. package/src/pages/project-workflows-page.tsx +19 -19
  177. package/src/pages/workflow-job-detail-page.test.tsx +52 -1
  178. package/src/pages/workflow-run-list-page.tsx +1 -1
  179. package/test/fixtures/workflow-run.ts +7 -0
  180. package/tsconfig.build.tsbuildinfo +1 -1
@@ -13,7 +13,7 @@ import { WorkflowStatusIcon } from '#components/workflow-status/workflow-status-
13
13
  import { isWorkflowStatus } from '#core/workflow-run.js';
14
14
  import { formatJobExecutionTimeLabel } from '../job-graph/job-duration-format.js';
15
15
  import { buildStepListModel, defaultStepListJobExecution, humanizeStatus } from './step-list-model.js';
16
- export function StepList({ job, jobExecution, selectedAttemptId, defaultSelectedAttemptId, onSelectedAttemptChange, autoSelectActiveAttempt = false, emptyState, renderExpandedStep, showHeader = true, className }) {
16
+ export function StepList({ job, jobExecution, selectedAttemptId, defaultSelectedAttemptId, onSelectedAttemptChange, inspectorOpenAttemptId = null, onInspectorOpenChange, autoSelectActiveAttempt = false, emptyState, renderExpandedStep, renderInspector, showHeader = true, className }) {
17
17
  const selectedJobExecution = jobExecution ?? defaultStepListJobExecution(job);
18
18
  const model = useMemo(()=>buildStepListModel({
19
19
  job,
@@ -27,14 +27,17 @@ export function StepList({ job, jobExecution, selectedAttemptId, defaultSelected
27
27
  selectedAttemptId: selectedAttemptId,
28
28
  defaultSelectedAttemptId: defaultSelectedAttemptId,
29
29
  onSelectedAttemptChange: onSelectedAttemptChange,
30
+ inspectorOpenAttemptId: inspectorOpenAttemptId,
31
+ onInspectorOpenChange: onInspectorOpenChange,
30
32
  autoSelectActiveAttempt: autoSelectActiveAttempt,
31
33
  emptyState: emptyState,
32
34
  renderExpandedStep: renderExpandedStep,
35
+ renderInspector: renderInspector,
33
36
  showHeader: showHeader,
34
37
  className: className
35
38
  }, model.jobExecutionId);
36
39
  }
37
- function StepListContent({ model, selectedAttemptId, defaultSelectedAttemptId, onSelectedAttemptChange, autoSelectActiveAttempt, emptyState, renderExpandedStep, showHeader, className }) {
40
+ function StepListContent({ model, selectedAttemptId, defaultSelectedAttemptId, onSelectedAttemptChange, inspectorOpenAttemptId = null, onInspectorOpenChange, autoSelectActiveAttempt, emptyState, renderExpandedStep, renderInspector, showHeader, className }) {
38
41
  const titleId = useId();
39
42
  const [localSelectedAttemptIds, setLocalSelectedAttemptIds] = useState(()=>selectedAttemptId ? [
40
43
  selectedAttemptId
@@ -97,6 +100,15 @@ function StepListContent({ model, selectedAttemptId, defaultSelectedAttemptId, o
97
100
  }, [
98
101
  selectedAttemptId
99
102
  ]);
103
+ useEffect(()=>{
104
+ if (inspectorOpenAttemptId !== null && !model.entries.some((entry)=>entry.id === inspectorOpenAttemptId && !entry.carriedOver)) {
105
+ onInspectorOpenChange?.(null);
106
+ }
107
+ }, [
108
+ inspectorOpenAttemptId,
109
+ model.entries,
110
+ onInspectorOpenChange
111
+ ]);
100
112
  function selectAttempt(nextAttemptIds) {
101
113
  const nextAttemptId = nextSelectedAttemptId(selectedAttemptIds, nextAttemptIds);
102
114
  setUserSelectedAttempt(true);
@@ -104,61 +116,67 @@ function StepListContent({ model, selectedAttemptId, defaultSelectedAttemptId, o
104
116
  lastNotifiedSelectedAttemptId.current = nextAttemptId ?? null;
105
117
  onSelectedAttemptChange?.(nextAttemptId);
106
118
  }
107
- return /*#__PURE__*/ _jsx(TimeTickerProvider, {
119
+ const inspectorEntry = model.entries.find((entry)=>entry.id === inspectorOpenAttemptId && !entry.carriedOver);
120
+ return /*#__PURE__*/ _jsxs(TimeTickerProvider, {
108
121
  intervalMs: 1000,
109
122
  reducedMotionIntervalMs: 10_000,
110
- children: /*#__PURE__*/ _jsxs("section", {
111
- "aria-labelledby": showHeader ? titleId : undefined,
112
- className: cn('flex min-h-0 flex-col rounded-8 border border-border-neutral-base bg-background-components-base', className),
113
- children: [
114
- showHeader ? /*#__PURE__*/ _jsx("div", {
115
- className: "flex min-h-40 items-center border-b border-border-neutral-base px-16 py-8",
116
- children: /*#__PURE__*/ _jsx(Text, {
117
- as: "h2",
118
- id: titleId,
119
- size: "sm",
120
- bold: true,
121
- className: "text-foreground-neutral-base",
122
- children: model.jobName
123
- })
124
- }) : null,
125
- model.entries.length === 0 ? /*#__PURE__*/ _jsx(StepListEmptyStateView, {
126
- emptyState: emptyState
127
- }) : /*#__PURE__*/ _jsx(Accordion, {
128
- type: "multiple",
129
- value: selectedAttemptIds,
130
- onValueChange: selectAttempt,
131
- asChild: true,
132
- children: /*#__PURE__*/ _jsx("ol", {
133
- children: model.entries.map((entry)=>{
134
- const selected = selectedAttemptIds.includes(entry.id);
135
- return /*#__PURE__*/ _jsx(StepRow, {
136
- entry: entry,
137
- selected: selected,
138
- hasExpandedContent: hasExpandedContent,
139
- onSelect: ()=>{
140
- selectAttempt(hasExpandedContent ? toggleAttemptId(selectedAttemptIds, entry.id) : selected ? [] : [
141
- entry.id
142
- ]);
143
- },
144
- expandedContent: selected ? renderExpandedStep?.({
145
- step: entry.step,
146
- stepId: entry.step.id,
147
- stepLabel: entry.step.label,
148
- sourceLocation: entry.step.sourceLocation,
149
- attempt: entry.attempt,
150
- attemptId: entry.id,
151
- attemptStartedAt: entry.startedAt,
152
- attemptError: entry.error,
153
- attemptStatus: entry.statusVisual.kind,
154
- carriedOver: entry.carriedOver
155
- }) : null
156
- }, entry.id);
123
+ children: [
124
+ /*#__PURE__*/ _jsxs("section", {
125
+ "aria-labelledby": showHeader ? titleId : undefined,
126
+ className: cn('flex min-h-0 flex-col rounded-8 border border-border-neutral-base bg-background-components-base', className),
127
+ children: [
128
+ showHeader ? /*#__PURE__*/ _jsx("div", {
129
+ className: "flex min-h-40 items-center border-b border-border-neutral-base px-row py-row",
130
+ children: /*#__PURE__*/ _jsx(Text, {
131
+ as: "h2",
132
+ id: titleId,
133
+ size: "sm",
134
+ bold: true,
135
+ className: "text-foreground-neutral-base",
136
+ children: model.jobName
137
+ })
138
+ }) : null,
139
+ model.entries.length === 0 ? /*#__PURE__*/ _jsx(StepListEmptyStateView, {
140
+ emptyState: emptyState
141
+ }) : /*#__PURE__*/ _jsx(Accordion, {
142
+ type: "multiple",
143
+ value: selectedAttemptIds,
144
+ onValueChange: selectAttempt,
145
+ asChild: true,
146
+ children: /*#__PURE__*/ _jsx("ol", {
147
+ children: model.entries.map((entry, index)=>{
148
+ const selected = selectedAttemptIds.includes(entry.id);
149
+ return /*#__PURE__*/ _jsx(StepRow, {
150
+ entry: entry,
151
+ selected: selected,
152
+ hasExpandedContent: hasExpandedContent,
153
+ isLast: index === model.entries.length - 1,
154
+ onSelect: ()=>{
155
+ selectAttempt(hasExpandedContent ? toggleAttemptId(selectedAttemptIds, entry.id) : selected ? [] : [
156
+ entry.id
157
+ ]);
158
+ },
159
+ onInspect: onInspectorOpenChange && !entry.carriedOver ? ()=>onInspectorOpenChange(entry.id) : undefined,
160
+ expandedContent: selected ? renderExpandedStep?.({
161
+ step: entry.step,
162
+ stepId: entry.step.id,
163
+ stepLabel: entry.step.label,
164
+ sourceLocation: entry.step.sourceLocation,
165
+ attempt: entry.attempt,
166
+ attemptId: entry.id,
167
+ attemptStartedAt: entry.startedAt,
168
+ attemptError: entry.error,
169
+ attemptStatus: entry.statusVisual.kind,
170
+ carriedOver: entry.carriedOver
171
+ }) : null
172
+ }, entry.id);
173
+ })
157
174
  })
158
175
  })
159
- })
160
- ]
161
- })
176
+ ]
177
+ }),
178
+ inspectorEntry ? renderInspector?.(inspectorEntry) : null
179
+ ]
162
180
  });
163
181
  }
164
182
  function StepListEmptyStateView({ emptyState = {
@@ -167,7 +185,7 @@ function StepListEmptyStateView({ emptyState = {
167
185
  } }) {
168
186
  if (!emptyState.status) {
169
187
  return /*#__PURE__*/ _jsx(EmptyState, {
170
- className: "min-h-120 px-16 py-20",
188
+ className: "min-h-120 p-panel",
171
189
  icon: "componentLine",
172
190
  title: emptyState.title,
173
191
  description: emptyState.description,
@@ -175,7 +193,7 @@ function StepListEmptyStateView({ emptyState = {
175
193
  });
176
194
  }
177
195
  return /*#__PURE__*/ _jsxs("div", {
178
- className: "flex min-h-120 flex-col items-center justify-center gap-10 px-16 py-20",
196
+ className: "flex min-h-120 flex-col items-center justify-center gap-inline p-panel",
179
197
  children: [
180
198
  /*#__PURE__*/ _jsx(StepListEmptyStateIcon, {
181
199
  status: emptyState.status
@@ -201,7 +219,7 @@ function StepListEmptyStateView({ emptyState = {
201
219
  function StepListEmptyStateIcon({ status }) {
202
220
  if (status !== 'running') {
203
221
  return /*#__PURE__*/ _jsx("div", {
204
- className: "flex size-32 items-center justify-center rounded-6 border border-border-neutral-strong bg-background-neutral-base p-8",
222
+ className: "flex size-32 items-center justify-center rounded-6 border border-border-neutral-strong bg-background-neutral-base p-tight",
205
223
  children: /*#__PURE__*/ _jsx(WorkflowStatusIcon, {
206
224
  status: status,
207
225
  size: 20,
@@ -210,7 +228,7 @@ function StepListEmptyStateIcon({ status }) {
210
228
  });
211
229
  }
212
230
  return /*#__PURE__*/ _jsx("div", {
213
- className: "flex size-32 items-center justify-center rounded-6 border border-border-neutral-strong bg-background-neutral-base p-8 text-foreground-neutral-muted",
231
+ className: "flex size-32 items-center justify-center rounded-6 border border-border-neutral-strong bg-background-neutral-base p-tight text-foreground-neutral-muted",
214
232
  children: /*#__PURE__*/ _jsx(Icon, {
215
233
  name: "timerLine",
216
234
  size: 18,
@@ -218,7 +236,7 @@ function StepListEmptyStateIcon({ status }) {
218
236
  })
219
237
  });
220
238
  }
221
- function StepRow({ entry, selected, hasExpandedContent, onSelect, expandedContent }) {
239
+ function StepRow({ entry, selected, hasExpandedContent, isLast, onSelect, onInspect, expandedContent }) {
222
240
  const shouldShowLabelTooltip = entry.step.label.length > 32;
223
241
  const rowContent = /*#__PURE__*/ _jsxs(_Fragment, {
224
242
  children: [
@@ -233,7 +251,7 @@ function StepRow({ entry, selected, hasExpandedContent, onSelect, expandedConten
233
251
  /*#__PURE__*/ _jsx("div", {
234
252
  className: "min-w-0 flex-1",
235
253
  children: /*#__PURE__*/ _jsxs("div", {
236
- className: "flex min-w-0 items-center gap-8",
254
+ className: "flex min-w-0 items-center gap-inline",
237
255
  children: [
238
256
  /*#__PURE__*/ _jsx(Text, {
239
257
  size: "sm",
@@ -253,7 +271,7 @@ function StepRow({ entry, selected, hasExpandedContent, onSelect, expandedConten
253
271
  })
254
272
  ]
255
273
  });
256
- const rowClasses = cn('group grid min-h-44 w-full grid-cols-[14px_14px_minmax(0,1fr)_auto] items-center gap-x-8 px-12 py-6 text-left transition-colors hover:bg-background-components-hover focus-visible:shadow-border-interactive-with-active focus-visible:outline-none', selected && 'bg-background-components-hover', entry.carriedOver && 'opacity-[0.55]');
274
+ const rowClasses = cn('group flex min-h-44 min-w-0 flex-1 items-center gap-x-inline bg-transparent px-row py-row text-left transition-colors hover:bg-transparent active:bg-transparent focus-visible:shadow-border-interactive-with-active focus-visible:outline-none', entry.carriedOver && 'opacity-[0.55]');
257
275
  const button = hasExpandedContent ? /*#__PURE__*/ _jsx(AccordionTrigger, {
258
276
  showIcon: false,
259
277
  "aria-label": entryAccessibleLabel(entry),
@@ -283,26 +301,44 @@ function StepRow({ entry, selected, hasExpandedContent, onSelect, expandedConten
283
301
  }) : button;
284
302
  const row = /*#__PURE__*/ _jsxs(_Fragment, {
285
303
  children: [
286
- triggerNode,
304
+ /*#__PURE__*/ _jsxs("div", {
305
+ className: cn('group flex min-w-0 items-center gap-inline pr-[8px] transition-colors hover:bg-background-components-hover active:bg-background-components-pressed', selected && 'bg-background-components-hover', !hasExpandedContent && [
306
+ 'border-b border-border-neutral-base',
307
+ isLast && 'border-b-0'
308
+ ]),
309
+ children: [
310
+ /*#__PURE__*/ _jsx("div", {
311
+ className: "min-w-0 flex-1",
312
+ children: triggerNode
313
+ }),
314
+ onInspect ? /*#__PURE__*/ _jsxs(Tooltip, {
315
+ children: [
316
+ /*#__PURE__*/ _jsx(TooltipTrigger, {
317
+ asChild: true,
318
+ children: /*#__PURE__*/ _jsx("button", {
319
+ type: "button",
320
+ "aria-label": `Inspect ${entry.step.label}, attempt ${entry.attempt}`,
321
+ onClick: onInspect,
322
+ className: "flex size-28 shrink-0 items-center justify-center rounded-4 bg-transparent text-foreground-neutral-muted outline-none transition-colors hover:bg-transparent hover:text-foreground-neutral-base active:bg-transparent focus-visible:shadow-button-neutral-focus",
323
+ children: /*#__PURE__*/ _jsx(Icon, {
324
+ name: "informationLine",
325
+ size: 14,
326
+ "aria-hidden": "true"
327
+ })
328
+ })
329
+ }),
330
+ /*#__PURE__*/ _jsx(TooltipContent, {
331
+ children: "Inspect step details"
332
+ })
333
+ ]
334
+ }) : null
335
+ ]
336
+ }),
287
337
  selected && expandedContent ? /*#__PURE__*/ _jsx(AccordionContent, {
288
- className: "border-t border-border-neutral-base bg-background-neutral-base px-12 py-12",
289
- children: /*#__PURE__*/ _jsxs("div", {
290
- className: "grid grid-cols-[14px_14px_minmax(0,1fr)_auto] gap-x-8",
291
- children: [
292
- /*#__PURE__*/ _jsx("span", {
293
- "aria-hidden": "true"
294
- }),
295
- /*#__PURE__*/ _jsx("span", {
296
- "aria-hidden": "true"
297
- }),
298
- /*#__PURE__*/ _jsx("div", {
299
- className: "min-w-0",
300
- children: expandedContent
301
- }),
302
- /*#__PURE__*/ _jsx("span", {
303
- "aria-hidden": "true"
304
- })
305
- ]
338
+ className: "border-t border-border-neutral-base bg-transparent px-0 py-0",
339
+ children: /*#__PURE__*/ _jsx("div", {
340
+ className: "min-w-0",
341
+ children: expandedContent
306
342
  })
307
343
  }) : null
308
344
  ]
@@ -317,7 +353,6 @@ function StepRow({ entry, selected, hasExpandedContent, onSelect, expandedConten
317
353
  });
318
354
  }
319
355
  return /*#__PURE__*/ _jsx("li", {
320
- className: "border-b border-border-neutral-base last:border-b-0",
321
356
  children: row
322
357
  });
323
358
  }
@@ -384,10 +419,10 @@ const attemptChipClasses = {
384
419
  };
385
420
  function StepAttemptChip({ attempt }) {
386
421
  return /*#__PURE__*/ _jsx("div", {
387
- className: "flex shrink-0 items-center gap-4",
422
+ className: "flex shrink-0 items-center gap-tight",
388
423
  "aria-hidden": "true",
389
424
  children: /*#__PURE__*/ _jsxs("span", {
390
- className: cn('inline-flex h-18 min-w-24 items-center justify-center rounded-4 border px-6 font-code text-xs leading-16 text-foreground-neutral-base', attemptChipClasses[attempt.statusVisual.badge ?? 'neutral']),
425
+ className: cn('inline-flex h-18 min-w-24 items-center justify-center rounded-4 border px-tight font-code text-xs leading-16 text-foreground-neutral-base', attemptChipClasses[attempt.statusVisual.badge ?? 'neutral']),
391
426
  children: [
392
427
  "#",
393
428
  attempt.attempt
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/step-list/step-list.tsx"],"sourcesContent":["import {\n Accordion,\n AccordionContent,\n AccordionItem,\n AccordionTrigger,\n} from '@shipfox/react-ui/accordion';\nimport {Badge, type BadgeVariant} from '@shipfox/react-ui/badge';\nimport {Dot} from '@shipfox/react-ui/dot';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {TimeTickerProvider, useTimeTick} from '@shipfox/react-ui/time-ticker';\nimport {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Code, Text} from '@shipfox/react-ui/typography';\nimport {cn, humanDuration} from '@shipfox/react-ui/utils';\nimport type {ReactNode} from 'react';\nimport {useEffect, useId, useMemo, useRef, useState} from 'react';\nimport {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';\nimport {\n isWorkflowStatus,\n type Job,\n type JobDisplayStatus,\n type JobExecution,\n type Step,\n type StepSourceLocation,\n} from '#core/workflow-run.js';\nimport {formatJobExecutionTimeLabel} from '../job-graph/job-duration-format.js';\nimport {\n buildStepListModel,\n defaultStepListJobExecution,\n humanizeStatus,\n type StepAttemptModel,\n type StepListEntryModel,\n type StepListModel,\n} from './step-list-model.js';\n\nexport interface StepExpandedContext {\n step: Step;\n stepId: string;\n stepLabel: string;\n sourceLocation: StepSourceLocation | null;\n attempt: number;\n attemptId: string;\n attemptStartedAt: string;\n attemptError: Record<string, unknown> | null;\n attemptStatus: string;\n carriedOver: boolean;\n}\n\nexport interface StepListEmptyState {\n title: string;\n description: string;\n status?: JobDisplayStatus | undefined;\n}\n\nexport interface StepListProps {\n job: Job;\n jobExecution?: JobExecution | undefined;\n selectedAttemptId?: string | null | undefined;\n defaultSelectedAttemptId?: string | undefined;\n onSelectedAttemptChange?: ((attemptId: string | undefined) => void) | undefined;\n autoSelectActiveAttempt?: boolean | undefined;\n emptyState?: StepListEmptyState | undefined;\n renderExpandedStep?: ((context: StepExpandedContext) => ReactNode) | undefined;\n showHeader?: boolean | undefined;\n className?: string | undefined;\n}\n\nexport function StepList({\n job,\n jobExecution,\n selectedAttemptId,\n defaultSelectedAttemptId,\n onSelectedAttemptChange,\n autoSelectActiveAttempt = false,\n emptyState,\n renderExpandedStep,\n showHeader = true,\n className,\n}: StepListProps) {\n const selectedJobExecution = jobExecution ?? defaultStepListJobExecution(job);\n const model = useMemo(\n () => buildStepListModel({job, jobExecution: selectedJobExecution}),\n [job, selectedJobExecution],\n );\n\n return (\n <StepListContent\n key={model.jobExecutionId}\n model={model}\n selectedAttemptId={selectedAttemptId}\n defaultSelectedAttemptId={defaultSelectedAttemptId}\n onSelectedAttemptChange={onSelectedAttemptChange}\n autoSelectActiveAttempt={autoSelectActiveAttempt}\n emptyState={emptyState}\n renderExpandedStep={renderExpandedStep}\n showHeader={showHeader}\n className={className}\n />\n );\n}\n\nfunction StepListContent({\n model,\n selectedAttemptId,\n defaultSelectedAttemptId,\n onSelectedAttemptChange,\n autoSelectActiveAttempt,\n emptyState,\n renderExpandedStep,\n showHeader,\n className,\n}: Omit<StepListProps, 'job' | 'jobExecution'> & {model: StepListModel}) {\n const titleId = useId();\n const [localSelectedAttemptIds, setLocalSelectedAttemptIds] = useState<string[]>(() =>\n selectedAttemptId\n ? [selectedAttemptId]\n : defaultSelectedAttemptId\n ? [defaultSelectedAttemptId]\n : [],\n );\n const [userSelectedAttempt, setUserSelectedAttempt] = useState(false);\n const lastNotifiedSelectedAttemptId = useRef<string | null>(null);\n const autoSelectedAttemptIdRef = useRef<string | undefined>(undefined);\n const previousSelectedAttemptIdRef = useRef<string | null | undefined>(selectedAttemptId);\n const shouldUseControlledCollapsedState =\n selectedAttemptId === null && lastNotifiedSelectedAttemptId.current === null;\n const autoSelectedAttemptId =\n selectedAttemptId === undefined && autoSelectActiveAttempt && !userSelectedAttempt\n ? (autoSelectedAttemptIdRef.current ?? model.activeEntryId)\n : undefined;\n const autoSelectedAttemptIds = autoSelectedAttemptId ? [autoSelectedAttemptId] : [];\n const selectedAttemptIds = shouldUseControlledCollapsedState\n ? []\n : localSelectedAttemptIds.length > 0\n ? localSelectedAttemptIds\n : autoSelectedAttemptIds;\n const hasExpandedContent = renderExpandedStep !== undefined;\n\n useEffect(() => {\n if (selectedAttemptId !== undefined) {\n previousSelectedAttemptIdRef.current = selectedAttemptId;\n return;\n }\n\n // A changing landing candidate is normal while a live job polls. Only reset the local\n // accordion when the parent has just cleared a previously controlled URL selection.\n const wasControlled = previousSelectedAttemptIdRef.current !== undefined;\n previousSelectedAttemptIdRef.current = undefined;\n if (!wasControlled) return;\n\n setLocalSelectedAttemptIds(defaultSelectedAttemptId ? [defaultSelectedAttemptId] : []);\n setUserSelectedAttempt(false);\n autoSelectedAttemptIdRef.current = undefined;\n }, [defaultSelectedAttemptId, selectedAttemptId]);\n\n useEffect(() => {\n if (\n selectedAttemptId !== undefined ||\n !autoSelectActiveAttempt ||\n userSelectedAttempt ||\n localSelectedAttemptIds.length > 0 ||\n autoSelectedAttemptIdRef.current !== undefined\n ) {\n return;\n }\n if (model.activeEntryId) autoSelectedAttemptIdRef.current = model.activeEntryId;\n }, [\n autoSelectActiveAttempt,\n localSelectedAttemptIds.length,\n model.activeEntryId,\n selectedAttemptId,\n userSelectedAttempt,\n ]);\n\n useEffect(() => {\n if (selectedAttemptId === undefined) return;\n\n const nextSelectedAttemptId = selectedAttemptId ?? null;\n if (lastNotifiedSelectedAttemptId.current === nextSelectedAttemptId) {\n lastNotifiedSelectedAttemptId.current = null;\n return;\n }\n\n setLocalSelectedAttemptIds(selectedAttemptId ? [selectedAttemptId] : []);\n setUserSelectedAttempt(true);\n }, [selectedAttemptId]);\n\n function selectAttempt(nextAttemptIds: string[]) {\n const nextAttemptId = nextSelectedAttemptId(selectedAttemptIds, nextAttemptIds);\n setUserSelectedAttempt(true);\n setLocalSelectedAttemptIds(nextAttemptIds);\n lastNotifiedSelectedAttemptId.current = nextAttemptId ?? null;\n onSelectedAttemptChange?.(nextAttemptId);\n }\n\n return (\n <TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>\n <section\n aria-labelledby={showHeader ? titleId : undefined}\n className={cn(\n 'flex min-h-0 flex-col rounded-8 border border-border-neutral-base bg-background-components-base',\n className,\n )}\n >\n {showHeader ? (\n <div className=\"flex min-h-40 items-center border-b border-border-neutral-base px-16 py-8\">\n <Text as=\"h2\" id={titleId} size=\"sm\" bold className=\"text-foreground-neutral-base\">\n {model.jobName}\n </Text>\n </div>\n ) : null}\n\n {model.entries.length === 0 ? (\n <StepListEmptyStateView emptyState={emptyState} />\n ) : (\n <Accordion\n type=\"multiple\"\n value={selectedAttemptIds}\n onValueChange={selectAttempt}\n asChild\n >\n <ol>\n {model.entries.map((entry) => {\n const selected = selectedAttemptIds.includes(entry.id);\n return (\n <StepRow\n key={entry.id}\n entry={entry}\n selected={selected}\n hasExpandedContent={hasExpandedContent}\n onSelect={() => {\n selectAttempt(\n hasExpandedContent\n ? toggleAttemptId(selectedAttemptIds, entry.id)\n : selected\n ? []\n : [entry.id],\n );\n }}\n expandedContent={\n selected\n ? renderExpandedStep?.({\n step: entry.step,\n stepId: entry.step.id,\n stepLabel: entry.step.label,\n sourceLocation: entry.step.sourceLocation,\n attempt: entry.attempt,\n attemptId: entry.id,\n attemptStartedAt: entry.startedAt,\n attemptError: entry.error,\n attemptStatus: entry.statusVisual.kind,\n carriedOver: entry.carriedOver,\n })\n : null\n }\n />\n );\n })}\n </ol>\n </Accordion>\n )}\n </section>\n </TimeTickerProvider>\n );\n}\n\nfunction StepListEmptyStateView({\n emptyState = {\n title: 'No steps recorded',\n description: 'This job has not recorded any steps.',\n },\n}: {\n emptyState?: StepListEmptyState | undefined;\n}) {\n if (!emptyState.status) {\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\n return (\n <div className=\"flex min-h-120 flex-col items-center justify-center gap-10 px-16 py-20\">\n <StepListEmptyStateIcon status={emptyState.status} />\n <div className=\"text-center\">\n <Text size=\"sm\" className=\"text-foreground-neutral-subtle\">\n {emptyState.title}\n </Text>\n <Text size=\"xs\" className=\"text-foreground-neutral-muted\">\n {emptyState.description}\n </Text>\n </div>\n </div>\n );\n}\n\nfunction StepListEmptyStateIcon({status}: {status: JobDisplayStatus}) {\n if (status !== 'running') {\n return (\n <div className=\"flex size-32 items-center justify-center rounded-6 border border-border-neutral-strong bg-background-neutral-base p-8\">\n <WorkflowStatusIcon status={status} size={20} tooltip={false} />\n </div>\n );\n }\n\n return (\n <div className=\"flex size-32 items-center justify-center rounded-6 border border-border-neutral-strong bg-background-neutral-base p-8 text-foreground-neutral-muted\">\n <Icon name=\"timerLine\" size={18} aria-hidden=\"true\" />\n </div>\n );\n}\n\nfunction StepRow({\n entry,\n selected,\n hasExpandedContent,\n onSelect,\n expandedContent,\n}: {\n entry: StepListEntryModel;\n selected: boolean;\n hasExpandedContent: boolean;\n onSelect: () => void;\n expandedContent: ReactNode;\n}) {\n const shouldShowLabelTooltip = entry.step.label.length > 32;\n const rowContent = (\n <>\n <Icon\n name=\"chevronRight\"\n aria-hidden=\"true\"\n className={cn(\n 'size-14 shrink-0 text-foreground-neutral-muted transition-transform',\n selected && 'rotate-90',\n )}\n />\n <StepStatusIcon entry={entry} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex min-w-0 items-center gap-8\">\n <Text size=\"sm\" bold className=\"truncate text-foreground-neutral-base\">\n {entry.step.label}\n </Text>\n {entry.step.attempts.length > 1 ? <StepAttemptChip attempt={entry} /> : null}\n {entry.carriedOver ? <CarriedOverBadge /> : null}\n </div>\n </div>\n <StepAttemptDurationLabel attempt={entry} />\n </>\n );\n const rowClasses = cn(\n 'group grid min-h-44 w-full grid-cols-[14px_14px_minmax(0,1fr)_auto] items-center gap-x-8 px-12 py-6 text-left transition-colors hover:bg-background-components-hover focus-visible:shadow-border-interactive-with-active focus-visible:outline-none',\n selected && 'bg-background-components-hover',\n entry.carriedOver && 'opacity-[0.55]',\n );\n const button = hasExpandedContent ? (\n <AccordionTrigger\n showIcon={false}\n aria-label={entryAccessibleLabel(entry)}\n className={rowClasses}\n >\n {rowContent}\n </AccordionTrigger>\n ) : (\n <button\n type=\"button\"\n aria-expanded={false}\n aria-label={entryAccessibleLabel(entry)}\n onClick={onSelect}\n className={rowClasses}\n >\n {rowContent}\n </button>\n );\n const triggerNode = shouldShowLabelTooltip ? (\n <Tooltip>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent>\n <span className=\"block max-w-320 break-words\">{entry.step.label}</span>\n </TooltipContent>\n </Tooltip>\n ) : (\n button\n );\n const row = (\n <>\n {triggerNode}\n {selected && expandedContent ? (\n <AccordionContent className=\"border-t border-border-neutral-base bg-background-neutral-base px-12 py-12\">\n <div className=\"grid grid-cols-[14px_14px_minmax(0,1fr)_auto] gap-x-8\">\n <span aria-hidden=\"true\" />\n <span aria-hidden=\"true\" />\n <div className=\"min-w-0\">{expandedContent}</div>\n <span aria-hidden=\"true\" />\n </div>\n </AccordionContent>\n ) : null}\n </>\n );\n\n if (hasExpandedContent) {\n return (\n <AccordionItem value={entry.id} asChild>\n <li>{row}</li>\n </AccordionItem>\n );\n }\n\n return <li className=\"border-b border-border-neutral-base last:border-b-0\">{row}</li>;\n}\n\nfunction CarriedOverBadge() {\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <span className=\"shrink-0\">\n <Badge variant=\"neutral\" size=\"2xs\">\n reused\n </Badge>\n </span>\n </TooltipTrigger>\n <TooltipContent>\n Carried over from a previous attempt; did not run in this attempt.\n </TooltipContent>\n </Tooltip>\n );\n}\n\nfunction toggleAttemptId(selectedAttemptIds: readonly string[], attemptId: string): string[] {\n if (selectedAttemptIds.includes(attemptId)) {\n return selectedAttemptIds.filter((selectedAttemptId) => selectedAttemptId !== attemptId);\n }\n return [...selectedAttemptIds, attemptId];\n}\n\nfunction nextSelectedAttemptId(\n selectedAttemptIds: readonly string[],\n nextAttemptIds: readonly string[],\n): string | undefined {\n if (nextAttemptIds.length === 0) return undefined;\n\n const openedAttemptId = nextAttemptIds.find(\n (attemptId) => !selectedAttemptIds.includes(attemptId),\n );\n return openedAttemptId ?? nextAttemptIds.at(-1);\n}\n\nfunction StepStatusIcon({entry}: {entry: StepListEntryModel}) {\n if (isWorkflowStatus(entry.statusVisual.kind)) {\n return (\n <WorkflowStatusIcon\n status={entry.statusVisual.kind}\n ripple={entry.statusVisual.ripple}\n size={14}\n />\n );\n }\n\n return (\n <span role=\"img\" aria-label={entry.statusVisual.label} className=\"inline-flex shrink-0\">\n <Dot\n variant={entry.statusVisual.dot}\n ripple={entry.statusVisual.ripple}\n className=\"size-12 shrink-0\"\n />\n </span>\n );\n}\n\nconst attemptChipClasses: Record<NonNullable<BadgeVariant>, string> = {\n neutral: 'bg-tag-neutral-bg border-tag-neutral-border',\n info: 'bg-tag-blue-bg border-tag-blue-border',\n feature: 'bg-tag-purple-bg border-tag-purple-border',\n success: 'bg-tag-success-bg border-tag-success-border',\n warning: 'bg-tag-warning-bg border-tag-warning-border',\n error: 'bg-tag-error-bg border-tag-error-border',\n};\n\nfunction StepAttemptChip({attempt}: {attempt: StepAttemptModel}) {\n return (\n <div className=\"flex shrink-0 items-center gap-4\" aria-hidden=\"true\">\n <span\n className={cn(\n 'inline-flex h-18 min-w-24 items-center justify-center rounded-4 border px-6 font-code text-xs leading-16 text-foreground-neutral-base',\n attemptChipClasses[attempt.statusVisual.badge ?? 'neutral'],\n )}\n >\n #{attempt.attempt}\n </span>\n </div>\n );\n}\n\nfunction entryAccessibleLabel(entry: StepListEntryModel): string {\n const parts = [entry.step.label, entry.statusVisual.label, `attempt ${entry.attempt}`];\n if (entry.step.error?.category) parts.push(humanizeStatus(entry.step.error.category));\n return parts.join(', ');\n}\n\nfunction StepAttemptDurationLabel({attempt}: {attempt: StepAttemptModel}) {\n const duration = attempt.displayDuration;\n if (duration === null) return null;\n\n if (duration.state === 'live') {\n return <LiveDurationText fromIso={duration.fromIso} />;\n }\n\n return <DurationText>{formatJobExecutionTimeLabel(duration)}</DurationText>;\n}\n\nfunction LiveDurationText({fromIso}: {fromIso: string}) {\n useTimeTick();\n return <DurationText>{humanDuration(fromIso)}</DurationText>;\n}\n\nfunction DurationText({children}: {children: string}) {\n return (\n <Code as=\"span\" variant=\"label\" className=\"shrink-0 tabular-nums text-foreground-neutral-muted\">\n {children}\n </Code>\n );\n}\n"],"names":["Accordion","AccordionContent","AccordionItem","AccordionTrigger","Badge","Dot","EmptyState","Icon","TimeTickerProvider","useTimeTick","Tooltip","TooltipContent","TooltipTrigger","Code","Text","cn","humanDuration","useEffect","useId","useMemo","useRef","useState","WorkflowStatusIcon","isWorkflowStatus","formatJobExecutionTimeLabel","buildStepListModel","defaultStepListJobExecution","humanizeStatus","StepList","job","jobExecution","selectedAttemptId","defaultSelectedAttemptId","onSelectedAttemptChange","autoSelectActiveAttempt","emptyState","renderExpandedStep","showHeader","className","selectedJobExecution","model","StepListContent","jobExecutionId","titleId","localSelectedAttemptIds","setLocalSelectedAttemptIds","userSelectedAttempt","setUserSelectedAttempt","lastNotifiedSelectedAttemptId","autoSelectedAttemptIdRef","undefined","previousSelectedAttemptIdRef","shouldUseControlledCollapsedState","current","autoSelectedAttemptId","activeEntryId","autoSelectedAttemptIds","selectedAttemptIds","length","hasExpandedContent","wasControlled","nextSelectedAttemptId","selectAttempt","nextAttemptIds","nextAttemptId","intervalMs","reducedMotionIntervalMs","section","aria-labelledby","div","as","id","size","bold","jobName","entries","StepListEmptyStateView","type","value","onValueChange","asChild","ol","map","entry","selected","includes","StepRow","onSelect","toggleAttemptId","expandedContent","step","stepId","stepLabel","label","sourceLocation","attempt","attemptId","attemptStartedAt","startedAt","attemptError","error","attemptStatus","statusVisual","kind","carriedOver","title","description","status","icon","variant","StepListEmptyStateIcon","tooltip","name","aria-hidden","shouldShowLabelTooltip","rowContent","StepStatusIcon","attempts","StepAttemptChip","CarriedOverBadge","StepAttemptDurationLabel","rowClasses","button","showIcon","aria-label","entryAccessibleLabel","aria-expanded","onClick","triggerNode","span","row","li","filter","openedAttemptId","find","at","ripple","role","dot","attemptChipClasses","neutral","info","feature","success","warning","badge","parts","category","push","join","duration","displayDuration","state","LiveDurationText","fromIso","DurationText","children"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/components/step-list/step-list.tsx"],"sourcesContent":["import {\n Accordion,\n AccordionContent,\n AccordionItem,\n AccordionTrigger,\n} from '@shipfox/react-ui/accordion';\nimport {Badge, type BadgeVariant} from '@shipfox/react-ui/badge';\nimport {Dot} from '@shipfox/react-ui/dot';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {TimeTickerProvider, useTimeTick} from '@shipfox/react-ui/time-ticker';\nimport {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Code, Text} from '@shipfox/react-ui/typography';\nimport {cn, humanDuration} from '@shipfox/react-ui/utils';\nimport type {ReactNode} from 'react';\nimport {useEffect, useId, useMemo, useRef, useState} from 'react';\nimport {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';\nimport {\n isWorkflowStatus,\n type Job,\n type JobDisplayStatus,\n type JobExecution,\n type Step,\n type StepSourceLocation,\n} from '#core/workflow-run.js';\nimport {formatJobExecutionTimeLabel} from '../job-graph/job-duration-format.js';\nimport {\n buildStepListModel,\n defaultStepListJobExecution,\n humanizeStatus,\n type StepAttemptModel,\n type StepListEntryModel,\n type StepListModel,\n} from './step-list-model.js';\n\nexport interface StepExpandedContext {\n step: Step;\n stepId: string;\n stepLabel: string;\n sourceLocation: StepSourceLocation | null;\n attempt: number;\n attemptId: string;\n attemptStartedAt: string;\n attemptError: Record<string, unknown> | null;\n attemptStatus: string;\n carriedOver: boolean;\n}\n\nexport interface StepListEmptyState {\n title: string;\n description: string;\n status?: JobDisplayStatus | undefined;\n}\n\nexport interface StepListProps {\n job: Job;\n jobExecution?: JobExecution | undefined;\n selectedAttemptId?: string | null | undefined;\n defaultSelectedAttemptId?: string | undefined;\n onSelectedAttemptChange?: ((attemptId: string | undefined) => void) | undefined;\n inspectorOpenAttemptId?: string | null | undefined;\n onInspectorOpenChange?: ((attemptId: string | null) => void) | undefined;\n autoSelectActiveAttempt?: boolean | undefined;\n emptyState?: StepListEmptyState | undefined;\n renderExpandedStep?: ((context: StepExpandedContext) => ReactNode) | undefined;\n renderInspector?: ((entry: StepListEntryModel) => ReactNode) | undefined;\n showHeader?: boolean | undefined;\n className?: string | undefined;\n}\n\nexport function StepList({\n job,\n jobExecution,\n selectedAttemptId,\n defaultSelectedAttemptId,\n onSelectedAttemptChange,\n inspectorOpenAttemptId = null,\n onInspectorOpenChange,\n autoSelectActiveAttempt = false,\n emptyState,\n renderExpandedStep,\n renderInspector,\n showHeader = true,\n className,\n}: StepListProps) {\n const selectedJobExecution = jobExecution ?? defaultStepListJobExecution(job);\n const model = useMemo(\n () => buildStepListModel({job, jobExecution: selectedJobExecution}),\n [job, selectedJobExecution],\n );\n\n return (\n <StepListContent\n key={model.jobExecutionId}\n model={model}\n selectedAttemptId={selectedAttemptId}\n defaultSelectedAttemptId={defaultSelectedAttemptId}\n onSelectedAttemptChange={onSelectedAttemptChange}\n inspectorOpenAttemptId={inspectorOpenAttemptId}\n onInspectorOpenChange={onInspectorOpenChange}\n autoSelectActiveAttempt={autoSelectActiveAttempt}\n emptyState={emptyState}\n renderExpandedStep={renderExpandedStep}\n renderInspector={renderInspector}\n showHeader={showHeader}\n className={className}\n />\n );\n}\n\nfunction StepListContent({\n model,\n selectedAttemptId,\n defaultSelectedAttemptId,\n onSelectedAttemptChange,\n inspectorOpenAttemptId = null,\n onInspectorOpenChange,\n autoSelectActiveAttempt,\n emptyState,\n renderExpandedStep,\n renderInspector,\n showHeader,\n className,\n}: Omit<StepListProps, 'job' | 'jobExecution'> & {model: StepListModel}) {\n const titleId = useId();\n const [localSelectedAttemptIds, setLocalSelectedAttemptIds] = useState<string[]>(() =>\n selectedAttemptId\n ? [selectedAttemptId]\n : defaultSelectedAttemptId\n ? [defaultSelectedAttemptId]\n : [],\n );\n const [userSelectedAttempt, setUserSelectedAttempt] = useState(false);\n const lastNotifiedSelectedAttemptId = useRef<string | null>(null);\n const autoSelectedAttemptIdRef = useRef<string | undefined>(undefined);\n const previousSelectedAttemptIdRef = useRef<string | null | undefined>(selectedAttemptId);\n const shouldUseControlledCollapsedState =\n selectedAttemptId === null && lastNotifiedSelectedAttemptId.current === null;\n const autoSelectedAttemptId =\n selectedAttemptId === undefined && autoSelectActiveAttempt && !userSelectedAttempt\n ? (autoSelectedAttemptIdRef.current ?? model.activeEntryId)\n : undefined;\n const autoSelectedAttemptIds = autoSelectedAttemptId ? [autoSelectedAttemptId] : [];\n const selectedAttemptIds = shouldUseControlledCollapsedState\n ? []\n : localSelectedAttemptIds.length > 0\n ? localSelectedAttemptIds\n : autoSelectedAttemptIds;\n const hasExpandedContent = renderExpandedStep !== undefined;\n\n useEffect(() => {\n if (selectedAttemptId !== undefined) {\n previousSelectedAttemptIdRef.current = selectedAttemptId;\n return;\n }\n\n // A changing landing candidate is normal while a live job polls. Only reset the local\n // accordion when the parent has just cleared a previously controlled URL selection.\n const wasControlled = previousSelectedAttemptIdRef.current !== undefined;\n previousSelectedAttemptIdRef.current = undefined;\n if (!wasControlled) return;\n\n setLocalSelectedAttemptIds(defaultSelectedAttemptId ? [defaultSelectedAttemptId] : []);\n setUserSelectedAttempt(false);\n autoSelectedAttemptIdRef.current = undefined;\n }, [defaultSelectedAttemptId, selectedAttemptId]);\n\n useEffect(() => {\n if (\n selectedAttemptId !== undefined ||\n !autoSelectActiveAttempt ||\n userSelectedAttempt ||\n localSelectedAttemptIds.length > 0 ||\n autoSelectedAttemptIdRef.current !== undefined\n ) {\n return;\n }\n if (model.activeEntryId) autoSelectedAttemptIdRef.current = model.activeEntryId;\n }, [\n autoSelectActiveAttempt,\n localSelectedAttemptIds.length,\n model.activeEntryId,\n selectedAttemptId,\n userSelectedAttempt,\n ]);\n\n useEffect(() => {\n if (selectedAttemptId === undefined) return;\n\n const nextSelectedAttemptId = selectedAttemptId ?? null;\n if (lastNotifiedSelectedAttemptId.current === nextSelectedAttemptId) {\n lastNotifiedSelectedAttemptId.current = null;\n return;\n }\n\n setLocalSelectedAttemptIds(selectedAttemptId ? [selectedAttemptId] : []);\n setUserSelectedAttempt(true);\n }, [selectedAttemptId]);\n\n useEffect(() => {\n if (\n inspectorOpenAttemptId !== null &&\n !model.entries.some((entry) => entry.id === inspectorOpenAttemptId && !entry.carriedOver)\n ) {\n onInspectorOpenChange?.(null);\n }\n }, [inspectorOpenAttemptId, model.entries, onInspectorOpenChange]);\n\n function selectAttempt(nextAttemptIds: string[]) {\n const nextAttemptId = nextSelectedAttemptId(selectedAttemptIds, nextAttemptIds);\n setUserSelectedAttempt(true);\n setLocalSelectedAttemptIds(nextAttemptIds);\n lastNotifiedSelectedAttemptId.current = nextAttemptId ?? null;\n onSelectedAttemptChange?.(nextAttemptId);\n }\n\n const inspectorEntry = model.entries.find(\n (entry) => entry.id === inspectorOpenAttemptId && !entry.carriedOver,\n );\n\n return (\n <TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>\n <section\n aria-labelledby={showHeader ? titleId : undefined}\n className={cn(\n 'flex min-h-0 flex-col rounded-8 border border-border-neutral-base bg-background-components-base',\n className,\n )}\n >\n {showHeader ? (\n <div className=\"flex min-h-40 items-center border-b border-border-neutral-base px-row py-row\">\n <Text as=\"h2\" id={titleId} size=\"sm\" bold className=\"text-foreground-neutral-base\">\n {model.jobName}\n </Text>\n </div>\n ) : null}\n\n {model.entries.length === 0 ? (\n <StepListEmptyStateView emptyState={emptyState} />\n ) : (\n <Accordion\n type=\"multiple\"\n value={selectedAttemptIds}\n onValueChange={selectAttempt}\n asChild\n >\n <ol>\n {model.entries.map((entry, index) => {\n const selected = selectedAttemptIds.includes(entry.id);\n return (\n <StepRow\n key={entry.id}\n entry={entry}\n selected={selected}\n hasExpandedContent={hasExpandedContent}\n isLast={index === model.entries.length - 1}\n onSelect={() => {\n selectAttempt(\n hasExpandedContent\n ? toggleAttemptId(selectedAttemptIds, entry.id)\n : selected\n ? []\n : [entry.id],\n );\n }}\n onInspect={\n onInspectorOpenChange && !entry.carriedOver\n ? () => onInspectorOpenChange(entry.id)\n : undefined\n }\n expandedContent={\n selected\n ? renderExpandedStep?.({\n step: entry.step,\n stepId: entry.step.id,\n stepLabel: entry.step.label,\n sourceLocation: entry.step.sourceLocation,\n attempt: entry.attempt,\n attemptId: entry.id,\n attemptStartedAt: entry.startedAt,\n attemptError: entry.error,\n attemptStatus: entry.statusVisual.kind,\n carriedOver: entry.carriedOver,\n })\n : null\n }\n />\n );\n })}\n </ol>\n </Accordion>\n )}\n </section>\n {inspectorEntry ? renderInspector?.(inspectorEntry) : null}\n </TimeTickerProvider>\n );\n}\n\nfunction StepListEmptyStateView({\n emptyState = {\n title: 'No steps recorded',\n description: 'This job has not recorded any steps.',\n },\n}: {\n emptyState?: StepListEmptyState | undefined;\n}) {\n if (!emptyState.status) {\n return (\n <EmptyState\n className=\"min-h-120 p-panel\"\n icon=\"componentLine\"\n title={emptyState.title}\n description={emptyState.description}\n variant=\"compact\"\n />\n );\n }\n\n return (\n <div className=\"flex min-h-120 flex-col items-center justify-center gap-inline p-panel\">\n <StepListEmptyStateIcon status={emptyState.status} />\n <div className=\"text-center\">\n <Text size=\"sm\" className=\"text-foreground-neutral-subtle\">\n {emptyState.title}\n </Text>\n <Text size=\"xs\" className=\"text-foreground-neutral-muted\">\n {emptyState.description}\n </Text>\n </div>\n </div>\n );\n}\n\nfunction StepListEmptyStateIcon({status}: {status: JobDisplayStatus}) {\n if (status !== 'running') {\n return (\n <div className=\"flex size-32 items-center justify-center rounded-6 border border-border-neutral-strong bg-background-neutral-base p-tight\">\n <WorkflowStatusIcon status={status} size={20} tooltip={false} />\n </div>\n );\n }\n\n return (\n <div className=\"flex size-32 items-center justify-center rounded-6 border border-border-neutral-strong bg-background-neutral-base p-tight text-foreground-neutral-muted\">\n <Icon name=\"timerLine\" size={18} aria-hidden=\"true\" />\n </div>\n );\n}\n\nfunction StepRow({\n entry,\n selected,\n hasExpandedContent,\n isLast,\n onSelect,\n onInspect,\n expandedContent,\n}: {\n entry: StepListEntryModel;\n selected: boolean;\n hasExpandedContent: boolean;\n isLast: boolean;\n onSelect: () => void;\n onInspect?: (() => void) | undefined;\n expandedContent: ReactNode;\n}) {\n const shouldShowLabelTooltip = entry.step.label.length > 32;\n const rowContent = (\n <>\n <Icon\n name=\"chevronRight\"\n aria-hidden=\"true\"\n className={cn(\n 'size-14 shrink-0 text-foreground-neutral-muted transition-transform',\n selected && 'rotate-90',\n )}\n />\n <StepStatusIcon entry={entry} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex min-w-0 items-center gap-inline\">\n <Text size=\"sm\" bold className=\"truncate text-foreground-neutral-base\">\n {entry.step.label}\n </Text>\n {entry.step.attempts.length > 1 ? <StepAttemptChip attempt={entry} /> : null}\n {entry.carriedOver ? <CarriedOverBadge /> : null}\n </div>\n </div>\n <StepAttemptDurationLabel attempt={entry} />\n </>\n );\n const rowClasses = cn(\n 'group flex min-h-44 min-w-0 flex-1 items-center gap-x-inline bg-transparent px-row py-row text-left transition-colors hover:bg-transparent active:bg-transparent focus-visible:shadow-border-interactive-with-active focus-visible:outline-none',\n entry.carriedOver && 'opacity-[0.55]',\n );\n const button = hasExpandedContent ? (\n <AccordionTrigger\n showIcon={false}\n aria-label={entryAccessibleLabel(entry)}\n className={rowClasses}\n >\n {rowContent}\n </AccordionTrigger>\n ) : (\n <button\n type=\"button\"\n aria-expanded={false}\n aria-label={entryAccessibleLabel(entry)}\n onClick={onSelect}\n className={rowClasses}\n >\n {rowContent}\n </button>\n );\n const triggerNode = shouldShowLabelTooltip ? (\n <Tooltip>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent>\n <span className=\"block max-w-320 break-words\">{entry.step.label}</span>\n </TooltipContent>\n </Tooltip>\n ) : (\n button\n );\n const row = (\n <>\n <div\n className={cn(\n 'group flex min-w-0 items-center gap-inline pr-[8px] transition-colors hover:bg-background-components-hover active:bg-background-components-pressed',\n selected && 'bg-background-components-hover',\n !hasExpandedContent && ['border-b border-border-neutral-base', isLast && 'border-b-0'],\n )}\n >\n <div className=\"min-w-0 flex-1\">{triggerNode}</div>\n {onInspect ? (\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n type=\"button\"\n aria-label={`Inspect ${entry.step.label}, attempt ${entry.attempt}`}\n onClick={onInspect}\n className=\"flex size-28 shrink-0 items-center justify-center rounded-4 bg-transparent text-foreground-neutral-muted outline-none transition-colors hover:bg-transparent hover:text-foreground-neutral-base active:bg-transparent focus-visible:shadow-button-neutral-focus\"\n >\n <Icon name=\"informationLine\" size={14} aria-hidden=\"true\" />\n </button>\n </TooltipTrigger>\n <TooltipContent>Inspect step details</TooltipContent>\n </Tooltip>\n ) : null}\n </div>\n {selected && expandedContent ? (\n <AccordionContent className=\"border-t border-border-neutral-base bg-transparent px-0 py-0\">\n <div className=\"min-w-0\">{expandedContent}</div>\n </AccordionContent>\n ) : null}\n </>\n );\n\n if (hasExpandedContent) {\n return (\n <AccordionItem value={entry.id} asChild>\n <li>{row}</li>\n </AccordionItem>\n );\n }\n\n return <li>{row}</li>;\n}\n\nfunction CarriedOverBadge() {\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <span className=\"shrink-0\">\n <Badge variant=\"neutral\" size=\"2xs\">\n reused\n </Badge>\n </span>\n </TooltipTrigger>\n <TooltipContent>\n Carried over from a previous attempt; did not run in this attempt.\n </TooltipContent>\n </Tooltip>\n );\n}\n\nfunction toggleAttemptId(selectedAttemptIds: readonly string[], attemptId: string): string[] {\n if (selectedAttemptIds.includes(attemptId)) {\n return selectedAttemptIds.filter((selectedAttemptId) => selectedAttemptId !== attemptId);\n }\n return [...selectedAttemptIds, attemptId];\n}\n\nfunction nextSelectedAttemptId(\n selectedAttemptIds: readonly string[],\n nextAttemptIds: readonly string[],\n): string | undefined {\n if (nextAttemptIds.length === 0) return undefined;\n\n const openedAttemptId = nextAttemptIds.find(\n (attemptId) => !selectedAttemptIds.includes(attemptId),\n );\n return openedAttemptId ?? nextAttemptIds.at(-1);\n}\n\nfunction StepStatusIcon({entry}: {entry: StepListEntryModel}) {\n if (isWorkflowStatus(entry.statusVisual.kind)) {\n return (\n <WorkflowStatusIcon\n status={entry.statusVisual.kind}\n ripple={entry.statusVisual.ripple}\n size={14}\n />\n );\n }\n\n return (\n <span role=\"img\" aria-label={entry.statusVisual.label} className=\"inline-flex shrink-0\">\n <Dot\n variant={entry.statusVisual.dot}\n ripple={entry.statusVisual.ripple}\n className=\"size-12 shrink-0\"\n />\n </span>\n );\n}\n\nconst attemptChipClasses: Record<NonNullable<BadgeVariant>, string> = {\n neutral: 'bg-tag-neutral-bg border-tag-neutral-border',\n info: 'bg-tag-blue-bg border-tag-blue-border',\n feature: 'bg-tag-purple-bg border-tag-purple-border',\n success: 'bg-tag-success-bg border-tag-success-border',\n warning: 'bg-tag-warning-bg border-tag-warning-border',\n error: 'bg-tag-error-bg border-tag-error-border',\n};\n\nfunction StepAttemptChip({attempt}: {attempt: StepAttemptModel}) {\n return (\n <div className=\"flex shrink-0 items-center gap-tight\" aria-hidden=\"true\">\n <span\n className={cn(\n 'inline-flex h-18 min-w-24 items-center justify-center rounded-4 border px-tight font-code text-xs leading-16 text-foreground-neutral-base',\n attemptChipClasses[attempt.statusVisual.badge ?? 'neutral'],\n )}\n >\n #{attempt.attempt}\n </span>\n </div>\n );\n}\n\nfunction entryAccessibleLabel(entry: StepListEntryModel): string {\n const parts = [entry.step.label, entry.statusVisual.label, `attempt ${entry.attempt}`];\n if (entry.step.error?.category) parts.push(humanizeStatus(entry.step.error.category));\n return parts.join(', ');\n}\n\nfunction StepAttemptDurationLabel({attempt}: {attempt: StepAttemptModel}) {\n const duration = attempt.displayDuration;\n if (duration === null) return null;\n\n if (duration.state === 'live') {\n return <LiveDurationText fromIso={duration.fromIso} />;\n }\n\n return <DurationText>{formatJobExecutionTimeLabel(duration)}</DurationText>;\n}\n\nfunction LiveDurationText({fromIso}: {fromIso: string}) {\n useTimeTick();\n return <DurationText>{humanDuration(fromIso)}</DurationText>;\n}\n\nfunction DurationText({children}: {children: string}) {\n return (\n <Code as=\"span\" variant=\"label\" className=\"shrink-0 tabular-nums text-foreground-neutral-muted\">\n {children}\n </Code>\n );\n}\n"],"names":["Accordion","AccordionContent","AccordionItem","AccordionTrigger","Badge","Dot","EmptyState","Icon","TimeTickerProvider","useTimeTick","Tooltip","TooltipContent","TooltipTrigger","Code","Text","cn","humanDuration","useEffect","useId","useMemo","useRef","useState","WorkflowStatusIcon","isWorkflowStatus","formatJobExecutionTimeLabel","buildStepListModel","defaultStepListJobExecution","humanizeStatus","StepList","job","jobExecution","selectedAttemptId","defaultSelectedAttemptId","onSelectedAttemptChange","inspectorOpenAttemptId","onInspectorOpenChange","autoSelectActiveAttempt","emptyState","renderExpandedStep","renderInspector","showHeader","className","selectedJobExecution","model","StepListContent","jobExecutionId","titleId","localSelectedAttemptIds","setLocalSelectedAttemptIds","userSelectedAttempt","setUserSelectedAttempt","lastNotifiedSelectedAttemptId","autoSelectedAttemptIdRef","undefined","previousSelectedAttemptIdRef","shouldUseControlledCollapsedState","current","autoSelectedAttemptId","activeEntryId","autoSelectedAttemptIds","selectedAttemptIds","length","hasExpandedContent","wasControlled","nextSelectedAttemptId","entries","some","entry","id","carriedOver","selectAttempt","nextAttemptIds","nextAttemptId","inspectorEntry","find","intervalMs","reducedMotionIntervalMs","section","aria-labelledby","div","as","size","bold","jobName","StepListEmptyStateView","type","value","onValueChange","asChild","ol","map","index","selected","includes","StepRow","isLast","onSelect","toggleAttemptId","onInspect","expandedContent","step","stepId","stepLabel","label","sourceLocation","attempt","attemptId","attemptStartedAt","startedAt","attemptError","error","attemptStatus","statusVisual","kind","title","description","status","icon","variant","StepListEmptyStateIcon","tooltip","name","aria-hidden","shouldShowLabelTooltip","rowContent","StepStatusIcon","attempts","StepAttemptChip","CarriedOverBadge","StepAttemptDurationLabel","rowClasses","button","showIcon","aria-label","entryAccessibleLabel","aria-expanded","onClick","triggerNode","span","row","li","filter","openedAttemptId","at","ripple","role","dot","attemptChipClasses","neutral","info","feature","success","warning","badge","parts","category","push","join","duration","displayDuration","state","LiveDurationText","fromIso","DurationText","children"],"mappings":"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"}
@@ -79,7 +79,7 @@ function DurationText({ children, className, kind, ariaLabel }) {
79
79
  as: "span",
80
80
  variant: "label",
81
81
  "aria-label": ariaLabel,
82
- className: cn('inline-flex shrink-0 items-center gap-4 tabular-nums text-foreground-neutral-subtle', className),
82
+ className: cn('inline-flex shrink-0 items-center gap-tight tabular-nums text-foreground-neutral-subtle', className),
83
83
  children: [
84
84
  /*#__PURE__*/ _jsx(Icon, {
85
85
  name: GLYPH_BY_KIND[kind],
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/workflow-run-duration-label.tsx"],"sourcesContent":["import {Icon} from '@shipfox/react-ui/icon';\nimport {useTimeTick} from '@shipfox/react-ui/time-ticker';\nimport {Code} from '@shipfox/react-ui/typography';\nimport {cn, humanDuration} from '@shipfox/react-ui/utils';\nimport type {\n WorkflowRunAttemptDisplayDuration,\n WorkflowRunDisplayDuration,\n} from '#core/workflow-run.js';\n\n/**\n * The run list reads an attempt's own marks and cannot tell queue from run, so an unkinded\n * duration still renders as run time. Only surfaces holding the run's jobs pass a kind.\n */\nexport type WorkflowRunDurationDisplay =\n | WorkflowRunAttemptDisplayDuration\n | WorkflowRunDisplayDuration;\n\ntype DurationKind = 'queue' | 'run';\n\nconst GLYPH_BY_KIND = {queue: 'hourglassLine', run: 'timerLine'} as const;\nconst LIVE_VERB_BY_KIND = {queue: 'queued', run: 'running'} as const;\nconst FIXED_VERB_BY_KIND = {queue: 'queued', run: 'ran'} as const;\n\nexport function WorkflowRunDurationLabel({\n duration,\n className,\n}: {\n duration: WorkflowRunDurationDisplay | null;\n className?: string | undefined;\n}) {\n if (duration === null) return null;\n const kind = durationKind(duration);\n\n switch (duration.state) {\n case 'fixed': {\n const display = formatFixedDurationLabel(duration.elapsed);\n return (\n <DurationText\n className={className}\n kind={kind}\n ariaLabel={`${FIXED_VERB_BY_KIND[kind]} ${display}`}\n >\n {display}\n </DurationText>\n );\n }\n case 'live':\n return <LiveDurationText duration={duration} kind={kind} className={className} />;\n default: {\n const exhaustive: never = duration;\n return exhaustive;\n }\n }\n}\n\nexport function useWorkflowRunDurationAccessibleLabel(\n duration: WorkflowRunDurationDisplay | null,\n): string | undefined {\n useTimeTick();\n return workflowRunDurationAccessibleLabel(duration);\n}\n\nexport function workflowRunDurationAccessibleLabel(\n duration: WorkflowRunDurationDisplay | null,\n): string | undefined {\n if (duration === null) return undefined;\n const kind = durationKind(duration);\n\n switch (duration.state) {\n case 'live':\n return `${LIVE_VERB_BY_KIND[kind]} ${humanDuration(duration.fromIso)}`;\n case 'fixed':\n return `${FIXED_VERB_BY_KIND[kind]} ${formatFixedDurationLabel(duration.elapsed)}`;\n default: {\n const exhaustive: never = duration;\n return exhaustive;\n }\n }\n}\n\nfunction durationKind(duration: WorkflowRunDurationDisplay): DurationKind {\n return 'kind' in duration ? duration.kind : 'run';\n}\n\nfunction LiveDurationText({\n duration,\n kind,\n className,\n}: {\n duration: Extract<WorkflowRunDurationDisplay, {state: 'live'}>;\n kind: DurationKind;\n className?: string | undefined;\n}) {\n useTimeTick();\n const display = humanDuration(duration.fromIso);\n return (\n <DurationText\n className={className}\n kind={kind}\n ariaLabel={`${LIVE_VERB_BY_KIND[kind]} ${display}`}\n >\n {display}\n </DurationText>\n );\n}\n\nfunction DurationText({\n children,\n className,\n kind,\n ariaLabel,\n}: {\n children: string;\n className?: string | undefined;\n kind: DurationKind;\n ariaLabel?: string | undefined;\n}) {\n return (\n <Code\n as=\"span\"\n variant=\"label\"\n aria-label={ariaLabel}\n className={cn(\n 'inline-flex shrink-0 items-center gap-4 tabular-nums text-foreground-neutral-subtle',\n className,\n )}\n >\n {/* The glyph is the only visible mark of queue vs run: the status badge already writes\n \"Queued\", and the numeric columns are fixed width. The verb rides the accessible\n name instead. */}\n <Icon name={GLYPH_BY_KIND[kind]} className=\"size-12 shrink-0\" aria-hidden=\"true\" />\n {children}\n </Code>\n );\n}\n\nfunction formatFixedDurationLabel({\n years = 0,\n months = 0,\n weeks = 0,\n days = 0,\n hours = 0,\n minutes = 0,\n seconds = 0,\n}: Extract<WorkflowRunDurationDisplay, {state: 'fixed'}>['elapsed']): string {\n const totalDays = years * 365 + months * 30 + weeks * 7 + days;\n const totalHours = totalDays * 24 + hours;\n\n if (totalHours > 0) return `${totalHours}h ${pad2(minutes)}m`;\n if (minutes > 0) return `${minutes}m ${pad2(seconds)}s`;\n return `${seconds}s`;\n}\n\nfunction pad2(value: number): string {\n return value.toString().padStart(2, '0');\n}\n"],"names":["Icon","useTimeTick","Code","cn","humanDuration","GLYPH_BY_KIND","queue","run","LIVE_VERB_BY_KIND","FIXED_VERB_BY_KIND","WorkflowRunDurationLabel","duration","className","kind","durationKind","state","display","formatFixedDurationLabel","elapsed","DurationText","ariaLabel","LiveDurationText","exhaustive","useWorkflowRunDurationAccessibleLabel","workflowRunDurationAccessibleLabel","undefined","fromIso","children","as","variant","aria-label","name","aria-hidden","years","months","weeks","days","hours","minutes","seconds","totalDays","totalHours","pad2","value","toString","padStart"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/components/workflow-run-duration-label.tsx"],"sourcesContent":["import {Icon} from '@shipfox/react-ui/icon';\nimport {useTimeTick} from '@shipfox/react-ui/time-ticker';\nimport {Code} from '@shipfox/react-ui/typography';\nimport {cn, humanDuration} from '@shipfox/react-ui/utils';\nimport type {\n WorkflowRunAttemptDisplayDuration,\n WorkflowRunDisplayDuration,\n} from '#core/workflow-run.js';\n\n/**\n * The run list reads an attempt's own marks and cannot tell queue from run, so an unkinded\n * duration still renders as run time. Only surfaces holding the run's jobs pass a kind.\n */\nexport type WorkflowRunDurationDisplay =\n | WorkflowRunAttemptDisplayDuration\n | WorkflowRunDisplayDuration;\n\ntype DurationKind = 'queue' | 'run';\n\nconst GLYPH_BY_KIND = {queue: 'hourglassLine', run: 'timerLine'} as const;\nconst LIVE_VERB_BY_KIND = {queue: 'queued', run: 'running'} as const;\nconst FIXED_VERB_BY_KIND = {queue: 'queued', run: 'ran'} as const;\n\nexport function WorkflowRunDurationLabel({\n duration,\n className,\n}: {\n duration: WorkflowRunDurationDisplay | null;\n className?: string | undefined;\n}) {\n if (duration === null) return null;\n const kind = durationKind(duration);\n\n switch (duration.state) {\n case 'fixed': {\n const display = formatFixedDurationLabel(duration.elapsed);\n return (\n <DurationText\n className={className}\n kind={kind}\n ariaLabel={`${FIXED_VERB_BY_KIND[kind]} ${display}`}\n >\n {display}\n </DurationText>\n );\n }\n case 'live':\n return <LiveDurationText duration={duration} kind={kind} className={className} />;\n default: {\n const exhaustive: never = duration;\n return exhaustive;\n }\n }\n}\n\nexport function useWorkflowRunDurationAccessibleLabel(\n duration: WorkflowRunDurationDisplay | null,\n): string | undefined {\n useTimeTick();\n return workflowRunDurationAccessibleLabel(duration);\n}\n\nexport function workflowRunDurationAccessibleLabel(\n duration: WorkflowRunDurationDisplay | null,\n): string | undefined {\n if (duration === null) return undefined;\n const kind = durationKind(duration);\n\n switch (duration.state) {\n case 'live':\n return `${LIVE_VERB_BY_KIND[kind]} ${humanDuration(duration.fromIso)}`;\n case 'fixed':\n return `${FIXED_VERB_BY_KIND[kind]} ${formatFixedDurationLabel(duration.elapsed)}`;\n default: {\n const exhaustive: never = duration;\n return exhaustive;\n }\n }\n}\n\nfunction durationKind(duration: WorkflowRunDurationDisplay): DurationKind {\n return 'kind' in duration ? duration.kind : 'run';\n}\n\nfunction LiveDurationText({\n duration,\n kind,\n className,\n}: {\n duration: Extract<WorkflowRunDurationDisplay, {state: 'live'}>;\n kind: DurationKind;\n className?: string | undefined;\n}) {\n useTimeTick();\n const display = humanDuration(duration.fromIso);\n return (\n <DurationText\n className={className}\n kind={kind}\n ariaLabel={`${LIVE_VERB_BY_KIND[kind]} ${display}`}\n >\n {display}\n </DurationText>\n );\n}\n\nfunction DurationText({\n children,\n className,\n kind,\n ariaLabel,\n}: {\n children: string;\n className?: string | undefined;\n kind: DurationKind;\n ariaLabel?: string | undefined;\n}) {\n return (\n <Code\n as=\"span\"\n variant=\"label\"\n aria-label={ariaLabel}\n className={cn(\n 'inline-flex shrink-0 items-center gap-tight tabular-nums text-foreground-neutral-subtle',\n className,\n )}\n >\n {/* The glyph is the only visible mark of queue vs run: the status badge already writes\n \"Queued\", and the numeric columns are fixed width. The verb rides the accessible\n name instead. */}\n <Icon name={GLYPH_BY_KIND[kind]} className=\"size-12 shrink-0\" aria-hidden=\"true\" />\n {children}\n </Code>\n );\n}\n\nfunction formatFixedDurationLabel({\n years = 0,\n months = 0,\n weeks = 0,\n days = 0,\n hours = 0,\n minutes = 0,\n seconds = 0,\n}: Extract<WorkflowRunDurationDisplay, {state: 'fixed'}>['elapsed']): string {\n const totalDays = years * 365 + months * 30 + weeks * 7 + days;\n const totalHours = totalDays * 24 + hours;\n\n if (totalHours > 0) return `${totalHours}h ${pad2(minutes)}m`;\n if (minutes > 0) return `${minutes}m ${pad2(seconds)}s`;\n return `${seconds}s`;\n}\n\nfunction pad2(value: number): string {\n return value.toString().padStart(2, '0');\n}\n"],"names":["Icon","useTimeTick","Code","cn","humanDuration","GLYPH_BY_KIND","queue","run","LIVE_VERB_BY_KIND","FIXED_VERB_BY_KIND","WorkflowRunDurationLabel","duration","className","kind","durationKind","state","display","formatFixedDurationLabel","elapsed","DurationText","ariaLabel","LiveDurationText","exhaustive","useWorkflowRunDurationAccessibleLabel","workflowRunDurationAccessibleLabel","undefined","fromIso","children","as","variant","aria-label","name","aria-hidden","years","months","weeks","days","hours","minutes","seconds","totalDays","totalHours","pad2","value","toString","padStart"],"mappings":"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"}
@@ -63,10 +63,10 @@ const NOTABLE_STATUSES = [
63
63
  children: /*#__PURE__*/ _jsx("span", {
64
64
  role: "img",
65
65
  "aria-label": summary,
66
- className: cn('inline-flex items-center gap-3', className),
66
+ className: cn('inline-flex items-center gap-[3px]', className),
67
67
  children: /*#__PURE__*/ _jsxs("span", {
68
68
  "aria-hidden": "true",
69
- className: "inline-flex items-center gap-3",
69
+ className: "inline-flex items-center gap-[3px]",
70
70
  children: [
71
71
  visible.map((job)=>/*#__PURE__*/ _jsx(WorkflowStatusIcon, {
72
72
  status: job.status,
@@ -77,7 +77,7 @@ const NOTABLE_STATUSES = [
77
77
  tooltip: false
78
78
  }, job.id)),
79
79
  hiddenCount > 0 && overflowStatus ? /*#__PURE__*/ _jsxs("span", {
80
- className: "inline-flex items-center gap-2",
80
+ className: "inline-flex items-center gap-[2px]",
81
81
  children: [
82
82
  /*#__PURE__*/ _jsx(WorkflowStatusIcon, {
83
83
  status: overflowStatus,
@@ -118,7 +118,7 @@ function JobStatusStripTooltip({ jobs, summary }) {
118
118
  const notableTotal = NOTABLE_STATUSES.reduce((total, status)=>total + countOf(jobs, status), 0);
119
119
  const remaining = notableTotal - named.length;
120
120
  return /*#__PURE__*/ _jsxs("span", {
121
- className: "block max-w-[280px]",
121
+ className: "flex max-w-[280px] flex-col gap-tight",
122
122
  children: [
123
123
  /*#__PURE__*/ _jsx(Text, {
124
124
  as: "span",
@@ -129,7 +129,7 @@ function JobStatusStripTooltip({ jobs, summary }) {
129
129
  named.map((job)=>/*#__PURE__*/ _jsxs(Code, {
130
130
  as: "span",
131
131
  variant: "label",
132
- className: "mt-2 block truncate",
132
+ className: "block truncate",
133
133
  children: [
134
134
  getWorkflowStatusVisual(job.status).label.toLowerCase(),
135
135
  " · ",
@@ -139,7 +139,7 @@ function JobStatusStripTooltip({ jobs, summary }) {
139
139
  remaining > 0 ? /*#__PURE__*/ _jsxs(Text, {
140
140
  as: "span",
141
141
  size: "xs",
142
- className: "mt-2 block text-foreground-neutral-muted",
142
+ className: "block text-foreground-neutral-muted",
143
143
  children: [
144
144
  "and ",
145
145
  remaining,