@burdenoff/microfe-cosmicintersection 2026.731.1 → 2026.805.1

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 (27) hide show
  1. package/dist/cosmicintersection/CosmicIntersectionRoutes.js +1 -1
  2. package/dist/cosmicintersection/CosmicIntersectionRoutes.js.map +1 -1
  3. package/dist/cosmicintersection/pages/collections/CollectionDetailPage.js +1 -1
  4. package/dist/cosmicintersection/pages/collections/CollectionDetailPage.js.map +1 -1
  5. package/dist/cosmicintersection/pages/collections/CollectionFormPage.js +1 -1
  6. package/dist/cosmicintersection/pages/collections/CollectionFormPage.js.map +1 -1
  7. package/dist/cosmicintersection/pages/datasets/DatasetDetailPage.js +1 -1
  8. package/dist/cosmicintersection/pages/datasets/DatasetDetailPage.js.map +1 -1
  9. package/dist/cosmicintersection/pages/datasets/DatasetFormPage.js +1 -1
  10. package/dist/cosmicintersection/pages/datasets/DatasetFormPage.js.map +1 -1
  11. package/dist/cosmicintersection/pages/lineage/LineagePage.js +1 -1
  12. package/dist/cosmicintersection/pages/lineage/LineagePage.js.map +1 -1
  13. package/dist/cosmicintersection/pages/notebooks/NotebookDetailPage.js +1 -1
  14. package/dist/cosmicintersection/pages/notebooks/NotebookDetailPage.js.map +1 -1
  15. package/dist/cosmicintersection/pages/notebooks/NotebookFormPage.js +1 -1
  16. package/dist/cosmicintersection/pages/notebooks/NotebookFormPage.js.map +1 -1
  17. package/dist/cosmicintersection/pages/pipelines/PipelineDetailPage.js +1 -1
  18. package/dist/cosmicintersection/pages/pipelines/PipelineDetailPage.js.map +1 -1
  19. package/dist/cosmicintersection/pages/pipelines/PipelineFormPage.js +1 -1
  20. package/dist/cosmicintersection/pages/pipelines/PipelineFormPage.js.map +1 -1
  21. package/dist/cosmicintersection/pages/runs/RunDetailPage.js +1 -1
  22. package/dist/cosmicintersection/pages/runs/RunDetailPage.js.map +1 -1
  23. package/dist/cosmicintersection/pages/sources/SourceDetailPage.js +1 -1
  24. package/dist/cosmicintersection/pages/sources/SourceDetailPage.js.map +1 -1
  25. package/dist/cosmicintersection/pages/sources/SourceFormPage.js +1 -1
  26. package/dist/cosmicintersection/pages/sources/SourceFormPage.js.map +1 -1
  27. package/package.json +4 -4
@@ -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 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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"}
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';\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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"}
@@ -10,7 +10,7 @@ import u from "../../components/PageLayout.js";
10
10
  import d, { formatEnumLabel as f } from "../../components/StatusBadge.js";
11
11
  import "../../components/index.js";
12
12
  import { Fragment as p, jsx as m, jsxs as h } from "react/jsx-runtime";
13
- import { useParams as g } from "react-router-dom";
13
+ import { useParams as g } from "react-router";
14
14
  import { Database as _, Download as v, Pencil as y, Radio as b, RefreshCw as x } from "lucide-react";
15
15
  import { Badge as S, Button as C, Table as w, TableBody as T, TableCell as E, TableHead as D, TableHeader as O, TableRow as k } from "@burdenoff/fe-libs/ui";
16
16
  //#region src/cosmicintersection/pages/sources/SourceDetailPage.tsx
