@shipfox/client-workflows 16.0.0 → 18.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 (182) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +32 -0
  3. package/dist/components/identifier/identifier.js +2 -2
  4. package/dist/components/identifier/identifier.js.map +1 -1
  5. package/dist/components/job-detail/agent-config-failure-callout.d.ts.map +1 -1
  6. package/dist/components/job-detail/agent-config-failure-callout.js +1 -1
  7. package/dist/components/job-detail/agent-config-failure-callout.js.map +1 -1
  8. package/dist/components/job-detail/job-context-panel.d.ts +6 -0
  9. package/dist/components/job-detail/job-context-panel.d.ts.map +1 -0
  10. package/dist/components/job-detail/job-context-panel.js +228 -0
  11. package/dist/components/job-detail/job-context-panel.js.map +1 -0
  12. package/dist/components/job-detail/job-detail-header.d.ts +3 -1
  13. package/dist/components/job-detail/job-detail-header.d.ts.map +1 -1
  14. package/dist/components/job-detail/job-detail-header.js +54 -48
  15. package/dist/components/job-detail/job-detail-header.js.map +1 -1
  16. package/dist/components/job-detail/job-detail-view.d.ts +1 -1
  17. package/dist/components/job-detail/job-detail-view.d.ts.map +1 -1
  18. package/dist/components/job-detail/job-detail-view.js +100 -57
  19. package/dist/components/job-detail/job-detail-view.js.map +1 -1
  20. package/dist/components/job-detail/job-empty-states.d.ts +4 -0
  21. package/dist/components/job-detail/job-empty-states.d.ts.map +1 -1
  22. package/dist/components/job-detail/job-empty-states.js +60 -11
  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 +497 -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 +6 -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.d.ts.map +1 -1
  85. package/dist/components/workflow-run-view/run-workspace-nav.js +11 -10
  86. package/dist/components/workflow-run-view/run-workspace-nav.js.map +1 -1
  87. package/dist/components/workflow-run-view/workflow-run-states.js +6 -6
  88. package/dist/components/workflow-run-view/workflow-run-states.js.map +1 -1
  89. package/dist/components/workflow-run-view/workflow-run-view.d.ts.map +1 -1
  90. package/dist/components/workflow-run-view/workflow-run-view.js +72 -12
  91. package/dist/components/workflow-run-view/workflow-run-view.js.map +1 -1
  92. package/dist/core/entities/job-execution.d.ts +9 -0
  93. package/dist/core/entities/job-execution.d.ts.map +1 -1
  94. package/dist/core/entities/job-execution.js.map +1 -1
  95. package/dist/core/entities/job.d.ts +10 -1
  96. package/dist/core/entities/job.d.ts.map +1 -1
  97. package/dist/core/entities/job.js.map +1 -1
  98. package/dist/core/entities/step-attempt.d.ts +29 -0
  99. package/dist/core/entities/step-attempt.d.ts.map +1 -1
  100. package/dist/core/entities/step-attempt.js.map +1 -1
  101. package/dist/core/entities/step.d.ts +3 -1
  102. package/dist/core/entities/step.d.ts.map +1 -1
  103. package/dist/core/entities/step.js.map +1 -1
  104. package/dist/core/run-annotation.d.ts +6 -0
  105. package/dist/core/run-annotation.d.ts.map +1 -1
  106. package/dist/core/run-annotation.js.map +1 -1
  107. package/dist/core/workflow-run.d.ts +1 -1
  108. package/dist/core/workflow-run.d.ts.map +1 -1
  109. package/dist/core/workflow-run.js.map +1 -1
  110. package/dist/hooks/api/annotations.d.ts +63 -0
  111. package/dist/hooks/api/annotations.d.ts.map +1 -0
  112. package/dist/hooks/api/annotations.js +145 -0
  113. package/dist/hooks/api/annotations.js.map +1 -0
  114. package/dist/hooks/api/step-attempt-detail.d.ts +14 -0
  115. package/dist/hooks/api/step-attempt-detail.d.ts.map +1 -0
  116. package/dist/hooks/api/step-attempt-detail.js +43 -0
  117. package/dist/hooks/api/step-attempt-detail.js.map +1 -0
  118. package/dist/hooks/api/workflow-run-mapper.d.ts +9 -2
  119. package/dist/hooks/api/workflow-run-mapper.d.ts.map +1 -1
  120. package/dist/hooks/api/workflow-run-mapper.js +48 -0
  121. package/dist/hooks/api/workflow-run-mapper.js.map +1 -1
  122. package/dist/pages/project-workflows-page.js +19 -19
  123. package/dist/pages/project-workflows-page.js.map +1 -1
  124. package/dist/pages/workflow-run-list-page.js +1 -1
  125. package/dist/pages/workflow-run-list-page.js.map +1 -1
  126. package/dist/tsconfig.test.tsbuildinfo +1 -1
  127. package/package.json +10 -10
  128. package/src/components/identifier/identifier.tsx +2 -2
  129. package/src/components/job-detail/agent-config-failure-callout.tsx +5 -1
  130. package/src/components/job-detail/job-context-panel.test.tsx +72 -0
  131. package/src/components/job-detail/job-context-panel.tsx +241 -0
  132. package/src/components/job-detail/job-detail-header.tsx +52 -46
  133. package/src/components/job-detail/job-detail-view.stories.tsx +555 -53
  134. package/src/components/job-detail/job-detail-view.tsx +105 -59
  135. package/src/components/job-detail/job-empty-states.test.ts +105 -1
  136. package/src/components/job-detail/job-empty-states.tsx +77 -10
  137. package/src/components/job-detail/job-execution-switcher.tsx +5 -5
  138. package/src/components/job-detail/json-code.tsx +120 -0
  139. package/src/components/job-detail/step-attempt-log-panel.test.tsx +1 -0
  140. package/src/components/job-detail/step-attempt-log-panel.tsx +17 -6
  141. package/src/components/job-detail/step-troubleshooting.stories.tsx +120 -0
  142. package/src/components/job-detail/step-troubleshooting.test.tsx +337 -0
  143. package/src/components/job-detail/step-troubleshooting.tsx +541 -0
  144. package/src/components/job-graph/job-graph-content.tsx +4 -3
  145. package/src/components/job-graph/job-node.tsx +3 -3
  146. package/src/components/step-list/index.ts +1 -0
  147. package/src/components/step-list/step-list-model.ts +6 -0
  148. package/src/components/step-list/step-list.test.tsx +75 -2
  149. package/src/components/step-list/step-list.tsx +73 -20
  150. package/src/components/workflow-run-duration-label.tsx +1 -1
  151. package/src/components/workflow-run-list/job-status-strip.tsx +6 -6
  152. package/src/components/workflow-run-list/workflow-run-filter-menu.tsx +1 -1
  153. package/src/components/workflow-run-list/workflow-run-filters.tsx +4 -4
  154. package/src/components/workflow-run-list/workflow-run-list-states.tsx +6 -6
  155. package/src/components/workflow-run-list/workflow-run-list-view.tsx +1 -1
  156. package/src/components/workflow-run-list/workflow-run-row.tsx +7 -7
  157. package/src/components/workflow-run-summary/workflow-run-attempt-switcher.tsx +1 -1
  158. package/src/components/workflow-run-summary/workflow-run-summary.test.tsx +1 -1
  159. package/src/components/workflow-run-summary/workflow-run-summary.tsx +11 -11
  160. package/src/components/workflow-run-tabs/index.ts +5 -1
  161. package/src/components/workflow-run-tabs/run-annotation-count-chip.tsx +2 -2
  162. package/src/components/workflow-run-tabs/run-annotation-list.tsx +25 -10
  163. package/src/components/workflow-run-tabs/run-annotation-summary-line.tsx +5 -5
  164. package/src/components/workflow-run-view/run-workspace-nav.tsx +18 -11
  165. package/src/components/workflow-run-view/workflow-run-states.tsx +6 -6
  166. package/src/components/workflow-run-view/workflow-run-view.tsx +114 -14
  167. package/src/core/entities/job-execution.ts +9 -0
  168. package/src/core/entities/job.ts +12 -1
  169. package/src/core/entities/step-attempt.ts +33 -0
  170. package/src/core/entities/step.ts +3 -1
  171. package/src/core/run-annotation.ts +6 -0
  172. package/src/core/workflow-run.ts +4 -0
  173. package/src/hooks/api/annotations.test.ts +84 -0
  174. package/src/hooks/api/annotations.ts +240 -0
  175. package/src/hooks/api/step-attempt-detail.ts +67 -0
  176. package/src/hooks/api/workflow-run-mapper.ts +47 -0
  177. package/src/hooks/api/workflow-runs.test.tsx +61 -1
  178. package/src/pages/project-workflows-page.tsx +19 -19
  179. package/src/pages/workflow-job-detail-page.test.tsx +52 -1
  180. package/src/pages/workflow-run-list-page.tsx +1 -1
  181. package/test/fixtures/workflow-run.ts +7 -0
  182. package/tsconfig.build.tsbuildinfo +1 -1
