@burdenoff/microfe-cosmicintersection 2026.716.1 → 2026.731.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 (56) hide show
  1. package/dist/cosmicintersection/CosmicIntersectionRoot.js.map +1 -1
  2. package/dist/cosmicintersection/CosmicIntersectionRoutes.js.map +1 -1
  3. package/dist/cosmicintersection/api/cosmicApi.d.ts.map +1 -1
  4. package/dist/cosmicintersection/api/cosmicApi.js.map +1 -1
  5. package/dist/cosmicintersection/components/ActionMenu.js.map +1 -1
  6. package/dist/cosmicintersection/components/ActivityFeedPanel.js.map +1 -1
  7. package/dist/cosmicintersection/components/DiscussionPanel.js.map +1 -1
  8. package/dist/cosmicintersection/components/EmptyState.js.map +1 -1
  9. package/dist/cosmicintersection/components/EntityFormShell.js.map +1 -1
  10. package/dist/cosmicintersection/components/EntityTagsField.js.map +1 -1
  11. package/dist/cosmicintersection/components/ErrorState.js.map +1 -1
  12. package/dist/cosmicintersection/components/FacetPanel.js.map +1 -1
  13. package/dist/cosmicintersection/components/FileUploadField.js.map +1 -1
  14. package/dist/cosmicintersection/components/LineageGraph.d.ts.map +1 -1
  15. package/dist/cosmicintersection/components/LineageGraph.js.map +1 -1
  16. package/dist/cosmicintersection/components/LoadingState.js.map +1 -1
  17. package/dist/cosmicintersection/components/PageLayout.js.map +1 -1
  18. package/dist/cosmicintersection/components/Pagination.js.map +1 -1
  19. package/dist/cosmicintersection/components/ResultsChart.js.map +1 -1
  20. package/dist/cosmicintersection/components/SafeLink.js.map +1 -1
  21. package/dist/cosmicintersection/components/StatusBadge.js.map +1 -1
  22. package/dist/cosmicintersection/components/StepDAG.js.map +1 -1
  23. package/dist/cosmicintersection/context/CosmicIntersectionContext.js.map +1 -1
  24. package/dist/cosmicintersection/lib/mutationResult.js.map +1 -1
  25. package/dist/cosmicintersection/lib/toast.js.map +1 -1
  26. package/dist/cosmicintersection/pages/access/AccessPage.js.map +1 -1
  27. package/dist/cosmicintersection/pages/analyses/AnalysesPage.js.map +1 -1
  28. package/dist/cosmicintersection/pages/collections/CollectionDetailPage.js.map +1 -1
  29. package/dist/cosmicintersection/pages/collections/CollectionFormPage.js.map +1 -1
  30. package/dist/cosmicintersection/pages/collections/CollectionsPage.js.map +1 -1
  31. package/dist/cosmicintersection/pages/dashboard/DashboardPage.js.map +1 -1
  32. package/dist/cosmicintersection/pages/datasets/DatasetDetailPage.js.map +1 -1
  33. package/dist/cosmicintersection/pages/datasets/DatasetFormPage.js.map +1 -1
  34. package/dist/cosmicintersection/pages/datasets/DatasetsPage.js.map +1 -1
  35. package/dist/cosmicintersection/pages/discovery/DiscoveryPage.js.map +1 -1
  36. package/dist/cosmicintersection/pages/lineage/LineagePage.js.map +1 -1
  37. package/dist/cosmicintersection/pages/notebooks/NotebookDetailPage.js.map +1 -1
  38. package/dist/cosmicintersection/pages/notebooks/NotebookFormPage.js.map +1 -1
  39. package/dist/cosmicintersection/pages/notebooks/NotebooksPage.js.map +1 -1
  40. package/dist/cosmicintersection/pages/pipelines/PipelineDetailPage.js.map +1 -1
  41. package/dist/cosmicintersection/pages/pipelines/PipelineFormPage.js.map +1 -1
  42. package/dist/cosmicintersection/pages/pipelines/PipelinesPage.js.map +1 -1
  43. package/dist/cosmicintersection/pages/releases/ReleaseFormPage.js.map +1 -1
  44. package/dist/cosmicintersection/pages/releases/ReleasesPage.js.map +1 -1
  45. package/dist/cosmicintersection/pages/runs/RunDetailPage.js.map +1 -1
  46. package/dist/cosmicintersection/pages/runs/RunsPage.js.map +1 -1
  47. package/dist/cosmicintersection/pages/sources/SourceDetailPage.js.map +1 -1
  48. package/dist/cosmicintersection/pages/sources/SourceFormPage.js.map +1 -1
  49. package/dist/cosmicintersection/pages/sources/SourcesPage.js.map +1 -1
  50. package/dist/cosmicintersection/utils/cn.js.map +1 -1
  51. package/dist/node_modules/clsx/dist/clsx.js.map +1 -1
  52. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1 -1
  53. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -1
  54. package/dist/services/createSandboxAssistantTransport.js.map +1 -1
  55. package/dist/utils/pageContext.js.map +1 -1
  56. package/package.json +5 -5