@@ -1 +1 @@
1
- {"version":3,"file":"SourceDetailPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/sources/SourceDetailPage.tsx"],"sourcesContent":["/**\n * SourceDetailPage — full record for one data source.\n *\n * Mirrors CollectionDetailPage: a header with the source's metadata (type/status\n * badges, endpoint, last-synced, tags) over a table of the datasets ingested from\n * this source. Header actions edit the source, mark it synced\n * (`cosMarkSourceSynced`) or archive it (`cosArchiveSource`). Everything is read\n * from the backend — no fabricated data.\n */\nimport { Database, Download, Pencil, Radio, RefreshCw } from 'lucide-react';\nimport {\n Badge,\n Button,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\nimport { useParams } from 'react-router-dom';\n\nimport {\n useCosArchiveSourceMutation,\n useCosMarkSourceSyncedMutation,\n useCosSourceQuery,\n} from '../../api/cosmicApi';\nimport {\n ActionMenu,\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n SafeLink,\n StatusBadge,\n formatEnumLabel,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\n\nfunction formatDateTime(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 formatDate(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) {\n return '—';\n }\n return parsed.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });\n}\n\nfunction MetaTile({ label, value }: { label: string; value: React.ReactNode }) {\n return (\n <div className=\"rounded-card border border-border-default bg-bg-elevated p-4\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">{label}</p>\n <div className=\"mt-1 text-sm text-text-primary\">{value}</div>\n </div>\n );\n}\n\nexport default function SourceDetailPage() {\n const { id = '' } = useParams<{ id: string }>();\n const { navigate } = useCosmicIntersection();\n\n const { data, loading, error, refetch } = useCosSourceQuery(id);\n const source = data?.cosSource ?? null;\n\n const [markSynced, { loading: syncing }] = useCosMarkSourceSyncedMutation();\n const [archiveSource] = useCosArchiveSourceMutation();\n\n const handleMarkSynced = async () => {\n if (!source) {\n return;\n }\n const response = await markSynced({ variables: { id: source.id } });\n if (response.data?.cosMarkSourceSynced) {\n notify.success('Source marked synced', `“${source.name}” is now up to date.`);\n await refetch();\n }\n };\n\n const handleArchive = async () => {\n if (!source) {\n return;\n }\n const response = await archiveSource({ variables: { id: source.id } });\n if (response.error) {\n throw new Error(response.error.message);\n }\n if (!response.data?.cosArchiveSource) {\n notify.error('Could not archive source', 'The server did not confirm the change.');\n throw new Error('Archive not confirmed');\n }\n notify.success('Source archived');\n navigate('sources');\n };\n\n if (loading) {\n return (\n <PageLayout title=\"Source\" backPath=\"sources\">\n <LoadingState label=\"Loading source…\" />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout title=\"Source\" backPath=\"sources\">\n <ErrorState message=\"We could not load this source.\" onRetry={() => void refetch()} />\n </PageLayout>\n );\n }\n\n if (!source) {\n return (\n <PageLayout title=\"Source\" backPath=\"sources\">\n <EmptyState\n icon={Radio}\n title=\"Source not found.\"\n description=\"This source may have been removed or is outside your current scope.\"\n action={\n <Button type=\"button\" onClick={() => navigate('sources')}>\n Back to sources\n </Button>\n }\n />\n </PageLayout>\n );\n }\n\n const datasets = source.datasets ?? [];\n\n const renderDatasets = () => {\n if (datasets.length === 0) {\n return (\n <EmptyState\n icon={Database}\n title=\"No datasets from this source yet\"\n description=\"Datasets that ingest from this source will be listed here.\"\n />\n );\n }\n return (\n <>\n {/* Desktop / tablet: table */}\n <div className=\"hidden overflow-hidden rounded-card border border-border-default bg-bg-elevated md:block\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Name</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Access</TableHead>\n <TableHead>Rows</TableHead>\n <TableHead>Updated</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {datasets.map((dataset) => (\n <TableRow\n key={dataset.id}\n className=\"cursor-pointer\"\n onClick={() => navigate(`datasets/${dataset.id}`)}\n >\n <TableCell className=\"font-medium text-text-primary\">{dataset.name}</TableCell>\n <TableCell>\n <StatusBadge status={dataset.status} />\n </TableCell>\n <TableCell>\n <StatusBadge status={dataset.accessLevel} />\n </TableCell>\n <TableCell className=\"tabular-nums text-sm text-text-secondary\">\n {Number(dataset.rowCount) > 0 ? Number(dataset.rowCount).toLocaleString() : '—'}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {formatDate(dataset.updatedAt)}\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 {datasets.map((dataset) => (\n <li key={dataset.id}>\n <div\n role=\"button\"\n tabIndex={0}\n aria-label={`Open ${dataset.name}`}\n onClick={() => navigate(`datasets/${dataset.id}`)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n navigate(`datasets/${dataset.id}`);\n }\n }}\n className=\"flex flex-col gap-2 rounded-card border border-border-default bg-bg-elevated p-4 outline-none transition-colors hover:bg-bg-sunken focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <p className=\"font-medium text-text-primary\">{dataset.name}</p>\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={dataset.status} />\n <StatusBadge status={dataset.accessLevel} />\n </div>\n <div className=\"flex items-center justify-between gap-2 text-xs text-text-secondary\">\n <span className=\"tabular-nums\">\n {Number(dataset.rowCount) > 0\n ? `${Number(dataset.rowCount).toLocaleString()} rows`\n : 'No rows'}\n </span>\n <span>{formatDate(dataset.updatedAt)}</span>\n </div>\n </div>\n </li>\n ))}\n </ul>\n </>\n );\n };\n\n return (\n <PageLayout\n title={source.name}\n description={source.description ?? undefined}\n backPath=\"sources\"\n actions={\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => navigate(`sources/${source.id}/edit`)}\n >\n <Pencil className=\"size-4\" />\n Edit\n </Button>\n <ActionMenu\n ariaLabel={`Actions for ${source.name}`}\n entityName={source.name}\n onDelete={() => handleArchive()}\n deleteLabel=\"Archive source\"\n extraItems={[\n {\n key: 'mark-synced',\n label: syncing ? 'Marking synced…' : 'Mark synced',\n icon: RefreshCw,\n disabled: syncing,\n onSelect: () => void handleMarkSynced(),\n },\n ]}\n />\n </div>\n }\n >\n <div className=\"space-y-6\">\n {/* Status + key facts */}\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={source.status} />\n <Badge variant=\"secondary\">{formatEnumLabel(source.type)}</Badge>\n </div>\n\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4\">\n <MetaTile\n label=\"Endpoint\"\n value={\n source.endpoint ? (\n <SafeLink\n href={source.endpoint}\n className=\"inline-flex items-center gap-1.5 truncate text-text-primary underline-offset-2 hover:underline\"\n fallbackClassName=\"truncate text-text-secondary\"\n >\n <Download className=\"size-4 shrink-0 text-text-secondary\" aria-hidden=\"true\" />\n <span className=\"truncate\">{source.endpoint}</span>\n </SafeLink>\n ) : (\n <span className=\"text-text-secondary\">Not set</span>\n )\n }\n />\n <MetaTile\n label=\"Last synced\"\n value={<span>{formatDateTime(source.lastSyncedAt)}</span>}\n />\n <MetaTile label=\"Created\" value={<span>{formatDateTime(source.createdAt)}</span>} />\n <MetaTile label=\"Updated\" value={<span>{formatDateTime(source.updatedAt)}</span>} />\n </div>\n\n {source.tags.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {source.tags.map((tag) => (\n <Badge key={tag} variant=\"outline\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n </div>\n ) : null}\n\n {/* Datasets */}\n <section className=\"space-y-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Datasets from this source</h2>\n {renderDatasets()}\n </section>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SourceDetailPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/sources/SourceDetailPage.tsx"],"sourcesContent":["/**\n * SourceDetailPage — full record for one data source.\n *\n * Mirrors CollectionDetailPage: a header with the source's metadata (type/status\n * badges, endpoint, last-synced, tags) over a table of the datasets ingested from\n * this source. Header actions edit the source, mark it synced\n * (`cosMarkSourceSynced`) or archive it (`cosArchiveSource`). Everything is read\n * from the backend — no fabricated data.\n */\nimport { Database, Download, Pencil, Radio, RefreshCw } from 'lucide-react';\nimport {\n Badge,\n Button,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\nimport { useParams } from 'react-router';\n\nimport {\n useCosArchiveSourceMutation,\n useCosMarkSourceSyncedMutation,\n useCosSourceQuery,\n} from '../../api/cosmicApi';\nimport {\n ActionMenu,\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n SafeLink,\n StatusBadge,\n formatEnumLabel,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\n\nfunction formatDateTime(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 formatDate(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) {\n return '—';\n }\n return parsed.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });\n}\n\nfunction MetaTile({ label, value }: { label: string; value: React.ReactNode }) {\n return (\n <div className=\"rounded-card border border-border-default bg-bg-elevated p-4\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">{label}</p>\n <div className=\"mt-1 text-sm text-text-primary\">{value}</div>\n </div>\n );\n}\n\nexport default function SourceDetailPage() {\n const { id = '' } = useParams<{ id: string }>();\n const { navigate } = useCosmicIntersection();\n\n const { data, loading, error, refetch } = useCosSourceQuery(id);\n const source = data?.cosSource ?? null;\n\n const [markSynced, { loading: syncing }] = useCosMarkSourceSyncedMutation();\n const [archiveSource] = useCosArchiveSourceMutation();\n\n const handleMarkSynced = async () => {\n if (!source) {\n return;\n }\n const response = await markSynced({ variables: { id: source.id } });\n if (response.data?.cosMarkSourceSynced) {\n notify.success('Source marked synced', `“${source.name}” is now up to date.`);\n await refetch();\n }\n };\n\n const handleArchive = async () => {\n if (!source) {\n return;\n }\n const response = await archiveSource({ variables: { id: source.id } });\n if (response.error) {\n throw new Error(response.error.message);\n }\n if (!response.data?.cosArchiveSource) {\n notify.error('Could not archive source', 'The server did not confirm the change.');\n throw new Error('Archive not confirmed');\n }\n notify.success('Source archived');\n navigate('sources');\n };\n\n if (loading) {\n return (\n <PageLayout title=\"Source\" backPath=\"sources\">\n <LoadingState label=\"Loading source…\" />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout title=\"Source\" backPath=\"sources\">\n <ErrorState message=\"We could not load this source.\" onRetry={() => void refetch()} />\n </PageLayout>\n );\n }\n\n if (!source) {\n return (\n <PageLayout title=\"Source\" backPath=\"sources\">\n <EmptyState\n icon={Radio}\n title=\"Source not found.\"\n description=\"This source may have been removed or is outside your current scope.\"\n action={\n <Button type=\"button\" onClick={() => navigate('sources')}>\n Back to sources\n </Button>\n }\n />\n </PageLayout>\n );\n }\n\n const datasets = source.datasets ?? [];\n\n const renderDatasets = () => {\n if (datasets.length === 0) {\n return (\n <EmptyState\n icon={Database}\n title=\"No datasets from this source yet\"\n description=\"Datasets that ingest from this source will be listed here.\"\n />\n );\n }\n return (\n <>\n {/* Desktop / tablet: table */}\n <div className=\"hidden overflow-hidden rounded-card border border-border-default bg-bg-elevated md:block\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Name</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Access</TableHead>\n <TableHead>Rows</TableHead>\n <TableHead>Updated</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {datasets.map((dataset) => (\n <TableRow\n key={dataset.id}\n className=\"cursor-pointer\"\n onClick={() => navigate(`datasets/${dataset.id}`)}\n >\n <TableCell className=\"font-medium text-text-primary\">{dataset.name}</TableCell>\n <TableCell>\n <StatusBadge status={dataset.status} />\n </TableCell>\n <TableCell>\n <StatusBadge status={dataset.accessLevel} />\n </TableCell>\n <TableCell className=\"tabular-nums text-sm text-text-secondary\">\n {Number(dataset.rowCount) > 0 ? Number(dataset.rowCount).toLocaleString() : '—'}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {formatDate(dataset.updatedAt)}\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 {datasets.map((dataset) => (\n <li key={dataset.id}>\n <div\n role=\"button\"\n tabIndex={0}\n aria-label={`Open ${dataset.name}`}\n onClick={() => navigate(`datasets/${dataset.id}`)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n navigate(`datasets/${dataset.id}`);\n }\n }}\n className=\"flex flex-col gap-2 rounded-card border border-border-default bg-bg-elevated p-4 outline-none transition-colors hover:bg-bg-sunken focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <p className=\"font-medium text-text-primary\">{dataset.name}</p>\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={dataset.status} />\n <StatusBadge status={dataset.accessLevel} />\n </div>\n <div className=\"flex items-center justify-between gap-2 text-xs text-text-secondary\">\n <span className=\"tabular-nums\">\n {Number(dataset.rowCount) > 0\n ? `${Number(dataset.rowCount).toLocaleString()} rows`\n : 'No rows'}\n </span>\n <span>{formatDate(dataset.updatedAt)}</span>\n </div>\n </div>\n </li>\n ))}\n </ul>\n </>\n );\n };\n\n return (\n <PageLayout\n title={source.name}\n description={source.description ?? undefined}\n backPath=\"sources\"\n actions={\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => navigate(`sources/${source.id}/edit`)}\n >\n <Pencil className=\"size-4\" />\n Edit\n </Button>\n <ActionMenu\n ariaLabel={`Actions for ${source.name}`}\n entityName={source.name}\n onDelete={() => handleArchive()}\n deleteLabel=\"Archive source\"\n extraItems={[\n {\n key: 'mark-synced',\n label: syncing ? 'Marking synced…' : 'Mark synced',\n icon: RefreshCw,\n disabled: syncing,\n onSelect: () => void handleMarkSynced(),\n },\n ]}\n />\n </div>\n }\n >\n <div className=\"space-y-6\">\n {/* Status + key facts */}\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={source.status} />\n <Badge variant=\"secondary\">{formatEnumLabel(source.type)}</Badge>\n </div>\n\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4\">\n <MetaTile\n label=\"Endpoint\"\n value={\n source.endpoint ? (\n <SafeLink\n href={source.endpoint}\n className=\"inline-flex items-center gap-1.5 truncate text-text-primary underline-offset-2 hover:underline\"\n fallbackClassName=\"truncate text-text-secondary\"\n >\n <Download className=\"size-4 shrink-0 text-text-secondary\" aria-hidden=\"true\" />\n <span className=\"truncate\">{source.endpoint}</span>\n </SafeLink>\n ) : (\n <span className=\"text-text-secondary\">Not set</span>\n )\n }\n />\n <MetaTile\n label=\"Last synced\"\n value={<span>{formatDateTime(source.lastSyncedAt)}</span>}\n />\n <MetaTile label=\"Created\" value={<span>{formatDateTime(source.createdAt)}</span>} />\n <MetaTile label=\"Updated\" value={<span>{formatDateTime(source.updatedAt)}</span>} />\n </div>\n\n {source.tags.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {source.tags.map((tag) => (\n <Badge key={tag} variant=\"outline\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n </div>\n ) : null}\n\n {/* Datasets */}\n <section className=\"space-y-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Datasets from this source</h2>\n {renderDatasets()}\n </section>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAwCA,SAAS,EAAe,GAA+B;CACrD,IAAI,CAAC,GACH,OAAO;CAET,IAAM,IAAO,IAAI,KAAK,CAAK;CAC3B,OAAO,OAAO,MAAM,EAAK,QAAQ,CAAC,IAAI,MAAM,EAAK,eAAe;AAClE;AAEA,SAAS,EAAW,GAA+B;CACjD,IAAI,CAAC,GACH,OAAO;CAET,IAAM,IAAS,IAAI,KAAK,CAAK;CAI7B,OAHI,OAAO,MAAM,EAAO,QAAQ,CAAC,IACxB,MAEF,EAAO,mBAAmB,KAAA,GAAW;EAAE,MAAM;EAAW,OAAO;EAAS,KAAK;CAAU,CAAC;AACjG;AAEA,SAAS,EAAS,EAAE,UAAO,YAAoD;CAC7E,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,KAAD;GAAG,WAAU;aAAmE;EAAS,CAAA,GACzF,kBAAC,OAAD;GAAK,WAAU;aAAkC;EAAW,CAAA,CACzD;;AAET;AAEA,SAAwB,IAAmB;CACzC,IAAM,EAAE,QAAK,OAAO,EAA0B,GACxC,EAAE,gBAAa,EAAsB,GAErC,EAAE,SAAM,YAAS,UAAO,eAAY,EAAkB,CAAE,GACxD,IAAS,GAAM,aAAa,MAE5B,CAAC,GAAY,EAAE,SAAS,OAAa,EAA+B,GACpE,CAAC,KAAiB,EAA4B,GAE9C,IAAmB,YAAY;EAC9B,MAID,MADmB,EAAW,EAAE,WAAW,EAAE,IAAI,EAAO,GAAG,EAAE,CAAC,GACrD,MAAM,wBACjB,EAAO,QAAQ,wBAAwB,IAAI,EAAO,KAAK,qBAAqB,GAC5E,MAAM,EAAQ;CAElB,GAEM,IAAgB,YAAY;EAChC,IAAI,CAAC,GACH;EAEF,IAAM,IAAW,MAAM,EAAc,EAAE,WAAW,EAAE,IAAI,EAAO,GAAG,EAAE,CAAC;EACrE,IAAI,EAAS,OACX,MAAU,MAAM,EAAS,MAAM,OAAO;EAExC,IAAI,CAAC,EAAS,MAAM,kBAElB,MADA,EAAO,MAAM,4BAA4B,wCAAwC,GACvE,MAAM,uBAAuB;EAGzC,AADA,EAAO,QAAQ,iBAAiB,GAChC,EAAS,SAAS;CACpB;CAEA,IAAI,GACF,OACE,kBAAC,GAAD;EAAY,OAAM;EAAS,UAAS;YAClC,kBAAC,GAAD,EAAc,OAAM,kBAAmB,CAAA;CAC7B,CAAA;CAIhB,IAAI,GACF,OACE,kBAAC,GAAD;EAAY,OAAM;EAAS,UAAS;YAClC,kBAAC,GAAD;GAAY,SAAQ;GAAiC,eAAe,KAAK,EAAQ;EAAI,CAAA;CAC3E,CAAA;CAIhB,IAAI,CAAC,GACH,OACE,kBAAC,GAAD;EAAY,OAAM;EAAS,UAAS;YAClC,kBAAC,GAAD;GACE,MAAM;GACN,OAAM;GACN,aAAY;GACZ,QACE,kBAAC,GAAD;IAAQ,MAAK;IAAS,eAAe,EAAS,SAAS;cAAG;GAElD,CAAA;EAEX,CAAA;CACS,CAAA;CAIhB,IAAM,IAAW,EAAO,YAAY,CAAC;CA0FrC,OACE,kBAAC,GAAD;EACE,OAAO,EAAO;EACd,aAAa,EAAO,eAAe,KAAA;EACnC,UAAS;EACT,SACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IACE,MAAK;IACL,SAAQ;IACR,eAAe,EAAS,WAAW,EAAO,GAAG,MAAM;cAHrD,CAKE,kBAAC,GAAD,EAAQ,WAAU,SAAU,CAAA,GAAC,MAEvB;OACR,kBAAC,GAAD;IACE,WAAW,eAAe,EAAO;IACjC,YAAY,EAAO;IACnB,gBAAgB,EAAc;IAC9B,aAAY;IACZ,YAAY,CACV;KACE,KAAK;KACL,OAAO,IAAU,oBAAoB;KACrC,MAAM;KACN,UAAU;KACV,gBAAgB,KAAK,EAAiB;IACxC,CACF;GACD,CAAA,CACE;;YAGP,kBAAC,OAAD;GAAK,WAAU;aAAf;IAEE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAa,QAAQ,EAAO,OAAS,CAAA,GACrC,kBAAC,GAAD;MAAO,SAAQ;gBAAa,EAAgB,EAAO,IAAI;KAAS,CAAA,CAC7D;;IAEL,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OACE,OAAM;OACN,OACE,EAAO,WACL,kBAAC,GAAD;QACE,MAAM,EAAO;QACb,WAAU;QACV,mBAAkB;kBAHpB,CAKE,kBAAC,GAAD;SAAU,WAAU;SAAsC,eAAY;QAAQ,CAAA,GAC9E,kBAAC,QAAD;SAAM,WAAU;mBAAY,EAAO;QAAe,CAAA,CAC1C;YAEV,kBAAC,QAAD;QAAM,WAAU;kBAAsB;OAAa,CAAA;MAGxD,CAAA;MACD,kBAAC,GAAD;OACE,OAAM;OACN,OAAO,kBAAC,QAAD,EAAA,UAAO,EAAe,EAAO,YAAY,EAAQ,CAAA;MACzD,CAAA;MACD,kBAAC,GAAD;OAAU,OAAM;OAAU,OAAO,kBAAC,QAAD,EAAA,UAAO,EAAe,EAAO,SAAS,EAAQ,CAAA;MAAI,CAAA;MACnF,kBAAC,GAAD;OAAU,OAAM;OAAU,OAAO,kBAAC,QAAD,EAAA,UAAO,EAAe,EAAO,SAAS,EAAQ,CAAA;MAAI,CAAA;KAChF;;IAEJ,EAAO,KAAK,SAAS,IACpB,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAO,KAAK,KAAK,MAChB,kBAAC,GAAD;MAAiB,SAAQ;MAAU,WAAU;gBAC1C;KACI,GAFK,CAEL,CACR;IACE,CAAA,IACH;IAGJ,kBAAC,WAAD;KAAS,WAAU;eAAnB,CACE,kBAAC,MAAD;MAAI,WAAU;gBAA0C;KAA6B,CAAA,GArKvF,EAAS,WAAW,IAEpB,kBAAC,GAAD;MACE,MAAM;MACN,OAAM;MACN,aAAY;KACb,CAAA,IAIH,kBAAA,GAAA,EAAA,UAAA,CAEE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAA;OACE,kBAAC,GAAD,EAAA,UAAW,OAAe,CAAA;OAC1B,kBAAC,GAAD,EAAA,UAAW,SAAiB,CAAA;OAC5B,kBAAC,GAAD,EAAA,UAAW,SAAiB,CAAA;OAC5B,kBAAC,GAAD,EAAA,UAAW,OAAe,CAAA;OAC1B,kBAAC,GAAD,EAAA,UAAW,UAAkB,CAAA;MACrB,EAAA,CAAA,EACC,CAAA,GACb,kBAAC,GAAD,EAAA,UACG,EAAS,KAAK,MACb,kBAAC,GAAD;OAEE,WAAU;OACV,eAAe,EAAS,YAAY,EAAQ,IAAI;iBAHlD;QAKE,kBAAC,GAAD;SAAW,WAAU;mBAAiC,EAAQ;QAAgB,CAAA;QAC9E,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAa,QAAQ,EAAQ,OAAS,CAAA,EAC7B,CAAA;QACX,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAa,QAAQ,EAAQ,YAAc,CAAA,EAClC,CAAA;QACX,kBAAC,GAAD;SAAW,WAAU;mBAClB,OAAO,EAAQ,QAAQ,IAAI,IAAI,OAAO,EAAQ,QAAQ,EAAE,eAAe,IAAI;QACnE,CAAA;QACX,kBAAC,GAAD;SAAW,WAAU;mBAClB,EAAW,EAAQ,SAAS;QACpB,CAAA;OACH;SAjBH,EAAQ,EAiBL,CACX,EACQ,CAAA,CACN,EAAA,CAAA;KACJ,CAAA,GAGL,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAS,KAAK,MACb,kBAAC,MAAD,EAAA,UACE,kBAAC,OAAD;OACE,MAAK;OACL,UAAU;OACV,cAAY,QAAQ,EAAQ;OAC5B,eAAe,EAAS,YAAY,EAAQ,IAAI;OAChD,YAAY,MAAU;QACpB,CAAI,EAAM,QAAQ,WAAW,EAAM,QAAQ,SACzC,EAAM,eAAe,GACrB,EAAS,YAAY,EAAQ,IAAI;OAErC;OACA,WAAU;iBAXZ;QAaE,kBAAC,KAAD;SAAG,WAAU;mBAAiC,EAAQ;QAAQ,CAAA;QAC9D,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD,EAAa,QAAQ,EAAQ,OAAS,CAAA,GACtC,kBAAC,GAAD,EAAa,QAAQ,EAAQ,YAAc,CAAA,CACxC;;QACL,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,QAAD;UAAM,WAAU;oBACb,OAAO,EAAQ,QAAQ,IAAI,IACxB,GAAG,OAAO,EAAQ,QAAQ,EAAE,eAAe,EAAE,SAC7C;SACA,CAAA,GACN,kBAAC,QAAD,EAAA,UAAO,EAAW,EAAQ,SAAS,EAAQ,CAAA,CACxC;;OACF;SACH,GA5BK,EAAQ,EA4Bb,CACL;KACC,CAAA,CACJ,EAAA,CAAA,CAoFS;;GACN;;CACK,CAAA;AAEhB"}
@@ -7,7 +7,7 @@ import { formatEnumLabel as s } from "../../components/StatusBadge.js";
7
7
  import "../../components/index.js";
