@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":"PipelineDetailPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/pipelines/PipelineDetailPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { Archive, Pencil, Play } from 'lucide-react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n Button,\n buttonVariants,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosArchivePipelineMutation,\n useCosPipelineQuery,\n useCosRunsQuery,\n useCosStartRunMutation,\n} 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';\nimport { mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\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\nexport default function PipelineDetailPage() {\n const { id = '' } = useParams<{ id: string }>();\n const { navigate } = useCosmicIntersection();\n\n const { data, loading, error, refetch } = useCosPipelineQuery(id);\n const runsPagination = usePagination({ pageSize: 10, resetKey: id });\n const {\n data: runsData,\n loading: runsLoading,\n error: runsError,\n refetch: refetchRuns,\n } = useCosRunsQuery({\n pipelineId: id,\n pagination: { limit: runsPagination.limit, offset: runsPagination.offset },\n });\n\n const [startRun, { loading: starting }] = useCosStartRunMutation();\n const [archivePipeline, { loading: archiving }] = useCosArchivePipelineMutation();\n const [confirmArchive, setConfirmArchive] = useState(false);\n\n const pipeline = data?.cosPipeline;\n const runs = useMemo(() => runsData?.cosListRuns.items ?? [], [runsData]);\n const runsPageInfo = runsData?.cosListRuns.pageInfo;\n\n const handleStartRun = async () => {\n if (!pipeline) {\n return;\n }\n const response = await startRun({ variables: { input: { pipelineId: pipeline.id } } });\n const runId = response.data?.cosStartRun?.id;\n if (!mutationSucceeded(response, runId, 'Could not start a run')) {\n return;\n }\n notify.success('Run started', 'A new run was queued.');\n await Promise.all([refetch(), refetchRuns()]);\n if (runId) {\n navigate(`runs/${runId}`);\n }\n };\n\n const handleArchive = async () => {\n if (!pipeline) {\n return;\n }\n const response = await archivePipeline({ variables: { id: pipeline.id } });\n if (\n !mutationSucceeded(response, response.data?.cosArchivePipeline, 'Could not archive pipeline')\n ) {\n return;\n }\n setConfirmArchive(false);\n notify.success('Pipeline archived', `“${pipeline.name}” was archived.`);\n navigate('pipelines');\n };\n\n if (loading) {\n return (\n <PageLayout title=\"Pipeline\" backPath=\"pipelines\">\n <LoadingState label=\"Loading pipeline…\" />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout title=\"Pipeline\" backPath=\"pipelines\">\n <ErrorState onRetry={() => void refetch()} />\n </PageLayout>\n );\n }\n\n if (!pipeline) {\n return (\n <PageLayout title=\"Pipeline\" backPath=\"pipelines\">\n <EmptyState\n title=\"Pipeline not found\"\n description=\"This pipeline may have been archived or you may not have access to it.\"\n action={\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('pipelines')}>\n Back to pipelines\n </Button>\n }\n />\n </PageLayout>\n );\n }\n\n const isArchived = pipeline.status === 'ARCHIVED';\n\n return (\n <PageLayout\n title={pipeline.name}\n description={pipeline.description ?? undefined}\n backPath=\"pipelines\"\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button\n type=\"button\"\n onClick={() => void handleStartRun()}\n disabled={starting || isArchived}\n >\n <Play className=\"size-4\" />\n {starting ? 'Starting…' : 'Start run'}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => navigate(`pipelines/${pipeline.id}/edit`)}\n >\n <Pencil className=\"size-4\" />\n Edit\n </Button>\n {!isArchived ? (\n <Button type=\"button\" variant=\"outline\" onClick={() => setConfirmArchive(true)}>\n <Archive className=\"size-4\" />\n Archive\n </Button>\n ) : null}\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={pipeline.status} />\n </DetailRow>\n <DetailRow label=\"Trigger\">{formatEnumLabel(pipeline.trigger)}</DetailRow>\n <DetailRow label=\"Schedule\">\n {pipeline.schedule ? (\n <span className=\"font-mono text-xs\">{pipeline.schedule}</span>\n ) : (\n '—'\n )}\n </DetailRow>\n <DetailRow label=\"Version\">v{pipeline.version}</DetailRow>\n <DetailRow label=\"Last run\">{formatDate(pipeline.lastRunAt)}</DetailRow>\n <DetailRow label=\"Created\">{formatDate(pipeline.createdAt)}</DetailRow>\n <DetailRow label=\"Tags\">\n {pipeline.tags.length === 0 ? (\n '—'\n ) : (\n <span className=\"flex flex-wrap gap-1\">\n {pipeline.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </span>\n )}\n </DetailRow>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Definition</CardTitle>\n </CardHeader>\n <CardContent>\n {pipeline.definition && Object.keys(pipeline.definition).length > 0 ? (\n <pre className=\"overflow-x-auto rounded-card border border-border-default bg-bg-sunken p-4 font-mono text-xs text-text-primary\">\n {JSON.stringify(pipeline.definition, null, 2)}\n </pre>\n ) : (\n <p className=\"text-sm text-text-secondary\">\n No definition configured yet. Edit the pipeline to describe its steps.\n </p>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Runs</CardTitle>\n </CardHeader>\n <CardContent>\n {runsLoading ? (\n <LoadingState variant=\"rows\" rows={4} label=\"Loading runs…\" />\n ) : runsError ? (\n <ErrorState message=\"Could not load runs.\" onRetry={() => void refetchRuns()} />\n ) : runs.length === 0 ? (\n <EmptyState\n icon={Play}\n title=\"No runs yet\"\n description=\"Start a run to process data through this pipeline.\"\n action={\n <Button type=\"button\" onClick={() => void handleStartRun()} disabled={isArchived}>\n <Play className=\"size-4\" />\n Start run\n </Button>\n }\n />\n ) : (\n <>\n <div className=\"overflow-x-auto rounded-card border border-border-default\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Run</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Started</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\">\n #{run.runNumber}\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=\"tabular-nums text-text-secondary\">\n {formatDuration(run.durationMs)}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {run.triggeredBy || '—'}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n <Pagination\n offset={runsPagination.offset}\n limit={runsPagination.limit}\n loadedCount={runs.length}\n hasMore={runsPageInfo?.hasMore ?? false}\n onPrev={runsPagination.prevPage}\n onNext={runsPagination.nextPage}\n itemLabel=\"runs\"\n loading={runsLoading}\n />\n </>\n )}\n </CardContent>\n </Card>\n </div>\n\n <AlertDialog\n open={confirmArchive}\n onOpenChange={(next) => (archiving ? undefined : setConfirmArchive(next))}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Archive “{pipeline.name}”?</AlertDialogTitle>\n <AlertDialogDescription>\n Archiving stops the pipeline from being triggered. Existing runs are kept for\n reference.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel disabled={archiving}>Cancel</AlertDialogCancel>\n <AlertDialogAction\n onClick={(event) => {\n event.preventDefault();\n void handleArchive();\n }}\n disabled={archiving}\n className={buttonVariants({ variant: 'destructive' })}\n >\n {archiving ? 'Archiving…' : 'Archive pipeline'}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"PipelineDetailPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/pipelines/PipelineDetailPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { Archive, Clock, Hash, Pencil, Play } from 'lucide-react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n Button,\n buttonVariants,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosArchivePipelineMutation,\n useCosPipelineQuery,\n useCosRunStepsQuery,\n useCosRunsQuery,\n useCosStartRunMutation,\n type CosStepKind,\n} from '../../api/cosmicApi';\nimport {\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n Pagination,\n StatusBadge,\n formatEnumLabel,\n usePagination,\n} from '../../components';\nimport { StepDAG, type StepDAGStep } from '../../components/StepDAG';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\nconst STEP_KINDS: CosStepKind[] = [\n 'INGEST',\n 'CALIBRATE',\n 'TRANSFORM',\n 'ANALYZE',\n 'EXPORT',\n 'NOTIFY',\n];\n\nfunction isStepKind(value: unknown): value is CosStepKind {\n return typeof value === 'string' && (STEP_KINDS as string[]).includes(value);\n}\n\n/**\n * Parse an ordered step list out of a pipeline definition object. Tolerates\n * unknown/legacy shapes — only well-formed `{ kind, name? }` entries survive.\n */\nfunction parseDefinitionSteps(definition?: Record<string, unknown> | null): StepDAGStep[] {\n if (!definition) {\n return [];\n }\n const raw = (definition as { steps?: unknown }).steps;\n if (!Array.isArray(raw)) {\n return [];\n }\n const out: StepDAGStep[] = [];\n for (const item of raw) {\n if (item && typeof item === 'object' && !Array.isArray(item)) {\n const kind = (item as { kind?: unknown }).kind;\n const name = (item as { name?: unknown }).name;\n if (isStepKind(kind)) {\n out.push({ kind, name: typeof name === 'string' ? name : undefined });\n }\n }\n }\n return out;\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\n/** Small pill used in the header meta row (trigger / cron / version). */\nfunction MetaPill({ icon: Icon, children }: { icon?: typeof Clock; children: React.ReactNode }) {\n return (\n <span className=\"inline-flex items-center gap-1.5 rounded-full border border-border-default bg-bg-elevated px-2.5 py-0.5 text-xs text-text-secondary\">\n {Icon ? <Icon className=\"size-3.5\" aria-hidden=\"true\" /> : null}\n {children}\n </span>\n );\n}\n\nexport default function PipelineDetailPage() {\n const { id = '' } = useParams<{ id: string }>();\n const { navigate } = useCosmicIntersection();\n\n const { data, loading, error, refetch } = useCosPipelineQuery(id);\n const runsPagination = usePagination({ pageSize: 10, resetKey: id });\n const {\n data: runsData,\n loading: runsLoading,\n error: runsError,\n refetch: refetchRuns,\n } = useCosRunsQuery({\n pipelineId: id,\n pagination: { limit: runsPagination.limit, offset: runsPagination.offset },\n });\n\n const pipeline = data?.cosPipeline;\n const runs = useMemo(() => runsData?.cosListRuns.items ?? [], [runsData]);\n const runsPageInfo = runsData?.cosListRuns.pageInfo;\n\n // Prefer the authored definition steps. If the definition has none, fall back\n // to the most recent run's recorded steps so the flow is never empty when the\n // pipeline has actually executed.\n const definitionSteps = useMemo(\n () => parseDefinitionSteps(pipeline?.definition),\n [pipeline?.definition]\n );\n const latestRunId = runs[0]?.id ?? '';\n const shouldDerive = Boolean(pipeline) && definitionSteps.length === 0 && Boolean(latestRunId);\n const { data: latestStepsData } = useCosRunStepsQuery(shouldDerive ? latestRunId : '');\n const derivedSteps = useMemo<StepDAGStep[]>(() => {\n const list = latestStepsData?.cosListRunSteps ?? [];\n return [...list]\n .sort((a, b) => a.position - b.position)\n .map((step) => ({ kind: step.kind, name: step.name, status: step.status }));\n }, [latestStepsData]);\n const dagSteps = definitionSteps.length > 0 ? definitionSteps : derivedSteps;\n const derivedFromRun = definitionSteps.length === 0 && derivedSteps.length > 0;\n\n const [startRun, { loading: starting }] = useCosStartRunMutation();\n const [archivePipeline, { loading: archiving }] = useCosArchivePipelineMutation();\n const [confirmArchive, setConfirmArchive] = useState(false);\n\n const handleStartRun = async () => {\n if (!pipeline) {\n return;\n }\n const response = await startRun({ variables: { input: { pipelineId: pipeline.id } } });\n const runId = response.data?.cosStartRun?.id;\n if (!mutationSucceeded(response, runId, 'Could not start a run')) {\n return;\n }\n notify.success('Run started', 'A new run was queued.');\n await Promise.all([refetch(), refetchRuns()]);\n if (runId) {\n navigate(`runs/${runId}`);\n }\n };\n\n const handleArchive = async () => {\n if (!pipeline) {\n return;\n }\n const response = await archivePipeline({ variables: { id: pipeline.id } });\n if (\n !mutationSucceeded(response, response.data?.cosArchivePipeline, 'Could not archive pipeline')\n ) {\n return;\n }\n setConfirmArchive(false);\n notify.success('Pipeline archived', `“${pipeline.name}” was archived.`);\n navigate('pipelines');\n };\n\n if (loading) {\n return (\n <PageLayout title=\"Pipeline\" backPath=\"pipelines\">\n <LoadingState label=\"Loading pipeline…\" />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout title=\"Pipeline\" backPath=\"pipelines\">\n <ErrorState onRetry={() => void refetch()} />\n </PageLayout>\n );\n }\n\n if (!pipeline) {\n return (\n <PageLayout title=\"Pipeline\" backPath=\"pipelines\">\n <EmptyState\n title=\"Pipeline not found\"\n description=\"This pipeline may have been archived or you may not have access to it.\"\n action={\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('pipelines')}>\n Back to pipelines\n </Button>\n }\n />\n </PageLayout>\n );\n }\n\n const isArchived = pipeline.status === 'ARCHIVED';\n const hasDefinition = Boolean(pipeline.definition && Object.keys(pipeline.definition).length > 0);\n\n return (\n <PageLayout\n title={pipeline.name}\n description={pipeline.description ?? undefined}\n backPath=\"pipelines\"\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button\n type=\"button\"\n onClick={() => void handleStartRun()}\n disabled={starting || isArchived}\n >\n <Play className=\"size-4\" />\n {starting ? 'Starting…' : 'Start run'}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => navigate(`pipelines/${pipeline.id}/edit`)}\n >\n <Pencil className=\"size-4\" />\n Edit\n </Button>\n {!isArchived ? (\n <Button type=\"button\" variant=\"outline\" onClick={() => setConfirmArchive(true)}>\n <Archive className=\"size-4\" />\n Archive\n </Button>\n ) : null}\n </div>\n }\n >\n <div className=\"space-y-6\">\n {/* Header meta — status, trigger, cron, version — mirrors the mock's badge row. */}\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={pipeline.status} />\n <MetaPill icon={Clock}>{formatEnumLabel(pipeline.trigger)}</MetaPill>\n {pipeline.schedule ? (\n <MetaPill>\n <span className=\"font-mono\">{pipeline.schedule}</span>\n </MetaPill>\n ) : null}\n <MetaPill icon={Hash}>v{pipeline.version}</MetaPill>\n </div>\n\n {/* Overview — the step-flow DAG (mockup 11). */}\n <Card>\n <CardHeader>\n <CardTitle>Overview</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"flex flex-wrap items-baseline justify-between gap-2\">\n <span className=\"text-sm font-medium text-text-secondary\">Step flow</span>\n {derivedFromRun ? (\n <span className=\"text-xs text-text-secondary\">Derived from the latest run.</span>\n ) : null}\n </div>\n\n {dagSteps.length > 0 ? (\n <StepDAG steps={dagSteps} />\n ) : (\n <div className=\"rounded-card border border-dashed border-border-default bg-bg-sunken px-4 py-8 text-center\">\n <p className=\"text-sm text-text-secondary\">\n No steps defined yet. Edit the pipeline to add ingest, calibrate, transform,\n analyze or export steps.\n </p>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"mt-3\"\n onClick={() => navigate(`pipelines/${pipeline.id}/edit`)}\n >\n <Pencil className=\"size-4\" />\n Add steps\n </Button>\n </div>\n )}\n\n {hasDefinition ? (\n <details className=\"group rounded-card border border-border-default bg-bg-sunken\">\n <summary className=\"flex cursor-pointer list-none items-center justify-between px-4 py-2.5 text-sm font-medium text-text-secondary transition-colors hover:text-text-primary\">\n Advanced — raw definition JSON\n <span className=\"text-xs text-text-secondary transition-transform group-open:rotate-180\">\n ▾\n </span>\n </summary>\n <pre className=\"overflow-x-auto border-t border-border-seam p-4 font-mono text-xs text-text-primary\">\n {JSON.stringify(pipeline.definition, null, 2)}\n </pre>\n </details>\n ) : null}\n </CardContent>\n </Card>\n\n {/* Details — the full attribute list (app value-add over the mock). */}\n <Card>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent>\n <DetailRow label=\"Status\">\n <StatusBadge status={pipeline.status} />\n </DetailRow>\n <DetailRow label=\"Trigger\">{formatEnumLabel(pipeline.trigger)}</DetailRow>\n <DetailRow label=\"Schedule\">\n {pipeline.schedule ? (\n <span className=\"font-mono text-xs\">{pipeline.schedule}</span>\n ) : (\n '—'\n )}\n </DetailRow>\n <DetailRow label=\"Version\">v{pipeline.version}</DetailRow>\n <DetailRow label=\"Last run\">{formatDate(pipeline.lastRunAt)}</DetailRow>\n <DetailRow label=\"Created\">{formatDate(pipeline.createdAt)}</DetailRow>\n <DetailRow label=\"Tags\">\n {pipeline.tags.length === 0 ? (\n '—'\n ) : (\n <span className=\"flex flex-wrap gap-1\">\n {pipeline.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </span>\n )}\n </DetailRow>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Runs</CardTitle>\n </CardHeader>\n <CardContent>\n {runsLoading ? (\n <LoadingState variant=\"rows\" rows={4} label=\"Loading runs…\" />\n ) : runsError ? (\n <ErrorState message=\"Could not load runs.\" onRetry={() => void refetchRuns()} />\n ) : runs.length === 0 ? (\n <EmptyState\n icon={Play}\n title=\"No runs yet\"\n description=\"Start a run to process data through this pipeline.\"\n action={\n <Button type=\"button\" onClick={() => void handleStartRun()} disabled={isArchived}>\n <Play className=\"size-4\" />\n Start run\n </Button>\n }\n />\n ) : (\n <>\n {/* Desktop: table. */}\n <div className=\"hidden rounded-card border border-border-default md:block\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Run</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Started</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\">\n #{run.runNumber}\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=\"tabular-nums text-text-secondary\">\n {formatDuration(run.durationMs)}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {run.triggeredBy || '—'}\n </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 <dl className=\"grid grid-cols-2 gap-x-3 gap-y-1 text-xs text-text-secondary\">\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=\"col-span-2 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={runsPagination.offset}\n limit={runsPagination.limit}\n loadedCount={runs.length}\n hasMore={runsPageInfo?.hasMore ?? false}\n onPrev={runsPagination.prevPage}\n onNext={runsPagination.nextPage}\n itemLabel=\"runs\"\n loading={runsLoading}\n />\n </>\n )}\n </CardContent>\n </Card>\n </div>\n\n <AlertDialog\n open={confirmArchive}\n onOpenChange={(next) => (archiving ? undefined : setConfirmArchive(next))}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Archive “{pipeline.name}”?</AlertDialogTitle>\n <AlertDialogDescription>\n Archiving stops the pipeline from being triggered. Existing runs are kept for\n reference.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel disabled={archiving}>Cancel</AlertDialogCancel>\n <AlertDialogAction\n onClick={(event) => {\n event.preventDefault();\n void handleArchive();\n }}\n disabled={archiving}\n className={buttonVariants({ variant: 'destructive' })}\n >\n {archiving ? 'Archiving…' : 'Archive pipeline'}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -2,127 +2,178 @@ import { useCosmicIntersection as e } from "../../context/CosmicIntersectionCont
2
2
  import { notify as t } from "../../lib/toast.js";
3
3
  import { useCosCreatePipelineMutation as n, useCosPipelineQuery as r, useCosUpdatePipelineMutation as i } from "../../api/cosmicApi.js";
4
4
  import a from "../../components/EntityFormShell.js";
5
- import o from "../../components/EntityTagsField.js";
6
- import { formatEnumLabel as s } from "../../components/StatusBadge.js";
5
+ import ee from "../../components/EntityTagsField.js";
6
+ import { formatEnumLabel as o } from "../../components/StatusBadge.js";
7
7
  import "../../components/index.js";
8
- import { describeError as c, mutationSucceeded as l } from "../../lib/mutationResult.js";
9
- import { useState as u } from "react";
8
+ import { describeError as s, mutationSucceeded as c } from "../../lib/mutationResult.js";
9
+ import { useMemo as l, useState as u } from "react";
10
10
  import { jsx as d, jsxs as f } from "react/jsx-runtime";
11
11
  import { useParams as p } from "react-router-dom";
12
- import { Input as m, Label as h, Select as g, SelectContent as _, SelectItem as v, SelectTrigger as y, SelectValue as b, Textarea as x } from "@burdenoff/fe-libs/ui";
12
+ import { ArrowDown as m, ArrowUp as te, GripVertical as h, Plus as g, Trash2 as _ } from "lucide-react";
13
+ import { Button as v, Input as y, Label as b, Select as x, SelectContent as S, SelectItem as C, SelectTrigger as w, SelectValue as T, Textarea as E } from "@burdenoff/fe-libs/ui";
13
14
  //#region src/cosmicintersection/pages/pipelines/PipelineFormPage.tsx
14
- var S = [
15
+ var D = [
15
16
  "MANUAL",
16
17
  "SCHEDULED",
17
18
  "ON_INGEST",
18
19
  "WEBHOOK"
19
- ], C = {
20
+ ], O = [
21
+ "INGEST",
22
+ "CALIBRATE",
23
+ "TRANSFORM",
24
+ "ANALYZE",
25
+ "EXPORT",
26
+ "NOTIFY"
27
+ ];
28
+ function k(e) {
29
+ return typeof e == "string" && O.includes(e);
30
+ }
31
+ var A = 0, j = () => `step-${A += 1}`, M = {
20
32
  name: "",
21
33
  description: "",
22
34
  trigger: "MANUAL",
23
35
  schedule: "",
24
- definition: "",
36
+ steps: [],
25
37
  tags: []
26
38
  };
27
- function w(e) {
28
- let t = e.trim();
29
- if (!t) return;
30
- let n = JSON.parse(t);
31
- if (typeof n != "object" || !n || Array.isArray(n)) throw Error("Definition must be a JSON object.");
39
+ function N(e) {
40
+ if (!e) return [];
41
+ let t = e.steps;
42
+ if (!Array.isArray(t)) return [];
43
+ let n = [];
44
+ for (let e of t) if (e && typeof e == "object" && !Array.isArray(e)) {
45
+ let t = e.kind, r = e.name;
46
+ k(t) && n.push({
47
+ id: j(),
48
+ kind: t,
49
+ name: typeof r == "string" ? r : ""
50
+ });
51
+ }
32
52
  return n;
33
53
  }
34
- function T() {
35
- let { id: T = "" } = p(), E = !!T, { navigate: D } = e(), { data: O, loading: k } = r(T), [A, { loading: j }] = n(), [M, { loading: N }] = i(), [P, F] = u(C), I = E ? O?.cosPipeline : null, [L, R] = u(null);
36
- I && I.id !== L && (R(I.id), F({
37
- name: I.name ?? "",
38
- description: I.description ?? "",
39
- trigger: I.trigger ?? "MANUAL",
40
- schedule: I.schedule ?? "",
41
- definition: I.definition ? JSON.stringify(I.definition, null, 2) : "",
42
- tags: I.tags ?? []
43
- }));
44
- let z = (e, t) => {
45
- F((n) => ({
54
+ function P(e) {
55
+ return e.map((e) => {
56
+ let t = { kind: e.kind }, n = e.name.trim();
57
+ return n && (t.name = n), t;
58
+ });
59
+ }
60
+ function F() {
61
+ let { id: A = "" } = p(), F = !!A, { navigate: I } = e(), { data: ne, loading: L } = r(A), [R, { loading: z }] = n(), [B, { loading: V }] = i(), [H, U] = u(M), [W, G] = u({}), [K, q] = u(""), [J, Y] = u(null), X = F ? ne?.cosPipeline : null, [re, ie] = u(null);
62
+ if (X && X.id !== re) {
63
+ ie(X.id), U({
64
+ name: X.name ?? "",
65
+ description: X.description ?? "",
66
+ trigger: X.trigger ?? "MANUAL",
67
+ schedule: X.schedule ?? "",
68
+ steps: N(X.definition),
69
+ tags: X.tags ?? []
70
+ });
71
+ let e = { ...X.definition ?? {} };
72
+ delete e.steps, G(e);
73
+ }
74
+ let Z = (e, t) => {
75
+ U((n) => ({
46
76
  ...n,
47
77
  [e]: t
48
78
  }));
79
+ }, ae = l(() => JSON.stringify({
80
+ ...W,
81
+ steps: P(H.steps)
82
+ }, null, 2), [W, H.steps]), oe = () => {
83
+ Z("steps", [...H.steps, {
84
+ id: j(),
85
+ kind: "INGEST",
86
+ name: ""
87
+ }]);
88
+ }, se = (e) => {
89
+ Z("steps", H.steps.filter((t) => t.id !== e));
90
+ }, Q = (e, t) => {
91
+ Z("steps", H.steps.map((n) => n.id === e ? {
92
+ ...n,
93
+ ...t
94
+ } : n));
95
+ }, $ = (e, t) => {
96
+ let n = e + t;
97
+ if (n < 0 || n >= H.steps.length) return;
98
+ let r = [...H.steps], [i] = r.splice(e, 1);
99
+ r.splice(n, 0, i), Z("steps", r);
49
100
  };
50
101
  return /* @__PURE__ */ f(a, {
51
- title: E ? "Edit pipeline" : "New pipeline",
102
+ title: F ? "Edit pipeline" : "New pipeline",
52
103
  description: "Define how raw data is ingested, processed and released.",
53
- purpose: "A pipeline chains ordered steps (ingest, calibrate, transform, analyze, export) over your datasets. Choose how it is triggered, add an optional cron schedule, and describe its steps as a JSON definition.",
54
- backLabel: E ? "Back to pipeline" : "Back to pipelines",
55
- onBack: () => D(E ? `pipelines/${T}` : "pipelines"),
104
+ purpose: "A pipeline chains ordered steps (ingest, calibrate, transform, analyze, export) over your datasets. Choose how it is triggered, add an optional cron schedule, and build its steps below.",
105
+ backLabel: F ? "Back to pipeline" : "Back to pipelines",
106
+ onBack: () => I(F ? `pipelines/${A}` : "pipelines"),
56
107
  onSubmit: async (e) => {
57
- if (e.preventDefault(), !P.name.trim()) {
108
+ if (e.preventDefault(), !H.name.trim()) {
58
109
  t.error("Name is required", "Give the pipeline a name before saving.");
59
110
  return;
60
111
  }
61
- let n;
112
+ let n = P(H.steps), r = { ...W };
113
+ delete r.steps;
114
+ let i;
115
+ i = n.length > 0 ? {
116
+ ...r,
117
+ steps: n
118
+ } : Object.keys(r).length > 0 ? r : void 0;
119
+ let a = H.trigger === "SCHEDULED" ? H.schedule.trim() : "";
62
120
  try {
63
- n = w(P.definition);
64
- } catch (e) {
65
- t.error("Invalid definition JSON", c(e) ?? "Please provide valid JSON.");
66
- return;
67
- }
68
- let r = P.trigger === "SCHEDULED" ? P.schedule.trim() : "";
69
- try {
70
- if (E) {
71
- let e = await M({ variables: {
72
- id: T,
121
+ if (F) {
122
+ let e = await B({ variables: {
123
+ id: A,
73
124
  input: {
74
- name: P.name.trim(),
75
- description: P.description.trim() || void 0,
76
- trigger: P.trigger,
77
- schedule: r || void 0,
78
- definition: n,
79
- tags: P.tags
125
+ name: H.name.trim(),
126
+ description: H.description.trim() || void 0,
127
+ trigger: H.trigger,
128
+ schedule: a || void 0,
129
+ definition: i,
130
+ tags: H.tags
80
131
  }
81
132
  } });
82
- if (!l(e, e.data?.cosUpdatePipeline?.id, "Could not update pipeline")) return;
83
- t.success("Pipeline updated", `“${P.name.trim()}” was saved.`), D(`pipelines/${T}`);
133
+ if (!c(e, e.data?.cosUpdatePipeline?.id, "Could not update pipeline")) return;
134
+ t.success("Pipeline updated", `“${H.name.trim()}” was saved.`), I(`pipelines/${A}`);
84
135
  return;
85
136
  }
86
- let e = await A({ variables: { input: {
87
- name: P.name.trim(),
88
- description: P.description.trim() || void 0,
89
- trigger: P.trigger,
90
- schedule: r || void 0,
91
- definition: n,
92
- tags: P.tags
93
- } } }), i = e.data?.cosCreatePipeline?.id;
94
- if (!l(e, i, "Could not create pipeline")) return;
95
- t.success("Pipeline created", `“${P.name.trim()}” is ready.`), D(i ? `pipelines/${i}` : "pipelines");
137
+ let e = await R({ variables: { input: {
138
+ name: H.name.trim(),
139
+ description: H.description.trim() || void 0,
140
+ trigger: H.trigger,
141
+ schedule: a || void 0,
142
+ definition: i,
143
+ tags: H.tags
144
+ } } }), n = e.data?.cosCreatePipeline?.id;
145
+ if (!c(e, n, "Could not create pipeline")) return;
146
+ t.success("Pipeline created", `“${H.name.trim()}” is ready.`), I(n ? `pipelines/${n}` : "pipelines");
96
147
  } catch (e) {
97
- t.error("Could not save pipeline", c(e));
148
+ t.error("Could not save pipeline", s(e));
98
149
  }
99
150
  },
100
- submitLabel: E ? "Save changes" : "Create pipeline",
101
- saving: j || N,
102
- submitDisabled: E && k,
151
+ submitLabel: F ? "Save changes" : "Create pipeline",
152
+ saving: z || V,
153
+ submitDisabled: F && L,
103
154
  children: [
104
155
  /* @__PURE__ */ f("div", {
105
156
  className: "space-y-1",
106
- children: [/* @__PURE__ */ d(h, {
157
+ children: [/* @__PURE__ */ d(b, {
107
158
  htmlFor: "pipeline-name",
108
159
  children: "Name"
109
- }), /* @__PURE__ */ d(m, {
160
+ }), /* @__PURE__ */ d(y, {
110
161
  id: "pipeline-name",
111
- value: P.name,
112
- onChange: (e) => z("name", e.target.value),
162
+ value: H.name,
163
+ onChange: (e) => Z("name", e.target.value),
113
164
  placeholder: "e.g. Kepler DR3 ingest",
114
165
  required: !0
115
166
  })]
116
167
  }),
117
168
  /* @__PURE__ */ f("div", {
118
169
  className: "space-y-1",
119
- children: [/* @__PURE__ */ d(h, {
170
+ children: [/* @__PURE__ */ d(b, {
120
171
  htmlFor: "pipeline-description",
121
172
  children: "Description"
122
- }), /* @__PURE__ */ d(x, {
173
+ }), /* @__PURE__ */ d(E, {
123
174
  id: "pipeline-description",
124
- value: P.description,
125
- onChange: (e) => z("description", e.target.value),
175
+ value: H.description,
176
+ onChange: (e) => Z("description", e.target.value),
126
177
  rows: 3,
127
178
  placeholder: "What this pipeline does and when it runs."
128
179
  })]
@@ -131,32 +182,32 @@ function T() {
131
182
  className: "grid grid-cols-1 gap-4 md:grid-cols-2",
132
183
  children: [/* @__PURE__ */ f("div", {
133
184
  className: "space-y-1",
134
- children: [/* @__PURE__ */ d(h, {
185
+ children: [/* @__PURE__ */ d(b, {
135
186
  htmlFor: "pipeline-trigger",
136
187
  children: "Trigger"
137
- }), /* @__PURE__ */ f(g, {
138
- value: P.trigger,
139
- onValueChange: (e) => z("trigger", e),
140
- children: [/* @__PURE__ */ d(y, {
188
+ }), /* @__PURE__ */ f(x, {
189
+ value: H.trigger,
190
+ onValueChange: (e) => Z("trigger", e),
191
+ children: [/* @__PURE__ */ d(w, {
141
192
  id: "pipeline-trigger",
142
193
  "aria-label": "Trigger",
143
- children: /* @__PURE__ */ d(b, {})
144
- }), /* @__PURE__ */ d(_, { children: S.map((e) => /* @__PURE__ */ d(v, {
194
+ children: /* @__PURE__ */ d(T, {})
195
+ }), /* @__PURE__ */ d(S, { children: D.map((e) => /* @__PURE__ */ d(C, {
145
196
  value: e,
146
- children: s(e)
197
+ children: o(e)
147
198
  }, e)) })]
148
199
  })]
149
- }), P.trigger === "SCHEDULED" ? /* @__PURE__ */ f("div", {
200
+ }), H.trigger === "SCHEDULED" ? /* @__PURE__ */ f("div", {
150
201
  className: "space-y-1",
151
202
  children: [
152
- /* @__PURE__ */ d(h, {
203
+ /* @__PURE__ */ d(b, {
153
204
  htmlFor: "pipeline-schedule",
154
205
  children: "Schedule (cron)"
155
206
  }),
156
- /* @__PURE__ */ d(m, {
207
+ /* @__PURE__ */ d(y, {
157
208
  id: "pipeline-schedule",
158
- value: P.schedule,
159
- onChange: (e) => z("schedule", e.target.value),
209
+ value: H.schedule,
210
+ onChange: (e) => Z("schedule", e.target.value),
160
211
  placeholder: "0 2 * * *",
161
212
  className: "font-mono"
162
213
  }),
@@ -176,35 +227,186 @@ function T() {
176
227
  }) : null]
177
228
  }),
178
229
  /* @__PURE__ */ f("div", {
179
- className: "space-y-1",
230
+ className: "space-y-2",
180
231
  children: [
181
- /* @__PURE__ */ d(h, {
182
- htmlFor: "pipeline-definition",
183
- children: "Definition (JSON)"
184
- }),
185
- /* @__PURE__ */ d(x, {
186
- id: "pipeline-definition",
187
- value: P.definition,
188
- onChange: (e) => z("definition", e.target.value),
189
- rows: 8,
190
- spellCheck: !1,
191
- className: "font-mono text-xs",
192
- placeholder: "{\n \"steps\": [\n { \"kind\": \"INGEST\", \"name\": \"Fetch raw frames\" }\n ]\n}"
193
- }),
232
+ /* @__PURE__ */ d(b, { children: "Definition — steps" }),
194
233
  /* @__PURE__ */ d("p", {
195
234
  className: "text-xs text-text-secondary",
196
- children: "Optional. A JSON object describing the pipeline steps. Leave blank to configure steps later."
235
+ children: "The ordered stages this pipeline runs. Add a step, pick its kind, optionally name it, and reorder with the arrows."
236
+ }),
237
+ H.steps.length === 0 ? /* @__PURE__ */ d("div", {
238
+ className: "rounded-card border border-dashed border-border-default bg-bg-sunken px-4 py-6 text-center",
239
+ children: /* @__PURE__ */ d("p", {
240
+ className: "text-sm text-text-secondary",
241
+ children: "No steps yet. Add the first stage to start building the flow."
242
+ })
243
+ }) : /* @__PURE__ */ d("ol", {
244
+ className: "space-y-2",
245
+ children: H.steps.map((e, t) => /* @__PURE__ */ f("li", {
246
+ className: "flex items-center gap-2 rounded-card border border-border-default bg-bg-elevated p-2.5",
247
+ children: [
248
+ /* @__PURE__ */ f("span", {
249
+ className: "flex items-center gap-1.5 text-text-secondary",
250
+ children: [/* @__PURE__ */ d(h, {
251
+ className: "size-4 shrink-0",
252
+ "aria-hidden": "true"
253
+ }), /* @__PURE__ */ d("span", {
254
+ className: "w-4 text-xs font-semibold tabular-nums",
255
+ children: t + 1
256
+ })]
257
+ }),
258
+ /* @__PURE__ */ f("div", {
259
+ className: "grid min-w-0 flex-1 grid-cols-1 gap-2 sm:grid-cols-[10rem_1fr]",
260
+ children: [/* @__PURE__ */ f(x, {
261
+ value: e.kind,
262
+ onValueChange: (t) => Q(e.id, { kind: t }),
263
+ children: [/* @__PURE__ */ d(w, {
264
+ "aria-label": `Kind for step ${t + 1}`,
265
+ children: /* @__PURE__ */ d(T, {})
266
+ }), /* @__PURE__ */ d(S, { children: O.map((e) => /* @__PURE__ */ d(C, {
267
+ value: e,
268
+ children: o(e)
269
+ }, e)) })]
270
+ }), /* @__PURE__ */ d(y, {
271
+ value: e.name,
272
+ onChange: (t) => Q(e.id, { name: t.target.value }),
273
+ placeholder: "Optional step name",
274
+ "aria-label": `Name for step ${t + 1}`
275
+ })]
276
+ }),
277
+ /* @__PURE__ */ f("div", {
278
+ className: "flex shrink-0 items-center gap-0.5",
279
+ children: [
280
+ /* @__PURE__ */ d(v, {
281
+ type: "button",
282
+ variant: "ghost",
283
+ size: "icon-sm",
284
+ onClick: () => $(t, -1),
285
+ disabled: t === 0,
286
+ "aria-label": `Move step ${t + 1} up`,
287
+ children: /* @__PURE__ */ d(te, { className: "size-4" })
288
+ }),
289
+ /* @__PURE__ */ d(v, {
290
+ type: "button",
291
+ variant: "ghost",
292
+ size: "icon-sm",
293
+ onClick: () => $(t, 1),
294
+ disabled: t === H.steps.length - 1,
295
+ "aria-label": `Move step ${t + 1} down`,
296
+ children: /* @__PURE__ */ d(m, { className: "size-4" })
297
+ }),
298
+ /* @__PURE__ */ d(v, {
299
+ type: "button",
300
+ variant: "ghost",
301
+ size: "icon-sm",
302
+ onClick: () => se(e.id),
303
+ "aria-label": `Remove step ${t + 1}`,
304
+ className: "text-text-secondary hover:text-status-error-text",
305
+ children: /* @__PURE__ */ d(_, { className: "size-4" })
306
+ })
307
+ ]
308
+ })
309
+ ]
310
+ }, e.id))
311
+ }),
312
+ /* @__PURE__ */ f(v, {
313
+ type: "button",
314
+ variant: "outline",
315
+ size: "sm",
316
+ onClick: oe,
317
+ children: [/* @__PURE__ */ d(g, { className: "size-4" }), "Add step"]
318
+ }),
319
+ /* @__PURE__ */ f("details", {
320
+ className: "group mt-2 rounded-card border border-border-default bg-bg-sunken",
321
+ onToggle: (e) => {
322
+ e.currentTarget.open && (q(ae), Y(null));
323
+ },
324
+ children: [/* @__PURE__ */ f("summary", {
325
+ className: "flex cursor-pointer list-none items-center justify-between px-4 py-2.5 text-sm font-medium text-text-secondary transition-colors hover:text-text-primary",
326
+ children: ["Advanced — edit as JSON", /* @__PURE__ */ d("span", {
327
+ className: "text-xs transition-transform group-open:rotate-180",
328
+ children: "▾"
329
+ })]
330
+ }), /* @__PURE__ */ f("div", {
331
+ className: "space-y-2 border-t border-border-seam p-4",
332
+ children: [
333
+ /* @__PURE__ */ d(E, {
334
+ value: K,
335
+ onChange: (e) => {
336
+ q(e.target.value), Y(null);
337
+ },
338
+ rows: 8,
339
+ spellCheck: !1,
340
+ className: "font-mono text-xs",
341
+ "aria-label": "Definition JSON"
342
+ }),
343
+ J ? /* @__PURE__ */ d("p", {
344
+ className: "text-xs text-status-error-text",
345
+ children: J
346
+ }) : null,
347
+ /* @__PURE__ */ f("div", {
348
+ className: "flex items-center justify-between gap-2",
349
+ children: [/* @__PURE__ */ d("p", {
350
+ className: "text-xs text-text-secondary",
351
+ children: "Edit the raw definition, then apply it back to the builder above."
352
+ }), /* @__PURE__ */ d(v, {
353
+ type: "button",
354
+ variant: "outline",
355
+ size: "sm",
356
+ onClick: () => {
357
+ let e;
358
+ try {
359
+ e = JSON.parse(K);
360
+ } catch (e) {
361
+ Y(s(e) ?? "Invalid JSON.");
362
+ return;
363
+ }
364
+ if (!e || typeof e != "object" || Array.isArray(e)) {
365
+ Y("Definition must be a JSON object with a \"steps\" array.");
366
+ return;
367
+ }
368
+ let n = e.steps;
369
+ if (!Array.isArray(n)) {
370
+ Y("Definition must contain a \"steps\" array.");
371
+ return;
372
+ }
373
+ let r = [];
374
+ for (let e of n) {
375
+ if (!e || typeof e != "object" || Array.isArray(e)) {
376
+ Y("Each step must be an object with a \"kind\".");
377
+ return;
378
+ }
379
+ let t = e.kind;
380
+ if (!k(t)) {
381
+ Y(`Invalid step kind "${String(t)}". Allowed: ${O.join(", ")}.`);
382
+ return;
383
+ }
384
+ let n = e.name;
385
+ r.push({
386
+ id: j(),
387
+ kind: t,
388
+ name: typeof n == "string" ? n : ""
389
+ });
390
+ }
391
+ let i = { ...e };
392
+ delete i.steps, G(i), Z("steps", r), Y(null), t.success("Steps updated", "The builder now reflects your JSON.");
393
+ },
394
+ children: "Apply to builder"
395
+ })]
396
+ })
397
+ ]
398
+ })]
197
399
  })
198
400
  ]
199
401
  }),
200
- /* @__PURE__ */ d(o, {
201
- value: P.tags,
202
- onChange: (e) => z("tags", e)
402
+ /* @__PURE__ */ d(ee, {
403
+ value: H.tags,
404
+ onChange: (e) => Z("tags", e)
203
405
  })
204
406
  ]
205
407
  });
206
408
  }
207
409
  //#endregion
208
- export { T as default };
410
+ export { F as default };
209
411
 
210
412
  //# sourceMappingURL=PipelineFormPage.js.map