@shipfox/client-workflows 18.0.0 → 20.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 (62) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +25 -0
  3. package/dist/components/workflow-run-duration-label.d.ts +8 -7
  4. package/dist/components/workflow-run-duration-label.d.ts.map +1 -1
  5. package/dist/components/workflow-run-duration-label.js +15 -31
  6. package/dist/components/workflow-run-duration-label.js.map +1 -1
  7. package/dist/components/workflow-run-list/job-status-strip.d.ts.map +1 -1
  8. package/dist/components/workflow-run-list/job-status-strip.js +20 -7
  9. package/dist/components/workflow-run-list/job-status-strip.js.map +1 -1
  10. package/dist/components/workflow-run-list/workflow-run-row.d.ts.map +1 -1
  11. package/dist/components/workflow-run-list/workflow-run-row.js +9 -6
  12. package/dist/components/workflow-run-list/workflow-run-row.js.map +1 -1
  13. package/dist/components/workflow-run-summary/workflow-run-summary.d.ts.map +1 -1
  14. package/dist/components/workflow-run-summary/workflow-run-summary.js +7 -34
  15. package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -1
  16. package/dist/components/workflow-status/status-visuals.d.ts.map +1 -1
  17. package/dist/components/workflow-status/status-visuals.js +0 -9
  18. package/dist/components/workflow-status/status-visuals.js.map +1 -1
  19. package/dist/components/workflow-status/workflow-status-icon.d.ts.map +1 -1
  20. package/dist/components/workflow-status/workflow-status-icon.js +0 -11
  21. package/dist/components/workflow-status/workflow-status-icon.js.map +1 -1
  22. package/dist/core/entities/job.d.ts +5 -2
  23. package/dist/core/entities/job.d.ts.map +1 -1
  24. package/dist/core/entities/job.js +8 -1
  25. package/dist/core/entities/job.js.map +1 -1
  26. package/dist/core/entities/workflow-run.d.ts +15 -57
  27. package/dist/core/entities/workflow-run.d.ts.map +1 -1
  28. package/dist/core/entities/workflow-run.js +1 -94
  29. package/dist/core/entities/workflow-run.js.map +1 -1
  30. package/dist/core/workflow-run.d.ts +2 -2
  31. package/dist/core/workflow-run.d.ts.map +1 -1
  32. package/dist/core/workflow-run.js +1 -1
  33. package/dist/core/workflow-run.js.map +1 -1
  34. package/dist/hooks/api/workflow-run-mapper.d.ts.map +1 -1
  35. package/dist/hooks/api/workflow-run-mapper.js +14 -2
  36. package/dist/hooks/api/workflow-run-mapper.js.map +1 -1
  37. package/dist/hooks/api/workflow-runs.js +1 -0
  38. package/dist/hooks/api/workflow-runs.js.map +1 -1
  39. package/dist/tsconfig.test.tsbuildinfo +1 -1
  40. package/package.json +2 -2
  41. package/src/components/job-graph/job-node.test.tsx +2 -2
  42. package/src/components/workflow-run-duration-label.tsx +21 -44
  43. package/src/components/workflow-run-list/job-status-strip.tsx +25 -12
  44. package/src/components/workflow-run-list/workflow-run-list-view.test.tsx +121 -0
  45. package/src/components/workflow-run-list/workflow-run-list.stories.tsx +31 -0
  46. package/src/components/workflow-run-list/workflow-run-row.tsx +7 -6
  47. package/src/components/workflow-run-summary/workflow-run-summary.stories.tsx +0 -38
  48. package/src/components/workflow-run-summary/workflow-run-summary.test.tsx +73 -21
  49. package/src/components/workflow-run-summary/workflow-run-summary.tsx +6 -27
  50. package/src/components/workflow-status/status-visuals.ts +0 -4
  51. package/src/components/workflow-status/workflow-status-icon.stories.tsx +0 -1
  52. package/src/components/workflow-status/workflow-status-icon.tsx +0 -18
  53. package/src/core/entities/job-status.test.ts +47 -1
  54. package/src/core/entities/job.ts +15 -3
  55. package/src/core/entities/workflow-run.ts +21 -134
  56. package/src/core/workflow-run.test.ts +15 -0
  57. package/src/core/workflow-run.ts +0 -8
  58. package/src/hooks/api/workflow-run-mapper.ts +20 -1
  59. package/src/hooks/api/workflow-runs.ts +1 -1
  60. package/test/fixtures/workflow-run.ts +60 -13
  61. package/tsconfig.build.tsbuildinfo +1 -1
  62. package/src/core/entities/workflow-run-display.test.ts +0 -159
@@ -1,2 +1,2 @@
1
1
  $ shipfox-swc
2
- Successfully compiled: 86 files with swc (559.86ms)
2
+ Successfully compiled: 86 files with swc (666.85ms)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # @shipfox/client-workflows
2
2
 
3
+ ## 20.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 4df5e37: Workflow run list items and run detail now carry `has_started_job_execution`, reporting whether any
8
+ job execution of the attempt reached a runner. Run surfaces no longer show a client-derived `Queued`
9
+ state: the run list and run detail present the API attempt status and duration, and read the new
10
+ field to label a finished run's duration as run or elapsed time.
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies [4df5e37]
15
+ - @shipfox/api-workflows-dto@12.7.0
16
+
17
+ ## 19.0.0
18
+
19
+ ### Minor Changes
20
+
21
+ - 53b87f0: Distinguish queued and executing jobs in workflow run list status strips.
22
+
23
+ ### Patch Changes
24
+
25
+ - Updated dependencies [53b87f0]
26
+ - @shipfox/api-workflows-dto@12.6.0
27
+
3
28
  ## 18.0.0
4
29
 
5
30
  ### Minor Changes
@@ -1,13 +1,14 @@
1
- import type { WorkflowRunAttemptDisplayDuration, WorkflowRunDisplayDuration } from '#core/workflow-run.js';
1
+ import type { WorkflowRunAttemptDisplayDuration } from '#core/workflow-run.js';
2
2
  /**
3
- * The run list reads an attempt's own marks and cannot tell queue from run, so an unkinded
4
- * duration still renders as run time. Only surfaces holding the run's jobs pass a kind.
3
+ * A run reports one span: how long its attempt has been under way. Queueing is a job
4
+ * execution's own affair, and the job surfaces report it there.
5
5
  */
