@shipfox/client-workflows 20.0.0 → 22.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 (118) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +51 -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 +232 -44
  5. package/dist/components/job-detail/job-detail-view.js.map +1 -1
  6. package/dist/components/job-detail/job-empty-states.js +2 -2
  7. package/dist/components/job-detail/job-empty-states.js.map +1 -1
  8. package/dist/components/job-detail/step-attempt-log-panel.d.ts +7 -1
  9. package/dist/components/job-detail/step-attempt-log-panel.d.ts.map +1 -1
  10. package/dist/components/job-detail/step-attempt-log-panel.js +37 -10
  11. package/dist/components/job-detail/step-attempt-log-panel.js.map +1 -1
  12. package/dist/components/job-graph/job-graph-content.js +2 -2
  13. package/dist/components/job-graph/job-graph-content.js.map +1 -1
  14. package/dist/components/step-list/step-list-model.d.ts +1 -0
  15. package/dist/components/step-list/step-list-model.d.ts.map +1 -1
  16. package/dist/components/step-list/step-list-model.js +1 -1
  17. package/dist/components/step-list/step-list-model.js.map +1 -1
  18. package/dist/components/step-list/step-list.d.ts +2 -1
  19. package/dist/components/step-list/step-list.d.ts.map +1 -1
  20. package/dist/components/step-list/step-list.js +25 -9
  21. package/dist/components/step-list/step-list.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-content.d.ts.map +1 -1
  29. package/dist/components/workflow-run-list/workflow-run-list-content.js +13 -7
  30. package/dist/components/workflow-run-list/workflow-run-list-content.js.map +1 -1
  31. package/dist/components/workflow-run-list/workflow-run-list-states.d.ts.map +1 -1
  32. package/dist/components/workflow-run-list/workflow-run-list-states.js +43 -38
  33. package/dist/components/workflow-run-list/workflow-run-list-states.js.map +1 -1
  34. package/dist/components/workflow-run-list/workflow-run-list-view.d.ts.map +1 -1
  35. package/dist/components/workflow-run-list/workflow-run-list-view.js +34 -22
  36. package/dist/components/workflow-run-list/workflow-run-list-view.js.map +1 -1
  37. package/dist/components/workflow-run-list/workflow-run-row.d.ts +3 -3
  38. package/dist/components/workflow-run-list/workflow-run-row.d.ts.map +1 -1
  39. package/dist/components/workflow-run-list/workflow-run-row.js +25 -18
  40. package/dist/components/workflow-run-list/workflow-run-row.js.map +1 -1
  41. package/dist/components/workflow-run-summary/workflow-run-summary.d.ts.map +1 -1
  42. package/dist/components/workflow-run-summary/workflow-run-summary.js +4 -4
  43. package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -1
  44. package/dist/components/workflow-run-view/run-workspace-nav.d.ts.map +1 -1
  45. package/dist/components/workflow-run-view/run-workspace-nav.js +20 -7
  46. package/dist/components/workflow-run-view/run-workspace-nav.js.map +1 -1
  47. package/dist/components/workflow-run-view/workflow-run-states.d.ts.map +1 -1
  48. package/dist/components/workflow-run-view/workflow-run-states.js +1 -1
  49. package/dist/components/workflow-run-view/workflow-run-states.js.map +1 -1
  50. package/dist/components/workflow-run-view/workflow-run-view.d.ts.map +1 -1
  51. package/dist/components/workflow-run-view/workflow-run-view.js +103 -78
  52. package/dist/components/workflow-run-view/workflow-run-view.js.map +1 -1
  53. package/dist/components/workflow-source-panel/workflow-source-content.js +1 -1
  54. package/dist/components/workflow-source-panel/workflow-source-content.js.map +1 -1
  55. package/dist/pages/project-workflows-page.d.ts.map +1 -1
  56. package/dist/pages/project-workflows-page.js +171 -158
  57. package/dist/pages/project-workflows-page.js.map +1 -1
  58. package/dist/pages/workflow-job-detail-page.d.ts.map +1 -1
  59. package/dist/pages/workflow-job-detail-page.js +3 -2
  60. package/dist/pages/workflow-job-detail-page.js.map +1 -1
  61. package/dist/pages/workflow-run-detail-page.js +2 -1
  62. package/dist/pages/workflow-run-detail-page.js.map +1 -1
  63. package/dist/pages/workflow-run-list-page.d.ts.map +1 -1
  64. package/dist/pages/workflow-run-list-page.js +9 -11
  65. package/dist/pages/workflow-run-list-page.js.map +1 -1
  66. package/dist/routes/job-detail.d.ts +1 -1
  67. package/dist/routes/job-detail.js +1 -1
  68. package/dist/routes/job-detail.js.map +1 -1
  69. package/dist/routes/run-detail.d.ts +1 -1
  70. package/dist/routes/run-detail.js +1 -1
  71. package/dist/routes/run-detail.js.map +1 -1
  72. package/dist/routes/runs.d.ts +1 -1
  73. package/dist/routes/runs.js +1 -1
  74. package/dist/routes/runs.js.map +1 -1
  75. package/dist/routes/workflows.d.ts +3 -0
  76. package/dist/routes/workflows.js +3 -0
  77. package/dist/routes/workflows.js.map +1 -1
  78. package/dist/tsconfig.test.tsbuildinfo +1 -1
  79. package/package.json +8 -8
  80. package/src/components/job-detail/job-detail-view.tsx +264 -49
  81. package/src/components/job-detail/job-empty-states.tsx +2 -2
  82. package/src/components/job-detail/step-attempt-log-panel.test.tsx +30 -12
  83. package/src/components/job-detail/step-attempt-log-panel.tsx +68 -10
  84. package/src/components/job-graph/job-graph-content.tsx +2 -2
  85. package/src/components/job-graph/job-graph.stories.tsx +1 -1
  86. package/src/components/job-graph/job-node.stories.tsx +1 -1
  87. package/src/components/step-list/step-list-model.ts +1 -1
  88. package/src/components/step-list/step-list.test.tsx +29 -1
  89. package/src/components/step-list/step-list.tsx +29 -11
  90. package/src/components/workflow-run-list/job-status-strip.tsx +25 -12
  91. package/src/components/workflow-run-list/workflow-run-filters.tsx +1 -1
  92. package/src/components/workflow-run-list/workflow-run-list-content.tsx +7 -2
  93. package/src/components/workflow-run-list/workflow-run-list-states.tsx +42 -41
  94. package/src/components/workflow-run-list/workflow-run-list-view.test.tsx +53 -4
  95. package/src/components/workflow-run-list/workflow-run-list-view.tsx +28 -22
  96. package/src/components/workflow-run-list/workflow-run-list.stories.tsx +9 -20
  97. package/src/components/workflow-run-list/workflow-run-row.tsx +26 -19
  98. package/src/components/workflow-run-summary/workflow-run-summary.stories.tsx +3 -3
  99. package/src/components/workflow-run-summary/workflow-run-summary.test.tsx +11 -2
  100. package/src/components/workflow-run-summary/workflow-run-summary.tsx +4 -7
  101. package/src/components/workflow-run-view/run-workspace-nav.test.tsx +40 -2
  102. package/src/components/workflow-run-view/run-workspace-nav.tsx +28 -9
  103. package/src/components/workflow-run-view/workflow-run-states.tsx +1 -4
  104. package/src/components/workflow-run-view/workflow-run-view.stories.tsx +142 -0
  105. package/src/components/workflow-run-view/workflow-run-view.test.tsx +42 -8
  106. package/src/components/workflow-run-view/workflow-run-view.tsx +112 -96
  107. package/src/components/workflow-source-panel/workflow-source-content.tsx +2 -2
  108. package/src/pages/project-workflows-page.tsx +135 -125
  109. package/src/pages/workflow-job-detail-page.test.tsx +32 -0
  110. package/src/pages/workflow-job-detail-page.tsx +5 -1
  111. package/src/pages/workflow-run-detail-page.tsx +1 -1
  112. package/src/pages/workflow-run-list-page.tsx +9 -11
  113. package/src/pages/workflow-run-pages.test.tsx +26 -0
  114. package/src/routes/job-detail.tsx +1 -1
  115. package/src/routes/run-detail.tsx +1 -1
  116. package/src/routes/runs.tsx +1 -1
  117. package/src/routes/workflows.tsx +1 -0
  118. package/tsconfig.build.tsbuildinfo +1 -1
