@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
@@ -10,11 +10,18 @@ import {
10
10
  SelectValue,
11
11
  } from '@shipfox/react-ui/select';
12
12
  import {toast} from '@shipfox/react-ui/toast';
13
+ import {Text} from '@shipfox/react-ui/typography';
13
14
  import {useNavigate} from '@tanstack/react-router';
14
- import {type ReactNode, useEffect} from 'react';
15
- import type {Job, WorkflowRunRerunMode} from '#core/workflow-run.js';
16
- import type {RunAnnotationSummary} from '#core/workflow-run-tabs.js';
15
+ import {type ReactNode, useEffect, useMemo} from 'react';
16
+ import {buildRunAnnotationList, type RunAnnotationSummary} from '#core/run-annotation.js';
17
+ import {
18
+ type EvaluationTraceEntry,
19
+ isWorkflowRunTerminal,
20
+ type Job,
21
+ type WorkflowRunRerunMode,
22
+ } from '#core/workflow-run.js';
17
23
  import {withoutWorkflowRunSelectionSearch} from '#core/workflow-run-url-state.js';
24
+ import {useRunAnnotationsQuery} from '#hooks/api/run-annotations.js';
18
25
  import {
19
26
  useCancelWorkflowRunMutation,
20
27
  useRerunWorkflowRunMutation,
@@ -30,7 +37,11 @@ import {
30
37
  import {JobGraph} from '../job-graph/index.js';
31
38
  import type {JobGraphSelectionSource} from '../job-graph/types.js';
32
39
  import {WorkflowRunSummary} from '../workflow-run-summary/index.js';
33
- import {RunAnnotationSummaryLine, RunAnnotationsEmpty} from '../workflow-run-tabs/index.js';
40
+ import {
41
+ type DerivedRunAnnotation,
42
+ RunAnnotationList,
43
+ RunAnnotationSummaryLine,
44
+ } from '../workflow-run-tabs/index.js';
34
45
  import {WorkflowSourceContent} from '../workflow-source-panel/index.js';
35
46
  import {RunWorkspaceNav} from './run-workspace-nav.js';
36
47
  import {resolveWorkflowRunSelection} from './workflow-run-selection.js';
@@ -51,7 +62,6 @@ export interface WorkflowRunViewProps {
51
62
  runAttempt?: number | undefined;
52
63
  selection?: WorkflowRunsSearch | undefined;
53
64
  tab?: WorkflowRunTab | undefined;
54
- annotationSummary?: RunAnnotationSummary | undefined;
55
65
  activeJobId?: string | undefined;
56
66
  jobSearch?: WorkflowJobSearch | undefined;
57
67
  jobContent?: ReactNode | undefined;
@@ -69,7 +79,6 @@ export function WorkflowRunView({
69
79
  runAttempt,
70
80
  selection,
71
81
  tab,
72
- annotationSummary,
73
82
  activeJobId,
74
83
  jobSearch,
75
84
  jobContent,
@@ -79,6 +88,22 @@ export function WorkflowRunView({
79
88
  runAttempt: selection?.runAttempt ?? runAttempt,
80
89
  });
81
90
  const rerunMutation = useRerunWorkflowRunMutation(projectId);
91
+ // Annotations are read on their own cadence rather than hydrated into the run, so the two
92
+ // modules stay decoupled. The key is shared, so the job page's count chip costs no extra fetch.
93
+ //
94
+ // Polling is scoped to the surface that renders bodies. The read has no counts-only mode, so
95
+ // every poll transfers whole Markdown bodies; doing that every four seconds behind Summary,
96
+ // Source, and the job log spends megabytes to keep one rail number warm. Off that surface the
97
+ // counts still refresh on navigation and on window focus.
98
+ const annotationsQuery = useRunAnnotationsQuery({
99
+ workflowRunId,
100
+ runAttempt: runQuery.data?.runAttempt.attempt,
101
+ live:
102
+ runWorkspaceSection(tab) === 'annotations' &&
103
+ !activeJobId &&
104
+ Boolean(runQuery.data) &&
105
+ !isWorkflowRunTerminal(runQuery.data?.runAttempt.status ?? 'pending'),
106
+ });
82
107
 
83
108
  return (
84
109
  <RelativeTimeProvider>
@@ -87,11 +112,11 @@ export function WorkflowRunView({
87
112
  workspaceSlug={workspaceSlug}
88
113
  projectSlug={projectSlug}
89
114
  query={runQuery}
115
+ annotations={annotationsQuery}
90
116
  rerunMutation={rerunMutation}
91
117
  runAttempt={runAttempt}
92
118
  selection={selection}
93
119
  tab={tab}
94
- annotationSummary={annotationSummary}
95
120
  activeJobId={activeJobId}
96
121
  jobSearch={jobSearch}
97
122
  jobContent={jobContent}
@@ -105,11 +130,11 @@ function RunViewContent({
105
130
  workspaceSlug,
106
131
  projectSlug,
107
132
  query,
133
+ annotations,
108
134
  rerunMutation,
109
135
  runAttempt,
110
136
  selection,
111
137
  tab,
112
- annotationSummary,
113
138
  activeJobId,
114
139
  jobSearch,
115
140
  jobContent,
@@ -117,17 +142,18 @@ function RunViewContent({
117
142
  workspaceSlug: string | undefined;
118
143
  projectSlug: string | undefined;
119
144
  query: ReturnType<typeof useWorkflowRunAttemptQuery>;
145
+ annotations: ReturnType<typeof useRunAnnotationsQuery>;
120
146
  rerunMutation: ReturnType<typeof useRerunWorkflowRunMutation>;
121
147
  runAttempt: number | undefined;
122
148
  selection: WorkflowRunsSearch | undefined;
123
149
  tab: WorkflowRunTab | undefined;
124
- annotationSummary: RunAnnotationSummary | undefined;
125
150
  activeJobId: string | undefined;
126
151
  jobSearch: WorkflowJobSearch | undefined;
127
152
  jobContent: ReactNode | undefined;
128
153
  }) {
129
154
  const navigate = useNavigate();
130
155
  const runData = query.data;
156
+ const annotationSummary = annotations.summary;
131
157
  const activeSection = runWorkspaceSection(tab);
132
158
  const cancelMutation = useCancelWorkflowRunMutation(runData);
133
159
  const sourceSnapshot = runData?.sourceSnapshot ?? null;
@@ -228,6 +254,24 @@ function RunViewContent({
228
254
  });
229
255
  }
230
256
 
257
+ function clearAnnotationFilters() {
258
+ if (!runData || !workspaceSlug || !projectSlug) return;
259
+ const nextSearch: WorkflowRunsSearch = {...selection, tab: 'annotations'};
260
+ delete nextSearch.jobId;
261
+ delete nextSearch.jobExecutionId;
262
+ delete nextSearch.stepId;
263
+ delete nextSearch.stepAttemptId;
264
+ delete nextSearch.severity;
265
+ delete nextSearch.annotation;
266
+
267
+ void navigate({
268
+ to: '/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId',
269
+ params: {workspaceSlug, projectSlug, workflowRunId: runData.id},
270
+ search: workflowRunSearchParams(nextSearch, nextSearch) as never,
271
+ replace: true,
272
+ });
273
+ }
274
+
231
275
  function cancelRun() {
232
276
  cancelMutation.mutate(undefined, {
233
277
  onError: (error) => toast.error(cancelErrorMessage(error)),
@@ -291,6 +335,7 @@ function RunViewContent({
291
335
  <RunSectionContent
292
336
  section={activeSection}
293
337
  run={runData}
338
+ annotations={annotations}
294
339
  annotationSummary={annotationSummary}
295
340
  workspaceSlug={workspaceSlug}
296
341
  projectSlug={projectSlug}
@@ -298,11 +343,12 @@ function RunViewContent({
298
343
  selectedJobId={selection?.jobId}
299
344
  onSelectGraphJob={selectGraphJob}
300
345
  onSelectAnnotationJob={selectAnnotationJob}
346
+ onClearAnnotationFilters={clearAnnotationFilters}
301
347
  sourceSnapshot={sourceSnapshot}
302
348
  highlightedLineRange={highlightedLineRange}
303
349
  />
304
350
  ) : (
305
- <div className="min-h-0 flex-1 overflow-auto p-24">{loadingOrError}</div>
351
+ <div className="min-h-0 flex-1 overflow-auto p-panel">{loadingOrError}</div>
306
352
  )}
307
353
  </div>
308
354
  </div>
@@ -313,6 +359,7 @@ function RunViewContent({
313
359
  function RunSectionContent({
314
360
  section,
315
361
  run,
362
+ annotations,
316
363
  annotationSummary,
317
364
  workspaceSlug,
318
365
  projectSlug,
@@ -320,11 +367,13 @@ function RunSectionContent({
320
367
  selectedJobId,
321
368
  onSelectGraphJob,
322
369
  onSelectAnnotationJob,
370
+ onClearAnnotationFilters,
323
371
  sourceSnapshot,
324
372
  highlightedLineRange,
325
373
  }: {
326
374
  section: RunWorkspaceSection;
327
375
  run: NonNullable<ReturnType<typeof useWorkflowRunAttemptQuery>['data']>;
376
+ annotations: ReturnType<typeof useRunAnnotationsQuery>;
328
377
  annotationSummary: RunAnnotationSummary | undefined;
329
378
  workspaceSlug: string | undefined;
330
379
  projectSlug: string | undefined;
@@ -332,6 +381,7 @@ function RunSectionContent({
332
381
  selectedJobId: string | undefined;
333
382
  onSelectGraphJob: (jobId: string | undefined, source?: JobGraphSelectionSource) => void;
334
383
  onSelectAnnotationJob: (jobId: string | undefined) => void;
384
+ onClearAnnotationFilters: () => void;
335
385
  sourceSnapshot: NonNullable<
336
386
  ReturnType<typeof useWorkflowRunAttemptQuery>['data']
337
387
  >['sourceSnapshot'];
@@ -341,8 +391,14 @@ function RunSectionContent({
341
391
  }) {
342
392
  if (section === 'summary') {
343
393
  return (
344
- <section aria-label="All jobs summary" className="min-h-0 flex-1 overflow-auto pb-24 pt-16">
345
- <div className="mx-auto flex w-full max-w-[1120px] flex-col gap-16 px-24">
394
+ <section
395
+ aria-label="All jobs summary"
396
+ className="min-h-0 flex-1 overflow-auto pb-panel pt-[16px]"
397
+ >
398
+ <div className="mx-auto flex w-full max-w-[1120px] flex-col gap-group px-frame">
399
+ <Text as="h2" className="sr-only">
400
+ All jobs summary
401
+ </Text>
346
402
  <JobGraph
347
403
  run={run}
348
404
  selectedJobId={selectedJobId}
@@ -355,33 +411,30 @@ function RunSectionContent({
355
411
  }
356
412
 
357
413
  if (section === 'annotations') {
358
- const selectedJob = run.jobs.find((job) => job.id === selectedJobId);
359
414
  return (
360
- <section aria-label="Run annotations" className="min-h-0 flex-1 overflow-auto pb-24 pt-16">
361
- <div className="mx-auto flex w-full max-w-[1120px] flex-col gap-16 px-24">
362
- <div className="flex flex-wrap items-center justify-between gap-8">
363
- <RunAnnotationSummaryLine
364
- summary={annotationSummary}
365
- workspaceSlug={workspaceSlug}
366
- projectSlug={projectSlug}
367
- workflowRunId={run.id}
368
- search={selection}
369
- />
370
- <AnnotationJobFilter
371
- jobs={run.jobs}
372
- selectedJobId={selectedJob?.id}
373
- onSelect={onSelectAnnotationJob}
374
- />
375
- </div>
376
- <RunAnnotationsEmpty jobName={selectedJob?.displayName} />
377
- </div>
378
- </section>
415
+ <RunAnnotationsSection
416
+ run={run}
417
+ annotations={annotations}
418
+ annotationSummary={annotationSummary}
419
+ workspaceSlug={workspaceSlug}
420
+ projectSlug={projectSlug}
421
+ selection={selection}
422
+ selectedJobId={selectedJobId}
423
+ onSelectAnnotationJob={onSelectAnnotationJob}
424
+ onClearAnnotationFilters={onClearAnnotationFilters}
425
+ />
379
426
  );
380
427
  }
381
428
 
382
429
  return (
383
- <section aria-label="Workflow source" className="min-h-0 flex-1 overflow-auto pb-24 pt-16">
384
- <div className="mx-auto flex min-h-full w-full max-w-[1120px] px-24">
430
+ <section
431
+ aria-label="Workflow source"
432
+ className="min-h-0 flex-1 overflow-auto pb-panel pt-[16px]"
433
+ >
434
+ <div className="mx-auto flex min-h-full w-full max-w-[1120px] flex-col px-frame">
435
+ <Text as="h2" className="sr-only">
436
+ Workflow source
437
+ </Text>
385
438
  {sourceSnapshot ? (
386
439
  <WorkflowSourceContent
387
440
  source={sourceSnapshot}
@@ -405,6 +458,171 @@ function RunSectionContent({
405
458
  );
406
459
  }
407
460
 
461
+ function RunAnnotationsSection({
462
+ run,
463
+ annotations,
464
+ annotationSummary,
465
+ workspaceSlug,
466
+ projectSlug,
467
+ selection,
468
+ selectedJobId,
469
+ onSelectAnnotationJob,
470
+ onClearAnnotationFilters,
471
+ }: {
472
+ run: NonNullable<ReturnType<typeof useWorkflowRunAttemptQuery>['data']>;
473
+ annotations: ReturnType<typeof useRunAnnotationsQuery>;
474
+ annotationSummary: RunAnnotationSummary | undefined;
475
+ workspaceSlug: string | undefined;
476
+ projectSlug: string | undefined;
477
+ selection: WorkflowRunsSearch | undefined;
478
+ selectedJobId: string | undefined;
479
+ onSelectAnnotationJob: (jobId: string | undefined) => void;
480
+ onClearAnnotationFilters: () => void;
481
+ }) {
482
+ const selectedJob = run.jobs.find((job) => job.id === selectedJobId);
483
+ const severity = selection?.severity;
484
+ const records = annotations.annotations;
485
+
486
+ const entries = useMemo(
487
+ () =>
488
+ records
489
+ ? buildRunAnnotationList({
490
+ annotations: records,
491
+ jobs: run.jobs,
492
+ severity,
493
+ jobId: selectedJob?.id,
494
+ })
495
+ : undefined,
496
+ [records, run.jobs, selectedJob?.id, severity],
497
+ );
498
+ const derivedAnnotations = useMemo<readonly DerivedRunAnnotation[] | undefined>(() => {
499
+ if (!records) return undefined;
500
+ const jobsWithAnnotations = new Set(records.map((annotation) => annotation.jobId));
501
+
502
+ return run.jobs
503
+ .filter((job) => {
504
+ const style = job.status === 'failed' ? 'error' : 'warning';
505
+ return (
506
+ (!selectedJob || selectedJob.id === job.id) &&
507
+ !jobsWithAnnotations.has(job.id) &&
508
+ (job.status === 'failed' || job.status === 'skipped') &&
509
+ job.jobExecutions.length === 0 &&
510
+ matchesDerivedAnnotationFilters(style, selection)
511
+ );
512
+ })
513
+ .map((job) => ({
514
+ id: `derived-${job.id}`,
515
+ style: job.status === 'failed' ? 'error' : 'warning',
516
+ body: derivedJobAnnotation(job),
517
+ }));
518
+ }, [records, run.jobs, selectedJob, selection]);
519
+ const hasSynthesizedJobAnnotations = run.jobs.some(
520
+ (job) =>
521
+ (job.status === 'failed' || job.status === 'skipped') && job.jobExecutions.length === 0,
522
+ );
523
+
524
+ return (
525
+ <section
526
+ aria-label="Run annotations"
527
+ className="min-h-0 flex-1 overflow-auto pb-panel pt-[16px]"
528
+ >
529
+ <div className="mx-auto flex w-full max-w-[1120px] flex-col gap-group px-frame">
530
+ <Text as="h2" className="sr-only">
531
+ Annotations
532
+ </Text>
533
+ <div className="flex flex-wrap items-center justify-between gap-inline">
534
+ <RunAnnotationSummaryLine
535
+ summary={annotationSummary}
536
+ workspaceSlug={workspaceSlug}
537
+ projectSlug={projectSlug}
538
+ workflowRunId={run.id}
539
+ search={selection}
540
+ />
541
+ <AnnotationJobFilter
542
+ jobs={run.jobs}
543
+ selectedJobId={selectedJob?.id}
544
+ onSelect={onSelectAnnotationJob}
545
+ />
546
+ </div>
547
+ <RunAnnotationList
548
+ // Remounting on a filter or run-attempt change resets the render window, so the next
549
+ // list never inherits a "show more" position from different data.
550
+ key={`${run.id}:${run.runAttempt.attempt}:${severity ?? 'all'}:${selectedJob?.id ?? 'all'}`}
551
+ query={annotations.query}
552
+ entries={entries}
553
+ derivedAnnotations={derivedAnnotations}
554
+ workspaceSlug={workspaceSlug}
555
+ projectSlug={projectSlug}
556
+ workflowRunId={run.id}
557
+ runAttempt={run.runAttempt.attempt}
558
+ // A run with no annotations at all offers no filter to clear, whatever the URL says.
559
+ filtered={Boolean(
560
+ (severity || selectedJob || selection?.annotation) &&
561
+ ((annotationSummary?.total ?? 0) > 0 || hasSynthesizedJobAnnotations),
562
+ )}
563
+ filteredJobName={selectedJob?.displayName}
564
+ filteredSeverity={severity}
565
+ onClearFilters={onClearAnnotationFilters}
566
+ selectedAnnotationId={selection?.annotation}
567
+ />
568
+ </div>
569
+ </section>
570
+ );
571
+ }
572
+
573
+ function matchesDerivedAnnotationFilters(
574
+ style: 'warning' | 'error',
575
+ selection: WorkflowRunsSearch | undefined,
576
+ ): boolean {
577
+ if (selection?.severity && style !== selection.severity) return false;
578
+ // A selected annotation is a deep-link to a real annotation record. Synthetic diagnostics
579
+ // have no id or context, so they must not remain visible behind that selection.
580
+ return !selection?.annotation;
581
+ }
582
+
583
+ function derivedJobAnnotation(job: Job): string {
584
+ const reason = job.statusReason ? `Reason: \`${job.statusReason}\`` : null;
585
+ const traceSummary = formatConditionEvaluation(job.evaluationTrace);
586
+ const details = [reason, traceSummary].filter(Boolean).join('\n');
587
+
588
+ if (job.status === 'skipped') {
589
+ return [
590
+ `**${job.displayName}** was skipped before an execution was created.`,
591
+ '',
592
+ 'Review its dependencies or condition before re-running.',
593
+ details,
594
+ ]
595
+ .filter(Boolean)
596
+ .join('\n');
597
+ }
598
+ return [
599
+ `**${job.displayName}** failed before an execution was created.`,
600
+ '',
601
+ 'Check runner availability and workflow configuration before re-running.',
602
+ details,
603
+ ]
604
+ .filter(Boolean)
605
+ .join('\n');
606
+ }
607
+
608
+ function formatConditionEvaluation(
609
+ trace: readonly EvaluationTraceEntry[] | null | undefined,
610
+ ): string | null {
611
+ if (!trace?.length) return null;
612
+
613
+ return [
614
+ 'Condition evaluation:',
615
+ ...trace.map((entry) => {
616
+ if ('dropped' in entry) {
617
+ return `- ${entry.dropped} additional evaluation${entry.dropped === 1 ? '' : 's'} not recorded`;
618
+ }
619
+ const value =
620
+ entry.value === undefined || entry.value === '' ? '(empty)' : `\`${entry.value}\``;
621
+ return `- \`${entry.field}\` evaluated \`${entry.expression}\` to ${value}${entry.degraded ? ' (degraded)' : ''}`;
622
+ }),
623
+ ].join('\n');
624
+ }
625
+
408
626
  const ALL_ANNOTATION_JOBS = 'all-jobs';
409
627
 
410
628
  function AnnotationJobFilter({
@@ -448,11 +666,11 @@ function RunWorkspaceNavSkeleton() {
448
666
  return (
449
667
  <aside
450
668
  aria-label="Loading run navigation"
451
- className="hidden w-240 shrink-0 border-r border-border-neutral-base bg-background-neutral-background p-12 min-[768px]:block"
669
+ className="hidden w-240 shrink-0 border-r border-border-neutral-base bg-background-neutral-background p-panel-compact min-[768px]:block"
452
670
  >
453
- <div className="flex flex-col gap-16">
671
+ <div className="flex flex-col gap-group">
454
672
  <div className="h-32 rounded-4 bg-background-components-subtle" />
455
- <div className="flex flex-col gap-6">
673
+ <div className="flex flex-col gap-inline">
456
674
  <div className="h-16 w-64 rounded-4 bg-background-components-subtle" />
457
675
  <div className="h-32 rounded-4 bg-background-components-subtle" />
458
676
  <div className="h-32 rounded-4 bg-background-components-subtle" />
@@ -1,5 +1,6 @@
1
1
  import {type Duration, intervalToDuration} from 'date-fns';
2
2
  import type {Step} from './step.js';
3
+ import type {EvaluationTraceEntry} from './step-attempt.js';
3
4
 
4
5
  export type JobExecutionStatus = 'pending' | 'running' | 'succeeded' | 'failed' | 'cancelled';
5
6
  export type JobExecutionDisplayStatus = JobExecutionStatus;
@@ -28,11 +29,14 @@ interface JobExecutionFields {
28
29
  name: string;
29
30
  status: JobExecutionStatus;
30
31
  statusReason: string | null;
32
+ runner: string[] | null;
33
+ outputs: Record<string, unknown> | null;
31
34
  triggerEvents: WorkflowExecutionEvent[];
32
35
  queuedAt: string | null;
33
36
  startedAt: string | null;
34
37
  finishedAt: string | null;
35
38
  timedOutAt: string | null;
39
+ evaluationTrace: EvaluationTraceEntry[] | null;
36
40
  createdAt: string;
37
41
  updatedAt: string;
38
42
  steps: Step[];
@@ -45,11 +49,14 @@ export class JobExecution {
45
49
  name!: string;
46
50
  status!: JobExecutionStatus;
47
51
  statusReason!: string | null;
52
+ runner!: string[] | null;
53
+ outputs!: Record<string, unknown> | null;
48
54
  triggerEvents!: WorkflowExecutionEvent[];
49
55
  queuedAt!: string | null;
50
56
  startedAt!: string | null;
51
57
  finishedAt!: string | null;
52
58
  timedOutAt!: string | null;
59
+ evaluationTrace!: EvaluationTraceEntry[] | null;
53
60
  createdAt!: string;
54
61
  updatedAt!: string;
55
62
  steps!: Step[];
@@ -3,6 +3,7 @@ import {
3
3
  type JobExecution,
4
4
  type JobExecutionDisplayDuration,
5
5
  } from './job-execution.js';
6
+ import type {EvaluationTraceEntry} from './step-attempt.js';
6
7
 
7
8
  export type JobStatus = 'pending' | 'running' | 'succeeded' | 'failed' | 'cancelled' | 'skipped';
8
9
  export type JobDisplayStatus = JobStatus | 'listening';
@@ -75,6 +76,10 @@ interface JobFields {
75
76
  status: JobStatus;
76
77
  statusReason: JobStatusReason | null;
77
78
  carriedOver: boolean;
79
+ outputs: Record<string, unknown> | null;
80
+ success: string | null;
81
+ runner: string[] | null;
82
+ evaluationTrace: EvaluationTraceEntry[] | null;
78
83
  listening: JobListening | null;
79
84
  listenerStatus: ListenerStatus;
80
85
  resolutionReason: ResolutionReason | null;
@@ -94,6 +99,10 @@ export class Job {
94
99
  status!: JobStatus;
95
100
  statusReason!: JobStatusReason | null;
96
101
  carriedOver!: boolean;
102
+ outputs!: Record<string, unknown> | null;
103
+ success!: string | null;
104
+ runner!: string[] | null;
105
+ evaluationTrace!: EvaluationTraceEntry[] | null;
97
106
  listening!: JobListening | null;
98
107
  listenerStatus!: ListenerStatus;
99
108
  resolutionReason!: ResolutionReason | null;
@@ -13,6 +13,27 @@ export type StepAttemptDisplayDuration =
13
13
  | {state: 'fixed'; elapsed: Duration}
14
14
  | {state: 'live'; fromIso: string};
15
15
 
16
+ export interface EvaluationTraceValueEntry {
17
+ expression: string;
18
+ roots: string[];
19
+ fillTarget: string;
20
+ evaluatedAt: string;
21
+ field: string;
22
+ value?: string;
23
+ truncated?: boolean;
24
+ exprTruncated?: boolean;
25
+ reference?: boolean;
26
+ degraded?: boolean;
27
+ envKey?: string;
28
+ }
29
+
30
+ export interface EvaluationTraceLimitEntry {
31
+ truncated: true;
32
+ dropped: number;
33
+ }
34
+
35
+ export type EvaluationTraceEntry = EvaluationTraceValueEntry | EvaluationTraceLimitEntry;
36
+
16
37
  interface StepAttemptFields {
17
38
  id: string;
18
39
  stepId: string;
@@ -22,6 +43,8 @@ interface StepAttemptFields {
22
43
  status: string;
23
44
  exitCode: number | null;
24
45
  output: Record<string, unknown> | null;
46
+ outputs: Record<string, unknown> | null;
47
+ response: string | null;
25
48
  error: Record<string, unknown> | null;
26
49
  gateResult: StepGateResult;
27
50
  restartFeedback: string | null;
@@ -38,6 +61,8 @@ export class StepAttempt {
38
61
  status!: string;
39
62
  exitCode!: number | null;
40
63
  output!: Record<string, unknown> | null;
64
+ outputs!: Record<string, unknown> | null;
65
+ response!: string | null;
41
66
  error!: Record<string, unknown> | null;
42
67
  gateResult!: StepGateResult;
43
68
  restartFeedback!: string | null;
@@ -53,6 +78,14 @@ export class StepAttempt {
53
78
  }
54
79
  }
55
80
 
81
+ export interface StepAttemptDetail {
82
+ stepId: string;
83
+ attempt: number;
84
+ authoredConfig: Record<string, unknown> | null;
85
+ config: Record<string, unknown> | null;
86
+ evaluationTrace: EvaluationTraceEntry[] | null;
87
+ }
88
+
56
89
  export function stepAttemptDisplayDurationFromTimestamps({
57
90
  startedAt,
58
91
  finishedAt,
@@ -1,4 +1,4 @@
1
- import type {StepAttempt} from './step-attempt.js';
1
+ import type {EvaluationTraceEntry, StepAttempt} from './step-attempt.js';
2
2
 
3
3
  export type StepErrorReason =
4
4
  | 'checkout_failed'
@@ -71,8 +71,10 @@ export interface Step {
71
71
  name: string;
72
72
  sourceLocation: StepSourceLocation | null;
73
73
  status: string;
74
+ statusReason: string | null;
74
75
  type: string;
75
76
  config: Record<string, unknown>;
77
+ evaluationTrace: EvaluationTraceEntry[] | null;
76
78
  agentConfig: AgentStepConfig | null;
77
79
  error: StepError | null;
78
80
  position: number;