@burdenoff/microfe-cosmicintersection 2026.704.2 → 2026.704.3

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 (50) hide show
  1. package/dist/cosmicintersection/CosmicIntersectionRoutes.js +39 -27
  2. package/dist/cosmicintersection/CosmicIntersectionRoutes.js.map +1 -1
  3. package/dist/cosmicintersection/api/cosmicApi.js +6 -0
  4. package/dist/cosmicintersection/api/cosmicApi.js.map +1 -1
  5. package/dist/cosmicintersection/components/FacetPanel.js +66 -0
  6. package/dist/cosmicintersection/components/FacetPanel.js.map +1 -0
  7. package/dist/cosmicintersection/components/LineageGraph.js +388 -0
  8. package/dist/cosmicintersection/components/LineageGraph.js.map +1 -0
  9. package/dist/cosmicintersection/components/ResultsChart.js +329 -0
  10. package/dist/cosmicintersection/components/ResultsChart.js.map +1 -0
  11. package/dist/cosmicintersection/components/StepDAG.js +104 -0
  12. package/dist/cosmicintersection/components/StepDAG.js.map +1 -0
  13. package/dist/cosmicintersection/pages/access/AccessPage.js +207 -155
  14. package/dist/cosmicintersection/pages/access/AccessPage.js.map +1 -1
  15. package/dist/cosmicintersection/pages/analyses/AnalysesPage.js +325 -165
  16. package/dist/cosmicintersection/pages/analyses/AnalysesPage.js.map +1 -1
  17. package/dist/cosmicintersection/pages/collections/CollectionDetailPage.js +138 -93
  18. package/dist/cosmicintersection/pages/collections/CollectionDetailPage.js.map +1 -1
  19. package/dist/cosmicintersection/pages/collections/CollectionFormPage.js.map +1 -1
  20. package/dist/cosmicintersection/pages/dashboard/DashboardPage.js +26 -15
  21. package/dist/cosmicintersection/pages/dashboard/DashboardPage.js.map +1 -1
  22. package/dist/cosmicintersection/pages/datasets/DatasetDetailPage.js +345 -219
  23. package/dist/cosmicintersection/pages/datasets/DatasetDetailPage.js.map +1 -1
  24. package/dist/cosmicintersection/pages/datasets/DatasetsPage.js +321 -163
  25. package/dist/cosmicintersection/pages/datasets/DatasetsPage.js.map +1 -1
  26. package/dist/cosmicintersection/pages/discovery/DiscoveryPage.js +358 -0
  27. package/dist/cosmicintersection/pages/discovery/DiscoveryPage.js.map +1 -0
  28. package/dist/cosmicintersection/pages/lineage/LineagePage.js +171 -0
  29. package/dist/cosmicintersection/pages/lineage/LineagePage.js.map +1 -0
  30. package/dist/cosmicintersection/pages/notebooks/NotebookDetailPage.js +232 -162
  31. package/dist/cosmicintersection/pages/notebooks/NotebookDetailPage.js.map +1 -1
  32. package/dist/cosmicintersection/pages/notebooks/NotebooksPage.js +143 -82
  33. package/dist/cosmicintersection/pages/notebooks/NotebooksPage.js.map +1 -1
  34. package/dist/cosmicintersection/pages/pipelines/PipelineDetailPage.js +286 -142
  35. package/dist/cosmicintersection/pages/pipelines/PipelineDetailPage.js.map +1 -1
  36. package/dist/cosmicintersection/pages/pipelines/PipelineFormPage.js +305 -103
  37. package/dist/cosmicintersection/pages/pipelines/PipelineFormPage.js.map +1 -1
  38. package/dist/cosmicintersection/pages/pipelines/PipelinesPage.js +183 -79
  39. package/dist/cosmicintersection/pages/pipelines/PipelinesPage.js.map +1 -1
  40. package/dist/cosmicintersection/pages/releases/ReleasesPage.js +262 -170
  41. package/dist/cosmicintersection/pages/releases/ReleasesPage.js.map +1 -1
  42. package/dist/cosmicintersection/pages/runs/RunDetailPage.js +179 -136
  43. package/dist/cosmicintersection/pages/runs/RunDetailPage.js.map +1 -1
  44. package/dist/cosmicintersection/pages/runs/RunsPage.js +124 -47
  45. package/dist/cosmicintersection/pages/runs/RunsPage.js.map +1 -1
  46. package/dist/cosmicintersection/pages/sources/SourceDetailPage.js +114 -85
  47. package/dist/cosmicintersection/pages/sources/SourceDetailPage.js.map +1 -1
  48. package/dist/cosmicintersection/pages/sources/SourcesPage.js +263 -152
  49. package/dist/cosmicintersection/pages/sources/SourcesPage.js.map +1 -1
  50. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"RunDetailPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/runs/RunDetailPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ListTree, Plus } from 'lucide-react';\nimport {\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Input,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosAddRunStepMutation,\n useCosRunQuery,\n useCosRunStepsQuery,\n useCosTransitionRunMutation,\n type CosRunStatus,\n type CosStepKind,\n} from '../../api/cosmicApi';\nimport {\n ActivityFeedPanel,\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n SafeLink,\n StatusBadge,\n formatEnumLabel,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\nconst STEP_KIND_OPTIONS: CosStepKind[] = [\n 'INGEST',\n 'CALIBRATE',\n 'TRANSFORM',\n 'ANALYZE',\n 'EXPORT',\n 'NOTIFY',\n];\n\n// Valid forward transitions for the run state machine. Terminal states have none.\nconst NEXT_STATUSES: Record<CosRunStatus, CosRunStatus[]> = {\n QUEUED: ['RUNNING', 'CANCELLED'],\n RUNNING: ['SUCCEEDED', 'FAILED', 'CANCELLED'],\n SUCCEEDED: [],\n FAILED: [],\n CANCELLED: [],\n};\n\nfunction formatDate(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? '—' : date.toLocaleString();\n}\n\nfunction formatDuration(ms?: number | null): string {\n if (ms === null || ms === undefined) {\n return '—';\n }\n if (ms < 1000) {\n return `${ms} ms`;\n }\n const seconds = ms / 1000;\n if (seconds < 60) {\n return `${seconds.toFixed(1)} s`;\n }\n const minutes = Math.floor(seconds / 60);\n const rest = Math.round(seconds % 60);\n return `${minutes}m ${rest}s`;\n}\n\nfunction DetailRow({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <div className=\"flex flex-col gap-1 border-b border-border-seam py-3 last:border-0 sm:flex-row sm:items-start sm:gap-4\">\n <span className=\"w-40 shrink-0 text-sm font-medium text-text-secondary\">{label}</span>\n <span className=\"min-w-0 text-sm text-text-primary\">{children}</span>\n </div>\n );\n}\n\nfunction JsonBlock({ value }: { value?: Record<string, unknown> | null }) {\n if (!value || Object.keys(value).length === 0) {\n return <span className=\"text-sm text-text-secondary\">—</span>;\n }\n return (\n <pre className=\"overflow-x-auto rounded-card border border-border-default bg-bg-sunken p-3 font-mono text-xs text-text-primary\">\n {JSON.stringify(value, null, 2)}\n </pre>\n );\n}\n\nexport default function RunDetailPage() {\n const { id = '' } = useParams<{ id: string }>();\n const { navigate } = useCosmicIntersection();\n\n const { data, loading, error, refetch } = useCosRunQuery(id);\n const {\n data: stepsData,\n loading: stepsLoading,\n error: stepsError,\n refetch: refetchSteps,\n } = useCosRunStepsQuery(id);\n\n const [transitionRun, { loading: transitioning }] = useCosTransitionRunMutation();\n const [addStep, { loading: addingStep }] = useCosAddRunStepMutation();\n\n const [stepDialogOpen, setStepDialogOpen] = useState(false);\n const [stepName, setStepName] = useState('');\n const [stepKind, setStepKind] = useState<CosStepKind>('INGEST');\n const [stepPosition, setStepPosition] = useState('');\n const [stepInputs, setStepInputs] = useState('');\n\n const run = data?.cosRun;\n const steps = useMemo(\n () => [...(stepsData?.cosListRunSteps ?? [])].sort((a, b) => a.position - b.position),\n [stepsData]\n );\n\n const handleTransition = async (nextStatus: CosRunStatus) => {\n if (!run) {\n return;\n }\n const response = await transitionRun({ variables: { id: run.id, status: nextStatus } });\n if (\n !mutationSucceeded(response, response.data?.cosTransitionRun?.id, 'Could not update the run')\n ) {\n return;\n }\n notify.success('Run updated', `Run #${run.runNumber} moved to ${formatEnumLabel(nextStatus)}.`);\n await Promise.all([refetch(), refetchSteps()]);\n };\n\n const resetStepForm = () => {\n setStepName('');\n setStepKind('INGEST');\n setStepPosition('');\n setStepInputs('');\n };\n\n const handleAddStep = async () => {\n if (!run) {\n return;\n }\n if (!stepName.trim()) {\n notify.error('Step name is required', 'Give the step a name before adding it.');\n return;\n }\n\n let inputs: Record<string, unknown> | undefined;\n if (stepInputs.trim()) {\n try {\n const parsed = JSON.parse(stepInputs.trim()) as unknown;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new Error('Inputs must be a JSON object.');\n }\n inputs = parsed as Record<string, unknown>;\n } catch (parseError) {\n notify.error(\n 'Invalid inputs JSON',\n describeError(parseError) ?? 'Please provide valid JSON.'\n );\n return;\n }\n }\n\n const positionValue = stepPosition.trim() ? Number(stepPosition.trim()) : undefined;\n if (positionValue !== undefined && (Number.isNaN(positionValue) || positionValue < 0)) {\n notify.error('Invalid position', 'Position must be a non-negative number.');\n return;\n }\n\n const response = await addStep({\n variables: {\n input: {\n runId: run.id,\n name: stepName.trim(),\n kind: stepKind,\n inputs,\n position: positionValue,\n },\n },\n });\n if (!mutationSucceeded(response, response.data?.cosAddRunStep?.id, 'Could not add the step')) {\n return;\n }\n notify.success('Step added', `“${stepName.trim()}” was added to the run.`);\n setStepDialogOpen(false);\n resetStepForm();\n await refetchSteps();\n };\n\n if (loading) {\n return (\n <PageLayout title=\"Run\" backPath=\"runs\">\n <LoadingState label=\"Loading run…\" />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout title=\"Run\" backPath=\"runs\">\n <ErrorState onRetry={() => void refetch()} />\n </PageLayout>\n );\n }\n\n if (!run) {\n return (\n <PageLayout title=\"Run\" backPath=\"runs\">\n <EmptyState\n title=\"Run not found\"\n description=\"This run may have been removed or you may not have access to it.\"\n action={\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('runs')}>\n Back to runs\n </Button>\n }\n />\n </PageLayout>\n );\n }\n\n const validTransitions = NEXT_STATUSES[run.status] ?? [];\n\n return (\n <PageLayout\n title={`Run #${run.runNumber}`}\n description={run.pipeline ? `Pipeline: ${run.pipeline.name}` : undefined}\n backPath=\"runs\"\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n {validTransitions.map((next) => (\n <Button\n key={next}\n type=\"button\"\n variant={next === 'CANCELLED' || next === 'FAILED' ? 'outline' : 'default'}\n onClick={() => void handleTransition(next)}\n disabled={transitioning}\n >\n {`Mark ${formatEnumLabel(next)}`}\n </Button>\n ))}\n <Button type=\"button\" variant=\"outline\" onClick={() => setStepDialogOpen(true)}>\n <Plus className=\"size-4\" />\n Add step\n </Button>\n </div>\n }\n >\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Overview</CardTitle>\n </CardHeader>\n <CardContent>\n <DetailRow label=\"Status\">\n <StatusBadge status={run.status} />\n </DetailRow>\n <DetailRow label=\"Pipeline\">\n {run.pipeline ? (\n <button\n type=\"button\"\n className=\"text-text-primary underline-offset-2 hover:underline\"\n onClick={() => navigate(`pipelines/${run.pipelineId}`)}\n >\n {run.pipeline.name}\n </button>\n ) : (\n <span className=\"font-mono text-xs\">{run.pipelineId}</span>\n )}\n </DetailRow>\n <DetailRow label=\"Dataset\">\n {run.datasetId ? (\n <button\n type=\"button\"\n className=\"text-text-primary underline-offset-2 hover:underline\"\n onClick={() => navigate(`datasets/${run.datasetId}`)}\n >\n <span className=\"font-mono text-xs\">{run.datasetId}</span>\n </button>\n ) : (\n '—'\n )}\n </DetailRow>\n <DetailRow label=\"Triggered by\">{run.triggeredBy || '—'}</DetailRow>\n <DetailRow label=\"Started\">{formatDate(run.startedAt)}</DetailRow>\n <DetailRow label=\"Finished\">{formatDate(run.finishedAt)}</DetailRow>\n <DetailRow label=\"Duration\">{formatDuration(run.durationMs)}</DetailRow>\n {run.errorMessage ? (\n <DetailRow label=\"Error\">\n <span className=\"text-status-error-text\">{run.errorMessage}</span>\n </DetailRow>\n ) : null}\n {validTransitions.length === 0 ? (\n <DetailRow label=\"Discuss\">\n <SafeLink\n href=\"/conversations\"\n className=\"text-text-primary underline-offset-2 hover:underline\"\n >\n Open discussions\n </SafeLink>\n </DetailRow>\n ) : null}\n </CardContent>\n </Card>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <Card>\n <CardHeader>\n <CardTitle>Parameters</CardTitle>\n </CardHeader>\n <CardContent>\n <JsonBlock value={run.params} />\n </CardContent>\n </Card>\n <Card>\n <CardHeader>\n <CardTitle>Metrics</CardTitle>\n </CardHeader>\n <CardContent>\n <JsonBlock value={run.metrics} />\n </CardContent>\n </Card>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Steps</CardTitle>\n </CardHeader>\n <CardContent>\n {stepsLoading ? (\n <LoadingState variant=\"rows\" rows={4} label=\"Loading steps…\" />\n ) : stepsError ? (\n <ErrorState message=\"Could not load steps.\" onRetry={() => void refetchSteps()} />\n ) : steps.length === 0 ? (\n <EmptyState\n icon={ListTree}\n title=\"No steps yet\"\n description=\"Add steps to record the ingest, calibration, transform, analyze or export stages of this run.\"\n action={\n <Button type=\"button\" onClick={() => setStepDialogOpen(true)}>\n <Plus className=\"size-4\" />\n Add step\n </Button>\n }\n />\n ) : (\n <ol className=\"space-y-3\">\n {steps.map((step) => (\n <li\n key={step.id}\n className=\"flex flex-col gap-2 rounded-card border border-border-default bg-bg-elevated p-4 sm:flex-row sm:items-start sm:justify-between\"\n >\n <div className=\"flex min-w-0 items-start gap-3\">\n <span className=\"mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-xs font-semibold tabular-nums text-text-secondary\">\n {step.position}\n </span>\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"font-medium text-text-primary\">{step.name}</span>\n <StatusBadge status={step.kind} tone=\"neutral\" />\n </div>\n {step.errorMessage ? (\n <p className=\"mt-1 text-xs text-status-error-text\">{step.errorMessage}</p>\n ) : null}\n <p className=\"mt-1 text-xs text-text-secondary\">\n Duration: {formatDuration(step.durationMs)}\n </p>\n </div>\n </div>\n <div className=\"shrink-0\">\n <StatusBadge status={step.status} />\n </div>\n </li>\n ))}\n </ol>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Activity</CardTitle>\n </CardHeader>\n <CardContent>\n <ActivityFeedPanel resourceId={run.id} resourceType=\"cosmicintersection-run\" />\n </CardContent>\n </Card>\n </div>\n\n <Dialog\n open={stepDialogOpen}\n onOpenChange={(next) => {\n if (addingStep) {\n return;\n }\n setStepDialogOpen(next);\n if (!next) {\n resetStepForm();\n }\n }}\n >\n <DialogContent>\n <DialogHeader>\n <DialogTitle>Add step</DialogTitle>\n <DialogDescription>\n Record a stage of this run — its kind, order and optional inputs.\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-4\">\n <div className=\"space-y-1\">\n <Label htmlFor=\"step-name\">Name</Label>\n <Input\n id=\"step-name\"\n value={stepName}\n onChange={(event) => setStepName(event.target.value)}\n placeholder=\"e.g. Calibrate flat fields\"\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div className=\"space-y-1\">\n <Label htmlFor=\"step-kind\">Kind</Label>\n <Select\n value={stepKind}\n onValueChange={(value) => setStepKind(value as CosStepKind)}\n >\n <SelectTrigger id=\"step-kind\" aria-label=\"Step kind\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {STEP_KIND_OPTIONS.map((option) => (\n <SelectItem key={option} value={option}>\n {formatEnumLabel(option)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"space-y-1\">\n <Label htmlFor=\"step-position\">Position</Label>\n <Input\n id=\"step-position\"\n type=\"number\"\n min=\"0\"\n value={stepPosition}\n onChange={(event) => setStepPosition(event.target.value)}\n placeholder=\"Auto\"\n />\n </div>\n </div>\n\n <div className=\"space-y-1\">\n <Label htmlFor=\"step-inputs\">Inputs (JSON)</Label>\n <Textarea\n id=\"step-inputs\"\n value={stepInputs}\n onChange={(event) => setStepInputs(event.target.value)}\n rows={4}\n spellCheck={false}\n className=\"font-mono text-xs\"\n placeholder={'{\\n \"source\": \"raw-frames\"\\n}'}\n />\n <p className=\"text-xs text-text-secondary\">Optional. A JSON object of step inputs.</p>\n </div>\n </div>\n\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => {\n setStepDialogOpen(false);\n resetStepForm();\n }}\n disabled={addingStep}\n >\n Cancel\n </Button>\n <Button type=\"button\" onClick={() => void handleAddStep()} disabled={addingStep}>\n <Plus className=\"size-4\" />\n {addingStep ? 'Adding…' : 'Add step'}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA+CA,IAAM,KAAmC;CACvC;CACA;CACA;CACA;CACA;CACA;CACD,EAGK,KAAsD;CAC1D,QAAQ,CAAC,WAAW,YAAY;CAChC,SAAS;EAAC;EAAa;EAAU;EAAY;CAC7C,WAAW,EAAE;CACb,QAAQ,EAAE;CACV,WAAW,EAAE;CACd;AAED,SAAS,EAAW,GAA+B;AACjD,KAAI,CAAC,EACH,QAAO;CAET,IAAM,IAAO,IAAI,KAAK,EAAM;AAC5B,QAAO,OAAO,MAAM,EAAK,SAAS,CAAC,GAAG,MAAM,EAAK,gBAAgB;;AAGnE,SAAS,EAAe,GAA4B;AAClD,KAAI,KAAO,KACT,QAAO;AAET,KAAI,IAAK,IACP,QAAO,GAAG,EAAG;CAEf,IAAM,IAAU,IAAK;AAMrB,QALI,IAAU,KACL,GAAG,EAAQ,QAAQ,EAAE,CAAC,MAIxB,GAFS,KAAK,MAAM,IAAU,GAAG,CAEtB,IADL,KAAK,MAAM,IAAU,GAAG,CACV;;AAG7B,SAAS,EAAU,EAAE,UAAO,eAA0D;AACpF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,QAAD;GAAM,WAAU;aAAyD;GAAa,CAAA,EACtF,kBAAC,QAAD;GAAM,WAAU;GAAqC;GAAgB,CAAA,CACjE;;;AAIV,SAAS,EAAU,EAAE,YAAqD;AAIxE,QAHI,CAAC,KAAS,OAAO,KAAK,EAAM,CAAC,WAAW,IACnC,kBAAC,QAAD;EAAM,WAAU;YAA8B;EAAQ,CAAA,GAG7D,kBAAC,OAAD;EAAK,WAAU;YACZ,KAAK,UAAU,GAAO,MAAM,EAAE;EAC3B,CAAA;;AAIV,SAAwB,IAAgB;CACtC,IAAM,EAAE,QAAK,OAAO,GAA2B,EACzC,EAAE,gBAAa,GAAuB,EAEtC,EAAE,SAAM,YAAS,UAAO,eAAY,EAAe,EAAG,EACtD,EACJ,MAAM,GACN,SAAS,IACT,OAAO,IACP,SAAS,MACP,GAAoB,EAAG,EAErB,CAAC,IAAe,EAAE,SAAS,QAAmB,GAA6B,EAC3E,CAAC,IAAS,EAAE,SAAS,OAAgB,GAA0B,EAE/D,CAAC,IAAgB,KAAqB,EAAS,GAAM,EACrD,CAAC,GAAU,KAAe,EAAS,GAAG,EACtC,CAAC,GAAU,KAAe,EAAsB,SAAS,EACzD,CAAC,GAAc,KAAmB,EAAS,GAAG,EAC9C,CAAC,GAAY,KAAiB,EAAS,GAAG,EAE1C,IAAM,GAAM,QACZ,IAAQ,SACN,CAAC,GAAI,GAAW,mBAAmB,EAAE,CAAE,CAAC,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,SAAS,EACrF,CAAC,EAAU,CACZ,EAEK,KAAmB,OAAO,MAA6B;AAC3D,MAAI,CAAC,EACH;EAEF,IAAM,IAAW,MAAM,GAAc,EAAE,WAAW;GAAE,IAAI,EAAI;GAAI,QAAQ;GAAY,EAAE,CAAC;AAEpF,IAAkB,GAAU,EAAS,MAAM,kBAAkB,IAAI,2BAA2B,KAI/F,EAAO,QAAQ,eAAe,QAAQ,EAAI,UAAU,YAAY,EAAgB,EAAW,CAAC,GAAG,EAC/F,MAAM,QAAQ,IAAI,CAAC,GAAS,EAAE,GAAc,CAAC,CAAC;IAG1C,UAAsB;AAI1B,EAHA,EAAY,GAAG,EACf,EAAY,SAAS,EACrB,EAAgB,GAAG,EACnB,EAAc,GAAG;IAGb,KAAgB,YAAY;AAChC,MAAI,CAAC,EACH;AAEF,MAAI,CAAC,EAAS,MAAM,EAAE;AACpB,KAAO,MAAM,yBAAyB,yCAAyC;AAC/E;;EAGF,IAAI;AACJ,MAAI,EAAW,MAAM,CACnB,KAAI;GACF,IAAM,IAAS,KAAK,MAAM,EAAW,MAAM,CAAC;AAC5C,OAAI,OAAO,KAAW,aAAY,KAAmB,MAAM,QAAQ,EAAO,CACxE,OAAU,MAAM,gCAAgC;AAElD,OAAS;WACF,GAAY;AACnB,KAAO,MACL,uBACA,GAAc,EAAW,IAAI,6BAC9B;AACD;;EAIJ,IAAM,IAAgB,EAAa,MAAM,GAAG,OAAO,EAAa,MAAM,CAAC,GAAG,KAAA;AAC1E,MAAI,MAAkB,KAAA,MAAc,OAAO,MAAM,EAAc,IAAI,IAAgB,IAAI;AACrF,KAAO,MAAM,oBAAoB,0CAA0C;AAC3E;;EAGF,IAAM,IAAW,MAAM,GAAQ,EAC7B,WAAW,EACT,OAAO;GACL,OAAO,EAAI;GACX,MAAM,EAAS,MAAM;GACrB,MAAM;GACN;GACA,UAAU;GACX,EACF,EACF,CAAC;AACG,IAAkB,GAAU,EAAS,MAAM,eAAe,IAAI,yBAAyB,KAG5F,EAAO,QAAQ,cAAc,IAAI,EAAS,MAAM,CAAC,yBAAyB,EAC1E,EAAkB,GAAM,EACxB,GAAe,EACf,MAAM,GAAc;;AAGtB,KAAI,EACF,QACE,kBAAC,GAAD;EAAY,OAAM;EAAM,UAAS;YAC/B,kBAAC,GAAD,EAAc,OAAM,gBAAiB,CAAA;EAC1B,CAAA;AAIjB,KAAI,EACF,QACE,kBAAC,GAAD;EAAY,OAAM;EAAM,UAAS;YAC/B,kBAAC,GAAD,EAAY,eAAe,KAAK,GAAS,EAAI,CAAA;EAClC,CAAA;AAIjB,KAAI,CAAC,EACH,QACE,kBAAC,GAAD;EAAY,OAAM;EAAM,UAAS;YAC/B,kBAAC,GAAD;GACE,OAAM;GACN,aAAY;GACZ,QACE,kBAAC,GAAD;IAAQ,MAAK;IAAS,SAAQ;IAAU,eAAe,EAAS,OAAO;cAAE;IAEhE,CAAA;GAEX,CAAA;EACS,CAAA;CAIjB,IAAM,IAAmB,GAAc,EAAI,WAAW,EAAE;AAExD,QACE,kBAAC,GAAD;EACE,OAAO,QAAQ,EAAI;EACnB,aAAa,EAAI,WAAW,aAAa,EAAI,SAAS,SAAS,KAAA;EAC/D,UAAS;EACT,SACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACG,EAAiB,KAAK,MACrB,kBAAC,GAAD;IAEE,MAAK;IACL,SAAS,MAAS,eAAe,MAAS,WAAW,YAAY;IACjE,eAAe,KAAK,GAAiB,EAAK;IAC1C,UAAU;cAET,QAAQ,EAAgB,EAAK;IACvB,EAPF,EAOE,CACT,EACF,kBAAC,GAAD;IAAQ,MAAK;IAAS,SAAQ;IAAU,eAAe,EAAkB,GAAK;cAA9E,CACE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,EAAA,WAEpB;MACL;;YArBV,CAwBE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,YAAoB,CAAA,EACpB,CAAA,EACb,kBAAC,GAAD,EAAA,UAAA;KACE,kBAAC,GAAD;MAAW,OAAM;gBACf,kBAAC,GAAD,EAAa,QAAQ,EAAI,QAAU,CAAA;MACzB,CAAA;KACZ,kBAAC,GAAD;MAAW,OAAM;gBACd,EAAI,WACH,kBAAC,UAAD;OACE,MAAK;OACL,WAAU;OACV,eAAe,EAAS,aAAa,EAAI,aAAa;iBAErD,EAAI,SAAS;OACP,CAAA,GAET,kBAAC,QAAD;OAAM,WAAU;iBAAqB,EAAI;OAAkB,CAAA;MAEnD,CAAA;KACZ,kBAAC,GAAD;MAAW,OAAM;gBACd,EAAI,YACH,kBAAC,UAAD;OACE,MAAK;OACL,WAAU;OACV,eAAe,EAAS,YAAY,EAAI,YAAY;iBAEpD,kBAAC,QAAD;QAAM,WAAU;kBAAqB,EAAI;QAAiB,CAAA;OACnD,CAAA,GAET;MAEQ,CAAA;KACZ,kBAAC,GAAD;MAAW,OAAM;gBAAgB,EAAI,eAAe;MAAgB,CAAA;KACpE,kBAAC,GAAD;MAAW,OAAM;gBAAW,EAAW,EAAI,UAAU;MAAa,CAAA;KAClE,kBAAC,GAAD;MAAW,OAAM;gBAAY,EAAW,EAAI,WAAW;MAAa,CAAA;KACpE,kBAAC,GAAD;MAAW,OAAM;gBAAY,EAAe,EAAI,WAAW;MAAa,CAAA;KACvE,EAAI,eACH,kBAAC,GAAD;MAAW,OAAM;gBACf,kBAAC,QAAD;OAAM,WAAU;iBAA0B,EAAI;OAAoB,CAAA;MACxD,CAAA,GACV;KACH,EAAiB,WAAW,IAC3B,kBAAC,GAAD;MAAW,OAAM;gBACf,kBAAC,GAAD;OACE,MAAK;OACL,WAAU;iBACX;OAEU,CAAA;MACD,CAAA,GACV;KACQ,EAAA,CAAA,CACT,EAAA,CAAA;IAEP,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,cAAsB,CAAA,EACtB,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAW,OAAO,EAAI,QAAU,CAAA,EACpB,CAAA,CACT,EAAA,CAAA,EACP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,WAAmB,CAAA,EACnB,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAW,OAAO,EAAI,SAAW,CAAA,EACrB,CAAA,CACT,EAAA,CAAA,CACH;;IAEN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,SAAiB,CAAA,EACjB,CAAA,EACb,kBAAC,GAAD,EAAA,UACG,KACC,kBAAC,GAAD;KAAc,SAAQ;KAAO,MAAM;KAAG,OAAM;KAAmB,CAAA,GAC7D,KACF,kBAAC,GAAD;KAAY,SAAQ;KAAwB,eAAe,KAAK,GAAc;KAAI,CAAA,GAChF,EAAM,WAAW,IACnB,kBAAC,GAAD;KACE,MAAM;KACN,OAAM;KACN,aAAY;KACZ,QACE,kBAAC,GAAD;MAAQ,MAAK;MAAS,eAAe,EAAkB,GAAK;gBAA5D,CACE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,EAAA,WAEpB;;KAEX,CAAA,GAEF,kBAAC,MAAD;KAAI,WAAU;eACX,EAAM,KAAK,MACV,kBAAC,MAAD;MAEE,WAAU;gBAFZ,CAIE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAK;QACD,CAAA,EACP,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,QAAD;WAAM,WAAU;qBAAiC,EAAK;WAAY,CAAA,EAClE,kBAAC,GAAD;WAAa,QAAQ,EAAK;WAAM,MAAK;WAAY,CAAA,CAC7C;;SACL,EAAK,eACJ,kBAAC,KAAD;UAAG,WAAU;oBAAuC,EAAK;UAAiB,CAAA,GACxE;SACJ,kBAAC,KAAD;UAAG,WAAU;oBAAb,CAAgD,cACnC,EAAe,EAAK,WAAW,CACxC;;SACA;UACF;UACN,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD,EAAa,QAAQ,EAAK,QAAU,CAAA;OAChC,CAAA,CACH;QAvBE,EAAK,GAuBP,CACL;KACC,CAAA,EAEK,CAAA,CACT,EAAA,CAAA;IAEP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,YAAoB,CAAA,EACpB,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;KAAmB,YAAY,EAAI;KAAI,cAAa;KAA2B,CAAA,EACnE,CAAA,CACT,EAAA,CAAA;IACH;MAEN,kBAAC,IAAD;GACE,MAAM;GACN,eAAe,MAAS;AAClB,UAGJ,EAAkB,EAAK,EAClB,KACH,GAAe;;aAInB,kBAAC,GAAD,EAAA,UAAA;IACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,IAAD,EAAA,UAAa,YAAsB,CAAA,EACnC,kBAAC,IAAD,EAAA,UAAmB,qEAEC,CAAA,CACP,EAAA,CAAA;IAEf,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QAAO,SAAQ;kBAAY;QAAY,CAAA,EACvC,kBAAC,GAAD;QACE,IAAG;QACH,OAAO;QACP,WAAW,MAAU,EAAY,EAAM,OAAO,MAAM;QACpD,aAAY;QACZ,CAAA,CACE;;MAEN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAO,SAAQ;mBAAY;SAAY,CAAA,EACvC,kBAAC,IAAD;SACE,OAAO;SACP,gBAAgB,MAAU,EAAY,EAAqB;mBAF7D,CAIE,kBAAC,IAAD;UAAe,IAAG;UAAY,cAAW;oBACvC,kBAAC,IAAD,EAAe,CAAA;UACD,CAAA,EAChB,kBAAC,IAAD,EAAA,UACG,GAAkB,KAAK,MACtB,kBAAC,IAAD;UAAyB,OAAO;oBAC7B,EAAgB,EAAO;UACb,EAFI,EAEJ,CACb,EACY,CAAA,CACT;WACL;WACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAO,SAAQ;mBAAgB;SAAgB,CAAA,EAC/C,kBAAC,GAAD;SACE,IAAG;SACH,MAAK;SACL,KAAI;SACJ,OAAO;SACP,WAAW,MAAU,EAAgB,EAAM,OAAO,MAAM;SACxD,aAAY;SACZ,CAAA,CACE;UACF;;MAEN,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD;SAAO,SAAQ;mBAAc;SAAqB,CAAA;QAClD,kBAAC,IAAD;SACE,IAAG;SACH,OAAO;SACP,WAAW,MAAU,EAAc,EAAM,OAAO,MAAM;SACtD,MAAM;SACN,YAAY;SACZ,WAAU;SACV,aAAa;SACb,CAAA;QACF,kBAAC,KAAD;SAAG,WAAU;mBAA8B;SAA2C,CAAA;QAClF;;MACF;;IAEN,kBAAC,IAAD,EAAA,UAAA,CACE,kBAAC,GAAD;KACE,MAAK;KACL,SAAQ;KACR,eAAe;AAEb,MADA,EAAkB,GAAM,EACxB,GAAe;;KAEjB,UAAU;eACX;KAEQ,CAAA,EACT,kBAAC,GAAD;KAAQ,MAAK;KAAS,eAAe,KAAK,IAAe;KAAE,UAAU;eAArE,CACE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,EAC1B,IAAa,YAAY,WACnB;OACI,EAAA,CAAA;IACD,EAAA,CAAA;GACT,CAAA,CACE"}