@@ -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":";AAAA,SAAQA,8BAA8B,QAAO,6BAA6B;AAC1E,SAAQC,OAAO,EAAEC,cAAc,EAAEC,cAAc,QAAO,4BAA4B;AAClF,SAAQC,IAAI,EAAEC,IAAI,QAAO,+BAA+B;AACxD,SAAQC,EAAE,QAAO,0BAA0B;AAC3C,SAAQC,IAAI,QAAO,QAAQ;AAC3B,SAAQC,uBAAuB,QAAO,gDAAgD;AACtF,SAAQC,kBAAkB,QAAO,sDAAsD;AACvF,SAAQC,sBAAsB,QAA8B,wBAAwB;AAGpF,MAAMC,aAAa;AACnB,MAAMC,YAAY;AAClB,MAAMC,eAAe;AACrB,MAAMC,uBAAuB;AAC7B,MAAMC,wBAAwB;AAE9B;;;;;;;CAOC,GACD,MAAMC,2BAA2B;AACjC,MAAMC,uBAAuB,IAAIC,KAAKC,YAAY,CAAC,MAAM;IACvDC,UAAU;IACVC,uBAAuB;AACzB;AAEA;;;;;CAKC,GACD,OAAO,SAASC,8BAA8BC,YAAoB;IAChE,MAAMC,eAAeD,eAAeZ,aAAac,KAAKC,GAAG,CAACH,eAAe,GAAG,KAAKX;IACjF,MAAMe,gBAAgBf,YAAYD,aAAaE,eAAeC;IAC9D,OAAOU,eAAeG;AACxB;AAEA,OAAO,MAAMC,yBAAyBN,8BAA8BtB,gCAAgC;AAEpG,4FAA4F;AAC5F,6FAA6F;AAC7F,MAAM6B,kBAAoD;IACxDC,QAAQ;IACRC,SAAS;IACTC,WAAW;IACXC,SAAS;IACTC,WAAW;IACXC,SAAS;IACTC,WAAW;AACb;AAEA,MAAMC,mBAAgD;IAAC;IAAU;IAAW;CAAY;AAOxF;;;;;;;;;;CAUC,GACD,OAAO,MAAMC,+BAAiB/B,KAAK,SAAS+B,eAAe,EAACC,IAAI,EAAEC,SAAS,EAAsB;IAC/F,IAAID,KAAKE,KAAK,KAAK,GAAG,OAAO;IAE7B,4FAA4F;IAC5F,uFAAuF;IACvF,8BAA8B;IAC9B,MAAMC,UAAUH,KAAKI,OAAO;IAC5B,MAAMC,cAAcL,KAAKE,KAAK,GAAGC,QAAQG,MAAM;IAC/C,MAAMC,iBAAiBC,kBAAkBR,MAAMG;IAC/C,MAAMM,UAAUC,iBAAiBV;IAEjC,qBACE,MAACtC;;0BACC,KAACE;gBAAe+C,OAAO;0BACrB,cAAA,KAACC;oBACCC,MAAK;oBACLC,cAAYL;oBACZR,WAAWlC,GAAG,sCAAsCkC;8BAEpD,cAAA,MAACW;wBAAKG,eAAY;wBAAOd,WAAU;;4BAChCE,QAAQa,GAAG,CAAC,CAACC,oBACZ,KAAC/C;oCAECgD,QAAQC,cAAcF;oCACtBG,MAAMhD;oCACN,0EAA0E;oCAC1E,sEAAsE;oCACtEiD,QAAQ;oCACRC,SAAS;mCANJL,IAAIM,EAAE;4BASdlB,cAAc,KAAKE,+BAClB,MAACK;gCAAKX,WAAU;;kDACd,KAAC/B;wCACCgD,QAAQX;wCACRa,MAAMhD;wCACNiD,QAAQ;wCACRC,SAAS;;kDAEX,MAACzD;wCACC2D,IAAG;wCACHC,SAAQ;wCACRxB,WAAU;;4CACX;4CACGyB,mBAAmBrB;;;;iCAGvB;;;;;0BAIV,KAAC1C;0BACC,cAAA,KAACgE;oBAAsB3B,MAAMA;oBAAMS,SAASA;;;;;AAIpD,GAAG;AAEH,SAASkB,sBAAsB,EAAC3B,IAAI,EAAES,OAAO,EAA2C;IACtF,yFAAyF;IACzF,2FAA2F;IAC3F,2FAA2F;IAC3F,mEAAmE;IACnE,MAAMmB,QAAQ5B,KAAKI,OAAO,CACvByB,MAAM,CAAC,CAACZ,MAAQnB,iBAAiBgC,QAAQ,CAACX,cAAcF,OACxDc,KAAK,CAAC,GAAGvD;IACZ,MAAMwD,eAAelC,iBAAiBmC,MAAM,CAAC,CAAC/B,OAAOgB,SAAWhB,QAAQgC,QAAQlC,MAAMkB,SAAS;IAC/F,MAAMiB,YAAYH,eAAeJ,MAAMtB,MAAM;IAE7C,qBACE,MAACM;QAAKX,WAAU;;0BACd,KAACnC;gBAAK0D,IAAG;gBAAOJ,MAAK;gBAAKnB,WAAU;0BACjCQ;;YAEFmB,MAAMZ,GAAG,CAAC,CAACC,oBACV,MAACpD;oBAAK2D,IAAG;oBAAOC,SAAQ;oBAAqBxB,WAAU;;wBACpDhC,wBAAwBkD,cAAcF,MAAMmB,KAAK,CAACC,WAAW;wBAAG;wBAAIpB,IAAIqB,IAAI,IAAIrB,IAAIsB,GAAG;;mBADrDtB,IAAIM,EAAE;YAI5CY,YAAY,kBACX,MAACrE;gBAAK0D,IAAG;gBAAOJ,MAAK;gBAAKnB,WAAU;;oBAAsC;oBACnEkC;oBAAU;;iBAEf;;;AAGV;AAEA,gGAAgG,GAChG,OAAO,SAASzB,iBAAiBV,IAAqB;IACpD,MAAMwC,YAAY;WAAIxC,KAAKyC,YAAY;KAAC,CACrCC,IAAI,CAAC,CAACC,MAAMC,QAAUtD,eAAe,CAACsD,MAAM1B,MAAM,CAAC,GAAG5B,eAAe,CAACqD,KAAKzB,MAAM,CAAC,EAClFF,GAAG,CAAC,CAAC,EAACE,MAAM,EAAE2B,KAAK,EAAC,GAAK,GAAGA,MAAM,CAAC,EAAE5E,wBAAwBiD,QAAQkB,KAAK,CAACC,WAAW,IAAI,EAC1FS,IAAI,CAAC;IAER,OAAO,GAAG9C,KAAKE,KAAK,CAAC,CAAC,EAAEF,KAAKE,KAAK,KAAK,IAAI,QAAQ,OAAO,EAAE,EAAEsC,WAAW;AAC3E;AAEA;;;CAGC,GACD,SAAShC,kBACPR,IAAqB,EACrBG,OAAyC;IAEzC,MAAM4C,SAAS,IAAIC,IAAIhD,KAAKyC,YAAY,CAACzB,GAAG,CAAC,CAAC,EAACE,MAAM,EAAE2B,KAAK,EAAC,GAAK;YAAC3B;YAAQ2B;SAAM;IACjF,KAAK,MAAM5B,OAAOd,QAAS;QACzB,MAAMe,SAASC,cAAcF;QAC7B,MAAMkB,YAAY,AAACY,CAAAA,OAAOE,GAAG,CAAC/B,WAAW,CAAA,IAAK;QAC9C,IAAIiB,YAAY,GAAGY,OAAOG,GAAG,CAAChC,QAAQiB;aACjCY,OAAOI,MAAM,CAACjC;IACrB;IAEA,IAAIkC,QAAiC;IACrC,KAAK,MAAM,CAAClC,QAAQ2B,MAAM,IAAIE,OAAQ;QACpC,IAAIF,SAAS,GAAG;QAChB,IAAIO,UAAU,QAAQ9D,eAAe,CAAC4B,OAAO,GAAG5B,eAAe,CAAC8D,MAAM,EAAEA,QAAQlC;IAClF;IACA,OAAOkC;AACT;AAEA,OAAO,SAAS1B,mBAAmBrB,WAAmB;IACpD,OAAOA,eAAe5B,2BAClB4E,OAAOhD,eACP3B,qBAAqB4E,MAAM,CAACjD;AAClC;AAEA,SAAS6B,QAAQlC,IAAqB,EAAEkB,MAAwB;IAC9D,OAAOlB,KAAKyC,YAAY,CAACc,IAAI,CAAC,CAACC,QAAUA,MAAMtC,MAAM,KAAKA,SAAS2B,SAAS;AAC9E;AAEA,SAAS1B,cAAcF,GAA0B;IAC/C,OAAO9C,uBAAuB;QAC5BsF,MAAMxC,IAAIwC,IAAI;QACdvC,QAAQD,IAAIC,MAAM;QAClBwC,gBAAgBzC,IAAIyC,cAAc;QAClCC,iBAAiB1C,IAAI0C,eAAe;QACpCC,eAAe,EAAE;IACnB;AACF"}
@@ -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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-run-list-content.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-list/workflow-run-list-content.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,uBAAuB,CAAC;AAC/D,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,YAAY,CAAC;AAUrD,UAAU,2BAA2B;IACnC,KAAK,EAAE,oBAAoB,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,mBAAmB,EAAE,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,WAAW,EAAE,OAAO,CAAC;IACrB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,wBAAgB,sBAAsB,CAAC,EACrC,KAAK,EACL,SAAS,EACT,IAAI,EACJ,aAAa,EACb,WAAW,EACX,gBAAgB,EAChB,cAAc,EACd,WAAW,EACX,kBAAkB,EAClB,oBAAoB,EACpB,UAAU,GACX,EAAE,2BAA2B,+BA+C7B"}
1
+ {"version":3,"file":"workflow-run-list-content.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-list/workflow-run-list-content.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,uBAAuB,CAAC;AAC/D,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,YAAY,CAAC;AAUrD,UAAU,2BAA2B;IACnC,KAAK,EAAE,oBAAoB,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,mBAAmB,EAAE,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,WAAW,EAAE,OAAO,CAAC;IACrB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,wBAAgB,sBAAsB,CAAC,EACrC,KAAK,EACL,SAAS,EACT,IAAI,EACJ,aAAa,EACb,WAAW,EACX,gBAAgB,EAChB,cAAc,EACd,WAAW,EACX,kBAAkB,EAClB,oBAAoB,EACpB,UAAU,GACX,EAAE,2BAA2B,+BAmD7B"}
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { QueryLoadError } from '@shipfox/client-ui';
3
+ import { Panel } from '@shipfox/react-ui/panel';
3
4
  import { WorkflowRunListEmpty, WorkflowRunListLoadMore, WorkflowRunListNoMatches, WorkflowRunListSkeleton, WorkflowRunListStaleError } from './workflow-run-list-states.js';
4
5
  import { WorkflowRunRowList } from './workflow-run-row.js';