@@ -2,13 +2,25 @@
2
2
  // biome-ignore-all lint/a11y/noNoninteractiveTabindex: the story mirrors the focusable log surface.
3
3
 
4
4
  import {type StepLogSnapshot, stepLogsQueryKeys} from '@shipfox/client-logs';
5
- import {Text} from '@shipfox/react-ui/typography';
6
5
  import type {Meta, StoryObj} from '@storybook/react';
7
6
  import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
7
+ import {
8
+ createMemoryHistory,
9
+ createRootRoute,
10
+ createRoute,
11
+ createRouter,
12
+ Outlet,
13
+ RouterProvider,
14
+ } from '@tanstack/react-router';
8
15
  import {type ReactNode, useState} from 'react';
9
- import type {Job, JobExecution, WorkflowRunDetail} from '#core/workflow-run.js';
10
- import {runAnnotationsQueryKeys} from '#hooks/api/run-annotations.js';
16
+ import {userEvent, within} from 'storybook/test';
17
+ import type {RunAnnotationSummary} from '#core/run-annotation.js';
18
+ import type {Job, JobExecution, StepAttemptDetail, WorkflowRunDetail} from '#core/workflow-run.js';
19
+ import {workflowRunAnnotationsQueryKeys} from '#hooks/api/annotations.js';
20
+ import {stepAttemptDetailQueryKeys} from '#hooks/api/step-attempt-detail.js';
11
21
  import type {useWorkflowRunAttemptQuery} from '#hooks/api/workflow-runs.js';
