@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
@@ -2,12 +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';
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';
10
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';
11
24
  import {
12
25
  workflowJob,
13
26
  workflowJobExecutionDto,
@@ -21,6 +34,7 @@ import {JobDetailView} from './job-detail-view.js';
21
34
  const WORKSPACE_SLUG = 'acme';
22
35
  const PROJECT_SLUG = 'platform';
23
36
  const RUN_ID = '11111111-1111-4111-8111-111111111111';
37
+ const INSPECTOR_TRIGGER_NAME = /Inspect Run tests/;
24
38
  const LOG_RECORDS: StepLogSnapshot['records'] = [
25
39
  {
26
40
  v: 1,
@@ -42,6 +56,8 @@ interface JobDetailStoryArgs {
42
56
  run: WorkflowRunDetail;
43
57
  jobId: string;
44
58
  selectedExecutionId?: string | undefined;
59
+ search?: Parameters<typeof JobDetailView>[0]['search'];
60
+ stepDetails?: readonly StepAttemptDetail[];
45
61
  }
46
62
 
47
63
  type JobDetailQuery = ReturnType<typeof useWorkflowRunAttemptQuery>;
@@ -72,52 +88,132 @@ export const Executions: Story = {
72
88
  },
73
89
  };
74
90
 
75
- export const DataStates: Story = {
91
+ export const Loading: Story = {
76
92
  render: () => {
77
- const partialRun = partialJobRun();
78
- const emptyRun = emptyJobRun();
79
- const loadedRun = playgroundRun();
93
+ const run = playgroundRun();
80
94
  return (
81
- <div className="grid max-w-[1120px] gap-16 p-16 md:grid-cols-2">
82
- <StateExample label="Loading">
83
- <JobDetailStoryState
84
- query={makeQuery(undefined, {isPending: true})}
85
- jobId={loadedRun.jobs[0]?.id ?? ''}
86
- />
87
- </StateExample>
88
- <StateExample label="Empty">
89
- <JobDetailStoryState query={makeQuery(emptyRun)} jobId={emptyRun.jobs[0]?.id ?? ''} />
90
- </StateExample>
91
- <StateExample label="Error">
92
- <JobDetailStoryState
93
- query={makeQuery(undefined, {isError: true, error: new Error('Storybook error')})}
94
- jobId="missing-job"
95
- />
96
- </StateExample>
97
- <StateExample label="Partial">
98
- <JobDetailStoryState query={makeQuery(partialRun)} jobId={partialRun.jobs[0]?.id ?? ''} />
99
- </StateExample>
100
- <StateExample label="Job not found">
101
- <JobDetailStoryState query={makeQuery(loadedRun)} jobId="missing-job" />
102
- </StateExample>
103
- </div>
95
+ <JobDetailStoryViewport>
96
+ <JobDetailStoryState
97
+ query={makeQuery(undefined, {isPending: true})}
98
+ jobId={run.jobs[0]?.id ?? ''}
99
+ />
100
+ </JobDetailStoryViewport>
104
101
  );
105
102
  },
106
103
  };
107
104
 
108
- 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) {
109
198
  const job = run.jobs.find((candidate) => candidate.id === jobId) ?? run.jobs[0];
110
199
  const selectedExecution = job?.jobExecutions.find(
111
200
  (execution) => execution.id === selectedExecutionId,
112
201
  );
113
202
  return (
114
- <div className="flex min-h-screen min-w-0 bg-background-neutral-base">
203
+ <JobDetailStoryViewport>
115
204
  <JobDetailStoryState
116
205
  query={makeQuery(run)}
117
206
  jobId={jobId}
118
- search={selectedExecution ? {jobExecutionId: selectedExecution.id} : {}}
207
+ search={search ?? (selectedExecution ? {jobExecutionId: selectedExecution.id} : {})}
208
+ stepDetails={stepDetails}
119
209
  />
120
- </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>
121
217
  );
122
218
  }
123
219
 
@@ -125,14 +221,16 @@ function JobDetailStoryState({
125
221
  query,
126
222
  jobId,
127
223
  search = {},
224
+ stepDetails = [],
128
225
  }: {
129
226
  query: JobDetailQuery;
130
227
  jobId: string;
131
228
  search?: Parameters<typeof JobDetailView>[0]['search'];
229
+ stepDetails?: readonly StepAttemptDetail[];
132
230
  }) {
133
231
  const run = query.data;
134
232
  return (
135
- <StoryQueryProvider run={run}>
233
+ <StoryQueryProvider run={run} stepDetails={stepDetails}>
136
234
  <JobDetailView
137
235
  workspaceSlug={WORKSPACE_SLUG}
138
236
  projectSlug={PROJECT_SLUG}
@@ -160,9 +258,11 @@ function makeQuery(data: WorkflowRunDetail | undefined, overrides: Partial<JobDe
160
258
 
161
259
  function StoryQueryProvider({
162
260
  run,
261
+ stepDetails = [],
163
262
  children,
164
263
  }: {
165
264
  run: WorkflowRunDetail | undefined;
265
+ stepDetails?: readonly StepAttemptDetail[];
166
266
  children: ReactNode;
167
267
  }) {
168
268
  const [queryClient] = useState(() => {
@@ -182,6 +282,34 @@ function StoryQueryProvider({
182
282
  }
183
283
  }
184
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
+ );
185
313
  }
186
314
  return client;
187
315
  });
@@ -244,6 +372,135 @@ function playgroundRun(): WorkflowRunDetail {
244
372
  });
245
373
  }
246
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
+
247
504
  function emptyJobRun(): WorkflowRunDetail {
248
505
  return storyRun({
249
506
  status: 'succeeded',
@@ -279,6 +536,250 @@ function partialJobRun(): WorkflowRunDetail {
279
536
  });
280
537
  }
281
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
+
282
783
  function executionStory() {
283
784
  const jobId = '77777777-7777-4777-8777-777777777777';
284
785
  const firstExecutionId = '88888888-8888-4888-8888-888888888888';
@@ -330,6 +831,9 @@ function storyRun({
330
831
  status: job.status,
331
832
  status_reason: job.statusReason,
332
833
  carried_over: job.carriedOver,
834
+ success: job.success,
835
+ runner: job.runner,
836
+ evaluation_trace: job.evaluationTrace,
333
837
  listening: job.listening,
334
838
  listener_status: job.listenerStatus,
335
839
  dependencies: job.dependencies,
@@ -344,8 +848,10 @@ function storyRun({
344
848
  name: execution.name,
345
849
  status: execution.status,
346
850
  status_reason: execution.statusReason,
851
+ runner: execution.runner,
347
852
  trigger_events: [],
348
- outputs: null,
853
+ outputs: execution.outputs,
854
+ evaluation_trace: execution.evaluationTrace,
349
855
  queued_at: execution.queuedAt,
350
856
  started_at: execution.startedAt,
351
857
  finished_at: execution.finishedAt,
@@ -361,7 +867,19 @@ function storyRun({
361
867
  status: step.status,
362
868
  type: step.type,
363
869
  config: step.config,
364
- 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,
365
883
  position: step.position,
366
884
  current_attempt: step.currentAttempt,
367
885
  exit_code: null,
@@ -388,7 +906,7 @@ function storyRun({
388
906
  })),
389
907
  })),
390
908
  })),
391
- outputs: job.jobExecutions[0]?.outputs ?? null,
909
+ outputs: job.outputs ?? job.jobExecutions[0]?.outputs ?? null,
392
910
  resolution_reason: job.resolutionReason,
393
911
  })),
394
912
  });
@@ -513,14 +1031,3 @@ function executionDto(execution: JobExecution) {
513
1031
  ),
514
1032
  });
515
1033
  }
516
-
517
- function StateExample({label, children}: {label: string; children: ReactNode}) {
518
- return (
519
- <section className="min-w-0 rounded-8 border border-border-neutral-base bg-background-neutral-base p-12">
520
- <Text as="h3" size="xs" bold className="mb-8 text-foreground-neutral-muted">
521
- {label}
522
- </Text>
523
- {children}
524
- </section>
525
- );
526
- }