5
6
  export function WorkflowRunListContent({ query, totalRuns, runs, workspaceSlug, projectSlug, hasActiveFilters, onClearFilters, hasNextPage, isFetchingNextPage, isFetchNextPageError, onLoadMore }) {
@@ -20,17 +21,22 @@ export function WorkflowRunListContent({ query, totalRuns, runs, workspaceSlug,
20
21
  className: "min-h-0 flex-1 overflow-y-auto",
21
22
  children: [
22
23
  isPending ? /*#__PURE__*/ _jsx(WorkflowRunListSkeleton, {}) : null,
23
- !isPending ? /*#__PURE__*/ _jsx(QueryLoadError, {
24
- query: query,
25
- subject: "workflow runs",
26
- icon: "pulseLine"
24
+ !isPending ? /*#__PURE__*/ _jsx(Panel, {
25
+ children: /*#__PURE__*/ _jsx(QueryLoadError, {
26
+ query: query,
27
+ subject: "workflow runs",
28
+ icon: "pulseLine",
29
+ variant: "panel"
30
+ })
27
31
  }) : null,
28
32
  !isPending && refreshFailed ? /*#__PURE__*/ _jsx(WorkflowRunListStaleError, {
29
33
  query: query
30
34
  }) : null,
31
- showEmptyState ? /*#__PURE__*/ _jsx(WorkflowRunListEmpty, {
32
- workspaceSlug: workspaceSlug,
33
- projectSlug: projectSlug
35
+ showEmptyState ? /*#__PURE__*/ _jsx(Panel, {
36
+ children: /*#__PURE__*/ _jsx(WorkflowRunListEmpty, {
37
+ workspaceSlug: workspaceSlug,
38
+ projectSlug: projectSlug
39
+ })
34
40
  }) : null,
35
41
  showNoMatches ? /*#__PURE__*/ _jsx(WorkflowRunListNoMatches, {
36
42
  onClear: onClearFilters,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/workflow-run-list/workflow-run-list-content.tsx"],"sourcesContent":["import {QueryLoadError} from '@shipfox/client-ui';\nimport type {WorkflowRunListItem} from '#core/workflow-run.js';\nimport type {WorkflowRunListQuery} from './types.js';\nimport {\n WorkflowRunListEmpty,\n WorkflowRunListLoadMore,\n WorkflowRunListNoMatches,\n WorkflowRunListSkeleton,\n WorkflowRunListStaleError,\n} from './workflow-run-list-states.js';\nimport {WorkflowRunRowList} from './workflow-run-row.js';\n\ninterface WorkflowRunListContentProps {\n query: WorkflowRunListQuery;\n totalRuns: number;\n runs: WorkflowRunListItem[];\n workspaceSlug?: string | undefined;\n projectSlug?: string | undefined;\n hasActiveFilters: boolean;\n onClearFilters: () => void;\n hasNextPage: boolean;\n isFetchingNextPage: boolean;\n isFetchNextPageError: boolean;\n onLoadMore?: () => void;\n}\n\nexport function WorkflowRunListContent({\n query,\n totalRuns,\n runs,\n workspaceSlug,\n projectSlug,\n hasActiveFilters,\n onClearFilters,\n hasNextPage,\n isFetchingNextPage,\n isFetchNextPageError,\n onLoadMore,\n}: WorkflowRunListContentProps) {\n const {isPending, isError} = query;\n // A refetch that fails after a prior success keeps the rows on screen behind a slim\n // banner. QueryLoadError owns the inverse case (errored before anything loaded) and\n // self-gates to nothing here once data exists. A failed fetch never renders as an empty\n // list: \"nothing here\" and \"we could not find out\" are different answers.\n const refreshFailed = isError && query.data !== undefined;\n // Keyed off \"we have an answer\" rather than \"no error\", so a stale refresh still reports\n // whether the filters match anything instead of leaving the list blank under the banner.\n const hasLoaded = !isPending && query.data !== undefined;\n // An empty result is only an empty *list* when no filter is on. With filters active it is a\n // no-matches state even at zero loaded runs, so the way out is clearing them.\n const showEmptyState = hasLoaded && totalRuns === 0 && !hasActiveFilters;\n const showNoMatches = hasLoaded && runs.length === 0 && !showEmptyState;\n\n return (\n <div className=\"min-h-0 flex-1 overflow-y-auto\">\n {isPending ? <WorkflowRunListSkeleton /> : null}\n {!isPending ? (\n <QueryLoadError query={query} subject=\"workflow runs\" icon=\"pulseLine\" />\n ) : null}\n {!isPending && refreshFailed ? <WorkflowRunListStaleError query={query} /> : null}\n {showEmptyState ? (\n <WorkflowRunListEmpty workspaceSlug={workspaceSlug} projectSlug={projectSlug} />\n ) : null}\n {showNoMatches ? (\n <WorkflowRunListNoMatches\n onClear={onClearFilters}\n hasNextPage={hasNextPage}\n isFetchingNextPage={isFetchingNextPage}\n isFetchNextPageError={isFetchNextPageError}\n {...(onLoadMore ? {onLoadMore} : {})}\n />\n ) : null}\n {!isPending && runs.length > 0 ? (\n <>\n <WorkflowRunRowList runs={runs} workspaceSlug={workspaceSlug} projectSlug={projectSlug} />\n <WorkflowRunListLoadMore\n hasNextPage={hasNextPage}\n isFetchingNextPage={isFetchingNextPage}\n isFetchNextPageError={isFetchNextPageError}\n {...(onLoadMore ? {onLoadMore} : {})}\n />\n </>\n ) : null}\n </div>\n );\n}\n"],"names":["QueryLoadError","WorkflowRunListEmpty","WorkflowRunListLoadMore","WorkflowRunListNoMatches","WorkflowRunListSkeleton","WorkflowRunListStaleError","WorkflowRunRowList","WorkflowRunListContent","query","totalRuns","runs","workspaceSlug","projectSlug","hasActiveFilters","onClearFilters","hasNextPage","isFetchingNextPage","isFetchNextPageError","onLoadMore","isPending","isError","refreshFailed","data","undefined","hasLoaded","showEmptyState","showNoMatches","length","div","className","subject","icon","onClear"],"mappings":";AAAA,SAAQA,cAAc,QAAO,qBAAqB;AAGlD,SACEC,oBAAoB,EACpBC,uBAAuB,EACvBC,wBAAwB,EACxBC,uBAAuB,EACvBC,yBAAyB,QACpB,gCAAgC;AACvC,SAAQC,kBAAkB,QAAO,wBAAwB;AAgBzD,OAAO,SAASC,uBAAuB,EACrCC,KAAK,EACLC,SAAS,EACTC,IAAI,EACJC,aAAa,EACbC,WAAW,EACXC,gBAAgB,EAChBC,cAAc,EACdC,WAAW,EACXC,kBAAkB,EAClBC,oBAAoB,EACpBC,UAAU,EACkB;IAC5B,MAAM,EAACC,SAAS,EAAEC,OAAO,EAAC,GAAGZ;IAC7B,oFAAoF;IACpF,oFAAoF;IACpF,wFAAwF;IACxF,0EAA0E;IAC1E,MAAMa,gBAAgBD,WAAWZ,MAAMc,IAAI,KAAKC;IAChD,yFAAyF;IACzF,yFAAyF;IACzF,MAAMC,YAAY,CAACL,aAAaX,MAAMc,IAAI,KAAKC;IAC/C,4FAA4F;IAC5F,8EAA8E;IAC9E,MAAME,iBAAiBD,aAAaf,cAAc,KAAK,CAACI;IACxD,MAAMa,gBAAgBF,aAAad,KAAKiB,MAAM,KAAK,KAAK,CAACF;IAEzD,qBACE,MAACG;QAAIC,WAAU;;YACZV,0BAAY,KAACf,+BAA6B;YAC1C,CAACe,0BACA,KAACnB;gBAAeQ,OAAOA;gBAAOsB,SAAQ;gBAAgBC,MAAK;iBACzD;YACH,CAACZ,aAAaE,8BAAgB,KAAChB;gBAA0BG,OAAOA;iBAAY;YAC5EiB,+BACC,KAACxB;gBAAqBU,eAAeA;gBAAeC,aAAaA;iBAC/D;YACHc,8BACC,KAACvB;gBACC6B,SAASlB;gBACTC,aAAaA;gBACbC,oBAAoBA;gBACpBC,sBAAsBA;gBACrB,GAAIC,aAAa;oBAACA;gBAAU,IAAI,CAAC,CAAC;iBAEnC;YACH,CAACC,aAAaT,KAAKiB,MAAM,GAAG,kBAC3B;;kCACE,KAACrB;wBAAmBI,MAAMA;wBAAMC,eAAeA;wBAAeC,aAAaA;;kCAC3E,KAACV;wBACCa,aAAaA;wBACbC,oBAAoBA;wBACpBC,sBAAsBA;wBACrB,GAAIC,aAAa;4BAACA;wBAAU,IAAI,CAAC,CAAC;;;iBAGrC;;;AAGV"}
1
+ {"version":3,"sources":["../../../src/components/workflow-run-list/workflow-run-list-content.tsx"],"sourcesContent":["import {QueryLoadError} from '@shipfox/client-ui';\nimport {Panel} from '@shipfox/react-ui/panel';\nimport type {WorkflowRunListItem} from '#core/workflow-run.js';\nimport type {WorkflowRunListQuery} from './types.js';\nimport {\n WorkflowRunListEmpty,\n WorkflowRunListLoadMore,\n WorkflowRunListNoMatches,\n WorkflowRunListSkeleton,\n WorkflowRunListStaleError,\n} from './workflow-run-list-states.js';\nimport {WorkflowRunRowList} from './workflow-run-row.js';\n\ninterface WorkflowRunListContentProps {\n query: WorkflowRunListQuery;\n totalRuns: number;\n runs: WorkflowRunListItem[];\n workspaceSlug?: string | undefined;\n projectSlug?: string | undefined;\n hasActiveFilters: boolean;\n onClearFilters: () => void;\n hasNextPage: boolean;\n isFetchingNextPage: boolean;\n isFetchNextPageError: boolean;\n onLoadMore?: () => void;\n}\n\nexport function WorkflowRunListContent({\n query,\n totalRuns,\n runs,\n workspaceSlug,\n projectSlug,\n hasActiveFilters,\n onClearFilters,\n hasNextPage,\n isFetchingNextPage,\n isFetchNextPageError,\n onLoadMore,\n}: WorkflowRunListContentProps) {\n const {isPending, isError} = query;\n // A refetch that fails after a prior success keeps the rows on screen behind a slim\n // banner. QueryLoadError owns the inverse case (errored before anything loaded) and\n // self-gates to nothing here once data exists. A failed fetch never renders as an empty\n // list: \"nothing here\" and \"we could not find out\" are different answers.\n const refreshFailed = isError && query.data !== undefined;\n // Keyed off \"we have an answer\" rather than \"no error\", so a stale refresh still reports\n // whether the filters match anything instead of leaving the list blank under the banner.\n const hasLoaded = !isPending && query.data !== undefined;\n // An empty result is only an empty *list* when no filter is on. With filters active it is a\n // no-matches state even at zero loaded runs, so the way out is clearing them.\n const showEmptyState = hasLoaded && totalRuns === 0 && !hasActiveFilters;\n const showNoMatches = hasLoaded && runs.length === 0 && !showEmptyState;\n\n return (\n <div className=\"min-h-0 flex-1 overflow-y-auto\">\n {isPending ? <WorkflowRunListSkeleton /> : null}\n {!isPending ? (\n <Panel>\n <QueryLoadError query={query} subject=\"workflow runs\" icon=\"pulseLine\" variant=\"panel\" />\n </Panel>\n ) : null}\n {!isPending && refreshFailed ? <WorkflowRunListStaleError query={query} /> : null}\n {showEmptyState ? (\n <Panel>\n <WorkflowRunListEmpty workspaceSlug={workspaceSlug} projectSlug={projectSlug} />\n </Panel>\n ) : null}\n {showNoMatches ? (\n <WorkflowRunListNoMatches\n onClear={onClearFilters}\n hasNextPage={hasNextPage}\n isFetchingNextPage={isFetchingNextPage}\n isFetchNextPageError={isFetchNextPageError}\n {...(onLoadMore ? {onLoadMore} : {})}\n />\n ) : null}\n {!isPending && runs.length > 0 ? (\n <>\n <WorkflowRunRowList runs={runs} workspaceSlug={workspaceSlug} projectSlug={projectSlug} />\n <WorkflowRunListLoadMore\n hasNextPage={hasNextPage}\n isFetchingNextPage={isFetchingNextPage}\n isFetchNextPageError={isFetchNextPageError}\n {...(onLoadMore ? {onLoadMore} : {})}\n />\n </>\n ) : null}\n </div>\n );\n}\n"],"names":["QueryLoadError","Panel","WorkflowRunListEmpty","WorkflowRunListLoadMore","WorkflowRunListNoMatches","WorkflowRunListSkeleton","WorkflowRunListStaleError","WorkflowRunRowList","WorkflowRunListContent","query","totalRuns","runs","workspaceSlug","projectSlug","hasActiveFilters","onClearFilters","hasNextPage","isFetchingNextPage","isFetchNextPageError","onLoadMore","isPending","isError","refreshFailed","data","undefined","hasLoaded","showEmptyState","showNoMatches","length","div","className","subject","icon","variant","onClear"],"mappings":";AAAA,SAAQA,cAAc,QAAO,qBAAqB;AAClD,SAAQC,KAAK,QAAO,0BAA0B;AAG9C,SACEC,oBAAoB,EACpBC,uBAAuB,EACvBC,wBAAwB,EACxBC,uBAAuB,EACvBC,yBAAyB,QACpB,gCAAgC;AACvC,SAAQC,kBAAkB,QAAO,wBAAwB;AAgBzD,OAAO,SAASC,uBAAuB,EACrCC,KAAK,EACLC,SAAS,EACTC,IAAI,EACJC,aAAa,EACbC,WAAW,EACXC,gBAAgB,EAChBC,cAAc,EACdC,WAAW,EACXC,kBAAkB,EAClBC,oBAAoB,EACpBC,UAAU,EACkB;IAC5B,MAAM,EAACC,SAAS,EAAEC,OAAO,EAAC,GAAGZ;IAC7B,oFAAoF;IACpF,oFAAoF;IACpF,wFAAwF;IACxF,0EAA0E;IAC1E,MAAMa,gBAAgBD,WAAWZ,MAAMc,IAAI,KAAKC;IAChD,yFAAyF;IACzF,yFAAyF;IACzF,MAAMC,YAAY,CAACL,aAAaX,MAAMc,IAAI,KAAKC;IAC/C,4FAA4F;IAC5F,8EAA8E;IAC9E,MAAME,iBAAiBD,aAAaf,cAAc,KAAK,CAACI;IACxD,MAAMa,gBAAgBF,aAAad,KAAKiB,MAAM,KAAK,KAAK,CAACF;IAEzD,qBACE,MAACG;QAAIC,WAAU;;YACZV,0BAAY,KAACf,+BAA6B;YAC1C,CAACe,0BACA,KAACnB;0BACC,cAAA,KAACD;oBAAeS,OAAOA;oBAAOsB,SAAQ;oBAAgBC,MAAK;oBAAYC,SAAQ;;iBAE/E;YACH,CAACb,aAAaE,8BAAgB,KAAChB;gBAA0BG,OAAOA;iBAAY;YAC5EiB,+BACC,KAACzB;0BACC,cAAA,KAACC;oBAAqBU,eAAeA;oBAAeC,aAAaA;;iBAEjE;YACHc,8BACC,KAACvB;gBACC8B,SAASnB;gBACTC,aAAaA;gBACbC,oBAAoBA;gBACpBC,sBAAsBA;gBACrB,GAAIC,aAAa;oBAACA;gBAAU,IAAI,CAAC,CAAC;iBAEnC;YACH,CAACC,aAAaT,KAAKiB,MAAM,GAAG,kBAC3B;;kCACE,KAACrB;wBAAmBI,MAAMA;wBAAMC,eAAeA;wBAAeC,aAAaA;;kCAC3E,KAACV;wBACCa,aAAaA;wBACbC,oBAAoBA;wBACpBC,sBAAsBA;wBACrB,GAAIC,aAAa;4BAACA;wBAAU,IAAI,CAAC,CAAC;;;iBAGrC;;;AAGV"}
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-run-list-states.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-list/workflow-run-list-states.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,oBAAoB,CAAC;AAU5D;;;;GAIG;AACH,wBAAgB,uBAAuB,gCAsBtC;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,EAAC,KAAK,EAAC,EAAE;IAAC,KAAK,EAAE,mBAAmB,CAAA;CAAC,+BAqB9E;AAED,wBAAgB,oBAAoB,CAAC,EACnC,aAAa,EACb,WAAW,GACZ,EAAE;IACD,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,+BAoBA;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,OAAO,EACP,WAAW,EACX,kBAAkB,EAClB,oBAAoB,EACpB,UAAU,GACX,EAAE;IACD,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB,+BAqCA;AAED,wBAAgB,uBAAuB,CAAC,EACtC,WAAW,EACX,kBAAkB,EAClB,oBAAoB,EACpB,UAAU,GACX,EAAE;IACD,WAAW,EAAE,OAAO,CAAC;IACrB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB,sCAqBA"}
1
+ {"version":3,"file":"workflow-run-list-states.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-list/workflow-run-list-states.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,oBAAoB,CAAC;AAW5D;;;;GAIG;AACH,wBAAgB,uBAAuB,gCAkBtC;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,EAAC,KAAK,EAAC,EAAE;IAAC,KAAK,EAAE,mBAAmB,CAAA;CAAC,+BAqB9E;AAED,wBAAgB,oBAAoB,CAAC,EACnC,aAAa,EACb,WAAW,GACZ,EAAE;IACD,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,+BAqBA;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,OAAO,EACP,WAAW,EACX,kBAAkB,EAClB,oBAAoB,EACpB,UAAU,GACX,EAAE;IACD,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB,+BAwCA;AAED,wBAAgB,uBAAuB,CAAC,EACtC,WAAW,EACX,kBAAkB,EAClB,oBAAoB,EACpB,UAAU,GACX,EAAE;IACD,WAAW,EAAE,OAAO,CAAC;IACrB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB,sCAqBA"}
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Button } from '@shipfox/react-ui/button';
3
3
  import { Callout } from '@shipfox/react-ui/callout';
4
4
  import { EmptyState } from '@shipfox/react-ui/empty-state';
5
+ import { Panel } from '@shipfox/react-ui/panel';
5
6
  import { Skeleton } from '@shipfox/react-ui/skeleton';
6
7
  import { Text } from '@shipfox/react-ui/typography';
7
8
  import { Link } from '@tanstack/react-router';
@@ -11,14 +12,14 @@ const SKELETON_ROW_COUNT = 8;
11
12
  * the first page lands. Only the initial load renders these: a refetch keeps the rows it has,
12
13
  * per the design system's rule against animating frequently-updating data.
13
14
  */ export function WorkflowRunListSkeleton() {
14
- return /*#__PURE__*/ _jsx("div", {
15
- className: "@container divide-y divide-border-neutral-base border-t border-border-neutral-base",
15
+ return /*#__PURE__*/ _jsx(Panel, {
16
16
  role: "status",
17
17
  "aria-label": "Loading runs",
18
+ className: "@container divide-y",
18
19
  children: Array.from({
19
20
  length: SKELETON_ROW_COUNT
20
21
  }).map((_, index)=>/*#__PURE__*/ _jsxs("div", {
21
- className: "flex items-center gap-inline px-row py-row @min-[976px]:h-44 @min-[976px]:py-0",
22
+ className: "flex min-h-44 items-center gap-inline px-row py-row @min-[976px]:h-44 @min-[976px]:py-0",
22
23
  children: [
23
24
  /*#__PURE__*/ _jsx(Skeleton, {
24
25
  className: "size-14 shrink-0 rounded-full"
@@ -27,7 +28,7 @@ const SKELETON_ROW_COUNT = 8;
27
28
  className: "h-12 w-[220px] max-w-[40%]"
28
29
  }),
29
30
  /*#__PURE__*/ _jsx(Skeleton, {
30
- className: "ml-auto hidden h-12 w-96 @min-[1040px]:block"
31
+ className: "ml-auto hidden h-12 w-96 @min-[1200px]:block"
31
32
  }),
32
33
  /*#__PURE__*/ _jsx(Skeleton, {
33
34
  className: "h-12 w-48"
@@ -89,7 +90,8 @@ export function WorkflowRunListEmpty({ workspaceSlug, projectSlug }) {
89
90
  },
90
91
  children: "View workflows"
91
92
  })
92
- }) : null
93
+ }) : null,
94
+ variant: "panel"
93
95
  });
94
96
  }
95
97
  /**
@@ -101,40 +103,43 @@ export function WorkflowRunListEmpty({ workspaceSlug, projectSlug }) {
101
103
  * different ones. It is also the only reset within reach below `md`, where the toolbar's
102
104
  * lives inside the filter sheet.
103
105
  */ export function WorkflowRunListNoMatches({ onClear, hasNextPage, isFetchingNextPage, isFetchNextPageError, onLoadMore }) {
104
- return /*#__PURE__*/ _jsxs("div", {
105
- className: "flex flex-col gap-inline",
106
- children: [
107
- isFetchNextPageError ? /*#__PURE__*/ _jsx(Callout, {
108
- role: "alert",
109
- type: "error",
110
- children: "Could not load more workflow runs. Try again to continue searching older runs."
111
- }) : null,
112
- /*#__PURE__*/ _jsx(EmptyState, {
113
- icon: "filterOffLine",
114
- title: hasNextPage ? 'No matches in loaded history' : 'No matching runs',
115
- description: hasNextPage ? 'No loaded run matches these filters. Load more to search further back.' : 'No run matches these filters.',
116
- action: /*#__PURE__*/ _jsxs("div", {
117
- className: "flex flex-wrap justify-center gap-inline",
118
- children: [
119
- hasNextPage && onLoadMore ? /*#__PURE__*/ _jsx(Button, {
120
- type: "button",
121
- size: "sm",
122
- variant: "primary",
123
- isLoading: isFetchingNextPage,
124
- onClick: onLoadMore,
125
- children: "Load more runs"
126
- }) : null,
127
- /*#__PURE__*/ _jsx(Button, {
128
- type: "button",
129
- size: "sm",
130
- variant: "secondary",
131
- onClick: onClear,
132
- children: "Show all runs"
133
- })
134
- ]
106
+ return /*#__PURE__*/ _jsx(Panel, {
107
+ children: /*#__PURE__*/ _jsxs("div", {
108
+ className: "flex flex-col gap-inline",
109
+ children: [
110
+ isFetchNextPageError ? /*#__PURE__*/ _jsx(Callout, {
111
+ role: "alert",
112
+ type: "error",
113
+ children: "Could not load more workflow runs. Try again to continue searching older runs."
114
+ }) : null,
115
+ /*#__PURE__*/ _jsx(EmptyState, {
116
+ icon: "filterOffLine",
117
+ title: hasNextPage ? 'No matches in loaded history' : 'No matching runs',
118
+ description: hasNextPage ? 'No loaded run matches these filters. Load more to search further back.' : 'No run matches these filters.',
119
+ action: /*#__PURE__*/ _jsxs("div", {
120
+ className: "flex flex-wrap justify-center gap-inline",
121
+ children: [
122
+ hasNextPage && onLoadMore ? /*#__PURE__*/ _jsx(Button, {
123
+ type: "button",
124
+ size: "sm",
125
+ variant: "primary",
126
+ isLoading: isFetchingNextPage,
127
+ onClick: onLoadMore,
128
+ children: "Load more runs"
129
+ }) : null,
130
+ /*#__PURE__*/ _jsx(Button, {
131
+ type: "button",
132
+ size: "sm",
133
+ variant: "secondary",
134
+ onClick: onClear,
135
+ children: "Show all runs"
136
+ })
137
+ ]
138
+ }),
139
+ variant: "panel"
135
140
  })
136
- })
137
- ]
141
+ ]
142
+ })
138
143
  });
139
144
  }
140
145
  export function WorkflowRunListLoadMore({ hasNextPage, isFetchingNextPage, isFetchNextPageError, onLoadMore }) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/workflow-run-list/workflow-run-list-states.tsx"],"sourcesContent":["import type {QueryLoadErrorQuery} from '@shipfox/client-ui';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {Link} from '@tanstack/react-router';\n\nconst SKELETON_ROW_COUNT = 8;\n\n/**\n * Placeholder rows at the real row height, so the list does not resize under the cursor when\n * the first page lands. Only the initial load renders these: a refetch keeps the rows it has,\n * per the design system's rule against animating frequently-updating data.\n */\nexport function WorkflowRunListSkeleton() {\n return (\n <div\n className=\"@container divide-y divide-border-neutral-base border-t border-border-neutral-base\"\n role=\"status\"\n aria-label=\"Loading runs\"\n >\n {Array.from({length: SKELETON_ROW_COUNT}).map((_, index) => (\n <div\n // biome-ignore lint/suspicious/noArrayIndexKey: skeleton row, stable position\n key={index}\n className=\"flex items-center gap-inline px-row py-row @min-[976px]:h-44 @min-[976px]:py-0\"\n >\n <Skeleton className=\"size-14 shrink-0 rounded-full\" />\n <Skeleton className=\"h-12 w-[220px] max-w-[40%]\" />\n <Skeleton className=\"ml-auto hidden h-12 w-96 @min-[1040px]:block\" />\n <Skeleton className=\"h-12 w-48\" />\n <Skeleton className=\"h-12 w-48\" />\n </div>\n ))}\n </div>\n );\n}\n\n/**\n * Slim non-blocking banner shown when a background refetch fails after a prior success,\n * so the already-loaded rows stay on screen with the retry inline rather than being\n * replaced by a full-placeholder error.\n */\nexport function WorkflowRunListStaleError({query}: {query: QueryLoadErrorQuery}) {\n return (\n <div className=\"pb-[8px]\">\n <Callout role=\"alert\" type=\"error\">\n <div className=\"flex items-center justify-between gap-inline\">\n <Text size=\"xs\">Could not refresh workflow runs.</Text>\n <Button\n type=\"button\"\n size=\"2xs\"\n variant=\"secondary\"\n isLoading={query.isFetching}\n onClick={() => {\n void query.refetch();\n }}\n >\n Retry\n </Button>\n </div>\n </Callout>\n </div>\n );\n}\n\nexport function WorkflowRunListEmpty({\n workspaceSlug,\n projectSlug,\n}: {\n workspaceSlug?: string | undefined;\n projectSlug?: string | undefined;\n}) {\n return (\n <EmptyState\n icon=\"pulseLine\"\n title=\"No runs yet\"\n description=\"Runs from this project's workflows appear here the moment one is launched.\"\n action={\n workspaceSlug && projectSlug ? (\n <Button asChild type=\"button\" size=\"sm\" variant=\"primary\">\n <Link\n to=\"/w/$workspaceSlug/p/$projectSlug/workflows\"\n params={{workspaceSlug, projectSlug}}\n >\n View workflows\n </Link>\n </Button>\n ) : null\n }\n />\n );\n}\n\n/**\n * The filtered-empty state, deliberately distinct from the unfiltered one: someone who\n * filtered their way to nothing needs a way back out, not an onboarding call to action.\n *\n * Its reset is worded as its outcome rather than repeating the toolbar's \"Clear filters\"\n * verbatim, so the two controls on screen read as one action offered twice rather than two\n * different ones. It is also the only reset within reach below `md`, where the toolbar's\n * lives inside the filter sheet.\n */\nexport function WorkflowRunListNoMatches({\n onClear,\n hasNextPage,\n isFetchingNextPage,\n isFetchNextPageError,\n onLoadMore,\n}: {\n onClear: () => void;\n hasNextPage: boolean;\n isFetchingNextPage: boolean;\n isFetchNextPageError: boolean;\n onLoadMore?: () => void;\n}) {\n return (\n <div className=\"flex flex-col gap-inline\">\n {isFetchNextPageError ? (\n <Callout role=\"alert\" type=\"error\">\n Could not load more workflow runs. Try again to continue searching older runs.\n </Callout>\n ) : null}\n <EmptyState\n icon=\"filterOffLine\"\n title={hasNextPage ? 'No matches in loaded history' : 'No matching runs'}\n description={\n hasNextPage\n ? 'No loaded run matches these filters. Load more to search further back.'\n : 'No run matches these filters.'\n }\n action={\n <div className=\"flex flex-wrap justify-center gap-inline\">\n {hasNextPage && onLoadMore ? (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"primary\"\n isLoading={isFetchingNextPage}\n onClick={onLoadMore}\n >\n Load more runs\n </Button>\n ) : null}\n <Button type=\"button\" size=\"sm\" variant=\"secondary\" onClick={onClear}>\n Show all runs\n </Button>\n </div>\n }\n />\n </div>\n );\n}\n\nexport function WorkflowRunListLoadMore({\n hasNextPage,\n isFetchingNextPage,\n isFetchNextPageError,\n onLoadMore,\n}: {\n hasNextPage: boolean;\n isFetchingNextPage: boolean;\n isFetchNextPageError: boolean;\n onLoadMore?: () => void;\n}) {\n if (!hasNextPage || !onLoadMore) return null;\n\n return (\n <div className=\"flex flex-col items-center gap-inline border-t border-border-neutral-base py-row\">\n {isFetchNextPageError ? (\n <Callout role=\"alert\" type=\"error\">\n Could not load more workflow runs. Try again.\n </Callout>\n ) : null}\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"secondary\"\n isLoading={isFetchingNextPage}\n onClick={onLoadMore}\n >\n Load more runs\n </Button>\n </div>\n );\n}\n"],"names":["Button","Callout","EmptyState","Skeleton","Text","Link","SKELETON_ROW_COUNT","WorkflowRunListSkeleton","div","className","role","aria-label","Array","from","length","map","_","index","WorkflowRunListStaleError","query","type","size","variant","isLoading","isFetching","onClick","refetch","WorkflowRunListEmpty","workspaceSlug","projectSlug","icon","title","description","action","asChild","to","params","WorkflowRunListNoMatches","onClear","hasNextPage","isFetchingNextPage","isFetchNextPageError","onLoadMore","WorkflowRunListLoadMore"],"mappings":";AACA,SAAQA,MAAM,QAAO,2BAA2B;AAChD,SAAQC,OAAO,QAAO,4BAA4B;AAClD,SAAQC,UAAU,QAAO,gCAAgC;AACzD,SAAQC,QAAQ,QAAO,6BAA6B;AACpD,SAAQC,IAAI,QAAO,+BAA+B;AAClD,SAAQC,IAAI,QAAO,yBAAyB;AAE5C,MAAMC,qBAAqB;AAE3B;;;;CAIC,GACD,OAAO,SAASC;IACd,qBACE,KAACC;QACCC,WAAU;QACVC,MAAK;QACLC,cAAW;kBAEVC,MAAMC,IAAI,CAAC;YAACC,QAAQR;QAAkB,GAAGS,GAAG,CAAC,CAACC,GAAGC,sBAChD,MAACT;gBAGCC,WAAU;;kCAEV,KAACN;wBAASM,WAAU;;kCACpB,KAACN;wBAASM,WAAU;;kCACpB,KAACN;wBAASM,WAAU;;kCACpB,KAACN;wBAASM,WAAU;;kCACpB,KAACN;wBAASM,WAAU;;;eAPfQ;;AAYf;AAEA;;;;CAIC,GACD,OAAO,SAASC,0BAA0B,EAACC,KAAK,EAA+B;IAC7E,qBACE,KAACX;QAAIC,WAAU;kBACb,cAAA,KAACR;YAAQS,MAAK;YAAQU,MAAK;sBACzB,cAAA,MAACZ;gBAAIC,WAAU;;kCACb,KAACL;wBAAKiB,MAAK;kCAAK;;kCAChB,KAACrB;wBACCoB,MAAK;wBACLC,MAAK;wBACLC,SAAQ;wBACRC,WAAWJ,MAAMK,UAAU;wBAC3BC,SAAS;4BACP,KAAKN,MAAMO,OAAO;wBACpB;kCACD;;;;;;AAOX;AAEA,OAAO,SAASC,qBAAqB,EACnCC,aAAa,EACbC,WAAW,EAIZ;IACC,qBACE,KAAC3B;QACC4B,MAAK;QACLC,OAAM;QACNC,aAAY;QACZC,QACEL,iBAAiBC,4BACf,KAAC7B;YAAOkC,OAAO;YAACd,MAAK;YAASC,MAAK;YAAKC,SAAQ;sBAC9C,cAAA,KAACjB;gBACC8B,IAAG;gBACHC,QAAQ;oBAACR;oBAAeC;gBAAW;0BACpC;;aAID;;AAIZ;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASQ,yBAAyB,EACvCC,OAAO,EACPC,WAAW,EACXC,kBAAkB,EAClBC,oBAAoB,EACpBC,UAAU,EAOX;IACC,qBACE,MAAClC;QAAIC,WAAU;;YACZgC,qCACC,KAACxC;gBAAQS,MAAK;gBAAQU,MAAK;0BAAQ;iBAGjC;0BACJ,KAAClB;gBACC4B,MAAK;gBACLC,OAAOQ,cAAc,iCAAiC;gBACtDP,aACEO,cACI,2EACA;gBAENN,sBACE,MAACzB;oBAAIC,WAAU;;wBACZ8B,eAAeG,2BACd,KAAC1C;4BACCoB,MAAK;4BACLC,MAAK;4BACLC,SAAQ;4BACRC,WAAWiB;4BACXf,SAASiB;sCACV;6BAGC;sCACJ,KAAC1C;4BAAOoB,MAAK;4BAASC,MAAK;4BAAKC,SAAQ;4BAAYG,SAASa;sCAAS;;;;;;;AAQlF;AAEA,OAAO,SAASK,wBAAwB,EACtCJ,WAAW,EACXC,kBAAkB,EAClBC,oBAAoB,EACpBC,UAAU,EAMX;IACC,IAAI,CAACH,eAAe,CAACG,YAAY,OAAO;IAExC,qBACE,MAAClC;QAAIC,WAAU;;YACZgC,qCACC,KAACxC;gBAAQS,MAAK;gBAAQU,MAAK;0BAAQ;iBAGjC;0BACJ,KAACpB;gBACCoB,MAAK;gBACLC,MAAK;gBACLC,SAAQ;gBACRC,WAAWiB;gBACXf,SAASiB;0BACV;;;;AAKP"}
1
+ {"version":3,"sources":["../../../src/components/workflow-run-list/workflow-run-list-states.tsx"],"sourcesContent":["import type {QueryLoadErrorQuery} from '@shipfox/client-ui';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Panel} from '@shipfox/react-ui/panel';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {Link} from '@tanstack/react-router';\n\nconst SKELETON_ROW_COUNT = 8;\n\n/**\n * Placeholder rows at the real row height, so the list does not resize under the cursor when\n * the first page lands. Only the initial load renders these: a refetch keeps the rows it has,\n * per the design system's rule against animating frequently-updating data.\n */\nexport function WorkflowRunListSkeleton() {\n return (\n <Panel role=\"status\" aria-label=\"Loading runs\" className=\"@container divide-y\">\n {Array.from({length: SKELETON_ROW_COUNT}).map((_, index) => (\n <div\n // biome-ignore lint/suspicious/noArrayIndexKey: skeleton row, stable position\n key={index}\n className=\"flex min-h-44 items-center gap-inline px-row py-row @min-[976px]:h-44 @min-[976px]:py-0\"\n >\n <Skeleton className=\"size-14 shrink-0 rounded-full\" />\n <Skeleton className=\"h-12 w-[220px] max-w-[40%]\" />\n <Skeleton className=\"ml-auto hidden h-12 w-96 @min-[1200px]:block\" />\n <Skeleton className=\"h-12 w-48\" />\n <Skeleton className=\"h-12 w-48\" />\n </div>\n ))}\n </Panel>\n );\n}\n\n/**\n * Slim non-blocking banner shown when a background refetch fails after a prior success,\n * so the already-loaded rows stay on screen with the retry inline rather than being\n * replaced by a full-placeholder error.\n */\nexport function WorkflowRunListStaleError({query}: {query: QueryLoadErrorQuery}) {\n return (\n <div className=\"pb-[8px]\">\n <Callout role=\"alert\" type=\"error\">\n <div className=\"flex items-center justify-between gap-inline\">\n <Text size=\"xs\">Could not refresh workflow runs.</Text>\n <Button\n type=\"button\"\n size=\"2xs\"\n variant=\"secondary\"\n isLoading={query.isFetching}\n onClick={() => {\n void query.refetch();\n }}\n >\n Retry\n </Button>\n </div>\n </Callout>\n </div>\n );\n}\n\nexport function WorkflowRunListEmpty({\n workspaceSlug,\n projectSlug,\n}: {\n workspaceSlug?: string | undefined;\n projectSlug?: string | undefined;\n}) {\n return (\n <EmptyState\n icon=\"pulseLine\"\n title=\"No runs yet\"\n description=\"Runs from this project's workflows appear here the moment one is launched.\"\n action={\n workspaceSlug && projectSlug ? (\n <Button asChild type=\"button\" size=\"sm\" variant=\"primary\">\n <Link\n to=\"/w/$workspaceSlug/p/$projectSlug/workflows\"\n params={{workspaceSlug, projectSlug}}\n >\n View workflows\n </Link>\n </Button>\n ) : null\n }\n variant=\"panel\"\n />\n );\n}\n\n/**\n * The filtered-empty state, deliberately distinct from the unfiltered one: someone who\n * filtered their way to nothing needs a way back out, not an onboarding call to action.\n *\n * Its reset is worded as its outcome rather than repeating the toolbar's \"Clear filters\"\n * verbatim, so the two controls on screen read as one action offered twice rather than two\n * different ones. It is also the only reset within reach below `md`, where the toolbar's\n * lives inside the filter sheet.\n */\nexport function WorkflowRunListNoMatches({\n onClear,\n hasNextPage,\n isFetchingNextPage,\n isFetchNextPageError,\n onLoadMore,\n}: {\n onClear: () => void;\n hasNextPage: boolean;\n isFetchingNextPage: boolean;\n isFetchNextPageError: boolean;\n onLoadMore?: () => void;\n}) {\n return (\n <Panel>\n <div className=\"flex flex-col gap-inline\">\n {isFetchNextPageError ? (\n <Callout role=\"alert\" type=\"error\">\n Could not load more workflow runs. Try again to continue searching older runs.\n </Callout>\n ) : null}\n <EmptyState\n icon=\"filterOffLine\"\n title={hasNextPage ? 'No matches in loaded history' : 'No matching runs'}\n description={\n hasNextPage\n ? 'No loaded run matches these filters. Load more to search further back.'\n : 'No run matches these filters.'\n }\n action={\n <div className=\"flex flex-wrap justify-center gap-inline\">\n {hasNextPage && onLoadMore ? (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"primary\"\n isLoading={isFetchingNextPage}\n onClick={onLoadMore}\n >\n Load more runs\n </Button>\n ) : null}\n <Button type=\"button\" size=\"sm\" variant=\"secondary\" onClick={onClear}>\n Show all runs\n </Button>\n </div>\n }\n variant=\"panel\"\n />\n </div>\n </Panel>\n );\n}\n\nexport function WorkflowRunListLoadMore({\n hasNextPage,\n isFetchingNextPage,\n isFetchNextPageError,\n onLoadMore,\n}: {\n hasNextPage: boolean;\n isFetchingNextPage: boolean;\n isFetchNextPageError: boolean;\n onLoadMore?: () => void;\n}) {\n if (!hasNextPage || !onLoadMore) return null;\n\n return (\n <div className=\"flex flex-col items-center gap-inline border-t border-border-neutral-base py-row\">\n {isFetchNextPageError ? (\n <Callout role=\"alert\" type=\"error\">\n Could not load more workflow runs. Try again.\n </Callout>\n ) : null}\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"secondary\"\n isLoading={isFetchingNextPage}\n onClick={onLoadMore}\n >\n Load more runs\n </Button>\n </div>\n );\n}\n"],"names":["Button","Callout","EmptyState","Panel","Skeleton","Text","Link","SKELETON_ROW_COUNT","WorkflowRunListSkeleton","role","aria-label","className","Array","from","length","map","_","index","div","WorkflowRunListStaleError","query","type","size","variant","isLoading","isFetching","onClick","refetch","WorkflowRunListEmpty","workspaceSlug","projectSlug","icon","title","description","action","asChild","to","params","WorkflowRunListNoMatches","onClear","hasNextPage","isFetchingNextPage","isFetchNextPageError","onLoadMore","WorkflowRunListLoadMore"],"mappings":";AACA,SAAQA,MAAM,QAAO,2BAA2B;AAChD,SAAQC,OAAO,QAAO,4BAA4B;AAClD,SAAQC,UAAU,QAAO,gCAAgC;AACzD,SAAQC,KAAK,QAAO,0BAA0B;AAC9C,SAAQC,QAAQ,QAAO,6BAA6B;AACpD,SAAQC,IAAI,QAAO,+BAA+B;AAClD,SAAQC,IAAI,QAAO,yBAAyB;AAE5C,MAAMC,qBAAqB;AAE3B;;;;CAIC,GACD,OAAO,SAASC;IACd,qBACE,KAACL;QAAMM,MAAK;QAASC,cAAW;QAAeC,WAAU;kBACtDC,MAAMC,IAAI,CAAC;YAACC,QAAQP;QAAkB,GAAGQ,GAAG,CAAC,CAACC,GAAGC,sBAChD,MAACC;gBAGCP,WAAU;;kCAEV,KAACP;wBAASO,WAAU;;kCACpB,KAACP;wBAASO,WAAU;;kCACpB,KAACP;wBAASO,WAAU;;kCACpB,KAACP;wBAASO,WAAU;;kCACpB,KAACP;wBAASO,WAAU;;;eAPfM;;AAYf;AAEA;;;;CAIC,GACD,OAAO,SAASE,0BAA0B,EAACC,KAAK,EAA+B;IAC7E,qBACE,KAACF;QAAIP,WAAU;kBACb,cAAA,KAACV;YAAQQ,MAAK;YAAQY,MAAK;sBACzB,cAAA,MAACH;gBAAIP,WAAU;;kCACb,KAACN;wBAAKiB,MAAK;kCAAK;;kCAChB,KAACtB;wBACCqB,MAAK;wBACLC,MAAK;wBACLC,SAAQ;wBACRC,WAAWJ,MAAMK,UAAU;wBAC3BC,SAAS;4BACP,KAAKN,MAAMO,OAAO;wBACpB;kCACD;;;;;;AAOX;AAEA,OAAO,SAASC,qBAAqB,EACnCC,aAAa,EACbC,WAAW,EAIZ;IACC,qBACE,KAAC5B;QACC6B,MAAK;QACLC,OAAM;QACNC,aAAY;QACZC,QACEL,iBAAiBC,4BACf,KAAC9B;YAAOmC,OAAO;YAACd,MAAK;YAASC,MAAK;YAAKC,SAAQ;sBAC9C,cAAA,KAACjB;gBACC8B,IAAG;gBACHC,QAAQ;oBAACR;oBAAeC;gBAAW;0BACpC;;aAID;QAENP,SAAQ;;AAGd;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASe,yBAAyB,EACvCC,OAAO,EACPC,WAAW,EACXC,kBAAkB,EAClBC,oBAAoB,EACpBC,UAAU,EAOX;IACC,qBACE,KAACxC;kBACC,cAAA,MAACe;YAAIP,WAAU;;gBACZ+B,qCACC,KAACzC;oBAAQQ,MAAK;oBAAQY,MAAK;8BAAQ;qBAGjC;8BACJ,KAACnB;oBACC6B,MAAK;oBACLC,OAAOQ,cAAc,iCAAiC;oBACtDP,aACEO,cACI,2EACA;oBAENN,sBACE,MAAChB;wBAAIP,WAAU;;4BACZ6B,eAAeG,2BACd,KAAC3C;gCACCqB,MAAK;gCACLC,MAAK;gCACLC,SAAQ;gCACRC,WAAWiB;gCACXf,SAASiB;0CACV;iCAGC;0CACJ,KAAC3C;gCAAOqB,MAAK;gCAASC,MAAK;gCAAKC,SAAQ;gCAAYG,SAASa;0CAAS;;;;oBAK1EhB,SAAQ;;;;;AAKlB;AAEA,OAAO,SAASqB,wBAAwB,EACtCJ,WAAW,EACXC,kBAAkB,EAClBC,oBAAoB,EACpBC,UAAU,EAMX;IACC,IAAI,CAACH,eAAe,CAACG,YAAY,OAAO;IAExC,qBACE,MAACzB;QAAIP,WAAU;;YACZ+B,qCACC,KAACzC;gBAAQQ,MAAK;gBAAQY,MAAK;0BAAQ;iBAGjC;0BACJ,KAACrB;gBACCqB,MAAK;gBACLC,MAAK;gBACLC,SAAQ;gBACRC,WAAWiB;gBACXf,SAASiB;0BACV;;;;AAKP"}
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-run-list-view.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-list/workflow-run-list-view.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,YAAY,CAAC;AAMzD,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,KAAK,EACL,aAAa,EACb,WAAW,EACX,SAAS,EACT,MAAqB,EACrB,eAAe,EACf,cAAc,EACd,WAAmB,EACnB,kBAA0B,EAC1B,oBAA4B,EAC5B,UAAU,GACX,EAAE,wBAAwB,+BA6D1B"}
1
+ {"version":3,"file":"workflow-run-list-view.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-list/workflow-run-list-view.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,YAAY,CAAC;AAMzD,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,KAAK,EACL,aAAa,EACb,WAAW,EACX,SAAS,EACT,MAAqB,EACrB,eAAe,EACf,cAAc,EACd,WAAmB,EACnB,kBAA0B,EAC1B,oBAA4B,EAC5B,UAAU,GACX,EAAE,wBAAwB,+BAkE1B"}
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Panel, PanelBody, PanelHeader } from '@shipfox/react-ui/panel';
2
3
  import { TimeTickerProvider } from '@shipfox/react-ui/time-ticker';
3
4
  import { Header } from '@shipfox/react-ui/typography';
4
5
  import { cn } from '@shipfox/react-ui/utils';
@@ -37,7 +38,7 @@ export function WorkflowRunListView({ runs, query, workspaceSlug, projectSlug, c
37
38
  reducedMotionIntervalMs: 10_000,
38
39
  children: /*#__PURE__*/ _jsxs("section", {
39
40
  "aria-labelledby": headingId,
40
- className: cn('flex min-h-0 min-w-0 flex-1 flex-col gap-cluster', className),
41
+ className: cn('flex min-h-0 min-w-0 flex-1 flex-col', className),
41
42
  children: [
42
43
  /*#__PURE__*/ _jsx(Header, {
43
44
  id: headingId,
@@ -45,27 +46,38 @@ export function WorkflowRunListView({ runs, query, workspaceSlug, projectSlug, c
45
46
  className: "sr-only",
46
47
  children: "Workflow runs"
47
48
  }),
48
- /*#__PURE__*/ _jsx(WorkflowRunFilters, {
49
- search: currentSearch,
50
- facets: facets,
51
- onChange: handleFiltersChange,
52
- onClear: handleClearFilters,
53
- hasActiveFilters: hasActiveFilters
54
- }),
55
- /*#__PURE__*/ _jsx(WorkflowRunListContent, {
56
- query: query,
57
- totalRuns: runs.length,
58
- runs: filteredRuns,
59
- workspaceSlug: workspaceSlug,
60
- projectSlug: projectSlug,
61
- hasActiveFilters: hasActiveFilters,
62
- onClearFilters: handleClearFilters,
63
- hasNextPage: hasNextPage,
64
- isFetchingNextPage: isFetchingNextPage,
65
- isFetchNextPageError: isFetchNextPageError,
66
- ...onLoadMore ? {
67
- onLoadMore
68
- } : {}
49
+ /*#__PURE__*/ _jsxs(Panel, {
50
+ className: "min-h-0 flex-1",
51
+ children: [
52
+ /*#__PURE__*/ _jsx(PanelHeader, {
53
+ className: "flex-wrap",
54
+ children: /*#__PURE__*/ _jsx(WorkflowRunFilters, {
55
+ search: currentSearch,
56
+ facets: facets,
57
+ onChange: handleFiltersChange,
58
+ onClear: handleClearFilters,
59
+ hasActiveFilters: hasActiveFilters
60
+ })
61
+ }),
62
+ /*#__PURE__*/ _jsx(PanelBody, {
63
+ className: "min-h-0 flex-1",
64
+ children: /*#__PURE__*/ _jsx(WorkflowRunListContent, {
65
+ query: query,
66
+ totalRuns: runs.length,
67
+ runs: filteredRuns,
68
+ workspaceSlug: workspaceSlug,
69
+ projectSlug: projectSlug,
70
+ hasActiveFilters: hasActiveFilters,
71
+ onClearFilters: handleClearFilters,
72
+ hasNextPage: hasNextPage,
73
+ isFetchingNextPage: isFetchingNextPage,
74
+ isFetchNextPageError: isFetchNextPageError,
75
+ ...onLoadMore ? {
76
+ onLoadMore
77
+ } : {}
78
+ })
79
+ })
80
+ ]
69
81
  })
70
82
  ]
71
83
  })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/workflow-run-list/workflow-run-list-view.tsx"],"sourcesContent":["import {TimeTickerProvider} from '@shipfox/react-ui/time-ticker';\nimport {Header} from '@shipfox/react-ui/typography';\nimport {cn} from '@shipfox/react-ui/utils';\nimport {useId, useMemo, useState} from 'react';\nimport {\n applyWorkflowRunFilterPatch,\n clearWorkflowRunFilters,\n hasWorkflowRunFilters,\n type WorkflowRunFilterPatch,\n type WorkflowRunsSearch,\n} from '#routes/inputs.js';\nimport {runMatchesFilters, workflowRunFacets} from './run-display.js';\nimport type {WorkflowRunListViewProps} from './types.js';\nimport {WorkflowRunFilters} from './workflow-run-filters.js';\nimport {WorkflowRunListContent} from './workflow-run-list-content.js';\n\nconst EMPTY_SEARCH: WorkflowRunsSearch = {};\n\nexport function WorkflowRunListView({\n runs,\n query,\n workspaceSlug,\n projectSlug,\n className,\n search = EMPTY_SEARCH,\n onFiltersChange,\n onClearFilters,\n hasNextPage = false,\n isFetchingNextPage = false,\n isFetchNextPageError = false,\n onLoadMore,\n}: WorkflowRunListViewProps) {\n const headingId = useId();\n const [localSearch, setLocalSearch] = useState<WorkflowRunsSearch>(search);\n const currentSearch = onFiltersChange ? search : localSearch;\n\n const filteredRuns = useMemo(\n () => runs.filter((run) => runMatchesFilters(run, currentSearch)),\n [runs, currentSearch],\n );\n const facets = useMemo(() => workflowRunFacets(runs, currentSearch), [runs, currentSearch]);\n const hasActiveFilters = hasWorkflowRunFilters(currentSearch);\n\n function handleFiltersChange(patch: WorkflowRunFilterPatch) {\n if (onFiltersChange) onFiltersChange(patch);\n else setLocalSearch((previous) => applyWorkflowRunFilterPatch(previous, patch));\n }\n\n function handleClearFilters() {\n // Uncontrolled, the view owns the filters, so it clears them itself and still reports the\n // change; a caller that passes only `onClearFilters` would otherwise see nothing happen.\n if (!onFiltersChange) setLocalSearch(clearWorkflowRunFilters);\n else if (!onClearFilters) onFiltersChange(CLEAR_ALL_FILTERS);\n onClearFilters?.();\n }\n\n return (\n <TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>\n <section\n aria-labelledby={headingId}\n className={cn('flex min-h-0 min-w-0 flex-1 flex-col gap-cluster', className)}\n >\n {/* The tab strip above already reads \"Runs\", so the page heading is for structure and\n assistive tech rather than a second visible title competing with it. */}\n <Header id={headingId} variant=\"h1\" className=\"sr-only\">\n Workflow runs\n </Header>\n\n <WorkflowRunFilters\n search={currentSearch}\n facets={facets}\n onChange={handleFiltersChange}\n onClear={handleClearFilters}\n hasActiveFilters={hasActiveFilters}\n />\n\n <WorkflowRunListContent\n query={query}\n totalRuns={runs.length}\n runs={filteredRuns}\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n hasActiveFilters={hasActiveFilters}\n onClearFilters={handleClearFilters}\n hasNextPage={hasNextPage}\n isFetchingNextPage={isFetchingNextPage}\n isFetchNextPageError={isFetchNextPageError}\n {...(onLoadMore ? {onLoadMore} : {})}\n />\n </section>\n </TimeTickerProvider>\n );\n}\n\nconst CLEAR_ALL_FILTERS: WorkflowRunFilterPatch = {\n search: undefined,\n status: undefined,\n branch: undefined,\n actor: undefined,\n event: undefined,\n after: undefined,\n before: undefined,\n};\n"],"names":["TimeTickerProvider","Header","cn","useId","useMemo","useState","applyWorkflowRunFilterPatch","clearWorkflowRunFilters","hasWorkflowRunFilters","runMatchesFilters","workflowRunFacets","WorkflowRunFilters","WorkflowRunListContent","EMPTY_SEARCH","WorkflowRunListView","runs","query","workspaceSlug","projectSlug","className","search","onFiltersChange","onClearFilters","hasNextPage","isFetchingNextPage","isFetchNextPageError","onLoadMore","headingId","localSearch","setLocalSearch","currentSearch","filteredRuns","filter","run","facets","hasActiveFilters","handleFiltersChange","patch","previous","handleClearFilters","CLEAR_ALL_FILTERS","intervalMs","reducedMotionIntervalMs","section","aria-labelledby","id","variant","onChange","onClear","totalRuns","length","undefined","status","branch","actor","event","after","before"],"mappings":";AAAA,SAAQA,kBAAkB,QAAO,gCAAgC;AACjE,SAAQC,MAAM,QAAO,+BAA+B;AACpD,SAAQC,EAAE,QAAO,0BAA0B;AAC3C,SAAQC,KAAK,EAAEC,OAAO,EAAEC,QAAQ,QAAO,QAAQ;AAC/C,SACEC,2BAA2B,EAC3BC,uBAAuB,EACvBC,qBAAqB,QAGhB,oBAAoB;AAC3B,SAAQC,iBAAiB,EAAEC,iBAAiB,QAAO,mBAAmB;AAEtE,SAAQC,kBAAkB,QAAO,4BAA4B;AAC7D,SAAQC,sBAAsB,QAAO,iCAAiC;AAEtE,MAAMC,eAAmC,CAAC;AAE1C,OAAO,SAASC,oBAAoB,EAClCC,IAAI,EACJC,KAAK,EACLC,aAAa,EACbC,WAAW,EACXC,SAAS,EACTC,SAASP,YAAY,EACrBQ,eAAe,EACfC,cAAc,EACdC,cAAc,KAAK,EACnBC,qBAAqB,KAAK,EAC1BC,uBAAuB,KAAK,EAC5BC,UAAU,EACe;IACzB,MAAMC,YAAYxB;IAClB,MAAM,CAACyB,aAAaC,eAAe,GAAGxB,SAA6Be;IACnE,MAAMU,gBAAgBT,kBAAkBD,SAASQ;IAEjD,MAAMG,eAAe3B,QACnB,IAAMW,KAAKiB,MAAM,CAAC,CAACC,MAAQxB,kBAAkBwB,KAAKH,iBAClD;QAACf;QAAMe;KAAc;IAEvB,MAAMI,SAAS9B,QAAQ,IAAMM,kBAAkBK,MAAMe,gBAAgB;QAACf;QAAMe;KAAc;IAC1F,MAAMK,mBAAmB3B,sBAAsBsB;IAE/C,SAASM,oBAAoBC,KAA6B;QACxD,IAAIhB,iBAAiBA,gBAAgBgB;aAChCR,eAAe,CAACS,WAAahC,4BAA4BgC,UAAUD;IAC1E;IAEA,SAASE;QACP,0FAA0F;QAC1F,yFAAyF;QACzF,IAAI,CAAClB,iBAAiBQ,eAAetB;aAChC,IAAI,CAACe,gBAAgBD,gBAAgBmB;QAC1ClB;IACF;IAEA,qBACE,KAACtB;QAAmByC,YAAY;QAAMC,yBAAyB;kBAC7D,cAAA,MAACC;YACCC,mBAAiBjB;YACjBR,WAAWjB,GAAG,oDAAoDiB;;8BAIlE,KAAClB;oBAAO4C,IAAIlB;oBAAWmB,SAAQ;oBAAK3B,WAAU;8BAAU;;8BAIxD,KAACR;oBACCS,QAAQU;oBACRI,QAAQA;oBACRa,UAAUX;oBACVY,SAAST;oBACTJ,kBAAkBA;;8BAGpB,KAACvB;oBACCI,OAAOA;oBACPiC,WAAWlC,KAAKmC,MAAM;oBACtBnC,MAAMgB;oBACNd,eAAeA;oBACfC,aAAaA;oBACbiB,kBAAkBA;oBAClBb,gBAAgBiB;oBAChBhB,aAAaA;oBACbC,oBAAoBA;oBACpBC,sBAAsBA;oBACrB,GAAIC,aAAa;wBAACA;oBAAU,IAAI,CAAC,CAAC;;;;;AAK7C;AAEA,MAAMc,oBAA4C;IAChDpB,QAAQ+B;IACRC,QAAQD;IACRE,QAAQF;IACRG,OAAOH;IACPI,OAAOJ;IACPK,OAAOL;IACPM,QAAQN;AACV"}
1
+ {"version":3,"sources":["../../../src/components/workflow-run-list/workflow-run-list-view.tsx"],"sourcesContent":["import {Panel, PanelBody, PanelHeader} from '@shipfox/react-ui/panel';\nimport {TimeTickerProvider} from '@shipfox/react-ui/time-ticker';\nimport {Header} from '@shipfox/react-ui/typography';\nimport {cn} from '@shipfox/react-ui/utils';\nimport {useId, useMemo, useState} from 'react';\nimport {\n applyWorkflowRunFilterPatch,\n clearWorkflowRunFilters,\n hasWorkflowRunFilters,\n type WorkflowRunFilterPatch,\n type WorkflowRunsSearch,\n} from '#routes/inputs.js';\nimport {runMatchesFilters, workflowRunFacets} from './run-display.js';\nimport type {WorkflowRunListViewProps} from './types.js';\nimport {WorkflowRunFilters} from './workflow-run-filters.js';\nimport {WorkflowRunListContent} from './workflow-run-list-content.js';\n\nconst EMPTY_SEARCH: WorkflowRunsSearch = {};\n\nexport function WorkflowRunListView({\n runs,\n query,\n workspaceSlug,\n projectSlug,\n className,\n search = EMPTY_SEARCH,\n onFiltersChange,\n onClearFilters,\n hasNextPage = false,\n isFetchingNextPage = false,\n isFetchNextPageError = false,\n onLoadMore,\n}: WorkflowRunListViewProps) {\n const headingId = useId();\n const [localSearch, setLocalSearch] = useState<WorkflowRunsSearch>(search);\n const currentSearch = onFiltersChange ? search : localSearch;\n\n const filteredRuns = useMemo(\n () => runs.filter((run) => runMatchesFilters(run, currentSearch)),\n [runs, currentSearch],\n );\n const facets = useMemo(() => workflowRunFacets(runs, currentSearch), [runs, currentSearch]);\n const hasActiveFilters = hasWorkflowRunFilters(currentSearch);\n\n function handleFiltersChange(patch: WorkflowRunFilterPatch) {\n if (onFiltersChange) onFiltersChange(patch);\n else setLocalSearch((previous) => applyWorkflowRunFilterPatch(previous, patch));\n }\n\n function handleClearFilters() {\n // Uncontrolled, the view owns the filters, so it clears them itself and still reports the\n // change; a caller that passes only `onClearFilters` would otherwise see nothing happen.\n if (!onFiltersChange) setLocalSearch(clearWorkflowRunFilters);\n else if (!onClearFilters) onFiltersChange(CLEAR_ALL_FILTERS);\n onClearFilters?.();\n }\n\n return (\n <TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>\n <section\n aria-labelledby={headingId}\n className={cn('flex min-h-0 min-w-0 flex-1 flex-col', className)}\n >\n {/* The tab strip above already reads \"Runs\", so the page heading is for structure and\n assistive tech rather than a second visible title competing with it. */}\n <Header id={headingId} variant=\"h1\" className=\"sr-only\">\n Workflow runs\n </Header>\n\n <Panel className=\"min-h-0 flex-1\">\n <PanelHeader className=\"flex-wrap\">\n <WorkflowRunFilters\n search={currentSearch}\n facets={facets}\n onChange={handleFiltersChange}\n onClear={handleClearFilters}\n hasActiveFilters={hasActiveFilters}\n />\n </PanelHeader>\n <PanelBody className=\"min-h-0 flex-1\">\n <WorkflowRunListContent\n query={query}\n totalRuns={runs.length}\n runs={filteredRuns}\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n hasActiveFilters={hasActiveFilters}\n onClearFilters={handleClearFilters}\n hasNextPage={hasNextPage}\n isFetchingNextPage={isFetchingNextPage}\n isFetchNextPageError={isFetchNextPageError}\n {...(onLoadMore ? {onLoadMore} : {})}\n />\n </PanelBody>\n </Panel>\n </section>\n </TimeTickerProvider>\n );\n}\n\nconst CLEAR_ALL_FILTERS: WorkflowRunFilterPatch = {\n search: undefined,\n status: undefined,\n branch: undefined,\n actor: undefined,\n event: undefined,\n after: undefined,\n before: undefined,\n};\n"],"names":["Panel","PanelBody","PanelHeader","TimeTickerProvider","Header","cn","useId","useMemo","useState","applyWorkflowRunFilterPatch","clearWorkflowRunFilters","hasWorkflowRunFilters","runMatchesFilters","workflowRunFacets","WorkflowRunFilters","WorkflowRunListContent","EMPTY_SEARCH","WorkflowRunListView","runs","query","workspaceSlug","projectSlug","className","search","onFiltersChange","onClearFilters","hasNextPage","isFetchingNextPage","isFetchNextPageError","onLoadMore","headingId","localSearch","setLocalSearch","currentSearch","filteredRuns","filter","run","facets","hasActiveFilters","handleFiltersChange","patch","previous","handleClearFilters","CLEAR_ALL_FILTERS","intervalMs","reducedMotionIntervalMs","section","aria-labelledby","id","variant","onChange","onClear","totalRuns","length","undefined","status","branch","actor","event","after","before"],"mappings":";AAAA,SAAQA,KAAK,EAAEC,SAAS,EAAEC,WAAW,QAAO,0BAA0B;AACtE,SAAQC,kBAAkB,QAAO,gCAAgC;AACjE,SAAQC,MAAM,QAAO,+BAA+B;AACpD,SAAQC,EAAE,QAAO,0BAA0B;AAC3C,SAAQC,KAAK,EAAEC,OAAO,EAAEC,QAAQ,QAAO,QAAQ;AAC/C,SACEC,2BAA2B,EAC3BC,uBAAuB,EACvBC,qBAAqB,QAGhB,oBAAoB;AAC3B,SAAQC,iBAAiB,EAAEC,iBAAiB,QAAO,mBAAmB;AAEtE,SAAQC,kBAAkB,QAAO,4BAA4B;AAC7D,SAAQC,sBAAsB,QAAO,iCAAiC;AAEtE,MAAMC,eAAmC,CAAC;AAE1C,OAAO,SAASC,oBAAoB,EAClCC,IAAI,EACJC,KAAK,EACLC,aAAa,EACbC,WAAW,EACXC,SAAS,EACTC,SAASP,YAAY,EACrBQ,eAAe,EACfC,cAAc,EACdC,cAAc,KAAK,EACnBC,qBAAqB,KAAK,EAC1BC,uBAAuB,KAAK,EAC5BC,UAAU,EACe;IACzB,MAAMC,YAAYxB;IAClB,MAAM,CAACyB,aAAaC,eAAe,GAAGxB,SAA6Be;IACnE,MAAMU,gBAAgBT,kBAAkBD,SAASQ;IAEjD,MAAMG,eAAe3B,QACnB,IAAMW,KAAKiB,MAAM,CAAC,CAACC,MAAQxB,kBAAkBwB,KAAKH,iBAClD;QAACf;QAAMe;KAAc;IAEvB,MAAMI,SAAS9B,QAAQ,IAAMM,kBAAkBK,MAAMe,gBAAgB;QAACf;QAAMe;KAAc;IAC1F,MAAMK,mBAAmB3B,sBAAsBsB;IAE/C,SAASM,oBAAoBC,KAA6B;QACxD,IAAIhB,iBAAiBA,gBAAgBgB;aAChCR,eAAe,CAACS,WAAahC,4BAA4BgC,UAAUD;IAC1E;IAEA,SAASE;QACP,0FAA0F;QAC1F,yFAAyF;QACzF,IAAI,CAAClB,iBAAiBQ,eAAetB;aAChC,IAAI,CAACe,gBAAgBD,gBAAgBmB;QAC1ClB;IACF;IAEA,qBACE,KAACtB;QAAmByC,YAAY;QAAMC,yBAAyB;kBAC7D,cAAA,MAACC;YACCC,mBAAiBjB;YACjBR,WAAWjB,GAAG,wCAAwCiB;;8BAItD,KAAClB;oBAAO4C,IAAIlB;oBAAWmB,SAAQ;oBAAK3B,WAAU;8BAAU;;8BAIxD,MAACtB;oBAAMsB,WAAU;;sCACf,KAACpB;4BAAYoB,WAAU;sCACrB,cAAA,KAACR;gCACCS,QAAQU;gCACRI,QAAQA;gCACRa,UAAUX;gCACVY,SAAST;gCACTJ,kBAAkBA;;;sCAGtB,KAACrC;4BAAUqB,WAAU;sCACnB,cAAA,KAACP;gCACCI,OAAOA;gCACPiC,WAAWlC,KAAKmC,MAAM;gCACtBnC,MAAMgB;gCACNd,eAAeA;gCACfC,aAAaA;gCACbiB,kBAAkBA;gCAClBb,gBAAgBiB;gCAChBhB,aAAaA;gCACbC,oBAAoBA;gCACpBC,sBAAsBA;gCACrB,GAAIC,aAAa;oCAACA;gCAAU,IAAI,CAAC,CAAC;;;;;;;;AAOjD;AAEA,MAAMc,oBAA4C;IAChDpB,QAAQ+B;IACRC,QAAQD;IACRE,QAAQF;IACRG,OAAOH;IACPI,OAAOJ;IACPK,OAAOL;IACPM,QAAQN;AACV"}
@@ -12,9 +12,9 @@ export declare function WorkflowRunRowList({ runs, workspaceSlug, projectSlug, }
12
12
  * drops beneath. The numeric columns are fixed-width so duration and time form real columns
13
13
  * down the list rather than tracking each row's name length.
14
14
  *
15
- * The thresholds are the container widths the spec's viewport breakpoints produce, given a
16
- * column capped at 1120px with 24px of page padding on each side: a 1280px viewport leaves
17
- * the row 1072px, and a 1024px viewport leaves it 976px.
15
+ * The 976px threshold keeps identity and provenance on one line; below it, provenance drops
16
+ * beneath the identity. The 1200px threshold keeps the full job preview and numeric columns
17
+ * readable together; below it, the strip is hidden so the numeric columns remain aligned.
18
18
  */
19
19
  export declare function WorkflowRunRow({ run, workspaceSlug, projectSlug, }: {
20
20
  run: WorkflowRunListItem;
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-run-row.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-list/workflow-run-row.tsx"],"names":[],"mappings":"AAiBA,OAAO,EACL,KAAK,mBAAmB,EAIzB,MAAM,uBAAuB,CAAC;AAI/B,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,aAAa,EACb,WAAW,GACZ,EAAE;IACD,IAAI,EAAE,mBAAmB,EAAE,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,+BAaA;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,EAC7B,GAAG,EACH,aAAa,EACb,WAAW,GACZ,EAAE;IACD,GAAG,EAAE,mBAAmB,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,+BA4FA"}
1
+ {"version":3,"file":"workflow-run-row.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-list/workflow-run-row.tsx"],"names":[],"mappings":"AAkBA,OAAO,EACL,KAAK,mBAAmB,EAIzB,MAAM,uBAAuB,CAAC;AAI/B,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,aAAa,EACb,WAAW,GACZ,EAAE;IACD,IAAI,EAAE,mBAAmB,EAAE,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,+BAeA;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,EAC7B,GAAG,EACH,aAAa,EACb,WAAW,GACZ,EAAE;IACD,GAAG,EAAE,mBAAmB,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,+BAgGA"}