@shipfox/client-workflows 12.0.2 → 14.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 (325) hide show
  1. package/.storybook/main.ts +16 -1
  2. package/.storybook/preview.tsx +3 -3
  3. package/.storybook/workspace-source.ts +31 -0
  4. package/.turbo/turbo-build.log +1 -1
  5. package/CHANGELOG.md +79 -0
  6. package/dist/components/job-graph/job-duration-format.d.ts +2 -2
  7. package/dist/components/job-graph/job-duration-format.d.ts.map +1 -1
  8. package/dist/components/job-graph/job-duration-format.js +4 -2
  9. package/dist/components/job-graph/job-duration-format.js.map +1 -1
  10. package/dist/components/job-graph/job-duration-label.js +1 -1
  11. package/dist/components/job-graph/job-duration-label.js.map +1 -1
  12. package/dist/components/job-graph/job-graph-content.d.ts +2 -1
  13. package/dist/components/job-graph/job-graph-content.d.ts.map +1 -1
  14. package/dist/components/job-graph/job-graph-content.js +3 -3
  15. package/dist/components/job-graph/job-graph-content.js.map +1 -1
  16. package/dist/components/job-graph/job-graph-view.d.ts +2 -1
  17. package/dist/components/job-graph/job-graph-view.d.ts.map +1 -1
  18. package/dist/components/job-graph/job-graph-view.js +2 -2
  19. package/dist/components/job-graph/job-graph-view.js.map +1 -1
  20. package/dist/components/job-graph/job-node.d.ts +1 -1
  21. package/dist/components/job-graph/job-node.d.ts.map +1 -1
  22. package/dist/components/job-graph/job-node.js +10 -9
  23. package/dist/components/job-graph/job-node.js.map +1 -1
  24. package/dist/components/job-graph/types.d.ts +2 -1
  25. package/dist/components/job-graph/types.d.ts.map +1 -1
  26. package/dist/components/job-graph/types.js.map +1 -1
  27. package/dist/components/step-list/step-list.d.ts +2 -2
  28. package/dist/components/step-list/step-list.d.ts.map +1 -1
  29. package/dist/components/step-list/step-list.js.map +1 -1
  30. package/dist/components/workflow-run-duration-label.js +1 -1
  31. package/dist/components/workflow-run-duration-label.js.map +1 -1
  32. package/dist/components/workflow-run-list/job-status-strip.d.ts +21 -0
  33. package/dist/components/workflow-run-list/job-status-strip.d.ts.map +1 -0
  34. package/dist/components/workflow-run-list/job-status-strip.js +185 -0
  35. package/dist/components/workflow-run-list/job-status-strip.js.map +1 -0
  36. package/dist/components/workflow-run-list/run-display.d.ts +27 -3
  37. package/dist/components/workflow-run-list/run-display.d.ts.map +1 -1
  38. package/dist/components/workflow-run-list/run-display.js +78 -7
  39. package/dist/components/workflow-run-list/run-display.js.map +1 -1
  40. package/dist/components/workflow-run-list/types.d.ts +23 -24
  41. package/dist/components/workflow-run-list/types.d.ts.map +1 -1
  42. package/dist/components/workflow-run-list/types.js.map +1 -1
  43. package/dist/components/workflow-run-list/workflow-run-filter-menu.d.ts +22 -0
  44. package/dist/components/workflow-run-list/workflow-run-filter-menu.d.ts.map +1 -0
  45. package/dist/components/workflow-run-list/workflow-run-filter-menu.js +68 -0
  46. package/dist/components/workflow-run-list/workflow-run-filter-menu.js.map +1 -0
  47. package/dist/components/workflow-run-list/workflow-run-filters.d.ts +18 -0
  48. package/dist/components/workflow-run-list/workflow-run-filters.d.ts.map +1 -0
  49. package/dist/components/workflow-run-list/workflow-run-filters.js +236 -0
  50. package/dist/components/workflow-run-list/workflow-run-filters.js.map +1 -0
  51. package/dist/components/workflow-run-list/workflow-run-list-content.d.ts +8 -4
  52. package/dist/components/workflow-run-list/workflow-run-list-content.d.ts.map +1 -1
  53. package/dist/components/workflow-run-list/workflow-run-list-content.js +40 -12
  54. package/dist/components/workflow-run-list/workflow-run-list-content.js.map +1 -1
  55. package/dist/components/workflow-run-list/workflow-run-list-states.d.ts +29 -2
  56. package/dist/components/workflow-run-list/workflow-run-list-states.d.ts.map +1 -1
  57. package/dist/components/workflow-run-list/workflow-run-list-states.js +103 -44
  58. package/dist/components/workflow-run-list/workflow-run-list-states.js.map +1 -1
  59. package/dist/components/workflow-run-list/workflow-run-list-view.d.ts +1 -1
  60. package/dist/components/workflow-run-list/workflow-run-list-view.d.ts.map +1 -1
  61. package/dist/components/workflow-run-list/workflow-run-list-view.js +60 -43
  62. package/dist/components/workflow-run-list/workflow-run-list-view.js.map +1 -1
  63. package/dist/components/workflow-run-list/workflow-run-list.d.ts +1 -1
  64. package/dist/components/workflow-run-list/workflow-run-list.d.ts.map +1 -1
  65. package/dist/components/workflow-run-list/workflow-run-list.js +20 -8
  66. package/dist/components/workflow-run-list/workflow-run-list.js.map +1 -1
  67. package/dist/components/workflow-run-list/workflow-run-row.d.ts +19 -9
  68. package/dist/components/workflow-run-list/workflow-run-row.d.ts.map +1 -1
  69. package/dist/components/workflow-run-list/workflow-run-row.js +179 -112
  70. package/dist/components/workflow-run-list/workflow-run-row.js.map +1 -1
  71. package/dist/components/workflow-run-number-label.d.ts +15 -0
  72. package/dist/components/workflow-run-number-label.d.ts.map +1 -0
  73. package/dist/components/workflow-run-number-label.js +41 -0
  74. package/dist/components/workflow-run-number-label.js.map +1 -0
  75. package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.d.ts +3 -3
  76. package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.d.ts.map +1 -1
  77. package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js +10 -9
  78. package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js.map +1 -1
  79. package/dist/components/workflow-run-summary/workflow-run-summary.d.ts +4 -9
  80. package/dist/components/workflow-run-summary/workflow-run-summary.d.ts.map +1 -1
  81. package/dist/components/workflow-run-summary/workflow-run-summary.js +107 -74
  82. package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -1
  83. package/dist/components/workflow-run-tabs/index.d.ts +6 -0
  84. package/dist/components/workflow-run-tabs/index.d.ts.map +1 -0
  85. package/dist/components/workflow-run-tabs/index.js +7 -0
  86. package/dist/components/workflow-run-tabs/index.js.map +1 -0
  87. package/dist/components/workflow-run-tabs/run-annotation-summary-line.d.ts +11 -0
  88. package/dist/components/workflow-run-tabs/run-annotation-summary-line.d.ts.map +1 -0
  89. package/dist/components/workflow-run-tabs/run-annotation-summary-line.js +71 -0
  90. package/dist/components/workflow-run-tabs/run-annotation-summary-line.js.map +1 -0
  91. package/dist/components/workflow-run-tabs/run-annotations-empty.d.ts +2 -0
  92. package/dist/components/workflow-run-tabs/run-annotations-empty.d.ts.map +1 -0
  93. package/dist/components/workflow-run-tabs/run-annotations-empty.js +11 -0
  94. package/dist/components/workflow-run-tabs/run-annotations-empty.js.map +1 -0
  95. package/dist/components/workflow-run-tabs/run-jobs-list.d.ts +18 -0
  96. package/dist/components/workflow-run-tabs/run-jobs-list.d.ts.map +1 -0
  97. package/dist/components/workflow-run-tabs/run-jobs-list.js +107 -0
  98. package/dist/components/workflow-run-tabs/run-jobs-list.js.map +1 -0
  99. package/dist/components/workflow-run-tabs/run-tab-count.d.ts +6 -0
  100. package/dist/components/workflow-run-tabs/run-tab-count.d.ts.map +1 -0
  101. package/dist/components/workflow-run-tabs/run-tab-count.js +27 -0
  102. package/dist/components/workflow-run-tabs/run-tab-count.js.map +1 -0
  103. package/dist/components/workflow-run-tabs/run-tab-strip.d.ts +8 -0
  104. package/dist/components/workflow-run-tabs/run-tab-strip.d.ts.map +1 -0
  105. package/dist/components/workflow-run-tabs/run-tab-strip.js +82 -0
  106. package/dist/components/workflow-run-tabs/run-tab-strip.js.map +1 -0
  107. package/dist/components/workflow-run-view/agent-config-failure-callout.d.ts +2 -2
  108. package/dist/components/workflow-run-view/agent-config-failure-callout.d.ts.map +1 -1
  109. package/dist/components/workflow-run-view/agent-config-failure-callout.js +4 -4
  110. package/dist/components/workflow-run-view/agent-config-failure-callout.js.map +1 -1
  111. package/dist/components/workflow-run-view/job-card.d.ts +2 -2
  112. package/dist/components/workflow-run-view/job-card.d.ts.map +1 -1
  113. package/dist/components/workflow-run-view/job-card.js +51 -32
  114. package/dist/components/workflow-run-view/job-card.js.map +1 -1
  115. package/dist/components/workflow-run-view/job-execution-switcher.d.ts +1 -1
  116. package/dist/components/workflow-run-view/job-execution-switcher.d.ts.map +1 -1
  117. package/dist/components/workflow-run-view/job-execution-switcher.js +20 -8
  118. package/dist/components/workflow-run-view/job-execution-switcher.js.map +1 -1
  119. package/dist/components/workflow-run-view/workflow-run-selection.d.ts.map +1 -1
  120. package/dist/components/workflow-run-view/workflow-run-selection.js +40 -1
  121. package/dist/components/workflow-run-view/workflow-run-selection.js.map +1 -1
  122. package/dist/components/workflow-run-view/workflow-run-states.d.ts +1 -0
  123. package/dist/components/workflow-run-view/workflow-run-states.d.ts.map +1 -1
  124. package/dist/components/workflow-run-view/workflow-run-states.js +10 -1
  125. package/dist/components/workflow-run-view/workflow-run-states.js.map +1 -1
  126. package/dist/components/workflow-run-view/workflow-run-view.d.ts +10 -5
  127. package/dist/components/workflow-run-view/workflow-run-view.d.ts.map +1 -1
  128. package/dist/components/workflow-run-view/workflow-run-view.js +136 -85
  129. package/dist/components/workflow-run-view/workflow-run-view.js.map +1 -1
  130. package/dist/components/workflow-source-panel/index.d.ts +1 -0
  131. package/dist/components/workflow-source-panel/index.d.ts.map +1 -1
  132. package/dist/components/workflow-source-panel/index.js +1 -0
  133. package/dist/components/workflow-source-panel/index.js.map +1 -1
  134. package/dist/components/workflow-source-panel/workflow-source-content.d.ts +12 -0
  135. package/dist/components/workflow-source-panel/workflow-source-content.d.ts.map +1 -0
  136. package/dist/components/workflow-source-panel/workflow-source-content.js +53 -0
  137. package/dist/components/workflow-source-panel/workflow-source-content.js.map +1 -0
  138. package/dist/components/workflow-source-panel/workflow-source-panel.d.ts +1 -1
  139. package/dist/components/workflow-source-panel/workflow-source-panel.d.ts.map +1 -1
  140. package/dist/components/workflow-source-panel/workflow-source-panel.js +7 -54
  141. package/dist/components/workflow-source-panel/workflow-source-panel.js.map +1 -1
  142. package/dist/components/workflow-status/status-visuals.d.ts +4 -4
  143. package/dist/components/workflow-status/status-visuals.d.ts.map +1 -1
  144. package/dist/components/workflow-status/status-visuals.js +7 -0
  145. package/dist/components/workflow-status/status-visuals.js.map +1 -1
  146. package/dist/components/workflow-status/workflow-status-icon.d.ts +3 -4
  147. package/dist/components/workflow-status/workflow-status-icon.d.ts.map +1 -1
  148. package/dist/components/workflow-status/workflow-status-icon.js +6 -4
  149. package/dist/components/workflow-status/workflow-status-icon.js.map +1 -1
  150. package/dist/core/entities/job-execution.d.ts +10 -0
  151. package/dist/core/entities/job-execution.d.ts.map +1 -1
  152. package/dist/core/entities/job-execution.js +11 -0
  153. package/dist/core/entities/job-execution.js.map +1 -1
  154. package/dist/core/entities/job.d.ts +4 -2
  155. package/dist/core/entities/job.d.ts.map +1 -1
  156. package/dist/core/entities/job.js +8 -1
  157. package/dist/core/entities/job.js.map +1 -1
  158. package/dist/core/entities/step.d.ts +1 -1
  159. package/dist/core/entities/step.d.ts.map +1 -1
  160. package/dist/core/entities/step.js +2 -0
  161. package/dist/core/entities/step.js.map +1 -1
  162. package/dist/core/entities/workflow-run.d.ts +52 -4
  163. package/dist/core/entities/workflow-run.d.ts.map +1 -1
  164. package/dist/core/entities/workflow-run.js +22 -3
  165. package/dist/core/entities/workflow-run.js.map +1 -1
  166. package/dist/core/workflow-run-tabs.d.ts +9 -0
  167. package/dist/core/workflow-run-tabs.d.ts.map +1 -0
  168. package/dist/core/workflow-run-tabs.js +3 -0
  169. package/dist/core/workflow-run-tabs.js.map +1 -0
  170. package/dist/core/workflow-run.d.ts +6 -6
  171. package/dist/core/workflow-run.d.ts.map +1 -1
  172. package/dist/core/workflow-run.js +3 -3
  173. package/dist/core/workflow-run.js.map +1 -1
  174. package/dist/feature.d.ts +7 -7
  175. package/dist/feature.js +5 -5
  176. package/dist/feature.js.map +1 -1
  177. package/dist/hooks/api/workflow-run-mapper.d.ts +4 -3
  178. package/dist/hooks/api/workflow-run-mapper.d.ts.map +1 -1
  179. package/dist/hooks/api/workflow-run-mapper.js +31 -3
  180. package/dist/hooks/api/workflow-run-mapper.js.map +1 -1
  181. package/dist/hooks/api/workflow-runs.d.ts +4 -4
  182. package/dist/hooks/api/workflow-runs.d.ts.map +1 -1
  183. package/dist/hooks/api/workflow-runs.js +12 -4
  184. package/dist/hooks/api/workflow-runs.js.map +1 -1
  185. package/dist/index.d.ts +4 -3
  186. package/dist/index.d.ts.map +1 -1
  187. package/dist/index.js +3 -2
  188. package/dist/index.js.map +1 -1
  189. package/dist/pages/project-workflows-page.js +46 -1
  190. package/dist/pages/project-workflows-page.js.map +1 -1
  191. package/dist/pages/workflow-run-detail-page.d.ts +11 -0
  192. package/dist/pages/workflow-run-detail-page.d.ts.map +1 -0
  193. package/dist/pages/workflow-run-detail-page.js +52 -0
  194. package/dist/pages/workflow-run-detail-page.js.map +1 -0
  195. package/dist/pages/workflow-run-list-page.d.ts +10 -0
  196. package/dist/pages/workflow-run-list-page.d.ts.map +1 -0
  197. package/dist/pages/workflow-run-list-page.js +43 -0
  198. package/dist/pages/workflow-run-list-page.js.map +1 -0
  199. package/dist/routes/inputs.d.ts +94 -5
  200. package/dist/routes/inputs.d.ts.map +1 -1
  201. package/dist/routes/inputs.js +199 -13
  202. package/dist/routes/inputs.js.map +1 -1
  203. package/dist/routes/project-route.d.ts +6 -0
  204. package/dist/routes/project-route.d.ts.map +1 -0
  205. package/dist/routes/project-route.js +25 -0
  206. package/dist/routes/project-route.js.map +1 -0
  207. package/dist/routes/run-detail.d.ts.map +1 -1
  208. package/dist/routes/run-detail.js +12 -7
  209. package/dist/routes/run-detail.js.map +1 -1
  210. package/dist/routes/runs.d.ts.map +1 -1
  211. package/dist/routes/runs.js +11 -6
  212. package/dist/routes/runs.js.map +1 -1
  213. package/dist/routes/workflows.d.ts.map +1 -1
  214. package/dist/routes/workflows.js +6 -5
  215. package/dist/routes/workflows.js.map +1 -1
  216. package/dist/tsconfig.test.tsbuildinfo +1 -1
  217. package/package.json +9 -9
  218. package/src/components/job-graph/job-duration-format.ts +11 -2
  219. package/src/components/job-graph/job-duration-label.tsx +5 -1
  220. package/src/components/job-graph/job-graph-content.tsx +7 -4
  221. package/src/components/job-graph/job-graph-view.test.tsx +19 -8
  222. package/src/components/job-graph/job-graph-view.tsx +6 -3
  223. package/src/components/job-graph/job-graph.stories.tsx +24 -20
  224. package/src/components/job-graph/job-node.stories.tsx +11 -6
  225. package/src/components/job-graph/job-node.test.tsx +82 -12
  226. package/src/components/job-graph/job-node.tsx +22 -15
  227. package/src/components/job-graph/types.ts +5 -1
  228. package/src/components/step-list/step-list.stories.tsx +2 -3
  229. package/src/components/step-list/step-list.tsx +3 -2
  230. package/src/components/workflow-run-duration-label.tsx +1 -1
  231. package/src/components/workflow-run-list/job-status-strip.tsx +189 -0
  232. package/src/components/workflow-run-list/run-display.test.ts +169 -30
  233. package/src/components/workflow-run-list/run-display.ts +116 -9
  234. package/src/components/workflow-run-list/types.ts +21 -17
  235. package/src/components/workflow-run-list/workflow-run-filter-menu.tsx +100 -0
  236. package/src/components/workflow-run-list/workflow-run-filters.tsx +243 -0
  237. package/src/components/workflow-run-list/workflow-run-list-content.tsx +44 -16
  238. package/src/components/workflow-run-list/workflow-run-list-states.tsx +128 -27
  239. package/src/components/workflow-run-list/workflow-run-list-view.test.tsx +441 -104
  240. package/src/components/workflow-run-list/workflow-run-list-view.tsx +73 -43
  241. package/src/components/workflow-run-list/workflow-run-list.stories.tsx +140 -20
  242. package/src/components/workflow-run-list/workflow-run-list.tsx +20 -10
  243. package/src/components/workflow-run-list/workflow-run-row.tsx +173 -106
  244. package/src/components/workflow-run-number-label.test.tsx +45 -0
  245. package/src/components/workflow-run-number-label.tsx +47 -0
  246. package/src/components/workflow-run-summary/workflow-run-attempt-switcher.test.tsx +7 -8
  247. package/src/components/workflow-run-summary/workflow-run-attempt-switcher.tsx +15 -14
  248. package/src/components/workflow-run-summary/workflow-run-summary.stories.tsx +29 -38
  249. package/src/components/workflow-run-summary/workflow-run-summary.test.tsx +30 -24
  250. package/src/components/workflow-run-summary/workflow-run-summary.tsx +102 -77
  251. package/src/components/workflow-run-tabs/index.ts +5 -0
  252. package/src/components/workflow-run-tabs/run-annotation-summary-line.test.tsx +69 -0
  253. package/src/components/workflow-run-tabs/run-annotation-summary-line.tsx +97 -0
  254. package/src/components/workflow-run-tabs/run-annotations-empty.tsx +11 -0
  255. package/src/components/workflow-run-tabs/run-jobs-list.tsx +151 -0
  256. package/src/components/workflow-run-tabs/run-tab-count.tsx +28 -0
  257. package/src/components/workflow-run-tabs/run-tab-strip.stories.tsx +52 -0
  258. package/src/components/workflow-run-tabs/run-tab-strip.test.tsx +117 -0
  259. package/src/components/workflow-run-tabs/run-tab-strip.tsx +97 -0
  260. package/src/components/workflow-run-view/agent-config-failure-callout.stories.tsx +2 -3
  261. package/src/components/workflow-run-view/agent-config-failure-callout.tsx +4 -4
  262. package/src/components/workflow-run-view/job-card.stories.tsx +11 -10
  263. package/src/components/workflow-run-view/job-card.test.tsx +115 -0
  264. package/src/components/workflow-run-view/job-card.tsx +74 -35
  265. package/src/components/workflow-run-view/job-execution-switcher.tsx +31 -9
  266. package/src/components/workflow-run-view/workflow-run-selection.test.ts +48 -0
  267. package/src/components/workflow-run-view/workflow-run-selection.ts +48 -1
  268. package/src/components/workflow-run-view/workflow-run-states.tsx +12 -1
  269. package/src/components/workflow-run-view/workflow-run-view.test.tsx +152 -94
  270. package/src/components/workflow-run-view/workflow-run-view.tsx +219 -95
  271. package/src/components/workflow-source-panel/index.ts +1 -0
  272. package/src/components/workflow-source-panel/workflow-source-content.tsx +84 -0
  273. package/src/components/workflow-source-panel/workflow-source-panel.tsx +14 -81
  274. package/src/components/workflow-status/status-visuals.test.ts +9 -0
  275. package/src/components/workflow-status/status-visuals.ts +6 -4
  276. package/src/components/workflow-status/workflow-status-icon.stories.tsx +10 -2
  277. package/src/components/workflow-status/workflow-status-icon.test.tsx +18 -0
  278. package/src/components/workflow-status/workflow-status-icon.tsx +17 -24
  279. package/src/core/entities/job-execution.ts +22 -0
  280. package/src/core/entities/job-status.test.ts +104 -0
  281. package/src/core/entities/job.ts +18 -3
  282. package/src/core/entities/step.ts +4 -0
  283. package/src/core/entities/workflow-run.ts +76 -7
  284. package/src/core/workflow-run-tabs.ts +8 -0
  285. package/src/core/workflow-run.test.ts +24 -9
  286. package/src/core/workflow-run.ts +17 -2
  287. package/src/feature.ts +5 -5
  288. package/src/hooks/api/workflow-run-mapper.ts +30 -3
  289. package/src/hooks/api/workflow-runs.test.tsx +2 -0
  290. package/src/hooks/api/workflow-runs.ts +12 -7
  291. package/src/index.ts +7 -2
  292. package/src/page-harness-budget.test.ts +1 -1
  293. package/src/pages/project-workflows-page.test.tsx +59 -5
  294. package/src/pages/project-workflows-page.tsx +34 -1
  295. package/src/pages/workflow-run-detail-page.tsx +72 -0
  296. package/src/pages/workflow-run-list-page.tsx +65 -0
  297. package/src/pages/workflow-run-pages.test.tsx +823 -0
  298. package/src/routes/inputs.test.ts +240 -12
  299. package/src/routes/inputs.ts +215 -11
  300. package/src/routes/project-route.tsx +25 -0
  301. package/src/routes/run-detail.tsx +15 -8
  302. package/src/routes/runs.tsx +14 -7
  303. package/src/routes/workflows.tsx +5 -4
  304. package/src/storybook-config.test.ts +27 -0
  305. package/test/fixtures/workflow-run.ts +76 -7
  306. package/test/pages.tsx +18 -8
  307. package/test/render.tsx +8 -2
  308. package/tsconfig.build.tsbuildinfo +1 -1
  309. package/tsconfig.test.json +7 -1
  310. package/dist/components/workflow-run-list/workflow-run-list-header.d.ts +0 -10
  311. package/dist/components/workflow-run-list/workflow-run-list-header.d.ts.map +0 -1
  312. package/dist/components/workflow-run-list/workflow-run-list-header.js +0 -55
  313. package/dist/components/workflow-run-list/workflow-run-list-header.js.map +0 -1
  314. package/dist/pages/workflow-run-first-time-use.d.ts +0 -6
  315. package/dist/pages/workflow-run-first-time-use.d.ts.map +0 -1
  316. package/dist/pages/workflow-run-first-time-use.js +0 -17
  317. package/dist/pages/workflow-run-first-time-use.js.map +0 -1
  318. package/dist/pages/workflow-run-page.d.ts +0 -10
  319. package/dist/pages/workflow-run-page.d.ts.map +0 -1
  320. package/dist/pages/workflow-run-page.js +0 -90
  321. package/dist/pages/workflow-run-page.js.map +0 -1
  322. package/src/components/workflow-run-list/workflow-run-list-header.tsx +0 -53
  323. package/src/pages/workflow-run-first-time-use.tsx +0 -17
  324. package/src/pages/workflow-run-page.test.tsx +0 -519
  325. package/src/pages/workflow-run-page.tsx +0 -94
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shipfox/client-workflows",
3
3
  "license": "MIT",