1
+ {"version":3,"file":"RunDetailPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/runs/RunDetailPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport type { LucideIcon } from 'lucide-react';\nimport { Check, Circle, ListTree, Loader2, Plus, X } from 'lucide-react';\nimport {\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Input,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosAddRunStepMutation,\n useCosRunQuery,\n useCosRunStepsQuery,\n useCosTransitionRunMutation,\n type CosRunStatus,\n type CosStepKind,\n} from '../../api/cosmicApi';\nimport {\n ActivityFeedPanel,\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n SafeLink,\n StatusBadge,\n formatEnumLabel,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\nconst STEP_KIND_OPTIONS: CosStepKind[] = [\n 'INGEST',\n 'CALIBRATE',\n 'TRANSFORM',\n 'ANALYZE',\n 'EXPORT',\n 'NOTIFY',\n];\n\n// Valid forward transitions for the run state machine. Terminal states have none.\nconst NEXT_STATUSES: Record<CosRunStatus, CosRunStatus[]> = {\n QUEUED: ['RUNNING', 'CANCELLED'],\n RUNNING: ['SUCCEEDED', 'FAILED', 'CANCELLED'],\n SUCCEEDED: [],\n FAILED: [],\n CANCELLED: [],\n};\n\n// Per-status timeline node: the icon rendered inside the spine circle + its\n// tone classes. Colour comes only from the semantic status token families.\nconst STEP_NODE: Record<CosRunStatus, { icon: LucideIcon; spin: boolean; wrap: string }> = {\n SUCCEEDED: {\n icon: Check,\n spin: false,\n wrap: 'border-status-success-border bg-status-success-bg-subtle text-status-success-text',\n },\n RUNNING: {\n icon: Loader2,\n spin: true,\n wrap: 'border-status-warning-border bg-status-warning-bg-subtle text-status-warning-text',\n },\n FAILED: {\n icon: X,\n spin: false,\n wrap: 'border-status-error-border bg-status-error-bg-subtle text-status-error-text',\n },\n QUEUED: {\n icon: Circle,\n spin: false,\n wrap: 'border-status-info-border bg-status-info-bg-subtle text-status-info-text',\n },\n CANCELLED: {\n icon: X,\n spin: false,\n wrap: 'border-border-default bg-bg-sunken text-text-secondary',\n },\n};\n\nfunction formatDate(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? '—' : date.toLocaleString();\n}\n\nfunction formatDuration(ms?: number | null): string {\n if (ms === null || ms === undefined) {\n return '—';\n }\n if (ms < 1000) {\n return `${ms} ms`;\n }\n const seconds = ms / 1000;\n if (seconds < 60) {\n return `${seconds.toFixed(1)} s`;\n }\n const minutes = Math.floor(seconds / 60);\n const rest = Math.round(seconds % 60);\n return `${minutes}m ${rest}s`;\n}\n\nfunction DetailRow({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <div className=\"flex flex-col gap-1 border-b border-border-seam py-3 last:border-0 sm:flex-row sm:items-start sm:gap-4\">\n <span className=\"w-40 shrink-0 text-sm font-medium text-text-secondary\">{label}</span>\n <span className=\"min-w-0 text-sm text-text-primary\">{children}</span>\n </div>\n );\n}\n\nfunction JsonBlock({ value }: { value?: Record<string, unknown> | null }) {\n if (!value || Object.keys(value).length === 0) {\n return <span className=\"text-sm text-text-secondary\">—</span>;\n }\n return (\n <pre className=\"overflow-x-auto rounded-card border border-border-default bg-bg-sunken p-3 font-mono text-xs text-text-primary\">\n {JSON.stringify(value, null, 2)}\n </pre>\n );\n}\n\nexport default function RunDetailPage() {\n const { id = '' } = useParams<{ id: string }>();\n const { navigate } = useCosmicIntersection();\n\n const { data, loading, error, refetch } = useCosRunQuery(id);\n const {\n data: stepsData,\n loading: stepsLoading,\n error: stepsError,\n refetch: refetchSteps,\n } = useCosRunStepsQuery(id);\n\n const [transitionRun, { loading: transitioning }] = useCosTransitionRunMutation();\n const [addStep, { loading: addingStep }] = useCosAddRunStepMutation();\n\n const [stepDialogOpen, setStepDialogOpen] = useState(false);\n const [stepName, setStepName] = useState('');\n const [stepKind, setStepKind] = useState<CosStepKind>('INGEST');\n const [stepPosition, setStepPosition] = useState('');\n const [stepInputs, setStepInputs] = useState('');\n\n const run = data?.cosRun;\n const steps = useMemo(\n () => [...(stepsData?.cosListRunSteps ?? [])].sort((a, b) => a.position - b.position),\n [stepsData]\n );\n\n const handleTransition = async (nextStatus: CosRunStatus) => {\n if (!run) {\n return;\n }\n const response = await transitionRun({ variables: { id: run.id, status: nextStatus } });\n if (\n !mutationSucceeded(response, response.data?.cosTransitionRun?.id, 'Could not update the run')\n ) {\n return;\n }\n notify.success('Run updated', `Run #${run.runNumber} moved to ${formatEnumLabel(nextStatus)}.`);\n await Promise.all([refetch(), refetchSteps()]);\n };\n\n const resetStepForm = () => {\n setStepName('');\n setStepKind('INGEST');\n setStepPosition('');\n setStepInputs('');\n };\n\n const handleAddStep = async () => {\n if (!run) {\n return;\n }\n if (!stepName.trim()) {\n notify.error('Step name is required', 'Give the step a name before adding it.');\n return;\n }\n\n let inputs: Record<string, unknown> | undefined;\n if (stepInputs.trim()) {\n try {\n const parsed = JSON.parse(stepInputs.trim()) as unknown;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new Error('Inputs must be a JSON object.');\n }\n inputs = parsed as Record<string, unknown>;\n } catch (parseError) {\n notify.error(\n 'Invalid inputs JSON',\n describeError(parseError) ?? 'Please provide valid JSON.'\n );\n return;\n }\n }\n\n const positionValue = stepPosition.trim() ? Number(stepPosition.trim()) : undefined;\n if (positionValue !== undefined && (Number.isNaN(positionValue) || positionValue < 0)) {\n notify.error('Invalid position', 'Position must be a non-negative number.');\n return;\n }\n\n const response = await addStep({\n variables: {\n input: {\n runId: run.id,\n name: stepName.trim(),\n kind: stepKind,\n inputs,\n position: positionValue,\n },\n },\n });\n if (!mutationSucceeded(response, response.data?.cosAddRunStep?.id, 'Could not add the step')) {\n return;\n }\n notify.success('Step added', `“${stepName.trim()}” was added to the run.`);\n setStepDialogOpen(false);\n resetStepForm();\n await refetchSteps();\n };\n\n if (loading) {\n return (\n <PageLayout title=\"Run\" backPath=\"runs\">\n <LoadingState label=\"Loading run…\" />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout title=\"Run\" backPath=\"runs\">\n <ErrorState onRetry={() => void refetch()} />\n </PageLayout>\n );\n }\n\n if (!run) {\n return (\n <PageLayout title=\"Run\" backPath=\"runs\">\n <EmptyState\n title=\"Run not found\"\n description=\"This run may have been removed or you may not have access to it.\"\n action={\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('runs')}>\n Back to runs\n </Button>\n }\n />\n </PageLayout>\n );\n }\n\n const validTransitions = NEXT_STATUSES[run.status] ?? [];\n\n return (\n <PageLayout\n title={`Run #${run.runNumber}`}\n description={run.pipeline ? `Pipeline: ${run.pipeline.name}` : undefined}\n backPath=\"runs\"\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n {validTransitions.map((next) => (\n <Button\n key={next}\n type=\"button\"\n variant={next === 'CANCELLED' || next === 'FAILED' ? 'outline' : 'default'}\n onClick={() => void handleTransition(next)}\n disabled={transitioning}\n >\n {`Mark ${formatEnumLabel(next)}`}\n </Button>\n ))}\n <Button type=\"button\" variant=\"outline\" onClick={() => setStepDialogOpen(true)}>\n <Plus className=\"size-4\" />\n Add step\n </Button>\n </div>\n }\n >\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Overview</CardTitle>\n </CardHeader>\n <CardContent>\n <DetailRow label=\"Status\">\n <StatusBadge status={run.status} />\n </DetailRow>\n <DetailRow label=\"Pipeline\">\n {run.pipeline ? (\n <button\n type=\"button\"\n className=\"text-text-primary underline-offset-2 hover:underline\"\n onClick={() => navigate(`pipelines/${run.pipelineId}`)}\n >\n {run.pipeline.name}\n </button>\n ) : (\n <span className=\"font-mono text-xs\">{run.pipelineId}</span>\n )}\n </DetailRow>\n <DetailRow label=\"Dataset\">\n {run.datasetId ? (\n <button\n type=\"button\"\n className=\"text-text-primary underline-offset-2 hover:underline\"\n onClick={() => navigate(`datasets/${run.datasetId}`)}\n >\n <span className=\"font-mono text-xs\">{run.datasetId}</span>\n </button>\n ) : (\n '—'\n )}\n </DetailRow>\n <DetailRow label=\"Triggered by\">{run.triggeredBy || '—'}</DetailRow>\n <DetailRow label=\"Started\">{formatDate(run.startedAt)}</DetailRow>\n <DetailRow label=\"Finished\">{formatDate(run.finishedAt)}</DetailRow>\n <DetailRow label=\"Duration\">{formatDuration(run.durationMs)}</DetailRow>\n {run.errorMessage ? (\n <DetailRow label=\"Error\">\n <span className=\"text-status-error-text\">{run.errorMessage}</span>\n </DetailRow>\n ) : null}\n {validTransitions.length === 0 ? (\n <DetailRow label=\"Discuss\">\n <SafeLink\n href=\"/conversations\"\n className=\"text-text-primary underline-offset-2 hover:underline\"\n >\n Open discussions\n </SafeLink>\n </DetailRow>\n ) : null}\n </CardContent>\n </Card>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <Card>\n <CardHeader>\n <CardTitle>Parameters</CardTitle>\n </CardHeader>\n <CardContent>\n <JsonBlock value={run.params} />\n </CardContent>\n </Card>\n <Card>\n <CardHeader>\n <CardTitle>Metrics</CardTitle>\n </CardHeader>\n <CardContent>\n <JsonBlock value={run.metrics} />\n </CardContent>\n </Card>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Steps</CardTitle>\n </CardHeader>\n <CardContent>\n {stepsLoading ? (\n <LoadingState variant=\"rows\" rows={4} label=\"Loading steps…\" />\n ) : stepsError ? (\n <ErrorState message=\"Could not load steps.\" onRetry={() => void refetchSteps()} />\n ) : steps.length === 0 ? (\n <EmptyState\n icon={ListTree}\n title=\"No steps yet\"\n description=\"Add steps to record the ingest, calibration, transform, analyze or export stages of this run.\"\n action={\n <Button type=\"button\" onClick={() => setStepDialogOpen(true)}>\n <Plus className=\"size-4\" />\n Add step\n </Button>\n }\n />\n ) : (\n <ol className=\"flex flex-col\">\n {steps.map((step, index) => {\n const node = STEP_NODE[step.status] ?? STEP_NODE.QUEUED;\n const NodeIcon = node.icon;\n const isLast = index === steps.length - 1;\n return (\n <li key={step.id} className=\"flex gap-4\">\n {/* Spine: status node + connecting line to the next step. */}\n <div className=\"flex flex-col items-center\">\n <span\n className={`flex size-8 shrink-0 items-center justify-center rounded-full border ${node.wrap}`}\n >\n <NodeIcon\n className={`size-4 ${node.spin ? 'animate-spin' : ''}`}\n aria-hidden=\"true\"\n />\n </span>\n {!isLast ? (\n <span className=\"w-px flex-1 bg-border-default\" aria-hidden=\"true\" />\n ) : null}\n </div>\n\n <div\n className={`min-w-0 flex-1 rounded-card border border-border-default bg-bg-elevated p-4 ${\n isLast ? '' : 'mb-4'\n }`}\n >\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-xs font-semibold tabular-nums text-text-secondary\">\n {index + 1}.\n </span>\n <span className=\"min-w-0 truncate font-medium text-text-primary\">\n {step.name}\n </span>\n <StatusBadge status={step.kind} tone=\"neutral\" />\n <StatusBadge status={step.status} className=\"ml-auto\" />\n </div>\n {step.errorMessage ? (\n <p className=\"mt-1.5 text-xs text-status-error-text\">\n {step.errorMessage}\n </p>\n ) : null}\n <p className=\"mt-1.5 text-xs text-text-secondary\">\n Duration: {formatDuration(step.durationMs)}\n </p>\n </div>\n </li>\n );\n })}\n </ol>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Activity</CardTitle>\n </CardHeader>\n <CardContent>\n <ActivityFeedPanel resourceId={run.id} resourceType=\"cosmicintersection-run\" />\n </CardContent>\n </Card>\n </div>\n\n <Dialog\n open={stepDialogOpen}\n onOpenChange={(next) => {\n if (addingStep) {\n return;\n }\n setStepDialogOpen(next);\n if (!next) {\n resetStepForm();\n }\n }}\n >\n <DialogContent>\n <DialogHeader>\n <DialogTitle>Add step</DialogTitle>\n <DialogDescription>\n Record a stage of this run — its kind, order and optional inputs.\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-4\">\n <div className=\"space-y-1\">\n <Label htmlFor=\"step-name\">Name</Label>\n <Input\n id=\"step-name\"\n value={stepName}\n onChange={(event) => setStepName(event.target.value)}\n placeholder=\"e.g. Calibrate flat fields\"\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div className=\"space-y-1\">\n <Label htmlFor=\"step-kind\">Kind</Label>\n <Select\n value={stepKind}\n onValueChange={(value) => setStepKind(value as CosStepKind)}\n >\n <SelectTrigger id=\"step-kind\" aria-label=\"Step kind\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {STEP_KIND_OPTIONS.map((option) => (\n <SelectItem key={option} value={option}>\n {formatEnumLabel(option)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"space-y-1\">\n <Label htmlFor=\"step-position\">Position</Label>\n <Input\n id=\"step-position\"\n type=\"number\"\n min=\"0\"\n value={stepPosition}\n onChange={(event) => setStepPosition(event.target.value)}\n placeholder=\"Auto\"\n />\n </div>\n </div>\n\n <div className=\"space-y-1\">\n <Label htmlFor=\"step-inputs\">Inputs (JSON)</Label>\n <Textarea\n id=\"step-inputs\"\n value={stepInputs}\n onChange={(event) => setStepInputs(event.target.value)}\n rows={4}\n spellCheck={false}\n className=\"font-mono text-xs\"\n placeholder={'{\\n \"source\": \"raw-frames\"\\n}'}\n />\n <p className=\"text-xs text-text-secondary\">Optional. A JSON object of step inputs.</p>\n </div>\n </div>\n\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => {\n setStepDialogOpen(false);\n resetStepForm();\n }}\n disabled={addingStep}\n >\n Cancel\n </Button>\n <Button type=\"button\" onClick={() => void handleAddStep()} disabled={addingStep}>\n <Plus className=\"size-4\" />\n {addingStep ? 'Adding…' : 'Add step'}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -104,58 +104,135 @@ function P() {
104
104
  icon: g,
105
105
  title: F === j && L === j ? "No runs yet" : "No runs match these filters",
106
106
  description: F === j && L === j ? "Runs appear here once you start a pipeline. Each run tracks its steps, timings and metrics." : "Try a different status or pipeline filter."
107
- }) : /* @__PURE__ */ h(p, { children: [/* @__PURE__ */ m("div", {
108
- className: "overflow-x-auto rounded-card border border-border-default bg-bg-elevated",
109
- children: /* @__PURE__ */ h(w, { children: [/* @__PURE__ */ m(O, { children: /* @__PURE__ */ h(k, { children: [
110
- /* @__PURE__ */ m(D, { children: "Run" }),
111
- /* @__PURE__ */ m(D, { children: "Pipeline" }),
112
- /* @__PURE__ */ m(D, { children: "Status" }),
113
- /* @__PURE__ */ m(D, { children: "Started" }),
114
- /* @__PURE__ */ m(D, { children: "Finished" }),
115
- /* @__PURE__ */ m(D, { children: "Duration" })
116
- ] }) }), /* @__PURE__ */ m(T, { children: q.map((e) => /* @__PURE__ */ h(k, {
117
- className: "cursor-pointer",
118
- onClick: () => P(`runs/${e.id}`),
119
- children: [
120
- /* @__PURE__ */ h(E, {
121
- className: "font-medium text-text-primary",
122
- children: ["#", e.runNumber]
123
- }),
124
- /* @__PURE__ */ m(E, {
125
- className: "text-text-secondary",
126
- children: /* @__PURE__ */ m("span", {
127
- className: "truncate",
107
+ }) : /* @__PURE__ */ h(p, { children: [
108
+ /* @__PURE__ */ m("div", {
109
+ className: "hidden rounded-card border border-border-default bg-bg-elevated md:block",
110
+ children: /* @__PURE__ */ h(w, { children: [/* @__PURE__ */ m(O, { children: /* @__PURE__ */ h(k, { children: [
111
+ /* @__PURE__ */ m(D, { children: "Run" }),
112
+ /* @__PURE__ */ m(D, { children: "Pipeline" }),
113
+ /* @__PURE__ */ m(D, { children: "Status" }),
114
+ /* @__PURE__ */ m(D, { children: "Started" }),
115
+ /* @__PURE__ */ m(D, { children: "Finished" }),
116
+ /* @__PURE__ */ m(D, { children: "Duration" }),
117
+ /* @__PURE__ */ m(D, { children: "Triggered by" })
118
+ ] }) }), /* @__PURE__ */ m(T, { children: q.map((e) => /* @__PURE__ */ h(k, {
119
+ className: "cursor-pointer",
120
+ onClick: () => P(`runs/${e.id}`),
121
+ children: [
122
+ /* @__PURE__ */ h(E, {
123
+ className: "font-medium text-text-primary",
124
+ children: ["#", e.runNumber]
125
+ }),
126
+ /* @__PURE__ */ m(E, {
127
+ className: "text-text-secondary",
128
+ children: /* @__PURE__ */ m("span", {
129
+ className: "truncate",
130
+ children: H.get(e.pipelineId) ?? /* @__PURE__ */ m("span", {
131
+ className: "font-mono text-xs",
132
+ children: e.pipelineId
133
+ })
134
+ })
135
+ }),
136
+ /* @__PURE__ */ m(E, { children: /* @__PURE__ */ m(l, { status: e.status }) }),
137
+ /* @__PURE__ */ m(E, {
138
+ className: "whitespace-nowrap text-text-secondary",
139
+ children: M(e.startedAt)
140
+ }),
141
+ /* @__PURE__ */ m(E, {
142
+ className: "whitespace-nowrap text-text-secondary",
143
+ children: M(e.finishedAt)
144
+ }),
145
+ /* @__PURE__ */ m(E, {
146
+ className: "tabular-nums text-text-secondary",
147
+ children: N(e.durationMs)
148
+ }),
149
+ /* @__PURE__ */ m(E, {
150
+ className: "text-text-secondary",
151
+ children: e.triggeredBy || "—"
152
+ })
153
+ ]
154
+ }, e.id)) })] })
155
+ }),
156
+ /* @__PURE__ */ m("ul", {
157
+ className: "space-y-3 md:hidden",
158
+ children: q.map((e) => /* @__PURE__ */ m("li", { children: /* @__PURE__ */ h("button", {
159
+ type: "button",
160
+ onClick: () => P(`runs/${e.id}`),
161
+ className: "flex w-full flex-col gap-2 rounded-card border border-border-default bg-bg-elevated p-4 text-left transition-colors hover:border-border-strong",
162
+ children: [
163
+ /* @__PURE__ */ h("div", {
164
+ className: "flex items-center justify-between gap-2",
165
+ children: [/* @__PURE__ */ h("span", {
166
+ className: "font-medium text-text-primary",
167
+ children: ["#", e.runNumber]
168
+ }), /* @__PURE__ */ m(l, { status: e.status })]
169
+ }),
170
+ /* @__PURE__ */ m("p", {
171
+ className: "truncate text-sm text-text-secondary",
128
172
  children: H.get(e.pipelineId) ?? /* @__PURE__ */ m("span", {
129
173
  className: "font-mono text-xs",
130
174
  children: e.pipelineId
131
175
  })
176
+ }),
177
+ /* @__PURE__ */ h("dl", {
178
+ className: "grid grid-cols-2 gap-x-3 gap-y-1 text-xs",
179
+ children: [
180
+ /* @__PURE__ */ h("div", {
181
+ className: "flex flex-col",
182
+ children: [/* @__PURE__ */ m("dt", {
183
+ className: "text-text-secondary",
184
+ children: "Started"
185
+ }), /* @__PURE__ */ m("dd", {
186
+ className: "text-text-primary",
187
+ children: M(e.startedAt)
188
+ })]
189
+ }),
190
+ /* @__PURE__ */ h("div", {
191
+ className: "flex flex-col",
192
+ children: [/* @__PURE__ */ m("dt", {
193
+ className: "text-text-secondary",
194
+ children: "Duration"
195
+ }), /* @__PURE__ */ m("dd", {
196
+ className: "tabular-nums text-text-primary",
197
+ children: N(e.durationMs)
198
+ })]
199
+ }),
200
+ /* @__PURE__ */ h("div", {
201
+ className: "flex flex-col",
202
+ children: [/* @__PURE__ */ m("dt", {
203
+ className: "text-text-secondary",
204
+ children: "Finished"
205
+ }), /* @__PURE__ */ m("dd", {
206
+ className: "text-text-primary",
207
+ children: M(e.finishedAt)
208
+ })]
209
+ }),
210
+ /* @__PURE__ */ h("div", {
211
+ className: "flex flex-col",
212
+ children: [/* @__PURE__ */ m("dt", {
213
+ className: "text-text-secondary",
214
+ children: "Triggered by"
215
+ }), /* @__PURE__ */ m("dd", {
216
+ className: "text-text-primary",
217
+ children: e.triggeredBy || "—"
218
+ })]
219
+ })
220
+ ]
132
221
  })
133
- }),
134
- /* @__PURE__ */ m(E, { children: /* @__PURE__ */ m(l, { status: e.status }) }),
135
- /* @__PURE__ */ m(E, {
136
- className: "whitespace-nowrap text-text-secondary",
137
- children: M(e.startedAt)
138
- }),
139
- /* @__PURE__ */ m(E, {
140
- className: "whitespace-nowrap text-text-secondary",
141
- children: M(e.finishedAt)
142
- }),
143
- /* @__PURE__ */ m(E, {
144
- className: "tabular-nums text-text-secondary",
145
- children: N(e.durationMs)
146
- })
147
- ]
148
- }, e.id)) })] })
149
- }), /* @__PURE__ */ m(s, {
150
- offset: z.offset,
151
- limit: z.limit,
152
- loadedCount: q.length,
153
- hasMore: J?.hasMore ?? !1,
154
- onPrev: z.prevPage,
155
- onNext: z.nextPage,
156
- itemLabel: "runs",
157
- loading: W
158
- })] })]
222
+ ]
223
+ }) }, e.id))
224
+ }),
225
+ /* @__PURE__ */ m(s, {
226
+ offset: z.offset,
227
+ limit: z.limit,
228
+ loadedCount: q.length,
229
+ hasMore: J?.hasMore ?? !1,
230
+ onPrev: z.prevPage,
231
+ onNext: z.nextPage,
232
+ itemLabel: "runs",
233
+ loading: W
234
+ })
235
+ ] })]
159
236
  })
