@shipfox/client-workflows 16.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 (180) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-type.log +1 -0
  3. package/CHANGELOG.md +20 -0
  4. package/dist/components/identifier/identifier.js +2 -2
  5. package/dist/components/identifier/identifier.js.map +1 -1
  6. package/dist/components/job-detail/agent-config-failure-callout.d.ts.map +1 -1
  7. package/dist/components/job-detail/agent-config-failure-callout.js +1 -1
  8. package/dist/components/job-detail/agent-config-failure-callout.js.map +1 -1
  9. package/dist/components/job-detail/job-context-panel.d.ts +6 -0
  10. package/dist/components/job-detail/job-context-panel.d.ts.map +1 -0
  11. package/dist/components/job-detail/job-context-panel.js +224 -0
  12. package/dist/components/job-detail/job-context-panel.js.map +1 -0
  13. package/dist/components/job-detail/job-detail-header.d.ts +3 -1
  14. package/dist/components/job-detail/job-detail-header.d.ts.map +1 -1
  15. package/dist/components/job-detail/job-detail-header.js +54 -48
  16. package/dist/components/job-detail/job-detail-header.js.map +1 -1
  17. package/dist/components/job-detail/job-detail-view.d.ts +1 -1
  18. package/dist/components/job-detail/job-detail-view.d.ts.map +1 -1
  19. package/dist/components/job-detail/job-detail-view.js +80 -44
  20. package/dist/components/job-detail/job-detail-view.js.map +1 -1
  21. package/dist/components/job-detail/job-empty-states.d.ts.map +1 -1
  22. package/dist/components/job-detail/job-empty-states.js +19 -10
  23. package/dist/components/job-detail/job-empty-states.js.map +1 -1
  24. package/dist/components/job-detail/job-execution-switcher.js +4 -4
  25. package/dist/components/job-detail/job-execution-switcher.js.map +1 -1
  26. package/dist/components/job-detail/json-code.d.ts +14 -0
  27. package/dist/components/job-detail/json-code.d.ts.map +1 -0
  28. package/dist/components/job-detail/json-code.js +93 -0
  29. package/dist/components/job-detail/json-code.js.map +1 -0
  30. package/dist/components/job-detail/step-attempt-log-panel.d.ts.map +1 -1
  31. package/dist/components/job-detail/step-attempt-log-panel.js +9 -6
  32. package/dist/components/job-detail/step-attempt-log-panel.js.map +1 -1
  33. package/dist/components/job-detail/step-troubleshooting.d.ts +18 -0
  34. package/dist/components/job-detail/step-troubleshooting.d.ts.map +1 -0
  35. package/dist/components/job-detail/step-troubleshooting.js +493 -0
  36. package/dist/components/job-detail/step-troubleshooting.js.map +1 -0
  37. package/dist/components/job-graph/job-graph-content.d.ts.map +1 -1
  38. package/dist/components/job-graph/job-graph-content.js +5 -4
  39. package/dist/components/job-graph/job-graph-content.js.map +1 -1
  40. package/dist/components/job-graph/job-node.js +3 -3
  41. package/dist/components/job-graph/job-node.js.map +1 -1
  42. package/dist/components/step-list/index.d.ts +1 -0
  43. package/dist/components/step-list/index.d.ts.map +1 -1
  44. package/dist/components/step-list/index.js.map +1 -1
  45. package/dist/components/step-list/step-list-model.d.ts.map +1 -1
  46. package/dist/components/step-list/step-list-model.js +5 -0
  47. package/dist/components/step-list/step-list-model.js.map +1 -1
  48. package/dist/components/step-list/step-list.d.ts +5 -1
  49. package/dist/components/step-list/step-list.d.ts.map +1 -1
  50. package/dist/components/step-list/step-list.js +117 -82
  51. package/dist/components/step-list/step-list.js.map +1 -1
  52. package/dist/components/workflow-run-duration-label.js +1 -1
  53. package/dist/components/workflow-run-duration-label.js.map +1 -1
  54. package/dist/components/workflow-run-list/job-status-strip.js +6 -6
  55. package/dist/components/workflow-run-list/job-status-strip.js.map +1 -1
  56. package/dist/components/workflow-run-list/workflow-run-filter-menu.js +1 -1
  57. package/dist/components/workflow-run-list/workflow-run-filter-menu.js.map +1 -1
  58. package/dist/components/workflow-run-list/workflow-run-filters.js +4 -4
  59. package/dist/components/workflow-run-list/workflow-run-filters.js.map +1 -1
  60. package/dist/components/workflow-run-list/workflow-run-list-states.js +6 -6
  61. package/dist/components/workflow-run-list/workflow-run-list-states.js.map +1 -1
  62. package/dist/components/workflow-run-list/workflow-run-list-view.js +1 -1
  63. package/dist/components/workflow-run-list/workflow-run-list-view.js.map +1 -1
  64. package/dist/components/workflow-run-list/workflow-run-row.js +7 -7
  65. package/dist/components/workflow-run-list/workflow-run-row.js.map +1 -1
  66. package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js +1 -1
  67. package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js.map +1 -1
  68. package/dist/components/workflow-run-summary/workflow-run-summary.d.ts +0 -1
  69. package/dist/components/workflow-run-summary/workflow-run-summary.d.ts.map +1 -1
  70. package/dist/components/workflow-run-summary/workflow-run-summary.js +8 -13
  71. package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -1
  72. package/dist/components/workflow-run-tabs/index.d.ts +1 -1
  73. package/dist/components/workflow-run-tabs/index.d.ts.map +1 -1
  74. package/dist/components/workflow-run-tabs/index.js.map +1 -1
  75. package/dist/components/workflow-run-tabs/run-annotation-count-chip.js +2 -2
  76. package/dist/components/workflow-run-tabs/run-annotation-count-chip.js.map +1 -1
  77. package/dist/components/workflow-run-tabs/run-annotation-list.d.ts +9 -2
  78. package/dist/components/workflow-run-tabs/run-annotation-list.d.ts.map +1 -1
  79. package/dist/components/workflow-run-tabs/run-annotation-list.js +28 -19
  80. package/dist/components/workflow-run-tabs/run-annotation-list.js.map +1 -1
  81. package/dist/components/workflow-run-tabs/run-annotation-summary-line.d.ts.map +1 -1
  82. package/dist/components/workflow-run-tabs/run-annotation-summary-line.js +5 -5
  83. package/dist/components/workflow-run-tabs/run-annotation-summary-line.js.map +1 -1
  84. package/dist/components/workflow-run-view/run-workspace-nav.js +7 -7
  85. package/dist/components/workflow-run-view/run-workspace-nav.js.map +1 -1
  86. package/dist/components/workflow-run-view/workflow-run-states.js +6 -6
  87. package/dist/components/workflow-run-view/workflow-run-states.js.map +1 -1
  88. package/dist/components/workflow-run-view/workflow-run-view.d.ts.map +1 -1
  89. package/dist/components/workflow-run-view/workflow-run-view.js +72 -12
  90. package/dist/components/workflow-run-view/workflow-run-view.js.map +1 -1
  91. package/dist/core/entities/job-execution.d.ts +7 -0
  92. package/dist/core/entities/job-execution.d.ts.map +1 -1
  93. package/dist/core/entities/job-execution.js.map +1 -1
  94. package/dist/core/entities/job.d.ts +9 -0
  95. package/dist/core/entities/job.d.ts.map +1 -1
  96. package/dist/core/entities/job.js.map +1 -1
  97. package/dist/core/entities/step-attempt.d.ts +29 -0
  98. package/dist/core/entities/step-attempt.d.ts.map +1 -1
  99. package/dist/core/entities/step-attempt.js.map +1 -1
  100. package/dist/core/entities/step.d.ts +3 -1
  101. package/dist/core/entities/step.d.ts.map +1 -1
  102. package/dist/core/entities/step.js.map +1 -1
  103. package/dist/core/run-annotation.d.ts +6 -0
  104. package/dist/core/run-annotation.d.ts.map +1 -1
  105. package/dist/core/run-annotation.js.map +1 -1
  106. package/dist/core/workflow-run.d.ts +1 -1
  107. package/dist/core/workflow-run.d.ts.map +1 -1
  108. package/dist/core/workflow-run.js.map +1 -1
  109. package/dist/hooks/api/annotations.d.ts +63 -0
  110. package/dist/hooks/api/annotations.d.ts.map +1 -0
  111. package/dist/hooks/api/annotations.js +145 -0
  112. package/dist/hooks/api/annotations.js.map +1 -0
  113. package/dist/hooks/api/step-attempt-detail.d.ts +14 -0
  114. package/dist/hooks/api/step-attempt-detail.d.ts.map +1 -0
  115. package/dist/hooks/api/step-attempt-detail.js +43 -0
  116. package/dist/hooks/api/step-attempt-detail.js.map +1 -0
  117. package/dist/hooks/api/workflow-run-mapper.d.ts +9 -2
  118. package/dist/hooks/api/workflow-run-mapper.d.ts.map +1 -1
  119. package/dist/hooks/api/workflow-run-mapper.js +47 -0
  120. package/dist/hooks/api/workflow-run-mapper.js.map +1 -1
  121. package/dist/pages/project-workflows-page.js +19 -19
  122. package/dist/pages/project-workflows-page.js.map +1 -1
  123. package/dist/pages/workflow-run-list-page.js +1 -1
  124. package/dist/pages/workflow-run-list-page.js.map +1 -1
  125. package/dist/tsconfig.test.tsbuildinfo +1 -1
  126. package/package.json +10 -10
  127. package/src/components/identifier/identifier.tsx +2 -2
  128. package/src/components/job-detail/agent-config-failure-callout.tsx +5 -1
  129. package/src/components/job-detail/job-context-panel.test.tsx +72 -0
  130. package/src/components/job-detail/job-context-panel.tsx +237 -0
  131. package/src/components/job-detail/job-detail-header.tsx +52 -46
  132. package/src/components/job-detail/job-detail-view.stories.tsx +555 -53
  133. package/src/components/job-detail/job-detail-view.tsx +87 -45
  134. package/src/components/job-detail/job-empty-states.tsx +23 -10
  135. package/src/components/job-detail/job-execution-switcher.tsx +5 -5
  136. package/src/components/job-detail/json-code.tsx +120 -0
  137. package/src/components/job-detail/step-attempt-log-panel.test.tsx +1 -0
  138. package/src/components/job-detail/step-attempt-log-panel.tsx +17 -6
  139. package/src/components/job-detail/step-troubleshooting.stories.tsx +120 -0
  140. package/src/components/job-detail/step-troubleshooting.test.tsx +337 -0
  141. package/src/components/job-detail/step-troubleshooting.tsx +537 -0
  142. package/src/components/job-graph/job-graph-content.tsx +4 -3
  143. package/src/components/job-graph/job-node.tsx +3 -3
  144. package/src/components/step-list/index.ts +1 -0
  145. package/src/components/step-list/step-list-model.ts +5 -0
  146. package/src/components/step-list/step-list.test.tsx +75 -2
  147. package/src/components/step-list/step-list.tsx +73 -20
  148. package/src/components/workflow-run-duration-label.tsx +1 -1
  149. package/src/components/workflow-run-list/job-status-strip.tsx +6 -6
  150. package/src/components/workflow-run-list/workflow-run-filter-menu.tsx +1 -1
  151. package/src/components/workflow-run-list/workflow-run-filters.tsx +4 -4
  152. package/src/components/workflow-run-list/workflow-run-list-states.tsx +6 -6
  153. package/src/components/workflow-run-list/workflow-run-list-view.tsx +1 -1
  154. package/src/components/workflow-run-list/workflow-run-row.tsx +7 -7
  155. package/src/components/workflow-run-summary/workflow-run-attempt-switcher.tsx +1 -1
  156. package/src/components/workflow-run-summary/workflow-run-summary.test.tsx +1 -1
  157. package/src/components/workflow-run-summary/workflow-run-summary.tsx +11 -11
  158. package/src/components/workflow-run-tabs/index.ts +5 -1
  159. package/src/components/workflow-run-tabs/run-annotation-count-chip.tsx +2 -2
  160. package/src/components/workflow-run-tabs/run-annotation-list.tsx +25 -10
  161. package/src/components/workflow-run-tabs/run-annotation-summary-line.tsx +5 -5
  162. package/src/components/workflow-run-view/run-workspace-nav.tsx +7 -7
  163. package/src/components/workflow-run-view/workflow-run-states.tsx +6 -6
  164. package/src/components/workflow-run-view/workflow-run-view.tsx +114 -14
  165. package/src/core/entities/job-execution.ts +7 -0
  166. package/src/core/entities/job.ts +9 -0
  167. package/src/core/entities/step-attempt.ts +33 -0
  168. package/src/core/entities/step.ts +3 -1
  169. package/src/core/run-annotation.ts +6 -0
  170. package/src/core/workflow-run.ts +4 -0
  171. package/src/hooks/api/annotations.test.ts +84 -0
  172. package/src/hooks/api/annotations.ts +240 -0
  173. package/src/hooks/api/step-attempt-detail.ts +67 -0
  174. package/src/hooks/api/workflow-run-mapper.ts +46 -0
  175. package/src/hooks/api/workflow-runs.test.tsx +61 -1
  176. package/src/pages/project-workflows-page.tsx +19 -19
  177. package/src/pages/workflow-job-detail-page.test.tsx +52 -1
  178. package/src/pages/workflow-run-list-page.tsx +1 -1
  179. package/test/fixtures/workflow-run.ts +7 -0
  180. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/job-detail/job-detail-header.tsx"],"sourcesContent":["import {Icon} from '@shipfox/react-ui/icon';\nimport {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Code, Text} from '@shipfox/react-ui/typography';\nimport {formatTimestamp} from '@shipfox/react-ui/utils';\nimport {getWorkflowStatusVisual} from '#components/workflow-status/status-visuals.js';\nimport {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';\nimport type {RunAnnotationSummary} from '#core/run-annotation.js';\nimport {\n defaultJobExecution,\n deriveJobDisplayStatus,\n deriveJobExecutionDisplayStatus,\n type Job,\n type JobExecution,\n} from '#core/workflow-run.js';\nimport {RunAnnotationCountChip} from '../workflow-run-tabs/index.js';\nimport {JobExecutionSwitcher} from './job-execution-switcher.js';\nimport {JobExecutionTimeText} from './job-execution-time-text.js';\n\nexport interface JobDetailHeaderProps {\n job: Job;\n selectedJobExecution: JobExecution | undefined;\n onSelectedJobExecutionChange: (jobExecutionId: string) => void;\n workspaceSlug: string;\n projectSlug: string;\n workflowRunId: string;\n runAttempt?: number | undefined;\n /** Counts for this job only. Renders a link into the run's Annotations section, never a body. */\n annotationSummary?: RunAnnotationSummary | undefined;\n}\n\nexport function JobDetailHeader({\n job,\n selectedJobExecution,\n onSelectedJobExecutionChange,\n workspaceSlug,\n projectSlug,\n workflowRunId,\n runAttempt,\n annotationSummary,\n}: JobDetailHeaderProps) {\n const selectedStatus = selectedExecutionStatus(job, selectedJobExecution);\n const jobStatus = getWorkflowStatusVisual(selectedStatus);\n\n return (\n <header className=\"border-b border-border-neutral-base px-16 py-12\">\n <div className=\"flex min-w-0 flex-col gap-8\">\n <div className=\"flex min-w-0 items-center gap-8\">\n <WorkflowStatusIcon\n status={selectedStatus}\n size={14}\n tooltip\n ariaLabel={`Job status: ${jobStatus.label}`}\n />\n <Code\n as=\"h2\"\n variant=\"paragraph\"\n bold\n tabIndex={-1}\n className=\"min-w-0 truncate text-lg leading-24 text-foreground-neutral-base outline-none\"\n data-job-heading\n >\n {job.displayName}\n </Code>\n </div>\n\n {selectedJobExecution || annotationSummary?.total ? (\n <div className=\"flex min-w-0 flex-wrap items-center gap-10 text-foreground-neutral-muted\">\n {selectedJobExecution && job.executionCountVisible ? (\n <JobExecutionSwitcher\n job={job}\n selectedJobExecution={selectedJobExecution.id}\n onSelectedJobExecutionChange={onSelectedJobExecutionChange}\n variant=\"title\"\n />\n ) : null}\n {selectedJobExecution ? (\n <>\n <JobDurationMeta execution={selectedJobExecution} kind=\"queue\" />\n <JobDurationMeta execution={selectedJobExecution} kind=\"run\" />\n </>\n ) : null}\n <RunAnnotationCountChip\n summary={annotationSummary}\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n workflowRunId={workflowRunId}\n runAttempt={runAttempt}\n jobId={job.id}\n />\n </div>\n ) : null}\n </div>\n </header>\n );\n}\n\nfunction selectedExecutionStatus(job: Job, execution: JobExecution | undefined) {\n if (!execution) return deriveJobDisplayStatus(job);\n const defaultExecution = defaultJobExecution(job);\n return execution.id === defaultExecution?.id\n ? deriveJobDisplayStatus(job)\n : deriveJobExecutionDisplayStatus(execution);\n}\n\nfunction JobDurationMeta({execution, kind}: {execution: JobExecution; kind: 'queue' | 'run'}) {\n const time = kind === 'queue' ? execution.queueTime : execution.runTime;\n const from = kind === 'queue' ? execution.queuedAt : execution.startedAt;\n if (!time || !from) return null;\n\n const to = kind === 'queue' ? execution.startedAt : execution.finishedAt;\n const live = time.state === 'live';\n const label = kind === 'queue' ? 'queued' : live ? 'running' : 'ran';\n const tooltipLabel = kind === 'queue' ? 'Queued' : live ? 'Running' : 'Ran';\n const tooltip = `${tooltipLabel} ${formatTimestamp(from)}${to ? ` – ${formatTimestamp(to)}` : ' – now'}`;\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <span className=\"inline-flex items-center gap-4 whitespace-nowrap font-code text-xs leading-20 tabular-nums\">\n <Icon\n name={kind === 'queue' ? 'hourglassLine' : 'timerLine'}\n size={12}\n aria-hidden=\"true\"\n />\n <span>{label} </span>\n <JobExecutionTimeText time={time} />\n </span>\n </TooltipTrigger>\n <TooltipContent>\n <Text as=\"span\" size=\"xs\">\n {tooltip}\n </Text>\n </TooltipContent>\n </Tooltip>\n );\n}\n"],"names":["Icon","Tooltip","TooltipContent","TooltipTrigger","Code","Text","formatTimestamp","getWorkflowStatusVisual","WorkflowStatusIcon","defaultJobExecution","deriveJobDisplayStatus","deriveJobExecutionDisplayStatus","RunAnnotationCountChip","JobExecutionSwitcher","JobExecutionTimeText","JobDetailHeader","job","selectedJobExecution","onSelectedJobExecutionChange","workspaceSlug","projectSlug","workflowRunId","runAttempt","annotationSummary","selectedStatus","selectedExecutionStatus","jobStatus","header","className","div","status","size","tooltip","ariaLabel","label","as","variant","bold","tabIndex","data-job-heading","displayName","total","executionCountVisible","id","JobDurationMeta","execution","kind","summary","jobId","defaultExecution","time","queueTime","runTime","from","queuedAt","startedAt","to","finishedAt","live","state","tooltipLabel","asChild","span","name","aria-hidden"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/components/job-detail/job-detail-header.tsx"],"sourcesContent":["import {Icon} from '@shipfox/react-ui/icon';\nimport {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Code, Text} from '@shipfox/react-ui/typography';\nimport {formatTimestamp} from '@shipfox/react-ui/utils';\nimport type {ReactNode} from 'react';\nimport {getWorkflowStatusVisual} from '#components/workflow-status/status-visuals.js';\nimport {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';\nimport type {RunAnnotationSummary} from '#core/run-annotation.js';\nimport {\n defaultJobExecution,\n deriveJobDisplayStatus,\n deriveJobExecutionDisplayStatus,\n type Job,\n type JobExecution,\n} from '#core/workflow-run.js';\nimport {RunAnnotationCountChip} from '../workflow-run-tabs/index.js';\nimport {JobExecutionSwitcher} from './job-execution-switcher.js';\nimport {JobExecutionTimeText} from './job-execution-time-text.js';\n\nexport interface JobDetailHeaderProps {\n job: Job;\n selectedJobExecution: JobExecution | undefined;\n onSelectedJobExecutionChange: (jobExecutionId: string) => void;\n workspaceSlug: string;\n projectSlug: string;\n workflowRunId: string;\n runAttempt?: number | undefined;\n /** Counts for this job only. Renders a link into the run's Annotations section, never a body. */\n annotationSummary?: RunAnnotationSummary | undefined;\n jobContext?: ReactNode;\n}\n\nexport function JobDetailHeader({\n job,\n selectedJobExecution,\n onSelectedJobExecutionChange,\n workspaceSlug,\n projectSlug,\n workflowRunId,\n runAttempt,\n annotationSummary,\n jobContext,\n}: JobDetailHeaderProps) {\n const selectedStatus = selectedExecutionStatus(job, selectedJobExecution);\n const jobStatus = getWorkflowStatusVisual(selectedStatus);\n\n return (\n <header className=\"border-b border-border-neutral-base px-row py-row\">\n <div className=\"flex min-w-0 items-start justify-between gap-cluster\">\n <div className=\"flex min-w-0 flex-col gap-inline\">\n <div className=\"flex min-w-0 items-center gap-inline\">\n <WorkflowStatusIcon\n status={selectedStatus}\n size={14}\n tooltip\n ariaLabel={`Job status: ${jobStatus.label}`}\n />\n <Code\n as=\"h1\"\n variant=\"paragraph\"\n bold\n tabIndex={-1}\n className=\"min-w-0 truncate text-lg leading-24 text-foreground-neutral-base outline-none\"\n data-job-heading\n >\n {job.displayName}\n </Code>\n </div>\n\n {selectedJobExecution || annotationSummary?.total ? (\n <div className=\"flex min-w-0 flex-wrap items-center gap-inline text-foreground-neutral-muted\">\n {selectedJobExecution && job.executionCountVisible ? (\n <JobExecutionSwitcher\n job={job}\n selectedJobExecution={selectedJobExecution.id}\n onSelectedJobExecutionChange={onSelectedJobExecutionChange}\n variant=\"title\"\n />\n ) : null}\n {selectedJobExecution ? (\n <>\n <JobDurationMeta execution={selectedJobExecution} kind=\"queue\" />\n <JobDurationMeta execution={selectedJobExecution} kind=\"run\" />\n </>\n ) : null}\n <RunAnnotationCountChip\n summary={annotationSummary}\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n workflowRunId={workflowRunId}\n runAttempt={runAttempt}\n jobId={job.id}\n />\n </div>\n ) : null}\n </div>\n {jobContext}\n </div>\n </header>\n );\n}\n\nfunction selectedExecutionStatus(job: Job, execution: JobExecution | undefined) {\n if (!execution) return deriveJobDisplayStatus(job);\n const defaultExecution = defaultJobExecution(job);\n return execution.id === defaultExecution?.id\n ? deriveJobDisplayStatus(job)\n : deriveJobExecutionDisplayStatus(execution);\n}\n\nfunction JobDurationMeta({execution, kind}: {execution: JobExecution; kind: 'queue' | 'run'}) {\n const time = kind === 'queue' ? execution.queueTime : execution.runTime;\n const from = kind === 'queue' ? execution.queuedAt : execution.startedAt;\n if (!time || !from) return null;\n\n const to = kind === 'queue' ? execution.startedAt : execution.finishedAt;\n const live = time.state === 'live';\n const label = kind === 'queue' ? 'queued' : live ? 'running' : 'ran';\n const tooltipLabel = kind === 'queue' ? 'Queued' : live ? 'Running' : 'Ran';\n const tooltip = `${tooltipLabel} ${formatTimestamp(from)}${to ? ` – ${formatTimestamp(to)}` : ' – now'}`;\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <span className=\"inline-flex items-center gap-tight whitespace-nowrap font-code text-xs leading-20 tabular-nums\">\n <Icon\n name={kind === 'queue' ? 'hourglassLine' : 'timerLine'}\n size={12}\n aria-hidden=\"true\"\n />\n <span>{label} </span>\n <JobExecutionTimeText time={time} />\n </span>\n </TooltipTrigger>\n <TooltipContent>\n <Text as=\"span\" size=\"xs\">\n {tooltip}\n </Text>\n </TooltipContent>\n </Tooltip>\n );\n}\n"],"names":["Icon","Tooltip","TooltipContent","TooltipTrigger","Code","Text","formatTimestamp","getWorkflowStatusVisual","WorkflowStatusIcon","defaultJobExecution","deriveJobDisplayStatus","deriveJobExecutionDisplayStatus","RunAnnotationCountChip","JobExecutionSwitcher","JobExecutionTimeText","JobDetailHeader","job","selectedJobExecution","onSelectedJobExecutionChange","workspaceSlug","projectSlug","workflowRunId","runAttempt","annotationSummary","jobContext","selectedStatus","selectedExecutionStatus","jobStatus","header","className","div","status","size","tooltip","ariaLabel","label","as","variant","bold","tabIndex","data-job-heading","displayName","total","executionCountVisible","id","JobDurationMeta","execution","kind","summary","jobId","defaultExecution","time","queueTime","runTime","from","queuedAt","startedAt","to","finishedAt","live","state","tooltipLabel","asChild","span","name","aria-hidden"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import type { Job } from '#core/workflow-run.js';
1
+ import { type Job } from '#core/workflow-run.js';
2
2
  import type { useWorkflowRunAttemptQuery } from '#hooks/api/workflow-runs.js';
3
3
  import { type WorkflowJobSearch } from '#routes/inputs.js';
4
4
  export interface JobDetailViewProps {
@@ -1 +1 @@
1
- {"version":3,"file":"job-detail-view.d.ts","sourceRoot":"","sources":["../../../src/components/job-detail/job-detail-view.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAC,GAAG,EAAe,MAAM,uBAAuB,CAAC;AAE7D,OAAO,KAAK,EAAC,0BAA0B,EAAC,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EACL,KAAK,iBAAiB,EAGvB,MAAM,mBAAmB,CAAC;AAuB3B,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,iBAAiB,CAAC;IAC1B,KAAK,EAAE,UAAU,CAAC,OAAO,0BAA0B,CAAC,CAAC;IACrD,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,EAAE,GAAG,GAAG,SAAS,CAAC;IAC3B,iBAAiB,EAAE,CAAC,SAAS,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC3D;AAED,wBAAgB,aAAa,CAAC,EAC5B,aAAa,EACb,WAAW,EACX,aAAa,EACb,KAAK,EACL,MAAM,EACN,KAAK,EACL,YAAY,EACZ,QAAQ,EACR,iBAAiB,GAClB,EAAE,kBAAkB,+BA2NpB"}
1
+ {"version":3,"file":"job-detail-view.d.ts","sourceRoot":"","sources":["../../../src/components/job-detail/job-detail-view.tsx"],"names":[],"mappings":"AAcA,OAAO,EAAwB,KAAK,GAAG,EAAoB,MAAM,uBAAuB,CAAC;AAGzF,OAAO,KAAK,EAAC,0BAA0B,EAAC,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EACL,KAAK,iBAAiB,EAGvB,MAAM,mBAAmB,CAAC;AAwB3B,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,iBAAiB,CAAC;IAC1B,KAAK,EAAE,UAAU,CAAC,OAAO,0BAA0B,CAAC,CAAC;IACrD,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,EAAE,GAAG,GAAG,SAAS,CAAC;IAC3B,iBAAiB,EAAE,CAAC,SAAS,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC3D;AAED,wBAAgB,aAAa,CAAC,EAC5B,aAAa,EACb,WAAW,EACX,aAAa,EACb,KAAK,EACL,MAAM,EACN,KAAK,EACL,YAAY,EACZ,QAAQ,EACR,iBAAiB,GAClB,EAAE,kBAAkB,+BAqQpB"}
@@ -9,23 +9,27 @@ import { Skeleton } from '@shipfox/react-ui/skeleton';
9
9
  import { TimeTickerProvider } from '@shipfox/react-ui/time-ticker';
10
10
  import { Text } from '@shipfox/react-ui/typography';
11
11
  import { Link } from '@tanstack/react-router';
12
- import { useEffect, useRef } from 'react';
12
+ import { useEffect, useRef, useState } from 'react';
13
13
  import { summarizeJobAnnotations } from '#core/run-annotation.js';
14
+ import { isWorkflowRunTerminal } from '#core/workflow-run.js';
15
+ import { useWorkflowRunAnnotationSummaryQuery } from '#hooks/api/annotations.js';
14
16
  import { useRunAnnotationsQuery } from '#hooks/api/run-annotations.js';
15
17
  import { workflowJobSearchParams, workflowRunSearchParams } from '#routes/inputs.js';
16
18
  import { StepList } from '../step-list/index.js';
17
19
  import { WorkflowRunNotFound, WorkflowRunStaleError } from '../workflow-run-view/workflow-run-states.js';
18
- import { AgentConfigFailureCallout } from './agent-config-failure-callout.js';
20
+ import { JobContextPanel } from './job-context-panel.js';
19
21
  import { JobDetailHeader } from './job-detail-header.js';
20
- import { CarriedOverStepPanel, emptyStateForJob, emptyStateForMissingExecution, isAgentConfigFailure, jobSucceededSummary, toSelectedAttemptError } from './job-empty-states.js';
22
+ import { CarriedOverStepPanel, emptyStateForJob, emptyStateForMissingExecution, jobSucceededSummary } from './job-empty-states.js';
21
23
  import { resolveWorkflowJobSelection, workflowJobLandingSelection } from './job-selection.js';
22
24
  import { StepAttemptLogPanel } from './step-attempt-log-panel.js';
25
+ import { StepInspectorSheet } from './step-troubleshooting.js';
23
26
  export function JobDetailView({ workspaceSlug, projectSlug, workflowRunId, jobId, search, query, newerAttempt, newerJob, onSelectionChange }) {
24
27
  const rootRef = useRef(null);
25
28
  const pageScrollRef = useRef(null);
26
29
  const landingSelectionRef = useRef(undefined);
27
30
  const hasLoadedData = query.data !== undefined;
28
- // Same query key as the run workspace, so this reads the cache instead of fetching again.
31
+ // Reuse the run workspace's bounded annotation read for the job header chip. The separate
32
+ // summary query below stays counts-only and is scoped to the inspector's selected execution.
29
33
  const annotations = useRunAnnotationsQuery({
30
34
  workflowRunId,
31
35
  runAttempt: query.data?.runAttempt.attempt
@@ -33,6 +37,21 @@ export function JobDetailView({ workspaceSlug, projectSlug, workflowRunId, jobId
33
37
  const jobAnnotationSummary = annotations.annotations ? summarizeJobAnnotations(annotations.annotations, jobId, {
34
38
  truncated: annotations.summary?.truncated ?? false
35
39
  }) : undefined;
40
+ const inspectorResetKey = `${jobId}:${search.jobExecutionId ?? ''}`;
41
+ const [inspectorState, setInspectorState] = useState(()=>({
42
+ key: inspectorResetKey,
43
+ attemptId: null
44
+ }));
45
+ const inspectorOpenAttemptId = inspectorState.key === inspectorResetKey ? inspectorState.attemptId : null;
46
+ const annotationJob = query.data?.jobs.find((candidate)=>candidate.id === jobId);
47
+ const annotationExecutionId = annotationJob ? resolveWorkflowJobSelection({
48
+ job: annotationJob,
49
+ selection: search
50
+ }).jobExecution?.id : search.jobExecutionId;
51
+ const annotationPolling = query.data ? !isWorkflowRunTerminal(query.data.runAttempt.status) : true;
52
+ const annotationSummaryQuery = useWorkflowRunAnnotationSummaryQuery(query.data?.id, query.data?.runAttempt.attempt, annotationExecutionId, {
53
+ polling: annotationPolling
54
+ });
36
55
  useEffect(()=>{
37
56
  if (!jobId || !hasLoadedData) return;
38
57
  const heading = rootRef.current?.querySelector('[data-job-heading]');
@@ -128,6 +147,12 @@ export function JobDetailView({ workspaceSlug, projectSlug, workflowRunId, jobId
128
147
  stepAttemptId: match.attemptId
129
148
  });
130
149
  }
150
+ function onInspectorOpenChange(attemptId) {
151
+ setInspectorState({
152
+ key: inspectorResetKey,
153
+ attemptId
154
+ });
155
+ }
131
156
  function retargetToRunningStep() {
132
157
  if (!runningSelection || !selectedJobExecution) return;
133
158
  onSelectionChange({
@@ -156,10 +181,10 @@ export function JobDetailView({ workspaceSlug, projectSlug, workflowRunId, jobId
156
181
  }) : null,
157
182
  /*#__PURE__*/ _jsx("div", {
158
183
  ref: pageScrollRef,
159
- className: "@container min-h-0 flex-1 overflow-auto pb-24",
184
+ className: "@container min-h-0 flex-1 overflow-auto pb-panel",
160
185
  children: /*#__PURE__*/ _jsxs("section", {
161
186
  "aria-label": `${job.displayName} logs`,
162
- className: "mx-auto flex w-full max-w-[1120px] flex-col gap-8 px-24",
187
+ className: "flex w-full flex-col",
163
188
  children: [
164
189
  /*#__PURE__*/ _jsxs("section", {
165
190
  className: "min-w-0 overflow-hidden",
@@ -170,12 +195,16 @@ export function JobDetailView({ workspaceSlug, projectSlug, workflowRunId, jobId
170
195
  onSelectedJobExecutionChange: selectExecution,
171
196
  workspaceSlug: workspaceSlug,
172
197
  projectSlug: projectSlug,
173
- workflowRunId: workflowRunId,
198
+ workflowRunId: run.id,
174
199
  runAttempt: run.runAttempt.attempt,
175
- annotationSummary: jobAnnotationSummary
200
+ annotationSummary: jobAnnotationSummary,
201
+ jobContext: selectedJobExecution ? /*#__PURE__*/ _jsx(JobContextPanel, {
202
+ job: job,
203
+ execution: selectedJobExecution
204
+ }) : undefined
176
205
  }),
177
206
  /*#__PURE__*/ _jsx(Text, {
178
- as: "h3",
207
+ as: "h2",
179
208
  className: "sr-only",
180
209
  children: "Logs"
181
210
  }),
@@ -185,14 +214,26 @@ export function JobDetailView({ workspaceSlug, projectSlug, workflowRunId, jobId
185
214
  selectedAttemptId: selectedAttemptId,
186
215
  defaultSelectedAttemptId: landingSelection?.attemptId,
187
216
  onSelectedAttemptChange: selectAttempt,
217
+ inspectorOpenAttemptId: inspectorOpenAttemptId,
218
+ onInspectorOpenChange: onInspectorOpenChange,
188
219
  autoSelectActiveAttempt: true,
189
220
  emptyState: emptyStateForJob(job, selectedJobExecution),
190
221
  showHeader: false,
191
- className: "rounded-none border-0",
222
+ className: "rounded-none border-0 bg-transparent",
192
223
  renderExpandedStep: (context)=>/*#__PURE__*/ _jsx(ExpandedStep, {
193
224
  context: context,
194
- workspaceSlug: workspaceSlug,
195
225
  pageScrollRef: pageScrollRef
226
+ }),
227
+ renderInspector: (entry)=>/*#__PURE__*/ _jsx(StepInspectorSheet, {
228
+ entry: entry,
229
+ open: true,
230
+ onOpenChange: (open)=>onInspectorOpenChange(open ? entry.id : null),
231
+ workspaceSlug: workspaceSlug,
232
+ projectSlug: projectSlug,
233
+ workflowRunId: run.id,
234
+ runAttempt: run.runAttempt.attempt,
235
+ jobId: job.id,
236
+ annotationCount: annotationCountForStep(annotationSummaryQuery.data, entry.step.id, entry.attempt)
196
237
  })
197
238
  }) : /*#__PURE__*/ _jsx(EmptyStateForMissingExecution, {
198
239
  job: job
@@ -202,7 +243,7 @@ export function JobDetailView({ workspaceSlug, projectSlug, workflowRunId, jobId
202
243
  showRetargetNotice && runningSelection ? /*#__PURE__*/ _jsxs("div", {
203
244
  role: "status",
204
245
  "aria-live": "polite",
205
- className: "flex min-w-0 items-center justify-between gap-8 rounded-8 border border-border-neutral-base bg-background-components-base px-10 py-8",
246
+ className: "flex min-w-0 items-center justify-between gap-inline border-t border-border-neutral-base px-row py-row",
206
247
  children: [
207
248
  /*#__PURE__*/ _jsxs(Text, {
208
249
  size: "xs",
@@ -219,7 +260,7 @@ export function JobDetailView({ workspaceSlug, projectSlug, workflowRunId, jobId
219
260
  }),
220
261
  /*#__PURE__*/ _jsx("button", {
221
262
  type: "button",
222
- className: "shrink-0 rounded-4 px-6 py-4 text-xs font-medium text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active",
263
+ className: "shrink-0 rounded-4 px-tight py-[4px] text-xs font-medium text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active",
223
264
  onClick: retargetToRunningStep,
224
265
  children: "Jump to it"
225
266
  })
@@ -227,7 +268,7 @@ export function JobDetailView({ workspaceSlug, projectSlug, workflowRunId, jobId
227
268
  }) : null,
228
269
  succeededSummary ? /*#__PURE__*/ _jsx(Text, {
229
270
  size: "xs",
230
- className: "px-8 text-foreground-neutral-muted",
271
+ className: "px-row py-row text-foreground-neutral-muted",
231
272
  children: succeededSummary
232
273
  }) : null
233
274
  ]
@@ -237,35 +278,27 @@ export function JobDetailView({ workspaceSlug, projectSlug, workflowRunId, jobId
237
278
  })
238
279
  });
239
280
  }
240
- function ExpandedStep({ context, workspaceSlug, pageScrollRef }) {
281
+ function ExpandedStep({ context, pageScrollRef }) {
241
282
  if (context.carriedOver) return /*#__PURE__*/ _jsx(CarriedOverStepPanel, {});
242
- const selectedAttemptError = toSelectedAttemptError(context.step, context.attemptError) ?? context.step.error;
243
- return /*#__PURE__*/ _jsxs("section", {
283
+ return /*#__PURE__*/ _jsx("section", {
244
284
  role: "region",
245
285
  tabIndex: 0,
246
286
  "aria-label": `${context.stepLabel} output, attempt ${context.attempt}`,
247
- className: "flex min-w-0 flex-col gap-10 border-t border-border-neutral-base bg-background-neutral-subtle px-16 py-12 outline-none focus-visible:shadow-border-interactive-with-active",
248
- children: [
249
- isAgentConfigFailure(context.step, selectedAttemptError) ? /*#__PURE__*/ _jsx(AgentConfigFailureCallout, {
250
- workspaceSlug: workspaceSlug,
251
- config: context.step.agentConfig,
252
- error: selectedAttemptError
253
- }) : null,
254
- /*#__PURE__*/ _jsx(StepAttemptLogPanel, {
255
- stepId: context.stepId,
256
- attempt: context.attempt,
257
- attemptStatus: context.attemptStatus,
258
- attemptStartedAt: context.attemptStartedAt,
259
- pageScrollRef: pageScrollRef,
260
- surfaceClassName: "rounded-8"
261
- })
262
- ]
287
+ className: "flex min-w-0 flex-col border-t border-border-neutral-base bg-background-neutral-base outline-none focus-visible:shadow-border-interactive-with-active dark:bg-background-contrast-subtle",
288
+ children: /*#__PURE__*/ _jsx(StepAttemptLogPanel, {
289
+ stepId: context.stepId,
290
+ attempt: context.attempt,
291
+ attemptStatus: context.attemptStatus,
292
+ attemptStartedAt: context.attemptStartedAt,
293
+ pageScrollRef: pageScrollRef,
294
+ surfaceClassName: "rounded-none border-0 bg-transparent shadow-none"
295
+ })
263
296
  });
264
297
  }
265
298
  function EmptyStateForMissingExecution({ job }) {
266
299
  const emptyState = emptyStateForMissingExecution(job);
267
300
  return /*#__PURE__*/ _jsx(EmptyState, {
268
- className: "min-h-120 px-16 py-20",
301
+ className: "min-h-120 p-panel",
269
302
  icon: "componentLine",
270
303
  title: emptyState.title,
271
304
  description: emptyState.description,
@@ -274,7 +307,7 @@ function EmptyStateForMissingExecution({ job }) {
274
307
  }
275
308
  function JobNotFoundState({ workspaceSlug, projectSlug, workflowRunId, search }) {
276
309
  return /*#__PURE__*/ _jsxs("div", {
277
- className: "flex min-h-0 min-w-0 flex-1 flex-col items-center justify-center gap-12 p-24",
310
+ className: "flex min-h-0 min-w-0 flex-1 flex-col items-center justify-center gap-cluster p-panel",
278
311
  children: [
279
312
  /*#__PURE__*/ _jsx(EmptyState, {
280
313
  icon: "componentLine",
@@ -293,7 +326,7 @@ function JobNotFoundState({ workspaceSlug, projectSlug, workflowRunId, search })
293
326
  }, {
294
327
  runAttempt: search.runAttempt
295
328
  }),
296
- className: "inline-flex items-center gap-6 rounded-6 px-8 py-6 text-sm text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active",
329
+ className: "inline-flex items-center gap-inline rounded-6 px-tight py-[6px] text-sm text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active",
297
330
  children: [
298
331
  /*#__PURE__*/ _jsx(Icon, {
299
332
  name: "arrowLeftLine",
@@ -309,9 +342,9 @@ function JobNotFoundState({ workspaceSlug, projectSlug, workflowRunId, search })
309
342
  function NewerAttemptNotice({ workspaceSlug, projectSlug, runId, jobId, attempt }) {
310
343
  return /*#__PURE__*/ _jsx("div", {
311
344
  role: "status",
312
- className: "border-b border-border-neutral-base px-16 py-8",
345
+ className: "border-b border-border-neutral-base px-row py-row",
313
346
  children: /*#__PURE__*/ _jsxs("div", {
314
- className: "mx-auto flex max-w-[1120px] items-center justify-between gap-8 rounded-8 border border-border-neutral-base bg-background-components-base px-10 py-8",
347
+ className: "flex items-center justify-between gap-inline",
315
348
  children: [
316
349
  /*#__PURE__*/ _jsx(Text, {
317
350
  size: "xs",
@@ -329,7 +362,7 @@ function NewerAttemptNotice({ workspaceSlug, projectSlug, runId, jobId, attempt
329
362
  search: workflowJobSearchParams({
330
363
  runAttempt: attempt
331
364
  }),
332
- className: "shrink-0 rounded-4 px-6 py-4 text-xs font-medium text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active",
365
+ className: "shrink-0 rounded-4 px-tight py-[4px] text-xs font-medium text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active",
333
366
  children: [
334
367
  "View attempt #",
335
368
  attempt
@@ -368,14 +401,17 @@ function findAttempt(jobExecution, attemptId) {
368
401
  }
369
402
  return undefined;
370
403
  }
404
+ function annotationCountForStep(summary, stepId, attempt) {
405
+ return summary?.stepCounts?.find((entry)=>entry.stepId === stepId && entry.attempt === attempt)?.total;
406
+ }
371
407
  function JobDetailSkeleton() {
372
408
  return /*#__PURE__*/ _jsx("div", {
373
- className: "min-h-0 min-w-0 flex-1 overflow-auto px-24 pb-24",
409
+ className: "min-h-0 min-w-0 flex-1 overflow-auto pb-panel",
374
410
  children: /*#__PURE__*/ _jsxs("div", {
375
- className: "mx-auto max-w-[1120px] overflow-hidden",
411
+ className: "w-full",
376
412
  children: [
377
413
  /*#__PURE__*/ _jsxs("header", {
378
- className: "flex items-center gap-12 border-b border-border-neutral-base px-16 py-12",
414
+ className: "flex items-center gap-cluster border-b border-border-neutral-base px-row py-row",
379
415
  children: [
380
416
  /*#__PURE__*/ _jsx(Skeleton, {
381
417
  className: "size-20 rounded-full"
@@ -392,9 +428,9 @@ function JobDetailSkeleton() {
392
428
  ]
393
429
  }),
394
430
  /*#__PURE__*/ _jsx("div", {
395
- className: "p-16",
431
+ className: "p-panel-compact",
396
432
  children: JOB_DETAIL_SKELETON_ROWS.map((row)=>/*#__PURE__*/ _jsxs("div", {
397
- className: "flex min-h-44 items-center gap-8 border-b border-border-neutral-base last:border-b-0",
433
+ className: "flex min-h-44 items-center gap-inline border-b border-border-neutral-base last:border-b-0",
398
434
  children: [
399
435
  /*#__PURE__*/ _jsx(Skeleton, {
400
436
  className: "size-14 rounded-full"
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/job-detail/job-detail-view.tsx"],"sourcesContent":["// biome-ignore-all lint/a11y/noRedundantRoles: the job log region keeps an explicit role for the public accessibility contract.\n// biome-ignore-all lint/a11y/noNoninteractiveTabindex: the job log region is intentionally keyboard focusable.\n\nimport {ApiError} from '@shipfox/client-api';\nimport {QueryLoadError} from '@shipfox/client-ui';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {TimeTickerProvider} from '@shipfox/react-ui/time-ticker';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {Link} from '@tanstack/react-router';\nimport {type RefObject, useEffect, useRef} from 'react';\nimport {summarizeJobAnnotations} from '#core/run-annotation.js';\nimport type {Job, JobExecution} from '#core/workflow-run.js';\nimport {useRunAnnotationsQuery} from '#hooks/api/run-annotations.js';\nimport type {useWorkflowRunAttemptQuery} from '#hooks/api/workflow-runs.js';\nimport {\n type WorkflowJobSearch,\n workflowJobSearchParams,\n workflowRunSearchParams,\n} from '#routes/inputs.js';\nimport {type StepExpandedContext, StepList} from '../step-list/index.js';\nimport {\n WorkflowRunNotFound,\n WorkflowRunStaleError,\n} from '../workflow-run-view/workflow-run-states.js';\nimport {AgentConfigFailureCallout} from './agent-config-failure-callout.js';\nimport {JobDetailHeader} from './job-detail-header.js';\nimport {\n CarriedOverStepPanel,\n emptyStateForJob,\n emptyStateForMissingExecution,\n isAgentConfigFailure,\n jobSucceededSummary,\n toSelectedAttemptError,\n} from './job-empty-states.js';\nimport {\n resolveWorkflowJobSelection,\n type WorkflowJobLandingSelection,\n workflowJobLandingSelection,\n} from './job-selection.js';\nimport {StepAttemptLogPanel} from './step-attempt-log-panel.js';\n\nexport interface JobDetailViewProps {\n workspaceSlug: string;\n projectSlug: string;\n workflowRunId: string;\n jobId: string;\n search: WorkflowJobSearch;\n query: ReturnType<typeof useWorkflowRunAttemptQuery>;\n newerAttempt?: number | undefined;\n newerJob?: Job | undefined;\n onSelectionChange: (selection: WorkflowJobSearch) => void;\n}\n\nexport function JobDetailView({\n workspaceSlug,\n projectSlug,\n workflowRunId,\n jobId,\n search,\n query,\n newerAttempt,\n newerJob,\n onSelectionChange,\n}: JobDetailViewProps) {\n const rootRef = useRef<HTMLDivElement>(null);\n const pageScrollRef = useRef<HTMLDivElement>(null);\n const landingSelectionRef = useRef<FrozenLandingSelection | undefined>(undefined);\n const hasLoadedData = query.data !== undefined;\n // Same query key as the run workspace, so this reads the cache instead of fetching again.\n const annotations = useRunAnnotationsQuery({\n workflowRunId,\n runAttempt: query.data?.runAttempt.attempt,\n });\n const jobAnnotationSummary = annotations.annotations\n ? summarizeJobAnnotations(annotations.annotations, jobId, {\n truncated: annotations.summary?.truncated ?? false,\n })\n : undefined;\n\n useEffect(() => {\n if (!jobId || !hasLoadedData) return;\n const heading = rootRef.current?.querySelector<HTMLElement>('[data-job-heading]');\n // The heading is intentionally the route-change focus target: it announces the new job\n // after rail navigation without putting focus on a decorative status element.\n heading?.focus({preventScroll: true});\n }, [hasLoadedData, jobId]);\n\n if (query.isPending) return <JobDetailSkeleton />;\n\n if (query.isError && query.data === undefined) {\n if (query.error instanceof ApiError && query.error.status === 404) {\n return <WorkflowRunNotFound />;\n }\n return <QueryLoadError query={query} subject=\"workflow run\" icon=\"pulseLine\" />;\n }\n\n if (query.data === undefined) return <JobDetailSkeleton />;\n\n const run = query.data;\n const job = run.jobs.find((candidate) => candidate.id === jobId);\n if (!job) {\n return (\n <JobNotFoundState\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n workflowRunId={workflowRunId}\n search={search}\n />\n );\n }\n\n const resolvedSelection = resolveWorkflowJobSelection({job, selection: search});\n const selectedJobExecution = resolvedSelection.jobExecution;\n const hasExplicitStep = Boolean(search.stepId && resolvedSelection.step);\n const currentLandingSelection = workflowJobLandingSelection(selectedJobExecution);\n if (selectedJobExecution) {\n const frozenLanding = landingSelectionRef.current;\n if (\n !frozenLanding ||\n frozenLanding.jobId !== job.id ||\n frozenLanding.jobExecutionId !== selectedJobExecution.id\n ) {\n landingSelectionRef.current = {\n jobId: job.id,\n jobExecutionId: selectedJobExecution.id,\n selection: currentLandingSelection,\n hasSelection: currentLandingSelection !== undefined,\n };\n } else if (!frozenLanding.hasSelection && currentLandingSelection) {\n landingSelectionRef.current = {\n ...frozenLanding,\n selection: currentLandingSelection,\n hasSelection: true,\n };\n }\n } else {\n landingSelectionRef.current = undefined;\n }\n const landingSelection = landingSelectionRef.current?.selection;\n const selectedAttemptId = hasExplicitStep ? resolvedSelection.selectedAttemptId : undefined;\n const runningSelection = runningStepSelection(selectedJobExecution);\n const selectedStepId = resolvedSelection.step?.id ?? landingSelection?.stepId;\n const selectedAttemptForNotice = hasExplicitStep\n ? resolvedSelection.selectedAttemptId\n : landingSelection?.attemptId;\n const showRetargetNotice =\n runningSelection !== undefined &&\n (selectedStepId !== runningSelection.stepId ||\n selectedAttemptForNotice !== runningSelection.attemptId);\n const succeededSummary =\n !hasExplicitStep && landingSelection === undefined && selectedJobExecution\n ? jobSucceededSummary(job, selectedJobExecution)\n : undefined;\n\n function selectExecution(jobExecutionId: string) {\n onSelectionChange({\n ...search,\n jobExecutionId,\n stepId: undefined,\n stepAttemptId: undefined,\n });\n }\n\n function selectAttempt(attemptId: string | undefined) {\n if (!selectedJobExecution) return;\n if (!attemptId) {\n onSelectionChange({\n ...search,\n jobExecutionId: selectedJobExecution.id,\n stepId: undefined,\n stepAttemptId: undefined,\n });\n return;\n }\n\n const match = findAttempt(selectedJobExecution, attemptId);\n if (!match) return;\n onSelectionChange({\n ...search,\n jobExecutionId: selectedJobExecution.id,\n stepId: match.step.id,\n stepAttemptId: match.attemptId,\n });\n }\n\n function retargetToRunningStep() {\n if (!runningSelection || !selectedJobExecution) return;\n onSelectionChange({\n ...search,\n jobExecutionId: selectedJobExecution.id,\n stepId: runningSelection.stepId,\n stepAttemptId: runningSelection.attemptId,\n });\n }\n\n return (\n <TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>\n <div ref={rootRef} className=\"flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden\">\n {query.isError ? <WorkflowRunStaleError query={query} /> : null}\n {newerJob && newerAttempt && newerAttempt > run.runAttempt.attempt ? (\n <NewerAttemptNotice\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n runId={run.id}\n jobId={newerJob.id}\n attempt={newerAttempt}\n />\n ) : null}\n <div ref={pageScrollRef} className=\"@container min-h-0 flex-1 overflow-auto pb-24\">\n <section\n aria-label={`${job.displayName} logs`}\n className=\"mx-auto flex w-full max-w-[1120px] flex-col gap-8 px-24\"\n >\n <section className=\"min-w-0 overflow-hidden\">\n <JobDetailHeader\n job={job}\n selectedJobExecution={selectedJobExecution}\n onSelectedJobExecutionChange={selectExecution}\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n workflowRunId={workflowRunId}\n runAttempt={run.runAttempt.attempt}\n annotationSummary={jobAnnotationSummary}\n />\n <Text as=\"h3\" className=\"sr-only\">\n Logs\n </Text>\n {selectedJobExecution ? (\n <StepList\n job={job}\n jobExecution={selectedJobExecution}\n selectedAttemptId={selectedAttemptId}\n defaultSelectedAttemptId={landingSelection?.attemptId}\n onSelectedAttemptChange={selectAttempt}\n autoSelectActiveAttempt\n emptyState={emptyStateForJob(job, selectedJobExecution)}\n showHeader={false}\n className=\"rounded-none border-0\"\n renderExpandedStep={(context) => (\n <ExpandedStep\n context={context}\n workspaceSlug={workspaceSlug}\n pageScrollRef={pageScrollRef}\n />\n )}\n />\n ) : (\n <EmptyStateForMissingExecution job={job} />\n )}\n </section>\n {showRetargetNotice && runningSelection ? (\n <div\n role=\"status\"\n aria-live=\"polite\"\n className=\"flex min-w-0 items-center justify-between gap-8 rounded-8 border border-border-neutral-base bg-background-components-base px-10 py-8\"\n >\n <Text size=\"xs\" className=\"min-w-0 text-foreground-neutral-muted\">\n Run moved on to{' '}\n <span className=\"font-code text-foreground-neutral-base\">\n {runningSelection.stepLabel}\n </span>\n .\n </Text>\n <button\n type=\"button\"\n className=\"shrink-0 rounded-4 px-6 py-4 text-xs font-medium text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active\"\n onClick={retargetToRunningStep}\n >\n Jump to it\n </button>\n </div>\n ) : null}\n {succeededSummary ? (\n <Text size=\"xs\" className=\"px-8 text-foreground-neutral-muted\">\n {succeededSummary}\n </Text>\n ) : null}\n </section>\n </div>\n </div>\n </TimeTickerProvider>\n );\n}\n\nfunction ExpandedStep({\n context,\n workspaceSlug,\n pageScrollRef,\n}: {\n context: StepExpandedContext;\n workspaceSlug: string;\n pageScrollRef: RefObject<HTMLDivElement | null>;\n}) {\n if (context.carriedOver) return <CarriedOverStepPanel />;\n\n const selectedAttemptError =\n toSelectedAttemptError(context.step, context.attemptError) ?? context.step.error;\n\n return (\n <section\n role=\"region\"\n tabIndex={0}\n aria-label={`${context.stepLabel} output, attempt ${context.attempt}`}\n className=\"flex min-w-0 flex-col gap-10 border-t border-border-neutral-base bg-background-neutral-subtle px-16 py-12 outline-none focus-visible:shadow-border-interactive-with-active\"\n >\n {isAgentConfigFailure(context.step, selectedAttemptError) ? (\n <AgentConfigFailureCallout\n workspaceSlug={workspaceSlug}\n config={context.step.agentConfig}\n error={selectedAttemptError}\n />\n ) : null}\n <StepAttemptLogPanel\n stepId={context.stepId}\n attempt={context.attempt}\n attemptStatus={context.attemptStatus}\n attemptStartedAt={context.attemptStartedAt}\n pageScrollRef={pageScrollRef}\n surfaceClassName=\"rounded-8\"\n />\n </section>\n );\n}\n\nfunction EmptyStateForMissingExecution({job}: {job: Job}) {\n const emptyState = emptyStateForMissingExecution(job);\n return (\n <EmptyState\n className=\"min-h-120 px-16 py-20\"\n icon=\"componentLine\"\n title={emptyState.title}\n description={emptyState.description}\n variant=\"compact\"\n />\n );\n}\n\nfunction JobNotFoundState({\n workspaceSlug,\n projectSlug,\n workflowRunId,\n search,\n}: {\n workspaceSlug: string;\n projectSlug: string;\n workflowRunId: string;\n search: WorkflowJobSearch;\n}) {\n return (\n <div className=\"flex min-h-0 min-w-0 flex-1 flex-col items-center justify-center gap-12 p-24\">\n <EmptyState\n icon=\"componentLine\"\n title=\"Job not found\"\n description=\"This job is not part of the selected workflow run.\"\n />\n <Link\n to=\"/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId\"\n params={{workspaceSlug, projectSlug, workflowRunId}}\n search={\n workflowRunSearchParams(\n {runAttempt: search.runAttempt},\n {runAttempt: search.runAttempt},\n ) as never\n }\n className=\"inline-flex items-center gap-6 rounded-6 px-8 py-6 text-sm text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active\"\n >\n <Icon name=\"arrowLeftLine\" size={14} aria-hidden=\"true\" />\n Back to run summary\n </Link>\n </div>\n );\n}\n\nfunction NewerAttemptNotice({\n workspaceSlug,\n projectSlug,\n runId,\n jobId,\n attempt,\n}: {\n workspaceSlug: string;\n projectSlug: string;\n runId: string;\n jobId: string;\n attempt: number;\n}) {\n return (\n <div role=\"status\" className=\"border-b border-border-neutral-base px-16 py-8\">\n <div className=\"mx-auto flex max-w-[1120px] items-center justify-between gap-8 rounded-8 border border-border-neutral-base bg-background-components-base px-10 py-8\">\n <Text size=\"xs\" className=\"text-foreground-neutral-muted\">\n A newer run attempt is available.\n </Text>\n <Link\n to=\"/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId/jobs/$jobId\"\n params={{workspaceSlug, projectSlug, workflowRunId: runId, jobId}}\n search={workflowJobSearchParams({runAttempt: attempt}) as never}\n className=\"shrink-0 rounded-4 px-6 py-4 text-xs font-medium text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active\"\n >\n View attempt #{attempt}\n </Link>\n </div>\n </div>\n );\n}\n\nfunction runningStepSelection(jobExecution: JobExecution | undefined) {\n if (!jobExecution) return undefined;\n const steps = [...jobExecution.steps].sort(\n (left, right) => left.position - right.position || left.id.localeCompare(right.id),\n );\n for (let stepIndex = steps.length - 1; stepIndex >= 0; stepIndex -= 1) {\n const step = steps[stepIndex];\n if (!step) continue;\n const attempt = [...step.attempts]\n .sort((left, right) => left.attempt - right.attempt || left.id.localeCompare(right.id))\n .reverse()\n .find((candidate) => candidate.status === 'running');\n if (attempt)\n return {stepId: step.id, attemptId: attempt.id, stepLabel: step.name || step.key || step.id};\n }\n return undefined;\n}\n\nfunction findAttempt(jobExecution: JobExecution, attemptId: string) {\n for (const step of jobExecution.steps) {\n const attempt = step.attempts.find((candidate) => candidate.id === attemptId);\n if (attempt) return {step, attemptId: attempt.id};\n }\n return undefined;\n}\n\nfunction JobDetailSkeleton() {\n return (\n <div className=\"min-h-0 min-w-0 flex-1 overflow-auto px-24 pb-24\">\n <div className=\"mx-auto max-w-[1120px] overflow-hidden\">\n <header className=\"flex items-center gap-12 border-b border-border-neutral-base px-16 py-12\">\n <Skeleton className=\"size-20 rounded-full\" />\n <Skeleton className=\"h-20 w-160 rounded-4\" />\n <Skeleton className=\"h-24 w-72 rounded-6\" />\n <Skeleton className=\"h-24 w-180 rounded-4\" />\n </header>\n <div className=\"p-16\">\n {JOB_DETAIL_SKELETON_ROWS.map((row) => (\n <div\n key={row}\n className=\"flex min-h-44 items-center gap-8 border-b border-border-neutral-base last:border-b-0\"\n >\n <Skeleton className=\"size-14 rounded-full\" />\n <Skeleton className=\"h-16 w-180 rounded-4\" />\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n\nconst JOB_DETAIL_SKELETON_ROWS = ['step-1', 'step-2', 'step-3', 'step-4', 'step-5'];\n\ninterface FrozenLandingSelection {\n jobId: string;\n jobExecutionId: string;\n selection: WorkflowJobLandingSelection | undefined;\n hasSelection: boolean;\n}\n"],"names":["ApiError","QueryLoadError","EmptyState","Icon","Skeleton","TimeTickerProvider","Text","Link","useEffect","useRef","summarizeJobAnnotations","useRunAnnotationsQuery","workflowJobSearchParams","workflowRunSearchParams","StepList","WorkflowRunNotFound","WorkflowRunStaleError","AgentConfigFailureCallout","JobDetailHeader","CarriedOverStepPanel","emptyStateForJob","emptyStateForMissingExecution","isAgentConfigFailure","jobSucceededSummary","toSelectedAttemptError","resolveWorkflowJobSelection","workflowJobLandingSelection","StepAttemptLogPanel","JobDetailView","workspaceSlug","projectSlug","workflowRunId","jobId","search","query","newerAttempt","newerJob","onSelectionChange","rootRef","pageScrollRef","landingSelectionRef","undefined","hasLoadedData","data","annotations","runAttempt","attempt","jobAnnotationSummary","truncated","summary","heading","current","querySelector","focus","preventScroll","isPending","JobDetailSkeleton","isError","error","status","subject","icon","run","job","jobs","find","candidate","id","JobNotFoundState","resolvedSelection","selection","selectedJobExecution","jobExecution","hasExplicitStep","Boolean","stepId","step","currentLandingSelection","frozenLanding","jobExecutionId","hasSelection","landingSelection","selectedAttemptId","runningSelection","runningStepSelection","selectedStepId","selectedAttemptForNotice","attemptId","showRetargetNotice","succeededSummary","selectExecution","stepAttemptId","selectAttempt","match","findAttempt","retargetToRunningStep","intervalMs","reducedMotionIntervalMs","div","ref","className","NewerAttemptNotice","runId","section","aria-label","displayName","onSelectedJobExecutionChange","annotationSummary","as","defaultSelectedAttemptId","onSelectedAttemptChange","autoSelectActiveAttempt","emptyState","showHeader","renderExpandedStep","context","ExpandedStep","EmptyStateForMissingExecution","role","aria-live","size","span","stepLabel","button","type","onClick","carriedOver","selectedAttemptError","attemptError","tabIndex","config","agentConfig","attemptStatus","attemptStartedAt","surfaceClassName","title","description","variant","to","params","name","aria-hidden","steps","sort","left","right","position","localeCompare","stepIndex","length","attempts","reverse","key","header","JOB_DETAIL_SKELETON_ROWS","map","row"],"mappings":";AAAA,gIAAgI;AAChI,+GAA+G;AAE/G,SAAQA,QAAQ,QAAO,sBAAsB;AAC7C,SAAQC,cAAc,QAAO,qBAAqB;AAClD,SAAQC,UAAU,QAAO,gCAAgC;AACzD,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,QAAQ,QAAO,6BAA6B;AACpD,SAAQC,kBAAkB,QAAO,gCAAgC;AACjE,SAAQC,IAAI,QAAO,+BAA+B;AAClD,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAwBC,SAAS,EAAEC,MAAM,QAAO,QAAQ;AACxD,SAAQC,uBAAuB,QAAO,0BAA0B;AAEhE,SAAQC,sBAAsB,QAAO,gCAAgC;AAErE,SAEEC,uBAAuB,EACvBC,uBAAuB,QAClB,oBAAoB;AAC3B,SAAkCC,QAAQ,QAAO,wBAAwB;AACzE,SACEC,mBAAmB,EACnBC,qBAAqB,QAChB,8CAA8C;AACrD,SAAQC,yBAAyB,QAAO,oCAAoC;AAC5E,SAAQC,eAAe,QAAO,yBAAyB;AACvD,SACEC,oBAAoB,EACpBC,gBAAgB,EAChBC,6BAA6B,EAC7BC,oBAAoB,EACpBC,mBAAmB,EACnBC,sBAAsB,QACjB,wBAAwB;AAC/B,SACEC,2BAA2B,EAE3BC,2BAA2B,QACtB,qBAAqB;AAC5B,SAAQC,mBAAmB,QAAO,8BAA8B;AAchE,OAAO,SAASC,cAAc,EAC5BC,aAAa,EACbC,WAAW,EACXC,aAAa,EACbC,KAAK,EACLC,MAAM,EACNC,KAAK,EACLC,YAAY,EACZC,QAAQ,EACRC,iBAAiB,EACE;IACnB,MAAMC,UAAU7B,OAAuB;IACvC,MAAM8B,gBAAgB9B,OAAuB;IAC7C,MAAM+B,sBAAsB/B,OAA2CgC;IACvE,MAAMC,gBAAgBR,MAAMS,IAAI,KAAKF;IACrC,0FAA0F;IAC1F,MAAMG,cAAcjC,uBAAuB;QACzCoB;QACAc,YAAYX,MAAMS,IAAI,EAAEE,WAAWC;IACrC;IACA,MAAMC,uBAAuBH,YAAYA,WAAW,GAChDlC,wBAAwBkC,YAAYA,WAAW,EAAEZ,OAAO;QACtDgB,WAAWJ,YAAYK,OAAO,EAAED,aAAa;IAC/C,KACAP;IAEJjC,UAAU;QACR,IAAI,CAACwB,SAAS,CAACU,eAAe;QAC9B,MAAMQ,UAAUZ,QAAQa,OAAO,EAAEC,cAA2B;QAC5D,uFAAuF;QACvF,8EAA8E;QAC9EF,SAASG,MAAM;YAACC,eAAe;QAAI;IACrC,GAAG;QAACZ;QAAeV;KAAM;IAEzB,IAAIE,MAAMqB,SAAS,EAAE,qBAAO,KAACC;IAE7B,IAAItB,MAAMuB,OAAO,IAAIvB,MAAMS,IAAI,KAAKF,WAAW;QAC7C,IAAIP,MAAMwB,KAAK,YAAY1D,YAAYkC,MAAMwB,KAAK,CAACC,MAAM,KAAK,KAAK;YACjE,qBAAO,KAAC5C;QACV;QACA,qBAAO,KAACd;YAAeiC,OAAOA;YAAO0B,SAAQ;YAAeC,MAAK;;IACnE;IAEA,IAAI3B,MAAMS,IAAI,KAAKF,WAAW,qBAAO,KAACe;IAEtC,MAAMM,MAAM5B,MAAMS,IAAI;IACtB,MAAMoB,MAAMD,IAAIE,IAAI,CAACC,IAAI,CAAC,CAACC,YAAcA,UAAUC,EAAE,KAAKnC;IAC1D,IAAI,CAAC+B,KAAK;QACR,qBACE,KAACK;YACCvC,eAAeA;YACfC,aAAaA;YACbC,eAAeA;YACfE,QAAQA;;IAGd;IAEA,MAAMoC,oBAAoB5C,4BAA4B;QAACsC;QAAKO,WAAWrC;IAAM;IAC7E,MAAMsC,uBAAuBF,kBAAkBG,YAAY;IAC3D,MAAMC,kBAAkBC,QAAQzC,OAAO0C,MAAM,IAAIN,kBAAkBO,IAAI;IACvE,MAAMC,0BAA0BnD,4BAA4B6C;IAC5D,IAAIA,sBAAsB;QACxB,MAAMO,gBAAgBtC,oBAAoBW,OAAO;QACjD,IACE,CAAC2B,iBACDA,cAAc9C,KAAK,KAAK+B,IAAII,EAAE,IAC9BW,cAAcC,cAAc,KAAKR,qBAAqBJ,EAAE,EACxD;YACA3B,oBAAoBW,OAAO,GAAG;gBAC5BnB,OAAO+B,IAAII,EAAE;gBACbY,gBAAgBR,qBAAqBJ,EAAE;gBACvCG,WAAWO;gBACXG,cAAcH,4BAA4BpC;YAC5C;QACF,OAAO,IAAI,CAACqC,cAAcE,YAAY,IAAIH,yBAAyB;YACjErC,oBAAoBW,OAAO,GAAG;gBAC5B,GAAG2B,aAAa;gBAChBR,WAAWO;gBACXG,cAAc;YAChB;QACF;IACF,OAAO;QACLxC,oBAAoBW,OAAO,GAAGV;IAChC;IACA,MAAMwC,mBAAmBzC,oBAAoBW,OAAO,EAAEmB;IACtD,MAAMY,oBAAoBT,kBAAkBJ,kBAAkBa,iBAAiB,GAAGzC;IAClF,MAAM0C,mBAAmBC,qBAAqBb;IAC9C,MAAMc,iBAAiBhB,kBAAkBO,IAAI,EAAET,MAAMc,kBAAkBN;IACvE,MAAMW,2BAA2Bb,kBAC7BJ,kBAAkBa,iBAAiB,GACnCD,kBAAkBM;IACtB,MAAMC,qBACJL,qBAAqB1C,aACpB4C,CAAAA,mBAAmBF,iBAAiBR,MAAM,IACzCW,6BAA6BH,iBAAiBI,SAAS,AAAD;IAC1D,MAAME,mBACJ,CAAChB,mBAAmBQ,qBAAqBxC,aAAa8B,uBAClDhD,oBAAoBwC,KAAKQ,wBACzB9B;IAEN,SAASiD,gBAAgBX,cAAsB;QAC7C1C,kBAAkB;YAChB,GAAGJ,MAAM;YACT8C;YACAJ,QAAQlC;YACRkD,eAAelD;QACjB;IACF;IAEA,SAASmD,cAAcL,SAA6B;QAClD,IAAI,CAAChB,sBAAsB;QAC3B,IAAI,CAACgB,WAAW;YACdlD,kBAAkB;gBAChB,GAAGJ,MAAM;gBACT8C,gBAAgBR,qBAAqBJ,EAAE;gBACvCQ,QAAQlC;gBACRkD,eAAelD;YACjB;YACA;QACF;QAEA,MAAMoD,QAAQC,YAAYvB,sBAAsBgB;QAChD,IAAI,CAACM,OAAO;QACZxD,kBAAkB;YAChB,GAAGJ,MAAM;YACT8C,gBAAgBR,qBAAqBJ,EAAE;YACvCQ,QAAQkB,MAAMjB,IAAI,CAACT,EAAE;YACrBwB,eAAeE,MAAMN,SAAS;QAChC;IACF;IAEA,SAASQ;QACP,IAAI,CAACZ,oBAAoB,CAACZ,sBAAsB;QAChDlC,kBAAkB;YAChB,GAAGJ,MAAM;YACT8C,gBAAgBR,qBAAqBJ,EAAE;YACvCQ,QAAQQ,iBAAiBR,MAAM;YAC/BgB,eAAeR,iBAAiBI,SAAS;QAC3C;IACF;IAEA,qBACE,KAAClF;QAAmB2F,YAAY;QAAMC,yBAAyB;kBAC7D,cAAA,MAACC;YAAIC,KAAK7D;YAAS8D,WAAU;;gBAC1BlE,MAAMuB,OAAO,iBAAG,KAACzC;oBAAsBkB,OAAOA;qBAAY;gBAC1DE,YAAYD,gBAAgBA,eAAe2B,IAAIjB,UAAU,CAACC,OAAO,iBAChE,KAACuD;oBACCxE,eAAeA;oBACfC,aAAaA;oBACbwE,OAAOxC,IAAIK,EAAE;oBACbnC,OAAOI,SAAS+B,EAAE;oBAClBrB,SAASX;qBAET;8BACJ,KAAC+D;oBAAIC,KAAK5D;oBAAe6D,WAAU;8BACjC,cAAA,MAACG;wBACCC,cAAY,GAAGzC,IAAI0C,WAAW,CAAC,KAAK,CAAC;wBACrCL,WAAU;;0CAEV,MAACG;gCAAQH,WAAU;;kDACjB,KAAClF;wCACC6C,KAAKA;wCACLQ,sBAAsBA;wCACtBmC,8BAA8BhB;wCAC9B7D,eAAeA;wCACfC,aAAaA;wCACbC,eAAeA;wCACfc,YAAYiB,IAAIjB,UAAU,CAACC,OAAO;wCAClC6D,mBAAmB5D;;kDAErB,KAACzC;wCAAKsG,IAAG;wCAAKR,WAAU;kDAAU;;oCAGjC7B,qCACC,KAACzD;wCACCiD,KAAKA;wCACLS,cAAcD;wCACdW,mBAAmBA;wCACnB2B,0BAA0B5B,kBAAkBM;wCAC5CuB,yBAAyBlB;wCACzBmB,uBAAuB;wCACvBC,YAAY5F,iBAAiB2C,KAAKQ;wCAClC0C,YAAY;wCACZb,WAAU;wCACVc,oBAAoB,CAACC,wBACnB,KAACC;gDACCD,SAASA;gDACTtF,eAAeA;gDACfU,eAAeA;;uDAKrB,KAAC8E;wCAA8BtD,KAAKA;;;;4BAGvCyB,sBAAsBL,iCACrB,MAACe;gCACCoB,MAAK;gCACLC,aAAU;gCACVnB,WAAU;;kDAEV,MAAC9F;wCAAKkH,MAAK;wCAAKpB,WAAU;;4CAAwC;4CAChD;0DAChB,KAACqB;gDAAKrB,WAAU;0DACbjB,iBAAiBuC,SAAS;;4CACtB;;;kDAGT,KAACC;wCACCC,MAAK;wCACLxB,WAAU;wCACVyB,SAAS9B;kDACV;;;iCAID;4BACHN,iCACC,KAACnF;gCAAKkH,MAAK;gCAAKpB,WAAU;0CACvBX;iCAED;;;;;;;AAMhB;AAEA,SAAS2B,aAAa,EACpBD,OAAO,EACPtF,aAAa,EACbU,aAAa,EAKd;IACC,IAAI4E,QAAQW,WAAW,EAAE,qBAAO,KAAC3G;IAEjC,MAAM4G,uBACJvG,uBAAuB2F,QAAQvC,IAAI,EAAEuC,QAAQa,YAAY,KAAKb,QAAQvC,IAAI,CAAClB,KAAK;IAElF,qBACE,MAAC6C;QACCe,MAAK;QACLW,UAAU;QACVzB,cAAY,GAAGW,QAAQO,SAAS,CAAC,iBAAiB,EAAEP,QAAQrE,OAAO,EAAE;QACrEsD,WAAU;;YAET9E,qBAAqB6F,QAAQvC,IAAI,EAAEmD,sCAClC,KAAC9G;gBACCY,eAAeA;gBACfqG,QAAQf,QAAQvC,IAAI,CAACuD,WAAW;gBAChCzE,OAAOqE;iBAEP;0BACJ,KAACpG;gBACCgD,QAAQwC,QAAQxC,MAAM;gBACtB7B,SAASqE,QAAQrE,OAAO;gBACxBsF,eAAejB,QAAQiB,aAAa;gBACpCC,kBAAkBlB,QAAQkB,gBAAgB;gBAC1C9F,eAAeA;gBACf+F,kBAAiB;;;;AAIzB;AAEA,SAASjB,8BAA8B,EAACtD,GAAG,EAAa;IACtD,MAAMiD,aAAa3F,8BAA8B0C;IACjD,qBACE,KAAC7D;QACCkG,WAAU;QACVvC,MAAK;QACL0E,OAAOvB,WAAWuB,KAAK;QACvBC,aAAaxB,WAAWwB,WAAW;QACnCC,SAAQ;;AAGd;AAEA,SAASrE,iBAAiB,EACxBvC,aAAa,EACbC,WAAW,EACXC,aAAa,EACbE,MAAM,EAMP;IACC,qBACE,MAACiE;QAAIE,WAAU;;0BACb,KAAClG;gBACC2D,MAAK;gBACL0E,OAAM;gBACNC,aAAY;;0BAEd,MAACjI;gBACCmI,IAAG;gBACHC,QAAQ;oBAAC9G;oBAAeC;oBAAaC;gBAAa;gBAClDE,QACEpB,wBACE;oBAACgC,YAAYZ,OAAOY,UAAU;gBAAA,GAC9B;oBAACA,YAAYZ,OAAOY,UAAU;gBAAA;gBAGlCuD,WAAU;;kCAEV,KAACjG;wBAAKyI,MAAK;wBAAgBpB,MAAM;wBAAIqB,eAAY;;oBAAS;;;;;AAKlE;AAEA,SAASxC,mBAAmB,EAC1BxE,aAAa,EACbC,WAAW,EACXwE,KAAK,EACLtE,KAAK,EACLc,OAAO,EAOR;IACC,qBACE,KAACoD;QAAIoB,MAAK;QAASlB,WAAU;kBAC3B,cAAA,MAACF;YAAIE,WAAU;;8BACb,KAAC9F;oBAAKkH,MAAK;oBAAKpB,WAAU;8BAAgC;;8BAG1D,MAAC7F;oBACCmI,IAAG;oBACHC,QAAQ;wBAAC9G;wBAAeC;wBAAaC,eAAeuE;wBAAOtE;oBAAK;oBAChEC,QAAQrB,wBAAwB;wBAACiC,YAAYC;oBAAO;oBACpDsD,WAAU;;wBACX;wBACgBtD;;;;;;AAKzB;AAEA,SAASsC,qBAAqBZ,YAAsC;IAClE,IAAI,CAACA,cAAc,OAAO/B;IAC1B,MAAMqG,QAAQ;WAAItE,aAAasE,KAAK;KAAC,CAACC,IAAI,CACxC,CAACC,MAAMC,QAAUD,KAAKE,QAAQ,GAAGD,MAAMC,QAAQ,IAAIF,KAAK7E,EAAE,CAACgF,aAAa,CAACF,MAAM9E,EAAE;IAEnF,IAAK,IAAIiF,YAAYN,MAAMO,MAAM,GAAG,GAAGD,aAAa,GAAGA,aAAa,EAAG;QACrE,MAAMxE,OAAOkE,KAAK,CAACM,UAAU;QAC7B,IAAI,CAACxE,MAAM;QACX,MAAM9B,UAAU;eAAI8B,KAAK0E,QAAQ;SAAC,CAC/BP,IAAI,CAAC,CAACC,MAAMC,QAAUD,KAAKlG,OAAO,GAAGmG,MAAMnG,OAAO,IAAIkG,KAAK7E,EAAE,CAACgF,aAAa,CAACF,MAAM9E,EAAE,GACpFoF,OAAO,GACPtF,IAAI,CAAC,CAACC,YAAcA,UAAUP,MAAM,KAAK;QAC5C,IAAIb,SACF,OAAO;YAAC6B,QAAQC,KAAKT,EAAE;YAAEoB,WAAWzC,QAAQqB,EAAE;YAAEuD,WAAW9C,KAAKgE,IAAI,IAAIhE,KAAK4E,GAAG,IAAI5E,KAAKT,EAAE;QAAA;IAC/F;IACA,OAAO1B;AACT;AAEA,SAASqD,YAAYtB,YAA0B,EAAEe,SAAiB;IAChE,KAAK,MAAMX,QAAQJ,aAAasE,KAAK,CAAE;QACrC,MAAMhG,UAAU8B,KAAK0E,QAAQ,CAACrF,IAAI,CAAC,CAACC,YAAcA,UAAUC,EAAE,KAAKoB;QACnE,IAAIzC,SAAS,OAAO;YAAC8B;YAAMW,WAAWzC,QAAQqB,EAAE;QAAA;IAClD;IACA,OAAO1B;AACT;AAEA,SAASe;IACP,qBACE,KAAC0C;QAAIE,WAAU;kBACb,cAAA,MAACF;YAAIE,WAAU;;8BACb,MAACqD;oBAAOrD,WAAU;;sCAChB,KAAChG;4BAASgG,WAAU;;sCACpB,KAAChG;4BAASgG,WAAU;;sCACpB,KAAChG;4BAASgG,WAAU;;sCACpB,KAAChG;4BAASgG,WAAU;;;;8BAEtB,KAACF;oBAAIE,WAAU;8BACZsD,yBAAyBC,GAAG,CAAC,CAACC,oBAC7B,MAAC1D;4BAECE,WAAU;;8CAEV,KAAChG;oCAASgG,WAAU;;8CACpB,KAAChG;oCAASgG,WAAU;;;2BAJfwD;;;;;AAWnB;AAEA,MAAMF,2BAA2B;IAAC;IAAU;IAAU;IAAU;IAAU;CAAS"}
1
+ {"version":3,"sources":["../../../src/components/job-detail/job-detail-view.tsx"],"sourcesContent":["// biome-ignore-all lint/a11y/noRedundantRoles: the job log region keeps an explicit role for the public accessibility contract.\n// biome-ignore-all lint/a11y/noNoninteractiveTabindex: the job log region is intentionally keyboard focusable.\n\nimport {ApiError} from '@shipfox/client-api';\nimport {QueryLoadError} from '@shipfox/client-ui';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {TimeTickerProvider} from '@shipfox/react-ui/time-ticker';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {Link} from '@tanstack/react-router';\nimport {type RefObject, useEffect, useRef, useState} from 'react';\nimport type {RunAnnotationSummary} from '#core/run-annotation.js';\nimport {summarizeJobAnnotations} from '#core/run-annotation.js';\nimport {isWorkflowRunTerminal, type Job, type JobExecution} from '#core/workflow-run.js';\nimport {useWorkflowRunAnnotationSummaryQuery} from '#hooks/api/annotations.js';\nimport {useRunAnnotationsQuery} from '#hooks/api/run-annotations.js';\nimport type {useWorkflowRunAttemptQuery} from '#hooks/api/workflow-runs.js';\nimport {\n type WorkflowJobSearch,\n workflowJobSearchParams,\n workflowRunSearchParams,\n} from '#routes/inputs.js';\nimport {type StepExpandedContext, StepList} from '../step-list/index.js';\nimport {\n WorkflowRunNotFound,\n WorkflowRunStaleError,\n} from '../workflow-run-view/workflow-run-states.js';\nimport {JobContextPanel} from './job-context-panel.js';\nimport {JobDetailHeader} from './job-detail-header.js';\nimport {\n CarriedOverStepPanel,\n emptyStateForJob,\n emptyStateForMissingExecution,\n jobSucceededSummary,\n} from './job-empty-states.js';\nimport {\n resolveWorkflowJobSelection,\n type WorkflowJobLandingSelection,\n workflowJobLandingSelection,\n} from './job-selection.js';\nimport {StepAttemptLogPanel} from './step-attempt-log-panel.js';\nimport {StepInspectorSheet} from './step-troubleshooting.js';\n\ntype InspectorState = {key: string; attemptId: string | null};\n\nexport interface JobDetailViewProps {\n workspaceSlug: string;\n projectSlug: string;\n workflowRunId: string;\n jobId: string;\n search: WorkflowJobSearch;\n query: ReturnType<typeof useWorkflowRunAttemptQuery>;\n newerAttempt?: number | undefined;\n newerJob?: Job | undefined;\n onSelectionChange: (selection: WorkflowJobSearch) => void;\n}\n\nexport function JobDetailView({\n workspaceSlug,\n projectSlug,\n workflowRunId,\n jobId,\n search,\n query,\n newerAttempt,\n newerJob,\n onSelectionChange,\n}: JobDetailViewProps) {\n const rootRef = useRef<HTMLDivElement>(null);\n const pageScrollRef = useRef<HTMLDivElement>(null);\n const landingSelectionRef = useRef<FrozenLandingSelection | undefined>(undefined);\n const hasLoadedData = query.data !== undefined;\n // Reuse the run workspace's bounded annotation read for the job header chip. The separate\n // summary query below stays counts-only and is scoped to the inspector's selected execution.\n const annotations = useRunAnnotationsQuery({\n workflowRunId,\n runAttempt: query.data?.runAttempt.attempt,\n });\n const jobAnnotationSummary = annotations.annotations\n ? summarizeJobAnnotations(annotations.annotations, jobId, {\n truncated: annotations.summary?.truncated ?? false,\n })\n : undefined;\n const inspectorResetKey = `${jobId}:${search.jobExecutionId ?? ''}`;\n const [inspectorState, setInspectorState] = useState<InspectorState>(() => ({\n key: inspectorResetKey,\n attemptId: null,\n }));\n const inspectorOpenAttemptId =\n inspectorState.key === inspectorResetKey ? inspectorState.attemptId : null;\n const annotationJob = query.data?.jobs.find((candidate) => candidate.id === jobId);\n const annotationExecutionId = annotationJob\n ? resolveWorkflowJobSelection({job: annotationJob, selection: search}).jobExecution?.id\n : search.jobExecutionId;\n const annotationPolling = query.data\n ? !isWorkflowRunTerminal(query.data.runAttempt.status)\n : true;\n const annotationSummaryQuery = useWorkflowRunAnnotationSummaryQuery(\n query.data?.id,\n query.data?.runAttempt.attempt,\n annotationExecutionId,\n {polling: annotationPolling},\n );\n\n useEffect(() => {\n if (!jobId || !hasLoadedData) return;\n const heading = rootRef.current?.querySelector<HTMLElement>('[data-job-heading]');\n // The heading is intentionally the route-change focus target: it announces the new job\n // after rail navigation without putting focus on a decorative status element.\n heading?.focus({preventScroll: true});\n }, [hasLoadedData, jobId]);\n\n if (query.isPending) return <JobDetailSkeleton />;\n\n if (query.isError && query.data === undefined) {\n if (query.error instanceof ApiError && query.error.status === 404) {\n return <WorkflowRunNotFound />;\n }\n return <QueryLoadError query={query} subject=\"workflow run\" icon=\"pulseLine\" />;\n }\n\n if (query.data === undefined) return <JobDetailSkeleton />;\n\n const run = query.data;\n const job = run.jobs.find((candidate) => candidate.id === jobId);\n if (!job) {\n return (\n <JobNotFoundState\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n workflowRunId={workflowRunId}\n search={search}\n />\n );\n }\n\n const resolvedSelection = resolveWorkflowJobSelection({job, selection: search});\n const selectedJobExecution = resolvedSelection.jobExecution;\n const hasExplicitStep = Boolean(search.stepId && resolvedSelection.step);\n const currentLandingSelection = workflowJobLandingSelection(selectedJobExecution);\n if (selectedJobExecution) {\n const frozenLanding = landingSelectionRef.current;\n if (\n !frozenLanding ||\n frozenLanding.jobId !== job.id ||\n frozenLanding.jobExecutionId !== selectedJobExecution.id\n ) {\n landingSelectionRef.current = {\n jobId: job.id,\n jobExecutionId: selectedJobExecution.id,\n selection: currentLandingSelection,\n hasSelection: currentLandingSelection !== undefined,\n };\n } else if (!frozenLanding.hasSelection && currentLandingSelection) {\n landingSelectionRef.current = {\n ...frozenLanding,\n selection: currentLandingSelection,\n hasSelection: true,\n };\n }\n } else {\n landingSelectionRef.current = undefined;\n }\n const landingSelection = landingSelectionRef.current?.selection;\n const selectedAttemptId = hasExplicitStep ? resolvedSelection.selectedAttemptId : undefined;\n const runningSelection = runningStepSelection(selectedJobExecution);\n const selectedStepId = resolvedSelection.step?.id ?? landingSelection?.stepId;\n const selectedAttemptForNotice = hasExplicitStep\n ? resolvedSelection.selectedAttemptId\n : landingSelection?.attemptId;\n const showRetargetNotice =\n runningSelection !== undefined &&\n (selectedStepId !== runningSelection.stepId ||\n selectedAttemptForNotice !== runningSelection.attemptId);\n const succeededSummary =\n !hasExplicitStep && landingSelection === undefined && selectedJobExecution\n ? jobSucceededSummary(job, selectedJobExecution)\n : undefined;\n\n function selectExecution(jobExecutionId: string) {\n onSelectionChange({\n ...search,\n jobExecutionId,\n stepId: undefined,\n stepAttemptId: undefined,\n });\n }\n\n function selectAttempt(attemptId: string | undefined) {\n if (!selectedJobExecution) return;\n if (!attemptId) {\n onSelectionChange({\n ...search,\n jobExecutionId: selectedJobExecution.id,\n stepId: undefined,\n stepAttemptId: undefined,\n });\n return;\n }\n\n const match = findAttempt(selectedJobExecution, attemptId);\n if (!match) return;\n onSelectionChange({\n ...search,\n jobExecutionId: selectedJobExecution.id,\n stepId: match.step.id,\n stepAttemptId: match.attemptId,\n });\n }\n\n function onInspectorOpenChange(attemptId: string | null) {\n setInspectorState({key: inspectorResetKey, attemptId});\n }\n\n function retargetToRunningStep() {\n if (!runningSelection || !selectedJobExecution) return;\n onSelectionChange({\n ...search,\n jobExecutionId: selectedJobExecution.id,\n stepId: runningSelection.stepId,\n stepAttemptId: runningSelection.attemptId,\n });\n }\n\n return (\n <TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>\n <div ref={rootRef} className=\"flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden\">\n {query.isError ? <WorkflowRunStaleError query={query} /> : null}\n {newerJob && newerAttempt && newerAttempt > run.runAttempt.attempt ? (\n <NewerAttemptNotice\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n runId={run.id}\n jobId={newerJob.id}\n attempt={newerAttempt}\n />\n ) : null}\n <div ref={pageScrollRef} className=\"@container min-h-0 flex-1 overflow-auto pb-panel\">\n <section aria-label={`${job.displayName} logs`} className=\"flex w-full flex-col\">\n <section className=\"min-w-0 overflow-hidden\">\n <JobDetailHeader\n job={job}\n selectedJobExecution={selectedJobExecution}\n onSelectedJobExecutionChange={selectExecution}\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n workflowRunId={run.id}\n runAttempt={run.runAttempt.attempt}\n annotationSummary={jobAnnotationSummary}\n jobContext={\n selectedJobExecution ? (\n <JobContextPanel job={job} execution={selectedJobExecution} />\n ) : undefined\n }\n />\n <Text as=\"h2\" className=\"sr-only\">\n Logs\n </Text>\n {selectedJobExecution ? (\n <StepList\n job={job}\n jobExecution={selectedJobExecution}\n selectedAttemptId={selectedAttemptId}\n defaultSelectedAttemptId={landingSelection?.attemptId}\n onSelectedAttemptChange={selectAttempt}\n inspectorOpenAttemptId={inspectorOpenAttemptId}\n onInspectorOpenChange={onInspectorOpenChange}\n autoSelectActiveAttempt\n emptyState={emptyStateForJob(job, selectedJobExecution)}\n showHeader={false}\n className=\"rounded-none border-0 bg-transparent\"\n renderExpandedStep={(context) => (\n <ExpandedStep context={context} pageScrollRef={pageScrollRef} />\n )}\n renderInspector={(entry) => (\n <StepInspectorSheet\n entry={entry}\n open\n onOpenChange={(open) => onInspectorOpenChange(open ? entry.id : null)}\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n workflowRunId={run.id}\n runAttempt={run.runAttempt.attempt}\n jobId={job.id}\n annotationCount={annotationCountForStep(\n annotationSummaryQuery.data,\n entry.step.id,\n entry.attempt,\n )}\n />\n )}\n />\n ) : (\n <EmptyStateForMissingExecution job={job} />\n )}\n </section>\n {showRetargetNotice && runningSelection ? (\n <div\n role=\"status\"\n aria-live=\"polite\"\n className=\"flex min-w-0 items-center justify-between gap-inline border-t border-border-neutral-base px-row py-row\"\n >\n <Text size=\"xs\" className=\"min-w-0 text-foreground-neutral-muted\">\n Run moved on to{' '}\n <span className=\"font-code text-foreground-neutral-base\">\n {runningSelection.stepLabel}\n </span>\n .\n </Text>\n <button\n type=\"button\"\n className=\"shrink-0 rounded-4 px-tight py-[4px] text-xs font-medium text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active\"\n onClick={retargetToRunningStep}\n >\n Jump to it\n </button>\n </div>\n ) : null}\n {succeededSummary ? (\n <Text size=\"xs\" className=\"px-row py-row text-foreground-neutral-muted\">\n {succeededSummary}\n </Text>\n ) : null}\n </section>\n </div>\n </div>\n </TimeTickerProvider>\n );\n}\n\nfunction ExpandedStep({\n context,\n pageScrollRef,\n}: {\n context: StepExpandedContext;\n pageScrollRef: RefObject<HTMLDivElement | null>;\n}) {\n if (context.carriedOver) return <CarriedOverStepPanel />;\n\n return (\n <section\n role=\"region\"\n tabIndex={0}\n aria-label={`${context.stepLabel} output, attempt ${context.attempt}`}\n className=\"flex min-w-0 flex-col border-t border-border-neutral-base bg-background-neutral-base outline-none focus-visible:shadow-border-interactive-with-active dark:bg-background-contrast-subtle\"\n >\n <StepAttemptLogPanel\n stepId={context.stepId}\n attempt={context.attempt}\n attemptStatus={context.attemptStatus}\n attemptStartedAt={context.attemptStartedAt}\n pageScrollRef={pageScrollRef}\n surfaceClassName=\"rounded-none border-0 bg-transparent shadow-none\"\n />\n </section>\n );\n}\n\nfunction EmptyStateForMissingExecution({job}: {job: Job}) {\n const emptyState = emptyStateForMissingExecution(job);\n return (\n <EmptyState\n className=\"min-h-120 p-panel\"\n icon=\"componentLine\"\n title={emptyState.title}\n description={emptyState.description}\n variant=\"compact\"\n />\n );\n}\n\nfunction JobNotFoundState({\n workspaceSlug,\n projectSlug,\n workflowRunId,\n search,\n}: {\n workspaceSlug: string;\n projectSlug: string;\n workflowRunId: string;\n search: WorkflowJobSearch;\n}) {\n return (\n <div className=\"flex min-h-0 min-w-0 flex-1 flex-col items-center justify-center gap-cluster p-panel\">\n <EmptyState\n icon=\"componentLine\"\n title=\"Job not found\"\n description=\"This job is not part of the selected workflow run.\"\n />\n <Link\n to=\"/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId\"\n params={{workspaceSlug, projectSlug, workflowRunId}}\n search={\n workflowRunSearchParams(\n {runAttempt: search.runAttempt},\n {runAttempt: search.runAttempt},\n ) as never\n }\n className=\"inline-flex items-center gap-inline rounded-6 px-tight py-[6px] text-sm text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active\"\n >\n <Icon name=\"arrowLeftLine\" size={14} aria-hidden=\"true\" />\n Back to run summary\n </Link>\n </div>\n );\n}\n\nfunction NewerAttemptNotice({\n workspaceSlug,\n projectSlug,\n runId,\n jobId,\n attempt,\n}: {\n workspaceSlug: string;\n projectSlug: string;\n runId: string;\n jobId: string;\n attempt: number;\n}) {\n return (\n <div role=\"status\" className=\"border-b border-border-neutral-base px-row py-row\">\n <div className=\"flex items-center justify-between gap-inline\">\n <Text size=\"xs\" className=\"text-foreground-neutral-muted\">\n A newer run attempt is available.\n </Text>\n <Link\n to=\"/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId/jobs/$jobId\"\n params={{workspaceSlug, projectSlug, workflowRunId: runId, jobId}}\n search={workflowJobSearchParams({runAttempt: attempt}) as never}\n className=\"shrink-0 rounded-4 px-tight py-[4px] text-xs font-medium text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active\"\n >\n View attempt #{attempt}\n </Link>\n </div>\n </div>\n );\n}\n\nfunction runningStepSelection(jobExecution: JobExecution | undefined) {\n if (!jobExecution) return undefined;\n const steps = [...jobExecution.steps].sort(\n (left, right) => left.position - right.position || left.id.localeCompare(right.id),\n );\n for (let stepIndex = steps.length - 1; stepIndex >= 0; stepIndex -= 1) {\n const step = steps[stepIndex];\n if (!step) continue;\n const attempt = [...step.attempts]\n .sort((left, right) => left.attempt - right.attempt || left.id.localeCompare(right.id))\n .reverse()\n .find((candidate) => candidate.status === 'running');\n if (attempt)\n return {stepId: step.id, attemptId: attempt.id, stepLabel: step.name || step.key || step.id};\n }\n return undefined;\n}\n\nfunction findAttempt(jobExecution: JobExecution, attemptId: string) {\n for (const step of jobExecution.steps) {\n const attempt = step.attempts.find((candidate) => candidate.id === attemptId);\n if (attempt) return {step, attemptId: attempt.id};\n }\n return undefined;\n}\n\nfunction annotationCountForStep(\n summary: RunAnnotationSummary | undefined,\n stepId: string,\n attempt: number,\n): number | undefined {\n return summary?.stepCounts?.find((entry) => entry.stepId === stepId && entry.attempt === attempt)\n ?.total;\n}\n\nfunction JobDetailSkeleton() {\n return (\n <div className=\"min-h-0 min-w-0 flex-1 overflow-auto pb-panel\">\n <div className=\"w-full\">\n <header className=\"flex items-center gap-cluster border-b border-border-neutral-base px-row py-row\">\n <Skeleton className=\"size-20 rounded-full\" />\n <Skeleton className=\"h-20 w-160 rounded-4\" />\n <Skeleton className=\"h-24 w-72 rounded-6\" />\n <Skeleton className=\"h-24 w-180 rounded-4\" />\n </header>\n <div className=\"p-panel-compact\">\n {JOB_DETAIL_SKELETON_ROWS.map((row) => (\n <div\n key={row}\n className=\"flex min-h-44 items-center gap-inline border-b border-border-neutral-base last:border-b-0\"\n >\n <Skeleton className=\"size-14 rounded-full\" />\n <Skeleton className=\"h-16 w-180 rounded-4\" />\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n\nconst JOB_DETAIL_SKELETON_ROWS = ['step-1', 'step-2', 'step-3', 'step-4', 'step-5'];\n\ninterface FrozenLandingSelection {\n jobId: string;\n jobExecutionId: string;\n selection: WorkflowJobLandingSelection | undefined;\n hasSelection: boolean;\n}\n"],"names":["ApiError","QueryLoadError","EmptyState","Icon","Skeleton","TimeTickerProvider","Text","Link","useEffect","useRef","useState","summarizeJobAnnotations","isWorkflowRunTerminal","useWorkflowRunAnnotationSummaryQuery","useRunAnnotationsQuery","workflowJobSearchParams","workflowRunSearchParams","StepList","WorkflowRunNotFound","WorkflowRunStaleError","JobContextPanel","JobDetailHeader","CarriedOverStepPanel","emptyStateForJob","emptyStateForMissingExecution","jobSucceededSummary","resolveWorkflowJobSelection","workflowJobLandingSelection","StepAttemptLogPanel","StepInspectorSheet","JobDetailView","workspaceSlug","projectSlug","workflowRunId","jobId","search","query","newerAttempt","newerJob","onSelectionChange","rootRef","pageScrollRef","landingSelectionRef","undefined","hasLoadedData","data","annotations","runAttempt","attempt","jobAnnotationSummary","truncated","summary","inspectorResetKey","jobExecutionId","inspectorState","setInspectorState","key","attemptId","inspectorOpenAttemptId","annotationJob","jobs","find","candidate","id","annotationExecutionId","job","selection","jobExecution","annotationPolling","status","annotationSummaryQuery","polling","heading","current","querySelector","focus","preventScroll","isPending","JobDetailSkeleton","isError","error","subject","icon","run","JobNotFoundState","resolvedSelection","selectedJobExecution","hasExplicitStep","Boolean","stepId","step","currentLandingSelection","frozenLanding","hasSelection","landingSelection","selectedAttemptId","runningSelection","runningStepSelection","selectedStepId","selectedAttemptForNotice","showRetargetNotice","succeededSummary","selectExecution","stepAttemptId","selectAttempt","match","findAttempt","onInspectorOpenChange","retargetToRunningStep","intervalMs","reducedMotionIntervalMs","div","ref","className","NewerAttemptNotice","runId","section","aria-label","displayName","onSelectedJobExecutionChange","annotationSummary","jobContext","execution","as","defaultSelectedAttemptId","onSelectedAttemptChange","autoSelectActiveAttempt","emptyState","showHeader","renderExpandedStep","context","ExpandedStep","renderInspector","entry","open","onOpenChange","annotationCount","annotationCountForStep","EmptyStateForMissingExecution","role","aria-live","size","span","stepLabel","button","type","onClick","carriedOver","tabIndex","attemptStatus","attemptStartedAt","surfaceClassName","title","description","variant","to","params","name","aria-hidden","steps","sort","left","right","position","localeCompare","stepIndex","length","attempts","reverse","stepCounts","total","header","JOB_DETAIL_SKELETON_ROWS","map","row"],"mappings":";AAAA,gIAAgI;AAChI,+GAA+G;AAE/G,SAAQA,QAAQ,QAAO,sBAAsB;AAC7C,SAAQC,cAAc,QAAO,qBAAqB;AAClD,SAAQC,UAAU,QAAO,gCAAgC;AACzD,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,QAAQ,QAAO,6BAA6B;AACpD,SAAQC,kBAAkB,QAAO,gCAAgC;AACjE,SAAQC,IAAI,QAAO,+BAA+B;AAClD,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAwBC,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAO,QAAQ;AAElE,SAAQC,uBAAuB,QAAO,0BAA0B;AAChE,SAAQC,qBAAqB,QAAoC,wBAAwB;AACzF,SAAQC,oCAAoC,QAAO,4BAA4B;AAC/E,SAAQC,sBAAsB,QAAO,gCAAgC;AAErE,SAEEC,uBAAuB,EACvBC,uBAAuB,QAClB,oBAAoB;AAC3B,SAAkCC,QAAQ,QAAO,wBAAwB;AACzE,SACEC,mBAAmB,EACnBC,qBAAqB,QAChB,8CAA8C;AACrD,SAAQC,eAAe,QAAO,yBAAyB;AACvD,SAAQC,eAAe,QAAO,yBAAyB;AACvD,SACEC,oBAAoB,EACpBC,gBAAgB,EAChBC,6BAA6B,EAC7BC,mBAAmB,QACd,wBAAwB;AAC/B,SACEC,2BAA2B,EAE3BC,2BAA2B,QACtB,qBAAqB;AAC5B,SAAQC,mBAAmB,QAAO,8BAA8B;AAChE,SAAQC,kBAAkB,QAAO,4BAA4B;AAgB7D,OAAO,SAASC,cAAc,EAC5BC,aAAa,EACbC,WAAW,EACXC,aAAa,EACbC,KAAK,EACLC,MAAM,EACNC,KAAK,EACLC,YAAY,EACZC,QAAQ,EACRC,iBAAiB,EACE;IACnB,MAAMC,UAAU/B,OAAuB;IACvC,MAAMgC,gBAAgBhC,OAAuB;IAC7C,MAAMiC,sBAAsBjC,OAA2CkC;IACvE,MAAMC,gBAAgBR,MAAMS,IAAI,KAAKF;IACrC,0FAA0F;IAC1F,6FAA6F;IAC7F,MAAMG,cAAchC,uBAAuB;QACzCmB;QACAc,YAAYX,MAAMS,IAAI,EAAEE,WAAWC;IACrC;IACA,MAAMC,uBAAuBH,YAAYA,WAAW,GAChDnC,wBAAwBmC,YAAYA,WAAW,EAAEZ,OAAO;QACtDgB,WAAWJ,YAAYK,OAAO,EAAED,aAAa;IAC/C,KACAP;IACJ,MAAMS,oBAAoB,GAAGlB,MAAM,CAAC,EAAEC,OAAOkB,cAAc,IAAI,IAAI;IACnE,MAAM,CAACC,gBAAgBC,kBAAkB,GAAG7C,SAAyB,IAAO,CAAA;YAC1E8C,KAAKJ;YACLK,WAAW;QACb,CAAA;IACA,MAAMC,yBACJJ,eAAeE,GAAG,KAAKJ,oBAAoBE,eAAeG,SAAS,GAAG;IACxE,MAAME,gBAAgBvB,MAAMS,IAAI,EAAEe,KAAKC,KAAK,CAACC,YAAcA,UAAUC,EAAE,KAAK7B;IAC5E,MAAM8B,wBAAwBL,gBAC1BjC,4BAA4B;QAACuC,KAAKN;QAAeO,WAAW/B;IAAM,GAAGgC,YAAY,EAAEJ,KACnF5B,OAAOkB,cAAc;IACzB,MAAMe,oBAAoBhC,MAAMS,IAAI,GAChC,CAACjC,sBAAsBwB,MAAMS,IAAI,CAACE,UAAU,CAACsB,MAAM,IACnD;IACJ,MAAMC,yBAAyBzD,qCAC7BuB,MAAMS,IAAI,EAAEkB,IACZ3B,MAAMS,IAAI,EAAEE,WAAWC,SACvBgB,uBACA;QAACO,SAASH;IAAiB;IAG7B5D,UAAU;QACR,IAAI,CAAC0B,SAAS,CAACU,eAAe;QAC9B,MAAM4B,UAAUhC,QAAQiC,OAAO,EAAEC,cAA2B;QAC5D,uFAAuF;QACvF,8EAA8E;QAC9EF,SAASG,MAAM;YAACC,eAAe;QAAI;IACrC,GAAG;QAAChC;QAAeV;KAAM;IAEzB,IAAIE,MAAMyC,SAAS,EAAE,qBAAO,KAACC;IAE7B,IAAI1C,MAAM2C,OAAO,IAAI3C,MAAMS,IAAI,KAAKF,WAAW;QAC7C,IAAIP,MAAM4C,KAAK,YAAYhF,YAAYoC,MAAM4C,KAAK,CAACX,MAAM,KAAK,KAAK;YACjE,qBAAO,KAACnD;QACV;QACA,qBAAO,KAACjB;YAAemC,OAAOA;YAAO6C,SAAQ;YAAeC,MAAK;;IACnE;IAEA,IAAI9C,MAAMS,IAAI,KAAKF,WAAW,qBAAO,KAACmC;IAEtC,MAAMK,MAAM/C,MAAMS,IAAI;IACtB,MAAMoB,MAAMkB,IAAIvB,IAAI,CAACC,IAAI,CAAC,CAACC,YAAcA,UAAUC,EAAE,KAAK7B;IAC1D,IAAI,CAAC+B,KAAK;QACR,qBACE,KAACmB;YACCrD,eAAeA;YACfC,aAAaA;YACbC,eAAeA;YACfE,QAAQA;;IAGd;IAEA,MAAMkD,oBAAoB3D,4BAA4B;QAACuC;QAAKC,WAAW/B;IAAM;IAC7E,MAAMmD,uBAAuBD,kBAAkBlB,YAAY;IAC3D,MAAMoB,kBAAkBC,QAAQrD,OAAOsD,MAAM,IAAIJ,kBAAkBK,IAAI;IACvE,MAAMC,0BAA0BhE,4BAA4B2D;IAC5D,IAAIA,sBAAsB;QACxB,MAAMM,gBAAgBlD,oBAAoB+B,OAAO;QACjD,IACE,CAACmB,iBACDA,cAAc1D,KAAK,KAAK+B,IAAIF,EAAE,IAC9B6B,cAAcvC,cAAc,KAAKiC,qBAAqBvB,EAAE,EACxD;YACArB,oBAAoB+B,OAAO,GAAG;gBAC5BvC,OAAO+B,IAAIF,EAAE;gBACbV,gBAAgBiC,qBAAqBvB,EAAE;gBACvCG,WAAWyB;gBACXE,cAAcF,4BAA4BhD;YAC5C;QACF,OAAO,IAAI,CAACiD,cAAcC,YAAY,IAAIF,yBAAyB;YACjEjD,oBAAoB+B,OAAO,GAAG;gBAC5B,GAAGmB,aAAa;gBAChB1B,WAAWyB;gBACXE,cAAc;YAChB;QACF;IACF,OAAO;QACLnD,oBAAoB+B,OAAO,GAAG9B;IAChC;IACA,MAAMmD,mBAAmBpD,oBAAoB+B,OAAO,EAAEP;IACtD,MAAM6B,oBAAoBR,kBAAkBF,kBAAkBU,iBAAiB,GAAGpD;IAClF,MAAMqD,mBAAmBC,qBAAqBX;IAC9C,MAAMY,iBAAiBb,kBAAkBK,IAAI,EAAE3B,MAAM+B,kBAAkBL;IACvE,MAAMU,2BAA2BZ,kBAC7BF,kBAAkBU,iBAAiB,GACnCD,kBAAkBrC;IACtB,MAAM2C,qBACJJ,qBAAqBrD,aACpBuD,CAAAA,mBAAmBF,iBAAiBP,MAAM,IACzCU,6BAA6BH,iBAAiBvC,SAAS,AAAD;IAC1D,MAAM4C,mBACJ,CAACd,mBAAmBO,qBAAqBnD,aAAa2C,uBAClD7D,oBAAoBwC,KAAKqB,wBACzB3C;IAEN,SAAS2D,gBAAgBjD,cAAsB;QAC7Cd,kBAAkB;YAChB,GAAGJ,MAAM;YACTkB;YACAoC,QAAQ9C;YACR4D,eAAe5D;QACjB;IACF;IAEA,SAAS6D,cAAc/C,SAA6B;QAClD,IAAI,CAAC6B,sBAAsB;QAC3B,IAAI,CAAC7B,WAAW;YACdlB,kBAAkB;gBAChB,GAAGJ,MAAM;gBACTkB,gBAAgBiC,qBAAqBvB,EAAE;gBACvC0B,QAAQ9C;gBACR4D,eAAe5D;YACjB;YACA;QACF;QAEA,MAAM8D,QAAQC,YAAYpB,sBAAsB7B;QAChD,IAAI,CAACgD,OAAO;QACZlE,kBAAkB;YAChB,GAAGJ,MAAM;YACTkB,gBAAgBiC,qBAAqBvB,EAAE;YACvC0B,QAAQgB,MAAMf,IAAI,CAAC3B,EAAE;YACrBwC,eAAeE,MAAMhD,SAAS;QAChC;IACF;IAEA,SAASkD,sBAAsBlD,SAAwB;QACrDF,kBAAkB;YAACC,KAAKJ;YAAmBK;QAAS;IACtD;IAEA,SAASmD;QACP,IAAI,CAACZ,oBAAoB,CAACV,sBAAsB;QAChD/C,kBAAkB;YAChB,GAAGJ,MAAM;YACTkB,gBAAgBiC,qBAAqBvB,EAAE;YACvC0B,QAAQO,iBAAiBP,MAAM;YAC/Bc,eAAeP,iBAAiBvC,SAAS;QAC3C;IACF;IAEA,qBACE,KAACpD;QAAmBwG,YAAY;QAAMC,yBAAyB;kBAC7D,cAAA,MAACC;YAAIC,KAAKxE;YAASyE,WAAU;;gBAC1B7E,MAAM2C,OAAO,iBAAG,KAAC5D;oBAAsBiB,OAAOA;qBAAY;gBAC1DE,YAAYD,gBAAgBA,eAAe8C,IAAIpC,UAAU,CAACC,OAAO,iBAChE,KAACkE;oBACCnF,eAAeA;oBACfC,aAAaA;oBACbmF,OAAOhC,IAAIpB,EAAE;oBACb7B,OAAOI,SAASyB,EAAE;oBAClBf,SAASX;qBAET;8BACJ,KAAC0E;oBAAIC,KAAKvE;oBAAewE,WAAU;8BACjC,cAAA,MAACG;wBAAQC,cAAY,GAAGpD,IAAIqD,WAAW,CAAC,KAAK,CAAC;wBAAEL,WAAU;;0CACxD,MAACG;gCAAQH,WAAU;;kDACjB,KAAC5F;wCACC4C,KAAKA;wCACLqB,sBAAsBA;wCACtBiC,8BAA8BjB;wCAC9BvE,eAAeA;wCACfC,aAAaA;wCACbC,eAAekD,IAAIpB,EAAE;wCACrBhB,YAAYoC,IAAIpC,UAAU,CAACC,OAAO;wCAClCwE,mBAAmBvE;wCACnBwE,YACEnC,qCACE,KAAClE;4CAAgB6C,KAAKA;4CAAKyD,WAAWpC;6CACpC3C;;kDAGR,KAACrC;wCAAKqH,IAAG;wCAAKV,WAAU;kDAAU;;oCAGjC3B,qCACC,KAACrE;wCACCgD,KAAKA;wCACLE,cAAcmB;wCACdS,mBAAmBA;wCACnB6B,0BAA0B9B,kBAAkBrC;wCAC5CoE,yBAAyBrB;wCACzB9C,wBAAwBA;wCACxBiD,uBAAuBA;wCACvBmB,uBAAuB;wCACvBC,YAAYxG,iBAAiB0C,KAAKqB;wCAClC0C,YAAY;wCACZf,WAAU;wCACVgB,oBAAoB,CAACC,wBACnB,KAACC;gDAAaD,SAASA;gDAASzF,eAAeA;;wCAEjD2F,iBAAiB,CAACC,sBAChB,KAACxG;gDACCwG,OAAOA;gDACPC,IAAI;gDACJC,cAAc,CAACD,OAAS3B,sBAAsB2B,OAAOD,MAAMtE,EAAE,GAAG;gDAChEhC,eAAeA;gDACfC,aAAaA;gDACbC,eAAekD,IAAIpB,EAAE;gDACrBhB,YAAYoC,IAAIpC,UAAU,CAACC,OAAO;gDAClCd,OAAO+B,IAAIF,EAAE;gDACbyE,iBAAiBC,uBACfnE,uBAAuBzB,IAAI,EAC3BwF,MAAM3C,IAAI,CAAC3B,EAAE,EACbsE,MAAMrF,OAAO;;uDAMrB,KAAC0F;wCAA8BzE,KAAKA;;;;4BAGvCmC,sBAAsBJ,iCACrB,MAACe;gCACC4B,MAAK;gCACLC,aAAU;gCACV3B,WAAU;;kDAEV,MAAC3G;wCAAKuI,MAAK;wCAAK5B,WAAU;;4CAAwC;4CAChD;0DAChB,KAAC6B;gDAAK7B,WAAU;0DACbjB,iBAAiB+C,SAAS;;4CACtB;;;kDAGT,KAACC;wCACCC,MAAK;wCACLhC,WAAU;wCACViC,SAAStC;kDACV;;;iCAID;4BACHP,iCACC,KAAC/F;gCAAKuI,MAAK;gCAAK5B,WAAU;0CACvBZ;iCAED;;;;;;;AAMhB;AAEA,SAAS8B,aAAa,EACpBD,OAAO,EACPzF,aAAa,EAId;IACC,IAAIyF,QAAQiB,WAAW,EAAE,qBAAO,KAAC7H;IAEjC,qBACE,KAAC8F;QACCuB,MAAK;QACLS,UAAU;QACV/B,cAAY,GAAGa,QAAQa,SAAS,CAAC,iBAAiB,EAAEb,QAAQlF,OAAO,EAAE;QACrEiE,WAAU;kBAEV,cAAA,KAACrF;YACC6D,QAAQyC,QAAQzC,MAAM;YACtBzC,SAASkF,QAAQlF,OAAO;YACxBqG,eAAenB,QAAQmB,aAAa;YACpCC,kBAAkBpB,QAAQoB,gBAAgB;YAC1C7G,eAAeA;YACf8G,kBAAiB;;;AAIzB;AAEA,SAASb,8BAA8B,EAACzE,GAAG,EAAa;IACtD,MAAM8D,aAAavG,8BAA8ByC;IACjD,qBACE,KAAC/D;QACC+G,WAAU;QACV/B,MAAK;QACLsE,OAAOzB,WAAWyB,KAAK;QACvBC,aAAa1B,WAAW0B,WAAW;QACnCC,SAAQ;;AAGd;AAEA,SAAStE,iBAAiB,EACxBrD,aAAa,EACbC,WAAW,EACXC,aAAa,EACbE,MAAM,EAMP;IACC,qBACE,MAAC4E;QAAIE,WAAU;;0BACb,KAAC/G;gBACCgF,MAAK;gBACLsE,OAAM;gBACNC,aAAY;;0BAEd,MAAClJ;gBACCoJ,IAAG;gBACHC,QAAQ;oBAAC7H;oBAAeC;oBAAaC;gBAAa;gBAClDE,QACEnB,wBACE;oBAAC+B,YAAYZ,OAAOY,UAAU;gBAAA,GAC9B;oBAACA,YAAYZ,OAAOY,UAAU;gBAAA;gBAGlCkE,WAAU;;kCAEV,KAAC9G;wBAAK0J,MAAK;wBAAgBhB,MAAM;wBAAIiB,eAAY;;oBAAS;;;;;AAKlE;AAEA,SAAS5C,mBAAmB,EAC1BnF,aAAa,EACbC,WAAW,EACXmF,KAAK,EACLjF,KAAK,EACLc,OAAO,EAOR;IACC,qBACE,KAAC+D;QAAI4B,MAAK;QAAS1B,WAAU;kBAC3B,cAAA,MAACF;YAAIE,WAAU;;8BACb,KAAC3G;oBAAKuI,MAAK;oBAAK5B,WAAU;8BAAgC;;8BAG1D,MAAC1G;oBACCoJ,IAAG;oBACHC,QAAQ;wBAAC7H;wBAAeC;wBAAaC,eAAekF;wBAAOjF;oBAAK;oBAChEC,QAAQpB,wBAAwB;wBAACgC,YAAYC;oBAAO;oBACpDiE,WAAU;;wBACX;wBACgBjE;;;;;;AAKzB;AAEA,SAASiD,qBAAqB9B,YAAsC;IAClE,IAAI,CAACA,cAAc,OAAOxB;IAC1B,MAAMoH,QAAQ;WAAI5F,aAAa4F,KAAK;KAAC,CAACC,IAAI,CACxC,CAACC,MAAMC,QAAUD,KAAKE,QAAQ,GAAGD,MAAMC,QAAQ,IAAIF,KAAKlG,EAAE,CAACqG,aAAa,CAACF,MAAMnG,EAAE;IAEnF,IAAK,IAAIsG,YAAYN,MAAMO,MAAM,GAAG,GAAGD,aAAa,GAAGA,aAAa,EAAG;QACrE,MAAM3E,OAAOqE,KAAK,CAACM,UAAU;QAC7B,IAAI,CAAC3E,MAAM;QACX,MAAM1C,UAAU;eAAI0C,KAAK6E,QAAQ;SAAC,CAC/BP,IAAI,CAAC,CAACC,MAAMC,QAAUD,KAAKjH,OAAO,GAAGkH,MAAMlH,OAAO,IAAIiH,KAAKlG,EAAE,CAACqG,aAAa,CAACF,MAAMnG,EAAE,GACpFyG,OAAO,GACP3G,IAAI,CAAC,CAACC,YAAcA,UAAUO,MAAM,KAAK;QAC5C,IAAIrB,SACF,OAAO;YAACyC,QAAQC,KAAK3B,EAAE;YAAEN,WAAWT,QAAQe,EAAE;YAAEgF,WAAWrD,KAAKmE,IAAI,IAAInE,KAAKlC,GAAG,IAAIkC,KAAK3B,EAAE;QAAA;IAC/F;IACA,OAAOpB;AACT;AAEA,SAAS+D,YAAYvC,YAA0B,EAAEV,SAAiB;IAChE,KAAK,MAAMiC,QAAQvB,aAAa4F,KAAK,CAAE;QACrC,MAAM/G,UAAU0C,KAAK6E,QAAQ,CAAC1G,IAAI,CAAC,CAACC,YAAcA,UAAUC,EAAE,KAAKN;QACnE,IAAIT,SAAS,OAAO;YAAC0C;YAAMjC,WAAWT,QAAQe,EAAE;QAAA;IAClD;IACA,OAAOpB;AACT;AAEA,SAAS8F,uBACPtF,OAAyC,EACzCsC,MAAc,EACdzC,OAAe;IAEf,OAAOG,SAASsH,YAAY5G,KAAK,CAACwE,QAAUA,MAAM5C,MAAM,KAAKA,UAAU4C,MAAMrF,OAAO,KAAKA,UACrF0H;AACN;AAEA,SAAS5F;IACP,qBACE,KAACiC;QAAIE,WAAU;kBACb,cAAA,MAACF;YAAIE,WAAU;;8BACb,MAAC0D;oBAAO1D,WAAU;;sCAChB,KAAC7G;4BAAS6G,WAAU;;sCACpB,KAAC7G;4BAAS6G,WAAU;;sCACpB,KAAC7G;4BAAS6G,WAAU;;sCACpB,KAAC7G;4BAAS6G,WAAU;;;;8BAEtB,KAACF;oBAAIE,WAAU;8BACZ2D,yBAAyBC,GAAG,CAAC,CAACC,oBAC7B,MAAC/D;4BAECE,WAAU;;8CAEV,KAAC7G;oCAAS6G,WAAU;;8CACpB,KAAC7G;oCAAS6G,WAAU;;;2BAJf6D;;;;;AAWnB;AAEA,MAAMF,2BAA2B;IAAC;IAAU;IAAU;IAAU;IAAU;CAAS"}
@@ -1 +1 @@
1
- {"version":3,"file":"job-empty-states.d.ts","sourceRoot":"","sources":["../../../src/components/job-detail/job-empty-states.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAO9E,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,uBAAuB,CAAC;AAG9D,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,GAAG,EACR,YAAY,EAAE,YAAY,GACzB,kBAAkB,GAAG,SAAS,CA+DhC;AAED,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,GAAG,GAAG,kBAAkB,CAsD1E;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,GAAG,CAAC,cAAc,CAAC,GAAG,MAAM,CAoBzE;AAiCD,wBAAgB,oBAAoB,gCAUnC;AAED,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GACpC,SAAS,GAAG,IAAI,CAgClB;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,GAAG,OAAO,CAEjF;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAMzF"}
1
+ {"version":3,"file":"job-empty-states.d.ts","sourceRoot":"","sources":["../../../src/components/job-detail/job-empty-states.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAO9E,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,uBAAuB,CAAC;AAG9D,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,GAAG,EACR,YAAY,EAAE,YAAY,GACzB,kBAAkB,GAAG,SAAS,CAkEhC;AAED,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,GAAG,GAAG,kBAAkB,CA8D1E;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,GAAG,CAAC,cAAc,CAAC,GAAG,MAAM,CAoBzE;AAmCD,wBAAgB,oBAAoB,gCAUnC;AAED,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GACpC,SAAS,GAAG,IAAI,CAgClB;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,GAAG,OAAO,CAEjF;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAMzF"}
@@ -11,10 +11,11 @@ export function emptyStateForJob(job, jobExecution) {
11
11
  };
12
12
  }
13
13
  const displayStatus = job.mode === 'listening' ? deriveJobExecutionDisplayStatus(jobExecution) : deriveJobDisplayStatus(job);
14
+ const runner = jobExecution.runner?.length ? jobExecution.runner : job.runner;
14
15
  if (jobExecution.status === 'running' && displayStatus === 'pending') {
15
16
  return {
16
- title: 'Runner preparing job',
17
- description: 'A runner is assigned. Steps will appear here when work begins.',
17
+ title: runner?.length ? 'Runner preparing job' : 'Waiting for a runner',
18
+ description: runner?.length ? `Runner ${runner.join(', ')} is preparing the job. Steps will appear here when work begins.` : 'No runner has claimed this job yet. Steps will appear here when a runner starts work.',
18
19
  status: displayStatus
19
20
  };
20
21
  }
@@ -42,7 +43,7 @@ export function emptyStateForJob(job, jobExecution) {
42
43
  if (displayStatus === 'failed') {
43
44
  return {
44
45
  title: 'Job failed before its first step started',
45
- description: preStepFailureDescription(jobExecution.statusReason ?? job.statusReason),
46
+ description: preStepFailureDescription(jobExecution.statusReason ?? job.statusReason, runner),
46
47
  status: displayStatus
47
48
  };
48
49
  }
@@ -98,6 +99,13 @@ export function emptyStateForMissingExecution(job) {
98
99
  status: 'cancelled'
99
100
  };
100
101
  }
102
+ if (job.status === 'failed') {
103
+ return {
104
+ title: 'Job failed before an execution was created',
105
+ description: preStepFailureDescription(job.statusReason, job.runner),
106
+ status: 'failed'
107
+ };
108
+ }
101
109
  return {
102
110
  title: 'Execution details unavailable',
103
111
  description: 'This job finished, but no job execution record is available.',
@@ -125,17 +133,18 @@ export function skippedJobDescription(reason) {
125
133
  return 'This job did not start.';
126
134
  }
127
135
  }
128
- function preStepFailureDescription(reason) {
136
+ function preStepFailureDescription(reason, runner = null) {
137
+ const runnerCopy = runner?.length ? ` Required runner labels: ${runner.join(', ')}.` : '';
129
138
  switch(reason){
130
139
  case 'runner_lost':
131
- return 'The runner stopped responding before work began. Check runner availability before re-running the workflow.';
140
+ return `The runner stopped responding before work began.${runnerCopy} Check runner availability before re-running the workflow.`;
132
141
  case 'timed_out':
133
- return 'The job timed out before work began. Check runner capacity and timeout configuration before re-running the workflow.';
142
+ return `The job timed out before work began.${runnerCopy} Check runner capacity and timeout configuration before re-running the workflow.`;
134
143
  case 'user_cancelled':
135
144
  case 'run_cancelled':
136
145
  return 'The execution ended while the run was being cancelled.';
137
146
  case 'step_failed':
138
- return 'The execution failed before step details were recorded. Review run annotations before re-running the workflow.';
147
+ return `The execution failed before step details were recorded.${runnerCopy} Review run annotations before re-running the workflow.`;
139
148
  case 'condition_errored':
140
149
  return 'The job condition could not be evaluated. Review run annotations before re-running the workflow.';
141
150
  case 'dependency_not_completed':
@@ -146,9 +155,9 @@ function preStepFailureDescription(reason) {
146
155
  return 'The job condition did not match, so no work started.';
147
156
  case 'unknown':
148
157
  case null:
149
- return 'Shipfox did not record a failure reason. Re-run only after checking the runner and workflow configuration.';
158
+ return `Shipfox did not record a failure reason.${runnerCopy} Re-run only after checking the runner and workflow configuration.`;
150
159
  default:
151
- return `The execution ended because ${humanizeFailureReason(reason)}. Resolve that condition before re-running the workflow.`;
160
+ return `The execution ended because ${humanizeFailureReason(reason)}.${runnerCopy} Resolve that condition before re-running the workflow.`;
152
161
  }
153
162
  }
154
163
  function humanizeFailureReason(reason) {
@@ -156,7 +165,7 @@ function humanizeFailureReason(reason) {
156
165
  }
157
166
  export function CarriedOverStepPanel() {
158
167
  return /*#__PURE__*/ _jsx(EmptyState, {
159
- className: "min-h-120 rounded-8 border border-border-neutral-base bg-background-components-base px-16 py-20",
168
+ className: "min-h-120 rounded-8 border border-border-neutral-base bg-background-components-base p-panel",
160
169
  icon: "componentLine",
161
170
  title: "Carried over from a previous attempt",
162
171
  description: "Not executed in this run.",