8
8
  import { useEffect as c, useMemo as l, useState as u } from "react";
9
9
  import { jsx as d, jsxs as f } from "react/jsx-runtime";
10
- import { useParams as p } from "react-router-dom";
10
+ import { useParams as p } from "react-router";
11
11
  import { Input as m, Select as h, SelectContent as g, SelectItem as _, SelectTrigger as v, SelectValue as y, Textarea as b } from "@burdenoff/fe-libs/ui";
12
12
  //#region src/cosmicintersection/pages/sources/SourceFormPage.tsx
13
13
  var x = [
@@ -1 +1 @@
1
- {"version":3,"file":"SourceFormPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/sources/SourceFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosCreateSourceMutation,\n useCosSourceQuery,\n useCosUpdateSourceMutation,\n type CosSourceStatus,\n type CosSourceType,\n} from '../../api/cosmicApi';\nimport { EntityFormShell, EntityTagsField, formatEnumLabel } from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\n\nconst SOURCE_TYPES: CosSourceType[] = [\n 'TELESCOPE',\n 'SATELLITE',\n 'GROUND_SENSOR',\n 'ARCHIVE',\n 'API',\n 'STREAM',\n 'FILE_DROP',\n 'SIMULATION',\n];\n\nconst SOURCE_STATUSES: CosSourceStatus[] = [\n 'CONFIGURING',\n 'CONNECTED',\n 'SYNCING',\n 'DEGRADED',\n 'ERROR',\n 'DISABLED',\n];\n\ninterface SourceFormState {\n name: string;\n description: string;\n type: CosSourceType;\n status: CosSourceStatus;\n endpoint: string;\n credentialRef: string;\n config: string;\n tags: string[];\n}\n\nconst INITIAL_STATE: SourceFormState = {\n name: '',\n description: '',\n type: 'TELESCOPE',\n status: 'CONFIGURING',\n endpoint: '',\n credentialRef: '',\n config: '',\n tags: [],\n};\n\nconst fieldLabelClass = 'block space-y-1.5 text-sm';\nconst fieldSpanClass = 'font-medium text-text-primary';\n\nexport default function SourceFormPage() {\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useCosmicIntersection();\n\n const { data, loading: loadingSource } = useCosSourceQuery(id);\n const [createSource, { loading: creating }] = useCosCreateSourceMutation();\n const [updateSource, { loading: updating }] = useCosUpdateSourceMutation();\n\n const [form, setForm] = useState<SourceFormState>(INITIAL_STATE);\n const [hydrated, setHydrated] = useState(false);\n\n useEffect(() => {\n if (!isEdit || hydrated || !data?.cosSource) {\n return;\n }\n const source = data.cosSource;\n setForm({\n name: source.name ?? '',\n description: source.description ?? '',\n type: source.type,\n status: source.status,\n endpoint: source.endpoint ?? '',\n credentialRef: '',\n config: source.config ? JSON.stringify(source.config, null, 2) : '',\n tags: source.tags ?? [],\n });\n setHydrated(true);\n }, [isEdit, hydrated, data]);\n\n const onChange = <K extends keyof SourceFormState>(field: K, value: SourceFormState[K]) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const saving = creating || updating;\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!form.name.trim()) {\n notify.error('Name is required', 'Give this source a recognizable name.');\n return;\n }\n\n let config: Record<string, unknown> | undefined;\n if (form.config.trim()) {\n try {\n const parsed = JSON.parse(form.config) as unknown;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new Error('Config must be a JSON object.');\n }\n config = parsed as Record<string, unknown>;\n } catch (parseError) {\n notify.error(\n 'Invalid config JSON',\n parseError instanceof Error ? parseError.message : 'Please provide valid JSON.'\n );\n return;\n }\n }\n\n const base = {\n name: form.name.trim(),\n type: form.type,\n status: form.status,\n description: form.description.trim() || undefined,\n endpoint: form.endpoint.trim() || undefined,\n credentialRef: form.credentialRef.trim() || undefined,\n config,\n tags: form.tags,\n };\n\n if (isEdit) {\n const response = await updateSource({ variables: { id, input: base } });\n if (response.data?.cosUpdateSource?.id) {\n notify.success('Source updated');\n navigate('sources');\n }\n return;\n }\n\n const response = await createSource({ variables: { input: base } });\n if (response.data?.cosCreateSource?.id) {\n notify.success('Source created');\n navigate('sources');\n }\n };\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit source' : 'New data source'}\n description=\"Instruments, archives and feeds that supply raw data into your federation.\"\n purpose=\"A source describes where data comes from — a telescope, satellite feed, archive, API or file drop. Configure it once, then attach datasets that ingest from it and mark it synced as data arrives.\"\n backLabel=\"Back to sources\"\n onBack={() => navigate('sources')}\n onSubmit={(event) => void onSubmit(event)}\n submitLabel={isEdit ? 'Save source' : 'Create source'}\n saving={saving || (isEdit && loadingSource && !hydrated)}\n >\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className={fieldLabelClass}>\n <span className={fieldSpanClass}>Name</span>\n <Input\n value={form.name}\n onChange={(event) => onChange('name', event.target.value)}\n placeholder=\"e.g. VLA Radio Array\"\n required\n />\n </label>\n <label className={fieldLabelClass}>\n <span className={fieldSpanClass}>Endpoint</span>\n <Input\n value={form.endpoint}\n onChange={(event) => onChange('endpoint', event.target.value)}\n placeholder=\"https://archive.example.org/api\"\n />\n </label>\n </div>\n\n <label className={fieldLabelClass}>\n <span className={fieldSpanClass}>Description</span>\n <Textarea\n value={form.description}\n onChange={(event) => onChange('description', event.target.value)}\n rows={3}\n placeholder=\"What this source provides and how it connects.\"\n />\n </label>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className={fieldLabelClass}>\n <span className={fieldSpanClass}>Type</span>\n <Select\n value={form.type}\n onValueChange={(value) => onChange('type', value as CosSourceType)}\n >\n <SelectTrigger aria-label=\"Source type\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {SOURCE_TYPES.map((type) => (\n <SelectItem key={type} value={type}>\n {formatEnumLabel(type)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className={fieldLabelClass}>\n <span className={fieldSpanClass}>Status</span>\n <Select\n value={form.status}\n onValueChange={(value) => onChange('status', value as CosSourceStatus)}\n >\n <SelectTrigger aria-label=\"Source status\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {SOURCE_STATUSES.map((status) => (\n <SelectItem key={status} value={status}>\n {formatEnumLabel(status)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n <label className={fieldLabelClass}>\n <span className={fieldSpanClass}>Credential reference</span>\n <Input\n value={form.credentialRef}\n onChange={(event) => onChange('credentialRef', event.target.value)}\n placeholder=\"Secret key or vault reference (write-only)\"\n />\n <span className=\"text-xs text-text-secondary\">\n A pointer to a stored credential — never paste raw secrets here.\n </span>\n </label>\n\n <label className={fieldLabelClass}>\n <span className={fieldSpanClass}>Config (JSON)</span>\n <Textarea\n value={form.config}\n onChange={(event) => onChange('config', event.target.value)}\n rows={5}\n className=\"font-mono text-xs\"\n placeholder='{ \"pollIntervalMinutes\": 30 }'\n />\n <span className=\"text-xs text-text-secondary\">\n Optional connection parameters as a JSON object.\n </span>\n </label>\n\n <EntityTagsField\n value={form.tags}\n onChange={(tags) => onChange('tags', tags)}\n disabled={saving}\n />\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SourceFormPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/sources/SourceFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\nimport {\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosCreateSourceMutation,\n useCosSourceQuery,\n useCosUpdateSourceMutation,\n type CosSourceStatus,\n type CosSourceType,\n} from '../../api/cosmicApi';\nimport { EntityFormShell, EntityTagsField, formatEnumLabel } from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\n\nconst SOURCE_TYPES: CosSourceType[] = [\n 'TELESCOPE',\n 'SATELLITE',\n 'GROUND_SENSOR',\n 'ARCHIVE',\n 'API',\n 'STREAM',\n 'FILE_DROP',\n 'SIMULATION',\n];\n\nconst SOURCE_STATUSES: CosSourceStatus[] = [\n 'CONFIGURING',\n 'CONNECTED',\n 'SYNCING',\n 'DEGRADED',\n 'ERROR',\n 'DISABLED',\n];\n\ninterface SourceFormState {\n name: string;\n description: string;\n type: CosSourceType;\n status: CosSourceStatus;\n endpoint: string;\n credentialRef: string;\n config: string;\n tags: string[];\n}\n\nconst INITIAL_STATE: SourceFormState = {\n name: '',\n description: '',\n type: 'TELESCOPE',\n status: 'CONFIGURING',\n endpoint: '',\n credentialRef: '',\n config: '',\n tags: [],\n};\n\nconst fieldLabelClass = 'block space-y-1.5 text-sm';\nconst fieldSpanClass = 'font-medium text-text-primary';\n\nexport default function SourceFormPage() {\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useCosmicIntersection();\n\n const { data, loading: loadingSource } = useCosSourceQuery(id);\n const [createSource, { loading: creating }] = useCosCreateSourceMutation();\n const [updateSource, { loading: updating }] = useCosUpdateSourceMutation();\n\n const [form, setForm] = useState<SourceFormState>(INITIAL_STATE);\n const [hydrated, setHydrated] = useState(false);\n\n useEffect(() => {\n if (!isEdit || hydrated || !data?.cosSource) {\n return;\n }\n const source = data.cosSource;\n setForm({\n name: source.name ?? '',\n description: source.description ?? '',\n type: source.type,\n status: source.status,\n endpoint: source.endpoint ?? '',\n credentialRef: '',\n config: source.config ? JSON.stringify(source.config, null, 2) : '',\n tags: source.tags ?? [],\n });\n setHydrated(true);\n }, [isEdit, hydrated, data]);\n\n const onChange = <K extends keyof SourceFormState>(field: K, value: SourceFormState[K]) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const saving = creating || updating;\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!form.name.trim()) {\n notify.error('Name is required', 'Give this source a recognizable name.');\n return;\n }\n\n let config: Record<string, unknown> | undefined;\n if (form.config.trim()) {\n try {\n const parsed = JSON.parse(form.config) as unknown;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new Error('Config must be a JSON object.');\n }\n config = parsed as Record<string, unknown>;\n } catch (parseError) {\n notify.error(\n 'Invalid config JSON',\n parseError instanceof Error ? parseError.message : 'Please provide valid JSON.'\n );\n return;\n }\n }\n\n const base = {\n name: form.name.trim(),\n type: form.type,\n status: form.status,\n description: form.description.trim() || undefined,\n endpoint: form.endpoint.trim() || undefined,\n credentialRef: form.credentialRef.trim() || undefined,\n config,\n tags: form.tags,\n };\n\n if (isEdit) {\n const response = await updateSource({ variables: { id, input: base } });\n if (response.data?.cosUpdateSource?.id) {\n notify.success('Source updated');\n navigate('sources');\n }\n return;\n }\n\n const response = await createSource({ variables: { input: base } });\n if (response.data?.cosCreateSource?.id) {\n notify.success('Source created');\n navigate('sources');\n }\n };\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit source' : 'New data source'}\n description=\"Instruments, archives and feeds that supply raw data into your federation.\"\n purpose=\"A source describes where data comes from — a telescope, satellite feed, archive, API or file drop. Configure it once, then attach datasets that ingest from it and mark it synced as data arrives.\"\n backLabel=\"Back to sources\"\n onBack={() => navigate('sources')}\n onSubmit={(event) => void onSubmit(event)}\n submitLabel={isEdit ? 'Save source' : 'Create source'}\n saving={saving || (isEdit && loadingSource && !hydrated)}\n >\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className={fieldLabelClass}>\n <span className={fieldSpanClass}>Name</span>\n <Input\n value={form.name}\n onChange={(event) => onChange('name', event.target.value)}\n placeholder=\"e.g. VLA Radio Array\"\n required\n />\n </label>\n <label className={fieldLabelClass}>\n <span className={fieldSpanClass}>Endpoint</span>\n <Input\n value={form.endpoint}\n onChange={(event) => onChange('endpoint', event.target.value)}\n placeholder=\"https://archive.example.org/api\"\n />\n </label>\n </div>\n\n <label className={fieldLabelClass}>\n <span className={fieldSpanClass}>Description</span>\n <Textarea\n value={form.description}\n onChange={(event) => onChange('description', event.target.value)}\n rows={3}\n placeholder=\"What this source provides and how it connects.\"\n />\n </label>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className={fieldLabelClass}>\n <span className={fieldSpanClass}>Type</span>\n <Select\n value={form.type}\n onValueChange={(value) => onChange('type', value as CosSourceType)}\n >\n <SelectTrigger aria-label=\"Source type\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {SOURCE_TYPES.map((type) => (\n <SelectItem key={type} value={type}>\n {formatEnumLabel(type)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className={fieldLabelClass}>\n <span className={fieldSpanClass}>Status</span>\n <Select\n value={form.status}\n onValueChange={(value) => onChange('status', value as CosSourceStatus)}\n >\n <SelectTrigger aria-label=\"Source status\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {SOURCE_STATUSES.map((status) => (\n <SelectItem key={status} value={status}>\n {formatEnumLabel(status)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n <label className={fieldLabelClass}>\n <span className={fieldSpanClass}>Credential reference</span>\n <Input\n value={form.credentialRef}\n onChange={(event) => onChange('credentialRef', event.target.value)}\n placeholder=\"Secret key or vault reference (write-only)\"\n />\n <span className=\"text-xs text-text-secondary\">\n A pointer to a stored credential — never paste raw secrets here.\n </span>\n </label>\n\n <label className={fieldLabelClass}>\n <span className={fieldSpanClass}>Config (JSON)</span>\n <Textarea\n value={form.config}\n onChange={(event) => onChange('config', event.target.value)}\n rows={5}\n className=\"font-mono text-xs\"\n placeholder='{ \"pollIntervalMinutes\": 30 }'\n />\n <span className=\"text-xs text-text-secondary\">\n Optional connection parameters as a JSON object.\n </span>\n </label>\n\n <EntityTagsField\n value={form.tags}\n onChange={(tags) => onChange('tags', tags)}\n disabled={saving}\n />\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-cosmicintersection",
3
- "version": "2026.731.1",
3
+ "version": "2026.805.1",
4
4
  "description": "Burdenoff CosmicIntersection Microfrontend",
5
5
  "type": "module",
6
6
  "files": [
@@ -46,14 +46,14 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "@apollo/client": "^4.0.11",
49
- "@burdenoff/fe-libs": "2026.713.6",
49
+ "@burdenoff/fe-libs": "2026.801.1",
50
50
  "@tanstack/react-query": "^5.90.16",
51
51
  "clsx": "^2.1.1",
52
52
  "graphql": "^16.10.0",
53
53
  "lucide-react": "^0.468.0",
54
54
  "react": "^19.0.0",
55
55
  "react-dom": "^19.0.0",
56
- "react-router-dom": "^7.1.3",
56
+ "react-router": "^8.3.0",
57
57
  "sonner": "^2.0.7",
58
58
  "tailwind-merge": "^3.4.0",
59
59
  "zustand": "^5.0.3",
@@ -101,7 +101,7 @@
101
101
  "zustand": "^5.0.0"
102
102
  },
103
103
  "publishConfig": {
104
- "access": "restricted"
104
+ "access": "public"
105
105
  },
106
106
  "overrides": {
107
107
  "lodash": ">=4.17.21",