160
237
  });
161
238
  }
@@ -1 +1 @@
1
- {"version":3,"file":"RunsPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/runs/RunsPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { Activity, Eye } from 'lucide-react';\nimport {\n Button,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\n\nimport { useCosPipelinesQuery, useCosRunsQuery, type CosRunStatus } from '../../api/cosmicApi';\nimport {\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n Pagination,\n StatusBadge,\n formatEnumLabel,\n usePagination,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\n\nconst STATUS_OPTIONS: CosRunStatus[] = ['QUEUED', 'RUNNING', 'SUCCEEDED', 'FAILED', 'CANCELLED'];\nconst ALL = 'ALL';\n\nfunction formatDate(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? '—' : date.toLocaleString();\n}\n\nfunction formatDuration(ms?: number | null): string {\n if (ms === null || ms === undefined) {\n return '—';\n }\n if (ms < 1000) {\n return `${ms} ms`;\n }\n const seconds = ms / 1000;\n if (seconds < 60) {\n return `${seconds.toFixed(1)} s`;\n }\n const minutes = Math.floor(seconds / 60);\n const rest = Math.round(seconds % 60);\n return `${minutes}m ${rest}s`;\n}\n\nexport default function RunsPage() {\n const { navigate } = useCosmicIntersection();\n const [status, setStatus] = useState<CosRunStatus | typeof ALL>(ALL);\n const [pipelineId, setPipelineId] = useState<string | typeof ALL>(ALL);\n const pagination = usePagination({ resetKey: `${status}:${pipelineId}` });\n\n // Load pipelines to populate the filter and to resolve run -> pipeline names.\n const { data: pipelinesData } = useCosPipelinesQuery({ pagination: { limit: 100, offset: 0 } });\n const pipelines = useMemo(() => pipelinesData?.cosListPipelines.items ?? [], [pipelinesData]);\n const pipelineNameById = useMemo(() => {\n const map = new Map<string, string>();\n pipelines.forEach((pipeline) => map.set(pipeline.id, pipeline.name));\n return map;\n }, [pipelines]);\n\n const { data, loading, error, refetch } = useCosRunsQuery({\n status: status === ALL ? undefined : status,\n pipelineId: pipelineId === ALL ? undefined : pipelineId,\n pagination: { limit: pagination.limit, offset: pagination.offset },\n });\n\n const runs = useMemo(() => data?.cosListRuns.items ?? [], [data]);\n const pageInfo = data?.cosListRuns.pageInfo;\n\n const renderBody = () => {\n if (loading) {\n return <LoadingState variant=\"rows\" rows={6} label=\"Loading runs…\" />;\n }\n if (error) {\n return (\n <ErrorState\n message=\"We could not load pipeline runs. Please try again.\"\n onRetry={() => void refetch()}\n />\n );\n }\n if (runs.length === 0) {\n return (\n <EmptyState\n icon={Activity}\n title={\n status === ALL && pipelineId === ALL ? 'No runs yet' : 'No runs match these filters'\n }\n description={\n status === ALL && pipelineId === ALL\n ? 'Runs appear here once you start a pipeline. Each run tracks its steps, timings and metrics.'\n : 'Try a different status or pipeline filter.'\n }\n />\n );\n }\n\n return (\n <>\n <div className=\"overflow-x-auto rounded-card border border-border-default bg-bg-elevated\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Run</TableHead>\n <TableHead>Pipeline</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Started</TableHead>\n <TableHead>Finished</TableHead>\n <TableHead>Duration</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {runs.map((run) => (\n <TableRow\n key={run.id}\n className=\"cursor-pointer\"\n onClick={() => navigate(`runs/${run.id}`)}\n >\n <TableCell className=\"font-medium text-text-primary\">#{run.runNumber}</TableCell>\n <TableCell className=\"text-text-secondary\">\n <span className=\"truncate\">\n {pipelineNameById.get(run.pipelineId) ?? (\n <span className=\"font-mono text-xs\">{run.pipelineId}</span>\n )}\n </span>\n </TableCell>\n <TableCell>\n <StatusBadge status={run.status} />\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-text-secondary\">\n {formatDate(run.startedAt)}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-text-secondary\">\n {formatDate(run.finishedAt)}\n </TableCell>\n <TableCell className=\"tabular-nums text-text-secondary\">\n {formatDuration(run.durationMs)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={runs.length}\n hasMore={pageInfo?.hasMore ?? false}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"runs\"\n loading={loading}\n />\n </>\n );\n };\n\n return (\n <PageLayout\n title=\"Runs\"\n description=\"Every pipeline execution, with status, timings and outcomes.\"\n actions={\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('pipelines')}>\n <Eye className=\"size-4\" />\n View pipelines\n </Button>\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <span>Status</span>\n <Select\n value={status}\n onValueChange={(value) => setStatus(value as CosRunStatus | typeof ALL)}\n >\n <SelectTrigger aria-label=\"Filter by status\" className=\"w-40\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All statuses</SelectItem>\n {STATUS_OPTIONS.map((option) => (\n <SelectItem key={option} value={option}>\n {formatEnumLabel(option)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <span>Pipeline</span>\n <Select\n value={pipelineId}\n onValueChange={(value) => setPipelineId(value as string | typeof ALL)}\n >\n <SelectTrigger aria-label=\"Filter by pipeline\" className=\"w-56\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All pipelines</SelectItem>\n {pipelines.map((pipeline) => (\n <SelectItem key={pipeline.id} value={pipeline.id}>\n {pipeline.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {renderBody()}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"RunsPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/runs/RunsPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { Activity, Eye } from 'lucide-react';\nimport {\n Button,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\n\nimport { useCosPipelinesQuery, useCosRunsQuery, type CosRunStatus } from '../../api/cosmicApi';\nimport {\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n Pagination,\n StatusBadge,\n formatEnumLabel,\n usePagination,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\n\nconst STATUS_OPTIONS: CosRunStatus[] = ['QUEUED', 'RUNNING', 'SUCCEEDED', 'FAILED', 'CANCELLED'];\nconst ALL = 'ALL';\n\nfunction formatDate(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? '—' : date.toLocaleString();\n}\n\nfunction formatDuration(ms?: number | null): string {\n if (ms === null || ms === undefined) {\n return '—';\n }\n if (ms < 1000) {\n return `${ms} ms`;\n }\n const seconds = ms / 1000;\n if (seconds < 60) {\n return `${seconds.toFixed(1)} s`;\n }\n const minutes = Math.floor(seconds / 60);\n const rest = Math.round(seconds % 60);\n return `${minutes}m ${rest}s`;\n}\n\nexport default function RunsPage() {\n const { navigate } = useCosmicIntersection();\n const [status, setStatus] = useState<CosRunStatus | typeof ALL>(ALL);\n const [pipelineId, setPipelineId] = useState<string | typeof ALL>(ALL);\n const pagination = usePagination({ resetKey: `${status}:${pipelineId}` });\n\n // Load pipelines to populate the filter and to resolve run -> pipeline names.\n const { data: pipelinesData } = useCosPipelinesQuery({ pagination: { limit: 100, offset: 0 } });\n const pipelines = useMemo(() => pipelinesData?.cosListPipelines.items ?? [], [pipelinesData]);\n const pipelineNameById = useMemo(() => {\n const map = new Map<string, string>();\n pipelines.forEach((pipeline) => map.set(pipeline.id, pipeline.name));\n return map;\n }, [pipelines]);\n\n const { data, loading, error, refetch } = useCosRunsQuery({\n status: status === ALL ? undefined : status,\n pipelineId: pipelineId === ALL ? undefined : pipelineId,\n pagination: { limit: pagination.limit, offset: pagination.offset },\n });\n\n const runs = useMemo(() => data?.cosListRuns.items ?? [], [data]);\n const pageInfo = data?.cosListRuns.pageInfo;\n\n const renderBody = () => {\n if (loading) {\n return <LoadingState variant=\"rows\" rows={6} label=\"Loading runs…\" />;\n }\n if (error) {\n return (\n <ErrorState\n message=\"We could not load pipeline runs. Please try again.\"\n onRetry={() => void refetch()}\n />\n );\n }\n if (runs.length === 0) {\n return (\n <EmptyState\n icon={Activity}\n title={\n status === ALL && pipelineId === ALL ? 'No runs yet' : 'No runs match these filters'\n }\n description={\n status === ALL && pipelineId === ALL\n ? 'Runs appear here once you start a pipeline. Each run tracks its steps, timings and metrics.'\n : 'Try a different status or pipeline filter.'\n }\n />\n );\n }\n\n return (\n <>\n {/* Desktop: table. */}\n <div className=\"hidden rounded-card border border-border-default bg-bg-elevated md:block\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Run</TableHead>\n <TableHead>Pipeline</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Started</TableHead>\n <TableHead>Finished</TableHead>\n <TableHead>Duration</TableHead>\n <TableHead>Triggered by</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {runs.map((run) => (\n <TableRow\n key={run.id}\n className=\"cursor-pointer\"\n onClick={() => navigate(`runs/${run.id}`)}\n >\n <TableCell className=\"font-medium text-text-primary\">#{run.runNumber}</TableCell>\n <TableCell className=\"text-text-secondary\">\n <span className=\"truncate\">\n {pipelineNameById.get(run.pipelineId) ?? (\n <span className=\"font-mono text-xs\">{run.pipelineId}</span>\n )}\n </span>\n </TableCell>\n <TableCell>\n <StatusBadge status={run.status} />\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-text-secondary\">\n {formatDate(run.startedAt)}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-text-secondary\">\n {formatDate(run.finishedAt)}\n </TableCell>\n <TableCell className=\"tabular-nums text-text-secondary\">\n {formatDuration(run.durationMs)}\n </TableCell>\n <TableCell className=\"text-text-secondary\">{run.triggeredBy || '—'}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n\n {/* Mobile: stacked cards. */}\n <ul className=\"space-y-3 md:hidden\">\n {runs.map((run) => (\n <li key={run.id}>\n <button\n type=\"button\"\n onClick={() => navigate(`runs/${run.id}`)}\n className=\"flex w-full flex-col gap-2 rounded-card border border-border-default bg-bg-elevated p-4 text-left transition-colors hover:border-border-strong\"\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"font-medium text-text-primary\">#{run.runNumber}</span>\n <StatusBadge status={run.status} />\n </div>\n <p className=\"truncate text-sm text-text-secondary\">\n {pipelineNameById.get(run.pipelineId) ?? (\n <span className=\"font-mono text-xs\">{run.pipelineId}</span>\n )}\n </p>\n <dl className=\"grid grid-cols-2 gap-x-3 gap-y-1 text-xs\">\n <div className=\"flex flex-col\">\n <dt className=\"text-text-secondary\">Started</dt>\n <dd className=\"text-text-primary\">{formatDate(run.startedAt)}</dd>\n </div>\n <div className=\"flex flex-col\">\n <dt className=\"text-text-secondary\">Duration</dt>\n <dd className=\"tabular-nums text-text-primary\">\n {formatDuration(run.durationMs)}\n </dd>\n </div>\n <div className=\"flex flex-col\">\n <dt className=\"text-text-secondary\">Finished</dt>\n <dd className=\"text-text-primary\">{formatDate(run.finishedAt)}</dd>\n </div>\n <div className=\"flex flex-col\">\n <dt className=\"text-text-secondary\">Triggered by</dt>\n <dd className=\"text-text-primary\">{run.triggeredBy || '—'}</dd>\n </div>\n </dl>\n </button>\n </li>\n ))}\n </ul>\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={runs.length}\n hasMore={pageInfo?.hasMore ?? false}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"runs\"\n loading={loading}\n />\n </>\n );\n };\n\n return (\n <PageLayout\n title=\"Runs\"\n description=\"Every pipeline execution, with status, timings and outcomes.\"\n actions={\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('pipelines')}>\n <Eye className=\"size-4\" />\n View pipelines\n </Button>\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <span>Status</span>\n <Select\n value={status}\n onValueChange={(value) => setStatus(value as CosRunStatus | typeof ALL)}\n >\n <SelectTrigger aria-label=\"Filter by status\" className=\"w-40\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All statuses</SelectItem>\n {STATUS_OPTIONS.map((option) => (\n <SelectItem key={option} value={option}>\n {formatEnumLabel(option)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <span>Pipeline</span>\n <Select\n value={pipelineId}\n onValueChange={(value) => setPipelineId(value as string | typeof ALL)}\n >\n <SelectTrigger aria-label=\"Filter by pipeline\" className=\"w-56\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All pipelines</SelectItem>\n {pipelines.map((pipeline) => (\n <SelectItem key={pipeline.id} value={pipeline.id}>\n {pipeline.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {renderBody()}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AA8BA,IAAM,IAAiC;CAAC;CAAU;CAAW;CAAa;CAAU;CAAY,EAC1F,IAAM;AAEZ,SAAS,EAAW,GAA+B;AACjD,KAAI,CAAC,EACH,QAAO;CAET,IAAM,IAAO,IAAI,KAAK,EAAM;AAC5B,QAAO,OAAO,MAAM,EAAK,SAAS,CAAC,GAAG,MAAM,EAAK,gBAAgB;;AAGnE,SAAS,EAAe,GAA4B;AAClD,KAAI,KAAO,KACT,QAAO;AAET,KAAI,IAAK,IACP,QAAO,GAAG,EAAG;CAEf,IAAM,IAAU,IAAK;AAMrB,QALI,IAAU,KACL,GAAG,EAAQ,QAAQ,EAAE,CAAC,MAIxB,GAFS,KAAK,MAAM,IAAU,GAAG,CAEtB,IADL,KAAK,MAAM,IAAU,GAAG,CACV;;AAG7B,SAAwB,IAAW;CACjC,IAAM,EAAE,gBAAa,GAAuB,EACtC,CAAC,GAAQ,KAAa,EAAoC,EAAI,EAC9D,CAAC,GAAY,KAAiB,EAA8B,EAAI,EAChE,IAAa,EAAc,EAAE,UAAU,GAAG,EAAO,GAAG,KAAc,CAAC,EAGnE,EAAE,MAAM,MAAkB,EAAqB,EAAE,YAAY;EAAE,OAAO;EAAK,QAAQ;EAAG,EAAE,CAAC,EACzF,IAAY,QAAc,GAAe,iBAAiB,SAAS,EAAE,EAAE,CAAC,EAAc,CAAC,EACvF,IAAmB,QAAc;EACrC,IAAM,oBAAM,IAAI,KAAqB;AAErC,SADA,EAAU,SAAS,MAAa,EAAI,IAAI,EAAS,IAAI,EAAS,KAAK,CAAC,EAC7D;IACN,CAAC,EAAU,CAAC,EAET,EAAE,SAAM,YAAS,UAAO,eAAY,EAAgB;EACxD,QAAQ,MAAW,IAAM,KAAA,IAAY;EACrC,YAAY,MAAe,IAAM,KAAA,IAAY;EAC7C,YAAY;GAAE,OAAO,EAAW;GAAO,QAAQ,EAAW;GAAQ;EACnE,CAAC,EAEI,IAAO,QAAc,GAAM,YAAY,SAAS,EAAE,EAAE,CAAC,EAAK,CAAC,EAC3D,IAAW,GAAM,YAAY;AAyInC,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SACE,kBAAC,GAAD;GAAQ,MAAK;GAAS,SAAQ;GAAU,eAAe,EAAS,YAAY;aAA5E,CACE,kBAAC,GAAD,EAAK,WAAU,UAAW,CAAA,EAAA,iBAEnB;;YAGX,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,SAAD;KAAO,WAAU;eAAjB,CACE,kBAAC,QAAD,EAAA,UAAM,UAAa,CAAA,EACnB,kBAAC,GAAD;MACE,OAAO;MACP,gBAAgB,MAAU,EAAU,EAAmC;gBAFzE,CAIE,kBAAC,GAAD;OAAe,cAAW;OAAmB,WAAU;iBACrD,kBAAC,GAAD,EAAe,CAAA;OACD,CAAA,EAChB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;OAAY,OAAO;iBAAK;OAAyB,CAAA,EAChD,EAAe,KAAK,MACnB,kBAAC,GAAD;OAAyB,OAAO;iBAC7B,EAAgB,EAAO;OACb,EAFI,EAEJ,CACb,CACY,EAAA,CAAA,CACT;QACH;QAER,kBAAC,SAAD;KAAO,WAAU;eAAjB,CACE,kBAAC,QAAD,EAAA,UAAM,YAAe,CAAA,EACrB,kBAAC,GAAD;MACE,OAAO;MACP,gBAAgB,MAAU,EAAc,EAA6B;gBAFvE,CAIE,kBAAC,GAAD;OAAe,cAAW;OAAqB,WAAU;iBACvD,kBAAC,GAAD,EAAe,CAAA;OACD,CAAA,EAChB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;OAAY,OAAO;iBAAK;OAA0B,CAAA,EACjD,EAAU,KAAK,MACd,kBAAC,GAAD;OAA8B,OAAO,EAAS;iBAC3C,EAAS;OACC,EAFI,EAAS,GAEb,CACb,CACY,EAAA,CAAA,CACT;QACH;OACJ;OA1LN,IACK,kBAAC,GAAD;IAAc,SAAQ;IAAO,MAAM;IAAG,OAAM;IAAkB,CAAA,GAEnE,IAEA,kBAAC,GAAD;IACE,SAAQ;IACR,eAAe,KAAK,GAAS;IAC7B,CAAA,GAGF,EAAK,WAAW,IAEhB,kBAAC,GAAD;IACE,MAAM;IACN,OACE,MAAW,KAAO,MAAe,IAAM,gBAAgB;IAEzD,aACE,MAAW,KAAO,MAAe,IAC7B,gGACA;IAEN,CAAA,GAKJ,kBAAA,GAAA,EAAA,UAAA;IAEE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAA;MACE,kBAAC,GAAD,EAAA,UAAW,OAAe,CAAA;MAC1B,kBAAC,GAAD,EAAA,UAAW,YAAoB,CAAA;MAC/B,kBAAC,GAAD,EAAA,UAAW,UAAkB,CAAA;MAC7B,kBAAC,GAAD,EAAA,UAAW,WAAmB,CAAA;MAC9B,kBAAC,GAAD,EAAA,UAAW,YAAoB,CAAA;MAC/B,kBAAC,GAAD,EAAA,UAAW,YAAoB,CAAA;MAC/B,kBAAC,GAAD,EAAA,UAAW,gBAAwB,CAAA;MAC1B,EAAA,CAAA,EACC,CAAA,EACd,kBAAC,GAAD,EAAA,UACG,EAAK,KAAK,MACT,kBAAC,GAAD;MAEE,WAAU;MACV,eAAe,EAAS,QAAQ,EAAI,KAAK;gBAH3C;OAKE,kBAAC,GAAD;QAAW,WAAU;kBAArB,CAAqD,KAAE,EAAI,UAAsB;;OACjF,kBAAC,GAAD;QAAW,WAAU;kBACnB,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAiB,IAAI,EAAI,WAAW,IACnC,kBAAC,QAAD;UAAM,WAAU;oBAAqB,EAAI;UAAkB,CAAA;SAExD,CAAA;QACG,CAAA;OACZ,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAa,QAAQ,EAAI,QAAU,CAAA,EACzB,CAAA;OACZ,kBAAC,GAAD;QAAW,WAAU;kBAClB,EAAW,EAAI,UAAU;QAChB,CAAA;OACZ,kBAAC,GAAD;QAAW,WAAU;kBAClB,EAAW,EAAI,WAAW;QACjB,CAAA;OACZ,kBAAC,GAAD;QAAW,WAAU;kBAClB,EAAe,EAAI,WAAW;QACrB,CAAA;OACZ,kBAAC,GAAD;QAAW,WAAU;kBAAuB,EAAI,eAAe;QAAgB,CAAA;OACtE;QAzBJ,EAAI,GAyBA,CACX,EACQ,CAAA,CACN,EAAA,CAAA;KACJ,CAAA;IAGN,kBAAC,MAAD;KAAI,WAAU;eACX,EAAK,KAAK,MACT,kBAAC,MAAD,EAAA,UACE,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAS,QAAQ,EAAI,KAAK;MACzC,WAAU;gBAHZ;OAKE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAhB,CAAgD,KAAE,EAAI,UAAiB;YACvE,kBAAC,GAAD,EAAa,QAAQ,EAAI,QAAU,CAAA,CAC/B;;OACN,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAiB,IAAI,EAAI,WAAW,IACnC,kBAAC,QAAD;SAAM,WAAU;mBAAqB,EAAI;SAAkB,CAAA;QAE3D,CAAA;OACJ,kBAAC,MAAD;QAAI,WAAU;kBAAd;SACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,MAAD;WAAI,WAAU;qBAAsB;WAAY,CAAA,EAChD,kBAAC,MAAD;WAAI,WAAU;qBAAqB,EAAW,EAAI,UAAU;WAAM,CAAA,CAC9D;;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,MAAD;WAAI,WAAU;qBAAsB;WAAa,CAAA,EACjD,kBAAC,MAAD;WAAI,WAAU;qBACX,EAAe,EAAI,WAAW;WAC5B,CAAA,CACD;;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,MAAD;WAAI,WAAU;qBAAsB;WAAa,CAAA,EACjD,kBAAC,MAAD;WAAI,WAAU;qBAAqB,EAAW,EAAI,WAAW;WAAM,CAAA,CAC/D;;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,MAAD;WAAI,WAAU;qBAAsB;WAAiB,CAAA,EACrD,kBAAC,MAAD;WAAI,WAAU;qBAAqB,EAAI,eAAe;WAAS,CAAA,CAC3D;;SACH;;OACE;SACN,EApCI,EAAI,GAoCR,CACL;KACC,CAAA;IAEL,kBAAC,GAAD;KACE,QAAQ,EAAW;KACnB,OAAO,EAAW;KAClB,aAAa,EAAK;KAClB,SAAS,GAAU,WAAW;KAC9B,QAAQ,EAAW;KACnB,QAAQ,EAAW;KACnB,WAAU;KACD;KACT,CAAA;IACD,EAAA,CAAA,CA2DG;;EACK,CAAA"}