@shipfox/client-workflows 15.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 (225) hide show
  1. package/.storybook/preview.css +4 -0
  2. package/.turbo/turbo-build.log +1 -1
  3. package/.turbo/turbo-type.log +1 -0
  4. package/CHANGELOG.md +41 -0
  5. package/dist/components/identifier/identifier.js +2 -2
  6. package/dist/components/identifier/identifier.js.map +1 -1
  7. package/dist/components/job-detail/agent-config-failure-callout.d.ts.map +1 -1
  8. package/dist/components/job-detail/agent-config-failure-callout.js +1 -1
  9. package/dist/components/job-detail/agent-config-failure-callout.js.map +1 -1
  10. package/dist/components/job-detail/job-context-panel.d.ts +6 -0
  11. package/dist/components/job-detail/job-context-panel.d.ts.map +1 -0
  12. package/dist/components/job-detail/job-context-panel.js +224 -0
  13. package/dist/components/job-detail/job-context-panel.js.map +1 -0
  14. package/dist/components/job-detail/job-detail-header.d.ts +10 -1
  15. package/dist/components/job-detail/job-detail-header.d.ts.map +1 -1
  16. package/dist/components/job-detail/job-detail-header.js +58 -39
  17. package/dist/components/job-detail/job-detail-header.js.map +1 -1
  18. package/dist/components/job-detail/job-detail-view.d.ts +1 -1
  19. package/dist/components/job-detail/job-detail-view.d.ts.map +1 -1
  20. package/dist/components/job-detail/job-detail-view.js +92 -41
  21. package/dist/components/job-detail/job-detail-view.js.map +1 -1
  22. package/dist/components/job-detail/job-empty-states.d.ts.map +1 -1
  23. package/dist/components/job-detail/job-empty-states.js +19 -10
  24. package/dist/components/job-detail/job-empty-states.js.map +1 -1
  25. package/dist/components/job-detail/job-execution-switcher.js +4 -4
  26. package/dist/components/job-detail/job-execution-switcher.js.map +1 -1
  27. package/dist/components/job-detail/json-code.d.ts +14 -0
  28. package/dist/components/job-detail/json-code.d.ts.map +1 -0
  29. package/dist/components/job-detail/json-code.js +93 -0
  30. package/dist/components/job-detail/json-code.js.map +1 -0
  31. package/dist/components/job-detail/step-attempt-log-panel.d.ts.map +1 -1
  32. package/dist/components/job-detail/step-attempt-log-panel.js +9 -6
  33. package/dist/components/job-detail/step-attempt-log-panel.js.map +1 -1
  34. package/dist/components/job-detail/step-troubleshooting.d.ts +18 -0
  35. package/dist/components/job-detail/step-troubleshooting.d.ts.map +1 -0
  36. package/dist/components/job-detail/step-troubleshooting.js +493 -0
  37. package/dist/components/job-detail/step-troubleshooting.js.map +1 -0
  38. package/dist/components/job-graph/job-graph-content.d.ts.map +1 -1
  39. package/dist/components/job-graph/job-graph-content.js +5 -4
  40. package/dist/components/job-graph/job-graph-content.js.map +1 -1
  41. package/dist/components/job-graph/job-node.js +3 -3
  42. package/dist/components/job-graph/job-node.js.map +1 -1
  43. package/dist/components/step-list/index.d.ts +1 -0
  44. package/dist/components/step-list/index.d.ts.map +1 -1
  45. package/dist/components/step-list/index.js.map +1 -1
  46. package/dist/components/step-list/step-list-model.d.ts.map +1 -1
  47. package/dist/components/step-list/step-list-model.js +5 -0
  48. package/dist/components/step-list/step-list-model.js.map +1 -1
  49. package/dist/components/step-list/step-list.d.ts +5 -1
  50. package/dist/components/step-list/step-list.d.ts.map +1 -1
  51. package/dist/components/step-list/step-list.js +117 -82
  52. package/dist/components/step-list/step-list.js.map +1 -1
  53. package/dist/components/workflow-run-duration-label.js +1 -1
  54. package/dist/components/workflow-run-duration-label.js.map +1 -1
  55. package/dist/components/workflow-run-list/job-status-strip.js +6 -6
  56. package/dist/components/workflow-run-list/job-status-strip.js.map +1 -1
  57. package/dist/components/workflow-run-list/workflow-run-filter-menu.js +1 -1
  58. package/dist/components/workflow-run-list/workflow-run-filter-menu.js.map +1 -1
  59. package/dist/components/workflow-run-list/workflow-run-filters.js +4 -4
  60. package/dist/components/workflow-run-list/workflow-run-filters.js.map +1 -1
  61. package/dist/components/workflow-run-list/workflow-run-list-states.js +6 -6
  62. package/dist/components/workflow-run-list/workflow-run-list-states.js.map +1 -1
  63. package/dist/components/workflow-run-list/workflow-run-list-view.js +1 -1
  64. package/dist/components/workflow-run-list/workflow-run-list-view.js.map +1 -1
  65. package/dist/components/workflow-run-list/workflow-run-row.js +7 -7
  66. package/dist/components/workflow-run-list/workflow-run-row.js.map +1 -1
  67. package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js +1 -1
  68. package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js.map +1 -1
  69. package/dist/components/workflow-run-summary/workflow-run-summary.d.ts +0 -1
  70. package/dist/components/workflow-run-summary/workflow-run-summary.d.ts.map +1 -1
  71. package/dist/components/workflow-run-summary/workflow-run-summary.js +9 -13
  72. package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -1
  73. package/dist/components/workflow-run-tabs/index.d.ts +3 -0
  74. package/dist/components/workflow-run-tabs/index.d.ts.map +1 -1
  75. package/dist/components/workflow-run-tabs/index.js +3 -0
  76. package/dist/components/workflow-run-tabs/index.js.map +1 -1
  77. package/dist/components/workflow-run-tabs/run-annotation-count-chip.d.ts +16 -0
  78. package/dist/components/workflow-run-tabs/run-annotation-count-chip.d.ts.map +1 -0
  79. package/dist/components/workflow-run-tabs/run-annotation-count-chip.js +50 -0
  80. package/dist/components/workflow-run-tabs/run-annotation-count-chip.js.map +1 -0
  81. package/dist/components/workflow-run-tabs/run-annotation-item.d.ts +20 -0
  82. package/dist/components/workflow-run-tabs/run-annotation-item.d.ts.map +1 -0
  83. package/dist/components/workflow-run-tabs/run-annotation-item.js +102 -0
  84. package/dist/components/workflow-run-tabs/run-annotation-item.js.map +1 -0
  85. package/dist/components/workflow-run-tabs/run-annotation-list.d.ts +39 -0
  86. package/dist/components/workflow-run-tabs/run-annotation-list.d.ts.map +1 -0
  87. package/dist/components/workflow-run-tabs/run-annotation-list.js +211 -0
  88. package/dist/components/workflow-run-tabs/run-annotation-list.js.map +1 -0
  89. package/dist/components/workflow-run-tabs/run-annotation-summary-line.d.ts +1 -1
  90. package/dist/components/workflow-run-tabs/run-annotation-summary-line.d.ts.map +1 -1
  91. package/dist/components/workflow-run-tabs/run-annotation-summary-line.js +90 -25
  92. package/dist/components/workflow-run-tabs/run-annotation-summary-line.js.map +1 -1
  93. package/dist/components/workflow-run-tabs/severity-visuals.d.ts +20 -0
  94. package/dist/components/workflow-run-tabs/severity-visuals.d.ts.map +1 -0
  95. package/dist/components/workflow-run-tabs/severity-visuals.js +31 -0
  96. package/dist/components/workflow-run-tabs/severity-visuals.js.map +1 -0
  97. package/dist/components/workflow-run-view/run-workspace-nav.d.ts +1 -1
  98. package/dist/components/workflow-run-view/run-workspace-nav.d.ts.map +1 -1
  99. package/dist/components/workflow-run-view/run-workspace-nav.js +16 -10
  100. package/dist/components/workflow-run-view/run-workspace-nav.js.map +1 -1
  101. package/dist/components/workflow-run-view/workflow-run-states.js +6 -6
  102. package/dist/components/workflow-run-view/workflow-run-states.js.map +1 -1
  103. package/dist/components/workflow-run-view/workflow-run-view.d.ts +1 -3
  104. package/dist/components/workflow-run-view/workflow-run-view.d.ts.map +1 -1
  105. package/dist/components/workflow-run-view/workflow-run-view.js +217 -59
  106. package/dist/components/workflow-run-view/workflow-run-view.js.map +1 -1
  107. package/dist/core/entities/job-execution.d.ts +7 -0
  108. package/dist/core/entities/job-execution.d.ts.map +1 -1
  109. package/dist/core/entities/job-execution.js.map +1 -1
  110. package/dist/core/entities/job.d.ts +9 -0
  111. package/dist/core/entities/job.d.ts.map +1 -1
  112. package/dist/core/entities/job.js.map +1 -1
  113. package/dist/core/entities/step-attempt.d.ts +29 -0
  114. package/dist/core/entities/step-attempt.d.ts.map +1 -1
  115. package/dist/core/entities/step-attempt.js.map +1 -1
  116. package/dist/core/entities/step.d.ts +3 -1
  117. package/dist/core/entities/step.d.ts.map +1 -1
  118. package/dist/core/entities/step.js.map +1 -1
  119. package/dist/core/run-annotation.d.ts +79 -0
  120. package/dist/core/run-annotation.d.ts.map +1 -0
  121. package/dist/core/run-annotation.js +125 -0
  122. package/dist/core/run-annotation.js.map +1 -0
  123. package/dist/core/workflow-run.d.ts +1 -1
  124. package/dist/core/workflow-run.d.ts.map +1 -1
  125. package/dist/core/workflow-run.js.map +1 -1
  126. package/dist/hooks/api/annotations.d.ts +63 -0
  127. package/dist/hooks/api/annotations.d.ts.map +1 -0
  128. package/dist/hooks/api/annotations.js +145 -0
  129. package/dist/hooks/api/annotations.js.map +1 -0
  130. package/dist/hooks/api/run-annotation-mapper.d.ts +10 -0
  131. package/dist/hooks/api/run-annotation-mapper.d.ts.map +1 -0
  132. package/dist/hooks/api/run-annotation-mapper.js +22 -0
  133. package/dist/hooks/api/run-annotation-mapper.js.map +1 -0
  134. package/dist/hooks/api/run-annotations.d.ts +25 -0
  135. package/dist/hooks/api/run-annotations.d.ts.map +1 -0
  136. package/dist/hooks/api/run-annotations.js +78 -0
  137. package/dist/hooks/api/run-annotations.js.map +1 -0
  138. package/dist/hooks/api/step-attempt-detail.d.ts +14 -0
  139. package/dist/hooks/api/step-attempt-detail.d.ts.map +1 -0
  140. package/dist/hooks/api/step-attempt-detail.js +43 -0
  141. package/dist/hooks/api/step-attempt-detail.js.map +1 -0
  142. package/dist/hooks/api/workflow-run-mapper.d.ts +9 -2
  143. package/dist/hooks/api/workflow-run-mapper.d.ts.map +1 -1
  144. package/dist/hooks/api/workflow-run-mapper.js +47 -0
  145. package/dist/hooks/api/workflow-run-mapper.js.map +1 -1
  146. package/dist/pages/project-workflows-page.js +19 -19
  147. package/dist/pages/project-workflows-page.js.map +1 -1
  148. package/dist/pages/workflow-run-list-page.js +1 -1
  149. package/dist/pages/workflow-run-list-page.js.map +1 -1
  150. package/dist/routes/inputs.d.ts +7 -2
  151. package/dist/routes/inputs.d.ts.map +1 -1
  152. package/dist/routes/inputs.js +5 -6
  153. package/dist/routes/inputs.js.map +1 -1
  154. package/dist/tsconfig.test.tsbuildinfo +1 -1
  155. package/package.json +10 -9
  156. package/src/components/identifier/identifier.tsx +2 -2
  157. package/src/components/job-detail/agent-config-failure-callout.tsx +5 -1
  158. package/src/components/job-detail/job-context-panel.test.tsx +72 -0
  159. package/src/components/job-detail/job-context-panel.tsx +237 -0
  160. package/src/components/job-detail/job-detail-header.test.tsx +3 -0
  161. package/src/components/job-detail/job-detail-header.tsx +65 -34
  162. package/src/components/job-detail/job-detail-view.stories.tsx +555 -48
  163. package/src/components/job-detail/job-detail-view.tsx +101 -42
  164. package/src/components/job-detail/job-empty-states.tsx +23 -10
  165. package/src/components/job-detail/job-execution-switcher.tsx +5 -5
  166. package/src/components/job-detail/json-code.tsx +120 -0
  167. package/src/components/job-detail/step-attempt-log-panel.test.tsx +1 -0
  168. package/src/components/job-detail/step-attempt-log-panel.tsx +17 -6
  169. package/src/components/job-detail/step-troubleshooting.stories.tsx +120 -0
  170. package/src/components/job-detail/step-troubleshooting.test.tsx +337 -0
  171. package/src/components/job-detail/step-troubleshooting.tsx +537 -0
  172. package/src/components/job-graph/job-graph-content.tsx +4 -3
  173. package/src/components/job-graph/job-node.tsx +3 -3
  174. package/src/components/step-list/index.ts +1 -0
  175. package/src/components/step-list/step-list-model.ts +5 -0
  176. package/src/components/step-list/step-list.test.tsx +75 -2
  177. package/src/components/step-list/step-list.tsx +73 -20
  178. package/src/components/workflow-run-duration-label.tsx +1 -1
  179. package/src/components/workflow-run-list/job-status-strip.tsx +6 -6
  180. package/src/components/workflow-run-list/workflow-run-filter-menu.tsx +1 -1
  181. package/src/components/workflow-run-list/workflow-run-filters.tsx +4 -4
  182. package/src/components/workflow-run-list/workflow-run-list-states.tsx +6 -6
  183. package/src/components/workflow-run-list/workflow-run-list-view.tsx +1 -1
  184. package/src/components/workflow-run-list/workflow-run-row.tsx +7 -7
  185. package/src/components/workflow-run-summary/workflow-run-attempt-switcher.tsx +1 -1
  186. package/src/components/workflow-run-summary/workflow-run-summary.test.tsx +1 -1
  187. package/src/components/workflow-run-summary/workflow-run-summary.tsx +12 -12
  188. package/src/components/workflow-run-tabs/index.ts +7 -0
  189. package/src/components/workflow-run-tabs/run-annotation-count-chip.tsx +63 -0
  190. package/src/components/workflow-run-tabs/run-annotation-item.tsx +123 -0
  191. package/src/components/workflow-run-tabs/run-annotation-list.tsx +273 -0
  192. package/src/components/workflow-run-tabs/run-annotation-summary-line.test.tsx +77 -3
  193. package/src/components/workflow-run-tabs/run-annotation-summary-line.tsx +95 -20
  194. package/src/components/workflow-run-tabs/run-annotations.stories.tsx +293 -0
  195. package/src/components/workflow-run-tabs/severity-visuals.ts +37 -0
  196. package/src/components/workflow-run-view/run-workspace-nav.test.tsx +44 -0
  197. package/src/components/workflow-run-view/run-workspace-nav.tsx +22 -9
  198. package/src/components/workflow-run-view/workflow-run-states.tsx +6 -6
  199. package/src/components/workflow-run-view/workflow-run-view.test.tsx +246 -20
  200. package/src/components/workflow-run-view/workflow-run-view.tsx +255 -37
  201. package/src/core/entities/job-execution.ts +7 -0
  202. package/src/core/entities/job.ts +9 -0
  203. package/src/core/entities/step-attempt.ts +33 -0
  204. package/src/core/entities/step.ts +3 -1
  205. package/src/core/run-annotation.test.ts +315 -0
  206. package/src/core/run-annotation.ts +244 -0
  207. package/src/core/workflow-run.ts +4 -0
  208. package/src/hooks/api/annotations.test.ts +84 -0
  209. package/src/hooks/api/annotations.ts +240 -0
  210. package/src/hooks/api/run-annotation-mapper.ts +30 -0
  211. package/src/hooks/api/run-annotations.ts +137 -0
  212. package/src/hooks/api/step-attempt-detail.ts +67 -0
  213. package/src/hooks/api/workflow-run-mapper.ts +46 -0
  214. package/src/hooks/api/workflow-runs.test.tsx +61 -1
  215. package/src/pages/project-workflows-page.tsx +19 -19
  216. package/src/pages/workflow-job-detail-page.test.tsx +118 -1
  217. package/src/pages/workflow-run-list-page.tsx +1 -1
  218. package/src/routes/inputs.ts +7 -2
  219. package/test/fixtures/workflow-run.ts +7 -0
  220. package/tsconfig.build.tsbuildinfo +1 -1
  221. package/dist/core/workflow-run-tabs.d.ts +0 -9
  222. package/dist/core/workflow-run-tabs.d.ts.map +0 -1
  223. package/dist/core/workflow-run-tabs.js +0 -3
  224. package/dist/core/workflow-run-tabs.js.map +0 -1
  225. package/src/core/workflow-run-tabs.ts +0 -8