6
- export type WorkflowRunDurationDisplay = WorkflowRunAttemptDisplayDuration | WorkflowRunDisplayDuration;
7
- export declare function WorkflowRunDurationLabel({ duration, className, }: {
6
+ export type WorkflowRunDurationDisplay = WorkflowRunAttemptDisplayDuration;
7
+ export declare function WorkflowRunDurationLabel({ duration, hasStarted, className, }: {
8
8
  duration: WorkflowRunDurationDisplay | null;
9
+ hasStarted: boolean;
9
10
  className?: string | undefined;
10
11
  }): import("react").JSX.Element | null;
11
- export declare function useWorkflowRunDurationAccessibleLabel(duration: WorkflowRunDurationDisplay | null): string | undefined;
12
- export declare function workflowRunDurationAccessibleLabel(duration: WorkflowRunDurationDisplay | null): string | undefined;
12
+ export declare function useWorkflowRunDurationAccessibleLabel(duration: WorkflowRunDurationDisplay | null, hasStarted: boolean): string | undefined;
13
+ export declare function workflowRunDurationAccessibleLabel(duration: WorkflowRunDurationDisplay | null, hasStarted: boolean): string | undefined;
13
14
  //# sourceMappingURL=workflow-run-duration-label.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-run-duration-label.d.ts","sourceRoot":"","sources":["../../src/components/workflow-run-duration-label.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,iCAAiC,EACjC,0BAA0B,EAC3B,MAAM,uBAAuB,CAAC;AAE/B;;;GAGG;AACH,MAAM,MAAM,0BAA0B,GAClC,iCAAiC,GACjC,0BAA0B,CAAC;AAQ/B,wBAAgB,wBAAwB,CAAC,EACvC,QAAQ,EACR,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,0BAA0B,GAAG,IAAI,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC,sCAwBA;AAED,wBAAgB,qCAAqC,CACnD,QAAQ,EAAE,0BAA0B,GAAG,IAAI,GAC1C,MAAM,GAAG,SAAS,CAGpB;AAED,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,0BAA0B,GAAG,IAAI,GAC1C,MAAM,GAAG,SAAS,CAcpB"}
1
+ {"version":3,"file":"workflow-run-duration-label.d.ts","sourceRoot":"","sources":["../../src/components/workflow-run-duration-label.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,iCAAiC,EAAC,MAAM,uBAAuB,CAAC;AAE7E;;;GAGG;AACH,MAAM,MAAM,0BAA0B,GAAG,iCAAiC,CAAC;AAE3E,wBAAgB,wBAAwB,CAAC,EACvC,QAAQ,EACR,UAAU,EACV,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,0BAA0B,GAAG,IAAI,CAAC;IAC5C,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC,sCAoBA;AAED,wBAAgB,qCAAqC,CACnD,QAAQ,EAAE,0BAA0B,GAAG,IAAI,EAC3C,UAAU,EAAE,OAAO,GAClB,MAAM,GAAG,SAAS,CAGpB;AAED,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,0BAA0B,GAAG,IAAI,EAC3C,UAAU,EAAE,OAAO,GAClB,MAAM,GAAG,SAAS,CAepB"}
@@ -3,36 +3,22 @@ import { Icon } from '@shipfox/react-ui/icon';
3
3
  import { useTimeTick } from '@shipfox/react-ui/time-ticker';
4
4
  import { Code } from '@shipfox/react-ui/typography';
5
5
  import { cn, humanDuration } from '@shipfox/react-ui/utils';
6
- const GLYPH_BY_KIND = {
7
- queue: 'hourglassLine',
8
- run: 'timerLine'
9
- };
10
- const LIVE_VERB_BY_KIND = {
11
- queue: 'queued',
12
- run: 'running'
13
- };
14
- const FIXED_VERB_BY_KIND = {
15
- queue: 'queued',
16
- run: 'ran'
17
- };
18
- export function WorkflowRunDurationLabel({ duration, className }) {
6
+ export function WorkflowRunDurationLabel({ duration, hasStarted, className }) {
19
7
  if (duration === null) return null;
20
- const kind = durationKind(duration);
21
8
  switch(duration.state){
22
9
  case 'fixed':
23
10
  {
24
11
  const display = formatFixedDurationLabel(duration.elapsed);
12
+ const verb = hasStarted ? 'ran' : 'lasted';
25
13
  return /*#__PURE__*/ _jsx(DurationText, {
26
14
  className: className,
27
- kind: kind,
28
- ariaLabel: `${FIXED_VERB_BY_KIND[kind]} ${display}`,
15
+ ariaLabel: `${verb} ${display}`,
29
16
  children: display
30
17
  });
31
18
  }
32
19
  case 'live':
33
20
  return /*#__PURE__*/ _jsx(LiveDurationText, {
34
21
  duration: duration,
35
- kind: kind,
36
22
  className: className
37
23
  });
38
24
  default:
@@ -42,18 +28,20 @@ export function WorkflowRunDurationLabel({ duration, className }) {
42
28
  }
43
29
  }
44
30
  }
45
- export function useWorkflowRunDurationAccessibleLabel(duration) {
31
+ export function useWorkflowRunDurationAccessibleLabel(duration, hasStarted) {
46
32
  useTimeTick();
47
- return workflowRunDurationAccessibleLabel(duration);
33
+ return workflowRunDurationAccessibleLabel(duration, hasStarted);
48
34
  }
49
- export function workflowRunDurationAccessibleLabel(duration) {
35
+ export function workflowRunDurationAccessibleLabel(duration, hasStarted) {
50
36
  if (duration === null) return undefined;
51
- const kind = durationKind(duration);
52
37
  switch(duration.state){
53
38
  case 'live':
54
- return `${LIVE_VERB_BY_KIND[kind]} ${humanDuration(duration.fromIso)}`;
39
+ return `running ${humanDuration(duration.fromIso)}`;
55
40
  case 'fixed':
56
- return `${FIXED_VERB_BY_KIND[kind]} ${formatFixedDurationLabel(duration.elapsed)}`;
41
+ {
42
+ const verb = hasStarted ? 'ran' : 'lasted';
43
+ return `${verb} ${formatFixedDurationLabel(duration.elapsed)}`;
44
+ }
57
45
  default:
58
46
  {
59
47
  const exhaustive = duration;
@@ -61,20 +49,16 @@ export function workflowRunDurationAccessibleLabel(duration) {
61
49
  }
62
50
  }
63
51
  }
64
- function durationKind(duration) {
65
- return 'kind' in duration ? duration.kind : 'run';
66
- }
67
- function LiveDurationText({ duration, kind, className }) {
52
+ function LiveDurationText({ duration, className }) {
68
53
  useTimeTick();
69
54
  const display = humanDuration(duration.fromIso);
70
55
  return /*#__PURE__*/ _jsx(DurationText, {
71
56
  className: className,
72
- kind: kind,
73
- ariaLabel: `${LIVE_VERB_BY_KIND[kind]} ${display}`,
57
+ ariaLabel: `running ${display}`,
74
58
  children: display
75
59
  });
76
60
  }
77
- function DurationText({ children, className, kind, ariaLabel }) {
61
+ function DurationText({ children, className, ariaLabel }) {
78
62
  return /*#__PURE__*/ _jsxs(Code, {
79
63
  as: "span",
80
64
  variant: "label",
@@ -82,7 +66,7 @@ function DurationText({ children, className, kind, ariaLabel }) {
82
66
  className: cn('inline-flex shrink-0 items-center gap-tight tabular-nums text-foreground-neutral-subtle', className),
83
67
  children: [
84
68
  /*#__PURE__*/ _jsx(Icon, {
85
- name: GLYPH_BY_KIND[kind],
69
+ name: "timerLine",
86
70
  className: "size-12 shrink-0",
87
71
  "aria-hidden": "true"
88
72
  }),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/workflow-run-duration-label.tsx"],"sourcesContent":["import {Icon} from '@shipfox/react-ui/icon';\nimport {useTimeTick} from '@shipfox/react-ui/time-ticker';\nimport {Code} from '@shipfox/react-ui/typography';\nimport {cn, humanDuration} from '@shipfox/react-ui/utils';\nimport type {\n WorkflowRunAttemptDisplayDuration,\n WorkflowRunDisplayDuration,\n} from '#core/workflow-run.js';\n\n/**\n * The run list reads an attempt's own marks and cannot tell queue from run, so an unkinded\n * duration still renders as run time. Only surfaces holding the run's jobs pass a kind.\n */\nexport type WorkflowRunDurationDisplay =\n | WorkflowRunAttemptDisplayDuration\n | WorkflowRunDisplayDuration;\n\ntype DurationKind = 'queue' | 'run';\n\nconst GLYPH_BY_KIND = {queue: 'hourglassLine', run: 'timerLine'} as const;\nconst LIVE_VERB_BY_KIND = {queue: 'queued', run: 'running'} as const;\nconst FIXED_VERB_BY_KIND = {queue: 'queued', run: 'ran'} as const;\n\nexport function WorkflowRunDurationLabel({\n duration,\n className,\n}: {\n duration: WorkflowRunDurationDisplay | null;\n className?: string | undefined;\n}) {\n if (duration === null) return null;\n const kind = durationKind(duration);\n\n switch (duration.state) {\n case 'fixed': {\n const display = formatFixedDurationLabel(duration.elapsed);\n return (\n <DurationText\n className={className}\n kind={kind}\n ariaLabel={`${FIXED_VERB_BY_KIND[kind]} ${display}`}\n >\n {display}\n </DurationText>\n );\n }\n case 'live':\n return <LiveDurationText duration={duration} kind={kind} className={className} />;\n default: {\n const exhaustive: never = duration;\n return exhaustive;\n }\n }\n}\n\nexport function useWorkflowRunDurationAccessibleLabel(\n duration: WorkflowRunDurationDisplay | null,\n): string | undefined {\n useTimeTick();\n return workflowRunDurationAccessibleLabel(duration);\n}\n\nexport function workflowRunDurationAccessibleLabel(\n duration: WorkflowRunDurationDisplay | null,\n): string | undefined {\n if (duration === null) return undefined;\n const kind = durationKind(duration);\n\n switch (duration.state) {\n case 'live':\n return `${LIVE_VERB_BY_KIND[kind]} ${humanDuration(duration.fromIso)}`;\n case 'fixed':\n return `${FIXED_VERB_BY_KIND[kind]} ${formatFixedDurationLabel(duration.elapsed)}`;\n default: {\n const exhaustive: never = duration;\n return exhaustive;\n }\n }\n}\n\nfunction durationKind(duration: WorkflowRunDurationDisplay): DurationKind {\n return 'kind' in duration ? duration.kind : 'run';\n}\n\nfunction LiveDurationText({\n duration,\n kind,\n className,\n}: {\n duration: Extract<WorkflowRunDurationDisplay, {state: 'live'}>;\n kind: DurationKind;\n className?: string | undefined;\n}) {\n useTimeTick();\n const display = humanDuration(duration.fromIso);\n return (\n <DurationText\n className={className}\n kind={kind}\n ariaLabel={`${LIVE_VERB_BY_KIND[kind]} ${display}`}\n >\n {display}\n </DurationText>\n );\n}\n\nfunction DurationText({\n children,\n className,\n kind,\n ariaLabel,\n}: {\n children: string;\n className?: string | undefined;\n kind: DurationKind;\n ariaLabel?: string | undefined;\n}) {\n return (\n <Code\n as=\"span\"\n variant=\"label\"\n aria-label={ariaLabel}\n className={cn(\n 'inline-flex shrink-0 items-center gap-tight tabular-nums text-foreground-neutral-subtle',\n className,\n )}\n >\n {/* The glyph is the only visible mark of queue vs run: the status badge already writes\n \"Queued\", and the numeric columns are fixed width. The verb rides the accessible\n name instead. */}\n <Icon name={GLYPH_BY_KIND[kind]} className=\"size-12 shrink-0\" aria-hidden=\"true\" />\n {children}\n </Code>\n );\n}\n\nfunction formatFixedDurationLabel({\n years = 0,\n months = 0,\n weeks = 0,\n days = 0,\n hours = 0,\n minutes = 0,\n seconds = 0,\n}: Extract<WorkflowRunDurationDisplay, {state: 'fixed'}>['elapsed']): string {\n const totalDays = years * 365 + months * 30 + weeks * 7 + days;\n const totalHours = totalDays * 24 + hours;\n\n if (totalHours > 0) return `${totalHours}h ${pad2(minutes)}m`;\n if (minutes > 0) return `${minutes}m ${pad2(seconds)}s`;\n return `${seconds}s`;\n}\n\nfunction pad2(value: number): string {\n return value.toString().padStart(2, '0');\n}\n"],"names":["Icon","useTimeTick","Code","cn","humanDuration","GLYPH_BY_KIND","queue","run","LIVE_VERB_BY_KIND","FIXED_VERB_BY_KIND","WorkflowRunDurationLabel","duration","className","kind","durationKind","state","display","formatFixedDurationLabel","elapsed","DurationText","ariaLabel","LiveDurationText","exhaustive","useWorkflowRunDurationAccessibleLabel","workflowRunDurationAccessibleLabel","undefined","fromIso","children","as","variant","aria-label","name","aria-hidden","years","months","weeks","days","hours","minutes","seconds","totalDays","totalHours","pad2","value","toString","padStart"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/components/workflow-run-duration-label.tsx"],"sourcesContent":["import {Icon} from '@shipfox/react-ui/icon';\nimport {useTimeTick} from '@shipfox/react-ui/time-ticker';\nimport {Code} from '@shipfox/react-ui/typography';\nimport {cn, humanDuration} from '@shipfox/react-ui/utils';\nimport type {WorkflowRunAttemptDisplayDuration} from '#core/workflow-run.js';\n\n/**\n * A run reports one span: how long its attempt has been under way. Queueing is a job\n * execution's own affair, and the job surfaces report it there.\n */\nexport type WorkflowRunDurationDisplay = WorkflowRunAttemptDisplayDuration;\n\nexport function WorkflowRunDurationLabel({\n duration,\n hasStarted,\n className,\n}: {\n duration: WorkflowRunDurationDisplay | null;\n hasStarted: boolean;\n className?: string | undefined;\n}) {\n if (duration === null) return null;\n\n switch (duration.state) {\n case 'fixed': {\n const display = formatFixedDurationLabel(duration.elapsed);\n const verb = hasStarted ? 'ran' : 'lasted';\n return (\n <DurationText className={className} ariaLabel={`${verb} ${display}`}>\n {display}\n </DurationText>\n );\n }\n case 'live':\n return <LiveDurationText duration={duration} className={className} />;\n default: {\n const exhaustive: never = duration;\n return exhaustive;\n }\n }\n}\n\nexport function useWorkflowRunDurationAccessibleLabel(\n duration: WorkflowRunDurationDisplay | null,\n hasStarted: boolean,\n): string | undefined {\n useTimeTick();\n return workflowRunDurationAccessibleLabel(duration, hasStarted);\n}\n\nexport function workflowRunDurationAccessibleLabel(\n duration: WorkflowRunDurationDisplay | null,\n hasStarted: boolean,\n): string | undefined {\n if (duration === null) return undefined;\n\n switch (duration.state) {\n case 'live':\n return `running ${humanDuration(duration.fromIso)}`;\n case 'fixed': {\n const verb = hasStarted ? 'ran' : 'lasted';\n return `${verb} ${formatFixedDurationLabel(duration.elapsed)}`;\n }\n default: {\n const exhaustive: never = duration;\n return exhaustive;\n }\n }\n}\n\nfunction LiveDurationText({\n duration,\n className,\n}: {\n duration: Extract<WorkflowRunDurationDisplay, {state: 'live'}>;\n className?: string | undefined;\n}) {\n useTimeTick();\n const display = humanDuration(duration.fromIso);\n return (\n <DurationText className={className} ariaLabel={`running ${display}`}>\n {display}\n </DurationText>\n );\n}\n\nfunction DurationText({\n children,\n className,\n ariaLabel,\n}: {\n children: string;\n className?: string | undefined;\n ariaLabel?: string | undefined;\n}) {\n return (\n <Code\n as=\"span\"\n variant=\"label\"\n aria-label={ariaLabel}\n className={cn(\n 'inline-flex shrink-0 items-center gap-tight tabular-nums text-foreground-neutral-subtle',\n className,\n )}\n >\n {/* The verb rides the accessible name: the numeric columns are fixed width and the\n status badge already says whether the run is still going. */}\n <Icon name=\"timerLine\" className=\"size-12 shrink-0\" aria-hidden=\"true\" />\n {children}\n </Code>\n );\n}\n\nfunction formatFixedDurationLabel({\n years = 0,\n months = 0,\n weeks = 0,\n days = 0,\n hours = 0,\n minutes = 0,\n seconds = 0,\n}: Extract<WorkflowRunDurationDisplay, {state: 'fixed'}>['elapsed']): string {\n const totalDays = years * 365 + months * 30 + weeks * 7 + days;\n const totalHours = totalDays * 24 + hours;\n\n if (totalHours > 0) return `${totalHours}h ${pad2(minutes)}m`;\n if (minutes > 0) return `${minutes}m ${pad2(seconds)}s`;\n return `${seconds}s`;\n}\n\nfunction pad2(value: number): string {\n return value.toString().padStart(2, '0');\n}\n"],"names":["Icon","useTimeTick","Code","cn","humanDuration","WorkflowRunDurationLabel","duration","hasStarted","className","state","display","formatFixedDurationLabel","elapsed","verb","DurationText","ariaLabel","LiveDurationText","exhaustive","useWorkflowRunDurationAccessibleLabel","workflowRunDurationAccessibleLabel","undefined","fromIso","children","as","variant","aria-label","name","aria-hidden","years","months","weeks","days","hours","minutes","seconds","totalDays","totalHours","pad2","value","toString","padStart"],"mappings":";AAAA,SAAQA,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,WAAW,QAAO,gCAAgC;AAC1D,SAAQC,IAAI,QAAO,+BAA+B;AAClD,SAAQC,EAAE,EAAEC,aAAa,QAAO,0BAA0B;AAS1D,OAAO,SAASC,yBAAyB,EACvCC,QAAQ,EACRC,UAAU,EACVC,SAAS,EAKV;IACC,IAAIF,aAAa,MAAM,OAAO;IAE9B,OAAQA,SAASG,KAAK;QACpB,KAAK;YAAS;gBACZ,MAAMC,UAAUC,yBAAyBL,SAASM,OAAO;gBACzD,MAAMC,OAAON,aAAa,QAAQ;gBAClC,qBACE,KAACO;oBAAaN,WAAWA;oBAAWO,WAAW,GAAGF,KAAK,CAAC,EAAEH,SAAS;8BAChEA;;YAGP;QACA,KAAK;YACH,qBAAO,KAACM;gBAAiBV,UAAUA;gBAAUE,WAAWA;;QAC1D;YAAS;gBACP,MAAMS,aAAoBX;gBAC1B,OAAOW;YACT;IACF;AACF;AAEA,OAAO,SAASC,sCACdZ,QAA2C,EAC3CC,UAAmB;IAEnBN;IACA,OAAOkB,mCAAmCb,UAAUC;AACtD;AAEA,OAAO,SAASY,mCACdb,QAA2C,EAC3CC,UAAmB;IAEnB,IAAID,aAAa,MAAM,OAAOc;IAE9B,OAAQd,SAASG,KAAK;QACpB,KAAK;YACH,OAAO,CAAC,QAAQ,EAAEL,cAAcE,SAASe,OAAO,GAAG;QACrD,KAAK;YAAS;gBACZ,MAAMR,OAAON,aAAa,QAAQ;gBAClC,OAAO,GAAGM,KAAK,CAAC,EAAEF,yBAAyBL,SAASM,OAAO,GAAG;YAChE;QACA;YAAS;gBACP,MAAMK,aAAoBX;gBAC1B,OAAOW;YACT;IACF;AACF;AAEA,SAASD,iBAAiB,EACxBV,QAAQ,EACRE,SAAS,EAIV;IACCP;IACA,MAAMS,UAAUN,cAAcE,SAASe,OAAO;IAC9C,qBACE,KAACP;QAAaN,WAAWA;QAAWO,WAAW,CAAC,QAAQ,EAAEL,SAAS;kBAChEA;;AAGP;AAEA,SAASI,aAAa,EACpBQ,QAAQ,EACRd,SAAS,EACTO,SAAS,EAKV;IACC,qBACE,MAACb;QACCqB,IAAG;QACHC,SAAQ;QACRC,cAAYV;QACZP,WAAWL,GACT,2FACAK;;0BAKF,KAACR;gBAAK0B,MAAK;gBAAYlB,WAAU;gBAAmBmB,eAAY;;YAC/DL;;;AAGP;AAEA,SAASX,yBAAyB,EAChCiB,QAAQ,CAAC,EACTC,SAAS,CAAC,EACVC,QAAQ,CAAC,EACTC,OAAO,CAAC,EACRC,QAAQ,CAAC,EACTC,UAAU,CAAC,EACXC,UAAU,CAAC,EACsD;IACjE,MAAMC,YAAYP,QAAQ,MAAMC,SAAS,KAAKC,QAAQ,IAAIC;IAC1D,MAAMK,aAAaD,YAAY,KAAKH;IAEpC,IAAII,aAAa,GAAG,OAAO,GAAGA,WAAW,EAAE,EAAEC,KAAKJ,SAAS,CAAC,CAAC;IAC7D,IAAIA,UAAU,GAAG,OAAO,GAAGA,QAAQ,EAAE,EAAEI,KAAKH,SAAS,CAAC,CAAC;IACvD,OAAO,GAAGA,QAAQ,CAAC,CAAC;AACtB;AAEA,SAASG,KAAKC,KAAa;IACzB,OAAOA,MAAMC,QAAQ,GAAGC,QAAQ,CAAC,GAAG;AACtC"}
@@ -1 +1 @@
1
- {"version":3,"file":"job-status-strip.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-list/job-status-strip.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAmC,eAAe,EAAC,MAAM,uBAAuB,CAAC;AAyC7F,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,eAAe,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,EAAC,IAAI,EAAE,SAAS,EAAC,EAAE,mBAAmB,sCAqDpE;AAgCD,kGAAkG;AAClG,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAO9D;AAyBD,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAI9D"}
1
+ {"version":3,"file":"job-status-strip.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-list/job-status-strip.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAwB,eAAe,EAAC,MAAM,uBAAuB,CAAC;AA0ClF,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,eAAe,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,EAAC,IAAI,EAAE,SAAS,EAAC,EAAE,mBAAmB,sCAqDpE;AAgCD,kGAAkG;AAClG,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAO9D;AA0BD,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAI9D"}
@@ -4,6 +4,7 @@ import { Code, Text } from '@shipfox/react-ui/typography';
4
4
  import { cn } from '@shipfox/react-ui/utils';
5
5
  import { getWorkflowStatusVisual } from '#components/workflow-status/status-visuals.js';
6
6
  import { WorkflowStatusIcon } from '#components/workflow-status/workflow-status-icon.js';
7
+ import { deriveJobDisplayStatus } from '#core/entities/job.js';
7
8
  /**
8
9
  * How many glyphs fit the strip's column before it starts costing the run name width.
9
10
  *
@@ -31,6 +32,7 @@ const COMPACT_COUNT_FORMAT = new Intl.NumberFormat('en', {
31
32
  const STATUS_SEVERITY = {
32
33
  failed: 5,
33
34
  running: 4,
35
+ listening: 4,
34
36
  pending: 3,
35
37
  cancelled: 2,
36
38
  skipped: 1,
@@ -38,7 +40,8 @@ const STATUS_SEVERITY = {
38
40
  };
39
41
  const NOTABLE_STATUSES = [
40
42
  'failed',
41
- 'running'
43
+ 'running',
44
+ 'listening'
42
45
  ];
43
46
  /**
44
47
  * A run's jobs as a row of status glyphs, in graph order.
@@ -69,7 +72,7 @@ const NOTABLE_STATUSES = [
69
72
  className: "inline-flex items-center gap-[3px]",
70
73
  children: [
71
74
  visible.map((job)=>/*#__PURE__*/ _jsx(WorkflowStatusIcon, {
72
- status: job.status,
75
+ status: displayStatus(job),
73
76
  size: GLYPH_SIZE,
74
77
  // One ripple per running job, on every row, would turn a calm list into a
75
78
  // field of pulses. The run's own glyph already carries the live edge.
@@ -114,7 +117,7 @@ function JobStatusStripTooltip({ jobs, summary }) {
114
117
  // left to the counts so the tooltip stays a glance rather than a list. Only previewed jobs
115
118
  // can be named, but the count of those left over is read off the totals, so a failure past
116
119
  // the preview is still accounted for rather than silently missing.
117
- const named = jobs.preview.filter((job)=>NOTABLE_STATUSES.includes(job.status)).slice(0, MAX_TOOLTIP_JOB_NAMES);
120
+ const named = jobs.preview.filter((job)=>NOTABLE_STATUSES.includes(displayStatus(job))).slice(0, MAX_TOOLTIP_JOB_NAMES);
118
121
  const notableTotal = NOTABLE_STATUSES.reduce((total, status)=>total + countOf(jobs, status), 0);
119
122
  const remaining = notableTotal - named.length;
120
123
  return /*#__PURE__*/ _jsxs("span", {
@@ -131,7 +134,7 @@ function JobStatusStripTooltip({ jobs, summary }) {
131
134
  variant: "label",
132
135
  className: "block truncate",
133
136
  children: [
134
- getWorkflowStatusVisual(job.status).label.toLowerCase(),
137
+ getWorkflowStatusVisual(displayStatus(job)).label.toLowerCase(),
135
138
  " · ",
136
139
  job.name ?? job.key
137
140
  ]
@@ -164,9 +167,10 @@ function JobStatusStripTooltip({ jobs, summary }) {
164
167
  count
165
168
  ]));
166
169
  for (const job of visible){
167
- const remaining = (hidden.get(job.status) ?? 0) - 1;
168
- if (remaining > 0) hidden.set(job.status, remaining);
169
- else hidden.delete(job.status);
170
+ const status = displayStatus(job);
171
+ const remaining = (hidden.get(status) ?? 0) - 1;
172
+ if (remaining > 0) hidden.set(status, remaining);
173
+ else hidden.delete(status);
170
174
  }
171
175
  let worst = null;
172
176
  for (const [status, count] of hidden){
@@ -181,5 +185,14 @@ export function overflowCountLabel(hiddenCount) {
181
185
  function countOf(jobs, status) {
182
186
  return jobs.statusCounts.find((entry)=>entry.status === status)?.count ?? 0;
183
187
  }
188
+ function displayStatus(job) {
189
+ return deriveJobDisplayStatus({
190
+ mode: job.mode,
191
+ status: job.status,
192
+ listenerStatus: job.listenerStatus,
193
+ executionStatus: job.executionStatus,
194
+ jobExecutions: []
195
+ });
196
+ }
184
197
 
185
198
  //# sourceMappingURL=job-status-strip.js.map
@@ -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 type {JobStatus, 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<JobStatus, number> = {\n failed: 5,\n running: 4,\n pending: 3,\n cancelled: 2,\n skipped: 1,\n succeeded: 0,\n};\n\nconst NOTABLE_STATUSES: readonly JobStatus[] = ['failed', 'running'];\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={job.status}\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(job.status))\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(job.status).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): JobStatus | null {\n const hidden = new Map(jobs.statusCounts.map(({status, count}) => [status, count]));\n for (const job of visible) {\n const remaining = (hidden.get(job.status) ?? 0) - 1;\n if (remaining > 0) hidden.set(job.status, remaining);\n else hidden.delete(job.status);\n }\n\n let worst: JobStatus | 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: JobStatus): number {\n return jobs.statusCounts.find((entry) => entry.status === status)?.count ?? 0;\n}\n"],"names":["Tooltip","TooltipContent","TooltipTrigger","Code","Text","cn","getWorkflowStatusVisual","WorkflowStatusIcon","MAX_VISIBLE_JOBS","GLYPH_SIZE","MAX_TOOLTIP_JOB_NAMES","MAX_EXACT_OVERFLOW_COUNT","COMPACT_COUNT_FORMAT","Intl","NumberFormat","notation","maximumFractionDigits","STATUS_SEVERITY","failed","running","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","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"],"mappings":"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"}
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":";AAAA,SAAQA,OAAO,EAAEC,cAAc,EAAEC,cAAc,QAAO,4BAA4B;AAClF,SAAQC,IAAI,EAAEC,IAAI,QAAO,+BAA+B;AACxD,SAAQC,EAAE,QAAO,0BAA0B;AAC3C,SAAQC,uBAAuB,QAAO,gDAAgD;AACtF,SAAQC,kBAAkB,QAAO,sDAAsD;AACvF,SAAQC,sBAAsB,QAA8B,wBAAwB;AAGpF;;;;;;;CAOC,GACD,MAAMC,mBAAmB;AACzB,MAAMC,aAAa;AACnB,MAAMC,wBAAwB;AAE9B;;;;;;;CAOC,GACD,MAAMC,2BAA2B;AACjC,MAAMC,uBAAuB,IAAIC,KAAKC,YAAY,CAAC,MAAM;IACvDC,UAAU;IACVC,uBAAuB;AACzB;AAEA,4FAA4F;AAC5F,6FAA6F;AAC7F,MAAMC,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,SAASC,eAAe,EAACC,IAAI,EAAEC,SAAS,EAAsB;IACnE,IAAID,KAAKE,KAAK,KAAK,GAAG,OAAO;IAE7B,MAAMC,UAAUH,KAAKI,OAAO,CAACC,KAAK,CAAC,GAAGxB;IACtC,MAAMyB,cAAcN,KAAKE,KAAK,GAAGC,QAAQI,MAAM;IAC/C,MAAMC,iBAAiBC,kBAAkBT,MAAMG;IAC/C,MAAMO,UAAUC,iBAAiBX;IAEjC,qBACE,MAAC5B;;0BACC,KAACE;gBAAesC,OAAO;0BACrB,cAAA,KAACC;oBACCC,MAAK;oBACLC,cAAYL;oBACZT,WAAWxB,GAAG,sCAAsCwB;8BAEpD,cAAA,MAACY;wBAAKG,eAAY;wBAAOf,WAAU;;4BAChCE,QAAQc,GAAG,CAAC,CAACC,oBACZ,KAACvC;oCAECwC,QAAQC,cAAcF;oCACtBG,MAAMvC;oCACN,0EAA0E;oCAC1E,sEAAsE;oCACtEwC,QAAQ;oCACRC,SAAS;mCANJL,IAAIM,EAAE;4BASdlB,cAAc,KAAKE,+BAClB,MAACK;gCAAKZ,WAAU;;kDACd,KAACtB;wCACCwC,QAAQX;wCACRa,MAAMvC;wCACNwC,QAAQ;wCACRC,SAAS;;kDAEX,MAAChD;wCACCkD,IAAG;wCACHC,SAAQ;wCACRzB,WAAU;;4CACX;4CACG0B,mBAAmBrB;;;;iCAGvB;;;;;0BAIV,KAACjC;0BACC,cAAA,KAACuD;oBAAsB5B,MAAMA;oBAAMU,SAASA;;;;;AAIpD;AAEA,SAASkB,sBAAsB,EAAC5B,IAAI,EAAEU,OAAO,EAA2C;IACtF,yFAAyF;IACzF,2FAA2F;IAC3F,2FAA2F;IAC3F,mEAAmE;IACnE,MAAMmB,QAAQ7B,KAAKI,OAAO,CACvB0B,MAAM,CAAC,CAACZ,MAAQpB,iBAAiBiC,QAAQ,CAACX,cAAcF,OACxDb,KAAK,CAAC,GAAGtB;IACZ,MAAMiD,eAAelC,iBAAiBmC,MAAM,CAAC,CAAC/B,OAAOiB,SAAWjB,QAAQgC,QAAQlC,MAAMmB,SAAS;IAC/F,MAAMgB,YAAYH,eAAeH,MAAMtB,MAAM;IAE7C,qBACE,MAACM;QAAKZ,WAAU;;0BACd,KAACzB;gBAAKiD,IAAG;gBAAOJ,MAAK;gBAAKpB,WAAU;0BACjCS;;YAEFmB,MAAMZ,GAAG,CAAC,CAACC,oBACV,MAAC3C;oBAAKkD,IAAG;oBAAOC,SAAQ;oBAAqBzB,WAAU;;wBACpDvB,wBAAwB0C,cAAcF,MAAMkB,KAAK,CAACC,WAAW;wBAAG;wBAAInB,IAAIoB,IAAI,IAAIpB,IAAIqB,GAAG;;mBADrDrB,IAAIM,EAAE;YAI5CW,YAAY,kBACX,MAAC3D;gBAAKiD,IAAG;gBAAOJ,MAAK;gBAAKpB,WAAU;;oBAAsC;oBACnEkC;oBAAU;;iBAEf;;;AAGV;AAEA,gGAAgG,GAChG,OAAO,SAASxB,iBAAiBX,IAAqB;IACpD,MAAMwC,YAAY;WAAIxC,KAAKyC,YAAY;KAAC,CACrCC,IAAI,CAAC,CAACC,MAAMC,QAAUtD,eAAe,CAACsD,MAAMzB,MAAM,CAAC,GAAG7B,eAAe,CAACqD,KAAKxB,MAAM,CAAC,EAClFF,GAAG,CAAC,CAAC,EAACE,MAAM,EAAE0B,KAAK,EAAC,GAAK,GAAGA,MAAM,CAAC,EAAEnE,wBAAwByC,QAAQiB,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,SAAS/B,kBACPT,IAAqB,EACrBG,OAAyC;IAEzC,MAAM4C,SAAS,IAAIC,IAAIhD,KAAKyC,YAAY,CAACxB,GAAG,CAAC,CAAC,EAACE,MAAM,EAAE0B,KAAK,EAAC,GAAK;YAAC1B;YAAQ0B;SAAM;IACjF,KAAK,MAAM3B,OAAOf,QAAS;QACzB,MAAMgB,SAASC,cAAcF;QAC7B,MAAMiB,YAAY,AAACY,CAAAA,OAAOE,GAAG,CAAC9B,WAAW,CAAA,IAAK;QAC9C,IAAIgB,YAAY,GAAGY,OAAOG,GAAG,CAAC/B,QAAQgB;aACjCY,OAAOI,MAAM,CAAChC;IACrB;IAEA,IAAIiC,QAAiC;IACrC,KAAK,MAAM,CAACjC,QAAQ0B,MAAM,IAAIE,OAAQ;QACpC,IAAIF,SAAS,GAAG;QAChB,IAAIO,UAAU,QAAQ9D,eAAe,CAAC6B,OAAO,GAAG7B,eAAe,CAAC8D,MAAM,EAAEA,QAAQjC;IAClF;IACA,OAAOiC;AACT;AAEA,OAAO,SAASzB,mBAAmBrB,WAAmB;IACpD,OAAOA,eAAetB,2BAClBqE,OAAO/C,eACPrB,qBAAqBqE,MAAM,CAAChD;AAClC;AAEA,SAAS4B,QAAQlC,IAAqB,EAAEmB,MAAwB;IAC9D,OAAOnB,KAAKyC,YAAY,CAACc,IAAI,CAAC,CAACC,QAAUA,MAAMrC,MAAM,KAAKA,SAAS0B,SAAS;AAC9E;AAEA,SAASzB,cAAcF,GAA0B;IAC/C,OAAOtC,uBAAuB;QAC5B6E,MAAMvC,IAAIuC,IAAI;QACdtC,QAAQD,IAAIC,MAAM;QAClBuC,gBAAgBxC,IAAIwC,cAAc;QAClCC,iBAAiBzC,IAAIyC,eAAe;QACpCC,eAAe,EAAE;IACnB;AACF"}
@@ -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,EAKzB,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,+BA0FA"}
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"}
@@ -10,7 +10,7 @@ import { useWorkflowRunDurationAccessibleLabel, WorkflowRunDurationLabel } from
10
10
  import { formatWorkflowRunNumberLabel, WorkflowRunNumberLabel } from '#components/workflow-run-number-label.js';
11
11
  import { getWorkflowStatusVisual } from '#components/workflow-status/status-visuals.js';
12
12
  import { WorkflowStatusIcon } from '#components/workflow-status/workflow-status-icon.js';
13
- import { workflowRunActor, workflowRunBranchLabel, workflowRunCommitLabel, workflowRunListItemDisplay } from '#core/workflow-run.js';
13
+ import { workflowRunActor, workflowRunBranchLabel, workflowRunCommitLabel } from '#core/workflow-run.js';
14
14
  import { withoutWorkflowRunSelectionSearch } from '#core/workflow-run-url-state.js';
15
15
  import { JobStatusStrip, jobStatusSummary } from './job-status-strip.js';
16
16
  export function WorkflowRunRowList({ runs, workspaceSlug, projectSlug }) {
@@ -40,9 +40,11 @@ export function WorkflowRunRowList({ runs, workspaceSlug, projectSlug }) {
40
40
  * column capped at 1120px with 24px of page padding on each side: a 1280px viewport leaves
41
41
  * the row 1072px, and a 1024px viewport leaves it 976px.
42
42
  */ export function WorkflowRunRow({ run, workspaceSlug, projectSlug }) {
43
- const display = workflowRunListItemDisplay(run);
44
- const durationLabel = useWorkflowRunDurationAccessibleLabel(display.duration);
45
- const statusLabel = getWorkflowStatusVisual(display.status).label;
43
+ const duration = run.runAttempt.displayDuration;
44
+ const hasStarted = run.jobs.hasStartedJobExecution;
45
+ const durationLabel = useWorkflowRunDurationAccessibleLabel(duration, hasStarted);
46
+ const status = run.runAttempt.status;
47
+ const statusLabel = getWorkflowStatusVisual(status).label;
46
48
  const runNumberLabel = formatWorkflowRunNumberLabel(run);
47
49
  const branch = workflowRunBranchLabel(run);
48
50
  const commit = workflowRunCommitLabel(run);
@@ -50,7 +52,7 @@ export function WorkflowRunRowList({ runs, workspaceSlug, projectSlug }) {
50
52
  const body = /*#__PURE__*/ _jsxs(_Fragment, {
51
53
  children: [
52
54
  /*#__PURE__*/ _jsx(WorkflowStatusIcon, {
53
- status: display.status,
55
+ status: status,
54
56
  size: 14,
55
57
  className: "shrink-0"
56
58
  }),
@@ -103,7 +105,8 @@ export function WorkflowRunRowList({ runs, workspaceSlug, projectSlug }) {
103
105
  /*#__PURE__*/ _jsx("span", {
104
106
  className: "flex w-64 justify-end",
105
107
  children: /*#__PURE__*/ _jsx(WorkflowRunDurationLabel, {
106
- duration: display.duration
108
+ duration: duration,
109
+ hasStarted: hasStarted
107
110
  })
108
111
  }),
109
112
  /*#__PURE__*/ _jsx(Code, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/workflow-run-list/workflow-run-row.tsx"],"sourcesContent":["import {TriggerSourceIcon} from '@shipfox/client-triggers';\nimport {Icon, type IconName} from '@shipfox/react-ui/icon';\nimport {RelativeTime} from '@shipfox/react-ui/relative-time';\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 {Link} from '@tanstack/react-router';\nimport {\n useWorkflowRunDurationAccessibleLabel,\n WorkflowRunDurationLabel,\n} from '#components/workflow-run-duration-label.js';\nimport {\n formatWorkflowRunNumberLabel,\n WorkflowRunNumberLabel,\n} from '#components/workflow-run-number-label.js';\nimport {getWorkflowStatusVisual} from '#components/workflow-status/status-visuals.js';\nimport {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';\nimport {\n type WorkflowRunListItem,\n workflowRunActor,\n workflowRunBranchLabel,\n workflowRunCommitLabel,\n workflowRunListItemDisplay,\n} from '#core/workflow-run.js';\nimport {withoutWorkflowRunSelectionSearch} from '#core/workflow-run-url-state.js';\nimport {JobStatusStrip, jobStatusSummary} from './job-status-strip.js';\n\nexport function WorkflowRunRowList({\n runs,\n workspaceSlug,\n projectSlug,\n}: {\n runs: WorkflowRunListItem[];\n workspaceSlug?: string | undefined;\n projectSlug?: string | undefined;\n}) {\n return (\n // A container, not the viewport, drives the row's breakpoints. The list is capped at\n // 1120px inside a wider page, so what a row can afford is its own width; keying off the\n // viewport would keep the job strip hidden on a wide screen or crush it on a narrow one.\n <ul className=\"@container divide-y divide-border-neutral-base border-t border-border-neutral-base\">\n {runs.map((run) => (\n <li key={run.id}>\n <WorkflowRunRow run={run} workspaceSlug={workspaceSlug} projectSlug={projectSlug} />\n </li>\n ))}\n </ul>\n );\n}\n\n/**\n * One run at full width.\n *\n * A single 44px line once the list is at least 976px wide, reflowing to two lines below that:\n * the identity and the numerics keep their places on line one, and the provenance metadata\n * drops beneath. The numeric columns are fixed-width so duration and time form real columns\n * down the list rather than tracking each row's name length.\n *\n * The thresholds are the container widths the spec's viewport breakpoints produce, given a\n * column capped at 1120px with 24px of page padding on each side: a 1280px viewport leaves\n * the row 1072px, and a 1024px viewport leaves it 976px.\n */\nexport function WorkflowRunRow({\n run,\n workspaceSlug,\n projectSlug,\n}: {\n run: WorkflowRunListItem;\n workspaceSlug?: string | undefined;\n projectSlug?: string | undefined;\n}) {\n const display = workflowRunListItemDisplay(run);\n const durationLabel = useWorkflowRunDurationAccessibleLabel(display.duration);\n const statusLabel = getWorkflowStatusVisual(display.status).label;\n const runNumberLabel = formatWorkflowRunNumberLabel(run);\n const branch = workflowRunBranchLabel(run);\n const commit = workflowRunCommitLabel(run);\n const actor = workflowRunActor(run);\n\n const body = (\n <>\n <WorkflowStatusIcon status={display.status} size={14} className=\"shrink-0\" />\n\n <div className=\"flex min-w-0 flex-1 flex-col gap-tight @min-[976px]:flex-row @min-[976px]:items-center @min-[976px]:gap-cluster\">\n <span className=\"flex min-w-0 items-center gap-inline @min-[976px]:flex-1\">\n <Code variant=\"label\" bold className=\"truncate text-foreground-neutral-base\">\n {run.name}\n </Code>\n {run.number !== null ? <WorkflowRunNumberLabel run={run} /> : null}\n </span>\n\n <span className=\"flex min-w-0 flex-wrap items-center gap-inline text-foreground-neutral-subtle @min-[976px]:flex-nowrap @min-[976px]:shrink-0\">\n {run.triggerDisplayLabel ? <TriggerLabel run={run} /> : null}\n {branch ? <BranchLabel branch={branch} isPullRequest={branch.startsWith('#')} /> : null}\n {commit ? <CommitLabel commit={commit} /> : null}\n {actor ? <ActorLabel actor={actor} /> : null}\n </span>\n </div>\n\n <span className=\"flex shrink-0 items-center gap-cluster\">\n {/* Fixed at the strip's worst case (see MAX_VISIBLE_JOBS) so the overflow count cannot\n paint over the duration, and kept even when a run has no jobs planned yet, so the\n numerics stay in line down the list instead of stepping in and out. */}\n <span className=\"hidden w-160 @min-[1040px]:flex\">\n <JobStatusStrip jobs={run.jobs} />\n </span>\n <span className=\"flex w-64 justify-end\">\n <WorkflowRunDurationLabel duration={display.duration} />\n </span>\n <Code\n variant=\"label\"\n className=\"w-64 shrink-0 text-right tabular-nums text-foreground-neutral-muted\"\n >\n <RelativeTime value={run.createdAt} />\n </Code>\n </span>\n </>\n );\n\n // Rows run edge to edge inside the list's scroll container, which would clip the standard\n // outset focus ring, so this one is inset per the design system's focus-ring rule.\n const rowClassName =\n 'flex w-full min-w-0 items-center gap-inline px-row py-row text-left transition-colors hover:bg-background-components-hover focus-visible:shadow-[inset_0_0_0_2px_var(--color-primary-500)] focus-visible:outline-none @min-[976px]:h-44 @min-[976px]:py-0';\n\n // Optimistic manual runs (temp-<uuid>) have no detail page until the canonical row\n // replaces them on the next poll, so they render non-interactively instead of as a link\n // that would navigate to a workflow run id the detail route rejects.\n if (run.isTemporary || !workspaceSlug || !projectSlug) {\n return <div className={rowClassName}>{body}</div>;\n }\n\n return (\n <Link\n to=\"/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId\"\n params={{workspaceSlug, projectSlug, workflowRunId: run.id}}\n search={\n ((previous: Record<string, unknown>) =>\n withoutWorkflowRunSelectionSearch(previous)) as never\n }\n // An aria-label is the link's whole accessible name, so anything inside the row that\n // carries its own label (the status glyph, the job strip) goes unspoken unless it is\n // curated in here. The job breakdown is the reason this list is worth its width, so it\n // belongs in what a tabbing user hears, not only in browse mode.\n aria-label={[\n run.name,\n runNumberLabel,\n statusLabel,\n durationLabel,\n run.triggerLabel,\n branch ? `branch ${branch}` : undefined,\n actor ? `by ${actor}` : undefined,\n run.jobs.total > 0 ? jobStatusSummary(run.jobs) : undefined,\n ]\n .filter((part): part is string => Boolean(part))\n .join(', ')}\n className={rowClassName}\n >\n {body}\n </Link>\n );\n}\n\nfunction TriggerLabel({run}: {run: WorkflowRunListItem}) {\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <span className=\"flex min-w-0 max-w-[140px] items-center gap-tight outline-none\">\n <TriggerSourceIcon\n provider={run.triggerProvider}\n source={run.triggerSource}\n aria-hidden=\"true\"\n className=\"size-12 shrink-0 text-foreground-neutral-muted\"\n />\n <Text as=\"span\" size=\"xs\" className=\"min-w-0 truncate\">\n {run.triggerDisplayLabel}\n </Text>\n </span>\n </TooltipTrigger>\n <TooltipContent>\n <Text as=\"span\" size=\"xs\" className=\"block max-w-[360px] break-words\">\n {run.triggerLabel}\n </Text>\n </TooltipContent>\n </Tooltip>\n );\n}\n\nfunction BranchLabel({branch, isPullRequest}: {branch: string; isPullRequest: boolean}) {\n return (\n <MetadataChip\n icon={isPullRequest ? 'gitPullRequestLine' : 'gitBranchLine'}\n title={isPullRequest ? `Pull request ${branch}` : `Branch ${branch}`}\n className=\"max-w-[180px]\"\n >\n {branch}\n </MetadataChip>\n );\n}\n\nfunction CommitLabel({commit}: {commit: string}) {\n return (\n <MetadataChip icon=\"gitCommitLine\" title={`Commit ${commit}`}>\n {commit}\n </MetadataChip>\n );\n}\n\nfunction ActorLabel({actor}: {actor: string}) {\n return (\n <MetadataChip\n icon=\"userLine\"\n title={`Triggered by ${actor}`}\n // Dropped once the row is two lines, where the provenance line has to stay readable on\n // a phone.\n className=\"hidden max-w-[120px] @min-[976px]:flex\"\n >\n {actor}\n </MetadataChip>\n );\n}\n\n/**\n * A ref, SHA, or handle: monospace because it is content a user copies or pattern-matches,\n * with the icon carrying the kind so the value itself never needs a written label.\n */\nfunction MetadataChip({\n icon,\n title,\n className,\n children,\n}: {\n icon: IconName;\n title: string;\n className?: string | undefined;\n children: string;\n}) {\n return (\n <span className={cn('flex min-w-0 shrink-0 items-center gap-tight', className)} title={title}>\n <Icon name={icon} className=\"size-12 shrink-0 text-foreground-neutral-muted\" aria-hidden />\n <Code as=\"span\" variant=\"label\" className=\"min-w-0 truncate\">\n {children}\n </Code>\n </span>\n );\n}\n"],"names":["TriggerSourceIcon","Icon","RelativeTime","Tooltip","TooltipContent","TooltipTrigger","Code","Text","cn","Link","useWorkflowRunDurationAccessibleLabel","WorkflowRunDurationLabel","formatWorkflowRunNumberLabel","WorkflowRunNumberLabel","getWorkflowStatusVisual","WorkflowStatusIcon","workflowRunActor","workflowRunBranchLabel","workflowRunCommitLabel","workflowRunListItemDisplay","withoutWorkflowRunSelectionSearch","JobStatusStrip","jobStatusSummary","WorkflowRunRowList","runs","workspaceSlug","projectSlug","ul","className","map","run","li","WorkflowRunRow","id","display","durationLabel","duration","statusLabel","status","label","runNumberLabel","branch","commit","actor","body","size","div","span","variant","bold","name","number","triggerDisplayLabel","TriggerLabel","BranchLabel","isPullRequest","startsWith","CommitLabel","ActorLabel","jobs","value","createdAt","rowClassName","isTemporary","to","params","workflowRunId","search","previous","aria-label","triggerLabel","undefined","total","filter","part","Boolean","join","asChild","provider","triggerProvider","source","triggerSource","aria-hidden","as","MetadataChip","icon","title","children"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/components/workflow-run-list/workflow-run-row.tsx"],"sourcesContent":["import {TriggerSourceIcon} from '@shipfox/client-triggers';\nimport {Icon, type IconName} from '@shipfox/react-ui/icon';\nimport {RelativeTime} from '@shipfox/react-ui/relative-time';\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 {Link} from '@tanstack/react-router';\nimport {\n useWorkflowRunDurationAccessibleLabel,\n WorkflowRunDurationLabel,\n} from '#components/workflow-run-duration-label.js';\nimport {\n formatWorkflowRunNumberLabel,\n WorkflowRunNumberLabel,\n} from '#components/workflow-run-number-label.js';\nimport {getWorkflowStatusVisual} from '#components/workflow-status/status-visuals.js';\nimport {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';\nimport {\n type WorkflowRunListItem,\n workflowRunActor,\n workflowRunBranchLabel,\n workflowRunCommitLabel,\n} from '#core/workflow-run.js';\nimport {withoutWorkflowRunSelectionSearch} from '#core/workflow-run-url-state.js';\nimport {JobStatusStrip, jobStatusSummary} from './job-status-strip.js';\n\nexport function WorkflowRunRowList({\n runs,\n workspaceSlug,\n projectSlug,\n}: {\n runs: WorkflowRunListItem[];\n workspaceSlug?: string | undefined;\n projectSlug?: string | undefined;\n}) {\n return (\n // A container, not the viewport, drives the row's breakpoints. The list is capped at\n // 1120px inside a wider page, so what a row can afford is its own width; keying off the\n // viewport would keep the job strip hidden on a wide screen or crush it on a narrow one.\n <ul className=\"@container divide-y divide-border-neutral-base border-t border-border-neutral-base\">\n {runs.map((run) => (\n <li key={run.id}>\n <WorkflowRunRow run={run} workspaceSlug={workspaceSlug} projectSlug={projectSlug} />\n </li>\n ))}\n </ul>\n );\n}\n\n/**\n * One run at full width.\n *\n * A single 44px line once the list is at least 976px wide, reflowing to two lines below that:\n * the identity and the numerics keep their places on line one, and the provenance metadata\n * drops beneath. The numeric columns are fixed-width so duration and time form real columns\n * down the list rather than tracking each row's name length.\n *\n * The thresholds are the container widths the spec's viewport breakpoints produce, given a\n * column capped at 1120px with 24px of page padding on each side: a 1280px viewport leaves\n * the row 1072px, and a 1024px viewport leaves it 976px.\n */\nexport function WorkflowRunRow({\n run,\n workspaceSlug,\n projectSlug,\n}: {\n run: WorkflowRunListItem;\n workspaceSlug?: string | undefined;\n projectSlug?: string | undefined;\n}) {\n const duration = run.runAttempt.displayDuration;\n const hasStarted = run.jobs.hasStartedJobExecution;\n const durationLabel = useWorkflowRunDurationAccessibleLabel(duration, hasStarted);\n const status = run.runAttempt.status;\n const statusLabel = getWorkflowStatusVisual(status).label;\n const runNumberLabel = formatWorkflowRunNumberLabel(run);\n const branch = workflowRunBranchLabel(run);\n const commit = workflowRunCommitLabel(run);\n const actor = workflowRunActor(run);\n\n const body = (\n <>\n <WorkflowStatusIcon status={status} size={14} className=\"shrink-0\" />\n\n <div className=\"flex min-w-0 flex-1 flex-col gap-tight @min-[976px]:flex-row @min-[976px]:items-center @min-[976px]:gap-cluster\">\n <span className=\"flex min-w-0 items-center gap-inline @min-[976px]:flex-1\">\n <Code variant=\"label\" bold className=\"truncate text-foreground-neutral-base\">\n {run.name}\n </Code>\n {run.number !== null ? <WorkflowRunNumberLabel run={run} /> : null}\n </span>\n\n <span className=\"flex min-w-0 flex-wrap items-center gap-inline text-foreground-neutral-subtle @min-[976px]:flex-nowrap @min-[976px]:shrink-0\">\n {run.triggerDisplayLabel ? <TriggerLabel run={run} /> : null}\n {branch ? <BranchLabel branch={branch} isPullRequest={branch.startsWith('#')} /> : null}\n {commit ? <CommitLabel commit={commit} /> : null}\n {actor ? <ActorLabel actor={actor} /> : null}\n </span>\n </div>\n\n <span className=\"flex shrink-0 items-center gap-cluster\">\n {/* Fixed at the strip's worst case (see MAX_VISIBLE_JOBS) so the overflow count cannot\n paint over the duration, and kept even when a run has no jobs planned yet, so the\n numerics stay in line down the list instead of stepping in and out. */}\n <span className=\"hidden w-160 @min-[1040px]:flex\">\n <JobStatusStrip jobs={run.jobs} />\n </span>\n <span className=\"flex w-64 justify-end\">\n <WorkflowRunDurationLabel duration={duration} hasStarted={hasStarted} />\n </span>\n <Code\n variant=\"label\"\n className=\"w-64 shrink-0 text-right tabular-nums text-foreground-neutral-muted\"\n >\n <RelativeTime value={run.createdAt} />\n </Code>\n </span>\n </>\n );\n\n // Rows run edge to edge inside the list's scroll container, which would clip the standard\n // outset focus ring, so this one is inset per the design system's focus-ring rule.\n const rowClassName =\n 'flex w-full min-w-0 items-center gap-inline px-row py-row text-left transition-colors hover:bg-background-components-hover focus-visible:shadow-[inset_0_0_0_2px_var(--color-primary-500)] focus-visible:outline-none @min-[976px]:h-44 @min-[976px]:py-0';\n\n // Optimistic manual runs (temp-<uuid>) have no detail page until the canonical row\n // replaces them on the next poll, so they render non-interactively instead of as a link\n // that would navigate to a workflow run id the detail route rejects.\n if (run.isTemporary || !workspaceSlug || !projectSlug) {\n return <div className={rowClassName}>{body}</div>;\n }\n\n return (\n <Link\n to=\"/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId\"\n params={{workspaceSlug, projectSlug, workflowRunId: run.id}}\n search={\n ((previous: Record<string, unknown>) =>\n withoutWorkflowRunSelectionSearch(previous)) as never\n }\n // An aria-label is the link's whole accessible name, so anything inside the row that\n // carries its own label (the status glyph, the job strip) goes unspoken unless it is\n // curated in here. The job breakdown is the reason this list is worth its width, so it\n // belongs in what a tabbing user hears, not only in browse mode.\n aria-label={[\n run.name,\n runNumberLabel,\n statusLabel,\n durationLabel,\n run.triggerLabel,\n branch ? `branch ${branch}` : undefined,\n actor ? `by ${actor}` : undefined,\n run.jobs.total > 0 ? jobStatusSummary(run.jobs) : undefined,\n ]\n .filter((part): part is string => Boolean(part))\n .join(', ')}\n className={rowClassName}\n >\n {body}\n </Link>\n );\n}\n\nfunction TriggerLabel({run}: {run: WorkflowRunListItem}) {\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <span className=\"flex min-w-0 max-w-[140px] items-center gap-tight outline-none\">\n <TriggerSourceIcon\n provider={run.triggerProvider}\n source={run.triggerSource}\n aria-hidden=\"true\"\n className=\"size-12 shrink-0 text-foreground-neutral-muted\"\n />\n <Text as=\"span\" size=\"xs\" className=\"min-w-0 truncate\">\n {run.triggerDisplayLabel}\n </Text>\n </span>\n </TooltipTrigger>\n <TooltipContent>\n <Text as=\"span\" size=\"xs\" className=\"block max-w-[360px] break-words\">\n {run.triggerLabel}\n </Text>\n </TooltipContent>\n </Tooltip>\n );\n}\n\nfunction BranchLabel({branch, isPullRequest}: {branch: string; isPullRequest: boolean}) {\n return (\n <MetadataChip\n icon={isPullRequest ? 'gitPullRequestLine' : 'gitBranchLine'}\n title={isPullRequest ? `Pull request ${branch}` : `Branch ${branch}`}\n className=\"max-w-[180px]\"\n >\n {branch}\n </MetadataChip>\n );\n}\n\nfunction CommitLabel({commit}: {commit: string}) {\n return (\n <MetadataChip icon=\"gitCommitLine\" title={`Commit ${commit}`}>\n {commit}\n </MetadataChip>\n );\n}\n\nfunction ActorLabel({actor}: {actor: string}) {\n return (\n <MetadataChip\n icon=\"userLine\"\n title={`Triggered by ${actor}`}\n // Dropped once the row is two lines, where the provenance line has to stay readable on\n // a phone.\n className=\"hidden max-w-[120px] @min-[976px]:flex\"\n >\n {actor}\n </MetadataChip>\n );\n}\n\n/**\n * A ref, SHA, or handle: monospace because it is content a user copies or pattern-matches,\n * with the icon carrying the kind so the value itself never needs a written label.\n */\nfunction MetadataChip({\n icon,\n title,\n className,\n children,\n}: {\n icon: IconName;\n title: string;\n className?: string | undefined;\n children: string;\n}) {\n return (\n <span className={cn('flex min-w-0 shrink-0 items-center gap-tight', className)} title={title}>\n <Icon name={icon} className=\"size-12 shrink-0 text-foreground-neutral-muted\" aria-hidden />\n <Code as=\"span\" variant=\"label\" className=\"min-w-0 truncate\">\n {children}\n </Code>\n </span>\n );\n}\n"],"names":["TriggerSourceIcon","Icon","RelativeTime","Tooltip","TooltipContent","TooltipTrigger","Code","Text","cn","Link","useWorkflowRunDurationAccessibleLabel","WorkflowRunDurationLabel","formatWorkflowRunNumberLabel","WorkflowRunNumberLabel","getWorkflowStatusVisual","WorkflowStatusIcon","workflowRunActor","workflowRunBranchLabel","workflowRunCommitLabel","withoutWorkflowRunSelectionSearch","JobStatusStrip","jobStatusSummary","WorkflowRunRowList","runs","workspaceSlug","projectSlug","ul","className","map","run","li","WorkflowRunRow","id","duration","runAttempt","displayDuration","hasStarted","jobs","hasStartedJobExecution","durationLabel","status","statusLabel","label","runNumberLabel","branch","commit","actor","body","size","div","span","variant","bold","name","number","triggerDisplayLabel","TriggerLabel","BranchLabel","isPullRequest","startsWith","CommitLabel","ActorLabel","value","createdAt","rowClassName","isTemporary","to","params","workflowRunId","search","previous","aria-label","triggerLabel","undefined","total","filter","part","Boolean","join","asChild","provider","triggerProvider","source","triggerSource","aria-hidden","as","MetadataChip","icon","title","children"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-run-summary.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-summary/workflow-run-summary.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAIL,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAG1B,MAAM,uBAAuB,CAAC;AAe/B,MAAM,WAAW,uBAAuB;IACtC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,GAAG,EAAE,iBAAiB,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;IACpC,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACnC,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,oBAAoB,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAC7D,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAED,wBAAgB,kBAAkB,CAAC,EACjC,aAAa,EACb,WAAW,EACX,GAAG,EACH,UAAkB,EAClB,QAAQ,EACR,YAAoB,EACpB,OAAO,EACP,aAAa,GACd,EAAE,uBAAuB,+BAqJzB"}
1
+ {"version":3,"file":"workflow-run-summary.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-summary/workflow-run-summary.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAIL,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAC1B,MAAM,uBAAuB,CAAC;AAa/B,MAAM,WAAW,uBAAuB;IACtC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,GAAG,EAAE,iBAAiB,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;IACpC,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACnC,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,oBAAoB,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAC7D,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAED,wBAAgB,kBAAkB,CAAC,EACjC,aAAa,EACb,WAAW,EACX,GAAG,EACH,UAAkB,EAClB,QAAQ,EACR,YAAoB,EACpB,OAAO,EACP,aAAa,GACd,EAAE,uBAAuB,+BAoIzB"}
@@ -8,22 +8,18 @@ import { useIsTextTruncated } from '@shipfox/react-ui/hooks';
8
8
  import { RelativeTime } from '@shipfox/react-ui/relative-time';
9
9
  import { TimeTickerProvider } from '@shipfox/react-ui/time-ticker';
10
10
  import { Tooltip, TooltipContent, TooltipTrigger } from '@shipfox/react-ui/tooltip';
11
- import { Code, Header, Text } from '@shipfox/react-ui/typography';
11
+ import { Header, Text } from '@shipfox/react-ui/typography';
12
12
  import { useId } from 'react';
13
13
  import { WorkflowRunNumberLabel } from '#components/workflow-run-number-label.js';
14
- import { isWorkflowRunTerminal, WORKFLOW_RUN_STATUSES, workflowRunBlockingJob, workflowRunDetailDisplay } from '#core/workflow-run.js';
14
+ import { isWorkflowRunTerminal, WORKFLOW_RUN_STATUSES } from '#core/workflow-run.js';
15
15
  import { WorkflowRunDurationLabel } from '../workflow-run-duration-label.js';
16
16
  import { getWorkflowStatusVisual } from '../workflow-status/status-visuals.js';
17
17
  import { WorkflowRunAttemptSwitcher } from './workflow-run-attempt-switcher.js';
18
- const STATUS_BADGE_LABEL_WIDTH_CH = Math.max(...[
19
- ...WORKFLOW_RUN_STATUSES,
20
- 'queued'
21
- ].map((status)=>getWorkflowStatusVisual(status).label.length));
18
+ const STATUS_BADGE_LABEL_WIDTH_CH = Math.max(...WORKFLOW_RUN_STATUSES.map((status)=>getWorkflowStatusVisual(status).label.length));
22
19
  const RERUN_BUTTON_SURFACE_CLASS_NAME = 'bg-background-neutral-base hover:bg-background-neutral-hover active:bg-background-neutral-pressed';
23
20
  export function WorkflowRunSummary({ workspaceSlug, projectSlug, run, cancelling = false, onCancel, rerunPending = false, onRerun, latestAttempt }) {
24
21
  const headingId = useId();
25
- const display = workflowRunDetailDisplay(run);
26
- const status = getWorkflowStatusVisual(display.status);
22
+ const status = getWorkflowStatusVisual(run.runAttempt.status);
27
23
  const action = workflowRunActionForRun(run);
28
24
  const hasAction = canRenderWorkflowRunAction(action, onCancel, onRerun);
29
25
  const attemptSwitcher = latestAttempt && latestAttempt > 1 && workspaceSlug && projectSlug ? {
@@ -31,10 +27,8 @@ export function WorkflowRunSummary({ workspaceSlug, projectSlug, run, cancelling
31
27
  projectSlug,
32
28
  latestAttempt
33
29
  } : null;
34
- const displayDuration = display.duration;
35
- // Named only while the run is waiting and has more than one job: with a single job the rail
36
- // and the graph already say which one it is.
37
- const blockingJob = display.status === 'queued' && run.jobs.length > 1 ? workflowRunBlockingJob(run.jobs) : null;
30
+ const displayDuration = run.runAttempt.displayDuration;
31
+ const hasStarted = run.hasStartedJobExecution;
38
32
  const { ref: headingTextRef, isTruncated: isHeadingTruncated } = useIsTextTruncated(run.name);
39
33
  return /*#__PURE__*/ _jsx(TimeTickerProvider, {
40
34
  intervalMs: 1000,
@@ -169,31 +163,10 @@ export function WorkflowRunSummary({ workspaceSlug, projectSlug, run, cancelling
169
163
  /*#__PURE__*/ _jsx(MetadataSeparator, {}),
170
164
  /*#__PURE__*/ _jsx(WorkflowRunDurationLabel, {
171
165
  duration: displayDuration,
166
+ hasStarted: hasStarted,
172
167
  className: "text-foreground-neutral-subtle"
173
168
  })
174
169
  ]
175
- }) : null,
176
- blockingJob ? /*#__PURE__*/ _jsxs(_Fragment, {
177
- children: [
178
- /*#__PURE__*/ _jsx(MetadataSeparator, {}),
179
- /*#__PURE__*/ _jsxs("span", {
180
- className: "flex min-w-0 items-center gap-tight",
181
- children: [
182
- /*#__PURE__*/ _jsx(Text, {
183
- as: "span",
184
- size: "xs",
185
- className: "shrink-0",
186
- children: "waiting on"
187
- }),
188
- /*#__PURE__*/ _jsx(Code, {
189
- as: "span",
190
- variant: "label",
191
- className: "min-w-0 truncate",
192
- children: blockingJob.displayName
193
- })
194
- ]
195
- })
196
- ]
197
170
  }) : null
198
171
  ]
199
172
  })