@@ -1 +1 @@
1
- {"version":3,"file":"ReleaseFormPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/releases/ReleaseFormPage.tsx"],"sourcesContent":["import { useState, type FormEvent } from 'react';\nimport {\n Checkbox,\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 useCosCreateReleaseMutation,\n useCosDatasetsQuery,\n useCosNotebooksQuery,\n type CosCreateReleaseInput,\n} from '../../api/cosmicApi';\nimport { EntityFormShell, EntityTagsField } from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\n\nconst NONE = 'NONE';\n\nexport default function ReleaseFormPage() {\n const { navigate } = useCosmicIntersection();\n const [createRelease, { loading: saving }] = useCosCreateReleaseMutation();\n\n // Pull candidate datasets/notebooks to link. A generous page covers the common\n // case; the backend enforces referential integrity on submit.\n const datasetsQuery = useCosDatasetsQuery({ pagination: { limit: 50, offset: 0 } });\n const notebooksQuery = useCosNotebooksQuery({ pagination: { limit: 50, offset: 0 } });\n const datasets = datasetsQuery.data?.cosListDatasets.items ?? [];\n const notebooks = notebooksQuery.data?.cosListNotebooks.items ?? [];\n\n const [title, setTitle] = useState('');\n const [version, setVersion] = useState('');\n const [abstract, setAbstract] = useState('');\n const [authors, setAuthors] = useState<string[]>([]);\n const [license, setLicense] = useState('');\n const [datasetIds, setDatasetIds] = useState<string[]>([]);\n const [notebookId, setNotebookId] = useState<string>(NONE);\n\n const toggleDataset = (id: string) => {\n setDatasetIds((current) =>\n current.includes(id) ? current.filter((existing) => existing !== id) : [...current, id]\n );\n };\n\n const onSubmit = async (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n const trimmedTitle = title.trim();\n if (!trimmedTitle) {\n notify.error('Title is required', 'Give the release a descriptive title before saving.');\n return;\n }\n\n const input: CosCreateReleaseInput = {\n title: trimmedTitle,\n version: version.trim() || undefined,\n abstract: abstract.trim() || undefined,\n authors: authors.length ? authors : undefined,\n license: license.trim() || undefined,\n datasetIds: datasetIds.length ? datasetIds : undefined,\n notebookId: notebookId === NONE ? undefined : notebookId,\n };\n\n const response = await createRelease({ variables: { input } });\n if (response.data?.cosCreateRelease?.id) {\n notify.success('Release created', 'Publish it from the releases list when it is ready.');\n navigate('releases');\n }\n };\n\n return (\n <EntityFormShell\n title=\"New release\"\n description=\"Bundle datasets and a notebook into a versioned, citable publication.\"\n purpose=\"A release is an immutable snapshot you can cite. Create it in draft here, then publish it (and optionally attach a DOI) from the releases list once the work is final.\"\n backLabel=\"Back to releases\"\n onBack={() => navigate('releases')}\n onSubmit={onSubmit}\n submitLabel=\"Create release\"\n saving={saving}\n submitDisabled={!title.trim()}\n >\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"release-title\">Title</Label>\n <Input\n id=\"release-title\"\n value={title}\n onChange={(event) => setTitle(event.target.value)}\n placeholder=\"Kepler DR3 stellar catalog\"\n required\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"release-version\">Version</Label>\n <Input\n id=\"release-version\"\n value={version}\n onChange={(event) => setVersion(event.target.value)}\n placeholder=\"1.0.0\"\n />\n </div>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"release-abstract\">Abstract</Label>\n <Textarea\n id=\"release-abstract\"\n value={abstract}\n onChange={(event) => setAbstract(event.target.value)}\n rows={4}\n placeholder=\"Summarize what this release contains and how it should be used.\"\n />\n </div>\n\n <EntityTagsField\n value={authors}\n onChange={setAuthors}\n label=\"Authors\"\n placeholder=\"Add an author and press Enter\"\n />\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"release-license\">License</Label>\n <Input\n id=\"release-license\"\n value={license}\n onChange={(event) => setLicense(event.target.value)}\n placeholder=\"CC-BY-4.0\"\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"release-notebook\">Notebook</Label>\n <Select value={notebookId} onValueChange={setNotebookId}>\n <SelectTrigger id=\"release-notebook\" aria-label=\"Linked notebook\">\n <SelectValue placeholder=\"No notebook\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={NONE}>No notebook</SelectItem>\n {notebooks.map((notebook) => (\n <SelectItem key={notebook.id} value={notebook.id}>\n {notebook.title}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {notebooksQuery.loading ? (\n <p className=\"text-xs text-text-secondary\">Loading notebooks…</p>\n ) : null}\n </div>\n </div>\n\n <div className=\"space-y-2\">\n <Label>Datasets {datasetIds.length ? `(${datasetIds.length} selected)` : ''}</Label>\n <div className=\"max-h-56 space-y-1 overflow-y-auto rounded-card border border-border-default bg-bg-sunken p-2\">\n {datasetsQuery.loading ? (\n <p className=\"px-2 py-3 text-sm text-text-secondary\">Loading datasets…</p>\n ) : datasets.length === 0 ? (\n <p className=\"px-2 py-3 text-sm text-text-secondary\">\n No datasets available to link yet.\n </p>\n ) : (\n datasets.map((dataset) => {\n const checkboxId = `release-dataset-${dataset.id}`;\n return (\n <label\n key={dataset.id}\n htmlFor={checkboxId}\n className=\"flex cursor-pointer items-center gap-3 rounded-button px-2 py-1.5 hover:bg-bg-elevated\"\n >\n <Checkbox\n id={checkboxId}\n checked={datasetIds.includes(dataset.id)}\n onCheckedChange={() => toggleDataset(dataset.id)}\n />\n <span className=\"min-w-0 flex-1 truncate text-sm text-text-primary\">\n {dataset.name}\n </span>\n <span className=\"text-xs text-text-secondary\">{dataset.status}</span>\n </label>\n );\n })\n )}\n </div>\n <p className=\"text-xs text-text-secondary\">\n Select the datasets this release certifies. You can attach a DOI when you publish.\n </p>\n </div>\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ReleaseFormPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/releases/ReleaseFormPage.tsx"],"sourcesContent":["import { useState, type FormEvent } from 'react';\nimport {\n Checkbox,\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 useCosCreateReleaseMutation,\n useCosDatasetsQuery,\n useCosNotebooksQuery,\n type CosCreateReleaseInput,\n} from '../../api/cosmicApi';\nimport { EntityFormShell, EntityTagsField } from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\n\nconst NONE = 'NONE';\n\nexport default function ReleaseFormPage() {\n const { navigate } = useCosmicIntersection();\n const [createRelease, { loading: saving }] = useCosCreateReleaseMutation();\n\n // Pull candidate datasets/notebooks to link. A generous page covers the common\n // case; the backend enforces referential integrity on submit.\n const datasetsQuery = useCosDatasetsQuery({ pagination: { limit: 50, offset: 0 } });\n const notebooksQuery = useCosNotebooksQuery({ pagination: { limit: 50, offset: 0 } });\n const datasets = datasetsQuery.data?.cosListDatasets.items ?? [];\n const notebooks = notebooksQuery.data?.cosListNotebooks.items ?? [];\n\n const [title, setTitle] = useState('');\n const [version, setVersion] = useState('');\n const [abstract, setAbstract] = useState('');\n const [authors, setAuthors] = useState<string[]>([]);\n const [license, setLicense] = useState('');\n const [datasetIds, setDatasetIds] = useState<string[]>([]);\n const [notebookId, setNotebookId] = useState<string>(NONE);\n\n const toggleDataset = (id: string) => {\n setDatasetIds((current) =>\n current.includes(id) ? current.filter((existing) => existing !== id) : [...current, id]\n );\n };\n\n const onSubmit = async (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n const trimmedTitle = title.trim();\n if (!trimmedTitle) {\n notify.error('Title is required', 'Give the release a descriptive title before saving.');\n return;\n }\n\n const input: CosCreateReleaseInput = {\n title: trimmedTitle,\n version: version.trim() || undefined,\n abstract: abstract.trim() || undefined,\n authors: authors.length ? authors : undefined,\n license: license.trim() || undefined,\n datasetIds: datasetIds.length ? datasetIds : undefined,\n notebookId: notebookId === NONE ? undefined : notebookId,\n };\n\n const response = await createRelease({ variables: { input } });\n if (response.data?.cosCreateRelease?.id) {\n notify.success('Release created', 'Publish it from the releases list when it is ready.');\n navigate('releases');\n }\n };\n\n return (\n <EntityFormShell\n title=\"New release\"\n description=\"Bundle datasets and a notebook into a versioned, citable publication.\"\n purpose=\"A release is an immutable snapshot you can cite. Create it in draft here, then publish it (and optionally attach a DOI) from the releases list once the work is final.\"\n backLabel=\"Back to releases\"\n onBack={() => navigate('releases')}\n onSubmit={onSubmit}\n submitLabel=\"Create release\"\n saving={saving}\n submitDisabled={!title.trim()}\n >\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"release-title\">Title</Label>\n <Input\n id=\"release-title\"\n value={title}\n onChange={(event) => setTitle(event.target.value)}\n placeholder=\"Kepler DR3 stellar catalog\"\n required\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"release-version\">Version</Label>\n <Input\n id=\"release-version\"\n value={version}\n onChange={(event) => setVersion(event.target.value)}\n placeholder=\"1.0.0\"\n />\n </div>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"release-abstract\">Abstract</Label>\n <Textarea\n id=\"release-abstract\"\n value={abstract}\n onChange={(event) => setAbstract(event.target.value)}\n rows={4}\n placeholder=\"Summarize what this release contains and how it should be used.\"\n />\n </div>\n\n <EntityTagsField\n value={authors}\n onChange={setAuthors}\n label=\"Authors\"\n placeholder=\"Add an author and press Enter\"\n />\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"release-license\">License</Label>\n <Input\n id=\"release-license\"\n value={license}\n onChange={(event) => setLicense(event.target.value)}\n placeholder=\"CC-BY-4.0\"\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"release-notebook\">Notebook</Label>\n <Select value={notebookId} onValueChange={setNotebookId}>\n <SelectTrigger id=\"release-notebook\" aria-label=\"Linked notebook\">\n <SelectValue placeholder=\"No notebook\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={NONE}>No notebook</SelectItem>\n {notebooks.map((notebook) => (\n <SelectItem key={notebook.id} value={notebook.id}>\n {notebook.title}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {notebooksQuery.loading ? (\n <p className=\"text-xs text-text-secondary\">Loading notebooks…</p>\n ) : null}\n </div>\n </div>\n\n <div className=\"space-y-2\">\n <Label>Datasets {datasetIds.length ? `(${datasetIds.length} selected)` : ''}</Label>\n <div className=\"max-h-56 space-y-1 overflow-y-auto rounded-card border border-border-default bg-bg-sunken p-2\">\n {datasetsQuery.loading ? (\n <p className=\"px-2 py-3 text-sm text-text-secondary\">Loading datasets…</p>\n ) : datasets.length === 0 ? (\n <p className=\"px-2 py-3 text-sm text-text-secondary\">\n No datasets available to link yet.\n </p>\n ) : (\n datasets.map((dataset) => {\n const checkboxId = `release-dataset-${dataset.id}`;\n return (\n <label\n key={dataset.id}\n htmlFor={checkboxId}\n className=\"flex cursor-pointer items-center gap-3 rounded-button px-2 py-1.5 hover:bg-bg-elevated\"\n >\n <Checkbox\n id={checkboxId}\n checked={datasetIds.includes(dataset.id)}\n onCheckedChange={() => toggleDataset(dataset.id)}\n />\n <span className=\"min-w-0 flex-1 truncate text-sm text-text-primary\">\n {dataset.name}\n </span>\n <span className=\"text-xs text-text-secondary\">{dataset.status}</span>\n </label>\n );\n })\n )}\n </div>\n <p className=\"text-xs text-text-secondary\">\n Select the datasets this release certifies. You can attach a DOI when you publish.\n </p>\n </div>\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ReleasesPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/releases/ReleasesPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { BookOpen, Database, ExternalLink, NotebookPen, Plus, Rocket } from 'lucide-react';\nimport {\n Button,\n Dialog,\n DialogClose,\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 Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosDatasetsQuery,\n useCosNotebooksQuery,\n useCosPublishReleaseMutation,\n useCosReleasesQuery,\n type CosRelease,\n type CosReleaseStatus,\n} from '../../api/cosmicApi';\nimport {\n DiscussionPanel,\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n Pagination,\n SafeLink,\n StatusBadge,\n formatEnumLabel,\n usePagination,\n} from '../../components';\nimport { ActionMenu } from '../../components/ActionMenu';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\n\nconst ALL = 'ALL';\n\nconst RELEASE_STATUSES: CosReleaseStatus[] = ['DRAFT', 'UNDER_REVIEW', 'PUBLISHED', 'RETRACTED'];\n\n/** A release can be published only while it is still in a pre-publication state. */\nfunction canPublish(status: CosReleaseStatus): boolean {\n return status === 'DRAFT' || status === 'UNDER_REVIEW';\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 doiUrl(doi: string): string {\n const trimmed = doi.trim();\n return /^https?:\\/\\//i.test(trimmed)\n ? trimmed\n : `https://doi.org/${trimmed.replace(/^doi:/i, '')}`;\n}\n\nexport default function ReleasesPage() {\n const { navigate } = useCosmicIntersection();\n const [statusFilter, setStatusFilter] = useState<string>(ALL);\n const pagination = usePagination({ pageSize: 20, resetKey: statusFilter });\n\n const { data, loading, error, refetch } = useCosReleasesQuery({\n status: statusFilter === ALL ? undefined : (statusFilter as CosReleaseStatus),\n pagination: { limit: pagination.limit, offset: pagination.offset },\n });\n\n const [publishRelease, { loading: publishing }] = useCosPublishReleaseMutation();\n\n const releases = useMemo(() => data?.cosListReleases.items ?? [], [data]);\n const pageInfo = data?.cosListReleases.pageInfo;\n\n // Resolve the release's linked dataset/notebook UUIDs to human names so the\n // detail dialog can render navigable buttons instead of raw ids.\n const datasetsQuery = useCosDatasetsQuery({ pagination: { limit: 100, offset: 0 } });\n const notebooksQuery = useCosNotebooksQuery({ pagination: { limit: 100, offset: 0 } });\n const datasetNameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const dataset of datasetsQuery.data?.cosListDatasets.items ?? []) {\n map.set(dataset.id, dataset.name);\n }\n return map;\n }, [datasetsQuery.data]);\n const notebookTitleById = useMemo(() => {\n const map = new Map<string, string>();\n for (const notebook of notebooksQuery.data?.cosListNotebooks.items ?? []) {\n map.set(notebook.id, notebook.title);\n }\n return map;\n }, [notebooksQuery.data]);\n\n // Detail (View) dialog — rendered from row data, no extra fetch needed.\n const [detail, setDetail] = useState<CosRelease | null>(null);\n\n // Close the dialog before navigating to a linked entity.\n const goToLinked = (path: string) => {\n setDetail(null);\n navigate(path);\n };\n\n // Publish dialog — captures an optional DOI before transitioning the release.\n const [publishTarget, setPublishTarget] = useState<CosRelease | null>(null);\n const [doiDraft, setDoiDraft] = useState('');\n\n const openPublish = (release: CosRelease) => {\n setDoiDraft(release.doi ?? '');\n setPublishTarget(release);\n };\n\n const submitPublish = async () => {\n if (!publishTarget) {\n return;\n }\n const doi = doiDraft.trim();\n const response = await publishRelease({\n variables: { id: publishTarget.id, doi: doi || undefined },\n });\n if (response.data?.cosPublishRelease?.id) {\n notify.success('Release published', `“${publishTarget.title}” is now public.`);\n setPublishTarget(null);\n setDoiDraft('');\n await refetch();\n }\n };\n\n const newButton = (\n <Button type=\"button\" onClick={() => navigate('releases/create')}>\n <Plus className=\"size-4\" />\n New release\n </Button>\n );\n\n const renderBody = () => {\n if (loading) {\n return <LoadingState variant=\"rows\" rows={6} label=\"Loading releases…\" />;\n }\n if (error) {\n return <ErrorState message={error.message} onRetry={() => void refetch()} />;\n }\n if (releases.length === 0) {\n const filtered = statusFilter !== ALL;\n return (\n <EmptyState\n icon={Rocket}\n title={filtered ? 'No releases match this filter' : 'No releases yet'}\n description={\n filtered\n ? 'Try clearing the status filter to see more releases.'\n : 'Package datasets and notebooks into a citable, versioned release for publication.'\n }\n action={filtered ? undefined : newButton}\n />\n );\n }\n\n return (\n <>\n {/* Desktop table */}\n <div className=\"hidden rounded-card border border-border-default bg-bg-elevated md:block\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Title</TableHead>\n <TableHead>Version</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>DOI</TableHead>\n <TableHead>Authors</TableHead>\n <TableHead>Published</TableHead>\n <TableHead>License</TableHead>\n <TableHead className=\"text-right\">Actions</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {releases.map((release) => (\n <TableRow key={release.id}>\n <TableCell>\n <button\n type=\"button\"\n onClick={() => setDetail(release)}\n className=\"text-left font-medium text-text-primary underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-focus\"\n >\n {release.title}\n </button>\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {release.version || '—'}\n </TableCell>\n <TableCell>\n <StatusBadge status={release.status} />\n </TableCell>\n <TableCell className=\"max-w-[14rem]\">\n {release.doi ? (\n <SafeLink\n href={doiUrl(release.doi)}\n className=\"inline-flex items-center gap-1 truncate text-sm text-text-primary underline-offset-2 hover:underline\"\n fallbackClassName=\"block truncate text-sm text-text-secondary\"\n >\n <span className=\"truncate\">{release.doi}</span>\n <ExternalLink className=\"size-3.5 shrink-0\" aria-hidden=\"true\" />\n </SafeLink>\n ) : (\n <span className=\"text-sm text-text-secondary\">—</span>\n )}\n </TableCell>\n <TableCell className=\"max-w-[14rem]\">\n {release.authors.length === 0 ? (\n <span className=\"text-sm text-text-secondary\">—</span>\n ) : (\n <span className=\"line-clamp-1 text-sm text-text-secondary\">\n {release.authors.join(', ')}\n </span>\n )}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {formatDate(release.publishedAt)}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {release.license || '—'}\n </TableCell>\n <TableCell className=\"text-right\">\n <ActionMenu\n ariaLabel={`Actions for ${release.title}`}\n onView={() => setDetail(release)}\n extraItems={\n canPublish(release.status)\n ? [\n {\n key: 'publish',\n label: 'Publish',\n icon: Rocket,\n onSelect: () => openPublish(release),\n },\n ]\n : []\n }\n />\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n\n {/* Mobile card list */}\n <ul className=\"space-y-3 md:hidden\">\n {releases.map((release) => (\n <li\n key={release.id}\n className=\"rounded-card border border-border-default bg-bg-elevated\"\n >\n <div className=\"flex items-start justify-between gap-2 p-4\">\n <div className=\"min-w-0 flex-1 space-y-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setDetail(release)}\n className=\"truncate text-left font-medium text-text-primary underline-offset-2 hover:underline\"\n >\n {release.title}\n </button>\n <StatusBadge status={release.status} />\n </div>\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-text-secondary\">\n <span>{release.version || 'v—'}</span>\n {release.license ? <span>{release.license}</span> : null}\n <span>{formatDate(release.publishedAt)}</span>\n </div>\n {release.authors.length > 0 ? (\n <p className=\"line-clamp-1 text-xs text-text-secondary\">\n {release.authors.join(', ')}\n </p>\n ) : null}\n {release.doi ? (\n <SafeLink\n href={doiUrl(release.doi)}\n className=\"inline-flex items-center gap-1 text-xs text-text-primary underline-offset-2 hover:underline\"\n fallbackClassName=\"block truncate text-xs text-text-secondary\"\n >\n <span className=\"truncate\">DOI: {release.doi}</span>\n <ExternalLink className=\"size-3 shrink-0\" aria-hidden=\"true\" />\n </SafeLink>\n ) : null}\n </div>\n <ActionMenu\n ariaLabel={`Actions for ${release.title}`}\n onView={() => setDetail(release)}\n extraItems={\n canPublish(release.status)\n ? [\n {\n key: 'publish',\n label: 'Publish',\n icon: Rocket,\n onSelect: () => openPublish(release),\n },\n ]\n : []\n }\n />\n </div>\n </li>\n ))}\n </ul>\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={releases.length}\n hasMore={pageInfo?.hasMore ?? false}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"releases\"\n loading={loading}\n />\n </>\n );\n };\n\n return (\n <PageLayout\n title=\"Releases\"\n description=\"Citable, versioned publications that bundle datasets and notebooks for the community.\"\n actions={newButton}\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"w-full max-w-[14rem]\">\n <Select value={statusFilter} onValueChange={setStatusFilter}>\n <SelectTrigger aria-label=\"Filter by status\">\n <SelectValue placeholder=\"All statuses\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All statuses</SelectItem>\n {RELEASE_STATUSES.map((status) => (\n <SelectItem key={status} value={status}>\n {formatEnumLabel(status)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n {renderBody()}\n </div>\n\n {/* View / detail dialog */}\n <Dialog open={detail !== null} onOpenChange={(open) => (open ? undefined : setDetail(null))}>\n <DialogContent className=\"max-h-[85vh] max-w-lg overflow-y-auto\">\n {detail ? (\n <>\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-2\">\n <BookOpen className=\"size-5 text-text-secondary\" aria-hidden=\"true\" />\n {detail.title}\n </DialogTitle>\n <DialogDescription>\n Version {detail.version || '—'} · <StatusBadge status={detail.status} />\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-4 text-sm\">\n <div>\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Abstract\n </p>\n <p className=\"mt-1 whitespace-pre-wrap text-text-primary\">\n {detail.abstract?.trim() || 'No abstract provided.'}\n </p>\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Authors\n </p>\n <p className=\"mt-1 text-text-primary\">\n {detail.authors.length ? detail.authors.join(', ') : '—'}\n </p>\n </div>\n <div>\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n License\n </p>\n <p className=\"mt-1 text-text-primary\">{detail.license || '—'}</p>\n </div>\n <div>\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Published\n </p>\n <p className=\"mt-1 text-text-primary\">{formatDate(detail.publishedAt)}</p>\n </div>\n <div>\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n DOI\n </p>\n <p className=\"mt-1\">\n {detail.doi ? (\n <SafeLink\n href={doiUrl(detail.doi)}\n className=\"inline-flex items-center gap-1 text-text-primary underline-offset-2 hover:underline\"\n fallbackClassName=\"text-text-secondary\"\n >\n {detail.doi}\n <ExternalLink className=\"size-3.5\" aria-hidden=\"true\" />\n </SafeLink>\n ) : (\n <span className=\"text-text-secondary\">Not assigned</span>\n )}\n </p>\n </div>\n </div>\n\n <div>\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Datasets ({detail.datasetIds.length})\n </p>\n <div className=\"mt-1 flex flex-wrap gap-1.5\">\n {detail.datasetIds.length === 0 ? (\n <span className=\"text-text-secondary\">None linked.</span>\n ) : (\n detail.datasetIds.map((datasetId) => {\n const name = datasetNameById.get(datasetId);\n return (\n <Button\n key={datasetId}\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-auto gap-1 px-2 py-1\"\n onClick={() => goToLinked(`datasets/${datasetId}`)}\n >\n <Database className=\"size-3.5\" aria-hidden=\"true\" />\n <span className={name ? '' : 'font-mono text-xs'}>\n {name ?? datasetId}\n </span>\n </Button>\n );\n })\n )}\n </div>\n </div>\n\n <div>\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Notebook\n </p>\n <div className=\"mt-1\">\n {detail.notebookId ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-auto gap-1 px-2 py-1\"\n onClick={() => goToLinked(`notebooks/${detail.notebookId}`)}\n >\n <NotebookPen className=\"size-3.5\" aria-hidden=\"true\" />\n <span\n className={\n notebookTitleById.get(detail.notebookId) ? '' : 'font-mono text-xs'\n }\n >\n {notebookTitleById.get(detail.notebookId) ?? detail.notebookId}\n </span>\n </Button>\n ) : (\n <span className=\"text-text-secondary\">None linked.</span>\n )}\n </div>\n </div>\n\n <div>\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Discussion\n </p>\n <div className=\"mt-2\">\n <DiscussionPanel\n entityType=\"cosmicintersection-release\"\n entityId={detail.id}\n title={detail.title}\n widgetKey=\"release-discussion\"\n />\n </div>\n </div>\n </div>\n\n <DialogFooter>\n {canPublish(detail.status) ? (\n <Button\n type=\"button\"\n onClick={() => {\n const target = detail;\n setDetail(null);\n openPublish(target);\n }}\n >\n <Rocket className=\"size-4\" />\n Publish\n </Button>\n ) : null}\n <DialogClose asChild>\n <Button type=\"button\" variant=\"outline\">\n Close\n </Button>\n </DialogClose>\n </DialogFooter>\n </>\n ) : null}\n </DialogContent>\n </Dialog>\n\n {/* Publish dialog */}\n <Dialog\n open={publishTarget !== null}\n onOpenChange={(open) => (open || publishing ? undefined : setPublishTarget(null))}\n >\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>Publish release</DialogTitle>\n <DialogDescription>\n {publishTarget\n ? `Publish “${publishTarget.title}” to make it publicly citable. You can attach a DOI now or later.`\n : ''}\n </DialogDescription>\n </DialogHeader>\n\n {publishTarget ? (\n <dl className=\"grid grid-cols-3 gap-x-4 gap-y-3 rounded-card border border-border-default bg-bg-sunken p-3\">\n <div className=\"col-span-2 min-w-0\">\n <dt className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Authors\n </dt>\n <dd\n className=\"mt-0.5 truncate text-sm text-text-primary\"\n title={publishTarget.authors.join(', ')}\n >\n {publishTarget.authors.length ? publishTarget.authors.join(', ') : '—'}\n </dd>\n </div>\n <div>\n <dt className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Datasets\n </dt>\n <dd className=\"mt-0.5 text-sm tabular-nums text-text-primary\">\n {publishTarget.datasetIds.length}\n </dd>\n </div>\n <div className=\"col-span-3 min-w-0\">\n <dt className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n License\n </dt>\n <dd className=\"mt-0.5 truncate text-sm text-text-primary\">\n {publishTarget.license || '—'}\n </dd>\n </div>\n </dl>\n ) : null}\n\n <div className=\"space-y-2\">\n <Label htmlFor=\"release-doi\">DOI (optional)</Label>\n <Input\n id=\"release-doi\"\n value={doiDraft}\n onChange={(event) => setDoiDraft(event.target.value)}\n placeholder=\"10.1234/cosmic.2026.0001\"\n />\n <p className=\"text-xs text-text-secondary\">\n A Digital Object Identifier makes the release permanently referenceable.\n </p>\n </div>\n\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => setPublishTarget(null)}\n disabled={publishing}\n >\n Cancel\n </Button>\n <Button type=\"button\" onClick={() => void submitPublish()} disabled={publishing}>\n <Rocket className=\"size-4\" />\n {publishing ? 'Publishing…' : 'Publish release'}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ReleasesPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/releases/ReleasesPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { BookOpen, Database, ExternalLink, NotebookPen, Plus, Rocket } from 'lucide-react';\nimport {\n Button,\n Dialog,\n DialogClose,\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 Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosDatasetsQuery,\n useCosNotebooksQuery,\n useCosPublishReleaseMutation,\n useCosReleasesQuery,\n type CosRelease,\n type CosReleaseStatus,\n} from '../../api/cosmicApi';\nimport {\n DiscussionPanel,\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n Pagination,\n SafeLink,\n StatusBadge,\n formatEnumLabel,\n usePagination,\n} from '../../components';\nimport { ActionMenu } from '../../components/ActionMenu';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\n\nconst ALL = 'ALL';\n\nconst RELEASE_STATUSES: CosReleaseStatus[] = ['DRAFT', 'UNDER_REVIEW', 'PUBLISHED', 'RETRACTED'];\n\n/** A release can be published only while it is still in a pre-publication state. */\nfunction canPublish(status: CosReleaseStatus): boolean {\n return status === 'DRAFT' || status === 'UNDER_REVIEW';\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 doiUrl(doi: string): string {\n const trimmed = doi.trim();\n return /^https?:\\/\\//i.test(trimmed)\n ? trimmed\n : `https://doi.org/${trimmed.replace(/^doi:/i, '')}`;\n}\n\nexport default function ReleasesPage() {\n const { navigate } = useCosmicIntersection();\n const [statusFilter, setStatusFilter] = useState<string>(ALL);\n const pagination = usePagination({ pageSize: 20, resetKey: statusFilter });\n\n const { data, loading, error, refetch } = useCosReleasesQuery({\n status: statusFilter === ALL ? undefined : (statusFilter as CosReleaseStatus),\n pagination: { limit: pagination.limit, offset: pagination.offset },\n });\n\n const [publishRelease, { loading: publishing }] = useCosPublishReleaseMutation();\n\n const releases = useMemo(() => data?.cosListReleases.items ?? [], [data]);\n const pageInfo = data?.cosListReleases.pageInfo;\n\n // Resolve the release's linked dataset/notebook UUIDs to human names so the\n // detail dialog can render navigable buttons instead of raw ids.\n const datasetsQuery = useCosDatasetsQuery({ pagination: { limit: 100, offset: 0 } });\n const notebooksQuery = useCosNotebooksQuery({ pagination: { limit: 100, offset: 0 } });\n const datasetNameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const dataset of datasetsQuery.data?.cosListDatasets.items ?? []) {\n map.set(dataset.id, dataset.name);\n }\n return map;\n }, [datasetsQuery.data]);\n const notebookTitleById = useMemo(() => {\n const map = new Map<string, string>();\n for (const notebook of notebooksQuery.data?.cosListNotebooks.items ?? []) {\n map.set(notebook.id, notebook.title);\n }\n return map;\n }, [notebooksQuery.data]);\n\n // Detail (View) dialog — rendered from row data, no extra fetch needed.\n const [detail, setDetail] = useState<CosRelease | null>(null);\n\n // Close the dialog before navigating to a linked entity.\n const goToLinked = (path: string) => {\n setDetail(null);\n navigate(path);\n };\n\n // Publish dialog — captures an optional DOI before transitioning the release.\n const [publishTarget, setPublishTarget] = useState<CosRelease | null>(null);\n const [doiDraft, setDoiDraft] = useState('');\n\n const openPublish = (release: CosRelease) => {\n setDoiDraft(release.doi ?? '');\n setPublishTarget(release);\n };\n\n const submitPublish = async () => {\n if (!publishTarget) {\n return;\n }\n const doi = doiDraft.trim();\n const response = await publishRelease({\n variables: { id: publishTarget.id, doi: doi || undefined },\n });\n if (response.data?.cosPublishRelease?.id) {\n notify.success('Release published', `“${publishTarget.title}” is now public.`);\n setPublishTarget(null);\n setDoiDraft('');\n await refetch();\n }\n };\n\n const newButton = (\n <Button type=\"button\" onClick={() => navigate('releases/create')}>\n <Plus className=\"size-4\" />\n New release\n </Button>\n );\n\n const renderBody = () => {\n if (loading) {\n return <LoadingState variant=\"rows\" rows={6} label=\"Loading releases…\" />;\n }\n if (error) {\n return <ErrorState message={error.message} onRetry={() => void refetch()} />;\n }\n if (releases.length === 0) {\n const filtered = statusFilter !== ALL;\n return (\n <EmptyState\n icon={Rocket}\n title={filtered ? 'No releases match this filter' : 'No releases yet'}\n description={\n filtered\n ? 'Try clearing the status filter to see more releases.'\n : 'Package datasets and notebooks into a citable, versioned release for publication.'\n }\n action={filtered ? undefined : newButton}\n />\n );\n }\n\n return (\n <>\n {/* Desktop table */}\n <div className=\"hidden rounded-card border border-border-default bg-bg-elevated md:block\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Title</TableHead>\n <TableHead>Version</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>DOI</TableHead>\n <TableHead>Authors</TableHead>\n <TableHead>Published</TableHead>\n <TableHead>License</TableHead>\n <TableHead className=\"text-right\">Actions</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {releases.map((release) => (\n <TableRow key={release.id}>\n <TableCell>\n <button\n type=\"button\"\n onClick={() => setDetail(release)}\n className=\"text-left font-medium text-text-primary underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-focus\"\n >\n {release.title}\n </button>\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {release.version || '—'}\n </TableCell>\n <TableCell>\n <StatusBadge status={release.status} />\n </TableCell>\n <TableCell className=\"max-w-[14rem]\">\n {release.doi ? (\n <SafeLink\n href={doiUrl(release.doi)}\n className=\"inline-flex items-center gap-1 truncate text-sm text-text-primary underline-offset-2 hover:underline\"\n fallbackClassName=\"block truncate text-sm text-text-secondary\"\n >\n <span className=\"truncate\">{release.doi}</span>\n <ExternalLink className=\"size-3.5 shrink-0\" aria-hidden=\"true\" />\n </SafeLink>\n ) : (\n <span className=\"text-sm text-text-secondary\">—</span>\n )}\n </TableCell>\n <TableCell className=\"max-w-[14rem]\">\n {release.authors.length === 0 ? (\n <span className=\"text-sm text-text-secondary\">—</span>\n ) : (\n <span className=\"line-clamp-1 text-sm text-text-secondary\">\n {release.authors.join(', ')}\n </span>\n )}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {formatDate(release.publishedAt)}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {release.license || '—'}\n </TableCell>\n <TableCell className=\"text-right\">\n <ActionMenu\n ariaLabel={`Actions for ${release.title}`}\n onView={() => setDetail(release)}\n extraItems={\n canPublish(release.status)\n ? [\n {\n key: 'publish',\n label: 'Publish',\n icon: Rocket,\n onSelect: () => openPublish(release),\n },\n ]\n : []\n }\n />\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n\n {/* Mobile card list */}\n <ul className=\"space-y-3 md:hidden\">\n {releases.map((release) => (\n <li\n key={release.id}\n className=\"rounded-card border border-border-default bg-bg-elevated\"\n >\n <div className=\"flex items-start justify-between gap-2 p-4\">\n <div className=\"min-w-0 flex-1 space-y-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setDetail(release)}\n className=\"truncate text-left font-medium text-text-primary underline-offset-2 hover:underline\"\n >\n {release.title}\n </button>\n <StatusBadge status={release.status} />\n </div>\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-text-secondary\">\n <span>{release.version || 'v—'}</span>\n {release.license ? <span>{release.license}</span> : null}\n <span>{formatDate(release.publishedAt)}</span>\n </div>\n {release.authors.length > 0 ? (\n <p className=\"line-clamp-1 text-xs text-text-secondary\">\n {release.authors.join(', ')}\n </p>\n ) : null}\n {release.doi ? (\n <SafeLink\n href={doiUrl(release.doi)}\n className=\"inline-flex items-center gap-1 text-xs text-text-primary underline-offset-2 hover:underline\"\n fallbackClassName=\"block truncate text-xs text-text-secondary\"\n >\n <span className=\"truncate\">DOI: {release.doi}</span>\n <ExternalLink className=\"size-3 shrink-0\" aria-hidden=\"true\" />\n </SafeLink>\n ) : null}\n </div>\n <ActionMenu\n ariaLabel={`Actions for ${release.title}`}\n onView={() => setDetail(release)}\n extraItems={\n canPublish(release.status)\n ? [\n {\n key: 'publish',\n label: 'Publish',\n icon: Rocket,\n onSelect: () => openPublish(release),\n },\n ]\n : []\n }\n />\n </div>\n </li>\n ))}\n </ul>\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={releases.length}\n hasMore={pageInfo?.hasMore ?? false}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"releases\"\n loading={loading}\n />\n </>\n );\n };\n\n return (\n <PageLayout\n title=\"Releases\"\n description=\"Citable, versioned publications that bundle datasets and notebooks for the community.\"\n actions={newButton}\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"w-full max-w-[14rem]\">\n <Select value={statusFilter} onValueChange={setStatusFilter}>\n <SelectTrigger aria-label=\"Filter by status\">\n <SelectValue placeholder=\"All statuses\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All statuses</SelectItem>\n {RELEASE_STATUSES.map((status) => (\n <SelectItem key={status} value={status}>\n {formatEnumLabel(status)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n {renderBody()}\n </div>\n\n {/* View / detail dialog */}\n <Dialog open={detail !== null} onOpenChange={(open) => (open ? undefined : setDetail(null))}>\n <DialogContent className=\"max-h-[85vh] max-w-lg overflow-y-auto\">\n {detail ? (\n <>\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-2\">\n <BookOpen className=\"size-5 text-text-secondary\" aria-hidden=\"true\" />\n {detail.title}\n </DialogTitle>\n <DialogDescription>\n Version {detail.version || '—'} · <StatusBadge status={detail.status} />\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-4 text-sm\">\n <div>\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Abstract\n </p>\n <p className=\"mt-1 whitespace-pre-wrap text-text-primary\">\n {detail.abstract?.trim() || 'No abstract provided.'}\n </p>\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Authors\n </p>\n <p className=\"mt-1 text-text-primary\">\n {detail.authors.length ? detail.authors.join(', ') : '—'}\n </p>\n </div>\n <div>\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n License\n </p>\n <p className=\"mt-1 text-text-primary\">{detail.license || '—'}</p>\n </div>\n <div>\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Published\n </p>\n <p className=\"mt-1 text-text-primary\">{formatDate(detail.publishedAt)}</p>\n </div>\n <div>\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n DOI\n </p>\n <p className=\"mt-1\">\n {detail.doi ? (\n <SafeLink\n href={doiUrl(detail.doi)}\n className=\"inline-flex items-center gap-1 text-text-primary underline-offset-2 hover:underline\"\n fallbackClassName=\"text-text-secondary\"\n >\n {detail.doi}\n <ExternalLink className=\"size-3.5\" aria-hidden=\"true\" />\n </SafeLink>\n ) : (\n <span className=\"text-text-secondary\">Not assigned</span>\n )}\n </p>\n </div>\n </div>\n\n <div>\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Datasets ({detail.datasetIds.length})\n </p>\n <div className=\"mt-1 flex flex-wrap gap-1.5\">\n {detail.datasetIds.length === 0 ? (\n <span className=\"text-text-secondary\">None linked.</span>\n ) : (\n detail.datasetIds.map((datasetId) => {\n const name = datasetNameById.get(datasetId);\n return (\n <Button\n key={datasetId}\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-auto gap-1 px-2 py-1\"\n onClick={() => goToLinked(`datasets/${datasetId}`)}\n >\n <Database className=\"size-3.5\" aria-hidden=\"true\" />\n <span className={name ? '' : 'font-mono text-xs'}>\n {name ?? datasetId}\n </span>\n </Button>\n );\n })\n )}\n </div>\n </div>\n\n <div>\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Notebook\n </p>\n <div className=\"mt-1\">\n {detail.notebookId ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-auto gap-1 px-2 py-1\"\n onClick={() => goToLinked(`notebooks/${detail.notebookId}`)}\n >\n <NotebookPen className=\"size-3.5\" aria-hidden=\"true\" />\n <span\n className={\n notebookTitleById.get(detail.notebookId) ? '' : 'font-mono text-xs'\n }\n >\n {notebookTitleById.get(detail.notebookId) ?? detail.notebookId}\n </span>\n </Button>\n ) : (\n <span className=\"text-text-secondary\">None linked.</span>\n )}\n </div>\n </div>\n\n <div>\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Discussion\n </p>\n <div className=\"mt-2\">\n <DiscussionPanel\n entityType=\"cosmicintersection-release\"\n entityId={detail.id}\n title={detail.title}\n widgetKey=\"release-discussion\"\n />\n </div>\n </div>\n </div>\n\n <DialogFooter>\n {canPublish(detail.status) ? (\n <Button\n type=\"button\"\n onClick={() => {\n const target = detail;\n setDetail(null);\n openPublish(target);\n }}\n >\n <Rocket className=\"size-4\" />\n Publish\n </Button>\n ) : null}\n <DialogClose asChild>\n <Button type=\"button\" variant=\"outline\">\n Close\n </Button>\n </DialogClose>\n </DialogFooter>\n </>\n ) : null}\n </DialogContent>\n </Dialog>\n\n {/* Publish dialog */}\n <Dialog\n open={publishTarget !== null}\n onOpenChange={(open) => (open || publishing ? undefined : setPublishTarget(null))}\n >\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>Publish release</DialogTitle>\n <DialogDescription>\n {publishTarget\n ? `Publish “${publishTarget.title}” to make it publicly citable. You can attach a DOI now or later.`\n : ''}\n </DialogDescription>\n </DialogHeader>\n\n {publishTarget ? (\n <dl className=\"grid grid-cols-3 gap-x-4 gap-y-3 rounded-card border border-border-default bg-bg-sunken p-3\">\n <div className=\"col-span-2 min-w-0\">\n <dt className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Authors\n </dt>\n <dd\n className=\"mt-0.5 truncate text-sm text-text-primary\"\n title={publishTarget.authors.join(', ')}\n >\n {publishTarget.authors.length ? publishTarget.authors.join(', ') : '—'}\n </dd>\n </div>\n <div>\n <dt className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Datasets\n </dt>\n <dd className=\"mt-0.5 text-sm tabular-nums text-text-primary\">\n {publishTarget.datasetIds.length}\n </dd>\n </div>\n <div className=\"col-span-3 min-w-0\">\n <dt className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n License\n </dt>\n <dd className=\"mt-0.5 truncate text-sm text-text-primary\">\n {publishTarget.license || '—'}\n </dd>\n </div>\n </dl>\n ) : null}\n\n <div className=\"space-y-2\">\n <Label htmlFor=\"release-doi\">DOI (optional)</Label>\n <Input\n id=\"release-doi\"\n value={doiDraft}\n onChange={(event) => setDoiDraft(event.target.value)}\n placeholder=\"10.1234/cosmic.2026.0001\"\n />\n <p className=\"text-xs text-text-secondary\">\n A Digital Object Identifier makes the release permanently referenceable.\n </p>\n </div>\n\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => setPublishTarget(null)}\n disabled={publishing}\n >\n Cancel\n </Button>\n <Button type=\"button\" onClick={() => void submitPublish()} disabled={publishing}>\n <Rocket className=\"size-4\" />\n {publishing ? 'Publishing…' : 'Publish release'}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -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-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 +1 @@
1
- {"version":3,"file":"RunsPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/runs/RunsPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { Activity, Eye } from 'lucide-react';\nimport {\n Button,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\n\nimport { useCosPipelinesQuery, useCosRunsQuery, type CosRunStatus } from '../../api/cosmicApi';\nimport {\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n Pagination,\n StatusBadge,\n formatEnumLabel,\n usePagination,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\n\nconst STATUS_OPTIONS: CosRunStatus[] = ['QUEUED', 'RUNNING', 'SUCCEEDED', 'FAILED', 'CANCELLED'];\nconst ALL = 'ALL';\n\nfunction formatDate(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? '—' : date.toLocaleString();\n}\n\nfunction formatDuration(ms?: number | null): string {\n if (ms === null || ms === undefined) {\n return '—';\n }\n if (ms < 1000) {\n return `${ms} ms`;\n }\n const seconds = ms / 1000;\n if (seconds < 60) {\n return `${seconds.toFixed(1)} s`;\n }\n const minutes = Math.floor(seconds / 60);\n const rest = Math.round(seconds % 60);\n return `${minutes}m ${rest}s`;\n}\n\nexport default function RunsPage() {\n const { navigate } = useCosmicIntersection();\n const [status, setStatus] = useState<CosRunStatus | typeof ALL>(ALL);\n const [pipelineId, setPipelineId] = useState<string | typeof ALL>(ALL);\n const pagination = usePagination({ resetKey: `${status}:${pipelineId}` });\n\n // Load pipelines to populate the filter and to resolve run -> pipeline names.\n const { data: pipelinesData } = useCosPipelinesQuery({ pagination: { limit: 100, offset: 0 } });\n const pipelines = useMemo(() => pipelinesData?.cosListPipelines.items ?? [], [pipelinesData]);\n const pipelineNameById = useMemo(() => {\n const map = new Map<string, string>();\n pipelines.forEach((pipeline) => map.set(pipeline.id, pipeline.name));\n return map;\n }, [pipelines]);\n\n const { data, loading, error, refetch } = useCosRunsQuery({\n status: status === ALL ? undefined : status,\n pipelineId: pipelineId === ALL ? undefined : pipelineId,\n pagination: { limit: pagination.limit, offset: pagination.offset },\n });\n\n const runs = useMemo(() => data?.cosListRuns.items ?? [], [data]);\n const pageInfo = data?.cosListRuns.pageInfo;\n\n const renderBody = () => {\n if (loading) {\n return <LoadingState variant=\"rows\" rows={6} label=\"Loading runs…\" />;\n }\n if (error) {\n return (\n <ErrorState\n message=\"We could not load pipeline runs. Please try again.\"\n onRetry={() => void refetch()}\n />\n );\n }\n if (runs.length === 0) {\n return (\n <EmptyState\n icon={Activity}\n title={\n status === ALL && pipelineId === ALL ? 'No runs yet' : 'No runs match these filters'\n }\n description={\n status === ALL && pipelineId === ALL\n ? 'Runs appear here once you start a pipeline. Each run tracks its steps, timings and metrics.'\n : 'Try a different status or pipeline filter.'\n }\n />\n );\n }\n\n return (\n <>\n {/* Desktop: table. */}\n <div className=\"hidden rounded-card border border-border-default bg-bg-elevated md:block\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Run</TableHead>\n <TableHead>Pipeline</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Started</TableHead>\n <TableHead>Finished</TableHead>\n <TableHead>Duration</TableHead>\n <TableHead>Triggered by</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {runs.map((run) => (\n <TableRow\n key={run.id}\n className=\"cursor-pointer\"\n onClick={() => navigate(`runs/${run.id}`)}\n >\n <TableCell className=\"font-medium text-text-primary\">#{run.runNumber}</TableCell>\n <TableCell className=\"text-text-secondary\">\n <span className=\"truncate\">\n {pipelineNameById.get(run.pipelineId) ?? (\n <span className=\"font-mono text-xs\">{run.pipelineId}</span>\n )}\n </span>\n </TableCell>\n <TableCell>\n <StatusBadge status={run.status} />\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-text-secondary\">\n {formatDate(run.startedAt)}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-text-secondary\">\n {formatDate(run.finishedAt)}\n </TableCell>\n <TableCell className=\"tabular-nums text-text-secondary\">\n {formatDuration(run.durationMs)}\n </TableCell>\n <TableCell className=\"text-text-secondary\">{run.triggeredBy || '—'}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n\n {/* Mobile: stacked cards. */}\n <ul className=\"space-y-3 md:hidden\">\n {runs.map((run) => (\n <li key={run.id}>\n <button\n type=\"button\"\n onClick={() => navigate(`runs/${run.id}`)}\n className=\"flex w-full flex-col gap-2 rounded-card border border-border-default bg-bg-elevated p-4 text-left transition-colors hover:border-border-strong\"\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"font-medium text-text-primary\">#{run.runNumber}</span>\n <StatusBadge status={run.status} />\n </div>\n <p className=\"truncate text-sm text-text-secondary\">\n {pipelineNameById.get(run.pipelineId) ?? (\n <span className=\"font-mono text-xs\">{run.pipelineId}</span>\n )}\n </p>\n <dl className=\"grid grid-cols-2 gap-x-3 gap-y-1 text-xs\">\n <div className=\"flex flex-col\">\n <dt className=\"text-text-secondary\">Started</dt>\n <dd className=\"text-text-primary\">{formatDate(run.startedAt)}</dd>\n </div>\n <div className=\"flex flex-col\">\n <dt className=\"text-text-secondary\">Duration</dt>\n <dd className=\"tabular-nums text-text-primary\">\n {formatDuration(run.durationMs)}\n </dd>\n </div>\n <div className=\"flex flex-col\">\n <dt className=\"text-text-secondary\">Finished</dt>\n <dd className=\"text-text-primary\">{formatDate(run.finishedAt)}</dd>\n </div>\n <div className=\"flex flex-col\">\n <dt className=\"text-text-secondary\">Triggered by</dt>\n <dd className=\"text-text-primary\">{run.triggeredBy || '—'}</dd>\n </div>\n </dl>\n </button>\n </li>\n ))}\n </ul>\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={runs.length}\n hasMore={pageInfo?.hasMore ?? false}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"runs\"\n loading={loading}\n />\n </>\n );\n };\n\n return (\n <PageLayout\n title=\"Runs\"\n description=\"Every pipeline execution, with status, timings and outcomes.\"\n actions={\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('pipelines')}>\n <Eye className=\"size-4\" />\n View pipelines\n </Button>\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <span>Status</span>\n <Select\n value={status}\n onValueChange={(value) => setStatus(value as CosRunStatus | typeof ALL)}\n >\n <SelectTrigger aria-label=\"Filter by status\" className=\"w-40\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All statuses</SelectItem>\n {STATUS_OPTIONS.map((option) => (\n <SelectItem key={option} value={option}>\n {formatEnumLabel(option)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <span>Pipeline</span>\n <Select\n value={pipelineId}\n onValueChange={(value) => setPipelineId(value as string | typeof ALL)}\n >\n <SelectTrigger aria-label=\"Filter by pipeline\" className=\"w-56\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All pipelines</SelectItem>\n {pipelines.map((pipeline) => (\n <SelectItem key={pipeline.id} value={pipeline.id}>\n {pipeline.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {renderBody()}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"RunsPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/runs/RunsPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { Activity, Eye } from 'lucide-react';\nimport {\n Button,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\n\nimport { useCosPipelinesQuery, useCosRunsQuery, type CosRunStatus } from '../../api/cosmicApi';\nimport {\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n Pagination,\n StatusBadge,\n formatEnumLabel,\n usePagination,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\n\nconst STATUS_OPTIONS: CosRunStatus[] = ['QUEUED', 'RUNNING', 'SUCCEEDED', 'FAILED', 'CANCELLED'];\nconst ALL = 'ALL';\n\nfunction formatDate(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? '—' : date.toLocaleString();\n}\n\nfunction formatDuration(ms?: number | null): string {\n if (ms === null || ms === undefined) {\n return '—';\n }\n if (ms < 1000) {\n return `${ms} ms`;\n }\n const seconds = ms / 1000;\n if (seconds < 60) {\n return `${seconds.toFixed(1)} s`;\n }\n const minutes = Math.floor(seconds / 60);\n const rest = Math.round(seconds % 60);\n return `${minutes}m ${rest}s`;\n}\n\nexport default function RunsPage() {\n const { navigate } = useCosmicIntersection();\n const [status, setStatus] = useState<CosRunStatus | typeof ALL>(ALL);\n const [pipelineId, setPipelineId] = useState<string | typeof ALL>(ALL);\n const pagination = usePagination({ resetKey: `${status}:${pipelineId}` });\n\n // Load pipelines to populate the filter and to resolve run -> pipeline names.\n const { data: pipelinesData } = useCosPipelinesQuery({ pagination: { limit: 100, offset: 0 } });\n const pipelines = useMemo(() => pipelinesData?.cosListPipelines.items ?? [], [pipelinesData]);\n const pipelineNameById = useMemo(() => {\n const map = new Map<string, string>();\n pipelines.forEach((pipeline) => map.set(pipeline.id, pipeline.name));\n return map;\n }, [pipelines]);\n\n const { data, loading, error, refetch } = useCosRunsQuery({\n status: status === ALL ? undefined : status,\n pipelineId: pipelineId === ALL ? undefined : pipelineId,\n pagination: { limit: pagination.limit, offset: pagination.offset },\n });\n\n const runs = useMemo(() => data?.cosListRuns.items ?? [], [data]);\n const pageInfo = data?.cosListRuns.pageInfo;\n\n const renderBody = () => {\n if (loading) {\n return <LoadingState variant=\"rows\" rows={6} label=\"Loading runs…\" />;\n }\n if (error) {\n return (\n <ErrorState\n message=\"We could not load pipeline runs. Please try again.\"\n onRetry={() => void refetch()}\n />\n );\n }\n if (runs.length === 0) {\n return (\n <EmptyState\n icon={Activity}\n title={\n status === ALL && pipelineId === ALL ? 'No runs yet' : 'No runs match these filters'\n }\n description={\n status === ALL && pipelineId === ALL\n ? 'Runs appear here once you start a pipeline. Each run tracks its steps, timings and metrics.'\n : 'Try a different status or pipeline filter.'\n }\n />\n );\n }\n\n return (\n <>\n {/* Desktop: table. */}\n <div className=\"hidden rounded-card border border-border-default bg-bg-elevated md:block\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Run</TableHead>\n <TableHead>Pipeline</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Started</TableHead>\n <TableHead>Finished</TableHead>\n <TableHead>Duration</TableHead>\n <TableHead>Triggered by</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {runs.map((run) => (\n <TableRow\n key={run.id}\n className=\"cursor-pointer\"\n onClick={() => navigate(`runs/${run.id}`)}\n >\n <TableCell className=\"font-medium text-text-primary\">#{run.runNumber}</TableCell>\n <TableCell className=\"text-text-secondary\">\n <span className=\"truncate\">\n {pipelineNameById.get(run.pipelineId) ?? (\n <span className=\"font-mono text-xs\">{run.pipelineId}</span>\n )}\n </span>\n </TableCell>\n <TableCell>\n <StatusBadge status={run.status} />\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-text-secondary\">\n {formatDate(run.startedAt)}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-text-secondary\">\n {formatDate(run.finishedAt)}\n </TableCell>\n <TableCell className=\"tabular-nums text-text-secondary\">\n {formatDuration(run.durationMs)}\n </TableCell>\n <TableCell className=\"text-text-secondary\">{run.triggeredBy || '—'}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n\n {/* Mobile: stacked cards. */}\n <ul className=\"space-y-3 md:hidden\">\n {runs.map((run) => (\n <li key={run.id}>\n <button\n type=\"button\"\n onClick={() => navigate(`runs/${run.id}`)}\n className=\"flex w-full flex-col gap-2 rounded-card border border-border-default bg-bg-elevated p-4 text-left transition-colors hover:border-border-strong\"\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"font-medium text-text-primary\">#{run.runNumber}</span>\n <StatusBadge status={run.status} />\n </div>\n <p className=\"truncate text-sm text-text-secondary\">\n {pipelineNameById.get(run.pipelineId) ?? (\n <span className=\"font-mono text-xs\">{run.pipelineId}</span>\n )}\n </p>\n <dl className=\"grid grid-cols-2 gap-x-3 gap-y-1 text-xs\">\n <div className=\"flex flex-col\">\n <dt className=\"text-text-secondary\">Started</dt>\n <dd className=\"text-text-primary\">{formatDate(run.startedAt)}</dd>\n </div>\n <div className=\"flex flex-col\">\n <dt className=\"text-text-secondary\">Duration</dt>\n <dd className=\"tabular-nums text-text-primary\">\n {formatDuration(run.durationMs)}\n </dd>\n </div>\n <div className=\"flex flex-col\">\n <dt className=\"text-text-secondary\">Finished</dt>\n <dd className=\"text-text-primary\">{formatDate(run.finishedAt)}</dd>\n </div>\n <div className=\"flex flex-col\">\n <dt className=\"text-text-secondary\">Triggered by</dt>\n <dd className=\"text-text-primary\">{run.triggeredBy || '—'}</dd>\n </div>\n </dl>\n </button>\n </li>\n ))}\n </ul>\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={runs.length}\n hasMore={pageInfo?.hasMore ?? false}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"runs\"\n loading={loading}\n />\n </>\n );\n };\n\n return (\n <PageLayout\n title=\"Runs\"\n description=\"Every pipeline execution, with status, timings and outcomes.\"\n actions={\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('pipelines')}>\n <Eye className=\"size-4\" />\n View pipelines\n </Button>\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <span>Status</span>\n <Select\n value={status}\n onValueChange={(value) => setStatus(value as CosRunStatus | typeof ALL)}\n >\n <SelectTrigger aria-label=\"Filter by status\" className=\"w-40\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All statuses</SelectItem>\n {STATUS_OPTIONS.map((option) => (\n <SelectItem key={option} value={option}>\n {formatEnumLabel(option)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <span>Pipeline</span>\n <Select\n value={pipelineId}\n onValueChange={(value) => setPipelineId(value as string | typeof ALL)}\n >\n <SelectTrigger aria-label=\"Filter by pipeline\" className=\"w-56\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All pipelines</SelectItem>\n {pipelines.map((pipeline) => (\n <SelectItem key={pipeline.id} value={pipeline.id}>\n {pipeline.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {renderBody()}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -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-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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"}