@@ -0,0 +1,537 @@
1
+ import {Button} from '@shipfox/react-ui/button';
2
+ import {Callout, CalloutContent, CalloutDescription, CalloutTitle} from '@shipfox/react-ui/callout';
3
+ import {
4
+ Sheet,
5
+ SheetBody,
6
+ SheetContent,
7
+ SheetDescription,
8
+ SheetHeader,
9
+ SheetTitle,
10
+ } from '@shipfox/react-ui/sheet';
11
+ import {Skeleton} from '@shipfox/react-ui/skeleton';
12
+ import {Code, Text} from '@shipfox/react-ui/typography';
13
+ import {cn} from '@shipfox/react-ui/utils';
14
+ import {Link} from '@tanstack/react-router';
15
+ import type {ReactNode} from 'react';
16
+ import type {
17
+ EvaluationTraceEntry,
18
+ JobStatusReason,
19
+ Step,
20
+ StepAttempt,
21
+ StepError,
22
+ } from '#core/workflow-run.js';
23
+ import {useStepAttemptDetailQuery} from '#hooks/api/step-attempt-detail.js';
24
+ import {workflowRunSearchParams} from '#routes/inputs.js';
25
+ import {humanizeStatus, type StepListEntryModel} from '../step-list/step-list-model.js';
26
+ import {AgentConfigFailureCallout} from './agent-config-failure-callout.js';
27
+ import {toSelectedAttemptError} from './job-empty-states.js';
28
+ import {JsonCode, type JsonCodeEntry, JsonCodeTabs} from './json-code.js';
29
+
30
+ export interface StepInspectorSheetProps {
31
+ entry: StepListEntryModel;
32
+ open: boolean;
33
+ onOpenChange: (open: boolean) => void;
34
+ workspaceSlug: string;
35
+ projectSlug: string;
36
+ workflowRunId: string;
37
+ runAttempt: number;
38
+ jobId: string;
39
+ annotationCount?: number | undefined;
40
+ }
41
+
42
+ export function StepInspectorSheet({
43
+ entry,
44
+ open,
45
+ onOpenChange,
46
+ workspaceSlug,
47
+ projectSlug,
48
+ workflowRunId,
49
+ runAttempt,
50
+ jobId,
51
+ annotationCount,
52
+ }: StepInspectorSheetProps) {
53
+ const error = selectedStepError(entry.step, entry.error);
54
+ const inspectorQuery = useStepAttemptDetailQuery(entry.step.id, entry.attempt, {
55
+ enabled: open,
56
+ });
57
+
58
+ return (
59
+ <Sheet open={open} onOpenChange={onOpenChange}>
60
+ <SheetContent side="right" className="w-full sm:max-w-[560px]">
61
+ <SheetHeader>
62
+ <SheetTitle>{entry.step.label}</SheetTitle>
63
+ <SheetDescription>
64
+ Attempt #{entry.attempt} · {humanizeStatus(entry.statusVisual.kind)}
65
+ </SheetDescription>
66
+ </SheetHeader>
67
+ <SheetBody className="gap-section">
68
+ <StepInspector
69
+ step={entry.step}
70
+ attempt={entry}
71
+ error={error}
72
+ showFailure={entry.statusVisual.kind === 'failed' || entry.error !== null}
73
+ query={inspectorQuery}
74
+ workspaceSlug={workspaceSlug}
75
+ projectSlug={projectSlug}
76
+ workflowRunId={workflowRunId}
77
+ runAttempt={runAttempt}
78
+ jobId={jobId}
79
+ annotationCount={annotationCount}
80
+ />
81
+ </SheetBody>
82
+ </SheetContent>
83
+ </Sheet>
84
+ );
85
+ }
86
+
87
+ function StepFailureCallout({
88
+ step,
89
+ attempt,
90
+ error,
91
+ workspaceSlug,
92
+ projectSlug,
93
+ workflowRunId,
94
+ runAttempt,
95
+ }: {
96
+ step: Step;
97
+ attempt: StepAttempt;
98
+ error: StepError | null;
99
+ workspaceSlug: string;
100
+ projectSlug: string;
101
+ workflowRunId: string;
102
+ runAttempt: number;
103
+ }) {
104
+ const reason = error?.reason ?? step.statusReason ?? 'unknown';
105
+ const title = failureTitle(reason);
106
+ const sourceLink = sourceLinkForFailure(reason) && step.sourceLocation;
107
+
108
+ if (step.type === 'agent' && reason === 'agent_config_invalid') {
109
+ return (
110
+ <AgentConfigFailureCallout
111
+ workspaceSlug={workspaceSlug}
112
+ config={step.agentConfig}
113
+ error={error}
114
+ />
115
+ );
116
+ }
117
+
118
+ return (
119
+ <Callout
120
+ role="alert"
121
+ type="error"
122
+ variant="secondary"
123
+ className="rounded-8 border border-tag-error-border p-panel-compact shadow-none"
124
+ >
125
+ <CalloutContent>
126
+ <CalloutTitle>{title}</CalloutTitle>
127
+ <CalloutDescription>
128
+ <div className="flex min-w-0 flex-wrap items-center gap-x-inline gap-y-tight">
129
+ <div className="flex min-w-0 flex-col gap-tight">
130
+ <span>{failureDescription(reason)}</span>
131
+ {error?.message ? (
132
+ <span className="text-foreground-neutral-muted">{error.message}</span>
133
+ ) : null}
134
+ </div>
135
+ <Code as="span" variant="label" className="text-tag-error-text">
136
+ {reason}
137
+ </Code>
138
+ {sourceLink ? (
139
+ <Link
140
+ to="/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId"
141
+ params={{workspaceSlug, projectSlug, workflowRunId}}
142
+ search={
143
+ workflowRunSearchParams(
144
+ {tab: 'source'},
145
+ {stepId: step.id, stepAttemptId: attempt.id, runAttempt},
146
+ ) as never
147
+ }
148
+ className="font-medium text-foreground-highlight-interactive underline-offset-2 hover:underline"
149
+ >
150
+ View in source
151
+ </Link>
152
+ ) : null}
153
+ </div>
154
+ </CalloutDescription>
155
+ </CalloutContent>
156
+ </Callout>
157
+ );
158
+ }
159
+
160
+ function StepInspector({
161
+ step,
162
+ attempt,
163
+ error,
164
+ showFailure,
165
+ query,
166
+ workspaceSlug,
167
+ projectSlug,
168
+ workflowRunId,
169
+ runAttempt,
170
+ jobId,
171
+ annotationCount,
172
+ }: {
173
+ step: Step;
174
+ attempt: StepAttempt;
175
+ error: StepError | null;
176
+ showFailure: boolean;
177
+ query: ReturnType<typeof useStepAttemptDetailQuery>;
178
+ workspaceSlug: string;
179
+ projectSlug: string;
180
+ workflowRunId: string;
181
+ runAttempt: number;
182
+ jobId: string;
183
+ annotationCount: number | undefined;
184
+ }) {
185
+ const detail = query.data;
186
+ const trace = detail?.evaluationTrace ?? null;
187
+ const resolvedConfig = detail?.config ?? null;
188
+ const hasInputs =
189
+ detail !== undefined &&
190
+ (countConfigValues(detail.authoredConfig) > 0 || countConfigValues(resolvedConfig) > 0);
191
+ const hasOutputs =
192
+ attempt.outputs !== null || attempt.output !== null || attempt.response !== null;
193
+ const hasTrace = trace !== null && trace.length > 0;
194
+ const hasAnnotations = annotationCount !== undefined && annotationCount > 0;
195
+ const detailCount = Number(hasInputs) + Number(hasOutputs) + Number(hasTrace);
196
+
197
+ return (
198
+ <div className="flex min-w-0 flex-col gap-section">
199
+ {showFailure ? (
200
+ <StepFailureCallout
201
+ step={step}
202
+ attempt={attempt}
203
+ error={error}
204
+ workspaceSlug={workspaceSlug}
205
+ projectSlug={projectSlug}
206
+ workflowRunId={workflowRunId}
207
+ runAttempt={runAttempt}
208
+ />
209
+ ) : null}
210
+ {query.isPending ? <InspectorLoading /> : null}
211
+ {query.isError ? (
212
+ <Callout
213
+ role="alert"
214
+ type="warning"
215
+ variant="secondary"
216
+ className="rounded-8 border border-tag-warning-border p-panel-compact shadow-none"
217
+ >
218
+ <CalloutContent>
219
+ <CalloutTitle>Details unavailable</CalloutTitle>
220
+ <CalloutDescription className="flex items-center justify-between gap-inline">
221
+ <span>We could not load the resolved configuration for this attempt.</span>
222
+ <Button
223
+ type="button"
224
+ size="2xs"
225
+ variant="secondary"
226
+ onClick={() => void query.refetch()}
227
+ >
228
+ Retry
229
+ </Button>
230
+ </CalloutDescription>
231
+ </CalloutContent>
232
+ </Callout>
233
+ ) : null}
234
+ {detail ? (
235
+ <div className="flex min-w-0 flex-col gap-group">
236
+ {hasInputs ? (
237
+ <InspectorSection title="Inputs">
238
+ <ConfigCode authoredConfig={detail.authoredConfig} resolvedConfig={resolvedConfig} />
239
+ </InspectorSection>
240
+ ) : null}
241
+ {hasOutputs ? (
242
+ <InspectorSection title="Outputs">
243
+ {attempt.outputs !== null || attempt.output !== null ? (
244
+ <JsonCode
245
+ value={attempt.outputs ?? attempt.output ?? {}}
246
+ emptyMessage="No outputs declared; the `outputs:` mapping is empty."
247
+ />
248
+ ) : null}
249
+ {attempt.response !== null ? (
250
+ <div className="flex min-w-0 flex-col gap-tight">
251
+ <Text size="xs" className="text-foreground-neutral-muted">
252
+ Response
253
+ </Text>
254
+ <pre className="max-h-160 min-w-0 overflow-auto rounded-6 border border-border-neutral-base bg-background-neutral-subtle p-tight font-code text-xs leading-18 text-foreground-neutral-muted scrollbar">
255
+ {attempt.response}
256
+ </pre>
257
+ </div>
258
+ ) : null}
259
+ </InspectorSection>
260
+ ) : null}
261
+ {hasTrace ? (
262
+ <InspectorSection title="Evaluation">
263
+ <EvaluationTrace trace={trace ?? []} />
264
+ </InspectorSection>
265
+ ) : null}
266
+ {detailCount === 0 && !showFailure && !hasAnnotations ? <EmptyInspector /> : null}
267
+ </div>
268
+ ) : null}
269
+ {hasAnnotations ? (
270
+ <Link
271
+ to="/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId"
272
+ params={{workspaceSlug, projectSlug, workflowRunId}}
273
+ search={workflowRunSearchParams({tab: 'annotations'}, {jobId, runAttempt}) as never}
274
+ className="inline-flex w-fit rounded-4 text-xs text-foreground-highlight-interactive underline-offset-2 hover:underline focus-visible:shadow-button-neutral-focus"
275
+ >
276
+ View {annotationCount} annotation{annotationCount === 1 ? '' : 's'}
277
+ </Link>
278
+ ) : null}
279
+ {!query.isPending && !query.isError && !detail && !showFailure && !hasAnnotations ? (
280
+ <EmptyInspector />
281
+ ) : null}
282
+ </div>
283
+ );
284
+ }
285
+
286
+ function InspectorSection({title, children}: {title: string; children: ReactNode}) {
287
+ return (
288
+ <section className="flex min-w-0 flex-col gap-inline" aria-label={title}>
289
+ <Text size="xs" bold className="text-foreground-neutral-base">
290
+ {title}
291
+ </Text>
292
+ {children}
293
+ </section>
294
+ );
295
+ }
296
+
297
+ function ConfigCode({
298
+ authoredConfig,
299
+ resolvedConfig,
300
+ }: {
301
+ authoredConfig: Record<string, unknown> | null;
302
+ resolvedConfig: Record<string, unknown> | null;
303
+ }) {
304
+ const entries: JsonCodeEntry[] = [
305
+ ...(authoredConfig
306
+ ? [
307
+ {
308
+ filename: 'authored.json',
309
+ label: 'Authored configuration',
310
+ value: authoredConfig,
311
+ },
312
+ ]
313
+ : []),
314
+ ...(resolvedConfig
315
+ ? [
316
+ {
317
+ filename: 'resolved.json',
318
+ label: 'Resolved configuration',
319
+ value: resolvedConfig,
320
+ },
321
+ ]
322
+ : []),
323
+ ];
324
+
325
+ if (entries.length === 0) return null;
326
+ return <JsonCodeTabs entries={entries} />;
327
+ }
328
+
329
+ export function EvaluationTrace({trace}: {trace: readonly EvaluationTraceEntry[]}) {
330
+ const keyCounts = new Map<string, number>();
331
+
332
+ return (
333
+ <dl className="flex min-w-0 flex-col divide-y divide-border-neutral-base rounded-6 border border-border-neutral-base">
334
+ {trace.map((entry) => {
335
+ if ('dropped' in entry) {
336
+ const keyBase = `limit-${entry.dropped}`;
337
+ const occurrence = keyCounts.get(keyBase) ?? 0;
338
+ keyCounts.set(keyBase, occurrence + 1);
339
+ return (
340
+ <div
341
+ key={`${keyBase}-${occurrence}`}
342
+ className="px-row py-row text-xs text-foreground-neutral-muted"
343
+ >
344
+ {entry.dropped} more evaluation{entry.dropped === 1 ? '' : 's'} not recorded
345
+ </div>
346
+ );
347
+ }
348
+
349
+ const empty = entry.value === undefined || entry.value === '';
350
+ const keyBase = `evaluation-${entry.field}-${entry.expression}-${entry.evaluatedAt}-${entry.fillTarget}`;
351
+ const occurrence = keyCounts.get(keyBase) ?? 0;
352
+ keyCounts.set(keyBase, occurrence + 1);
353
+ return (
354
+ <div
355
+ key={`${keyBase}-${occurrence}`}
356
+ className={cn(
357
+ 'grid min-w-0 grid-cols-1 gap-inline px-row py-row min-[768px]:grid-cols-[160px_minmax(0,1fr)]',
358
+ entry.degraded && 'border-l border-tag-error-icon',
359
+ )}
360
+ >
361
+ <dt
362
+ className="flex min-w-0 flex-col gap-tight font-code text-xs text-foreground-neutral-muted"
363
+ title={entry.field}
364
+ >
365
+ <span className="block truncate">{entry.field}</span>
366
+ <span className="block break-all text-foreground-neutral-subtle">
367
+ {entry.expression}
368
+ </span>
369
+ </dt>
370
+ <dd className="flex min-w-0 flex-col gap-tight text-xs text-foreground-neutral-base">
371
+ {entry.degraded ? <span className="sr-only">Degraded evaluation</span> : null}
372
+ <div className="break-words font-code">
373
+ {empty ? <span className="text-tag-error-text">(empty)</span> : entry.value}
374
+ </div>
375
+ <div className="flex min-w-0 flex-wrap gap-x-inline gap-y-tight text-foreground-neutral-muted">
376
+ {entry.degraded ? <span className="text-tag-error-text">degraded</span> : null}
377
+ {entry.truncated || entry.exprTruncated ? <span>truncated</span> : null}
378
+ </div>
379
+ </dd>
380
+ </div>
381
+ );
382
+ })}
383
+ </dl>
384
+ );
385
+ }
386
+
387
+ function InspectorLoading() {
388
+ return (
389
+ <div
390
+ role="status"
391
+ aria-label="Loading troubleshooting details"
392
+ className="flex flex-col gap-inline"
393
+ >
394
+ <Skeleton className="h-16 w-120" />
395
+ <Skeleton className="h-120 w-full" />
396
+ </div>
397
+ );
398
+ }
399
+
400
+ function EmptyInspector() {
401
+ return (
402
+ <Text size="xs" className="text-foreground-neutral-muted">
403
+ No additional troubleshooting details were recorded.
404
+ </Text>
405
+ );
406
+ }
407
+
408
+ function selectedStepError(
409
+ step: Step,
410
+ attemptError: Record<string, unknown> | null,
411
+ ): StepError | null {
412
+ return toSelectedAttemptError(step, attemptError) ?? step.error;
413
+ }
414
+
415
+ function failureTitle(reason: string | JobStatusReason): string {
416
+ switch (reason) {
417
+ case 'checkout_failed':
418
+ return 'Checkout failed';
419
+ case 'checkout_auth_failed':
420
+ return 'Checkout authentication failed';
421
+ case 'checkout_unavailable':
422
+ return 'Checkout service unavailable';
423
+ case 'checkout_path_invalid':
424
+ return 'Checkout path is invalid';
425
+ case 'checkout_destination_occupied':
426
+ return 'Checkout destination is already occupied';
427
+ case 'git_unavailable':
428
+ return 'Git was unavailable';
429
+ case 'workspace_prep_failed':
430
+ return 'Workspace preparation failed';
431
+ case 'setup_aborted':
432
+ return 'Step setup was aborted';
433
+ case 'config_unresolvable':
434
+ return 'Step configuration could not be resolved';
435
+ case 'output_invalid':
436
+ return 'Step output was invalid';
437
+ case 'agent_config_invalid':
438
+ return 'Agent configuration is invalid';
439
+ case 'agent_invocation_failed':
440
+ return 'Agent invocation failed';
441
+ case 'agent_harness_unavailable':
442
+ return 'Agent harness was unavailable';
443
+ case 'runner_lost':
444
+ return 'Runner stopped responding';
445
+ case 'timed_out':
446
+ return 'Step timed out';
447
+ case 'step_failed':
448
+ return 'A step failed';
449
+ case 'dependency_not_completed':
450
+ return 'A dependency did not complete';
451
+ case 'condition_false':
452
+ return 'The job condition was false';
453
+ case 'default_gate_rejected':
454
+ return 'The default gate rejected this job';
455
+ case 'condition_rejected':
456
+ return 'The job condition rejected this job';
457
+ case 'condition_errored':
458
+ return 'The job condition could not be evaluated';
459
+ case 'user_cancelled':
460
+ return 'The job was cancelled by a user';
461
+ case 'run_cancelled':
462
+ return 'The run was cancelled';
463
+ case 'unknown':
464
+ return 'The failure reason was not recorded';
465
+ default:
466
+ return 'Step failed';
467
+ }
468
+ }
469
+
470
+ function failureDescription(reason: string | JobStatusReason): string {
471
+ switch (reason) {
472
+ case 'checkout_auth_failed':
473
+ return 'Checkout credentials were rejected. Verify repository access before re-running.';
474
+ case 'checkout_unavailable':
475
+ return 'The checkout service was unavailable. Retry after the service recovers.';
476
+ case 'git_unavailable':
477
+ return 'The runner could not start Git. Check the runner image before re-running.';
478
+ case 'workspace_prep_failed':
479
+ return 'The runner could not prepare its workspace. Review the runner setup details.';
480
+ case 'config_unresolvable':
481
+ return 'The resolved configuration contains a value that could not be evaluated.';
482
+ case 'output_invalid':
483
+ return 'The step returned output that did not match the declared contract.';
484
+ case 'agent_config_invalid':
485
+ return 'The agent configuration is not valid for this step.';
486
+ case 'agent_invocation_failed':
487
+ return 'The agent invocation failed after configuration was accepted.';
488
+ case 'agent_harness_unavailable':
489
+ return 'The runner could not start the agent harness.';
490
+ case 'runner_lost':
491
+ return 'The runner stopped responding before the step completed.';
492
+ case 'timed_out':
493
+ return 'The step exceeded its configured time limit.';
494
+ case 'dependency_not_completed':
495
+ return 'A required job did not complete, so this job could not start.';
496
+ case 'condition_false':
497
+ case 'condition_rejected':
498
+ return 'The job condition did not allow this job to run.';
499
+ case 'condition_errored':
500
+ return 'The job condition could not be evaluated.';
501
+ case 'default_gate_rejected':
502
+ return 'A required job did not succeed, so this job was not allowed to run.';
503
+ case 'step_failed':
504
+ return 'A step failed before this job could complete.';
505
+ case 'user_cancelled':
506
+ case 'run_cancelled':
507
+ return 'The run was cancelled before this work completed.';
508
+ case 'unknown':
509
+ return 'No machine-readable failure reason was recorded.';
510
+ default:
511
+ return `${humanize(reason)}. Review the details below and re-run after resolving the cause.`;
512
+ }
513
+ }
514
+
515
+ function sourceLinkForFailure(reason: string | JobStatusReason): boolean {
516
+ return (
517
+ reason === 'config_unresolvable' ||
518
+ reason === 'agent_config_invalid' ||
519
+ reason === 'output_invalid' ||
520
+ reason === 'default_gate_rejected' ||
521
+ reason === 'condition_rejected' ||
522
+ reason === 'condition_errored'
523
+ );
524
+ }
525
+
526
+ function humanize(value: string): string {
527
+ return value.replaceAll('_', ' ').replace(/\b\w/g, (letter) => letter.toUpperCase());
528
+ }
529
+
530
+ function countConfigValues(value: unknown): number {
531
+ if (Array.isArray(value))
532
+ return value.reduce((total, item) => total + countConfigValues(item), 0);
533
+ if (value !== null && typeof value === 'object') {
534
+ return Object.values(value).reduce((total, item) => total + countConfigValues(item), 0);
535
+ }
536
+ return value === undefined ? 0 : 1;
537
+ }
@@ -12,6 +12,7 @@ import type {JobGraphSelectionSource} from './types.js';
12
12
  const NODE_WIDTH = 208;
