@shipfox/client-workflows 15.0.0 → 16.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 (91) hide show
  1. package/.storybook/preview.css +4 -0
  2. package/.turbo/turbo-build.log +1 -1
  3. package/CHANGELOG.md +21 -0
  4. package/dist/components/job-detail/job-detail-header.d.ts +8 -1
  5. package/dist/components/job-detail/job-detail-header.d.ts.map +1 -1
  6. package/dist/components/job-detail/job-detail-header.js +25 -12
  7. package/dist/components/job-detail/job-detail-header.js.map +1 -1
  8. package/dist/components/job-detail/job-detail-view.d.ts.map +1 -1
  9. package/dist/components/job-detail/job-detail-view.js +17 -2
  10. package/dist/components/job-detail/job-detail-view.js.map +1 -1
  11. package/dist/components/workflow-run-summary/workflow-run-summary.js +1 -0
  12. package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -1
  13. package/dist/components/workflow-run-tabs/index.d.ts +3 -0
  14. package/dist/components/workflow-run-tabs/index.d.ts.map +1 -1
  15. package/dist/components/workflow-run-tabs/index.js +3 -0
  16. package/dist/components/workflow-run-tabs/index.js.map +1 -1
  17. package/dist/components/workflow-run-tabs/run-annotation-count-chip.d.ts +16 -0
  18. package/dist/components/workflow-run-tabs/run-annotation-count-chip.d.ts.map +1 -0
  19. package/dist/components/workflow-run-tabs/run-annotation-count-chip.js +50 -0
  20. package/dist/components/workflow-run-tabs/run-annotation-count-chip.js.map +1 -0
  21. package/dist/components/workflow-run-tabs/run-annotation-item.d.ts +20 -0
  22. package/dist/components/workflow-run-tabs/run-annotation-item.d.ts.map +1 -0
  23. package/dist/components/workflow-run-tabs/run-annotation-item.js +102 -0
  24. package/dist/components/workflow-run-tabs/run-annotation-item.js.map +1 -0
  25. package/dist/components/workflow-run-tabs/run-annotation-list.d.ts +32 -0
  26. package/dist/components/workflow-run-tabs/run-annotation-list.d.ts.map +1 -0
  27. package/dist/components/workflow-run-tabs/run-annotation-list.js +202 -0
  28. package/dist/components/workflow-run-tabs/run-annotation-list.js.map +1 -0
  29. package/dist/components/workflow-run-tabs/run-annotation-summary-line.d.ts +1 -1
  30. package/dist/components/workflow-run-tabs/run-annotation-summary-line.d.ts.map +1 -1
  31. package/dist/components/workflow-run-tabs/run-annotation-summary-line.js +87 -22
  32. package/dist/components/workflow-run-tabs/run-annotation-summary-line.js.map +1 -1
  33. package/dist/components/workflow-run-tabs/severity-visuals.d.ts +20 -0
  34. package/dist/components/workflow-run-tabs/severity-visuals.d.ts.map +1 -0
  35. package/dist/components/workflow-run-tabs/severity-visuals.js +31 -0
  36. package/dist/components/workflow-run-tabs/severity-visuals.js.map +1 -0
  37. package/dist/components/workflow-run-view/run-workspace-nav.d.ts +1 -1
  38. package/dist/components/workflow-run-view/run-workspace-nav.d.ts.map +1 -1
  39. package/dist/components/workflow-run-view/run-workspace-nav.js +9 -3
  40. package/dist/components/workflow-run-view/run-workspace-nav.js.map +1 -1
  41. package/dist/components/workflow-run-view/workflow-run-view.d.ts +1 -3
  42. package/dist/components/workflow-run-view/workflow-run-view.d.ts.map +1 -1
  43. package/dist/components/workflow-run-view/workflow-run-view.js +150 -52
  44. package/dist/components/workflow-run-view/workflow-run-view.js.map +1 -1
  45. package/dist/core/run-annotation.d.ts +73 -0
  46. package/dist/core/run-annotation.d.ts.map +1 -0
  47. package/dist/core/run-annotation.js +125 -0
  48. package/dist/core/run-annotation.js.map +1 -0
  49. package/dist/hooks/api/run-annotation-mapper.d.ts +10 -0
  50. package/dist/hooks/api/run-annotation-mapper.d.ts.map +1 -0
  51. package/dist/hooks/api/run-annotation-mapper.js +22 -0
  52. package/dist/hooks/api/run-annotation-mapper.js.map +1 -0
  53. package/dist/hooks/api/run-annotations.d.ts +25 -0
  54. package/dist/hooks/api/run-annotations.d.ts.map +1 -0
  55. package/dist/hooks/api/run-annotations.js +78 -0
  56. package/dist/hooks/api/run-annotations.js.map +1 -0
  57. package/dist/routes/inputs.d.ts +7 -2
  58. package/dist/routes/inputs.d.ts.map +1 -1
  59. package/dist/routes/inputs.js +5 -6
  60. package/dist/routes/inputs.js.map +1 -1
  61. package/dist/tsconfig.test.tsbuildinfo +1 -1
  62. package/package.json +10 -9
  63. package/src/components/job-detail/job-detail-header.test.tsx +3 -0
  64. package/src/components/job-detail/job-detail-header.tsx +30 -5
  65. package/src/components/job-detail/job-detail-view.stories.tsx +5 -0
  66. package/src/components/job-detail/job-detail-view.tsx +18 -1
  67. package/src/components/workflow-run-summary/workflow-run-summary.tsx +1 -1
  68. package/src/components/workflow-run-tabs/index.ts +3 -0
  69. package/src/components/workflow-run-tabs/run-annotation-count-chip.tsx +63 -0
  70. package/src/components/workflow-run-tabs/run-annotation-item.tsx +123 -0
  71. package/src/components/workflow-run-tabs/run-annotation-list.tsx +258 -0
  72. package/src/components/workflow-run-tabs/run-annotation-summary-line.test.tsx +77 -3
  73. package/src/components/workflow-run-tabs/run-annotation-summary-line.tsx +92 -17
  74. package/src/components/workflow-run-tabs/run-annotations.stories.tsx +293 -0
  75. package/src/components/workflow-run-tabs/severity-visuals.ts +37 -0
  76. package/src/components/workflow-run-view/run-workspace-nav.test.tsx +44 -0
  77. package/src/components/workflow-run-view/run-workspace-nav.tsx +15 -2
  78. package/src/components/workflow-run-view/workflow-run-view.test.tsx +246 -20
  79. package/src/components/workflow-run-view/workflow-run-view.tsx +148 -30
  80. package/src/core/run-annotation.test.ts +315 -0
  81. package/src/core/run-annotation.ts +238 -0
  82. package/src/hooks/api/run-annotation-mapper.ts +30 -0
  83. package/src/hooks/api/run-annotations.ts +137 -0
  84. package/src/pages/workflow-job-detail-page.test.tsx +66 -0
  85. package/src/routes/inputs.ts +7 -2
  86. package/tsconfig.build.tsbuildinfo +1 -1
  87. package/dist/core/workflow-run-tabs.d.ts +0 -9
  88. package/dist/core/workflow-run-tabs.d.ts.map +0 -1
  89. package/dist/core/workflow-run-tabs.js +0 -3
  90. package/dist/core/workflow-run-tabs.js.map +0 -1
  91. package/src/core/workflow-run-tabs.ts +0 -8
