@shipfox/client-workflows 19.0.0 → 21.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 (139) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +43 -0
  3. package/dist/components/job-detail/job-detail-view.d.ts.map +1 -1
  4. package/dist/components/job-detail/job-detail-view.js +231 -42
  5. package/dist/components/job-detail/job-detail-view.js.map +1 -1
  6. package/dist/components/job-detail/step-attempt-log-panel.d.ts +7 -1
  7. package/dist/components/job-detail/step-attempt-log-panel.d.ts.map +1 -1
  8. package/dist/components/job-detail/step-attempt-log-panel.js +37 -10
  9. package/dist/components/job-detail/step-attempt-log-panel.js.map +1 -1
  10. package/dist/components/step-list/step-list-model.d.ts +1 -0
  11. package/dist/components/step-list/step-list-model.d.ts.map +1 -1
  12. package/dist/components/step-list/step-list-model.js +1 -1
  13. package/dist/components/step-list/step-list-model.js.map +1 -1
  14. package/dist/components/step-list/step-list.d.ts +2 -1
  15. package/dist/components/step-list/step-list.d.ts.map +1 -1
  16. package/dist/components/step-list/step-list.js +24 -7
  17. package/dist/components/step-list/step-list.js.map +1 -1
  18. package/dist/components/workflow-run-duration-label.d.ts +8 -7
  19. package/dist/components/workflow-run-duration-label.d.ts.map +1 -1
  20. package/dist/components/workflow-run-duration-label.js +15 -31
  21. package/dist/components/workflow-run-duration-label.js.map +1 -1
  22. package/dist/components/workflow-run-list/job-status-strip.d.ts +9 -1
  23. package/dist/components/workflow-run-list/job-status-strip.d.ts.map +1 -1
  24. package/dist/components/workflow-run-list/job-status-strip.js +22 -11
  25. package/dist/components/workflow-run-list/job-status-strip.js.map +1 -1
  26. package/dist/components/workflow-run-list/workflow-run-filters.js +1 -1
  27. package/dist/components/workflow-run-list/workflow-run-filters.js.map +1 -1
  28. package/dist/components/workflow-run-list/workflow-run-list-states.js +2 -2
  29. package/dist/components/workflow-run-list/workflow-run-list-states.js.map +1 -1
  30. package/dist/components/workflow-run-list/workflow-run-list-view.d.ts.map +1 -1
  31. package/dist/components/workflow-run-list/workflow-run-list-view.js +34 -22
  32. package/dist/components/workflow-run-list/workflow-run-list-view.js.map +1 -1
  33. package/dist/components/workflow-run-list/workflow-run-row.d.ts +3 -3
  34. package/dist/components/workflow-run-list/workflow-run-row.d.ts.map +1 -1
  35. package/dist/components/workflow-run-list/workflow-run-row.js +22 -16
  36. package/dist/components/workflow-run-list/workflow-run-row.js.map +1 -1
  37. package/dist/components/workflow-run-summary/workflow-run-summary.d.ts.map +1 -1
  38. package/dist/components/workflow-run-summary/workflow-run-summary.js +8 -35
  39. package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -1
  40. package/dist/components/workflow-run-view/run-workspace-nav.js +1 -1
  41. package/dist/components/workflow-run-view/run-workspace-nav.js.map +1 -1
  42. package/dist/components/workflow-run-view/workflow-run-states.d.ts.map +1 -1
  43. package/dist/components/workflow-run-view/workflow-run-states.js +1 -1
  44. package/dist/components/workflow-run-view/workflow-run-states.js.map +1 -1
  45. package/dist/components/workflow-run-view/workflow-run-view.d.ts.map +1 -1
  46. package/dist/components/workflow-run-view/workflow-run-view.js +43 -36
  47. package/dist/components/workflow-run-view/workflow-run-view.js.map +1 -1
  48. package/dist/components/workflow-source-panel/workflow-source-content.js +1 -1
  49. package/dist/components/workflow-source-panel/workflow-source-content.js.map +1 -1
  50. package/dist/components/workflow-status/status-visuals.d.ts.map +1 -1
  51. package/dist/components/workflow-status/status-visuals.js +0 -9
  52. package/dist/components/workflow-status/status-visuals.js.map +1 -1
  53. package/dist/components/workflow-status/workflow-status-icon.d.ts.map +1 -1
  54. package/dist/components/workflow-status/workflow-status-icon.js +0 -11
  55. package/dist/components/workflow-status/workflow-status-icon.js.map +1 -1
  56. package/dist/core/entities/workflow-run.d.ts +10 -55
  57. package/dist/core/entities/workflow-run.d.ts.map +1 -1
  58. package/dist/core/entities/workflow-run.js +1 -94
  59. package/dist/core/entities/workflow-run.js.map +1 -1
  60. package/dist/core/workflow-run.d.ts +2 -2
  61. package/dist/core/workflow-run.d.ts.map +1 -1
  62. package/dist/core/workflow-run.js +1 -1
  63. package/dist/core/workflow-run.js.map +1 -1
  64. package/dist/hooks/api/workflow-run-mapper.d.ts.map +1 -1
  65. package/dist/hooks/api/workflow-run-mapper.js +3 -1
  66. package/dist/hooks/api/workflow-run-mapper.js.map +1 -1
  67. package/dist/hooks/api/workflow-runs.js +1 -0
  68. package/dist/hooks/api/workflow-runs.js.map +1 -1
  69. package/dist/pages/project-workflows-page.d.ts.map +1 -1
  70. package/dist/pages/project-workflows-page.js +3 -2
  71. package/dist/pages/project-workflows-page.js.map +1 -1
  72. package/dist/pages/workflow-job-detail-page.d.ts.map +1 -1
  73. package/dist/pages/workflow-job-detail-page.js +3 -2
  74. package/dist/pages/workflow-job-detail-page.js.map +1 -1
  75. package/dist/pages/workflow-run-detail-page.js +2 -1
  76. package/dist/pages/workflow-run-detail-page.js.map +1 -1
  77. package/dist/pages/workflow-run-list-page.d.ts.map +1 -1
  78. package/dist/pages/workflow-run-list-page.js +9 -11
  79. package/dist/pages/workflow-run-list-page.js.map +1 -1
  80. package/dist/routes/job-detail.d.ts +1 -1
  81. package/dist/routes/job-detail.js +1 -1
  82. package/dist/routes/job-detail.js.map +1 -1
  83. package/dist/routes/run-detail.d.ts +1 -1
  84. package/dist/routes/run-detail.js +1 -1
  85. package/dist/routes/run-detail.js.map +1 -1
  86. package/dist/routes/runs.d.ts +1 -1
  87. package/dist/routes/runs.js +1 -1
  88. package/dist/routes/runs.js.map +1 -1
  89. package/dist/routes/workflows.d.ts +3 -0
  90. package/dist/routes/workflows.js +3 -0
  91. package/dist/routes/workflows.js.map +1 -1
  92. package/dist/tsconfig.test.tsbuildinfo +1 -1
  93. package/package.json +8 -8
  94. package/src/components/job-detail/job-detail-view.tsx +263 -47
  95. package/src/components/job-detail/step-attempt-log-panel.test.tsx +30 -12
  96. package/src/components/job-detail/step-attempt-log-panel.tsx +68 -10
  97. package/src/components/job-graph/job-graph.stories.tsx +1 -1
  98. package/src/components/job-graph/job-node.stories.tsx +1 -1
  99. package/src/components/step-list/step-list-model.ts +1 -1
  100. package/src/components/step-list/step-list.test.tsx +29 -1
  101. package/src/components/step-list/step-list.tsx +28 -9
  102. package/src/components/workflow-run-duration-label.tsx +21 -44
  103. package/src/components/workflow-run-list/job-status-strip.tsx +25 -12
  104. package/src/components/workflow-run-list/workflow-run-filters.tsx +1 -1
  105. package/src/components/workflow-run-list/workflow-run-list-states.tsx +2 -2
  106. package/src/components/workflow-run-list/workflow-run-list-view.test.tsx +101 -4
  107. package/src/components/workflow-run-list/workflow-run-list-view.tsx +28 -22
  108. package/src/components/workflow-run-list/workflow-run-list.stories.tsx +9 -20
  109. package/src/components/workflow-run-list/workflow-run-row.tsx +24 -19
  110. package/src/components/workflow-run-summary/workflow-run-summary.stories.tsx +2 -40
  111. package/src/components/workflow-run-summary/workflow-run-summary.test.tsx +75 -22
  112. package/src/components/workflow-run-summary/workflow-run-summary.tsx +7 -31
  113. package/src/components/workflow-run-view/run-workspace-nav.tsx +1 -1
  114. package/src/components/workflow-run-view/workflow-run-states.tsx +1 -4
  115. package/src/components/workflow-run-view/workflow-run-view.stories.tsx +140 -0
  116. package/src/components/workflow-run-view/workflow-run-view.test.tsx +38 -7
  117. package/src/components/workflow-run-view/workflow-run-view.tsx +45 -37
  118. package/src/components/workflow-source-panel/workflow-source-content.tsx +2 -2
  119. package/src/components/workflow-status/status-visuals.ts +0 -4
  120. package/src/components/workflow-status/workflow-status-icon.stories.tsx +0 -1
  121. package/src/components/workflow-status/workflow-status-icon.tsx +0 -18
  122. package/src/core/entities/workflow-run.ts +9 -136
  123. package/src/core/workflow-run.test.ts +2 -0
  124. package/src/core/workflow-run.ts +0 -8
  125. package/src/hooks/api/workflow-run-mapper.ts +2 -0
  126. package/src/hooks/api/workflow-runs.ts +1 -1
  127. package/src/pages/project-workflows-page.tsx +3 -2
  128. package/src/pages/workflow-job-detail-page.test.tsx +32 -0
  129. package/src/pages/workflow-job-detail-page.tsx +5 -1
  130. package/src/pages/workflow-run-detail-page.tsx +1 -1
  131. package/src/pages/workflow-run-list-page.tsx +9 -11
  132. package/src/pages/workflow-run-pages.test.tsx +26 -0
  133. package/src/routes/job-detail.tsx +1 -1
  134. package/src/routes/run-detail.tsx +1 -1
  135. package/src/routes/runs.tsx +1 -1
  136. package/src/routes/workflows.tsx +1 -0
  137. package/test/fixtures/workflow-run.ts +30 -3
  138. package/tsconfig.build.tsbuildinfo +1 -1
  139. package/src/core/entities/workflow-run-display.test.ts +0 -187
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/step-list/step-list.tsx"],"sourcesContent":["import {\n Accordion,\n AccordionContent,\n AccordionItem,\n AccordionTrigger,\n} from '@shipfox/react-ui/accordion';\nimport {Badge, type BadgeVariant} from '@shipfox/react-ui/badge';\nimport {Dot} from '@shipfox/react-ui/dot';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {TimeTickerProvider, useTimeTick} from '@shipfox/react-ui/time-ticker';\nimport {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Code, Text} from '@shipfox/react-ui/typography';\nimport {cn, humanDuration} from '@shipfox/react-ui/utils';\nimport type {ReactNode} from 'react';\nimport {useEffect, useId, useMemo, useRef, useState} from 'react';\nimport {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';\nimport {\n isWorkflowStatus,\n type Job,\n type JobDisplayStatus,\n type JobExecution,\n type Step,\n type StepSourceLocation,\n} from '#core/workflow-run.js';\nimport {formatJobExecutionTimeLabel} from '../job-graph/job-duration-format.js';\nimport {\n buildStepListModel,\n defaultStepListJobExecution,\n humanizeStatus,\n type StepAttemptModel,\n type StepListEntryModel,\n type StepListModel,\n} from './step-list-model.js';\n\nexport interface StepExpandedContext {\n step: Step;\n stepId: string;\n stepLabel: string;\n sourceLocation: StepSourceLocation | null;\n attempt: number;\n attemptId: string;\n attemptStartedAt: string;\n attemptError: Record<string, unknown> | null;\n attemptStatus: string;\n carriedOver: boolean;\n}\n\nexport interface StepListEmptyState {\n title: string;\n description: string;\n status?: JobDisplayStatus | undefined;\n}\n\nexport interface StepListProps {\n job: Job;\n jobExecution?: JobExecution | undefined;\n selectedAttemptId?: string | null | undefined;\n defaultSelectedAttemptId?: string | undefined;\n onSelectedAttemptChange?: ((attemptId: string | undefined) => void) | undefined;\n inspectorOpenAttemptId?: string | null | undefined;\n onInspectorOpenChange?: ((attemptId: string | null) => void) | undefined;\n autoSelectActiveAttempt?: boolean | undefined;\n emptyState?: StepListEmptyState | undefined;\n renderExpandedStep?: ((context: StepExpandedContext) => ReactNode) | undefined;\n renderInspector?: ((entry: StepListEntryModel) => ReactNode) | undefined;\n showHeader?: boolean | undefined;\n className?: string | undefined;\n}\n\nexport function StepList({\n job,\n jobExecution,\n selectedAttemptId,\n defaultSelectedAttemptId,\n onSelectedAttemptChange,\n inspectorOpenAttemptId = null,\n onInspectorOpenChange,\n autoSelectActiveAttempt = false,\n emptyState,\n renderExpandedStep,\n renderInspector,\n showHeader = true,\n className,\n}: StepListProps) {\n const selectedJobExecution = jobExecution ?? defaultStepListJobExecution(job);\n const model = useMemo(\n () => buildStepListModel({job, jobExecution: selectedJobExecution}),\n [job, selectedJobExecution],\n );\n\n return (\n <StepListContent\n key={model.jobExecutionId}\n model={model}\n selectedAttemptId={selectedAttemptId}\n defaultSelectedAttemptId={defaultSelectedAttemptId}\n onSelectedAttemptChange={onSelectedAttemptChange}\n inspectorOpenAttemptId={inspectorOpenAttemptId}\n onInspectorOpenChange={onInspectorOpenChange}\n autoSelectActiveAttempt={autoSelectActiveAttempt}\n emptyState={emptyState}\n renderExpandedStep={renderExpandedStep}\n renderInspector={renderInspector}\n showHeader={showHeader}\n className={className}\n />\n );\n}\n\nfunction StepListContent({\n model,\n selectedAttemptId,\n defaultSelectedAttemptId,\n onSelectedAttemptChange,\n inspectorOpenAttemptId = null,\n onInspectorOpenChange,\n autoSelectActiveAttempt,\n emptyState,\n renderExpandedStep,\n renderInspector,\n showHeader,\n className,\n}: Omit<StepListProps, 'job' | 'jobExecution'> & {model: StepListModel}) {\n const titleId = useId();\n const [localSelectedAttemptIds, setLocalSelectedAttemptIds] = useState<string[]>(() =>\n selectedAttemptId\n ? [selectedAttemptId]\n : defaultSelectedAttemptId\n ? [defaultSelectedAttemptId]\n : [],\n );\n const [userSelectedAttempt, setUserSelectedAttempt] = useState(false);\n const lastNotifiedSelectedAttemptId = useRef<string | null>(null);\n const autoSelectedAttemptIdRef = useRef<string | undefined>(undefined);\n const previousSelectedAttemptIdRef = useRef<string | null | undefined>(selectedAttemptId);\n const shouldUseControlledCollapsedState =\n selectedAttemptId === null && lastNotifiedSelectedAttemptId.current === null;\n const autoSelectedAttemptId =\n selectedAttemptId === undefined && autoSelectActiveAttempt && !userSelectedAttempt\n ? (autoSelectedAttemptIdRef.current ?? model.activeEntryId)\n : undefined;\n const autoSelectedAttemptIds = autoSelectedAttemptId ? [autoSelectedAttemptId] : [];\n const selectedAttemptIds = shouldUseControlledCollapsedState\n ? []\n : localSelectedAttemptIds.length > 0\n ? localSelectedAttemptIds\n : autoSelectedAttemptIds;\n const hasExpandedContent = renderExpandedStep !== undefined;\n\n useEffect(() => {\n if (selectedAttemptId !== undefined) {\n previousSelectedAttemptIdRef.current = selectedAttemptId;\n return;\n }\n\n // A changing landing candidate is normal while a live job polls. Only reset the local\n // accordion when the parent has just cleared a previously controlled URL selection.\n const wasControlled = previousSelectedAttemptIdRef.current !== undefined;\n previousSelectedAttemptIdRef.current = undefined;\n if (!wasControlled) return;\n\n setLocalSelectedAttemptIds(defaultSelectedAttemptId ? [defaultSelectedAttemptId] : []);\n setUserSelectedAttempt(false);\n autoSelectedAttemptIdRef.current = undefined;\n }, [defaultSelectedAttemptId, selectedAttemptId]);\n\n useEffect(() => {\n if (\n selectedAttemptId !== undefined ||\n !autoSelectActiveAttempt ||\n userSelectedAttempt ||\n localSelectedAttemptIds.length > 0 ||\n autoSelectedAttemptIdRef.current !== undefined\n ) {\n return;\n }\n if (model.activeEntryId) autoSelectedAttemptIdRef.current = model.activeEntryId;\n }, [\n autoSelectActiveAttempt,\n localSelectedAttemptIds.length,\n model.activeEntryId,\n selectedAttemptId,\n userSelectedAttempt,\n ]);\n\n useEffect(() => {\n if (selectedAttemptId === undefined) return;\n\n const nextSelectedAttemptId = selectedAttemptId ?? null;\n if (lastNotifiedSelectedAttemptId.current === nextSelectedAttemptId) {\n lastNotifiedSelectedAttemptId.current = null;\n return;\n }\n\n setLocalSelectedAttemptIds(selectedAttemptId ? [selectedAttemptId] : []);\n setUserSelectedAttempt(true);\n }, [selectedAttemptId]);\n\n useEffect(() => {\n if (\n inspectorOpenAttemptId !== null &&\n !model.entries.some((entry) => entry.id === inspectorOpenAttemptId && !entry.carriedOver)\n ) {\n onInspectorOpenChange?.(null);\n }\n }, [inspectorOpenAttemptId, model.entries, onInspectorOpenChange]);\n\n function selectAttempt(nextAttemptIds: string[]) {\n const nextAttemptId = nextSelectedAttemptId(selectedAttemptIds, nextAttemptIds);\n setUserSelectedAttempt(true);\n setLocalSelectedAttemptIds(nextAttemptIds);\n lastNotifiedSelectedAttemptId.current = nextAttemptId ?? null;\n onSelectedAttemptChange?.(nextAttemptId);\n }\n\n const inspectorEntry = model.entries.find(\n (entry) => entry.id === inspectorOpenAttemptId && !entry.carriedOver,\n );\n\n return (\n <TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>\n <section\n aria-labelledby={showHeader ? titleId : undefined}\n className={cn(\n 'flex min-h-0 flex-col rounded-8 border border-border-neutral-base bg-background-components-base',\n className,\n )}\n >\n {showHeader ? (\n <div className=\"flex min-h-40 items-center border-b border-border-neutral-base px-row py-row\">\n <Text as=\"h2\" id={titleId} size=\"sm\" bold className=\"text-foreground-neutral-base\">\n {model.jobName}\n </Text>\n </div>\n ) : null}\n\n {model.entries.length === 0 ? (\n <StepListEmptyStateView emptyState={emptyState} />\n ) : (\n <Accordion\n type=\"multiple\"\n value={selectedAttemptIds}\n onValueChange={selectAttempt}\n asChild\n >\n <ol>\n {model.entries.map((entry, index) => {\n const selected = selectedAttemptIds.includes(entry.id);\n return (\n <StepRow\n key={entry.id}\n entry={entry}\n selected={selected}\n hasExpandedContent={hasExpandedContent}\n isLast={index === model.entries.length - 1}\n onSelect={() => {\n selectAttempt(\n hasExpandedContent\n ? toggleAttemptId(selectedAttemptIds, entry.id)\n : selected\n ? []\n : [entry.id],\n );\n }}\n onInspect={\n onInspectorOpenChange && !entry.carriedOver\n ? () => onInspectorOpenChange(entry.id)\n : undefined\n }\n expandedContent={\n selected\n ? renderExpandedStep?.({\n step: entry.step,\n stepId: entry.step.id,\n stepLabel: entry.step.label,\n sourceLocation: entry.step.sourceLocation,\n attempt: entry.attempt,\n attemptId: entry.id,\n attemptStartedAt: entry.startedAt,\n attemptError: entry.error,\n attemptStatus: entry.statusVisual.kind,\n carriedOver: entry.carriedOver,\n })\n : null\n }\n />\n );\n })}\n </ol>\n </Accordion>\n )}\n </section>\n {inspectorEntry ? renderInspector?.(inspectorEntry) : null}\n </TimeTickerProvider>\n );\n}\n\nfunction StepListEmptyStateView({\n emptyState = {\n title: 'No steps recorded',\n description: 'This job has not recorded any steps.',\n },\n}: {\n emptyState?: StepListEmptyState | undefined;\n}) {\n if (!emptyState.status) {\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\n return (\n <div className=\"flex min-h-120 flex-col items-center justify-center gap-inline p-panel\">\n <StepListEmptyStateIcon status={emptyState.status} />\n <div className=\"text-center\">\n <Text size=\"sm\" className=\"text-foreground-neutral-subtle\">\n {emptyState.title}\n </Text>\n <Text size=\"xs\" className=\"text-foreground-neutral-muted\">\n {emptyState.description}\n </Text>\n </div>\n </div>\n );\n}\n\nfunction StepListEmptyStateIcon({status}: {status: JobDisplayStatus}) {\n if (status !== 'running') {\n return (\n <div className=\"flex size-32 items-center justify-center rounded-6 border border-border-neutral-strong bg-background-neutral-base p-tight\">\n <WorkflowStatusIcon status={status} size={20} tooltip={false} />\n </div>\n );\n }\n\n return (\n <div className=\"flex size-32 items-center justify-center rounded-6 border border-border-neutral-strong bg-background-neutral-base p-tight text-foreground-neutral-muted\">\n <Icon name=\"timerLine\" size={18} aria-hidden=\"true\" />\n </div>\n );\n}\n\nfunction StepRow({\n entry,\n selected,\n hasExpandedContent,\n isLast,\n onSelect,\n onInspect,\n expandedContent,\n}: {\n entry: StepListEntryModel;\n selected: boolean;\n hasExpandedContent: boolean;\n isLast: boolean;\n onSelect: () => void;\n onInspect?: (() => void) | undefined;\n expandedContent: ReactNode;\n}) {\n const shouldShowLabelTooltip = entry.step.label.length > 32;\n const rowContent = (\n <>\n <Icon\n name=\"chevronRight\"\n aria-hidden=\"true\"\n className={cn(\n 'size-14 shrink-0 text-foreground-neutral-muted transition-transform',\n selected && 'rotate-90',\n )}\n />\n <StepStatusIcon entry={entry} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex min-w-0 items-center gap-inline\">\n <Text size=\"sm\" bold className=\"truncate text-foreground-neutral-base\">\n {entry.step.label}\n </Text>\n {entry.step.attempts.length > 1 ? <StepAttemptChip attempt={entry} /> : null}\n {entry.carriedOver ? <CarriedOverBadge /> : null}\n </div>\n </div>\n <StepAttemptDurationLabel attempt={entry} />\n </>\n );\n const rowClasses = cn(\n 'group flex min-h-44 min-w-0 flex-1 items-center gap-x-inline bg-transparent px-row py-row text-left transition-colors hover:bg-transparent active:bg-transparent focus-visible:shadow-border-interactive-with-active focus-visible:outline-none',\n entry.carriedOver && 'opacity-[0.55]',\n );\n const button = hasExpandedContent ? (\n <AccordionTrigger\n showIcon={false}\n aria-label={entryAccessibleLabel(entry)}\n className={rowClasses}\n >\n {rowContent}\n </AccordionTrigger>\n ) : (\n <button\n type=\"button\"\n aria-expanded={false}\n aria-label={entryAccessibleLabel(entry)}\n onClick={onSelect}\n className={rowClasses}\n >\n {rowContent}\n </button>\n );\n const triggerNode = shouldShowLabelTooltip ? (\n <Tooltip>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent>\n <span className=\"block max-w-320 break-words\">{entry.step.label}</span>\n </TooltipContent>\n </Tooltip>\n ) : (\n button\n );\n const row = (\n <>\n <div\n className={cn(\n 'group flex min-w-0 items-center gap-inline pr-[8px] transition-colors hover:bg-background-components-hover active:bg-background-components-pressed',\n selected && 'bg-background-components-hover',\n !hasExpandedContent && ['border-b border-border-neutral-base', isLast && 'border-b-0'],\n )}\n >\n <div className=\"min-w-0 flex-1\">{triggerNode}</div>\n {onInspect ? (\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n type=\"button\"\n aria-label={`Inspect ${entry.step.label}, attempt ${entry.attempt}`}\n onClick={onInspect}\n className=\"flex size-28 shrink-0 items-center justify-center rounded-4 bg-transparent text-foreground-neutral-muted outline-none transition-colors hover:bg-transparent hover:text-foreground-neutral-base active:bg-transparent focus-visible:shadow-button-neutral-focus\"\n >\n <Icon name=\"informationLine\" size={14} aria-hidden=\"true\" />\n </button>\n </TooltipTrigger>\n <TooltipContent>Inspect step details</TooltipContent>\n </Tooltip>\n ) : null}\n </div>\n {selected && expandedContent ? (\n <AccordionContent className=\"border-t border-border-neutral-base bg-transparent px-0 py-0\">\n <div className=\"min-w-0\">{expandedContent}</div>\n </AccordionContent>\n ) : null}\n </>\n );\n\n if (hasExpandedContent) {\n return (\n <AccordionItem value={entry.id} asChild>\n <li>{row}</li>\n </AccordionItem>\n );\n }\n\n return <li>{row}</li>;\n}\n\nfunction CarriedOverBadge() {\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <span className=\"shrink-0\">\n <Badge variant=\"neutral\" size=\"2xs\">\n reused\n </Badge>\n </span>\n </TooltipTrigger>\n <TooltipContent>\n Carried over from a previous attempt; did not run in this attempt.\n </TooltipContent>\n </Tooltip>\n );\n}\n\nfunction toggleAttemptId(selectedAttemptIds: readonly string[], attemptId: string): string[] {\n if (selectedAttemptIds.includes(attemptId)) {\n return selectedAttemptIds.filter((selectedAttemptId) => selectedAttemptId !== attemptId);\n }\n return [...selectedAttemptIds, attemptId];\n}\n\nfunction nextSelectedAttemptId(\n selectedAttemptIds: readonly string[],\n nextAttemptIds: readonly string[],\n): string | undefined {\n if (nextAttemptIds.length === 0) return undefined;\n\n const openedAttemptId = nextAttemptIds.find(\n (attemptId) => !selectedAttemptIds.includes(attemptId),\n );\n return openedAttemptId ?? nextAttemptIds.at(-1);\n}\n\nfunction StepStatusIcon({entry}: {entry: StepListEntryModel}) {\n if (isWorkflowStatus(entry.statusVisual.kind)) {\n return (\n <WorkflowStatusIcon\n status={entry.statusVisual.kind}\n ripple={entry.statusVisual.ripple}\n size={14}\n />\n );\n }\n\n return (\n <span role=\"img\" aria-label={entry.statusVisual.label} className=\"inline-flex shrink-0\">\n <Dot\n variant={entry.statusVisual.dot}\n ripple={entry.statusVisual.ripple}\n className=\"size-12 shrink-0\"\n />\n </span>\n );\n}\n\nconst attemptChipClasses: Record<NonNullable<BadgeVariant>, string> = {\n neutral: 'bg-tag-neutral-bg border-tag-neutral-border',\n info: 'bg-tag-blue-bg border-tag-blue-border',\n feature: 'bg-tag-purple-bg border-tag-purple-border',\n success: 'bg-tag-success-bg border-tag-success-border',\n warning: 'bg-tag-warning-bg border-tag-warning-border',\n error: 'bg-tag-error-bg border-tag-error-border',\n};\n\nfunction StepAttemptChip({attempt}: {attempt: StepAttemptModel}) {\n return (\n <div className=\"flex shrink-0 items-center gap-tight\" aria-hidden=\"true\">\n <span\n className={cn(\n 'inline-flex h-18 min-w-24 items-center justify-center rounded-4 border px-tight font-code text-xs leading-16 text-foreground-neutral-base',\n attemptChipClasses[attempt.statusVisual.badge ?? 'neutral'],\n )}\n >\n #{attempt.attempt}\n </span>\n </div>\n );\n}\n\nfunction entryAccessibleLabel(entry: StepListEntryModel): string {\n const parts = [entry.step.label, entry.statusVisual.label, `attempt ${entry.attempt}`];\n if (entry.step.error?.category) parts.push(humanizeStatus(entry.step.error.category));\n return parts.join(', ');\n}\n\nfunction StepAttemptDurationLabel({attempt}: {attempt: StepAttemptModel}) {\n const duration = attempt.displayDuration;\n if (duration === null) return null;\n\n if (duration.state === 'live') {\n return <LiveDurationText fromIso={duration.fromIso} />;\n }\n\n return <DurationText>{formatJobExecutionTimeLabel(duration)}</DurationText>;\n}\n\nfunction LiveDurationText({fromIso}: {fromIso: string}) {\n useTimeTick();\n return <DurationText>{humanDuration(fromIso)}</DurationText>;\n}\n\nfunction DurationText({children}: {children: string}) {\n return (\n <Code as=\"span\" variant=\"label\" className=\"shrink-0 tabular-nums text-foreground-neutral-muted\">\n {children}\n </Code>\n );\n}\n"],"names":["Accordion","AccordionContent","AccordionItem","AccordionTrigger","Badge","Dot","EmptyState","Icon","TimeTickerProvider","useTimeTick","Tooltip","TooltipContent","TooltipTrigger","Code","Text","cn","humanDuration","useEffect","useId","useMemo","useRef","useState","WorkflowStatusIcon","isWorkflowStatus","formatJobExecutionTimeLabel","buildStepListModel","defaultStepListJobExecution","humanizeStatus","StepList","job","jobExecution","selectedAttemptId","defaultSelectedAttemptId","onSelectedAttemptChange","inspectorOpenAttemptId","onInspectorOpenChange","autoSelectActiveAttempt","emptyState","renderExpandedStep","renderInspector","showHeader","className","selectedJobExecution","model","StepListContent","jobExecutionId","titleId","localSelectedAttemptIds","setLocalSelectedAttemptIds","userSelectedAttempt","setUserSelectedAttempt","lastNotifiedSelectedAttemptId","autoSelectedAttemptIdRef","undefined","previousSelectedAttemptIdRef","shouldUseControlledCollapsedState","current","autoSelectedAttemptId","activeEntryId","autoSelectedAttemptIds","selectedAttemptIds","length","hasExpandedContent","wasControlled","nextSelectedAttemptId","entries","some","entry","id","carriedOver","selectAttempt","nextAttemptIds","nextAttemptId","inspectorEntry","find","intervalMs","reducedMotionIntervalMs","section","aria-labelledby","div","as","size","bold","jobName","StepListEmptyStateView","type","value","onValueChange","asChild","ol","map","index","selected","includes","StepRow","isLast","onSelect","toggleAttemptId","onInspect","expandedContent","step","stepId","stepLabel","label","sourceLocation","attempt","attemptId","attemptStartedAt","startedAt","attemptError","error","attemptStatus","statusVisual","kind","title","description","status","icon","variant","StepListEmptyStateIcon","tooltip","name","aria-hidden","shouldShowLabelTooltip","rowContent","StepStatusIcon","attempts","StepAttemptChip","CarriedOverBadge","StepAttemptDurationLabel","rowClasses","button","showIcon","aria-label","entryAccessibleLabel","aria-expanded","onClick","triggerNode","span","row","li","filter","openedAttemptId","at","ripple","role","dot","attemptChipClasses","neutral","info","feature","success","warning","badge","parts","category","push","join","duration","displayDuration","state","LiveDurationText","fromIso","DurationText","children"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/components/step-list/step-list.tsx"],"sourcesContent":["import {\n Accordion,\n AccordionContent,\n AccordionItem,\n AccordionTrigger,\n} from '@shipfox/react-ui/accordion';\nimport {Badge, type BadgeVariant} from '@shipfox/react-ui/badge';\nimport {Dot} from '@shipfox/react-ui/dot';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {TimeTickerProvider, useTimeTick} from '@shipfox/react-ui/time-ticker';\nimport {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Code, Text} from '@shipfox/react-ui/typography';\nimport {cn, humanDuration} from '@shipfox/react-ui/utils';\nimport type {ReactNode} from 'react';\nimport {useEffect, useId, useMemo, useRef, useState} from 'react';\nimport {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';\nimport {\n isWorkflowStatus,\n type Job,\n type JobDisplayStatus,\n type JobExecution,\n type Step,\n type StepSourceLocation,\n} from '#core/workflow-run.js';\nimport {formatJobExecutionTimeLabel} from '../job-graph/job-duration-format.js';\nimport {\n buildStepListModel,\n defaultStepListJobExecution,\n humanizeStatus,\n type StepAttemptModel,\n type StepListEntryModel,\n type StepListModel,\n} from './step-list-model.js';\n\nexport interface StepExpandedContext {\n step: Step;\n stepId: string;\n stepLabel: string;\n sourceLocation: StepSourceLocation | null;\n attempt: number;\n attemptId: string;\n attemptStartedAt: string;\n attemptError: Record<string, unknown> | null;\n attemptStatus: string;\n carriedOver: boolean;\n}\n\nexport interface StepListEmptyState {\n title: string;\n description: string;\n status?: JobDisplayStatus | undefined;\n}\n\nexport interface StepListProps {\n job: Job;\n jobExecution?: JobExecution | undefined;\n selectedAttemptId?: string | null | undefined;\n defaultSelectedAttemptId?: string | undefined;\n onSelectedAttemptChange?: ((attemptId: string | undefined) => void) | undefined;\n onExpandedAttemptIdsChange?: ((attemptIds: readonly string[]) => void) | undefined;\n inspectorOpenAttemptId?: string | null | undefined;\n onInspectorOpenChange?: ((attemptId: string | null) => void) | undefined;\n autoSelectActiveAttempt?: boolean | undefined;\n emptyState?: StepListEmptyState | undefined;\n renderExpandedStep?: ((context: StepExpandedContext) => ReactNode) | undefined;\n renderInspector?: ((entry: StepListEntryModel) => ReactNode) | undefined;\n showHeader?: boolean | undefined;\n className?: string | undefined;\n}\n\nexport function StepList({\n job,\n jobExecution,\n selectedAttemptId,\n defaultSelectedAttemptId,\n onSelectedAttemptChange,\n onExpandedAttemptIdsChange,\n inspectorOpenAttemptId = null,\n onInspectorOpenChange,\n autoSelectActiveAttempt = false,\n emptyState,\n renderExpandedStep,\n renderInspector,\n showHeader = true,\n className,\n}: StepListProps) {\n const selectedJobExecution = jobExecution ?? defaultStepListJobExecution(job);\n const model = useMemo(\n () => buildStepListModel({job, jobExecution: selectedJobExecution}),\n [job, selectedJobExecution],\n );\n\n return (\n <StepListContent\n key={model.jobExecutionId}\n model={model}\n selectedAttemptId={selectedAttemptId}\n defaultSelectedAttemptId={defaultSelectedAttemptId}\n onSelectedAttemptChange={onSelectedAttemptChange}\n onExpandedAttemptIdsChange={onExpandedAttemptIdsChange}\n inspectorOpenAttemptId={inspectorOpenAttemptId}\n onInspectorOpenChange={onInspectorOpenChange}\n autoSelectActiveAttempt={autoSelectActiveAttempt}\n emptyState={emptyState}\n renderExpandedStep={renderExpandedStep}\n renderInspector={renderInspector}\n showHeader={showHeader}\n className={className}\n />\n );\n}\n\nfunction StepListContent({\n model,\n selectedAttemptId,\n defaultSelectedAttemptId,\n onSelectedAttemptChange,\n onExpandedAttemptIdsChange,\n inspectorOpenAttemptId = null,\n onInspectorOpenChange,\n autoSelectActiveAttempt,\n emptyState,\n renderExpandedStep,\n renderInspector,\n showHeader,\n className,\n}: Omit<StepListProps, 'job' | 'jobExecution'> & {model: StepListModel}) {\n const titleId = useId();\n const [localSelectedAttemptIds, setLocalSelectedAttemptIds] = useState<string[]>(() =>\n selectedAttemptId\n ? [selectedAttemptId]\n : defaultSelectedAttemptId\n ? [defaultSelectedAttemptId]\n : [],\n );\n const [userSelectedAttempt, setUserSelectedAttempt] = useState(false);\n const lastNotifiedSelectedAttemptId = useRef<string | null>(null);\n const autoSelectedAttemptIdRef = useRef<string | undefined>(undefined);\n const previousSelectedAttemptIdRef = useRef<string | null | undefined>(selectedAttemptId);\n const lastReportedExpandedAttemptIdsKeyRef = useRef<string | null>(null);\n const shouldUseControlledCollapsedState =\n selectedAttemptId === null && lastNotifiedSelectedAttemptId.current === null;\n const autoSelectedAttemptId =\n selectedAttemptId === undefined && autoSelectActiveAttempt && !userSelectedAttempt\n ? (autoSelectedAttemptIdRef.current ?? model.activeEntryId)\n : undefined;\n const autoSelectedAttemptIds = useMemo(\n () => (autoSelectedAttemptId ? [autoSelectedAttemptId] : []),\n [autoSelectedAttemptId],\n );\n const selectedAttemptIds = useMemo(\n () =>\n shouldUseControlledCollapsedState\n ? []\n : localSelectedAttemptIds.length > 0\n ? localSelectedAttemptIds\n : autoSelectedAttemptIds,\n [autoSelectedAttemptIds, localSelectedAttemptIds, shouldUseControlledCollapsedState],\n );\n const hasExpandedContent = renderExpandedStep !== undefined;\n\n useEffect(() => {\n const key = selectedAttemptIds.join('|');\n if (lastReportedExpandedAttemptIdsKeyRef.current === key) return;\n lastReportedExpandedAttemptIdsKeyRef.current = key;\n onExpandedAttemptIdsChange?.(selectedAttemptIds);\n }, [onExpandedAttemptIdsChange, selectedAttemptIds]);\n\n useEffect(() => {\n if (selectedAttemptId !== undefined) {\n previousSelectedAttemptIdRef.current = selectedAttemptId;\n return;\n }\n\n // A changing landing candidate is normal while a live job polls. Only reset the local\n // accordion when the parent has just cleared a previously controlled URL selection.\n const wasControlled = previousSelectedAttemptIdRef.current !== undefined;\n previousSelectedAttemptIdRef.current = undefined;\n if (!wasControlled) return;\n\n setLocalSelectedAttemptIds(defaultSelectedAttemptId ? [defaultSelectedAttemptId] : []);\n setUserSelectedAttempt(false);\n autoSelectedAttemptIdRef.current = undefined;\n }, [defaultSelectedAttemptId, selectedAttemptId]);\n\n useEffect(() => {\n if (\n selectedAttemptId !== undefined ||\n !autoSelectActiveAttempt ||\n userSelectedAttempt ||\n localSelectedAttemptIds.length > 0 ||\n autoSelectedAttemptIdRef.current !== undefined\n ) {\n return;\n }\n if (model.activeEntryId) autoSelectedAttemptIdRef.current = model.activeEntryId;\n }, [\n autoSelectActiveAttempt,\n localSelectedAttemptIds.length,\n model.activeEntryId,\n selectedAttemptId,\n userSelectedAttempt,\n ]);\n\n useEffect(() => {\n if (selectedAttemptId === undefined) return;\n\n const nextSelectedAttemptId = selectedAttemptId ?? null;\n if (lastNotifiedSelectedAttemptId.current === nextSelectedAttemptId) {\n lastNotifiedSelectedAttemptId.current = null;\n return;\n }\n\n setLocalSelectedAttemptIds(selectedAttemptId ? [selectedAttemptId] : []);\n setUserSelectedAttempt(true);\n }, [selectedAttemptId]);\n\n useEffect(() => {\n if (\n inspectorOpenAttemptId !== null &&\n !model.entries.some((entry) => entry.id === inspectorOpenAttemptId && !entry.carriedOver)\n ) {\n onInspectorOpenChange?.(null);\n }\n }, [inspectorOpenAttemptId, model.entries, onInspectorOpenChange]);\n\n function selectAttempt(nextAttemptIds: string[]) {\n const nextAttemptId = nextSelectedAttemptId(selectedAttemptIds, nextAttemptIds);\n setUserSelectedAttempt(true);\n setLocalSelectedAttemptIds(nextAttemptIds);\n lastNotifiedSelectedAttemptId.current = nextAttemptId ?? null;\n onSelectedAttemptChange?.(nextAttemptId);\n }\n\n const inspectorEntry = model.entries.find(\n (entry) => entry.id === inspectorOpenAttemptId && !entry.carriedOver,\n );\n\n return (\n <TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>\n <section\n aria-labelledby={showHeader ? titleId : undefined}\n className={cn(\n 'flex min-h-0 flex-col rounded-8 border border-border-neutral-base bg-background-neutral-base',\n className,\n )}\n >\n {showHeader ? (\n <div className=\"flex min-h-40 items-center border-b border-border-neutral-base px-row py-row\">\n <Text as=\"h2\" id={titleId} size=\"sm\" bold className=\"text-foreground-neutral-base\">\n {model.jobName}\n </Text>\n </div>\n ) : null}\n\n {model.entries.length === 0 ? (\n <StepListEmptyStateView emptyState={emptyState} />\n ) : (\n <Accordion\n type=\"multiple\"\n value={selectedAttemptIds}\n onValueChange={selectAttempt}\n asChild\n >\n <ol>\n {model.entries.map((entry, index) => {\n const selected = selectedAttemptIds.includes(entry.id);\n return (\n <StepRow\n key={entry.id}\n entry={entry}\n selected={selected}\n hasExpandedContent={hasExpandedContent}\n isLast={index === model.entries.length - 1}\n onSelect={() => {\n selectAttempt(\n hasExpandedContent\n ? toggleAttemptId(selectedAttemptIds, entry.id)\n : selected\n ? []\n : [entry.id],\n );\n }}\n onInspect={\n onInspectorOpenChange && !entry.carriedOver\n ? () => onInspectorOpenChange(entry.id)\n : undefined\n }\n expandedContent={\n selected\n ? renderExpandedStep?.({\n step: entry.step,\n stepId: entry.step.id,\n stepLabel: entry.step.label,\n sourceLocation: entry.step.sourceLocation,\n attempt: entry.attempt,\n attemptId: entry.id,\n attemptStartedAt: entry.startedAt,\n attemptError: entry.error,\n attemptStatus: entry.statusVisual.kind,\n carriedOver: entry.carriedOver,\n })\n : null\n }\n />\n );\n })}\n </ol>\n </Accordion>\n )}\n </section>\n {inspectorEntry ? renderInspector?.(inspectorEntry) : null}\n </TimeTickerProvider>\n );\n}\n\nfunction StepListEmptyStateView({\n emptyState = {\n title: 'No steps recorded',\n description: 'This job has not recorded any steps.',\n },\n}: {\n emptyState?: StepListEmptyState | undefined;\n}) {\n if (!emptyState.status) {\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\n return (\n <div className=\"flex min-h-120 flex-col items-center justify-center gap-inline p-panel\">\n <StepListEmptyStateIcon status={emptyState.status} />\n <div className=\"text-center\">\n <Text size=\"sm\" className=\"text-foreground-neutral-subtle\">\n {emptyState.title}\n </Text>\n <Text size=\"xs\" className=\"text-foreground-neutral-muted\">\n {emptyState.description}\n </Text>\n </div>\n </div>\n );\n}\n\nfunction StepListEmptyStateIcon({status}: {status: JobDisplayStatus}) {\n if (status !== 'running') {\n return (\n <div className=\"flex size-32 items-center justify-center rounded-6 border border-border-neutral-strong bg-background-neutral-base p-tight\">\n <WorkflowStatusIcon status={status} size={20} tooltip={false} />\n </div>\n );\n }\n\n return (\n <div className=\"flex size-32 items-center justify-center rounded-6 border border-border-neutral-strong bg-background-neutral-base p-tight text-foreground-neutral-muted\">\n <Icon name=\"timerLine\" size={18} aria-hidden=\"true\" />\n </div>\n );\n}\n\nfunction StepRow({\n entry,\n selected,\n hasExpandedContent,\n isLast,\n onSelect,\n onInspect,\n expandedContent,\n}: {\n entry: StepListEntryModel;\n selected: boolean;\n hasExpandedContent: boolean;\n isLast: boolean;\n onSelect: () => void;\n onInspect?: (() => void) | undefined;\n expandedContent: ReactNode;\n}) {\n const shouldShowLabelTooltip = entry.step.label.length > 32;\n const rowContent = (\n <>\n <Icon\n name=\"chevronRight\"\n aria-hidden=\"true\"\n className={cn(\n 'size-14 shrink-0 text-foreground-neutral-muted transition-transform',\n selected && 'rotate-90',\n )}\n />\n <StepStatusIcon entry={entry} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex min-w-0 items-center gap-inline\">\n <Text size=\"sm\" bold className=\"truncate text-foreground-neutral-base\">\n {entry.step.label}\n </Text>\n {entry.step.attempts.length > 1 ? <StepAttemptChip attempt={entry} /> : null}\n {entry.carriedOver ? <CarriedOverBadge /> : null}\n </div>\n </div>\n <StepAttemptDurationLabel attempt={entry} />\n </>\n );\n const rowClasses = cn(\n 'group flex min-h-44 min-w-0 flex-1 items-center gap-x-inline bg-transparent px-row py-row text-left transition-colors hover:bg-transparent active:bg-transparent focus-visible:shadow-border-interactive-with-active focus-visible:outline-none',\n entry.carriedOver && 'opacity-[0.55]',\n );\n const button = hasExpandedContent ? (\n <AccordionTrigger\n showIcon={false}\n aria-label={entryAccessibleLabel(entry)}\n className={rowClasses}\n >\n {rowContent}\n </AccordionTrigger>\n ) : (\n <button\n type=\"button\"\n aria-expanded={false}\n aria-label={entryAccessibleLabel(entry)}\n onClick={onSelect}\n className={rowClasses}\n >\n {rowContent}\n </button>\n );\n const triggerNode = shouldShowLabelTooltip ? (\n <Tooltip>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent>\n <span className=\"block max-w-320 break-words\">{entry.step.label}</span>\n </TooltipContent>\n </Tooltip>\n ) : (\n button\n );\n const row = (\n <>\n <div\n className={cn(\n 'group flex min-w-0 items-center gap-inline bg-background-neutral-base pr-[8px] transition-colors hover:bg-background-neutral-hover active:bg-background-neutral-pressed',\n selected && 'bg-background-neutral-hover',\n !hasExpandedContent && ['border-b border-border-neutral-base', isLast && 'border-b-0'],\n )}\n >\n <div className=\"min-w-0 flex-1\">{triggerNode}</div>\n {onInspect ? (\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n type=\"button\"\n aria-label={`Inspect ${entry.step.label}, attempt ${entry.attempt}`}\n onClick={onInspect}\n className=\"flex size-28 shrink-0 items-center justify-center rounded-4 bg-transparent text-foreground-neutral-muted outline-none transition-colors hover:bg-transparent hover:text-foreground-neutral-base active:bg-transparent focus-visible:shadow-button-neutral-focus\"\n >\n <Icon name=\"informationLine\" size={14} aria-hidden=\"true\" />\n </button>\n </TooltipTrigger>\n <TooltipContent>Inspect step details</TooltipContent>\n </Tooltip>\n ) : null}\n </div>\n {selected && expandedContent ? (\n <AccordionContent className=\"border-t border-border-neutral-base bg-transparent px-0 py-0\">\n <div className=\"min-w-0\">{expandedContent}</div>\n </AccordionContent>\n ) : null}\n </>\n );\n\n if (hasExpandedContent) {\n return (\n <AccordionItem value={entry.id} asChild>\n <li>{row}</li>\n </AccordionItem>\n );\n }\n\n return <li>{row}</li>;\n}\n\nfunction CarriedOverBadge() {\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <span className=\"shrink-0\">\n <Badge variant=\"neutral\" size=\"2xs\">\n reused\n </Badge>\n </span>\n </TooltipTrigger>\n <TooltipContent>\n Carried over from a previous attempt; did not run in this attempt.\n </TooltipContent>\n </Tooltip>\n );\n}\n\nfunction toggleAttemptId(selectedAttemptIds: readonly string[], attemptId: string): string[] {\n if (selectedAttemptIds.includes(attemptId)) {\n return selectedAttemptIds.filter((selectedAttemptId) => selectedAttemptId !== attemptId);\n }\n return [...selectedAttemptIds, attemptId];\n}\n\nfunction nextSelectedAttemptId(\n selectedAttemptIds: readonly string[],\n nextAttemptIds: readonly string[],\n): string | undefined {\n if (nextAttemptIds.length === 0) return undefined;\n\n const openedAttemptId = nextAttemptIds.find(\n (attemptId) => !selectedAttemptIds.includes(attemptId),\n );\n return openedAttemptId ?? nextAttemptIds.at(-1);\n}\n\nfunction StepStatusIcon({entry}: {entry: StepListEntryModel}) {\n if (isWorkflowStatus(entry.statusVisual.kind)) {\n return (\n <WorkflowStatusIcon\n status={entry.statusVisual.kind}\n ripple={entry.statusVisual.ripple}\n size={14}\n />\n );\n }\n\n return (\n <span role=\"img\" aria-label={entry.statusVisual.label} className=\"inline-flex shrink-0\">\n <Dot\n variant={entry.statusVisual.dot}\n ripple={entry.statusVisual.ripple}\n className=\"size-12 shrink-0\"\n />\n </span>\n );\n}\n\nconst attemptChipClasses: Record<NonNullable<BadgeVariant>, string> = {\n neutral: 'bg-tag-neutral-bg border-tag-neutral-border',\n info: 'bg-tag-blue-bg border-tag-blue-border',\n feature: 'bg-tag-purple-bg border-tag-purple-border',\n success: 'bg-tag-success-bg border-tag-success-border',\n warning: 'bg-tag-warning-bg border-tag-warning-border',\n error: 'bg-tag-error-bg border-tag-error-border',\n};\n\nfunction StepAttemptChip({attempt}: {attempt: StepAttemptModel}) {\n return (\n <div className=\"flex shrink-0 items-center gap-tight\" aria-hidden=\"true\">\n <span\n className={cn(\n 'inline-flex h-18 min-w-24 items-center justify-center rounded-4 border px-tight font-code text-xs leading-16 text-foreground-neutral-base',\n attemptChipClasses[attempt.statusVisual.badge ?? 'neutral'],\n )}\n >\n #{attempt.attempt}\n </span>\n </div>\n );\n}\n\nfunction entryAccessibleLabel(entry: StepListEntryModel): string {\n const parts = [entry.step.label, entry.statusVisual.label, `attempt ${entry.attempt}`];\n if (entry.step.error?.category) parts.push(humanizeStatus(entry.step.error.category));\n return parts.join(', ');\n}\n\nfunction StepAttemptDurationLabel({attempt}: {attempt: StepAttemptModel}) {\n const duration = attempt.displayDuration;\n if (duration === null) return null;\n\n if (duration.state === 'live') {\n return <LiveDurationText fromIso={duration.fromIso} />;\n }\n\n return <DurationText>{formatJobExecutionTimeLabel(duration)}</DurationText>;\n}\n\nfunction LiveDurationText({fromIso}: {fromIso: string}) {\n useTimeTick();\n return <DurationText>{humanDuration(fromIso)}</DurationText>;\n}\n\nfunction DurationText({children}: {children: string}) {\n return (\n <Code as=\"span\" variant=\"label\" className=\"shrink-0 tabular-nums text-foreground-neutral-muted\">\n {children}\n </Code>\n );\n}\n"],"names":["Accordion","AccordionContent","AccordionItem","AccordionTrigger","Badge","Dot","EmptyState","Icon","TimeTickerProvider","useTimeTick","Tooltip","TooltipContent","TooltipTrigger","Code","Text","cn","humanDuration","useEffect","useId","useMemo","useRef","useState","WorkflowStatusIcon","isWorkflowStatus","formatJobExecutionTimeLabel","buildStepListModel","defaultStepListJobExecution","humanizeStatus","StepList","job","jobExecution","selectedAttemptId","defaultSelectedAttemptId","onSelectedAttemptChange","onExpandedAttemptIdsChange","inspectorOpenAttemptId","onInspectorOpenChange","autoSelectActiveAttempt","emptyState","renderExpandedStep","renderInspector","showHeader","className","selectedJobExecution","model","StepListContent","jobExecutionId","titleId","localSelectedAttemptIds","setLocalSelectedAttemptIds","userSelectedAttempt","setUserSelectedAttempt","lastNotifiedSelectedAttemptId","autoSelectedAttemptIdRef","undefined","previousSelectedAttemptIdRef","lastReportedExpandedAttemptIdsKeyRef","shouldUseControlledCollapsedState","current","autoSelectedAttemptId","activeEntryId","autoSelectedAttemptIds","selectedAttemptIds","length","hasExpandedContent","key","join","wasControlled","nextSelectedAttemptId","entries","some","entry","id","carriedOver","selectAttempt","nextAttemptIds","nextAttemptId","inspectorEntry","find","intervalMs","reducedMotionIntervalMs","section","aria-labelledby","div","as","size","bold","jobName","StepListEmptyStateView","type","value","onValueChange","asChild","ol","map","index","selected","includes","StepRow","isLast","onSelect","toggleAttemptId","onInspect","expandedContent","step","stepId","stepLabel","label","sourceLocation","attempt","attemptId","attemptStartedAt","startedAt","attemptError","error","attemptStatus","statusVisual","kind","title","description","status","icon","variant","StepListEmptyStateIcon","tooltip","name","aria-hidden","shouldShowLabelTooltip","rowContent","StepStatusIcon","attempts","StepAttemptChip","CarriedOverBadge","StepAttemptDurationLabel","rowClasses","button","showIcon","aria-label","entryAccessibleLabel","aria-expanded","onClick","triggerNode","span","row","li","filter","openedAttemptId","at","ripple","role","dot","attemptChipClasses","neutral","info","feature","success","warning","badge","parts","category","push","duration","displayDuration","state","LiveDurationText","fromIso","DurationText","children"],"mappings":";AAAA,SACEA,SAAS,EACTC,gBAAgB,EAChBC,aAAa,EACbC,gBAAgB,QACX,8BAA8B;AACrC,SAAQC,KAAK,QAA0B,0BAA0B;AACjE,SAAQC,GAAG,QAAO,wBAAwB;AAC1C,SAAQC,UAAU,QAAO,gCAAgC;AACzD,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,kBAAkB,EAAEC,WAAW,QAAO,gCAAgC;AAC9E,SAAQC,OAAO,EAAEC,cAAc,EAAEC,cAAc,QAAO,4BAA4B;AAClF,SAAQC,IAAI,EAAEC,IAAI,QAAO,+BAA+B;AACxD,SAAQC,EAAE,EAAEC,aAAa,QAAO,0BAA0B;AAE1D,SAAQC,SAAS,EAAEC,KAAK,EAAEC,OAAO,EAAEC,MAAM,EAAEC,QAAQ,QAAO,QAAQ;AAClE,SAAQC,kBAAkB,QAAO,sDAAsD;AACvF,SACEC,gBAAgB,QAMX,wBAAwB;AAC/B,SAAQC,2BAA2B,QAAO,sCAAsC;AAChF,SACEC,kBAAkB,EAClBC,2BAA2B,EAC3BC,cAAc,QAIT,uBAAuB;AAsC9B,OAAO,SAASC,SAAS,EACvBC,GAAG,EACHC,YAAY,EACZC,iBAAiB,EACjBC,wBAAwB,EACxBC,uBAAuB,EACvBC,0BAA0B,EAC1BC,yBAAyB,IAAI,EAC7BC,qBAAqB,EACrBC,0BAA0B,KAAK,EAC/BC,UAAU,EACVC,kBAAkB,EAClBC,eAAe,EACfC,aAAa,IAAI,EACjBC,SAAS,EACK;IACd,MAAMC,uBAAuBb,gBAAgBJ,4BAA4BG;IACzE,MAAMe,QAAQzB,QACZ,IAAMM,mBAAmB;YAACI;YAAKC,cAAca;QAAoB,IACjE;QAACd;QAAKc;KAAqB;IAG7B,qBACE,KAACE;QAECD,OAAOA;QACPb,mBAAmBA;QACnBC,0BAA0BA;QAC1BC,yBAAyBA;QACzBC,4BAA4BA;QAC5BC,wBAAwBA;QACxBC,uBAAuBA;QACvBC,yBAAyBA;QACzBC,YAAYA;QACZC,oBAAoBA;QACpBC,iBAAiBA;QACjBC,YAAYA;QACZC,WAAWA;OAbNE,MAAME,cAAc;AAgB/B;AAEA,SAASD,gBAAgB,EACvBD,KAAK,EACLb,iBAAiB,EACjBC,wBAAwB,EACxBC,uBAAuB,EACvBC,0BAA0B,EAC1BC,yBAAyB,IAAI,EAC7BC,qBAAqB,EACrBC,uBAAuB,EACvBC,UAAU,EACVC,kBAAkB,EAClBC,eAAe,EACfC,UAAU,EACVC,SAAS,EAC4D;IACrE,MAAMK,UAAU7B;IAChB,MAAM,CAAC8B,yBAAyBC,2BAA2B,GAAG5B,SAAmB,IAC/EU,oBACI;YAACA;SAAkB,GACnBC,2BACE;YAACA;SAAyB,GAC1B,EAAE;IAEV,MAAM,CAACkB,qBAAqBC,uBAAuB,GAAG9B,SAAS;IAC/D,MAAM+B,gCAAgChC,OAAsB;IAC5D,MAAMiC,2BAA2BjC,OAA2BkC;IAC5D,MAAMC,+BAA+BnC,OAAkCW;IACvE,MAAMyB,uCAAuCpC,OAAsB;IACnE,MAAMqC,oCACJ1B,sBAAsB,QAAQqB,8BAA8BM,OAAO,KAAK;IAC1E,MAAMC,wBACJ5B,sBAAsBuB,aAAajB,2BAA2B,CAACa,sBAC1DG,yBAAyBK,OAAO,IAAId,MAAMgB,aAAa,GACxDN;IACN,MAAMO,yBAAyB1C,QAC7B,IAAOwC,wBAAwB;YAACA;SAAsB,GAAG,EAAE,EAC3D;QAACA;KAAsB;IAEzB,MAAMG,qBAAqB3C,QACzB,IACEsC,oCACI,EAAE,GACFT,wBAAwBe,MAAM,GAAG,IAC/Bf,0BACAa,wBACR;QAACA;QAAwBb;QAAyBS;KAAkC;IAEtF,MAAMO,qBAAqBzB,uBAAuBe;IAElDrC,UAAU;QACR,MAAMgD,MAAMH,mBAAmBI,IAAI,CAAC;QACpC,IAAIV,qCAAqCE,OAAO,KAAKO,KAAK;QAC1DT,qCAAqCE,OAAO,GAAGO;QAC/C/B,6BAA6B4B;IAC/B,GAAG;QAAC5B;QAA4B4B;KAAmB;IAEnD7C,UAAU;QACR,IAAIc,sBAAsBuB,WAAW;YACnCC,6BAA6BG,OAAO,GAAG3B;YACvC;QACF;QAEA,sFAAsF;QACtF,oFAAoF;QACpF,MAAMoC,gBAAgBZ,6BAA6BG,OAAO,KAAKJ;QAC/DC,6BAA6BG,OAAO,GAAGJ;QACvC,IAAI,CAACa,eAAe;QAEpBlB,2BAA2BjB,2BAA2B;YAACA;SAAyB,GAAG,EAAE;QACrFmB,uBAAuB;QACvBE,yBAAyBK,OAAO,GAAGJ;IACrC,GAAG;QAACtB;QAA0BD;KAAkB;IAEhDd,UAAU;QACR,IACEc,sBAAsBuB,aACtB,CAACjB,2BACDa,uBACAF,wBAAwBe,MAAM,GAAG,KACjCV,yBAAyBK,OAAO,KAAKJ,WACrC;YACA;QACF;QACA,IAAIV,MAAMgB,aAAa,EAAEP,yBAAyBK,OAAO,GAAGd,MAAMgB,aAAa;IACjF,GAAG;QACDvB;QACAW,wBAAwBe,MAAM;QAC9BnB,MAAMgB,aAAa;QACnB7B;QACAmB;KACD;IAEDjC,UAAU;QACR,IAAIc,sBAAsBuB,WAAW;QAErC,MAAMc,wBAAwBrC,qBAAqB;QACnD,IAAIqB,8BAA8BM,OAAO,KAAKU,uBAAuB;YACnEhB,8BAA8BM,OAAO,GAAG;YACxC;QACF;QAEAT,2BAA2BlB,oBAAoB;YAACA;SAAkB,GAAG,EAAE;QACvEoB,uBAAuB;IACzB,GAAG;QAACpB;KAAkB;IAEtBd,UAAU;QACR,IACEkB,2BAA2B,QAC3B,CAACS,MAAMyB,OAAO,CAACC,IAAI,CAAC,CAACC,QAAUA,MAAMC,EAAE,KAAKrC,0BAA0B,CAACoC,MAAME,WAAW,GACxF;YACArC,wBAAwB;QAC1B;IACF,GAAG;QAACD;QAAwBS,MAAMyB,OAAO;QAAEjC;KAAsB;IAEjE,SAASsC,cAAcC,cAAwB;QAC7C,MAAMC,gBAAgBR,sBAAsBN,oBAAoBa;QAChExB,uBAAuB;QACvBF,2BAA2B0B;QAC3BvB,8BAA8BM,OAAO,GAAGkB,iBAAiB;QACzD3C,0BAA0B2C;IAC5B;IAEA,MAAMC,iBAAiBjC,MAAMyB,OAAO,CAACS,IAAI,CACvC,CAACP,QAAUA,MAAMC,EAAE,KAAKrC,0BAA0B,CAACoC,MAAME,WAAW;IAGtE,qBACE,MAACjE;QAAmBuE,YAAY;QAAMC,yBAAyB;;0BAC7D,MAACC;gBACCC,mBAAiBzC,aAAaM,UAAUO;gBACxCZ,WAAW3B,GACT,gGACA2B;;oBAGDD,2BACC,KAAC0C;wBAAIzC,WAAU;kCACb,cAAA,KAAC5B;4BAAKsE,IAAG;4BAAKZ,IAAIzB;4BAASsC,MAAK;4BAAKC,IAAI;4BAAC5C,WAAU;sCACjDE,MAAM2C,OAAO;;yBAGhB;oBAEH3C,MAAMyB,OAAO,CAACN,MAAM,KAAK,kBACxB,KAACyB;wBAAuBlD,YAAYA;uCAEpC,KAACtC;wBACCyF,MAAK;wBACLC,OAAO5B;wBACP6B,eAAejB;wBACfkB,OAAO;kCAEP,cAAA,KAACC;sCACEjD,MAAMyB,OAAO,CAACyB,GAAG,CAAC,CAACvB,OAAOwB;gCACzB,MAAMC,WAAWlC,mBAAmBmC,QAAQ,CAAC1B,MAAMC,EAAE;gCACrD,qBACE,KAAC0B;oCAEC3B,OAAOA;oCACPyB,UAAUA;oCACVhC,oBAAoBA;oCACpBmC,QAAQJ,UAAUnD,MAAMyB,OAAO,CAACN,MAAM,GAAG;oCACzCqC,UAAU;wCACR1B,cACEV,qBACIqC,gBAAgBvC,oBAAoBS,MAAMC,EAAE,IAC5CwB,WACE,EAAE,GACF;4CAACzB,MAAMC,EAAE;yCAAC;oCAEpB;oCACA8B,WACElE,yBAAyB,CAACmC,MAAME,WAAW,GACvC,IAAMrC,sBAAsBmC,MAAMC,EAAE,IACpClB;oCAENiD,iBACEP,WACIzD,qBAAqB;wCACnBiE,MAAMjC,MAAMiC,IAAI;wCAChBC,QAAQlC,MAAMiC,IAAI,CAAChC,EAAE;wCACrBkC,WAAWnC,MAAMiC,IAAI,CAACG,KAAK;wCAC3BC,gBAAgBrC,MAAMiC,IAAI,CAACI,cAAc;wCACzCC,SAAStC,MAAMsC,OAAO;wCACtBC,WAAWvC,MAAMC,EAAE;wCACnBuC,kBAAkBxC,MAAMyC,SAAS;wCACjCC,cAAc1C,MAAM2C,KAAK;wCACzBC,eAAe5C,MAAM6C,YAAY,CAACC,IAAI;wCACtC5C,aAAaF,MAAME,WAAW;oCAChC,KACA;mCAjCDF,MAAMC,EAAE;4BAqCnB;;;;;YAKPK,iBAAiBrC,kBAAkBqC,kBAAkB;;;AAG5D;AAEA,SAASW,uBAAuB,EAC9BlD,aAAa;IACXgF,OAAO;IACPC,aAAa;AACf,CAAC,EAGF;IACC,IAAI,CAACjF,WAAWkF,MAAM,EAAE;QACtB,qBACE,KAAClH;YACCoC,WAAU;YACV+E,MAAK;YACLH,OAAOhF,WAAWgF,KAAK;YACvBC,aAAajF,WAAWiF,WAAW;YACnCG,SAAQ;;IAGd;IAEA,qBACE,MAACvC;QAAIzC,WAAU;;0BACb,KAACiF;gBAAuBH,QAAQlF,WAAWkF,MAAM;;0BACjD,MAACrC;gBAAIzC,WAAU;;kCACb,KAAC5B;wBAAKuE,MAAK;wBAAK3C,WAAU;kCACvBJ,WAAWgF,KAAK;;kCAEnB,KAACxG;wBAAKuE,MAAK;wBAAK3C,WAAU;kCACvBJ,WAAWiF,WAAW;;;;;;AAKjC;AAEA,SAASI,uBAAuB,EAACH,MAAM,EAA6B;IAClE,IAAIA,WAAW,WAAW;QACxB,qBACE,KAACrC;YAAIzC,WAAU;sBACb,cAAA,KAACpB;gBAAmBkG,QAAQA;gBAAQnC,MAAM;gBAAIuC,SAAS;;;IAG7D;IAEA,qBACE,KAACzC;QAAIzC,WAAU;kBACb,cAAA,KAACnC;YAAKsH,MAAK;YAAYxC,MAAM;YAAIyC,eAAY;;;AAGnD;AAEA,SAAS5B,QAAQ,EACf3B,KAAK,EACLyB,QAAQ,EACRhC,kBAAkB,EAClBmC,MAAM,EACNC,QAAQ,EACRE,SAAS,EACTC,eAAe,EAShB;IACC,MAAMwB,yBAAyBxD,MAAMiC,IAAI,CAACG,KAAK,CAAC5C,MAAM,GAAG;IACzD,MAAMiE,2BACJ;;0BACE,KAACzH;gBACCsH,MAAK;gBACLC,eAAY;gBACZpF,WAAW3B,GACT,uEACAiF,YAAY;;0BAGhB,KAACiC;gBAAe1D,OAAOA;;0BACvB,KAACY;gBAAIzC,WAAU;0BACb,cAAA,MAACyC;oBAAIzC,WAAU;;sCACb,KAAC5B;4BAAKuE,MAAK;4BAAKC,IAAI;4BAAC5C,WAAU;sCAC5B6B,MAAMiC,IAAI,CAACG,KAAK;;wBAElBpC,MAAMiC,IAAI,CAAC0B,QAAQ,CAACnE,MAAM,GAAG,kBAAI,KAACoE;4BAAgBtB,SAAStC;6BAAY;wBACvEA,MAAME,WAAW,iBAAG,KAAC2D,wBAAsB;;;;0BAGhD,KAACC;gBAAyBxB,SAAStC;;;;IAGvC,MAAM+D,aAAavH,GACjB,mPACAwD,MAAME,WAAW,IAAI;IAEvB,MAAM8D,SAASvE,mCACb,KAAC7D;QACCqI,UAAU;QACVC,cAAYC,qBAAqBnE;QACjC7B,WAAW4F;kBAEVN;uBAGH,KAACO;QACC9C,MAAK;QACLkD,iBAAe;QACfF,cAAYC,qBAAqBnE;QACjCqE,SAASxC;QACT1D,WAAW4F;kBAEVN;;IAGL,MAAMa,cAAcd,uCAClB,MAACrH;;0BACC,KAACE;gBAAegF,OAAO;0BAAE2C;;0BACzB,KAAC5H;0BACC,cAAA,KAACmI;oBAAKpG,WAAU;8BAA+B6B,MAAMiC,IAAI,CAACG,KAAK;;;;SAInE4B;IAEF,MAAMQ,oBACJ;;0BACE,MAAC5D;gBACCzC,WAAW3B,GACT,2KACAiF,YAAY,+BACZ,CAAChC,sBAAsB;oBAAC;oBAAuCmC,UAAU;iBAAa;;kCAGxF,KAAChB;wBAAIzC,WAAU;kCAAkBmG;;oBAChCvC,0BACC,MAAC5F;;0CACC,KAACE;gCAAegF,OAAO;0CACrB,cAAA,KAAC2C;oCACC9C,MAAK;oCACLgD,cAAY,CAAC,QAAQ,EAAElE,MAAMiC,IAAI,CAACG,KAAK,CAAC,UAAU,EAAEpC,MAAMsC,OAAO,EAAE;oCACnE+B,SAAStC;oCACT5D,WAAU;8CAEV,cAAA,KAACnC;wCAAKsH,MAAK;wCAAkBxC,MAAM;wCAAIyC,eAAY;;;;0CAGvD,KAACnH;0CAAe;;;yBAEhB;;;YAELqF,YAAYO,gCACX,KAACtG;gBAAiByC,WAAU;0BAC1B,cAAA,KAACyC;oBAAIzC,WAAU;8BAAW6D;;iBAE1B;;;IAIR,IAAIvC,oBAAoB;QACtB,qBACE,KAAC9D;YAAcwF,OAAOnB,MAAMC,EAAE;YAAEoB,OAAO;sBACrC,cAAA,KAACoD;0BAAID;;;IAGX;IAEA,qBAAO,KAACC;kBAAID;;AACd;AAEA,SAASX;IACP,qBACE,MAAC1H;;0BACC,KAACE;gBAAegF,OAAO;0BACrB,cAAA,KAACkD;oBAAKpG,WAAU;8BACd,cAAA,KAACtC;wBAAMsH,SAAQ;wBAAUrC,MAAK;kCAAM;;;;0BAKxC,KAAC1E;0BAAe;;;;AAKtB;AAEA,SAAS0F,gBAAgBvC,kBAAqC,EAAEgD,SAAiB;IAC/E,IAAIhD,mBAAmBmC,QAAQ,CAACa,YAAY;QAC1C,OAAOhD,mBAAmBmF,MAAM,CAAC,CAAClH,oBAAsBA,sBAAsB+E;IAChF;IACA,OAAO;WAAIhD;QAAoBgD;KAAU;AAC3C;AAEA,SAAS1C,sBACPN,kBAAqC,EACrCa,cAAiC;IAEjC,IAAIA,eAAeZ,MAAM,KAAK,GAAG,OAAOT;IAExC,MAAM4F,kBAAkBvE,eAAeG,IAAI,CACzC,CAACgC,YAAc,CAAChD,mBAAmBmC,QAAQ,CAACa;IAE9C,OAAOoC,mBAAmBvE,eAAewE,EAAE,CAAC,CAAC;AAC/C;AAEA,SAASlB,eAAe,EAAC1D,KAAK,EAA8B;IAC1D,IAAIhD,iBAAiBgD,MAAM6C,YAAY,CAACC,IAAI,GAAG;QAC7C,qBACE,KAAC/F;YACCkG,QAAQjD,MAAM6C,YAAY,CAACC,IAAI;YAC/B+B,QAAQ7E,MAAM6C,YAAY,CAACgC,MAAM;YACjC/D,MAAM;;IAGZ;IAEA,qBACE,KAACyD;QAAKO,MAAK;QAAMZ,cAAYlE,MAAM6C,YAAY,CAACT,KAAK;QAAEjE,WAAU;kBAC/D,cAAA,KAACrC;YACCqH,SAASnD,MAAM6C,YAAY,CAACkC,GAAG;YAC/BF,QAAQ7E,MAAM6C,YAAY,CAACgC,MAAM;YACjC1G,WAAU;;;AAIlB;AAEA,MAAM6G,qBAAgE;IACpEC,SAAS;IACTC,MAAM;IACNC,SAAS;IACTC,SAAS;IACTC,SAAS;IACT1C,OAAO;AACT;AAEA,SAASiB,gBAAgB,EAACtB,OAAO,EAA8B;IAC7D,qBACE,KAAC1B;QAAIzC,WAAU;QAAuCoF,eAAY;kBAChE,cAAA,MAACgB;YACCpG,WAAW3B,GACT,6IACAwI,kBAAkB,CAAC1C,QAAQO,YAAY,CAACyC,KAAK,IAAI,UAAU;;gBAE9D;gBACGhD,QAAQA,OAAO;;;;AAIzB;AAEA,SAAS6B,qBAAqBnE,KAAyB;IACrD,MAAMuF,QAAQ;QAACvF,MAAMiC,IAAI,CAACG,KAAK;QAAEpC,MAAM6C,YAAY,CAACT,KAAK;QAAE,CAAC,QAAQ,EAAEpC,MAAMsC,OAAO,EAAE;KAAC;IACtF,IAAItC,MAAMiC,IAAI,CAACU,KAAK,EAAE6C,UAAUD,MAAME,IAAI,CAACrI,eAAe4C,MAAMiC,IAAI,CAACU,KAAK,CAAC6C,QAAQ;IACnF,OAAOD,MAAM5F,IAAI,CAAC;AACpB;AAEA,SAASmE,yBAAyB,EAACxB,OAAO,EAA8B;IACtE,MAAMoD,WAAWpD,QAAQqD,eAAe;IACxC,IAAID,aAAa,MAAM,OAAO;IAE9B,IAAIA,SAASE,KAAK,KAAK,QAAQ;QAC7B,qBAAO,KAACC;YAAiBC,SAASJ,SAASI,OAAO;;IACpD;IAEA,qBAAO,KAACC;kBAAc9I,4BAA4ByI;;AACpD;AAEA,SAASG,iBAAiB,EAACC,OAAO,EAAoB;IACpD5J;IACA,qBAAO,KAAC6J;kBAActJ,cAAcqJ;;AACtC;AAEA,SAASC,aAAa,EAACC,QAAQ,EAAqB;IAClD,qBACE,KAAC1J;QAAKuE,IAAG;QAAOsC,SAAQ;QAAQhF,WAAU;kBACvC6H;;AAGP"}
@@ -1,13 +1,14 @@
1
- import type { WorkflowRunAttemptDisplayDuration, WorkflowRunDisplayDuration } from '#core/workflow-run.js';
1
+ import type { WorkflowRunAttemptDisplayDuration } from '#core/workflow-run.js';
2
2
  /**
3
- * The run list reads an attempt's own marks and cannot tell queue from run, so an unkinded
4
- * duration still renders as run time. Only surfaces holding the run's jobs pass a kind.
3
+ * A run reports one span: how long its attempt has been under way. Queueing is a job
4
+ * execution's own affair, and the job surfaces report it there.
5
5
  */
6
- export type WorkflowRunDurationDisplay = WorkflowRunAttemptDisplayDuration | WorkflowRunDisplayDuration;
7
- export declare function WorkflowRunDurationLabel({ duration, className, }: {
6
+ export type WorkflowRunDurationDisplay = WorkflowRunAttemptDisplayDuration;
7
+ export declare function WorkflowRunDurationLabel({ duration, hasStarted, className, }: {
8
8
  duration: WorkflowRunDurationDisplay | null;
9
+ hasStarted: boolean;
9
10
  className?: string | undefined;
10
11
  }): import("react").JSX.Element | null;
11
- export declare function useWorkflowRunDurationAccessibleLabel(duration: WorkflowRunDurationDisplay | null): string | undefined;
12
- export declare function workflowRunDurationAccessibleLabel(duration: WorkflowRunDurationDisplay | null): string | undefined;
12
+ export declare function useWorkflowRunDurationAccessibleLabel(duration: WorkflowRunDurationDisplay | null, hasStarted: boolean): string | undefined;
13
+ export declare function workflowRunDurationAccessibleLabel(duration: WorkflowRunDurationDisplay | null, hasStarted: boolean): string | undefined;
13
14
  //# sourceMappingURL=workflow-run-duration-label.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-run-duration-label.d.ts","sourceRoot":"","sources":["../../src/components/workflow-run-duration-label.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,iCAAiC,EACjC,0BAA0B,EAC3B,MAAM,uBAAuB,CAAC;AAE/B;;;GAGG;AACH,MAAM,MAAM,0BAA0B,GAClC,iCAAiC,GACjC,0BAA0B,CAAC;AAQ/B,wBAAgB,wBAAwB,CAAC,EACvC,QAAQ,EACR,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,0BAA0B,GAAG,IAAI,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC,sCAwBA;AAED,wBAAgB,qCAAqC,CACnD,QAAQ,EAAE,0BAA0B,GAAG,IAAI,GAC1C,MAAM,GAAG,SAAS,CAGpB;AAED,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,0BAA0B,GAAG,IAAI,GAC1C,MAAM,GAAG,SAAS,CAcpB"}
1
+ {"version":3,"file":"workflow-run-duration-label.d.ts","sourceRoot":"","sources":["../../src/components/workflow-run-duration-label.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,iCAAiC,EAAC,MAAM,uBAAuB,CAAC;AAE7E;;;GAGG;AACH,MAAM,MAAM,0BAA0B,GAAG,iCAAiC,CAAC;AAE3E,wBAAgB,wBAAwB,CAAC,EACvC,QAAQ,EACR,UAAU,EACV,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,0BAA0B,GAAG,IAAI,CAAC;IAC5C,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC,sCAoBA;AAED,wBAAgB,qCAAqC,CACnD,QAAQ,EAAE,0BAA0B,GAAG,IAAI,EAC3C,UAAU,EAAE,OAAO,GAClB,MAAM,GAAG,SAAS,CAGpB;AAED,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,0BAA0B,GAAG,IAAI,EAC3C,UAAU,EAAE,OAAO,GAClB,MAAM,GAAG,SAAS,CAepB"}
@@ -3,36 +3,22 @@ import { Icon } from '@shipfox/react-ui/icon';
3
3
  import { useTimeTick } from '@shipfox/react-ui/time-ticker';
4
4
  import { Code } from '@shipfox/react-ui/typography';
5
5
  import { cn, humanDuration } from '@shipfox/react-ui/utils';
6
- const GLYPH_BY_KIND = {
7
- queue: 'hourglassLine',
8
- run: 'timerLine'
9
- };
10
- const LIVE_VERB_BY_KIND = {
11
- queue: 'queued',
12
- run: 'running'
13
- };
14
- const FIXED_VERB_BY_KIND = {
15
- queue: 'queued',
16
- run: 'ran'
17
- };
18
- export function WorkflowRunDurationLabel({ duration, className }) {
6
+ export function WorkflowRunDurationLabel({ duration, hasStarted, className }) {
19
7
  if (duration === null) return null;
20
- const kind = durationKind(duration);
21
8
  switch(duration.state){
22
9
  case 'fixed':
23
10
  {
24
11
  const display = formatFixedDurationLabel(duration.elapsed);
12
+ const verb = hasStarted ? 'ran' : 'lasted';
25
13
  return /*#__PURE__*/ _jsx(DurationText, {
26
14
  className: className,
27
- kind: kind,
28
- ariaLabel: `${FIXED_VERB_BY_KIND[kind]} ${display}`,
15
+ ariaLabel: `${verb} ${display}`,
29
16
  children: display
30
17
  });
31
18
  }
32
19
  case 'live':
33
20
  return /*#__PURE__*/ _jsx(LiveDurationText, {
34
21
  duration: duration,
35
- kind: kind,
36
22
  className: className
37
23
  });
38
24
  default:
@@ -42,18 +28,20 @@ export function WorkflowRunDurationLabel({ duration, className }) {
42
28
  }
43
29
  }
44
30
  }
45
- export function useWorkflowRunDurationAccessibleLabel(duration) {
31
+ export function useWorkflowRunDurationAccessibleLabel(duration, hasStarted) {
46
32
  useTimeTick();
47
- return workflowRunDurationAccessibleLabel(duration);
33
+ return workflowRunDurationAccessibleLabel(duration, hasStarted);
48
34
  }
49
- export function workflowRunDurationAccessibleLabel(duration) {
35
+ export function workflowRunDurationAccessibleLabel(duration, hasStarted) {
50
36
  if (duration === null) return undefined;
51
- const kind = durationKind(duration);
52
37
  switch(duration.state){
53
38
  case 'live':
54
- return `${LIVE_VERB_BY_KIND[kind]} ${humanDuration(duration.fromIso)}`;
39
+ return `running ${humanDuration(duration.fromIso)}`;
55
40
  case 'fixed':
56
- return `${FIXED_VERB_BY_KIND[kind]} ${formatFixedDurationLabel(duration.elapsed)}`;
41
+ {
42
+ const verb = hasStarted ? 'ran' : 'lasted';
43
+ return `${verb} ${formatFixedDurationLabel(duration.elapsed)}`;
44
+ }
57
45
  default:
58
46
  {
59
47
  const exhaustive = duration;
@@ -61,20 +49,16 @@ export function workflowRunDurationAccessibleLabel(duration) {
61
49
  }
62
50
  }
63
51
  }
64
- function durationKind(duration) {
65
- return 'kind' in duration ? duration.kind : 'run';
66
- }
67
- function LiveDurationText({ duration, kind, className }) {
52
+ function LiveDurationText({ duration, className }) {
68
53
  useTimeTick();
69
54
  const display = humanDuration(duration.fromIso);
70
55
  return /*#__PURE__*/ _jsx(DurationText, {
71
56
  className: className,
72
- kind: kind,
73
- ariaLabel: `${LIVE_VERB_BY_KIND[kind]} ${display}`,
57
+ ariaLabel: `running ${display}`,
74
58
  children: display
75
59
  });
76
60
  }
77
- function DurationText({ children, className, kind, ariaLabel }) {
61
+ function DurationText({ children, className, ariaLabel }) {
78
62
  return /*#__PURE__*/ _jsxs(Code, {
79
63
  as: "span",
80
64
  variant: "label",
@@ -82,7 +66,7 @@ function DurationText({ children, className, kind, ariaLabel }) {
82
66
  className: cn('inline-flex shrink-0 items-center gap-tight tabular-nums text-foreground-neutral-subtle', className),
83
67
  children: [
84
68
  /*#__PURE__*/ _jsx(Icon, {
85
- name: GLYPH_BY_KIND[kind],
69
+ name: "timerLine",
86
70
  className: "size-12 shrink-0",
87
71
  "aria-hidden": "true"
88
72
  }),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/workflow-run-duration-label.tsx"],"sourcesContent":["import {Icon} from '@shipfox/react-ui/icon';\nimport {useTimeTick} from '@shipfox/react-ui/time-ticker';\nimport {Code} from '@shipfox/react-ui/typography';\nimport {cn, humanDuration} from '@shipfox/react-ui/utils';\nimport type {\n WorkflowRunAttemptDisplayDuration,\n WorkflowRunDisplayDuration,\n} from '#core/workflow-run.js';\n\n/**\n * The run list reads an attempt's own marks and cannot tell queue from run, so an unkinded\n * duration still renders as run time. Only surfaces holding the run's jobs pass a kind.\n */\nexport type WorkflowRunDurationDisplay =\n | WorkflowRunAttemptDisplayDuration\n | WorkflowRunDisplayDuration;\n\ntype DurationKind = 'queue' | 'run';\n\nconst GLYPH_BY_KIND = {queue: 'hourglassLine', run: 'timerLine'} as const;\nconst LIVE_VERB_BY_KIND = {queue: 'queued', run: 'running'} as const;\nconst FIXED_VERB_BY_KIND = {queue: 'queued', run: 'ran'} as const;\n\nexport function WorkflowRunDurationLabel({\n duration,\n className,\n}: {\n duration: WorkflowRunDurationDisplay | null;\n className?: string | undefined;\n}) {\n if (duration === null) return null;\n const kind = durationKind(duration);\n\n switch (duration.state) {\n case 'fixed': {\n const display = formatFixedDurationLabel(duration.elapsed);\n return (\n <DurationText\n className={className}\n kind={kind}\n ariaLabel={`${FIXED_VERB_BY_KIND[kind]} ${display}`}\n >\n {display}\n </DurationText>\n );\n }\n case 'live':\n return <LiveDurationText duration={duration} kind={kind} className={className} />;\n default: {\n const exhaustive: never = duration;\n return exhaustive;\n }\n }\n}\n\nexport function useWorkflowRunDurationAccessibleLabel(\n duration: WorkflowRunDurationDisplay | null,\n): string | undefined {\n useTimeTick();\n return workflowRunDurationAccessibleLabel(duration);\n}\n\nexport function workflowRunDurationAccessibleLabel(\n duration: WorkflowRunDurationDisplay | null,\n): string | undefined {\n if (duration === null) return undefined;\n const kind = durationKind(duration);\n\n switch (duration.state) {\n case 'live':\n return `${LIVE_VERB_BY_KIND[kind]} ${humanDuration(duration.fromIso)}`;\n case 'fixed':\n return `${FIXED_VERB_BY_KIND[kind]} ${formatFixedDurationLabel(duration.elapsed)}`;\n default: {\n const exhaustive: never = duration;\n return exhaustive;\n }\n }\n}\n\nfunction durationKind(duration: WorkflowRunDurationDisplay): DurationKind {\n return 'kind' in duration ? duration.kind : 'run';\n}\n\nfunction LiveDurationText({\n duration,\n kind,\n className,\n}: {\n duration: Extract<WorkflowRunDurationDisplay, {state: 'live'}>;\n kind: DurationKind;\n className?: string | undefined;\n}) {\n useTimeTick();\n const display = humanDuration(duration.fromIso);\n return (\n <DurationText\n className={className}\n kind={kind}\n ariaLabel={`${LIVE_VERB_BY_KIND[kind]} ${display}`}\n >\n {display}\n </DurationText>\n );\n}\n\nfunction DurationText({\n children,\n className,\n kind,\n ariaLabel,\n}: {\n children: string;\n className?: string | undefined;\n kind: DurationKind;\n ariaLabel?: string | undefined;\n}) {\n return (\n <Code\n as=\"span\"\n variant=\"label\"\n aria-label={ariaLabel}\n className={cn(\n 'inline-flex shrink-0 items-center gap-tight tabular-nums text-foreground-neutral-subtle',\n className,\n )}\n >\n {/* The glyph is the only visible mark of queue vs run: the status badge already writes\n \"Queued\", and the numeric columns are fixed width. The verb rides the accessible\n name instead. */}\n <Icon name={GLYPH_BY_KIND[kind]} className=\"size-12 shrink-0\" aria-hidden=\"true\" />\n {children}\n </Code>\n );\n}\n\nfunction formatFixedDurationLabel({\n years = 0,\n months = 0,\n weeks = 0,\n days = 0,\n hours = 0,\n minutes = 0,\n seconds = 0,\n}: Extract<WorkflowRunDurationDisplay, {state: 'fixed'}>['elapsed']): string {\n const totalDays = years * 365 + months * 30 + weeks * 7 + days;\n const totalHours = totalDays * 24 + hours;\n\n if (totalHours > 0) return `${totalHours}h ${pad2(minutes)}m`;\n if (minutes > 0) return `${minutes}m ${pad2(seconds)}s`;\n return `${seconds}s`;\n}\n\nfunction pad2(value: number): string {\n return value.toString().padStart(2, '0');\n}\n"],"names":["Icon","useTimeTick","Code","cn","humanDuration","GLYPH_BY_KIND","queue","run","LIVE_VERB_BY_KIND","FIXED_VERB_BY_KIND","WorkflowRunDurationLabel","duration","className","kind","durationKind","state","display","formatFixedDurationLabel","elapsed","DurationText","ariaLabel","LiveDurationText","exhaustive","useWorkflowRunDurationAccessibleLabel","workflowRunDurationAccessibleLabel","undefined","fromIso","children","as","variant","aria-label","name","aria-hidden","years","months","weeks","days","hours","minutes","seconds","totalDays","totalHours","pad2","value","toString","padStart"],"mappings":";AAAA,SAAQA,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,WAAW,QAAO,gCAAgC;AAC1D,SAAQC,IAAI,QAAO,+BAA+B;AAClD,SAAQC,EAAE,EAAEC,aAAa,QAAO,0BAA0B;AAgB1D,MAAMC,gBAAgB;IAACC,OAAO;IAAiBC,KAAK;AAAW;AAC/D,MAAMC,oBAAoB;IAACF,OAAO;IAAUC,KAAK;AAAS;AAC1D,MAAME,qBAAqB;IAACH,OAAO;IAAUC,KAAK;AAAK;AAEvD,OAAO,SAASG,yBAAyB,EACvCC,QAAQ,EACRC,SAAS,EAIV;IACC,IAAID,aAAa,MAAM,OAAO;IAC9B,MAAME,OAAOC,aAAaH;IAE1B,OAAQA,SAASI,KAAK;QACpB,KAAK;YAAS;gBACZ,MAAMC,UAAUC,yBAAyBN,SAASO,OAAO;gBACzD,qBACE,KAACC;oBACCP,WAAWA;oBACXC,MAAMA;oBACNO,WAAW,GAAGX,kBAAkB,CAACI,KAAK,CAAC,CAAC,EAAEG,SAAS;8BAElDA;;YAGP;QACA,KAAK;YACH,qBAAO,KAACK;gBAAiBV,UAAUA;gBAAUE,MAAMA;gBAAMD,WAAWA;;QACtE;YAAS;gBACP,MAAMU,aAAoBX;gBAC1B,OAAOW;YACT;IACF;AACF;AAEA,OAAO,SAASC,sCACdZ,QAA2C;IAE3CV;IACA,OAAOuB,mCAAmCb;AAC5C;AAEA,OAAO,SAASa,mCACdb,QAA2C;IAE3C,IAAIA,aAAa,MAAM,OAAOc;IAC9B,MAAMZ,OAAOC,aAAaH;IAE1B,OAAQA,SAASI,KAAK;QACpB,KAAK;YACH,OAAO,GAAGP,iBAAiB,CAACK,KAAK,CAAC,CAAC,EAAET,cAAcO,SAASe,OAAO,GAAG;QACxE,KAAK;YACH,OAAO,GAAGjB,kBAAkB,CAACI,KAAK,CAAC,CAAC,EAAEI,yBAAyBN,SAASO,OAAO,GAAG;QACpF;YAAS;gBACP,MAAMI,aAAoBX;gBAC1B,OAAOW;YACT;IACF;AACF;AAEA,SAASR,aAAaH,QAAoC;IACxD,OAAO,UAAUA,WAAWA,SAASE,IAAI,GAAG;AAC9C;AAEA,SAASQ,iBAAiB,EACxBV,QAAQ,EACRE,IAAI,EACJD,SAAS,EAKV;IACCX;IACA,MAAMe,UAAUZ,cAAcO,SAASe,OAAO;IAC9C,qBACE,KAACP;QACCP,WAAWA;QACXC,MAAMA;QACNO,WAAW,GAAGZ,iBAAiB,CAACK,KAAK,CAAC,CAAC,EAAEG,SAAS;kBAEjDA;;AAGP;AAEA,SAASG,aAAa,EACpBQ,QAAQ,EACRf,SAAS,EACTC,IAAI,EACJO,SAAS,EAMV;IACC,qBACE,MAAClB;QACC0B,IAAG;QACHC,SAAQ;QACRC,cAAYV;QACZR,WAAWT,GACT,2FACAS;;0BAMF,KAACZ;gBAAK+B,MAAM1B,aAAa,CAACQ,KAAK;gBAAED,WAAU;gBAAmBoB,eAAY;;YACzEL;;;AAGP;AAEA,SAASV,yBAAyB,EAChCgB,QAAQ,CAAC,EACTC,SAAS,CAAC,EACVC,QAAQ,CAAC,EACTC,OAAO,CAAC,EACRC,QAAQ,CAAC,EACTC,UAAU,CAAC,EACXC,UAAU,CAAC,EACsD;IACjE,MAAMC,YAAYP,QAAQ,MAAMC,SAAS,KAAKC,QAAQ,IAAIC;IAC1D,MAAMK,aAAaD,YAAY,KAAKH;IAEpC,IAAII,aAAa,GAAG,OAAO,GAAGA,WAAW,EAAE,EAAEC,KAAKJ,SAAS,CAAC,CAAC;IAC7D,IAAIA,UAAU,GAAG,OAAO,GAAGA,QAAQ,EAAE,EAAEI,KAAKH,SAAS,CAAC,CAAC;IACvD,OAAO,GAAGA,QAAQ,CAAC,CAAC;AACtB;AAEA,SAASG,KAAKC,KAAa;IACzB,OAAOA,MAAMC,QAAQ,GAAGC,QAAQ,CAAC,GAAG;AACtC"}
1
+ {"version":3,"sources":["../../src/components/workflow-run-duration-label.tsx"],"sourcesContent":["import {Icon} from '@shipfox/react-ui/icon';\nimport {useTimeTick} from '@shipfox/react-ui/time-ticker';\nimport {Code} from '@shipfox/react-ui/typography';\nimport {cn, humanDuration} from '@shipfox/react-ui/utils';\nimport type {WorkflowRunAttemptDisplayDuration} from '#core/workflow-run.js';\n\n/**\n * A run reports one span: how long its attempt has been under way. Queueing is a job\n * execution's own affair, and the job surfaces report it there.\n */\nexport type WorkflowRunDurationDisplay = WorkflowRunAttemptDisplayDuration;\n\nexport function WorkflowRunDurationLabel({\n duration,\n hasStarted,\n className,\n}: {\n duration: WorkflowRunDurationDisplay | null;\n hasStarted: boolean;\n className?: string | undefined;\n}) {\n if (duration === null) return null;\n\n switch (duration.state) {\n case 'fixed': {\n const display = formatFixedDurationLabel(duration.elapsed);\n const verb = hasStarted ? 'ran' : 'lasted';\n return (\n <DurationText className={className} ariaLabel={`${verb} ${display}`}>\n {display}\n </DurationText>\n );\n }\n case 'live':\n return <LiveDurationText duration={duration} className={className} />;\n default: {\n const exhaustive: never = duration;\n return exhaustive;\n }\n }\n}\n\nexport function useWorkflowRunDurationAccessibleLabel(\n duration: WorkflowRunDurationDisplay | null,\n hasStarted: boolean,\n): string | undefined {\n useTimeTick();\n return workflowRunDurationAccessibleLabel(duration, hasStarted);\n}\n\nexport function workflowRunDurationAccessibleLabel(\n duration: WorkflowRunDurationDisplay | null,\n hasStarted: boolean,\n): string | undefined {\n if (duration === null) return undefined;\n\n switch (duration.state) {\n case 'live':\n return `running ${humanDuration(duration.fromIso)}`;\n case 'fixed': {\n const verb = hasStarted ? 'ran' : 'lasted';\n return `${verb} ${formatFixedDurationLabel(duration.elapsed)}`;\n }\n default: {\n const exhaustive: never = duration;\n return exhaustive;\n }\n }\n}\n\nfunction LiveDurationText({\n duration,\n className,\n}: {\n duration: Extract<WorkflowRunDurationDisplay, {state: 'live'}>;\n className?: string | undefined;\n}) {\n useTimeTick();\n const display = humanDuration(duration.fromIso);\n return (\n <DurationText className={className} ariaLabel={`running ${display}`}>\n {display}\n </DurationText>\n );\n}\n\nfunction DurationText({\n children,\n className,\n ariaLabel,\n}: {\n children: string;\n className?: string | undefined;\n ariaLabel?: string | undefined;\n}) {\n return (\n <Code\n as=\"span\"\n variant=\"label\"\n aria-label={ariaLabel}\n className={cn(\n 'inline-flex shrink-0 items-center gap-tight tabular-nums text-foreground-neutral-subtle',\n className,\n )}\n >\n {/* The verb rides the accessible name: the numeric columns are fixed width and the\n status badge already says whether the run is still going. */}\n <Icon name=\"timerLine\" className=\"size-12 shrink-0\" aria-hidden=\"true\" />\n {children}\n </Code>\n );\n}\n\nfunction formatFixedDurationLabel({\n years = 0,\n months = 0,\n weeks = 0,\n days = 0,\n hours = 0,\n minutes = 0,\n seconds = 0,\n}: Extract<WorkflowRunDurationDisplay, {state: 'fixed'}>['elapsed']): string {\n const totalDays = years * 365 + months * 30 + weeks * 7 + days;\n const totalHours = totalDays * 24 + hours;\n\n if (totalHours > 0) return `${totalHours}h ${pad2(minutes)}m`;\n if (minutes > 0) return `${minutes}m ${pad2(seconds)}s`;\n return `${seconds}s`;\n}\n\nfunction pad2(value: number): string {\n return value.toString().padStart(2, '0');\n}\n"],"names":["Icon","useTimeTick","Code","cn","humanDuration","WorkflowRunDurationLabel","duration","hasStarted","className","state","display","formatFixedDurationLabel","elapsed","verb","DurationText","ariaLabel","LiveDurationText","exhaustive","useWorkflowRunDurationAccessibleLabel","workflowRunDurationAccessibleLabel","undefined","fromIso","children","as","variant","aria-label","name","aria-hidden","years","months","weeks","days","hours","minutes","seconds","totalDays","totalHours","pad2","value","toString","padStart"],"mappings":";AAAA,SAAQA,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,WAAW,QAAO,gCAAgC;AAC1D,SAAQC,IAAI,QAAO,+BAA+B;AAClD,SAAQC,EAAE,EAAEC,aAAa,QAAO,0BAA0B;AAS1D,OAAO,SAASC,yBAAyB,EACvCC,QAAQ,EACRC,UAAU,EACVC,SAAS,EAKV;IACC,IAAIF,aAAa,MAAM,OAAO;IAE9B,OAAQA,SAASG,KAAK;QACpB,KAAK;YAAS;gBACZ,MAAMC,UAAUC,yBAAyBL,SAASM,OAAO;gBACzD,MAAMC,OAAON,aAAa,QAAQ;gBAClC,qBACE,KAACO;oBAAaN,WAAWA;oBAAWO,WAAW,GAAGF,KAAK,CAAC,EAAEH,SAAS;8BAChEA;;YAGP;QACA,KAAK;YACH,qBAAO,KAACM;gBAAiBV,UAAUA;gBAAUE,WAAWA;;QAC1D;YAAS;gBACP,MAAMS,aAAoBX;gBAC1B,OAAOW;YACT;IACF;AACF;AAEA,OAAO,SAASC,sCACdZ,QAA2C,EAC3CC,UAAmB;IAEnBN;IACA,OAAOkB,mCAAmCb,UAAUC;AACtD;AAEA,OAAO,SAASY,mCACdb,QAA2C,EAC3CC,UAAmB;IAEnB,IAAID,aAAa,MAAM,OAAOc;IAE9B,OAAQd,SAASG,KAAK;QACpB,KAAK;YACH,OAAO,CAAC,QAAQ,EAAEL,cAAcE,SAASe,OAAO,GAAG;QACrD,KAAK;YAAS;gBACZ,MAAMR,OAAON,aAAa,QAAQ;gBAClC,OAAO,GAAGM,KAAK,CAAC,EAAEF,yBAAyBL,SAASM,OAAO,GAAG;YAChE;QACA;YAAS;gBACP,MAAMK,aAAoBX;gBAC1B,OAAOW;YACT;IACF;AACF;AAEA,SAASD,iBAAiB,EACxBV,QAAQ,EACRE,SAAS,EAIV;IACCP;IACA,MAAMS,UAAUN,cAAcE,SAASe,OAAO;IAC9C,qBACE,KAACP;QAAaN,WAAWA;QAAWO,WAAW,CAAC,QAAQ,EAAEL,SAAS;kBAChEA;;AAGP;AAEA,SAASI,aAAa,EACpBQ,QAAQ,EACRd,SAAS,EACTO,SAAS,EAKV;IACC,qBACE,MAACb;QACCqB,IAAG;QACHC,SAAQ;QACRC,cAAYV;QACZP,WAAWL,GACT,2FACAK;;0BAKF,KAACR;gBAAK0B,MAAK;gBAAYlB,WAAU;gBAAmBmB,eAAY;;YAC/DL;;;AAGP;AAEA,SAASX,yBAAyB,EAChCiB,QAAQ,CAAC,EACTC,SAAS,CAAC,EACVC,QAAQ,CAAC,EACTC,OAAO,CAAC,EACRC,QAAQ,CAAC,EACTC,UAAU,CAAC,EACXC,UAAU,CAAC,EACsD;IACjE,MAAMC,YAAYP,QAAQ,MAAMC,SAAS,KAAKC,QAAQ,IAAIC;IAC1D,MAAMK,aAAaD,YAAY,KAAKH;IAEpC,IAAII,aAAa,GAAG,OAAO,GAAGA,WAAW,EAAE,EAAEC,KAAKJ,SAAS,CAAC,CAAC;IAC7D,IAAIA,UAAU,GAAG,OAAO,GAAGA,QAAQ,EAAE,EAAEI,KAAKH,SAAS,CAAC,CAAC;IACvD,OAAO,GAAGA,QAAQ,CAAC,CAAC;AACtB;AAEA,SAASG,KAAKC,KAAa;IACzB,OAAOA,MAAMC,QAAQ,GAAGC,QAAQ,CAAC,GAAG;AACtC"}
@@ -1,4 +1,12 @@
1
1
  import type { WorkflowRunJobs } from '#core/workflow-run.js';
2
+ /**
3
+ * Reserves one full API preview and its overflow marker in a run row.
4
+ *
5
+ * Keep this derived from the API preview bound so the row reservation grows with the payload
6
+ * contract instead of silently drifting into the duration and timestamp columns.
7
+ */
8
+ export declare function jobStatusStripWidthForPreview(previewLimit: number): number;
9
+ export declare const JOB_STATUS_STRIP_WIDTH: number;
2
10
  export interface JobStatusStripProps {
3
11
  jobs: WorkflowRunJobs;
4
12
  className?: string | undefined;
@@ -14,7 +22,7 @@ export interface JobStatusStripProps {
14
22
  * job including those the server never sent. That split is what lets the overflow glyph
15
23
  * report a failure sitting past the preview instead of quietly dropping it.
16
24
  */
17
- export declare function JobStatusStrip({ jobs, className }: JobStatusStripProps): import("react").JSX.Element | null;
25
+ export declare const JobStatusStrip: import("react").MemoExoticComponent<({ jobs, className }: JobStatusStripProps) => import("react").JSX.Element | null>;
18
26
  /** "12 jobs: 9 succeeded, 2 failed, 1 running" — a count is not an accessible name on its own. */
19
27
  export declare function jobStatusSummary(jobs: WorkflowRunJobs): string;
20
28
  export declare function overflowCountLabel(hiddenCount: number): string;
@@ -1 +1 @@
1
- {"version":3,"file":"job-status-strip.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-list/job-status-strip.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAwB,eAAe,EAAC,MAAM,uBAAuB,CAAC;AA0ClF,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,eAAe,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,EAAC,IAAI,EAAE,SAAS,EAAC,EAAE,mBAAmB,sCAqDpE;AAgCD,kGAAkG;AAClG,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAO9D;AA0BD,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAI9D"}
1
+ {"version":3,"file":"job-status-strip.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-list/job-status-strip.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAwB,eAAe,EAAC,MAAM,uBAAuB,CAAC;AAsBlF;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAI1E;AAED,eAAO,MAAM,sBAAsB,QAAgE,CAAC;AAgBpG,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,eAAe,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,cAAc,4DAAmD,mBAAmB,wCAwD/F,CAAC;AAgCH,kGAAkG;AAClG,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAO9D;AA0BD,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAI9D"}
@@ -1,19 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { WORKFLOW_RUN_JOB_PREVIEW_LIMIT } from '@shipfox/api-workflows-dto';
2
3
  import { Tooltip, TooltipContent, TooltipTrigger } from '@shipfox/react-ui/tooltip';
3
4
  import { Code, Text } from '@shipfox/react-ui/typography';
4
5
  import { cn } from '@shipfox/react-ui/utils';
6
+ import { memo } from 'react';
5
7
  import { getWorkflowStatusVisual } from '#components/workflow-status/status-visuals.js';
6
8
  import { WorkflowStatusIcon } from '#components/workflow-status/workflow-status-icon.js';
7
9
  import { deriveJobDisplayStatus } from '#core/entities/job.js';
8
- /**
9
- * How many glyphs fit the strip's column before it starts costing the run name width.
10
- *
11
- * Sized so the widest case still fits the row's 160px strip column: seven 12px glyphs with
12
- * 3px gaps is 102px, and the overflow indicator adds a glyph plus a count of at most five
13
- * monospace characters, about 56px. Overshooting does not wrap, it paints over the duration
14
- * column. This sits under the API's preview bound, so resizing the strip is a change here.
15
- */ const MAX_VISIBLE_JOBS = 7;
16
10
  const GLYPH_SIZE = 12;
11
+ const GLYPH_GAP = 3;
12
+ const OVERFLOW_GAP = 2;
13
+ const OVERFLOW_LABEL_WIDTH = 40;
17
14
  const MAX_TOOLTIP_JOB_NAMES = 6;
18
15
  /**
19
16
  * Above this the overflow count is abbreviated.
@@ -27,6 +24,17 @@ const COMPACT_COUNT_FORMAT = new Intl.NumberFormat('en', {
27
24
  notation: 'compact',
28
25
  maximumFractionDigits: 1
29
26
  });
27
+ /**
28
+ * Reserves one full API preview and its overflow marker in a run row.
29
+ *
30
+ * Keep this derived from the API preview bound so the row reservation grows with the payload
31
+ * contract instead of silently drifting into the duration and timestamp columns.
32
+ */ export function jobStatusStripWidthForPreview(previewLimit) {
33
+ const previewWidth = previewLimit * GLYPH_SIZE + Math.max(previewLimit - 1, 0) * GLYPH_GAP;
34
+ const overflowWidth = GLYPH_GAP + GLYPH_SIZE + OVERFLOW_GAP + OVERFLOW_LABEL_WIDTH;
35
+ return previewWidth + overflowWidth;
36
+ }
37
+ export const JOB_STATUS_STRIP_WIDTH = jobStatusStripWidthForPreview(WORKFLOW_RUN_JOB_PREVIEW_LIMIT);
30
38
  // Worst-first, so the overflow glyph reports the most alarming thing it is standing in for.
31
39
  // A strip that hides a failure behind eight green discs would be worse than no strip at all.
32
40
  const STATUS_SEVERITY = {
@@ -53,9 +61,12 @@ const NOTABLE_STATUSES = [
53
61
  * What it draws comes from the preview; what it says comes from the counts, which cover every
54
62
  * job including those the server never sent. That split is what lets the overflow glyph
55
63
  * report a failure sitting past the preview instead of quietly dropping it.
56
- */ export function JobStatusStrip({ jobs, className }) {
64
+ */ export const JobStatusStrip = /*#__PURE__*/ memo(function JobStatusStrip({ jobs, className }) {
57
65
  if (jobs.total === 0) return null;
58
- const visible = jobs.preview.slice(0, MAX_VISIBLE_JOBS);
66
+ // The data frame gives the row enough room for the whole API preview. Only jobs beyond that
67
+ // server-side preview need an overflow marker; the old seven-glyph cap belonged to the
68
+ // previous 1120px page width.
69
+ const visible = jobs.preview;
59
70
  const hiddenCount = jobs.total - visible.length;
60
71
  const overflowStatus = worstHiddenStatus(jobs, visible);
61
72
  const summary = jobStatusSummary(jobs);
@@ -111,7 +122,7 @@ const NOTABLE_STATUSES = [
111
122
  })
112
123
  ]
113
124
  });
114
- }
125
+ });
115
126
  function JobStatusStripTooltip({ jobs, summary }) {
116
127
  // Naming the jobs that need attention is the whole point of hovering; succeeded jobs are
117
128
  // left to the counts so the tooltip stays a glance rather than a list. Only previewed jobs
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/workflow-run-list/job-status-strip.tsx"],"sourcesContent":["import {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Code, Text} from '@shipfox/react-ui/typography';\nimport {cn} 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 {deriveJobDisplayStatus, type JobDisplayStatus} from '#core/entities/job.js';\nimport type {WorkflowRunJobSummary, WorkflowRunJobs} from '#core/workflow-run.js';\n\n/**\n * How many glyphs fit the strip's column before it starts costing the run name width.\n *\n * Sized so the widest case still fits the row's 160px strip column: seven 12px glyphs with\n * 3px gaps is 102px, and the overflow indicator adds a glyph plus a count of at most five\n * monospace characters, about 56px. Overshooting does not wrap, it paints over the duration\n * column. This sits under the API's preview bound, so resizing the strip is a change here.\n */\nconst MAX_VISIBLE_JOBS = 7;\nconst GLYPH_SIZE = 12;\nconst MAX_TOOLTIP_JOB_NAMES = 6;\n\n/**\n * Above this the overflow count is abbreviated.\n *\n * Nothing caps a workflow's job count, so an exact count is unbounded in width and would\n * eventually push the strip over its column. Abbreviating keeps the label at most five\n * characters for any magnitude, and the exact figure stays in the tooltip and the strip's\n * accessible name, which is where a precise number is actually read.\n */\nconst MAX_EXACT_OVERFLOW_COUNT = 999;\nconst COMPACT_COUNT_FORMAT = new Intl.NumberFormat('en', {\n notation: 'compact',\n maximumFractionDigits: 1,\n});\n\n// Worst-first, so the overflow glyph reports the most alarming thing it is standing in for.\n// A strip that hides a failure behind eight green discs would be worse than no strip at all.\nconst STATUS_SEVERITY: Record<JobDisplayStatus, number> = {\n failed: 5,\n running: 4,\n listening: 4,\n pending: 3,\n cancelled: 2,\n skipped: 1,\n succeeded: 0,\n};\n\nconst NOTABLE_STATUSES: readonly JobDisplayStatus[] = ['failed', 'running', 'listening'];\n\nexport interface JobStatusStripProps {\n jobs: WorkflowRunJobs;\n className?: string | undefined;\n}\n\n/**\n * A run's jobs as a row of status glyphs, in graph order.\n *\n * This is the reason the list is worth its width: it answers \"where did this run fail\"\n * without opening the run. Glyphs are silent to assistive tech and carry no tooltip of their\n * own; the strip speaks once, as a summary, so a 40-job run does not read out 40 labels.\n *\n * What it draws comes from the preview; what it says comes from the counts, which cover every\n * job including those the server never sent. That split is what lets the overflow glyph\n * report a failure sitting past the preview instead of quietly dropping it.\n */\nexport function JobStatusStrip({jobs, className}: JobStatusStripProps) {\n if (jobs.total === 0) return null;\n\n const visible = jobs.preview.slice(0, MAX_VISIBLE_JOBS);\n const hiddenCount = jobs.total - visible.length;\n const overflowStatus = worstHiddenStatus(jobs, visible);\n const summary = jobStatusSummary(jobs);\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <span\n role=\"img\"\n aria-label={summary}\n className={cn('inline-flex items-center gap-[3px]', className)}\n >\n <span aria-hidden=\"true\" className=\"inline-flex items-center gap-[3px]\">\n {visible.map((job) => (\n <WorkflowStatusIcon\n key={job.id}\n status={displayStatus(job)}\n size={GLYPH_SIZE}\n // One ripple per running job, on every row, would turn a calm list into a\n // field of pulses. The run's own glyph already carries the live edge.\n ripple={false}\n tooltip={false}\n />\n ))}\n {hiddenCount > 0 && overflowStatus ? (\n <span className=\"inline-flex items-center gap-[2px]\">\n <WorkflowStatusIcon\n status={overflowStatus}\n size={GLYPH_SIZE}\n ripple={false}\n tooltip={false}\n />\n <Code\n as=\"span\"\n variant=\"label\"\n className=\"tabular-nums text-foreground-neutral-muted\"\n >\n +{overflowCountLabel(hiddenCount)}\n </Code>\n </span>\n ) : null}\n </span>\n </span>\n </TooltipTrigger>\n <TooltipContent>\n <JobStatusStripTooltip jobs={jobs} summary={summary} />\n </TooltipContent>\n </Tooltip>\n );\n}\n\nfunction JobStatusStripTooltip({jobs, summary}: {jobs: WorkflowRunJobs; summary: string}) {\n // Naming the jobs that need attention is the whole point of hovering; succeeded jobs are\n // left to the counts so the tooltip stays a glance rather than a list. Only previewed jobs\n // can be named, but the count of those left over is read off the totals, so a failure past\n // the preview is still accounted for rather than silently missing.\n const named = jobs.preview\n .filter((job) => NOTABLE_STATUSES.includes(displayStatus(job)))\n .slice(0, MAX_TOOLTIP_JOB_NAMES);\n const notableTotal = NOTABLE_STATUSES.reduce((total, status) => total + countOf(jobs, status), 0);\n const remaining = notableTotal - named.length;\n\n return (\n <span className=\"flex max-w-[280px] flex-col gap-tight\">\n <Text as=\"span\" size=\"xs\" className=\"block\">\n {summary}\n </Text>\n {named.map((job) => (\n <Code as=\"span\" variant=\"label\" key={job.id} className=\"block truncate\">\n {getWorkflowStatusVisual(displayStatus(job)).label.toLowerCase()} · {job.name ?? job.key}\n </Code>\n ))}\n {remaining > 0 ? (\n <Text as=\"span\" size=\"xs\" className=\"block text-foreground-neutral-muted\">\n and {remaining} more\n </Text>\n ) : null}\n </span>\n );\n}\n\n/** \"12 jobs: 9 succeeded, 2 failed, 1 running\" — a count is not an accessible name on its own. */\nexport function jobStatusSummary(jobs: WorkflowRunJobs): string {\n const breakdown = [...jobs.statusCounts]\n .sort((left, right) => STATUS_SEVERITY[right.status] - STATUS_SEVERITY[left.status])\n .map(({status, count}) => `${count} ${getWorkflowStatusVisual(status).label.toLowerCase()}`)\n .join(', ');\n\n return `${jobs.total} ${jobs.total === 1 ? 'job' : 'jobs'}: ${breakdown}`;\n}\n\n/**\n * The worst status among the jobs the strip is not drawing, found by subtracting what it\n * draws from the run's totals rather than by inspecting jobs it was never sent.\n */\nfunction worstHiddenStatus(\n jobs: WorkflowRunJobs,\n visible: readonly WorkflowRunJobSummary[],\n): JobDisplayStatus | null {\n const hidden = new Map(jobs.statusCounts.map(({status, count}) => [status, count]));\n for (const job of visible) {\n const status = displayStatus(job);\n const remaining = (hidden.get(status) ?? 0) - 1;\n if (remaining > 0) hidden.set(status, remaining);\n else hidden.delete(status);\n }\n\n let worst: JobDisplayStatus | null = null;\n for (const [status, count] of hidden) {\n if (count <= 0) continue;\n if (worst === null || STATUS_SEVERITY[status] > STATUS_SEVERITY[worst]) worst = status;\n }\n return worst;\n}\n\nexport function overflowCountLabel(hiddenCount: number): string {\n return hiddenCount <= MAX_EXACT_OVERFLOW_COUNT\n ? String(hiddenCount)\n : COMPACT_COUNT_FORMAT.format(hiddenCount);\n}\n\nfunction countOf(jobs: WorkflowRunJobs, status: JobDisplayStatus): number {\n return jobs.statusCounts.find((entry) => entry.status === status)?.count ?? 0;\n}\n\nfunction displayStatus(job: WorkflowRunJobSummary): JobDisplayStatus {\n return deriveJobDisplayStatus({\n mode: job.mode,\n status: job.status,\n listenerStatus: job.listenerStatus,\n executionStatus: job.executionStatus,\n jobExecutions: [],\n });\n}\n"],"names":["Tooltip","TooltipContent","TooltipTrigger","Code","Text","cn","getWorkflowStatusVisual","WorkflowStatusIcon","deriveJobDisplayStatus","MAX_VISIBLE_JOBS","GLYPH_SIZE","MAX_TOOLTIP_JOB_NAMES","MAX_EXACT_OVERFLOW_COUNT","COMPACT_COUNT_FORMAT","Intl","NumberFormat","notation","maximumFractionDigits","STATUS_SEVERITY","failed","running","listening","pending","cancelled","skipped","succeeded","NOTABLE_STATUSES","JobStatusStrip","jobs","className","total","visible","preview","slice","hiddenCount","length","overflowStatus","worstHiddenStatus","summary","jobStatusSummary","asChild","span","role","aria-label","aria-hidden","map","job","status","displayStatus","size","ripple","tooltip","id","as","variant","overflowCountLabel","JobStatusStripTooltip","named","filter","includes","notableTotal","reduce","countOf","remaining","label","toLowerCase","name","key","breakdown","statusCounts","sort","left","right","count","join","hidden","Map","get","set","delete","worst","String","format","find","entry","mode","listenerStatus","executionStatus","jobExecutions"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/components/workflow-run-list/job-status-strip.tsx"],"sourcesContent":["import {WORKFLOW_RUN_JOB_PREVIEW_LIMIT} from '@shipfox/api-workflows-dto';\nimport {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Code, Text} from '@shipfox/react-ui/typography';\nimport {cn} from '@shipfox/react-ui/utils';\nimport {memo} from 'react';\nimport {getWorkflowStatusVisual} from '#components/workflow-status/status-visuals.js';\nimport {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';\nimport {deriveJobDisplayStatus, type JobDisplayStatus} from '#core/entities/job.js';\nimport type {WorkflowRunJobSummary, WorkflowRunJobs} from '#core/workflow-run.js';\n\nconst GLYPH_SIZE = 12;\nconst GLYPH_GAP = 3;\nconst OVERFLOW_GAP = 2;\nconst OVERFLOW_LABEL_WIDTH = 40;\nconst MAX_TOOLTIP_JOB_NAMES = 6;\n\n/**\n * Above this the overflow count is abbreviated.\n *\n * Nothing caps a workflow's job count, so an exact count is unbounded in width and would\n * eventually push the strip over its column. Abbreviating keeps the label at most five\n * characters for any magnitude, and the exact figure stays in the tooltip and the strip's\n * accessible name, which is where a precise number is actually read.\n */\nconst MAX_EXACT_OVERFLOW_COUNT = 999;\nconst COMPACT_COUNT_FORMAT = new Intl.NumberFormat('en', {\n notation: 'compact',\n maximumFractionDigits: 1,\n});\n\n/**\n * Reserves one full API preview and its overflow marker in a run row.\n *\n * Keep this derived from the API preview bound so the row reservation grows with the payload\n * contract instead of silently drifting into the duration and timestamp columns.\n */\nexport function jobStatusStripWidthForPreview(previewLimit: number): number {\n const previewWidth = previewLimit * GLYPH_SIZE + Math.max(previewLimit - 1, 0) * GLYPH_GAP;\n const overflowWidth = GLYPH_GAP + GLYPH_SIZE + OVERFLOW_GAP + OVERFLOW_LABEL_WIDTH;\n return previewWidth + overflowWidth;\n}\n\nexport const JOB_STATUS_STRIP_WIDTH = jobStatusStripWidthForPreview(WORKFLOW_RUN_JOB_PREVIEW_LIMIT);\n\n// Worst-first, so the overflow glyph reports the most alarming thing it is standing in for.\n// A strip that hides a failure behind eight green discs would be worse than no strip at all.\nconst STATUS_SEVERITY: Record<JobDisplayStatus, number> = {\n failed: 5,\n running: 4,\n listening: 4,\n pending: 3,\n cancelled: 2,\n skipped: 1,\n succeeded: 0,\n};\n\nconst NOTABLE_STATUSES: readonly JobDisplayStatus[] = ['failed', 'running', 'listening'];\n\nexport interface JobStatusStripProps {\n jobs: WorkflowRunJobs;\n className?: string | undefined;\n}\n\n/**\n * A run's jobs as a row of status glyphs, in graph order.\n *\n * This is the reason the list is worth its width: it answers \"where did this run fail\"\n * without opening the run. Glyphs are silent to assistive tech and carry no tooltip of their\n * own; the strip speaks once, as a summary, so a 40-job run does not read out 40 labels.\n *\n * What it draws comes from the preview; what it says comes from the counts, which cover every\n * job including those the server never sent. That split is what lets the overflow glyph\n * report a failure sitting past the preview instead of quietly dropping it.\n */\nexport const JobStatusStrip = memo(function JobStatusStrip({jobs, className}: JobStatusStripProps) {\n if (jobs.total === 0) return null;\n\n // The data frame gives the row enough room for the whole API preview. Only jobs beyond that\n // server-side preview need an overflow marker; the old seven-glyph cap belonged to the\n // previous 1120px page width.\n const visible = jobs.preview;\n const hiddenCount = jobs.total - visible.length;\n const overflowStatus = worstHiddenStatus(jobs, visible);\n const summary = jobStatusSummary(jobs);\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <span\n role=\"img\"\n aria-label={summary}\n className={cn('inline-flex items-center gap-[3px]', className)}\n >\n <span aria-hidden=\"true\" className=\"inline-flex items-center gap-[3px]\">\n {visible.map((job) => (\n <WorkflowStatusIcon\n key={job.id}\n status={displayStatus(job)}\n size={GLYPH_SIZE}\n // One ripple per running job, on every row, would turn a calm list into a\n // field of pulses. The run's own glyph already carries the live edge.\n ripple={false}\n tooltip={false}\n />\n ))}\n {hiddenCount > 0 && overflowStatus ? (\n <span className=\"inline-flex items-center gap-[2px]\">\n <WorkflowStatusIcon\n status={overflowStatus}\n size={GLYPH_SIZE}\n ripple={false}\n tooltip={false}\n />\n <Code\n as=\"span\"\n variant=\"label\"\n className=\"tabular-nums text-foreground-neutral-muted\"\n >\n +{overflowCountLabel(hiddenCount)}\n </Code>\n </span>\n ) : null}\n </span>\n </span>\n </TooltipTrigger>\n <TooltipContent>\n <JobStatusStripTooltip jobs={jobs} summary={summary} />\n </TooltipContent>\n </Tooltip>\n );\n});\n\nfunction JobStatusStripTooltip({jobs, summary}: {jobs: WorkflowRunJobs; summary: string}) {\n // Naming the jobs that need attention is the whole point of hovering; succeeded jobs are\n // left to the counts so the tooltip stays a glance rather than a list. Only previewed jobs\n // can be named, but the count of those left over is read off the totals, so a failure past\n // the preview is still accounted for rather than silently missing.\n const named = jobs.preview\n .filter((job) => NOTABLE_STATUSES.includes(displayStatus(job)))\n .slice(0, MAX_TOOLTIP_JOB_NAMES);\n const notableTotal = NOTABLE_STATUSES.reduce((total, status) => total + countOf(jobs, status), 0);\n const remaining = notableTotal - named.length;\n\n return (\n <span className=\"flex max-w-[280px] flex-col gap-tight\">\n <Text as=\"span\" size=\"xs\" className=\"block\">\n {summary}\n </Text>\n {named.map((job) => (\n <Code as=\"span\" variant=\"label\" key={job.id} className=\"block truncate\">\n {getWorkflowStatusVisual(displayStatus(job)).label.toLowerCase()} · {job.name ?? job.key}\n </Code>\n ))}\n {remaining > 0 ? (\n <Text as=\"span\" size=\"xs\" className=\"block text-foreground-neutral-muted\">\n and {remaining} more\n </Text>\n ) : null}\n </span>\n );\n}\n\n/** \"12 jobs: 9 succeeded, 2 failed, 1 running\" — a count is not an accessible name on its own. */\nexport function jobStatusSummary(jobs: WorkflowRunJobs): string {\n const breakdown = [...jobs.statusCounts]\n .sort((left, right) => STATUS_SEVERITY[right.status] - STATUS_SEVERITY[left.status])\n .map(({status, count}) => `${count} ${getWorkflowStatusVisual(status).label.toLowerCase()}`)\n .join(', ');\n\n return `${jobs.total} ${jobs.total === 1 ? 'job' : 'jobs'}: ${breakdown}`;\n}\n\n/**\n * The worst status among the jobs the strip is not drawing, found by subtracting what it\n * draws from the run's totals rather than by inspecting jobs it was never sent.\n */\nfunction worstHiddenStatus(\n jobs: WorkflowRunJobs,\n visible: readonly WorkflowRunJobSummary[],\n): JobDisplayStatus | null {\n const hidden = new Map(jobs.statusCounts.map(({status, count}) => [status, count]));\n for (const job of visible) {\n const status = displayStatus(job);\n const remaining = (hidden.get(status) ?? 0) - 1;\n if (remaining > 0) hidden.set(status, remaining);\n else hidden.delete(status);\n }\n\n let worst: JobDisplayStatus | null = null;\n for (const [status, count] of hidden) {\n if (count <= 0) continue;\n if (worst === null || STATUS_SEVERITY[status] > STATUS_SEVERITY[worst]) worst = status;\n }\n return worst;\n}\n\nexport function overflowCountLabel(hiddenCount: number): string {\n return hiddenCount <= MAX_EXACT_OVERFLOW_COUNT\n ? String(hiddenCount)\n : COMPACT_COUNT_FORMAT.format(hiddenCount);\n}\n\nfunction countOf(jobs: WorkflowRunJobs, status: JobDisplayStatus): number {\n return jobs.statusCounts.find((entry) => entry.status === status)?.count ?? 0;\n}\n\nfunction displayStatus(job: WorkflowRunJobSummary): JobDisplayStatus {\n return deriveJobDisplayStatus({\n mode: job.mode,\n status: job.status,\n listenerStatus: job.listenerStatus,\n executionStatus: job.executionStatus,\n jobExecutions: [],\n });\n}\n"],"names":["WORKFLOW_RUN_JOB_PREVIEW_LIMIT","Tooltip","TooltipContent","TooltipTrigger","Code","Text","cn","memo","getWorkflowStatusVisual","WorkflowStatusIcon","deriveJobDisplayStatus","GLYPH_SIZE","GLYPH_GAP","OVERFLOW_GAP","OVERFLOW_LABEL_WIDTH","MAX_TOOLTIP_JOB_NAMES","MAX_EXACT_OVERFLOW_COUNT","COMPACT_COUNT_FORMAT","Intl","NumberFormat","notation","maximumFractionDigits","jobStatusStripWidthForPreview","previewLimit","previewWidth","Math","max","overflowWidth","JOB_STATUS_STRIP_WIDTH","STATUS_SEVERITY","failed","running","listening","pending","cancelled","skipped","succeeded","NOTABLE_STATUSES","JobStatusStrip","jobs","className","total","visible","preview","hiddenCount","length","overflowStatus","worstHiddenStatus","summary","jobStatusSummary","asChild","span","role","aria-label","aria-hidden","map","job","status","displayStatus","size","ripple","tooltip","id","as","variant","overflowCountLabel","JobStatusStripTooltip","named","filter","includes","slice","notableTotal","reduce","countOf","remaining","label","toLowerCase","name","key","breakdown","statusCounts","sort","left","right","count","join","hidden","Map","get","set","delete","worst","String","format","find","entry","mode","listenerStatus","executionStatus","jobExecutions"],"mappings":"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"}
@@ -22,7 +22,7 @@ const STATUS_OPTIONS = WORKFLOW_RUN_LIST_STATUSES.map((status)=>({
22
22
  */ export function WorkflowRunFilters({ search, facets, onChange, onClear, hasActiveFilters }) {
23
23
  const [sheetOpen, setSheetOpen] = useState(false);
24
24
  return /*#__PURE__*/ _jsxs("div", {
25
- className: "flex flex-wrap items-center gap-inline",
25
+ className: "flex w-full flex-wrap items-center gap-inline",
26
26
  children: [
27
27
  /*#__PURE__*/ _jsx(Input, {
28
28
  value: search.search ?? '',
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/workflow-run-list/workflow-run-filters.tsx"],"sourcesContent":["import {Button} from '@shipfox/react-ui/button';\nimport {DateRangePicker} from '@shipfox/react-ui/date-range-picker';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Input} from '@shipfox/react-ui/input';\nimport {Label} from '@shipfox/react-ui/label';\nimport {\n Sheet,\n SheetBody,\n SheetContent,\n SheetFooter,\n SheetHeader,\n SheetTitle,\n SheetTrigger,\n} from '@shipfox/react-ui/sheet';\nimport {useState} from 'react';\nimport {getWorkflowStatusVisual} from '#components/workflow-status/status-visuals.js';\nimport {\n WORKFLOW_RUN_LIST_STATUSES,\n type WorkflowRunFilterPatch,\n type WorkflowRunListStatus,\n type WorkflowRunsSearch,\n} from '#routes/inputs.js';\nimport type {WorkflowRunFacets} from './run-display.js';\nimport {WorkflowRunFilterMenu, type WorkflowRunFilterOption} from './workflow-run-filter-menu.js';\n\nconst STATUS_OPTIONS: WorkflowRunFilterOption[] = WORKFLOW_RUN_LIST_STATUSES.map((status) => ({\n value: status,\n label: getWorkflowStatusVisual(status).label,\n}));\n\nexport interface WorkflowRunFiltersProps {\n search: WorkflowRunsSearch;\n facets: WorkflowRunFacets;\n onChange: (patch: WorkflowRunFilterPatch) => void;\n onClear: () => void;\n hasActiveFilters: boolean;\n}\n\n/**\n * The run list filter row.\n *\n * Inline from `md` up, and behind a sheet below it, where a five-control toolbar would eat\n * the viewport the list is supposed to fill. Both layouts render the same controls, so the\n * narrow surface is the full filter set rather than a reduced one.\n */\nexport function WorkflowRunFilters({\n search,\n facets,\n onChange,\n onClear,\n hasActiveFilters,\n}: WorkflowRunFiltersProps) {\n const [sheetOpen, setSheetOpen] = useState(false);\n\n return (\n <div className=\"flex flex-wrap items-center gap-inline\">\n <Input\n value={search.search ?? ''}\n onChange={(event) => onChange({search: event.target.value})}\n placeholder=\"Search runs by name, number, branch, or commit\"\n aria-label=\"Search runs\"\n size=\"small\"\n className=\"min-w-[240px] flex-1\"\n iconLeft={<Icon name=\"searchLine\" className=\"size-14 text-foreground-neutral-muted\" />}\n />\n\n <div className=\"hidden flex-wrap items-center gap-inline md:flex\">\n <WorkflowRunFilterControls search={search} facets={facets} onChange={onChange} />\n {hasActiveFilters ? <ClearFiltersButton onClear={onClear} /> : null}\n </div>\n\n <Sheet open={sheetOpen} onOpenChange={setSheetOpen}>\n <SheetTrigger asChild>\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n iconLeft=\"filterLine\"\n className=\"md:hidden\"\n >\n Filters\n </Button>\n </SheetTrigger>\n <SheetContent side=\"bottom\" aria-describedby={undefined}>\n <SheetHeader>\n <SheetTitle>Filter runs</SheetTitle>\n </SheetHeader>\n <SheetBody className=\"flex flex-col gap-cluster\">\n <WorkflowRunFilterControls\n search={search}\n facets={facets}\n onChange={onChange}\n stacked\n />\n </SheetBody>\n <SheetFooter>\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n disabled={!hasActiveFilters}\n onClick={onClear}\n >\n Clear filters\n </Button>\n <Button type=\"button\" variant=\"primary\" size=\"sm\" onClick={() => setSheetOpen(false)}>\n Show runs\n </Button>\n </SheetFooter>\n </SheetContent>\n </Sheet>\n </div>\n );\n}\n\nfunction WorkflowRunFilterControls({\n search,\n facets,\n onChange,\n stacked = false,\n}: {\n search: WorkflowRunsSearch;\n facets: WorkflowRunFacets;\n onChange: (patch: WorkflowRunFilterPatch) => void;\n stacked?: boolean;\n}) {\n const controlClassName = stacked ? 'w-full max-w-none' : undefined;\n\n return (\n <>\n <WorkflowRunFilterMenu\n label=\"Status\"\n options={STATUS_OPTIONS}\n selected={search.status ?? []}\n onChange={(next) => onChange({status: next as WorkflowRunListStatus[]})}\n emptyMessage=\"No statuses available.\"\n className={controlClassName}\n />\n <WorkflowRunFilterMenu\n label=\"Branch\"\n options={toOptions(facets.branch)}\n selected={search.branch ?? []}\n onChange={(branch) => onChange({branch})}\n emptyMessage=\"No branches in the loaded runs.\"\n className={controlClassName}\n />\n <WorkflowRunFilterMenu\n label=\"Actor\"\n options={toOptions(facets.actor)}\n selected={search.actor ?? []}\n onChange={(actor) => onChange({actor})}\n emptyMessage=\"No actors in the loaded runs.\"\n className={controlClassName}\n />\n <WorkflowRunFilterMenu\n label=\"Event\"\n options={toOptions(facets.event)}\n selected={search.event ?? []}\n onChange={(event) => onChange({event})}\n emptyMessage=\"No events in the loaded runs.\"\n className={controlClassName}\n />\n {stacked ? (\n <div className=\"flex flex-col gap-inline\">\n <Label>Created between</Label>\n <RunDateRangeFilter search={search} onChange={onChange} />\n </div>\n ) : (\n <RunDateRangeFilter search={search} onChange={onChange} />\n )}\n </>\n );\n}\n\nfunction RunDateRangeFilter({\n search,\n onChange,\n}: {\n search: WorkflowRunsSearch;\n onChange: (patch: WorkflowRunFilterPatch) => void;\n}) {\n return (\n <DateRangePicker\n size=\"small\"\n aria-label=\"Filter runs by creation date\"\n // The shared picker renders a value only when both bounds are set, so a one-sided filter\n // would otherwise read as unset. Naming the active bound keeps it visible without\n // inventing the bound the user did not choose.\n placeholder={partialDateLabel(search) ?? 'Any date'}\n dateRange={{\n ...(search.after ? {start: fromCalendarDate(search.after)} : {}),\n ...(search.before ? {end: fromCalendarDate(search.before)} : {}),\n }}\n onDateRangeSelect={(range) =>\n onChange({\n after: range?.start ? toCalendarDate(range.start) : undefined,\n before: range?.end ? toCalendarDate(range.end) : undefined,\n })\n }\n onClear={() => onChange({after: undefined, before: undefined})}\n />\n );\n}\n\nfunction ClearFiltersButton({onClear}: {onClear: () => void}) {\n return (\n <Button\n type=\"button\"\n variant=\"transparentMuted\"\n size=\"sm\"\n iconLeft=\"closeLine\"\n onClick={onClear}\n >\n Clear filters\n </Button>\n );\n}\n\n/** Describes a bound the picker cannot display on its own, or nothing when it can. */\nfunction partialDateLabel(search: WorkflowRunsSearch): string | undefined {\n if (search.after && search.before) return undefined;\n if (search.after) return `From ${search.after}`;\n if (search.before) return `Until ${search.before}`;\n return undefined;\n}\n\nfunction toOptions(values: readonly string[]): WorkflowRunFilterOption[] {\n return values.map((value) => ({value, label: value}));\n}\n\n// The bounds are local calendar dates, matching how each row's date reads, so the picker and\n// the URL agree with what the list shows rather than with UTC.\nfunction toCalendarDate(date: Date): string {\n const month = String(date.getMonth() + 1).padStart(2, '0');\n const day = String(date.getDate()).padStart(2, '0');\n return `${date.getFullYear()}-${month}-${day}`;\n}\n\nfunction fromCalendarDate(value: string): Date | undefined {\n const [year, month, day] = value.split('-').map(Number) as [number, number, number];\n const date = new Date(year, month - 1, day);\n return Number.isNaN(date.getTime()) ? undefined : date;\n}\n"],"names":["Button","DateRangePicker","Icon","Input","Label","Sheet","SheetBody","SheetContent","SheetFooter","SheetHeader","SheetTitle","SheetTrigger","useState","getWorkflowStatusVisual","WORKFLOW_RUN_LIST_STATUSES","WorkflowRunFilterMenu","STATUS_OPTIONS","map","status","value","label","WorkflowRunFilters","search","facets","onChange","onClear","hasActiveFilters","sheetOpen","setSheetOpen","div","className","event","target","placeholder","aria-label","size","iconLeft","name","WorkflowRunFilterControls","ClearFiltersButton","open","onOpenChange","asChild","type","variant","side","aria-describedby","undefined","stacked","disabled","onClick","controlClassName","options","selected","next","emptyMessage","toOptions","branch","actor","RunDateRangeFilter","partialDateLabel","dateRange","after","start","fromCalendarDate","before","end","onDateRangeSelect","range","toCalendarDate","values","date","month","String","getMonth","padStart","day","getDate","getFullYear","year","split","Number","Date","isNaN","getTime"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/components/workflow-run-list/workflow-run-filters.tsx"],"sourcesContent":["import {Button} from '@shipfox/react-ui/button';\nimport {DateRangePicker} from '@shipfox/react-ui/date-range-picker';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Input} from '@shipfox/react-ui/input';\nimport {Label} from '@shipfox/react-ui/label';\nimport {\n Sheet,\n SheetBody,\n SheetContent,\n SheetFooter,\n SheetHeader,\n SheetTitle,\n SheetTrigger,\n} from '@shipfox/react-ui/sheet';\nimport {useState} from 'react';\nimport {getWorkflowStatusVisual} from '#components/workflow-status/status-visuals.js';\nimport {\n WORKFLOW_RUN_LIST_STATUSES,\n type WorkflowRunFilterPatch,\n type WorkflowRunListStatus,\n type WorkflowRunsSearch,\n} from '#routes/inputs.js';\nimport type {WorkflowRunFacets} from './run-display.js';\nimport {WorkflowRunFilterMenu, type WorkflowRunFilterOption} from './workflow-run-filter-menu.js';\n\nconst STATUS_OPTIONS: WorkflowRunFilterOption[] = WORKFLOW_RUN_LIST_STATUSES.map((status) => ({\n value: status,\n label: getWorkflowStatusVisual(status).label,\n}));\n\nexport interface WorkflowRunFiltersProps {\n search: WorkflowRunsSearch;\n facets: WorkflowRunFacets;\n onChange: (patch: WorkflowRunFilterPatch) => void;\n onClear: () => void;\n hasActiveFilters: boolean;\n}\n\n/**\n * The run list filter row.\n *\n * Inline from `md` up, and behind a sheet below it, where a five-control toolbar would eat\n * the viewport the list is supposed to fill. Both layouts render the same controls, so the\n * narrow surface is the full filter set rather than a reduced one.\n */\nexport function WorkflowRunFilters({\n search,\n facets,\n onChange,\n onClear,\n hasActiveFilters,\n}: WorkflowRunFiltersProps) {\n const [sheetOpen, setSheetOpen] = useState(false);\n\n return (\n <div className=\"flex w-full flex-wrap items-center gap-inline\">\n <Input\n value={search.search ?? ''}\n onChange={(event) => onChange({search: event.target.value})}\n placeholder=\"Search runs by name, number, branch, or commit\"\n aria-label=\"Search runs\"\n size=\"small\"\n className=\"min-w-[240px] flex-1\"\n iconLeft={<Icon name=\"searchLine\" className=\"size-14 text-foreground-neutral-muted\" />}\n />\n\n <div className=\"hidden flex-wrap items-center gap-inline md:flex\">\n <WorkflowRunFilterControls search={search} facets={facets} onChange={onChange} />\n {hasActiveFilters ? <ClearFiltersButton onClear={onClear} /> : null}\n </div>\n\n <Sheet open={sheetOpen} onOpenChange={setSheetOpen}>\n <SheetTrigger asChild>\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n iconLeft=\"filterLine\"\n className=\"md:hidden\"\n >\n Filters\n </Button>\n </SheetTrigger>\n <SheetContent side=\"bottom\" aria-describedby={undefined}>\n <SheetHeader>\n <SheetTitle>Filter runs</SheetTitle>\n </SheetHeader>\n <SheetBody className=\"flex flex-col gap-cluster\">\n <WorkflowRunFilterControls\n search={search}\n facets={facets}\n onChange={onChange}\n stacked\n />\n </SheetBody>\n <SheetFooter>\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n disabled={!hasActiveFilters}\n onClick={onClear}\n >\n Clear filters\n </Button>\n <Button type=\"button\" variant=\"primary\" size=\"sm\" onClick={() => setSheetOpen(false)}>\n Show runs\n </Button>\n </SheetFooter>\n </SheetContent>\n </Sheet>\n </div>\n );\n}\n\nfunction WorkflowRunFilterControls({\n search,\n facets,\n onChange,\n stacked = false,\n}: {\n search: WorkflowRunsSearch;\n facets: WorkflowRunFacets;\n onChange: (patch: WorkflowRunFilterPatch) => void;\n stacked?: boolean;\n}) {\n const controlClassName = stacked ? 'w-full max-w-none' : undefined;\n\n return (\n <>\n <WorkflowRunFilterMenu\n label=\"Status\"\n options={STATUS_OPTIONS}\n selected={search.status ?? []}\n onChange={(next) => onChange({status: next as WorkflowRunListStatus[]})}\n emptyMessage=\"No statuses available.\"\n className={controlClassName}\n />\n <WorkflowRunFilterMenu\n label=\"Branch\"\n options={toOptions(facets.branch)}\n selected={search.branch ?? []}\n onChange={(branch) => onChange({branch})}\n emptyMessage=\"No branches in the loaded runs.\"\n className={controlClassName}\n />\n <WorkflowRunFilterMenu\n label=\"Actor\"\n options={toOptions(facets.actor)}\n selected={search.actor ?? []}\n onChange={(actor) => onChange({actor})}\n emptyMessage=\"No actors in the loaded runs.\"\n className={controlClassName}\n />\n <WorkflowRunFilterMenu\n label=\"Event\"\n options={toOptions(facets.event)}\n selected={search.event ?? []}\n onChange={(event) => onChange({event})}\n emptyMessage=\"No events in the loaded runs.\"\n className={controlClassName}\n />\n {stacked ? (\n <div className=\"flex flex-col gap-inline\">\n <Label>Created between</Label>\n <RunDateRangeFilter search={search} onChange={onChange} />\n </div>\n ) : (\n <RunDateRangeFilter search={search} onChange={onChange} />\n )}\n </>\n );\n}\n\nfunction RunDateRangeFilter({\n search,\n onChange,\n}: {\n search: WorkflowRunsSearch;\n onChange: (patch: WorkflowRunFilterPatch) => void;\n}) {\n return (\n <DateRangePicker\n size=\"small\"\n aria-label=\"Filter runs by creation date\"\n // The shared picker renders a value only when both bounds are set, so a one-sided filter\n // would otherwise read as unset. Naming the active bound keeps it visible without\n // inventing the bound the user did not choose.\n placeholder={partialDateLabel(search) ?? 'Any date'}\n dateRange={{\n ...(search.after ? {start: fromCalendarDate(search.after)} : {}),\n ...(search.before ? {end: fromCalendarDate(search.before)} : {}),\n }}\n onDateRangeSelect={(range) =>\n onChange({\n after: range?.start ? toCalendarDate(range.start) : undefined,\n before: range?.end ? toCalendarDate(range.end) : undefined,\n })\n }\n onClear={() => onChange({after: undefined, before: undefined})}\n />\n );\n}\n\nfunction ClearFiltersButton({onClear}: {onClear: () => void}) {\n return (\n <Button\n type=\"button\"\n variant=\"transparentMuted\"\n size=\"sm\"\n iconLeft=\"closeLine\"\n onClick={onClear}\n >\n Clear filters\n </Button>\n );\n}\n\n/** Describes a bound the picker cannot display on its own, or nothing when it can. */\nfunction partialDateLabel(search: WorkflowRunsSearch): string | undefined {\n if (search.after && search.before) return undefined;\n if (search.after) return `From ${search.after}`;\n if (search.before) return `Until ${search.before}`;\n return undefined;\n}\n\nfunction toOptions(values: readonly string[]): WorkflowRunFilterOption[] {\n return values.map((value) => ({value, label: value}));\n}\n\n// The bounds are local calendar dates, matching how each row's date reads, so the picker and\n// the URL agree with what the list shows rather than with UTC.\nfunction toCalendarDate(date: Date): string {\n const month = String(date.getMonth() + 1).padStart(2, '0');\n const day = String(date.getDate()).padStart(2, '0');\n return `${date.getFullYear()}-${month}-${day}`;\n}\n\nfunction fromCalendarDate(value: string): Date | undefined {\n const [year, month, day] = value.split('-').map(Number) as [number, number, number];\n const date = new Date(year, month - 1, day);\n return Number.isNaN(date.getTime()) ? undefined : date;\n}\n"],"names":["Button","DateRangePicker","Icon","Input","Label","Sheet","SheetBody","SheetContent","SheetFooter","SheetHeader","SheetTitle","SheetTrigger","useState","getWorkflowStatusVisual","WORKFLOW_RUN_LIST_STATUSES","WorkflowRunFilterMenu","STATUS_OPTIONS","map","status","value","label","WorkflowRunFilters","search","facets","onChange","onClear","hasActiveFilters","sheetOpen","setSheetOpen","div","className","event","target","placeholder","aria-label","size","iconLeft","name","WorkflowRunFilterControls","ClearFiltersButton","open","onOpenChange","asChild","type","variant","side","aria-describedby","undefined","stacked","disabled","onClick","controlClassName","options","selected","next","emptyMessage","toOptions","branch","actor","RunDateRangeFilter","partialDateLabel","dateRange","after","start","fromCalendarDate","before","end","onDateRangeSelect","range","toCalendarDate","values","date","month","String","getMonth","padStart","day","getDate","getFullYear","year","split","Number","Date","isNaN","getTime"],"mappings":"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"}