4
- "version": "12.0.2",
4
+ "version": "14.0.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/ShipfoxHQ/shipfox.git",
@@ -27,15 +27,15 @@
27
27
  "dependencies": {
28
28
  "@swc/helpers": "^0.5.17",
29
29
  "date-fns": "^4.1.0",
30
- "@shipfox/api-definitions-dto": "11.0.0",
31
- "@shipfox/api-workflows-dto": "10.0.0",
30
+ "@shipfox/api-definitions-dto": "12.0.0",
31
+ "@shipfox/api-workflows-dto": "12.1.0",
32
+ "@shipfox/client-logs": "14.0.0",
32
33
  "@shipfox/client-api": "6.0.1",
33
- "@shipfox/client-logs": "12.0.1",
34
- "@shipfox/client-projects": "12.0.2",
35
- "@shipfox/client-shell": "12.0.2",
36
- "@shipfox/client-triggers": "12.0.2",
37
- "@shipfox/client-ui": "6.0.2",
38
- "@shipfox/react-ui": "0.3.7"
34
+ "@shipfox/client-projects": "14.0.0",
35
+ "@shipfox/client-shell": "14.0.0",
36
+ "@shipfox/client-triggers": "14.0.0",
37
+ "@shipfox/client-ui": "14.0.0",
38
+ "@shipfox/react-ui": "0.5.0"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "@tanstack/react-query": "^5.101.0",
@@ -1,5 +1,9 @@
1
1
  import {humanDuration} from '@shipfox/react-ui/utils';
2
- import type {JobDisplayDuration, JobExecutionTime} from '#core/workflow-run.js';
2
+ import type {
3
+ JobDisplayDuration,
4
+ JobExecutionDisplayStatus,
5
+ JobExecutionTime,
6
+ } from '#core/workflow-run.js';
3
7
 
4
8
  export function formatJobExecutionTimeLabel(time: JobExecutionTime): string {
5
9
  switch (time.state) {
@@ -16,6 +20,7 @@ export function formatJobExecutionTimeLabel(time: JobExecutionTime): string {
16
20
 
17
21
  export function formatJobDurationAccessibleLabel(
18
22
  duration: JobDisplayDuration | null,
23
+ displayStatus?: JobExecutionDisplayStatus,
19
24
  ): string | undefined {
20
25
  if (duration === null) return undefined;
21
26
 
@@ -24,7 +29,11 @@ export function formatJobDurationAccessibleLabel(
24
29
  case 'queue':
25
30
  return duration.state === 'live' ? `queueing ${label}` : `queued ${label}`;
26
31
  case 'run':
27
- return duration.state === 'live' ? `running ${label}` : `ran ${label}`;
32
+ if (duration.state !== 'live') return `ran ${label}`;
33
+ if (displayStatus === 'running') return `running ${label}`;
34
+ return displayStatus === 'pending'
35
+ ? `runner assigned ${label}`
36
+ : `execution elapsed ${label}`;
28
37
  default: {
29
38
  const exhaustive: never = duration;
30
39
  return exhaustive;
@@ -22,7 +22,11 @@ function LiveDurationText({fromIso}: {fromIso: string}) {
22
22
  function DurationText({children}: {children: string}) {
23
23
  // The status icon names the state; the node aria-label carries the verb.
24
24
  return (
25
- <Code as="span" variant="label" className="shrink-0 tabular-nums text-foreground-neutral-muted">
25
+ <Code
26
+ as="span"
27
+ variant="label"
28
+ className="shrink-0 tabular-nums text-foreground-neutral-subtle"
29
+ >
26
30
  {children}
27
31
  </Code>
28
32
  );
@@ -7,6 +7,7 @@ import type {WorkflowRunDetail} from '#core/workflow-run.js';
7
7
  import type {JobGraphModel, JobGraphNavigationKey} from './graph-model.js';
8
8
  import {nextJobGraphNodeId} from './graph-model.js';
9
9
  import {JobNode, TriggerNode} from './job-node.js';
10
+ import type {JobGraphSelectionSource} from './types.js';
10
11
 
11
12
  const NODE_WIDTH = 208;
12
13
  const NODE_HEIGHT = 48;
@@ -27,7 +28,7 @@ export function JobGraphContent({
27
28
  'triggerDisplayLabel' | 'triggerLabel' | 'triggerProvider' | 'triggerSource'
28
29
  >;
29
30
  selectedJobId?: string | undefined;
30
- onSelectJob: (jobId: string | undefined) => void;
31
+ onSelectJob: (jobId: string | undefined, source?: JobGraphSelectionSource) => void;
31
32
  }) {
32
33
  const nodeRefs = useRef(new Map<string, HTMLButtonElement>());
33
34
  const [hoveredJobId, setHoveredJobId] = useState<string | undefined>();
@@ -74,13 +75,13 @@ export function JobGraphContent({
74
75
  if (!nextNodeId) return;
75
76
 
76
77
  event.preventDefault();
77
- onSelectJob(nextNodeId);
78
+ onSelectJob(nextNodeId, 'keyboard');
78
79
  nodeRefs.current.get(nextNodeId)?.focus();
79
80
  }
80
81
 
81
82
  return (
82
83
  <TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>
83
- <div className="min-h-0 overflow-auto bg-background-neutral-base">
84
+ <div className="min-h-0 overflow-auto bg-background-neutral-background">
84
85
  <div className="relative" style={{width: contentWidth, minHeight: contentHeight}}>
85
86
  <GraphEdges model={model} hoveredJobId={hoveredJobId} />
86
87
  <div
@@ -101,7 +102,9 @@ export function JobGraphContent({
101
102
  node={node}
102
103
  selected={node.id === selectedJobId}
103
104
  ref={setNodeRef(node.id)}
104
- onSelect={() => onSelectJob(node.id === selectedJobId ? undefined : node.id)}
105
+ onSelect={() =>
106
+ onSelectJob(node.id === selectedJobId ? undefined : node.id, 'pointer')
107
+ }
105
108
  onKeyDown={handleKeyDown}
106
109
  onHoverStart={() => setHoveredJobId(node.id)}
107
110
  onHoverEnd={() =>
@@ -6,6 +6,7 @@ import {
6
6
  workflowJob,
7
7
  workflowJobExecutionDto,
8
8
  workflowRunDetail,
9
+ workflowStepDto,
9
10
  } from '#test/fixtures/workflow-run.js';
10
11
  import {JobGraph} from './job-graph.js';
11
12
 
@@ -46,10 +47,7 @@ describe('JobGraph', () => {
46
47
  render(
47
48
  <JobGraph
48
49
  run={makeRun({
49
- jobs: [
50
- makeJob({name: 'linux', status: 'running'}),
51
- makeJob({name: 'macos', status: 'running', position: 1}),
52
- ],
50
+ jobs: [makeRunningJob({name: 'linux'}), makeRunningJob({name: 'macos', position: 1})],
53
51
  })}
54
52
  />,
55
53
  );
@@ -99,7 +97,7 @@ describe('JobGraph', () => {
99
97
  run={makeRun({
100
98
  jobs: [
101
99
  makeJob({name: 'build'}),
102
- makeJob({name: 'deploy', position: 1, dependencies: ['build'], status: 'running'}),
100
+ makeRunningJob({name: 'deploy', position: 1, dependencies: ['build']}),
103
101
  ],
104
102
  })}
105
103
  />,
@@ -161,7 +159,7 @@ describe('JobGraph', () => {
161
159
  />,
162
160
  );
163
161
 
164
- const build = screen.getByRole('button', {name: 'build, Pending, 2 executions'});
162
+ const build = screen.getByRole('button', {name: 'build, Succeeded, 2 executions'});
165
163
  const deploy = screen.getByRole('button', {name: 'deploy, Pending'});
166
164
  expect(within(build).getByText('2')).toBeInTheDocument();
167
165
  expect(within(build).queryByText('2 exec')).not.toBeInTheDocument();
@@ -224,7 +222,7 @@ describe('JobGraph', () => {
224
222
  dependencies: ['build'],
225
223
  status: 'succeeded',
226
224
  });
227
- const testJob = makeJob({
225
+ const testJob = makeRunningJob({
228
226
  name: 'test',
229
227
  position: 2,
230
228
  dependencies: ['build'],
@@ -257,7 +255,7 @@ describe('JobGraph', () => {
257
255
  test('routes skipped-column join edges away from intermediate nodes', () => {
258
256
  const packageJob = makeJob({name: 'package', position: 0, status: 'succeeded'});
259
257
  const securityScan = makeJob({name: 'security-scan', position: 1, status: 'succeeded'});
260
- const smokeTests = makeJob({
258
+ const smokeTests = makeRunningJob({
261
259
  name: 'smoke-tests',
262
260
  position: 2,
263
261
  dependencies: ['package'],
@@ -296,3 +294,16 @@ function makeRun(overrides: Partial<WorkflowRunDetail> = {}): WorkflowRunDetail
296
294
  function makeJob(overrides: Partial<WorkflowRunJobDetailDto> & {name: string}): Job {
297
295
  return workflowJob(overrides);
298
296
  }
297
+
298
+ function makeRunningJob(overrides: Partial<WorkflowRunJobDetailDto> & {name: string}): Job {
299
+ return makeJob({
300
+ ...overrides,
301
+ status: 'running',
302
+ job_executions: [
303
+ workflowJobExecutionDto({
304
+ status: 'running',
305
+ steps: [workflowStepDto({status: 'running'})],
306
+ }),
307
+ ],
308
+ });
309
+ }
@@ -3,6 +3,7 @@ import {useState} from 'react';
3
3
  import type {WorkflowRunDetail} from '#core/workflow-run.js';
4
4
  import type {JobGraphModel} from './graph-model.js';
5
5
  import {JobGraphContent} from './job-graph-content.js';
6
+ import type {JobGraphSelectionSource} from './types.js';
6
7
 
7
8
  export function JobGraphView({
8
9
  model,
@@ -19,7 +20,9 @@ export function JobGraphView({
19
20
  >;
20
21
  selectedJobId?: string | undefined;
21
22
  defaultSelectedJobId?: string | undefined;
22
- onSelectedJobChange?: ((jobId: string | undefined) => void) | undefined;
23
+ onSelectedJobChange?:
24
+ | ((jobId: string | undefined, source?: JobGraphSelectionSource) => void)
25
+ | undefined;
23
26
  className?: string | undefined;
24
27
  }) {
25
28
  const [localSelectedJobId, setLocalSelectedJobId] = useState<string | undefined>(
@@ -27,9 +30,9 @@ export function JobGraphView({
27
30
  );
28
31
  const selected = selectedJobId ?? localSelectedJobId;
29
32
 
30
- function selectJob(jobId: string | undefined) {
33
+ function selectJob(jobId: string | undefined, source?: JobGraphSelectionSource) {
31
34
  setLocalSelectedJobId(jobId);
32
- onSelectedJobChange?.(jobId);
35
+ onSelectedJobChange?.(jobId, source);
33
36
  }
34
37
 
35
38
  return (
@@ -2,7 +2,7 @@ import type {WorkflowRunJobDetailDto} from '@shipfox/api-workflows-dto';
2
2
  import type {Meta, StoryObj} from '@storybook/react';
3
3
  import {within} from 'storybook/test';
4
4
  import type {Job, WorkflowRunDetail} from '#core/workflow-run.js';
5
- import {workflowJob, workflowRunDetail} from '#test/fixtures/workflow-run.js';
5
+ import {workflowJob, workflowRunDetail, workflowStepDto} from '#test/fixtures/workflow-run.js';
6
6
  import {JobGraph} from './job-graph.js';
7
7
 
8
8
  const meta = {
@@ -11,7 +11,7 @@ const meta = {
11
11
  parameters: {layout: 'centered'},
12
12
  decorators: [
13
13
  (Story) => (
14
- <div className="h-520 w-900 overflow-auto bg-background-neutral-base p-16">
14
+ <div className="h-520 w-900 overflow-auto bg-background-neutral-background p-16">
15
15
  <Story />
16
16
  </div>
17
17
  ),
@@ -93,6 +93,23 @@ export const Empty: Story = {
93
93
  args: {run: makeRun({jobs: []})},
94
94
  };
95
95
 
96
+ export const LargeWideAndTall: Story = {
97
+ args: {
98
+ run: makeRun({
99
+ jobs: [
100
+ ...linearJobs(10),
101
+ ...Array.from({length: 10}, (_, index) =>
102
+ makeJob({
103
+ name: `parallel-${String(index + 1).padStart(2, '0')}`,
104
+ position: 20 + index,
105
+ status: index < 2 ? 'running' : 'pending',
106
+ }),
107
+ ),
108
+ ],
109
+ }),
110
+ },
111
+ };
112
+
96
113
  export const Selected: Story = {
97
114
  args: {
98
115
  run: makeRun({
@@ -111,23 +128,6 @@ export const Selected: Story = {
111
128
  },
112
129
  };
113
130
 
114
- export const LargeWideAndTall: Story = {
115
- args: {
116
- run: makeRun({
117
- jobs: [
118
- ...linearJobs(10),
119
- ...Array.from({length: 10}, (_, index) =>
120
- makeJob({
121
- name: `parallel-${String(index + 1).padStart(2, '0')}`,
122
- position: 20 + index,
123
- status: index < 2 ? 'running' : 'pending',
124
- }),
125
- ),
126
- ],
127
- }),
128
- },
129
- };
130
-
131
131
  function linearJobs(count: number): Job[] {
132
132
  return Array.from({length: count}, (_, index) =>
133
133
  makeJob({
@@ -178,5 +178,9 @@ function makeRun(overrides: Partial<WorkflowRunDetail> = {}): WorkflowRunDetail
178
178
  }
179
179
 
180
180
  function makeJob(overrides: Partial<WorkflowRunJobDetailDto> & {name: string}): Job {
181
- return workflowJob(overrides);
181
+ const job = workflowJob(overrides);
182
+ if (overrides.status === 'running' && job.jobExecutions.length === 0) {
183
+ return workflowJob({...overrides, steps: [workflowStepDto({status: 'running'})]});
184
+ }
185
+ return job;
182
186
  }
@@ -2,7 +2,11 @@ import type {WorkflowRunJobDetailDto} from '@shipfox/api-workflows-dto';
2
2
  import type {Meta, StoryObj} from '@storybook/react';
3
3
  import type {KeyboardEventHandler} from 'react';
4
4
  import type {JobExecutionStatus, JobStatus} from '#core/workflow-run.js';
5
- import {workflowJob, workflowJobExecutionDto} from '#test/fixtures/workflow-run.js';
5
+ import {
6
+ workflowJob,
7
+ workflowJobExecutionDto,
8
+ workflowStepDto,
9
+ } from '#test/fixtures/workflow-run.js';
6
10
  import type {JobGraphNode} from './graph-model.js';
7
11
  import {JobNode} from './job-node.js';
8
12
 
@@ -16,7 +20,7 @@ const meta = {
16
20
  parameters: {layout: 'centered'},
17
21
  decorators: [
18
22
  (Story) => (
19
- <div className="bg-background-neutral-base p-16">
23
+ <div className="bg-background-neutral-background p-16">
20
24
  <Story />
21
25
  </div>
22
26
  ),
@@ -183,7 +187,7 @@ export const ListeningProgression: Story = {
183
187
  <JobNode
184
188
  node={makeNode({
185
189
  id: 'job-listening-running',
186
- label: 'running, 0 executions',
190
+ label: 'listening, 0 executions',
187
191
  mode: 'listening',
188
192
  status: 'running',
189
193
  listenerStatus: 'listening',
@@ -200,7 +204,7 @@ export const ListeningProgression: Story = {
200
204
  <JobNode
201
205
  node={makeNode({
202
206
  id: 'job-one-running-execution',
203
- label: 'running, 1 running execution',
207
+ label: 'listening, 1 running execution',
204
208
  mode: 'listening',
205
209
  status: 'running',
206
210
  listenerStatus: 'listening',
@@ -217,7 +221,7 @@ export const ListeningProgression: Story = {
217
221
  <JobNode
218
222
  node={makeNode({
219
223
  id: 'job-mixed-executions',
220
- label: 'running, mixed executions',
224
+ label: 'listening, mixed executions',
221
225
  mode: 'listening',
222
226
  status: 'running',
223
227
  listenerStatus: 'listening',
@@ -330,6 +334,7 @@ function makeNode({
330
334
  queued_at: queuedAt,
331
335
  started_at: startedAt,
332
336
  finished_at: finishedAt,
337
+ ...(status === 'running' ? {steps: [workflowStepDto({status: 'running'})]} : {}),
333
338
  }),
334
339
  ]
335
340
  : jobExecutions,
@@ -359,6 +364,6 @@ function makeExecutionsByStatus(
359
364
  timed_out_at: null,
360
365
  created_at: '2026-06-26T11:54:00.000Z',
361
366
  updated_at: '2026-06-26T12:00:00.000Z',
362
- steps: [],
367
+ steps: status === 'running' ? [workflowStepDto({status: 'running'})] : [],
363
368
  }));
364
369
  }
@@ -2,14 +2,18 @@ import type {WorkflowRunJobDetailDto} from '@shipfox/api-workflows-dto';
2
2
  import {TimeTickerProvider} from '@shipfox/react-ui/time-ticker';
3
3
  import {act, render, screen, within} from '@testing-library/react';
4
4
  import userEvent from '@testing-library/user-event';
5
- import {workflowJob, workflowJobExecutionDto} from '#test/fixtures/workflow-run.js';
5
+ import {
6
+ workflowJob,
7
+ workflowJobExecutionDto,
8
+ workflowStepDto,
9
+ } from '#test/fixtures/workflow-run.js';
6
10
  import type {JobGraphNode} from './graph-model.js';
7
11
  import {JobNode} from './job-node.js';
8
12
 
9
13
  const NOW = Date.parse('2026-06-26T12:00:00.000Z');
10
14
 
11
15
  type NodeOverrides = Omit<Partial<WorkflowRunJobDetailDto>, 'job_executions'> & {
12
- name: string;
16
+ name?: string | null;
13
17
  job_executions?: WorkflowRunJobDetailDto['job_executions'];
14
18
  queued_at?: string | null;
15
19
  started_at?: string | null;
@@ -32,6 +36,9 @@ function makeNode(overrides: NodeOverrides): JobGraphNode {
32
36
  queued_at: queued_at ?? null,
33
37
  started_at: started_at ?? null,
34
38
  finished_at: finished_at ?? null,
39
+ ...(jobOverrides.status === 'running'
40
+ ? {steps: [workflowStepDto({status: 'running'})]}
41
+ : {}),
35
42
  }),
36
43
  ];
37
44
  } else if (job_executions !== undefined) {
@@ -45,11 +52,14 @@ function makeNode(overrides: NodeOverrides): JobGraphNode {
45
52
  });
46
53
  }
47
54
 
48
- function renderNode(node: JobGraphNode, {live = false}: {live?: boolean} = {}) {
55
+ function renderNode(
56
+ node: JobGraphNode,
57
+ {live = false, selected = false}: {live?: boolean; selected?: boolean} = {},
58
+ ) {
49
59
  const element = (
50
60
  <JobNode
51
61
  node={node}
52
- selected={false}
62
+ selected={selected}
53
63
  onSelect={() => undefined}
54
64
  onKeyDown={() => undefined}
55
65
  onHoverStart={() => undefined}
@@ -84,6 +94,28 @@ function setVisibility(state: 'visible' | 'hidden') {
84
94
  Object.defineProperty(document, 'visibilityState', {configurable: true, value: state});
85
95
  }
86
96
 
97
+ describe('JobNode interaction', () => {
98
+ test('presents the selectable node as an elevated interactive surface', () => {
99
+ renderNode(makeNode({name: 'build', status: 'pending'}));
100
+
101
+ expect(screen.getByRole('button', {name: 'build, Pending'})).toHaveClass(
102
+ 'cursor-pointer',
103
+ 'bg-background-neutral-base',
104
+ 'shadow-button-neutral',
105
+ 'hover:bg-background-components-hover',
106
+ );
107
+ });
108
+
109
+ test('uses a quiet neutral surface for the selected node', () => {
110
+ renderNode(makeNode({name: 'build', status: 'pending'}), {selected: true});
111
+
112
+ expect(screen.getByRole('button', {name: 'build, Pending'})).toHaveClass(
113
+ 'bg-background-components-hover',
114
+ 'hover:bg-background-components-pressed',
115
+ );
116
+ });
117
+ });
118
+
87
119
  describe('JobNode duration', () => {
88
120
  beforeEach(() => {
89
121
  setMatchMedia(false);
@@ -200,7 +232,7 @@ describe('JobNode duration', () => {
200
232
  renderNode(node);
201
233
 
202
234
  expect(screen.queryByText('2m 14s')).not.toBeInTheDocument();
203
- expect(screen.getByRole('button', {name: 'deploy-window, Running'})).toBeInTheDocument();
235
+ expect(screen.getByRole('button', {name: 'deploy-window, Listening'})).toBeInTheDocument();
204
236
  });
205
237
  });
206
238
 
@@ -227,12 +259,12 @@ describe('JobNode listening indicator', () => {
227
259
 
228
260
  renderNode(node);
229
261
 
230
- expect(screen.getByRole('button', {name: 'deploy-window, Running'})).toBeInTheDocument();
262
+ expect(screen.getByRole('button', {name: 'deploy-window, Listening'})).toBeInTheDocument();
231
263
  expect(screen.queryByLabelText('Waiting for events to start job')).not.toBeInTheDocument();
232
264
 
233
- await user.hover(screen.getByRole('img', {name: 'Running'}));
265
+ await user.hover(screen.getByRole('img', {name: 'Listening'}));
234
266
 
235
- expect(await screen.findByRole('tooltip')).toHaveTextContent('Running');
267
+ expect(await screen.findByRole('tooltip')).toHaveTextContent('Listening');
236
268
  });
237
269
 
238
270
  test('does not show the listener icon before the listener is armed or after it resolves', () => {
@@ -277,6 +309,12 @@ describe('JobNode status indicator', () => {
277
309
  const node = makeNode({
278
310
  name: 'deploy',
279
311
  status: 'running',
312
+ job_executions: [
313
+ workflowJobExecutionDto({
314
+ status: 'running',
315
+ steps: [workflowStepDto({status: 'running'})],
316
+ }),
317
+ ],
280
318
  });
281
319
 
282
320
  renderNode(node);
@@ -286,7 +324,15 @@ describe('JobNode status indicator', () => {
286
324
  expect(await screen.findByRole('tooltip')).toHaveTextContent('Running');
287
325
  });
288
326
 
289
- test('shows a running execution as running while the job is still pending', () => {
327
+ test('uses the job key when the static name is missing', () => {
328
+ const node = makeNode({key: 'deploy-prod', name: null, status: 'succeeded'});
329
+
330
+ renderNode(node);
331
+
332
+ expect(screen.getByRole('button', {name: 'deploy-prod, Succeeded'})).toBeInTheDocument();
333
+ });
334
+
335
+ test('shows a running lifecycle without a running step as pending', () => {
290
336
  const node = makeNode({
291
337
  name: 'deploy',
292
338
  status: 'pending',
@@ -295,6 +341,23 @@ describe('JobNode status indicator', () => {
295
341
 
296
342
  renderNode(node);
297
343
 
344
+ expect(screen.getByRole('button', {name: 'deploy, Pending'})).toBeInTheDocument();
345
+ });
346
+
347
+ test('shows a pending lifecycle as running while a step is active', () => {
348
+ const node = makeNode({
349
+ name: 'deploy',
350
+ status: 'pending',
351
+ job_executions: [
352
+ workflowJobExecutionDto({
353
+ status: 'pending',
354
+ steps: [workflowStepDto({status: 'running'})],
355
+ }),
356
+ ],
357
+ });
358
+
359
+ renderNode(node);
360
+
298
361
  expect(screen.getByRole('button', {name: 'deploy, Running'})).toBeInTheDocument();
299
362
  });
300
363
  });
@@ -317,10 +380,17 @@ describe('JobNode execution count indicator', () => {
317
380
  name: 'release-gates',
318
381
  mode: 'listening',
319
382
  status: 'running',
383
+ listener_status: 'listening',
320
384
  job_executions: [
321
385
  workflowJobExecutionDto({status: 'pending'}),
322
- workflowJobExecutionDto({status: 'running'}),
323
- workflowJobExecutionDto({status: 'running'}),
386
+ workflowJobExecutionDto({
387
+ status: 'running',
388
+ steps: [workflowStepDto({status: 'running'})],
389
+ }),
390
+ workflowJobExecutionDto({
391
+ status: 'running',
392
+ steps: [workflowStepDto({status: 'running'})],
393
+ }),
324
394
  workflowJobExecutionDto({status: 'succeeded'}),
325
395
  workflowJobExecutionDto({status: 'succeeded'}),
326
396
  workflowJobExecutionDto({status: 'succeeded'}),
@@ -331,7 +401,7 @@ describe('JobNode execution count indicator', () => {
331
401
 
332
402
  renderNode(node);
333
403
 
334
- const button = screen.getByRole('button', {name: 'release-gates, Running, 8 executions'});
404
+ const button = screen.getByRole('button', {name: 'release-gates, Listening, 8 executions'});
335
405
  expect(within(button).getByText('8')).toBeInTheDocument();
336
406
  expect(within(button).queryByText('8 exec')).not.toBeInTheDocument();
337
407
  expect(button.querySelector('[data-execution-status-segment]')).not.toBeInTheDocument();
@@ -9,7 +9,14 @@ import {cn} from '@shipfox/react-ui/utils';
9
9
  import type {KeyboardEventHandler, Ref} from 'react';
10
10
  import {getWorkflowStatusVisual} from '#components/workflow-status/status-visuals.js';
11
11
  import {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';
12
- import type {JobExecution, JobExecutionStatus, WorkflowRunDetail} from '#core/workflow-run.js';
12
+ import {
13
+ defaultJobExecution,
14
+ deriveJobDisplayStatus,
15
+ deriveJobExecutionDisplayStatus,
16
+ type JobExecution,
17
+ type JobExecutionStatus,
18
+ type WorkflowRunDetail,
19
+ } from '#core/workflow-run.js';
13
20
  import type {JobGraphNode} from './graph-model.js';
14
21
  import {formatJobDurationAccessibleLabel} from './job-duration-format.js';
15
22
  import {JobDurationLabel} from './job-duration-label.js';
@@ -33,14 +40,14 @@ export function TriggerNode({
33
40
  <button
34
41
  type="button"
35
42
  aria-label={label}
36
- className="flex items-center justify-center rounded-full border border-border-neutral-base bg-background-components-base transition-colors hover:bg-background-components-hover focus-visible:shadow-border-interactive-with-active focus-visible:outline-none"
43
+ className="flex cursor-help items-center justify-center rounded-full bg-background-neutral-base shadow-button-neutral transition-colors hover:bg-background-components-hover focus-visible:shadow-button-neutral-focus focus-visible:outline-none"
37
44
  style={{width: TRIGGER_SIZE, height: TRIGGER_SIZE}}
38
45
  >
39
46
  <TriggerSourceIcon
40
47
  provider={trigger.triggerProvider}
41
48
  source={trigger.triggerSource}
42
49
  aria-hidden
43
- className="size-14 shrink-0 text-foreground-neutral-muted"
50
+ className="size-14 shrink-0 text-foreground-neutral-subtle"
44
51
  />
45
52
  </button>
46
53
  </TooltipTrigger>
@@ -69,16 +76,16 @@ export function JobNode({
69
76
  ref?: Ref<HTMLButtonElement>;
70
77
  }) {
71
78
  useTimeTick();
72
- const status =
73
- node.status === 'pending' &&
74
- node.jobExecutions.some((jobExecution) => jobExecution.status === 'running')
75
- ? 'running'
76
- : node.status;
79
+ const status = deriveJobDisplayStatus(node);
80
+ const execution = defaultJobExecution(node);
77
81
  const visual = getWorkflowStatusVisual(status);
78
82
  const accessibleLabel = [
79
83
  node.displayName,
80
84
  visual.label,
81
- formatJobDurationAccessibleLabel(node.displayDuration),
85
+ formatJobDurationAccessibleLabel(
86
+ node.displayDuration,
87
+ execution ? deriveJobExecutionDisplayStatus(execution) : undefined,
88
+ ),
82
89
  node.executionCountVisible
83
90
  ? executionCountAccessibleLabel(node.jobExecutions.length)
84
91
  : undefined,
@@ -99,19 +106,19 @@ export function JobNode({
99
106
  onPointerEnter={onHoverStart}
100
107
  onPointerLeave={onHoverEnd}
101
108
  className={cn(
102
- 'group relative flex h-48 w-208 items-center gap-8 rounded-8 border border-border-neutral-base bg-background-components-base px-10 text-left transition-colors hover:bg-background-components-hover focus-visible:shadow-border-interactive-with-active focus-visible:outline-none',
103
- selected && 'bg-background-components-hover',
109
+ 'group relative flex h-48 w-208 cursor-pointer items-center gap-8 rounded-8 bg-background-neutral-base px-10 text-left shadow-button-neutral transition-colors hover:bg-background-components-hover focus-visible:shadow-button-neutral-focus focus-visible:outline-none',
110
+ selected && 'bg-background-components-hover hover:bg-background-components-pressed',
104
111
  node.carriedOver && 'opacity-[0.55]',
105
112
  )}
106
113
  >
107
114
  {selected ? (
108
115
  <span
109
116
  aria-hidden="true"
110
- className="absolute inset-y-6 left-0 w-3 rounded-full bg-border-highlights-interactive"
117
+ className="absolute inset-y-6 left-0 w-px rounded-full bg-border-highlights-interactive"
111
118
  />
112
119
  ) : null}
113
120
  <div className="flex min-w-0 flex-1 items-center gap-8">
114
- <WorkflowStatusIcon status={status} jobMode={node.mode} size={14} />
121
+ <WorkflowStatusIcon status={status} size={14} />
115
122
  <JobLabel label={node.displayName} />
116
123
  </div>
117
124
  <JobDurationLabel duration={node.displayDuration} />
@@ -129,7 +136,7 @@ function ExecutionCountText({executions}: {executions: JobExecution[]}) {
129
136
  <TooltipTrigger asChild>
130
137
  <span
131
138
  aria-hidden="true"
132
- className="inline-flex h-20 min-w-28 shrink-0 items-center justify-end gap-4 text-foreground-neutral-muted"
139
+ className="inline-flex h-20 min-w-28 shrink-0 items-center justify-end gap-4 text-foreground-neutral-subtle"
133
140
  >
134
141
  <Icon name="loopRightLine" className="size-12" />
135
142
  <Code as="span" variant="label" className="text-current">
@@ -199,7 +206,7 @@ const EXECUTION_STATUSES = [
199
206
  function executionStatusCounts(executions: JobExecution[]) {
200
207
  return executions.reduce(
201
208
  (counts, execution) => {
202
- counts[execution.status] += 1;
209
+ counts[deriveJobExecutionDisplayStatus(execution)] += 1;
203
210
  return counts;
204
211
  },
205
212
  {pending: 0, running: 0, succeeded: 0, failed: 0, cancelled: 0},
@@ -1,9 +1,13 @@
1
1
  import type {WorkflowRunDetail} from '#core/workflow-run.js';
2
2
 
3
+ export type JobGraphSelectionSource = 'pointer' | 'keyboard';
4
+
3
5
  export interface JobGraphProps {
4
6
  run: WorkflowRunDetail;
5
7
  selectedJobId?: string | undefined;
6
8
  defaultSelectedJobId?: string | undefined;
7
- onSelectedJobChange?: ((jobId: string | undefined) => void) | undefined;
9
+ onSelectedJobChange?:
10
+ | ((jobId: string | undefined, source?: JobGraphSelectionSource) => void)
11
+ | undefined;
8
12
  className?: string | undefined;
9
13
  }