@@ -0,0 +1,20 @@
1
+ import type { IconName } from '@shipfox/react-ui/icon';
2
+ import type { RunAnnotationSeverity } from '#core/run-annotation.js';
3
+ /**
4
+ * The glyph vocabulary for annotation severity, shared by every count surface.
5
+ *
6
+ * Deliberately the same marks the `Callout` uses on the cards themselves, so the glyph beside
7
+ * `1 error` in the summary is the glyph on the error card below it.
8
+ */
9
+ export declare const SEVERITY_ICON: Record<RunAnnotationSeverity, IconName>;
10
+ /**
11
+ * Severity tone lives in the glyph, never in the label.
12
+ *
13
+ * Coloring the text would make severity compete with the brand accent, which this system
14
+ * reserves for links, focus, and "you are here". Shape plus tone in a 12px mark distinguishes
15
+ * an error from a warning without either of them shouting.
16
+ */
17
+ export declare const SEVERITY_ICON_TONE: Record<RunAnnotationSeverity, string>;
18
+ /** Hairline tone for a bordered count chip, matching the glyph it sits beside. */
19
+ export declare const SEVERITY_CHIP_TONE: Record<RunAnnotationSeverity, string>;
20
+ //# sourceMappingURL=severity-visuals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"severity-visuals.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-tabs/severity-visuals.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,yBAAyB,CAAC;AAEnE;;;;;GAKG;AACH,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,qBAAqB,EAAE,QAAQ,CAKjE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,qBAAqB,EAAE,MAAM,CAKpE,CAAC;AAEF,kFAAkF;AAClF,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,qBAAqB,EAAE,MAAM,CAKpE,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The glyph vocabulary for annotation severity, shared by every count surface.
3
+ *
4
+ * Deliberately the same marks the `Callout` uses on the cards themselves, so the glyph beside
5
+ * `1 error` in the summary is the glyph on the error card below it.
6
+ */ export const SEVERITY_ICON = {
7
+ error: 'closeCircleFill',
8
+ warning: 'errorWarningFill',
9
+ info: 'info',
10
+ success: 'checkboxCircleFill'
11
+ };
12
+ /**
13
+ * Severity tone lives in the glyph, never in the label.
14
+ *
15
+ * Coloring the text would make severity compete with the brand accent, which this system
16
+ * reserves for links, focus, and "you are here". Shape plus tone in a 12px mark distinguishes
17
+ * an error from a warning without either of them shouting.
18
+ */ export const SEVERITY_ICON_TONE = {
19
+ error: 'text-tag-error-icon',
20
+ warning: 'text-tag-warning-icon',
21
+ info: 'text-tag-blue-icon',
22
+ success: 'text-tag-success-icon'
23
+ };
24
+ /** Hairline tone for a bordered count chip, matching the glyph it sits beside. */ export const SEVERITY_CHIP_TONE = {
25
+ error: 'border-tag-error-border text-tag-error-icon',
26
+ warning: 'border-tag-warning-border text-tag-warning-icon',
27
+ info: 'border-tag-blue-border text-tag-blue-icon',
28
+ success: 'border-tag-success-border text-tag-success-icon'
29
+ };
30
+
31
+ //# sourceMappingURL=severity-visuals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/workflow-run-tabs/severity-visuals.ts"],"sourcesContent":["import type {IconName} from '@shipfox/react-ui/icon';\nimport type {RunAnnotationSeverity} from '#core/run-annotation.js';\n\n/**\n * The glyph vocabulary for annotation severity, shared by every count surface.\n *\n * Deliberately the same marks the `Callout` uses on the cards themselves, so the glyph beside\n * `1 error` in the summary is the glyph on the error card below it.\n */\nexport const SEVERITY_ICON: Record<RunAnnotationSeverity, IconName> = {\n error: 'closeCircleFill',\n warning: 'errorWarningFill',\n info: 'info',\n success: 'checkboxCircleFill',\n};\n\n/**\n * Severity tone lives in the glyph, never in the label.\n *\n * Coloring the text would make severity compete with the brand accent, which this system\n * reserves for links, focus, and \"you are here\". Shape plus tone in a 12px mark distinguishes\n * an error from a warning without either of them shouting.\n */\nexport const SEVERITY_ICON_TONE: Record<RunAnnotationSeverity, string> = {\n error: 'text-tag-error-icon',\n warning: 'text-tag-warning-icon',\n info: 'text-tag-blue-icon',\n success: 'text-tag-success-icon',\n};\n\n/** Hairline tone for a bordered count chip, matching the glyph it sits beside. */\nexport const SEVERITY_CHIP_TONE: Record<RunAnnotationSeverity, string> = {\n error: 'border-tag-error-border text-tag-error-icon',\n warning: 'border-tag-warning-border text-tag-warning-icon',\n info: 'border-tag-blue-border text-tag-blue-icon',\n success: 'border-tag-success-border text-tag-success-icon',\n};\n"],"names":["SEVERITY_ICON","error","warning","info","success","SEVERITY_ICON_TONE","SEVERITY_CHIP_TONE"],"mappings":"AAGA;;;;;CAKC,GACD,OAAO,MAAMA,gBAAyD;IACpEC,OAAO;IACPC,SAAS;IACTC,MAAM;IACNC,SAAS;AACX,EAAE;AAEF;;;;;;CAMC,GACD,OAAO,MAAMC,qBAA4D;IACvEJ,OAAO;IACPC,SAAS;IACTC,MAAM;IACNC,SAAS;AACX,EAAE;AAEF,gFAAgF,GAChF,OAAO,MAAME,qBAA4D;IACvEL,OAAO;IACPC,SAAS;IACTC,MAAM;IACNC,SAAS;AACX,EAAE"}
@@ -1,5 +1,5 @@
1
+ import type { RunAnnotationSummary } from '#core/run-annotation.js';
1
2
  import { type WorkflowRunDetail } from '#core/workflow-run.js';
2
- import type { RunAnnotationSummary } from '#core/workflow-run-tabs.js';
3
3
  import { type WorkflowJobSearch, type WorkflowRunTab } from '#routes/inputs.js';
4
4
  type RunWorkspaceSection = Exclude<WorkflowRunTab, 'jobs'>;
5
5
  export interface RunWorkspaceNavProps {
@@ -1 +1 @@
1
- {"version":3,"file":"run-workspace-nav.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-view/run-workspace-nav.tsx"],"names":[],"mappings":"AAQA,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,4BAA4B,CAAC;AACrE,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EAGpB,MAAM,mBAAmB,CAAC;AAG3B,KAAK,mBAAmB,GAAG,OAAO,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;AAE3D,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,iBAAiB,CAAC;IACvB,aAAa,EAAE,mBAAmB,CAAC;IACnC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,SAAS,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;IAC1C,iBAAiB,CAAC,EAAE,oBAAoB,GAAG,SAAS,CAAC;CACtD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAC9B,aAAa,EACb,WAAW,EACX,GAAG,EACH,aAAa,EACb,YAAY,EACZ,SAAc,EACd,iBAAiB,GAClB,EAAE,oBAAoB,+BA0DtB"}
1
+ {"version":3,"file":"run-workspace-nav.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-view/run-workspace-nav.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EAGpB,MAAM,mBAAmB,CAAC;AAG3B,KAAK,mBAAmB,GAAG,OAAO,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;AAE3D,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,iBAAiB,CAAC;IACvB,aAAa,EAAE,mBAAmB,CAAC;IACnC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,SAAS,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;IAC1C,iBAAiB,CAAC,EAAE,oBAAoB,GAAG,SAAS,CAAC;CACtD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAC9B,aAAa,EACb,WAAW,EACX,GAAG,EACH,aAAa,EACb,YAAY,EACZ,SAAc,EACd,iBAAiB,GAClB,EAAE,oBAAoB,+BA0DtB"}
@@ -210,6 +210,7 @@ function RunWorkspaceNavContent({ workspaceSlug, projectSlug, run, jobs, activeS
210
210
  section: "annotations",
211
211
  current: !currentJobId && activeSection === 'annotations',
212
212
  count: annotationSummary?.total,
213
+ countTruncated: annotationSummary?.truncated,
213
214
  onNavigate: onNavigate
214
215
  })