13
13
  const NODE_HEIGHT = 48;
14
14
  const COLUMN_GAP = 64;
15
+ /** Fixed graph spacing shared by the DOM rows and the edge geometry below. */
15
16
  const ROW_GAP = 20;
16
17
  const TRIGGER_WIDTH = 36;
17
18
  const PADDING = 16;
@@ -36,7 +37,7 @@ export function JobGraphContent({
36
37
  if (model.nodes.length === 0) {
37
38
  return (
38
39
  <EmptyState
39
- className="min-h-160 px-16 py-24"
40
+ className="min-h-160 p-panel"
40
41
  icon="componentLine"
41
42
  title="No jobs yet"
42
43
  description="This run has not materialized jobs."
@@ -93,8 +94,8 @@ export function JobGraphContent({
93
94
  {model.columns.map((column, columnIndex) => (
94
95
  <div
95
96
  key={column.map((node) => node.id).join(':')}
96
- className="absolute flex flex-col gap-20"
97
- style={{left: jobLeft(columnIndex), top: PADDING}}
97
+ className="absolute flex flex-col"
98
+ style={{left: jobLeft(columnIndex), top: PADDING, rowGap: ROW_GAP}}
98
99
  >
99
100
  {column.map((node) => (
100
101
  <JobNode
@@ -106,7 +106,7 @@ export function JobNode({
106
106
  onPointerEnter={onHoverStart}
107
107
  onPointerLeave={onHoverEnd}
108
108
  className={cn(
109
- 'group relative flex h-48 w-208 cursor-pointer items-center gap-8 rounded-8 bg-background-neutral-base px-10 text-left shadow-button-neutral transition-colors hover:bg-background-components-hover focus-visible:shadow-button-neutral-focus focus-visible:outline-none',
109
+ 'group relative flex h-48 w-208 cursor-pointer items-center gap-inline rounded-8 bg-background-neutral-base px-[10px] text-left shadow-button-neutral transition-colors hover:bg-background-components-hover focus-visible:shadow-button-neutral-focus focus-visible:outline-none',
110
110
  selected && 'bg-background-components-hover hover:bg-background-components-pressed',
111
111
  node.carriedOver && 'opacity-[0.55]',
112
112
  )}
@@ -117,7 +117,7 @@ export function JobNode({
117
117
  className="absolute inset-y-6 left-0 w-px rounded-full bg-border-highlights-interactive"
118
118
  />
119
119
  ) : null}
120
- <div className="flex min-w-0 flex-1 items-center gap-8">
120
+ <div className="flex min-w-0 flex-1 items-center gap-inline">
121
121
  <WorkflowStatusIcon status={status} size={14} />
122
122
  <JobLabel label={node.displayName} />
123
123
  </div>
@@ -136,7 +136,7 @@ function ExecutionCountText({executions}: {executions: JobExecution[]}) {
136
136
  <TooltipTrigger asChild>
137
137
  <span
138
138
  aria-hidden="true"
139
- className="inline-flex h-20 min-w-28 shrink-0 items-center justify-end gap-4 text-foreground-neutral-subtle"
139
+ className="inline-flex h-20 min-w-28 shrink-0 items-center justify-end gap-tight text-foreground-neutral-subtle"
140
140
  >
141
141
  <Icon name="loopRightLine" className="size-12" />
142
142
  <Code as="span" variant="label" className="text-current">
@@ -4,3 +4,4 @@ export type {
4
4
  StepListProps,
5
5
  } from './step-list.js';
6
6
  export {StepList} from './step-list.js';
7
+ export type {StepListEntryModel} from './step-list-model.js';
@@ -77,11 +77,14 @@ export function emptyJobExecutionForJob(job: Job): JobExecution {
77
77
  name: job.name ?? job.key,
78
78
  status: job.status === 'skipped' ? 'cancelled' : job.status,
79
79
  statusReason: job.statusReason,
80
+ runner: job.runner,
81
+ outputs: job.outputs,
80
82
  triggerEvents: [],
81
83
  queuedAt: null,
82
84
  startedAt: null,
83
85
  finishedAt: null,
84
86
  timedOutAt: null,
87
+ evaluationTrace: job.evaluationTrace,
85
88
  createdAt: job.createdAt,
86
89
  updatedAt: job.updatedAt,
87
90
  steps: [],
@@ -158,6 +161,8 @@ function toStepEntries(step: StepModel, carriedOverJob: boolean): StepListEntryM
158
161
  status: step.status,
159
162
  exitCode: null,
160
163
  output: null,
164
+ outputs: null,
165
+ response: null,
161
166
  error: null,
162
167
  gateResult: null,
163
168
  restartFeedback: null,