22
+ import {workflowRunsQueryKeys} from '#hooks/api/workflow-runs.js';
23
+ import {WorkflowJobDetailPage} from '#pages/workflow-job-detail-page.js';
12
24
  import {
13
25
  workflowJob,
14
26
  workflowJobExecutionDto,
@@ -22,6 +34,7 @@ import {JobDetailView} from './job-detail-view.js';
22
34
  const WORKSPACE_SLUG = 'acme';
23
35
  const PROJECT_SLUG = 'platform';
24
36
  const RUN_ID = '11111111-1111-4111-8111-111111111111';
37
+ const INSPECTOR_TRIGGER_NAME = /Inspect Run tests/;
25
38
  const LOG_RECORDS: StepLogSnapshot['records'] = [
26
39
  {
27
40
  v: 1,
@@ -43,6 +56,8 @@ interface JobDetailStoryArgs {
43
56
  run: WorkflowRunDetail;
44
57
  jobId: string;
45
58
  selectedExecutionId?: string | undefined;
59
+ search?: Parameters<typeof JobDetailView>[0]['search'];
60
+ stepDetails?: readonly StepAttemptDetail[];
46
61
  }
47
62
 
48
63
  type JobDetailQuery = ReturnType<typeof useWorkflowRunAttemptQuery>;
@@ -73,52 +88,132 @@ export const Executions: Story = {
73
88
  },
74
89
  };
75
90
 
76
- export const DataStates: Story = {
91
+ export const Loading: Story = {
77
92
  render: () => {
78
- const partialRun = partialJobRun();
79
- const emptyRun = emptyJobRun();
80
- const loadedRun = playgroundRun();
93
+ const run = playgroundRun();
81
94
  return (
82
- <div className="grid max-w-[1120px] gap-16 p-16 md:grid-cols-2">
83
- <StateExample label="Loading">
84
- <JobDetailStoryState
85
- query={makeQuery(undefined, {isPending: true})}
86
- jobId={loadedRun.jobs[0]?.id ?? ''}
87
- />
88
- </StateExample>
89
- <StateExample label="Empty">
90
- <JobDetailStoryState query={makeQuery(emptyRun)} jobId={emptyRun.jobs[0]?.id ?? ''} />
91
- </StateExample>
92
- <StateExample label="Error">
93
- <JobDetailStoryState
94
- query={makeQuery(undefined, {isError: true, error: new Error('Storybook error')})}
95
- jobId="missing-job"
96
- />
97
- </StateExample>
98
- <StateExample label="Partial">
99
- <JobDetailStoryState query={makeQuery(partialRun)} jobId={partialRun.jobs[0]?.id ?? ''} />
100
- </StateExample>
101
- <StateExample label="Job not found">
102
- <JobDetailStoryState query={makeQuery(loadedRun)} jobId="missing-job" />
103
- </StateExample>
104
- </div>
95
+ <JobDetailStoryViewport>
96
+ <JobDetailStoryState
97
+ query={makeQuery(undefined, {isPending: true})}
98
+ jobId={run.jobs[0]?.id ?? ''}
99
+ />
100
+ </JobDetailStoryViewport>
105
101
  );
106
102
  },
107
103
  };
108
104
 
109
- function JobDetailStoryFrame({run, jobId, selectedExecutionId}: JobDetailStoryArgs) {
105
+ export const Empty: Story = {
106
+ render: () => {
107
+ const run = emptyJobRun();
108
+ return <JobDetailStoryFrame run={run} jobId={run.jobs[0]?.id ?? ''} />;
109
+ },
110
+ };
111
+
112
+ export const ErrorState: Story = {
113
+ render: () => (
114
+ <JobDetailStoryViewport>
115
+ <JobDetailStoryState
116
+ query={makeQuery(undefined, {isError: true, error: new Error('Storybook error')})}
117
+ jobId="missing-job"
118
+ />
119
+ </JobDetailStoryViewport>
120
+ ),
121
+ };
122
+
123
+ export const Partial: Story = {
124
+ render: () => {
125
+ const run = partialJobRun();
126
+ return <JobDetailStoryFrame run={run} jobId={run.jobs[0]?.id ?? ''} />;
127
+ },
128
+ };
129
+
130
+ export const JobNotFound: Story = {
131
+ render: () => {
132
+ const run = playgroundRun();
133
+ return <JobDetailStoryFrame run={run} jobId="missing-job" />;
134
+ },
135
+ };
136
+
137
+ export const StepFailure: Story = {
138
+ render: () => {
139
+ const {run, commandJobId, stepDetails} = failureRun();
140
+ return <JobDetailStoryFrame run={run} jobId={commandJobId} stepDetails={stepDetails} />;
141
+ },
142
+ };
143
+
144
+ export const TimedOutBeforeStep: Story = {
145
+ render: () => {
146
+ const {run, timeoutJobId} = failureRun();
147
+ return <JobDetailStoryFrame run={run} jobId={timeoutJobId} />;
148
+ },
149
+ };
150
+
151
+ export const ConditionRejected: Story = {
152
+ render: () => {
153
+ const {run, conditionJobId} = failureRun();
154
+ return <JobDetailStoryFrame run={run} jobId={conditionJobId} />;
155
+ },
156
+ };
157
+
158
+ export const FailureStates: Story = {
159
+ render: () => <FailureCompositionStory />,
160
+ };
161
+
162
+ export const InspectionData: Story = {
163
+ render: () => {
164
+ const {run, jobId, executionId, stepId, attemptId, stepDetails} = inspectionRun();
165
+ return (
166
+ <JobDetailStoryFrame
167
+ run={run}
168
+ jobId={jobId}
169
+ selectedExecutionId={executionId}
170
+ search={{jobExecutionId: executionId, stepId, stepAttemptId: attemptId}}
171
+ stepDetails={stepDetails}
172
+ />
173
+ );
174
+ },
175
+ play: async ({canvasElement}) => {
176
+ const canvas = within(canvasElement);
177
+ const inspectorTrigger = await canvas.findByRole('button', {
178
+ name: INSPECTOR_TRIGGER_NAME,
179
+ });
180
+ await userEvent.click(inspectorTrigger);
181
+ const documentBody = within(canvasElement.ownerDocument.body);
182
+ await documentBody.findByRole('region', {name: 'Inputs'});
183
+ await documentBody.findByRole('region', {name: 'Evaluation'});
184
+ },
185
+ };
186
+
187
+ export const RunComposition: Story = {
188
+ render: () => <RunCompositionStory />,
189
+ };
190
+
191
+ function JobDetailStoryFrame({
192
+ run,
193
+ jobId,
194
+ selectedExecutionId,
195
+ search,
196
+ stepDetails,
197
+ }: JobDetailStoryArgs) {
110
198
  const job = run.jobs.find((candidate) => candidate.id === jobId) ?? run.jobs[0];
111
199
  const selectedExecution = job?.jobExecutions.find(
112
200
  (execution) => execution.id === selectedExecutionId,
113
201
  );
114
202
  return (
115
- <div className="flex min-h-screen min-w-0 bg-background-neutral-base">
203
+ <JobDetailStoryViewport>
116
204
  <JobDetailStoryState
117
205
  query={makeQuery(run)}
118
206
  jobId={jobId}
119
- search={selectedExecution ? {jobExecutionId: selectedExecution.id} : {}}
207
+ search={search ?? (selectedExecution ? {jobExecutionId: selectedExecution.id} : {})}
208
+ stepDetails={stepDetails}
120
209
  />
121
- </div>
210
+ </JobDetailStoryViewport>
211
+ );
212
+ }
213
+
214
+ function JobDetailStoryViewport({children}: {children: ReactNode}) {
215
+ return (
216
+ <div className="flex min-h-screen min-w-0 w-full bg-background-neutral-base">{children}</div>
122
217
  );
123
218
  }
124
219
 
@@ -126,14 +221,16 @@ function JobDetailStoryState({
126
221
  query,
127
222
  jobId,
128
223
  search = {},
224
+ stepDetails = [],
129
225
  }: {
130
226
  query: JobDetailQuery;
131
227
  jobId: string;
132
228
  search?: Parameters<typeof JobDetailView>[0]['search'];
229
+ stepDetails?: readonly StepAttemptDetail[];
133
230
  }) {
134
231
  const run = query.data;
135
232
  return (
136
- <StoryQueryProvider run={run}>
233
+ <StoryQueryProvider run={run} stepDetails={stepDetails}>
137
234
  <JobDetailView
138
235
  workspaceSlug={WORKSPACE_SLUG}
139
236
  projectSlug={PROJECT_SLUG}
@@ -161,9 +258,11 @@ function makeQuery(data: WorkflowRunDetail | undefined, overrides: Partial<JobDe
161
258
 
162
259
  function StoryQueryProvider({
163
260
  run,
261
+ stepDetails = [],
164
262
  children,
165
263
  }: {
166
264
  run: WorkflowRunDetail | undefined;
265
+ stepDetails?: readonly StepAttemptDetail[];
167
266
  children: ReactNode;
168
267
  }) {
169
268
  const [queryClient] = useState(() => {
@@ -171,10 +270,6 @@ function StoryQueryProvider({
171
270
  defaultOptions: {queries: {staleTime: Number.POSITIVE_INFINITY}},
172
271
  });
173
272
  if (run) {
174
- client.setQueryData(runAnnotationsQueryKeys.list(run.id, run.runAttempt.attempt), {
175
- pages: [{annotations: [], hasMore: false, nextCursor: null}],
176
- pageParams: [undefined],
177
- });
178
273
  for (const job of run.jobs) {
179
274
  for (const execution of job.jobExecutions) {
180
275
  for (const step of execution.steps) {
@@ -187,6 +282,34 @@ function StoryQueryProvider({
187
282
  }
188
283
  }
189
284
  }
285
+ client.setQueryData(workflowRunsQueryKeys.detail(run.id), run);
286
+ for (const detail of stepDetails) {
287
+ client.setQueryData(
288
+ stepAttemptDetailQueryKeys.detail(detail.stepId, detail.attempt),
289
+ detail,
290
+ );
291
+ }
292
+ const summary: RunAnnotationSummary = {
293
+ total: 0,
294
+ error: 0,
295
+ warning: 0,
296
+ info: 0,
297
+ success: 0,
298
+ truncated: false,
299
+ stepCounts: [],
300
+ };
301
+ for (const job of run.jobs) {
302
+ for (const execution of job.jobExecutions) {
303
+ client.setQueryData(
304
+ workflowRunAnnotationsQueryKeys.summary(run.id, run.runAttempt.attempt, execution.id),
305
+ summary,
306
+ );
307
+ }
308
+ }
309
+ client.setQueryData(
310
+ workflowRunAnnotationsQueryKeys.summary(run.id, run.runAttempt.attempt),
311
+ summary,
312
+ );
190
313
  }
191
314
  return client;
192
315
  });
@@ -249,6 +372,135 @@ function playgroundRun(): WorkflowRunDetail {
249
372
  });
250
373
  }
251
374
 
375
+ function compositionRun() {
376
+ const setupId = '12121212-1212-4212-8212-121212121212';
377
+ const buildId = '13131313-1313-4313-8313-131313131313';
378
+ const buildExecutionId = '14141414-1414-4414-8414-141414141414';
379
+ const installStepId = '15151515-1515-4515-8515-151515151515';
380
+ const installAttemptId = '16161616-1616-4616-8616-161616161616';
381
+ const testStepId = '17171717-1717-4717-8717-171717171717';
382
+ const testAttemptId = '18181818-1818-4818-8818-181818181818';
383
+ const packageStepId = '19191919-1919-4919-8919-191919191919';
384
+ const deployId = '20202020-2020-4020-8020-202020202020';
385
+
386
+ const installStep = workflowStepDto({
387
+ id: installStepId,
388
+ job_execution_id: buildExecutionId,
389
+ key: 'install',
390
+ name: 'Install dependencies',
391
+ status: 'succeeded',
392
+ position: 0,
393
+ attempts: [
394
+ workflowStepAttemptDto({
395
+ id: installAttemptId,
396
+ step_id: installStepId,
397
+ status: 'succeeded',
398
+ finished_at: '2026-08-06T09:02:00.000Z',
399
+ }),
400
+ ],
401
+ });
402
+ const testStep = workflowStepDto({
403
+ id: testStepId,
404
+ job_execution_id: buildExecutionId,
405
+ key: 'test',
406
+ name: 'Run tests',
407
+ status: 'failed',
408
+ status_reason: 'agent_invocation_failed',
409
+ position: 1,
410
+ config: {run: 'pnpm test --filter=@shipfox/client-workflows'},
411
+ error: {
412
+ message: 'The test command exited with code 1.',
413
+ reason: 'agent_invocation_failed',
414
+ category: 'user',
415
+ exit_code: 1,
416
+ },
417
+ attempts: [
418
+ workflowStepAttemptDto({
419
+ id: testAttemptId,
420
+ step_id: testStepId,
421
+ status: 'failed',
422
+ exit_code: 1,
423
+ output: {summary: '4 tests failed in the workflow package.'},
424
+ outputs: {failed_tests: 4},
425
+ error: {
426
+ message: 'The test command exited with code 1.',
427
+ reason: 'agent_invocation_failed',
428
+ category: 'user',
429
+ exit_code: 1,
430
+ },
431
+ finished_at: '2026-08-06T09:05:00.000Z',
432
+ }),
433
+ ],
434
+ });
435
+ const packageStep = workflowStepDto({
436
+ id: packageStepId,
437
+ job_execution_id: buildExecutionId,
438
+ key: 'package',
439
+ name: 'Package artifacts',
440
+ status: 'skipped',
441
+ status_reason: 'upstream_failed',
442
+ position: 2,
443
+ attempts: [],
444
+ });
445
+ const buildExecution = workflowJobExecutionDto({
446
+ id: buildExecutionId,
447
+ job_id: buildId,
448
+ status: 'failed',
449
+ status_reason: 'step_failed',
450
+ runner: ['runner-linux-x64'],
451
+ outputs: {failed_tests: 4},
452
+ steps: [installStep, testStep, packageStep],
453
+ });
454
+ const buildJob = workflowJob({
455
+ id: buildId,
456
+ key: 'build',
457
+ name: 'build',
458
+ status: 'failed',
459
+ status_reason: 'step_failed',
460
+ runner: ['runner-linux-x64'],
461
+ outputs: {failed_tests: 4},
462
+ position: 1,
463
+ job_executions: [buildExecution],
464
+ });
465
+ const run = storyRun({
466
+ status: 'failed',
467
+ jobs: [
468
+ makeJob({
469
+ id: setupId,
470
+ key: 'setup',
471
+ name: 'setup',
472
+ status: 'succeeded',
473
+ position: 0,
474
+ executions: [
475
+ makeExecution('21212121-2121-4212-8212-212121212121', setupId, 1, 'succeeded'),
476
+ ],
477
+ }),
478
+ buildJob,
479
+ makeJob({
480
+ id: deployId,
481
+ key: 'deploy',
482
+ name: 'deploy',
483
+ status: 'pending',
484
+ position: 2,
485
+ dependencies: [buildId],
486
+ executions: [],
487
+ }),
488
+ ],
489
+ });
490
+
491
+ return {
492
+ run,
493
+ stepDetails: [
494
+ stepAttemptDetail({
495
+ stepId: testStepId,
496
+ attempt: 1,
497
+ authoredConfig: {run: String.raw`pnpm test --filter=\${{ inputs.package }}`},
498
+ config: {run: 'pnpm test --filter=@shipfox/client-workflows'},
499
+ }),
500
+ ],
501
+ };
502
+ }
503
+
252
504
  function emptyJobRun(): WorkflowRunDetail {
253
505
  return storyRun({
254
506
  status: 'succeeded',
@@ -284,6 +536,250 @@ function partialJobRun(): WorkflowRunDetail {
284
536
  });
285
537
  }
286
538
 
539
+ function failureRun() {
540
+ const commandJobId = 'eeeeeeee-eeee-4eee-8eee-eeeeeeeeeee1';
541
+ const commandExecutionId = 'eeeeeeee-eeee-4eee-8eee-eeeeeeeeeee2';
542
+ const commandStepId = 'eeeeeeee-eeee-4eee-8eee-eeeeeeeeeee3';
543
+ const commandAttemptId = 'eeeeeeee-eeee-4eee-8eee-eeeeeeeeeee4';
544
+ const timeoutJobId = 'eeeeeeee-eeee-4eee-8eee-eeeeeeeeeee5';
545
+ const timeoutExecutionId = 'eeeeeeee-eeee-4eee-8eee-eeeeeeeeeee6';
546
+ const conditionJobId = 'eeeeeeee-eeee-4eee-8eee-eeeeeeeeeee7';
547
+ const commandStep = workflowStepDto({
548
+ id: commandStepId,
549
+ job_execution_id: commandExecutionId,
550
+ key: 'run-tests',
551
+ name: 'Run tests',
552
+ status: 'failed',
553
+ status_reason: 'agent_invocation_failed',
554
+ config: {run: 'pnpm test --filter=@shipfox/client-workflows'},
555
+ evaluation_trace: [
556
+ {
557
+ expression: 'inputs["package"]',
558
+ roots: ['inputs.package'],
559
+ fill_target: 'step-dispatch',
560
+ evaluated_at: '2026-08-05T12:00:00.000Z',
561
+ field: 'run',
562
+ value: '@shipfox/client-workflows',
563
+ },
564
+ ],
565
+ error: {
566
+ message: 'The test command exited with code 1.',
567
+ reason: 'agent_invocation_failed',
568
+ category: 'user',
569
+ exit_code: 1,
570
+ },
571
+ attempts: [
572
+ workflowStepAttemptDto({
573
+ id: commandAttemptId,
574
+ step_id: commandStepId,
575
+ status: 'failed',
576
+ exit_code: 1,
577
+ output: {summary: '4 tests failed in the workflow package.'},
578
+ outputs: {failed_tests: 4},
579
+ error: {
580
+ message: 'The test command exited with code 1.',
581
+ reason: 'agent_invocation_failed',
582
+ category: 'user',
583
+ exit_code: 1,
584
+ },
585
+ finished_at: '2026-08-05T12:04:00.000Z',
586
+ }),
587
+ ],
588
+ });
589
+ const commandExecution = workflowJobExecutionDto({
590
+ id: commandExecutionId,
591
+ job_id: commandJobId,
592
+ status: 'failed',
593
+ status_reason: 'step_failed',
594
+ runner: ['runner-linux-x64'],
595
+ outputs: {test_count: 370, failed_tests: 4},
596
+ evaluation_trace: [
597
+ {
598
+ expression: 'inputs["package"]',
599
+ roots: ['inputs.package'],
600
+ fill_target: 'job-dispatch',
601
+ evaluated_at: '2026-08-05T12:00:00.000Z',
602
+ field: 'condition',
603
+ value: '@shipfox/client-workflows',
604
+ },
605
+ ],
606
+ steps: [commandStep],
607
+ });
608
+ const commandJob = workflowJob({
609
+ id: commandJobId,
610
+ key: 'test',
611
+ name: 'test',
612
+ status: 'failed',
613
+ status_reason: 'step_failed',
614
+ runner: ['runner-linux-x64'],
615
+ outputs: {test_count: 370, failed_tests: 4},
616
+ position: 0,
617
+ job_executions: [commandExecution],
618
+ });
619
+ const timeoutJob = workflowJob({
620
+ id: timeoutJobId,
621
+ key: 'deploy',
622
+ name: 'deploy',
623
+ status: 'failed',
624
+ status_reason: 'timed_out',
625
+ position: 1,
626
+ job_executions: [
627
+ workflowJobExecutionDto({
628
+ id: timeoutExecutionId,
629
+ job_id: timeoutJobId,
630
+ status: 'failed',
631
+ status_reason: 'timed_out',
632
+ steps: [],
633
+ }),
634
+ ],
635
+ });
636
+ const conditionJob = workflowJob({
637
+ id: conditionJobId,
638
+ key: 'publish',
639
+ name: 'publish',
640
+ status: 'skipped',
641
+ status_reason: 'condition_rejected',
642
+ position: 2,
643
+ job_executions: [],
644
+ });
645
+ const run = storyRun({status: 'failed', jobs: [commandJob, timeoutJob, conditionJob]});
646
+ const stepDetails = [
647
+ stepAttemptDetail({
648
+ stepId: commandStepId,
649
+ attempt: 1,
650
+ authoredConfig: {run: String.raw`pnpm test --filter=\${{ inputs.package }}`},
651
+ config: {run: 'pnpm test --filter=@shipfox/client-workflows'},
652
+ }),
653
+ ];
654
+
655
+ return {
656
+ run,
657
+ commandJobId,
658
+ timeoutJobId,
659
+ conditionJobId,
660
+ commandExecutionId,
661
+ commandStepId,
662
+ commandAttemptId,
663
+ stepDetails,
664
+ };
665
+ }
666
+
667
+ function inspectionRun() {
668
+ const story = failureRun();
669
+ return {
670
+ run: story.run,
671
+ jobId: story.commandJobId,
672
+ executionId: story.commandExecutionId,
673
+ stepId: story.commandStepId,
674
+ attemptId: story.commandAttemptId,
675
+ stepDetails: story.stepDetails,
676
+ };
677
+ }
678
+
679
+ function stepAttemptDetail({
680
+ stepId,
681
+ attempt,
682
+ authoredConfig,
683
+ config,
684
+ }: {
685
+ stepId: string;
686
+ attempt: number;
687
+ authoredConfig: Record<string, unknown>;
688
+ config: Record<string, unknown>;
689
+ }): StepAttemptDetail {
690
+ return {
691
+ stepId,
692
+ attempt,
693
+ authoredConfig,
694
+ config,
695
+ evaluationTrace: [
696
+ {
697
+ expression: 'inputs.package',
698
+ roots: ['inputs.package'],
699
+ fillTarget: 'run',
700
+ evaluatedAt: '2026-08-05T12:00:00.000Z',
701
+ field: 'run',
702
+ value: '@shipfox/client-workflows',
703
+ },
704
+ {
705
+ expression: 'inputs.branch',
706
+ roots: ['inputs.branch'],
707
+ fillTarget: 'run',
708
+ evaluatedAt: '2026-08-05T12:00:00.000Z',
709
+ field: 'run',
710
+ value: 'main',
711
+ },
712
+ ],
713
+ };
714
+ }
715
+
716
+ function RunCompositionStory() {
717
+ const {run, stepDetails} = compositionRun();
718
+ const job = run.jobs[1] ?? run.jobs[0];
719
+ if (!job) return null;
720
+
721
+ return (
722
+ <StoryQueryProvider run={run} stepDetails={stepDetails}>
723
+ <StoryRouter>
724
+ <WorkflowJobDetailPage
725
+ projectId="project-id"
726
+ workspaceSlug={WORKSPACE_SLUG}
727
+ projectSlug={PROJECT_SLUG}
728
+ workflowRunId={run.id}
729
+ jobId={job.id}
730
+ search={{}}
731
+ />
732
+ </StoryRouter>
733
+ </StoryQueryProvider>
734
+ );
735
+ }
736
+
737
+ function FailureCompositionStory() {
738
+ const {run, commandJobId, stepDetails} = failureRun();
739
+
740
+ return (
741
+ <StoryQueryProvider run={run} stepDetails={stepDetails}>
742
+ <StoryRouter>
743
+ <WorkflowJobDetailPage
744
+ projectId="project-id"
745
+ workspaceSlug={WORKSPACE_SLUG}
746
+ projectSlug={PROJECT_SLUG}
747
+ workflowRunId={run.id}
748
+ jobId={commandJobId}
749
+ search={{}}
750
+ />
751
+ </StoryRouter>
752
+ </StoryQueryProvider>
753
+ );
754
+ }
755
+
756
+ function StoryRouter({children}: {children: ReactNode}) {
757
+ const [router] = useState(() => {
758
+ const rootRoute = createRootRoute({component: Outlet});
759
+ const storyRoute = createRoute({
760
+ getParentRoute: () => rootRoute,
761
+ path: '/',
762
+ component: () => children,
763
+ });
764
+ const runRoute = createRoute({
765
+ getParentRoute: () => rootRoute,
766
+ path: '/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId',
767
+ component: () => null,
768
+ });
769
+ const jobRoute = createRoute({
770
+ getParentRoute: () => rootRoute,
771
+ path: '/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId/jobs/$jobId',
772
+ component: () => null,
773
+ });
774
+ return createRouter({
775
+ history: createMemoryHistory({initialEntries: ['/']}),
776
+ routeTree: rootRoute.addChildren([storyRoute, runRoute, jobRoute]),
777
+ });
778
+ });
779
+
780
+ return <RouterProvider router={router} />;
781
+ }
782
+
287
783
  function executionStory() {
288
784
  const jobId = '77777777-7777-4777-8777-777777777777';
289
785
  const firstExecutionId = '88888888-8888-4888-8888-888888888888';
@@ -335,6 +831,9 @@ function storyRun({
335
831
  status: job.status,
336
832
  status_reason: job.statusReason,
337
833
  carried_over: job.carriedOver,
834
+ success: job.success,
835
+ runner: job.runner,
836
+ evaluation_trace: job.evaluationTrace,
338
837
  listening: job.listening,
339
838
  listener_status: job.listenerStatus,
340
839
  dependencies: job.dependencies,
@@ -349,8 +848,10 @@ function storyRun({
349
848
  name: execution.name,
350
849
  status: execution.status,
351
850
  status_reason: execution.statusReason,
851
+ runner: execution.runner,
352
852
  trigger_events: [],
353
- outputs: null,
853
+ outputs: execution.outputs,
854
+ evaluation_trace: execution.evaluationTrace,
354
855
  queued_at: execution.queuedAt,
355
856
  started_at: execution.startedAt,
356
857
  finished_at: execution.finishedAt,
@@ -366,7 +867,19 @@ function storyRun({
366
867
  status: step.status,
367
868
  type: step.type,
368
869
  config: step.config,
369
- error: null,
870
+ evaluation_trace: step.evaluationTrace,
871
+ error: step.error
872
+ ? {
873
+ message: step.error.message,
874
+ ...(step.error.exitCode !== null ? {exit_code: step.error.exitCode} : {}),
875
+ ...(step.error.signal ? {signal: step.error.signal} : {}),
876
+ ...(step.error.reason ? {reason: step.error.reason} : {}),
877
+ ...(step.error.agentConfigIssue
878
+ ? {agent_config_issue: step.error.agentConfigIssue}
879
+ : {}),
880
+ ...(step.error.category ? {category: step.error.category} : {}),
881
+ }
882
+ : null,
370
883
  position: step.position,
371
884
  current_attempt: step.currentAttempt,
372
885
  exit_code: null,
@@ -393,7 +906,7 @@ function storyRun({
393
906
  })),
394
907
  })),
395
908
  })),
396
- outputs: job.jobExecutions[0]?.outputs ?? null,
909
+ outputs: job.outputs ?? job.jobExecutions[0]?.outputs ?? null,
397
910
  resolution_reason: job.resolutionReason,
398
911
  })),
399
912
  });
@@ -518,14 +1031,3 @@ function executionDto(execution: JobExecution) {
518
1031
  ),
519
1032
  });
520
1033
  }
521
-
522
- function StateExample({label, children}: {label: string; children: ReactNode}) {
523
- return (
524
- <section className="min-w-0 rounded-8 border border-border-neutral-base bg-background-neutral-base p-12">
525
- <Text as="h3" size="xs" bold className="mb-8 text-foreground-neutral-muted">
526
- {label}
527
- </Text>
528
- {children}
529
- </section>
530
- );
531
- }