215
216
  }),
@@ -231,7 +232,7 @@ function RunWorkspaceNavContent({ workspaceSlug, projectSlug, run, jobs, activeS
231
232
  ]
232
233
  });
233
234
  }
234
- function RunSectionLink({ workspaceSlug, projectSlug, runId, runAttempt, section, current, count, onNavigate }) {
235
+ function RunSectionLink({ workspaceSlug, projectSlug, runId, runAttempt, section, current, count, countTruncated = false, onNavigate }) {
235
236
  return /*#__PURE__*/ _jsxs(Link, {
236
237
  to: "/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId",
237
238
  params: {
@@ -249,12 +250,17 @@ function RunSectionLink({ workspaceSlug, projectSlug, runId, runAttempt, section
249
250
  }),
250
251
  onClick: onNavigate,
251
252
  "aria-current": current ? 'page' : undefined,
253
+ "aria-label": count ? `${sectionLabel(section)}, ${countTruncated ? `${count} or more` : count}` : undefined,
252
254
  className: cn('flex min-h-32 items-center gap-8 rounded-4 px-8 text-xs font-medium text-foreground-neutral-base outline-none transition-colors hover:bg-background-neutral-hover focus-visible:shadow-border-interactive-with-active @max-[767px]:min-h-44 [@media(pointer:coarse)]:min-h-44', current && 'bg-background-neutral-hover'),
253
255
  children: [
254
256
  sectionLabel(section),
255
- count ? /*#__PURE__*/ _jsx("span", {
257
+ count ? /*#__PURE__*/ _jsxs("span", {
258
+ "aria-hidden": "true",
256
259
  className: "ml-auto font-code text-xs text-foreground-neutral-muted tabular-nums",
257
- children: count
260
+ children: [
261
+ count,
262
+ countTruncated ? '+' : ''
263
+ ]
258
264
  }) : null
259
265
  ]
260
266
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/workflow-run-view/run-workspace-nav.tsx"],"sourcesContent":["import {Collapsible, CollapsibleTrigger} from '@shipfox/react-ui/collapsible';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {TimeTickerProvider} from '@shipfox/react-ui/time-ticker';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {cn} from '@shipfox/react-ui/utils';\nimport {Link} from '@tanstack/react-router';\nimport {useEffect, useMemo, useRef, useState} from 'react';\nimport {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';\nimport {\n defaultJobExecution,\n deriveJobDisplayStatus,\n type Job,\n type WorkflowRunDetail,\n} from '#core/workflow-run.js';\nimport type {RunAnnotationSummary} from '#core/workflow-run-tabs.js';\nimport {\n type WorkflowJobSearch,\n type WorkflowRunTab,\n workflowJobSearchParams,\n workflowRunSearchParams,\n} from '#routes/inputs.js';\nimport {JobExecutionTimeText} from '../job-detail/job-execution-time-text.js';\n\ntype RunWorkspaceSection = Exclude<WorkflowRunTab, 'jobs'>;\n\nexport interface RunWorkspaceNavProps {\n workspaceSlug: string;\n projectSlug: string;\n run: WorkflowRunDetail;\n activeSection: RunWorkspaceSection;\n currentJobId?: string | undefined;\n jobSearch?: WorkflowJobSearch | undefined;\n annotationSummary?: RunAnnotationSummary | undefined;\n}\n\n/**\n * The stable navigation model shared by run-level and job-level routes. The graph explains\n * topology; this compact index makes moving between dedicated job pages predictable.\n */\nexport function RunWorkspaceNav({\n workspaceSlug,\n projectSlug,\n run,\n activeSection,\n currentJobId,\n jobSearch = {},\n annotationSummary,\n}: RunWorkspaceNavProps) {\n const jobs = useMemo(() => [...run.jobs].sort(compareJobs), [run.jobs]);\n const [mobileOpen, setMobileOpen] = useState(false);\n const currentJob = jobs.find((job) => job.id === currentJobId);\n const currentLabel = currentJob?.displayName ?? sectionLabel(activeSection);\n\n return (\n <TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>\n <aside className=\"w-full shrink-0 border-b border-border-neutral-base bg-background-neutral-background min-[768px]:w-240 min-[768px]:border-b-0 min-[768px]:border-r\">\n <Collapsible open={mobileOpen} onOpenChange={setMobileOpen}>\n <CollapsibleTrigger asChild>\n <button\n type=\"button\"\n className=\"flex min-h-44 w-full items-center justify-between gap-12 px-16 text-left outline-none focus-visible:shadow-border-interactive-with-active min-[768px]:hidden\"\n aria-label=\"Toggle run navigation\"\n >\n <span className=\"min-w-0\">\n <Text as=\"span\" size=\"xs\" className=\"block text-foreground-neutral-muted\">\n Run navigation\n </Text>\n <span className=\"block truncate font-code text-xs leading-20 text-foreground-neutral-base\">\n {currentLabel}\n </span>\n </span>\n <Icon\n name=\"chevronDown\"\n size={14}\n aria-hidden=\"true\"\n className={cn(\n 'shrink-0 text-foreground-neutral-muted transition-transform',\n mobileOpen && 'rotate-180',\n )}\n />\n </button>\n </CollapsibleTrigger>\n <div\n className={cn(\n 'max-h-[50vh] overflow-y-auto p-8 min-[768px]:block min-[768px]:max-h-none min-[768px]:p-12',\n !mobileOpen && 'hidden',\n )}\n >\n <RunWorkspaceNavContent\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n run={run}\n jobs={jobs}\n activeSection={activeSection}\n currentJobId={currentJobId}\n jobSearch={jobSearch}\n annotationSummary={annotationSummary}\n mobileOpen={mobileOpen}\n onNavigate={() => setMobileOpen(false)}\n />\n </div>\n </Collapsible>\n </aside>\n </TimeTickerProvider>\n );\n}\n\nfunction RunWorkspaceNavContent({\n workspaceSlug,\n projectSlug,\n run,\n jobs,\n activeSection,\n currentJobId,\n jobSearch,\n annotationSummary,\n mobileOpen,\n onNavigate,\n}: RunWorkspaceNavProps & {\n jobs: Job[];\n jobSearch: WorkflowJobSearch;\n mobileOpen: boolean;\n onNavigate: () => void;\n}) {\n const currentRowRef = useRef<HTMLAnchorElement>(null);\n const runAttempt = jobSearch.runAttempt ?? run.runAttempt.attempt;\n\n useEffect(() => {\n if (!currentJobId || mobileOpen) return;\n currentRowRef.current?.scrollIntoView({block: 'nearest'});\n }, [currentJobId, mobileOpen]);\n\n useEffect(() => {\n if (!currentJobId || !mobileOpen) return;\n currentRowRef.current?.scrollIntoView({block: 'nearest'});\n }, [currentJobId, mobileOpen]);\n\n return (\n <nav aria-label=\"Run workspace\" className=\"flex min-w-0 flex-col gap-16\">\n <ul>\n <li>\n <RunSectionLink\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n runId={run.id}\n runAttempt={runAttempt}\n section=\"summary\"\n current={!currentJobId && activeSection === 'summary'}\n onNavigate={onNavigate}\n />\n </li>\n </ul>\n\n <section aria-labelledby=\"run-workspace-jobs-heading\" className=\"min-w-0\">\n <div className=\"flex items-center justify-between gap-8 px-8 pb-6\">\n <Text\n as=\"h2\"\n id=\"run-workspace-jobs-heading\"\n size=\"xs\"\n bold\n className=\"text-foreground-neutral-muted\"\n >\n Jobs\n </Text>\n <Text as=\"span\" size=\"xs\" className=\"font-code text-foreground-neutral-subtle\">\n {jobs.length}\n </Text>\n </div>\n <ol className=\"max-h-[320px] overflow-y-auto\">\n {jobs.map((job) => {\n const current = job.id === currentJobId;\n const execution = defaultJobExecution(job);\n const duration = execution?.displayDuration ?? job.displayDuration;\n\n return (\n <li key={job.id}>\n <Link\n ref={current ? currentRowRef : undefined}\n to=\"/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId/jobs/$jobId\"\n params={{workspaceSlug, projectSlug, workflowRunId: run.id, jobId: job.id}}\n search={workflowJobSearchParams({runAttempt}) as never}\n onClick={onNavigate}\n aria-current={current ? 'page' : undefined}\n className={cn(\n 'flex min-h-32 min-w-0 items-center gap-8 rounded-4 px-8 text-left outline-none transition-colors hover:bg-background-neutral-hover focus-visible:shadow-border-interactive-with-active @max-[767px]:min-h-44 [@media(pointer:coarse)]:min-h-44',\n current && 'bg-background-neutral-hover',\n )}\n >\n <WorkflowStatusIcon\n status={deriveJobDisplayStatus(job)}\n size={12}\n tooltip={false}\n />\n <span className=\"min-w-0 truncate font-code text-xs leading-20 text-foreground-neutral-base\">\n {job.displayName}\n </span>\n <span className=\"ml-auto shrink-0 font-code text-xs leading-20 text-foreground-neutral-muted tabular-nums\">\n {duration ? <JobExecutionTimeText time={duration} /> : '—'}\n </span>\n </Link>\n </li>\n );\n })}\n </ol>\n </section>\n\n <section aria-labelledby=\"run-workspace-details-heading\">\n <Text\n as=\"h2\"\n id=\"run-workspace-details-heading\"\n size=\"xs\"\n bold\n className=\"px-8 pb-6 text-foreground-neutral-muted\"\n >\n Run details\n </Text>\n <ul>\n <li>\n <RunSectionLink\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n runId={run.id}\n runAttempt={runAttempt}\n section=\"annotations\"\n current={!currentJobId && activeSection === 'annotations'}\n count={annotationSummary?.total}\n onNavigate={onNavigate}\n />\n </li>\n <li>\n <RunSectionLink\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n runId={run.id}\n runAttempt={runAttempt}\n section=\"source\"\n current={!currentJobId && activeSection === 'source'}\n onNavigate={onNavigate}\n />\n </li>\n </ul>\n </section>\n </nav>\n );\n}\n\nfunction RunSectionLink({\n workspaceSlug,\n projectSlug,\n runId,\n runAttempt,\n section,\n current,\n count,\n onNavigate,\n}: {\n workspaceSlug: string;\n projectSlug: string;\n runId: string;\n runAttempt: number;\n section: RunWorkspaceSection;\n current: boolean;\n count?: number | undefined;\n onNavigate?: (() => void) | undefined;\n}) {\n return (\n <Link\n to=\"/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId\"\n params={{workspaceSlug, projectSlug, workflowRunId: runId}}\n search={\n workflowRunSearchParams(\n {runAttempt, ...(section === 'summary' ? {} : {tab: section})},\n {runAttempt},\n ) as never\n }\n onClick={onNavigate}\n aria-current={current ? 'page' : undefined}\n className={cn(\n 'flex min-h-32 items-center gap-8 rounded-4 px-8 text-xs font-medium text-foreground-neutral-base outline-none transition-colors hover:bg-background-neutral-hover focus-visible:shadow-border-interactive-with-active @max-[767px]:min-h-44 [@media(pointer:coarse)]:min-h-44',\n current && 'bg-background-neutral-hover',\n )}\n >\n {sectionLabel(section)}\n {count ? (\n <span className=\"ml-auto font-code text-xs text-foreground-neutral-muted tabular-nums\">\n {count}\n </span>\n ) : null}\n </Link>\n );\n}\n\nfunction sectionLabel(section: RunWorkspaceSection): string {\n if (section === 'annotations') return 'Annotations';\n if (section === 'source') return 'Source';\n return 'Summary';\n}\n\nfunction compareJobs(left: Job, right: Job): number {\n return left.position - right.position || left.id.localeCompare(right.id);\n}\n"],"names":["Collapsible","CollapsibleTrigger","Icon","TimeTickerProvider","Text","cn","Link","useEffect","useMemo","useRef","useState","WorkflowStatusIcon","defaultJobExecution","deriveJobDisplayStatus","workflowJobSearchParams","workflowRunSearchParams","JobExecutionTimeText","RunWorkspaceNav","workspaceSlug","projectSlug","run","activeSection","currentJobId","jobSearch","annotationSummary","jobs","sort","compareJobs","mobileOpen","setMobileOpen","currentJob","find","job","id","currentLabel","displayName","sectionLabel","intervalMs","reducedMotionIntervalMs","aside","className","open","onOpenChange","asChild","button","type","aria-label","span","as","size","name","aria-hidden","div","RunWorkspaceNavContent","onNavigate","currentRowRef","runAttempt","attempt","current","scrollIntoView","block","nav","ul","li","RunSectionLink","runId","section","aria-labelledby","bold","length","ol","map","execution","duration","displayDuration","ref","undefined","to","params","workflowRunId","jobId","search","onClick","aria-current","status","tooltip","time","count","total","tab","left","right","position","localeCompare"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/components/workflow-run-view/run-workspace-nav.tsx"],"sourcesContent":["import {Collapsible, CollapsibleTrigger} from '@shipfox/react-ui/collapsible';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {TimeTickerProvider} from '@shipfox/react-ui/time-ticker';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {cn} from '@shipfox/react-ui/utils';\nimport {Link} from '@tanstack/react-router';\nimport {useEffect, useMemo, useRef, useState} from 'react';\nimport {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';\nimport type {RunAnnotationSummary} from '#core/run-annotation.js';\nimport {\n defaultJobExecution,\n deriveJobDisplayStatus,\n type Job,\n type WorkflowRunDetail,\n} from '#core/workflow-run.js';\nimport {\n type WorkflowJobSearch,\n type WorkflowRunTab,\n workflowJobSearchParams,\n workflowRunSearchParams,\n} from '#routes/inputs.js';\nimport {JobExecutionTimeText} from '../job-detail/job-execution-time-text.js';\n\ntype RunWorkspaceSection = Exclude<WorkflowRunTab, 'jobs'>;\n\nexport interface RunWorkspaceNavProps {\n workspaceSlug: string;\n projectSlug: string;\n run: WorkflowRunDetail;\n activeSection: RunWorkspaceSection;\n currentJobId?: string | undefined;\n jobSearch?: WorkflowJobSearch | undefined;\n annotationSummary?: RunAnnotationSummary | undefined;\n}\n\n/**\n * The stable navigation model shared by run-level and job-level routes. The graph explains\n * topology; this compact index makes moving between dedicated job pages predictable.\n */\nexport function RunWorkspaceNav({\n workspaceSlug,\n projectSlug,\n run,\n activeSection,\n currentJobId,\n jobSearch = {},\n annotationSummary,\n}: RunWorkspaceNavProps) {\n const jobs = useMemo(() => [...run.jobs].sort(compareJobs), [run.jobs]);\n const [mobileOpen, setMobileOpen] = useState(false);\n const currentJob = jobs.find((job) => job.id === currentJobId);\n const currentLabel = currentJob?.displayName ?? sectionLabel(activeSection);\n\n return (\n <TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>\n <aside className=\"w-full shrink-0 border-b border-border-neutral-base bg-background-neutral-background min-[768px]:w-240 min-[768px]:border-b-0 min-[768px]:border-r\">\n <Collapsible open={mobileOpen} onOpenChange={setMobileOpen}>\n <CollapsibleTrigger asChild>\n <button\n type=\"button\"\n className=\"flex min-h-44 w-full items-center justify-between gap-12 px-16 text-left outline-none focus-visible:shadow-border-interactive-with-active min-[768px]:hidden\"\n aria-label=\"Toggle run navigation\"\n >\n <span className=\"min-w-0\">\n <Text as=\"span\" size=\"xs\" className=\"block text-foreground-neutral-muted\">\n Run navigation\n </Text>\n <span className=\"block truncate font-code text-xs leading-20 text-foreground-neutral-base\">\n {currentLabel}\n </span>\n </span>\n <Icon\n name=\"chevronDown\"\n size={14}\n aria-hidden=\"true\"\n className={cn(\n 'shrink-0 text-foreground-neutral-muted transition-transform',\n mobileOpen && 'rotate-180',\n )}\n />\n </button>\n </CollapsibleTrigger>\n <div\n className={cn(\n 'max-h-[50vh] overflow-y-auto p-8 min-[768px]:block min-[768px]:max-h-none min-[768px]:p-12',\n !mobileOpen && 'hidden',\n )}\n >\n <RunWorkspaceNavContent\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n run={run}\n jobs={jobs}\n activeSection={activeSection}\n currentJobId={currentJobId}\n jobSearch={jobSearch}\n annotationSummary={annotationSummary}\n mobileOpen={mobileOpen}\n onNavigate={() => setMobileOpen(false)}\n />\n </div>\n </Collapsible>\n </aside>\n </TimeTickerProvider>\n );\n}\n\nfunction RunWorkspaceNavContent({\n workspaceSlug,\n projectSlug,\n run,\n jobs,\n activeSection,\n currentJobId,\n jobSearch,\n annotationSummary,\n mobileOpen,\n onNavigate,\n}: RunWorkspaceNavProps & {\n jobs: Job[];\n jobSearch: WorkflowJobSearch;\n mobileOpen: boolean;\n onNavigate: () => void;\n}) {\n const currentRowRef = useRef<HTMLAnchorElement>(null);\n const runAttempt = jobSearch.runAttempt ?? run.runAttempt.attempt;\n\n useEffect(() => {\n if (!currentJobId || mobileOpen) return;\n currentRowRef.current?.scrollIntoView({block: 'nearest'});\n }, [currentJobId, mobileOpen]);\n\n useEffect(() => {\n if (!currentJobId || !mobileOpen) return;\n currentRowRef.current?.scrollIntoView({block: 'nearest'});\n }, [currentJobId, mobileOpen]);\n\n return (\n <nav aria-label=\"Run workspace\" className=\"flex min-w-0 flex-col gap-16\">\n <ul>\n <li>\n <RunSectionLink\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n runId={run.id}\n runAttempt={runAttempt}\n section=\"summary\"\n current={!currentJobId && activeSection === 'summary'}\n onNavigate={onNavigate}\n />\n </li>\n </ul>\n\n <section aria-labelledby=\"run-workspace-jobs-heading\" className=\"min-w-0\">\n <div className=\"flex items-center justify-between gap-8 px-8 pb-6\">\n <Text\n as=\"h2\"\n id=\"run-workspace-jobs-heading\"\n size=\"xs\"\n bold\n className=\"text-foreground-neutral-muted\"\n >\n Jobs\n </Text>\n <Text as=\"span\" size=\"xs\" className=\"font-code text-foreground-neutral-subtle\">\n {jobs.length}\n </Text>\n </div>\n <ol className=\"max-h-[320px] overflow-y-auto\">\n {jobs.map((job) => {\n const current = job.id === currentJobId;\n const execution = defaultJobExecution(job);\n const duration = execution?.displayDuration ?? job.displayDuration;\n\n return (\n <li key={job.id}>\n <Link\n ref={current ? currentRowRef : undefined}\n to=\"/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId/jobs/$jobId\"\n params={{workspaceSlug, projectSlug, workflowRunId: run.id, jobId: job.id}}\n search={workflowJobSearchParams({runAttempt}) as never}\n onClick={onNavigate}\n aria-current={current ? 'page' : undefined}\n className={cn(\n 'flex min-h-32 min-w-0 items-center gap-8 rounded-4 px-8 text-left outline-none transition-colors hover:bg-background-neutral-hover focus-visible:shadow-border-interactive-with-active @max-[767px]:min-h-44 [@media(pointer:coarse)]:min-h-44',\n current && 'bg-background-neutral-hover',\n )}\n >\n <WorkflowStatusIcon\n status={deriveJobDisplayStatus(job)}\n size={12}\n tooltip={false}\n />\n <span className=\"min-w-0 truncate font-code text-xs leading-20 text-foreground-neutral-base\">\n {job.displayName}\n </span>\n <span className=\"ml-auto shrink-0 font-code text-xs leading-20 text-foreground-neutral-muted tabular-nums\">\n {duration ? <JobExecutionTimeText time={duration} /> : '—'}\n </span>\n </Link>\n </li>\n );\n })}\n </ol>\n </section>\n\n <section aria-labelledby=\"run-workspace-details-heading\">\n <Text\n as=\"h2\"\n id=\"run-workspace-details-heading\"\n size=\"xs\"\n bold\n className=\"px-8 pb-6 text-foreground-neutral-muted\"\n >\n Run details\n </Text>\n <ul>\n <li>\n <RunSectionLink\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n runId={run.id}\n runAttempt={runAttempt}\n section=\"annotations\"\n current={!currentJobId && activeSection === 'annotations'}\n count={annotationSummary?.total}\n countTruncated={annotationSummary?.truncated}\n onNavigate={onNavigate}\n />\n </li>\n <li>\n <RunSectionLink\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n runId={run.id}\n runAttempt={runAttempt}\n section=\"source\"\n current={!currentJobId && activeSection === 'source'}\n onNavigate={onNavigate}\n />\n </li>\n </ul>\n </section>\n </nav>\n );\n}\n\nfunction RunSectionLink({\n workspaceSlug,\n projectSlug,\n runId,\n runAttempt,\n section,\n current,\n count,\n countTruncated = false,\n onNavigate,\n}: {\n workspaceSlug: string;\n projectSlug: string;\n runId: string;\n runAttempt: number;\n section: RunWorkspaceSection;\n current: boolean;\n count?: number | undefined;\n /** The read hit its page budget, so the count is a lower bound and renders as `N+`. */\n countTruncated?: boolean | undefined;\n onNavigate?: (() => void) | undefined;\n}) {\n return (\n <Link\n to=\"/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId\"\n params={{workspaceSlug, projectSlug, workflowRunId: runId}}\n search={\n workflowRunSearchParams(\n {runAttempt, ...(section === 'summary' ? {} : {tab: section})},\n {runAttempt},\n ) as never\n }\n onClick={onNavigate}\n aria-current={current ? 'page' : undefined}\n aria-label={\n count\n ? `${sectionLabel(section)}, ${countTruncated ? `${count} or more` : count}`\n : undefined\n }\n className={cn(\n 'flex min-h-32 items-center gap-8 rounded-4 px-8 text-xs font-medium text-foreground-neutral-base outline-none transition-colors hover:bg-background-neutral-hover focus-visible:shadow-border-interactive-with-active @max-[767px]:min-h-44 [@media(pointer:coarse)]:min-h-44',\n current && 'bg-background-neutral-hover',\n )}\n >\n {sectionLabel(section)}\n {count ? (\n <span\n aria-hidden=\"true\"\n className=\"ml-auto font-code text-xs text-foreground-neutral-muted tabular-nums\"\n >\n {count}\n {countTruncated ? '+' : ''}\n </span>\n ) : null}\n </Link>\n );\n}\n\nfunction sectionLabel(section: RunWorkspaceSection): string {\n if (section === 'annotations') return 'Annotations';\n if (section === 'source') return 'Source';\n return 'Summary';\n}\n\nfunction compareJobs(left: Job, right: Job): number {\n return left.position - right.position || left.id.localeCompare(right.id);\n}\n"],"names":["Collapsible","CollapsibleTrigger","Icon","TimeTickerProvider","Text","cn","Link","useEffect","useMemo","useRef","useState","WorkflowStatusIcon","defaultJobExecution","deriveJobDisplayStatus","workflowJobSearchParams","workflowRunSearchParams","JobExecutionTimeText","RunWorkspaceNav","workspaceSlug","projectSlug","run","activeSection","currentJobId","jobSearch","annotationSummary","jobs","sort","compareJobs","mobileOpen","setMobileOpen","currentJob","find","job","id","currentLabel","displayName","sectionLabel","intervalMs","reducedMotionIntervalMs","aside","className","open","onOpenChange","asChild","button","type","aria-label","span","as","size","name","aria-hidden","div","RunWorkspaceNavContent","onNavigate","currentRowRef","runAttempt","attempt","current","scrollIntoView","block","nav","ul","li","RunSectionLink","runId","section","aria-labelledby","bold","length","ol","map","execution","duration","displayDuration","ref","undefined","to","params","workflowRunId","jobId","search","onClick","aria-current","status","tooltip","time","count","total","countTruncated","truncated","tab","left","right","position","localeCompare"],"mappings":"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"}
@@ -1,5 +1,4 @@
1
1
  import { type ReactNode } from 'react';
2
- import type { RunAnnotationSummary } from '#core/workflow-run-tabs.js';
3
2
  import { type WorkflowJobSearch, type WorkflowRunsSearch, type WorkflowRunTab } from '#routes/inputs.js';
4
3
  export interface WorkflowRunViewProps {
5
4
  projectId: string;
@@ -9,7 +8,6 @@ export interface WorkflowRunViewProps {
9
8
  runAttempt?: number | undefined;
10
9
  selection?: WorkflowRunsSearch | undefined;
11
10
  tab?: WorkflowRunTab | undefined;
12
- annotationSummary?: RunAnnotationSummary | undefined;
13
11
  activeJobId?: string | undefined;
14
12
  jobSearch?: WorkflowJobSearch | undefined;
15
13
  jobContent?: ReactNode | undefined;
@@ -18,5 +16,5 @@ export interface WorkflowRunViewProps {
18
16
  * The persistent run workspace. Run-level sections and dedicated job routes share the same
19
17
  * header and job index, while the all-jobs Summary always opens on the dependency graph.
20
18
  */
21
- export declare function WorkflowRunView({ projectId, workspaceSlug, projectSlug, workflowRunId, runAttempt, selection, tab, annotationSummary, activeJobId, jobSearch, jobContent, }: WorkflowRunViewProps): import("react").JSX.Element;
19
+ export declare function WorkflowRunView({ projectId, workspaceSlug, projectSlug, workflowRunId, runAttempt, selection, tab, activeJobId, jobSearch, jobContent, }: WorkflowRunViewProps): import("react").JSX.Element;
22
20
  //# sourceMappingURL=workflow-run-view.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-run-view.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-view/workflow-run-view.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAC,KAAK,SAAS,EAAY,MAAM,OAAO,CAAC;AAEhD,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,4BAA4B,CAAC;AAOrE,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EAGpB,MAAM,mBAAmB,CAAC;AAiB3B,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,SAAS,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IAC3C,GAAG,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IACjC,iBAAiB,CAAC,EAAE,oBAAoB,GAAG,SAAS,CAAC;IACrD,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,SAAS,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;IAC1C,UAAU,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CACpC;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAC9B,SAAS,EACT,aAAa,EACb,WAAW,EACX,aAAa,EACb,UAAU,EACV,SAAS,EACT,GAAG,EACH,iBAAiB,EACjB,WAAW,EACX,SAAS,EACT,UAAU,GACX,EAAE,oBAAoB,+BA0BtB"}
1
+ {"version":3,"file":"workflow-run-view.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-view/workflow-run-view.tsx"],"names":[],"mappings":"AAcA,OAAO,EAAC,KAAK,SAAS,EAAqB,MAAM,OAAO,CAAC;AAUzD,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EAGpB,MAAM,mBAAmB,CAAC;AAiB3B,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,SAAS,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IAC3C,GAAG,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,SAAS,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;IAC1C,UAAU,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CACpC;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAC9B,SAAS,EACT,aAAa,EACb,WAAW,EACX,aAAa,EACb,UAAU,EACV,SAAS,EACT,GAAG,EACH,WAAW,EACX,SAAS,EACT,UAAU,GACX,EAAE,oBAAoB,+BA0CtB"}
@@ -5,14 +5,18 @@ import { EmptyState } from '@shipfox/react-ui/empty-state';
5
5
  import { RelativeTimeProvider } from '@shipfox/react-ui/relative-time';
6
6
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@shipfox/react-ui/select';
7
7
  import { toast } from '@shipfox/react-ui/toast';
8
+ import { Text } from '@shipfox/react-ui/typography';
8
9
  import { useNavigate } from '@tanstack/react-router';
9
- import { useEffect } from 'react';
10
+ import { useEffect, useMemo } from 'react';
11
+ import { buildRunAnnotationList } from '#core/run-annotation.js';
12
+ import { isWorkflowRunTerminal } from '#core/workflow-run.js';
10
13
  import { withoutWorkflowRunSelectionSearch } from '#core/workflow-run-url-state.js';
14
+ import { useRunAnnotationsQuery } from '#hooks/api/run-annotations.js';
11
15
  import { useCancelWorkflowRunMutation, useRerunWorkflowRunMutation, useWorkflowRunAttemptQuery } from '#hooks/api/workflow-runs.js';
12
16
  import { workflowJobSearchParams, workflowRunSearchParams } from '#routes/inputs.js';
13
17
  import { JobGraph } from '../job-graph/index.js';
14
18
  import { WorkflowRunSummary } from '../workflow-run-summary/index.js';
15
- import { RunAnnotationSummaryLine, RunAnnotationsEmpty } from '../workflow-run-tabs/index.js';
19
+ import { RunAnnotationList, RunAnnotationSummaryLine } from '../workflow-run-tabs/index.js';
16
20
  import { WorkflowSourceContent } from '../workflow-source-panel/index.js';
17
21
  import { RunWorkspaceNav } from './run-workspace-nav.js';
18
22
  import { resolveWorkflowRunSelection } from './workflow-run-selection.js';
@@ -20,12 +24,24 @@ import { WorkflowRunContentSkeleton, WorkflowRunNotFound, WorkflowRunSkeleton, W
20
24
  /**
21
25
  * The persistent run workspace. Run-level sections and dedicated job routes share the same
22
26
  * header and job index, while the all-jobs Summary always opens on the dependency graph.
23
- */ export function WorkflowRunView({ projectId, workspaceSlug, projectSlug, workflowRunId, runAttempt, selection, tab, annotationSummary, activeJobId, jobSearch, jobContent }) {
27
+ */ export function WorkflowRunView({ projectId, workspaceSlug, projectSlug, workflowRunId, runAttempt, selection, tab, activeJobId, jobSearch, jobContent }) {
24
28
  const runQuery = useWorkflowRunAttemptQuery({
25
29
  workflowRunId,
26
30
  runAttempt: selection?.runAttempt ?? runAttempt
27
31
  });
28
32
  const rerunMutation = useRerunWorkflowRunMutation(projectId);
33
+ // Annotations are read on their own cadence rather than hydrated into the run, so the two
34
+ // modules stay decoupled. The key is shared, so the job page's count chip costs no extra fetch.
35
+ //
36
+ // Polling is scoped to the surface that renders bodies. The read has no counts-only mode, so
37
+ // every poll transfers whole Markdown bodies; doing that every four seconds behind Summary,
38
+ // Source, and the job log spends megabytes to keep one rail number warm. Off that surface the
39
+ // counts still refresh on navigation and on window focus.
40
+ const annotationsQuery = useRunAnnotationsQuery({
41
+ workflowRunId,
42
+ runAttempt: runQuery.data?.runAttempt.attempt,
43
+ live: runWorkspaceSection(tab) === 'annotations' && !activeJobId && Boolean(runQuery.data) && !isWorkflowRunTerminal(runQuery.data?.runAttempt.status ?? 'pending')
44
+ });
29
45
  return /*#__PURE__*/ _jsx(RelativeTimeProvider, {
30
46
  children: /*#__PURE__*/ _jsx("div", {
31
47
  className: "flex min-h-0 min-w-0 flex-1 overflow-hidden",
@@ -33,11 +49,11 @@ import { WorkflowRunContentSkeleton, WorkflowRunNotFound, WorkflowRunSkeleton, W
33
49
  workspaceSlug: workspaceSlug,
34
50
  projectSlug: projectSlug,
35
51
  query: runQuery,
52
+ annotations: annotationsQuery,
36
53
  rerunMutation: rerunMutation,
37
54
  runAttempt: runAttempt,
38
55
  selection: selection,
39
56
  tab: tab,
40
- annotationSummary: annotationSummary,
41
57
  activeJobId: activeJobId,
42
58
  jobSearch: jobSearch,
43
59
  jobContent: jobContent
@@ -45,9 +61,10 @@ import { WorkflowRunContentSkeleton, WorkflowRunNotFound, WorkflowRunSkeleton, W
45
61
  })
46
62
  });
47
63
  }
48
- function RunViewContent({ workspaceSlug, projectSlug, query, rerunMutation, runAttempt, selection, tab, annotationSummary, activeJobId, jobSearch, jobContent }) {
64
+ function RunViewContent({ workspaceSlug, projectSlug, query, annotations, rerunMutation, runAttempt, selection, tab, activeJobId, jobSearch, jobContent }) {
49
65
  const navigate = useNavigate();
50
66
  const runData = query.data;
67
+ const annotationSummary = annotations.summary;
51
68
  const activeSection = runWorkspaceSection(tab);
52
69
  const cancelMutation = useCancelWorkflowRunMutation(runData);
53
70
  const sourceSnapshot = runData?.sourceSnapshot ?? null;
@@ -147,6 +164,29 @@ function RunViewContent({ workspaceSlug, projectSlug, query, rerunMutation, runA
147
164
  search: workflowRunSearchParams(nextSearch, nextSearch)
148
165
  });
149
166
  }
167
+ function clearAnnotationFilters() {
168
+ if (!runData || !workspaceSlug || !projectSlug) return;
169
+ const nextSearch = {
170
+ ...selection,
171
+ tab: 'annotations'
172
+ };
173
+ delete nextSearch.jobId;
174
+ delete nextSearch.jobExecutionId;
175
+ delete nextSearch.stepId;
176
+ delete nextSearch.stepAttemptId;
177
+ delete nextSearch.severity;
178
+ delete nextSearch.annotation;
179
+ void navigate({
180
+ to: '/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId',
181
+ params: {
182
+ workspaceSlug,
183
+ projectSlug,
184
+ workflowRunId: runData.id
185
+ },
186
+ search: workflowRunSearchParams(nextSearch, nextSearch),
187
+ replace: true
188
+ });
189
+ }
150
190
  function cancelRun() {
151
191
  cancelMutation.mutate(undefined, {
152
192
  onError: (error)=>toast.error(cancelErrorMessage(error))
@@ -193,6 +233,7 @@ function RunViewContent({ workspaceSlug, projectSlug, query, rerunMutation, runA
193
233
  children: runData && jobContent ? jobContent : runData ? /*#__PURE__*/ _jsx(RunSectionContent, {
194
234
  section: activeSection,
195
235
  run: runData,
236
+ annotations: annotations,
196
237
  annotationSummary: annotationSummary,
197
238
  workspaceSlug: workspaceSlug,
198
239
  projectSlug: projectSlug,
@@ -200,6 +241,7 @@ function RunViewContent({ workspaceSlug, projectSlug, query, rerunMutation, runA
200
241
  selectedJobId: selection?.jobId,
201
242
  onSelectGraphJob: selectGraphJob,
202
243
  onSelectAnnotationJob: selectAnnotationJob,
244
+ onClearAnnotationFilters: clearAnnotationFilters,
203
245
  sourceSnapshot: sourceSnapshot,
204
246
  highlightedLineRange: highlightedLineRange
205
247
  }) : /*#__PURE__*/ _jsx("div", {
@@ -212,69 +254,125 @@ function RunViewContent({ workspaceSlug, projectSlug, query, rerunMutation, runA
212
254
  ]
213
255
  });
214
256
  }
215
- function RunSectionContent({ section, run, annotationSummary, workspaceSlug, projectSlug, selection, selectedJobId, onSelectGraphJob, onSelectAnnotationJob, sourceSnapshot, highlightedLineRange }) {
257
+ function RunSectionContent({ section, run, annotations, annotationSummary, workspaceSlug, projectSlug, selection, selectedJobId, onSelectGraphJob, onSelectAnnotationJob, onClearAnnotationFilters, sourceSnapshot, highlightedLineRange }) {
216
258
  if (section === 'summary') {
217
259
  return /*#__PURE__*/ _jsx("section", {
218
260
  "aria-label": "All jobs summary",
219
261
  className: "min-h-0 flex-1 overflow-auto pb-24 pt-16",
220
- children: /*#__PURE__*/ _jsx("div", {
221
- className: "mx-auto flex w-full max-w-[1120px] flex-col gap-16 px-24",
222
- children: /*#__PURE__*/ _jsx(JobGraph, {
223
- run: run,
224
- selectedJobId: selectedJobId,
225
- onSelectedJobChange: onSelectGraphJob,
226
- className: "min-h-160 overflow-hidden"
227
- })
228
- })
229
- });
230
- }
231
- if (section === 'annotations') {
232
- const selectedJob = run.jobs.find((job)=>job.id === selectedJobId);
233
- return /*#__PURE__*/ _jsx("section", {
234
- "aria-label": "Run annotations",
235
- className: "min-h-0 flex-1 overflow-auto pb-24 pt-16",
236
262
  children: /*#__PURE__*/ _jsxs("div", {
237
263
  className: "mx-auto flex w-full max-w-[1120px] flex-col gap-16 px-24",
238
264
  children: [
239
- /*#__PURE__*/ _jsxs("div", {
240
- className: "flex flex-wrap items-center justify-between gap-8",
241
- children: [
242
- /*#__PURE__*/ _jsx(RunAnnotationSummaryLine, {
243
- summary: annotationSummary,
244
- workspaceSlug: workspaceSlug,
245
- projectSlug: projectSlug,
246
- workflowRunId: run.id,
247
- search: selection
248
- }),
249
- /*#__PURE__*/ _jsx(AnnotationJobFilter, {
250
- jobs: run.jobs,
251
- selectedJobId: selectedJob?.id,
252
- onSelect: onSelectAnnotationJob
253
- })
254
- ]
265
+ /*#__PURE__*/ _jsx(Text, {
266
+ as: "h2",
267
+ className: "sr-only",
268
+ children: "All jobs summary"
255
269
  }),
256
- /*#__PURE__*/ _jsx(RunAnnotationsEmpty, {
257
- jobName: selectedJob?.displayName
270
+ /*#__PURE__*/ _jsx(JobGraph, {
271
+ run: run,
272
+ selectedJobId: selectedJobId,
273
+ onSelectedJobChange: onSelectGraphJob,
274
+ className: "min-h-160 overflow-hidden"
258
275
  })
259
276
  ]
260
277
  })
261
278
  });
262
279
  }
280
+ if (section === 'annotations') {
281
+ return /*#__PURE__*/ _jsx(RunAnnotationsSection, {
282
+ run: run,
283
+ annotations: annotations,
284
+ annotationSummary: annotationSummary,
285
+ workspaceSlug: workspaceSlug,
286
+ projectSlug: projectSlug,
287
+ selection: selection,
288
+ selectedJobId: selectedJobId,
289
+ onSelectAnnotationJob: onSelectAnnotationJob,
290
+ onClearAnnotationFilters: onClearAnnotationFilters
291
+ });
292
+ }
263
293
  return /*#__PURE__*/ _jsx("section", {
264
294
  "aria-label": "Workflow source",
265
295
  className: "min-h-0 flex-1 overflow-auto pb-24 pt-16",
266
- children: /*#__PURE__*/ _jsx("div", {
267
- className: "mx-auto flex min-h-full w-full max-w-[1120px] px-24",
268
- children: sourceSnapshot ? /*#__PURE__*/ _jsx(WorkflowSourceContent, {
269
- source: sourceSnapshot,
270
- highlightedLineRange: highlightedLineRange,
271
- scrollHighlightedIntoView: true
272
- }) : /*#__PURE__*/ _jsx(EmptyState, {
273
- className: "min-h-160 w-full",
274
- icon: "fileDamageLine",
275
- title: "Source snapshot unavailable",
276
- description: run.isTemporary ? 'Temporary runs do not capture workflow source.' : 'This run was created before workflow source snapshots were captured.'
277
- })
296
+ children: /*#__PURE__*/ _jsxs("div", {
297
+ className: "mx-auto flex min-h-full w-full max-w-[1120px] flex-col px-24",
298
+ children: [
299
+ /*#__PURE__*/ _jsx(Text, {
300
+ as: "h2",
301
+ className: "sr-only",
302
+ children: "Workflow source"
303
+ }),
304
+ sourceSnapshot ? /*#__PURE__*/ _jsx(WorkflowSourceContent, {
305
+ source: sourceSnapshot,
306
+ highlightedLineRange: highlightedLineRange,
307
+ scrollHighlightedIntoView: true
308
+ }) : /*#__PURE__*/ _jsx(EmptyState, {
309
+ className: "min-h-160 w-full",
310
+ icon: "fileDamageLine",
311
+ title: "Source snapshot unavailable",
312
+ description: run.isTemporary ? 'Temporary runs do not capture workflow source.' : 'This run was created before workflow source snapshots were captured.'
313
+ })
314
+ ]
315
+ })
316
+ });
317
+ }
318
+ function RunAnnotationsSection({ run, annotations, annotationSummary, workspaceSlug, projectSlug, selection, selectedJobId, onSelectAnnotationJob, onClearAnnotationFilters }) {
319
+ const selectedJob = run.jobs.find((job)=>job.id === selectedJobId);
320
+ const severity = selection?.severity;
321
+ const records = annotations.annotations;
322
+ const entries = useMemo(()=>records ? buildRunAnnotationList({
323
+ annotations: records,
324
+ jobs: run.jobs,
325
+ severity,
326
+ jobId: selectedJob?.id
327
+ }) : undefined, [
328
+ records,
329
+ run.jobs,
330
+ selectedJob?.id,
331
+ severity
332
+ ]);
333
+ return /*#__PURE__*/ _jsx("section", {
334
+ "aria-label": "Run annotations",
335
+ className: "min-h-0 flex-1 overflow-auto pb-24 pt-16",
336
+ children: /*#__PURE__*/ _jsxs("div", {
337
+ className: "mx-auto flex w-full max-w-[1120px] flex-col gap-16 px-24",
338
+ children: [
339
+ /*#__PURE__*/ _jsx(Text, {
340
+ as: "h2",
341
+ className: "sr-only",
342
+ children: "Annotations"
343
+ }),
344
+ /*#__PURE__*/ _jsxs("div", {
345
+ className: "flex flex-wrap items-center justify-between gap-8",
346
+ children: [
347
+ /*#__PURE__*/ _jsx(RunAnnotationSummaryLine, {
348
+ summary: annotationSummary,
349
+ workspaceSlug: workspaceSlug,
350
+ projectSlug: projectSlug,
351
+ workflowRunId: run.id,
352
+ search: selection
353
+ }),
354
+ /*#__PURE__*/ _jsx(AnnotationJobFilter, {
355
+ jobs: run.jobs,
356
+ selectedJobId: selectedJob?.id,
357
+ onSelect: onSelectAnnotationJob
358
+ })
359
+ ]
360
+ }),
361
+ /*#__PURE__*/ _jsx(RunAnnotationList, {
362
+ query: annotations.query,
363
+ entries: entries,
364
+ workspaceSlug: workspaceSlug,
365
+ projectSlug: projectSlug,
366
+ workflowRunId: run.id,
367
+ runAttempt: run.runAttempt.attempt,
368
+ // A run with no annotations at all offers no filter to clear, whatever the URL says.
369
+ filtered: Boolean((severity || selectedJob) && (annotationSummary?.total ?? 0) > 0),
370
+ filteredJobName: selectedJob?.displayName,
371
+ filteredSeverity: severity,
372
+ onClearFilters: onClearAnnotationFilters,
373
+ selectedAnnotationId: selection?.annotation
374
+ }, `${run.id}:${run.runAttempt.attempt}:${severity ?? 'all'}:${selectedJob?.id ?? 'all'}`)
375
+ ]
278
376
  })
279
377
  });
280
378
  }