@burdenoff/microfe-cosmicintersection 2026.830.1 → 2026.830.2

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 (35) hide show
  1. package/dist/cosmicintersection/components/ActionMenu.d.ts.map +1 -1
  2. package/dist/cosmicintersection/components/ActionMenu.js.map +1 -1
  3. package/dist/cosmicintersection/components/ErrorState.d.ts.map +1 -1
  4. package/dist/cosmicintersection/components/ErrorState.js.map +1 -1
  5. package/dist/cosmicintersection/components/FileUploadField.d.ts.map +1 -1
  6. package/dist/cosmicintersection/components/FileUploadField.js.map +1 -1
  7. package/dist/cosmicintersection/components/Pagination.d.ts.map +1 -1
  8. package/dist/cosmicintersection/components/Pagination.js.map +1 -1
  9. package/dist/cosmicintersection/components/StatusBadge.d.ts.map +1 -1
  10. package/dist/cosmicintersection/components/StatusBadge.js.map +1 -1
  11. package/dist/cosmicintersection/pages/access/AccessPage.js.map +1 -1
  12. package/dist/cosmicintersection/pages/analyses/AnalysesPage.d.ts.map +1 -1
  13. package/dist/cosmicintersection/pages/analyses/AnalysesPage.js.map +1 -1
  14. package/dist/cosmicintersection/pages/collections/CollectionsPage.d.ts.map +1 -1
  15. package/dist/cosmicintersection/pages/collections/CollectionsPage.js.map +1 -1
  16. package/dist/cosmicintersection/pages/dashboard/DashboardPage.d.ts.map +1 -1
  17. package/dist/cosmicintersection/pages/dashboard/DashboardPage.js.map +1 -1
  18. package/dist/cosmicintersection/pages/datasets/DatasetsPage.d.ts.map +1 -1
  19. package/dist/cosmicintersection/pages/datasets/DatasetsPage.js.map +1 -1
  20. package/dist/cosmicintersection/pages/discovery/DiscoveryPage.d.ts.map +1 -1
  21. package/dist/cosmicintersection/pages/discovery/DiscoveryPage.js.map +1 -1
  22. package/dist/cosmicintersection/pages/lineage/LineagePage.d.ts.map +1 -1
  23. package/dist/cosmicintersection/pages/lineage/LineagePage.js.map +1 -1
  24. package/dist/cosmicintersection/pages/notebooks/NotebooksPage.d.ts.map +1 -1
  25. package/dist/cosmicintersection/pages/notebooks/NotebooksPage.js.map +1 -1
  26. package/dist/cosmicintersection/pages/pipelines/PipelinesPage.d.ts.map +1 -1
  27. package/dist/cosmicintersection/pages/pipelines/PipelinesPage.js.map +1 -1
  28. package/dist/cosmicintersection/pages/releases/ReleasesPage.js.map +1 -1
  29. package/dist/cosmicintersection/pages/runs/RunsPage.d.ts.map +1 -1
  30. package/dist/cosmicintersection/pages/runs/RunsPage.js.map +1 -1
  31. package/dist/cosmicintersection/pages/sources/SourcesPage.d.ts.map +1 -1
  32. package/dist/cosmicintersection/pages/sources/SourcesPage.js.map +1 -1
  33. package/dist/shared/hooks/useTr.d.ts.map +1 -1
  34. package/dist/shared/hooks/useTr.js.map +1 -1
  35. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"DatasetsPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/datasets/DatasetsPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { Compass, Database, Plus } from 'lucide-react';\nimport {\n Badge,\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 {\n useCosArchiveDatasetMutation,\n useCosCollectionsQuery,\n useCosDatasetsQuery,\n useCosPublishDatasetMutation,\n useCosSourcesQuery,\n type CosAccessLevel,\n type CosDataset,\n type CosDatasetStatus,\n} from '../../api/cosmicApi';\nimport {\n ActionMenu,\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n Pagination,\n StatusBadge,\n formatEnumLabel,\n usePagination,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\nimport { useTr } from '../../../shared/hooks/useTr';\n\nconst DATASET_STATUSES: CosDatasetStatus[] = [\n 'DRAFT',\n 'INGESTING',\n 'AVAILABLE',\n 'DEPRECATED',\n 'ARCHIVED',\n];\n\nconst ACCESS_LEVELS: CosAccessLevel[] = ['PUBLIC', 'WORKSPACE', 'RESTRICTED', 'EMBARGOED'];\n\nconst ALL = '__all__';\n\n/** Generous ceiling for deriving the distinct-format dropdown options. */\nconst FORMAT_POOL_LIMIT = 200;\n\n/** BigInt byte string → human-readable size, or an em dash when absent/zero. */\nfunction formatBytes(raw: string | null | undefined): string {\n if (!raw) {\n return '—';\n }\n const bytes = Number(raw);\n if (!Number.isFinite(bytes) || bytes <= 0) {\n return '—';\n }\n const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];\n let value = bytes;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit += 1;\n }\n return `${value >= 10 || unit === 0 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;\n}\n\nfunction formatDate(raw: string | null | undefined): string {\n if (!raw) {\n return '—';\n }\n const date = new Date(raw);\n return Number.isNaN(date.getTime()) ? '—' : date.toLocaleDateString();\n}\n\nexport default function DatasetsPage() {\n const { navigate } = useCosmicIntersection();\n const tr = useTr();\n\n const [status, setStatus] = useState<string>(ALL);\n const [sourceId, setSourceId] = useState<string>(ALL);\n const [collectionId, setCollectionId] = useState<string>(ALL);\n const [format, setFormat] = useState<string>(ALL);\n const [accessLevel, setAccessLevel] = useState<string>(ALL);\n\n const pagination = usePagination({\n pageSize: 20,\n resetKey: `${status}|${sourceId}|${collectionId}|${format}|${accessLevel}`,\n });\n\n const { data, loading, error, refetch } = useCosDatasetsQuery({\n status: status === ALL ? undefined : (status as CosDatasetStatus),\n sourceId: sourceId === ALL ? undefined : sourceId,\n collectionId: collectionId === ALL ? undefined : collectionId,\n format: format === ALL ? undefined : format,\n accessLevel: accessLevel === ALL ? undefined : (accessLevel as CosAccessLevel),\n pagination: { limit: pagination.limit, offset: pagination.offset },\n });\n\n const sourcesQuery = useCosSourcesQuery({ pagination: { limit: 100, offset: 0 } });\n const collectionsQuery = useCosCollectionsQuery();\n // Distinct format values are derived from a real sample of the catalog so the\n // Format dropdown reflects what actually exists (format is free-text, no enum).\n const formatsPool = useCosDatasetsQuery({\n pagination: { limit: FORMAT_POOL_LIMIT, offset: 0 },\n });\n\n const [publishDataset] = useCosPublishDatasetMutation();\n const [archiveDataset] = useCosArchiveDatasetMutation();\n\n const datasets = useMemo(() => data?.cosListDatasets?.items ?? [], [data]);\n const pageInfo = data?.cosListDatasets?.pageInfo;\n const sources = sourcesQuery.data?.cosListSources?.items ?? [];\n const collections = collectionsQuery.data?.cosListCollections ?? [];\n const formats = useMemo(() => {\n const seen = new Set<string>();\n for (const dataset of formatsPool.data?.cosListDatasets?.items ?? []) {\n if (dataset.format) {\n seen.add(dataset.format);\n }\n }\n return [...seen].sort((a, b) => a.localeCompare(b));\n }, [formatsPool.data]);\n\n const noFilters =\n status === ALL &&\n sourceId === ALL &&\n collectionId === ALL &&\n format === ALL &&\n accessLevel === ALL;\n\n const handlePublish = async (dataset: CosDataset) => {\n const response = await publishDataset({ variables: { id: dataset.id } });\n if (response.data?.cosPublishDataset) {\n notify.success(\n tr('cosmicintersection.pages.datasets.publishSuccess', 'Dataset published'),\n tr(\n 'cosmicintersection.pages.datasets.publishSuccessDescription',\n '“{{name}}” is now available.',\n { name: dataset.name },\n ),\n );\n await refetch();\n }\n };\n\n const handleArchive = async (dataset: CosDataset) => {\n const response = await archiveDataset({ variables: { id: dataset.id } });\n if (response.error) {\n throw new Error(response.error.message);\n }\n if (!response.data?.cosArchiveDataset) {\n notify.error(\n tr('cosmicintersection.pages.datasets.archiveError', 'Could not archive dataset'),\n tr('cosmicintersection.pages.datasets.archiveErrorDescription', 'The server did not confirm the change.'),\n );\n throw new Error('Archive not confirmed');\n }\n notify.success(tr('cosmicintersection.pages.datasets.archiveSuccess', 'Dataset archived'));\n await refetch();\n };\n\n return (\n <PageLayout\n title={tr('cosmicintersection.pages.datasets.title', 'Datasets')}\n description={tr(\n 'cosmicintersection.pages.datasets.description',\n 'Federated science datasets — catalog, schema, lineage and access, all from live data.',\n )}\n actions={\n <>\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('discovery')}>\n <Compass className=\"size-4\" />\n {tr('nav.discover', 'Discover')}\n </Button>\n <Button type=\"button\" onClick={() => navigate('datasets/create')}>\n <Plus className=\"size-4\" />\n {tr('cosmicintersection.pages.datasets.create', 'New dataset')}\n </Button>\n </>\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-end gap-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">\n {tr('cosmicintersection.common.status', 'Status')}\n </span>\n <Select value={status} onValueChange={setStatus}>\n <SelectTrigger aria-label={tr('cosmicintersection.common.filterByStatus', 'Filter by status')} className=\"w-44\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>{tr('cosmicintersection.common.allStatuses', 'All statuses')}</SelectItem>\n {DATASET_STATUSES.map((value) => (\n <SelectItem key={value} value={value}>\n {formatEnumLabel(value)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">\n {tr('cosmicintersection.common.source', 'Source')}\n </span>\n <Select value={sourceId} onValueChange={setSourceId}>\n <SelectTrigger aria-label={tr('cosmicintersection.common.filterBySource', 'Filter by source')} className=\"w-48\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>{tr('cosmicintersection.common.allSources', 'All sources')}</SelectItem>\n {sources.map((source) => (\n <SelectItem key={source.id} value={source.id}>\n {source.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">\n {tr('cosmicintersection.common.collection', 'Collection')}\n </span>\n <Select value={collectionId} onValueChange={setCollectionId}>\n <SelectTrigger aria-label={tr('cosmicintersection.common.filterByCollection', 'Filter by collection')} className=\"w-48\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>{tr('cosmicintersection.common.allCollections', 'All collections')}</SelectItem>\n {collections.map((collection) => (\n <SelectItem key={collection.id} value={collection.id}>\n {collection.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">\n {tr('cosmicintersection.common.format', 'Format')}\n </span>\n <Select value={format} onValueChange={setFormat} disabled={formats.length === 0}>\n <SelectTrigger aria-label={tr('cosmicintersection.common.filterByFormat', 'Filter by format')} className=\"w-40\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>{tr('cosmicintersection.common.allFormats', 'All formats')}</SelectItem>\n {formats.map((value) => (\n <SelectItem key={value} value={value}>\n {value}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">\n {tr('cosmicintersection.common.access', 'Access')}\n </span>\n <Select value={accessLevel} onValueChange={setAccessLevel}>\n <SelectTrigger aria-label={tr('cosmicintersection.common.filterByAccess', 'Filter by access level')} className=\"w-44\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>{tr('cosmicintersection.common.allAccessLevels', 'All access levels')}</SelectItem>\n {ACCESS_LEVELS.map((value) => (\n <SelectItem key={value} value={value}>\n {formatEnumLabel(value)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {loading ? (\n <LoadingState variant=\"rows\" rows={6} label={tr('cosmicintersection.pages.datasets.loading', 'Loading datasets…')} />\n ) : error ? (\n <ErrorState\n message={tr('cosmicintersection.pages.datasets.error', 'We could not load datasets. Please try again.')}\n onRetry={() => void refetch()}\n />\n ) : datasets.length === 0 ? (\n <EmptyState\n icon={Database}\n title={\n noFilters\n ? tr('cosmicintersection.pages.datasets.emptyTitle', 'No datasets yet.')\n : tr('cosmicintersection.pages.datasets.emptyFilterTitle', 'No datasets match these filters.')\n }\n description={\n noFilters\n ? tr(\n 'cosmicintersection.pages.datasets.emptyDescription',\n 'Register your first dataset to start federating science data across sources.',\n )\n : tr(\n 'cosmicintersection.pages.datasets.emptyFilterDescription',\n 'Try a different status, source, collection, format or access level.',\n )\n }\n action={\n noFilters ? (\n <Button type=\"button\" onClick={() => navigate('datasets/create')}>\n <Plus className=\"size-4\" />\n {tr('cosmicintersection.pages.datasets.create', 'New dataset')}\n </Button>\n ) : undefined\n }\n />\n ) : (\n <>\n {/* Desktop / tablet: table */}\n <div className=\"hidden rounded-card border border-border-default bg-bg-elevated md:block\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>{tr('cosmicintersection.common.name', 'Name')}</TableHead>\n <TableHead>{tr('cosmicintersection.common.status', 'Status')}</TableHead>\n <TableHead>{tr('cosmicintersection.common.access', 'Access')}</TableHead>\n <TableHead>{tr('cosmicintersection.common.format', 'Format')}</TableHead>\n <TableHead className=\"text-right\">{tr('cosmicintersection.common.size', 'Size')}</TableHead>\n <TableHead>{tr('cosmicintersection.common.tags', 'Tags')}</TableHead>\n <TableHead>{tr('cosmicintersection.common.updated', 'Updated')}</TableHead>\n <TableHead className=\"text-right\">{tr('cosmicintersection.common.actions', 'Actions')}</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>\n <span className=\"font-medium text-text-primary\">{dataset.name}</span>\n {dataset.description ? (\n <p className=\"mt-0.5 line-clamp-1 max-w-xs text-xs text-text-secondary\">\n {dataset.description}\n </p>\n ) : null}\n </TableCell>\n <TableCell>\n <StatusBadge status={dataset.status} />\n </TableCell>\n <TableCell>\n <span className=\"text-sm text-text-secondary\">\n {formatEnumLabel(dataset.accessLevel)}\n </span>\n </TableCell>\n <TableCell>\n <span className=\"text-sm text-text-secondary\">{dataset.format || '—'}</span>\n </TableCell>\n <TableCell className=\"text-right text-sm tabular-nums text-text-secondary\">\n {formatBytes(dataset.sizeBytes)}\n </TableCell>\n <TableCell>\n <div className=\"flex max-w-[12rem] flex-wrap gap-1\">\n {dataset.tags.length === 0 ? (\n <span className=\"text-xs text-text-secondary\">—</span>\n ) : (\n <>\n {dataset.tags.slice(0, 2).map((tag) => (\n <Badge key={tag} variant=\"secondary\">\n {tag}\n </Badge>\n ))}\n {dataset.tags.length > 2 ? (\n <Badge variant=\"secondary\">+{dataset.tags.length - 2}</Badge>\n ) : null}\n </>\n )}\n </div>\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {formatDate(dataset.updatedAt)}\n </TableCell>\n <TableCell className=\"text-right\">\n <ActionMenu\n ariaLabel={tr('cosmicintersection.common.actionsFor', 'Actions for {{name}}', { name: dataset.name })}\n entityName={dataset.name}\n onView={() => navigate(`datasets/${dataset.id}`)}\n onEdit={() => navigate(`datasets/${dataset.id}/edit`)}\n extraItems={\n dataset.status === 'AVAILABLE' || dataset.status === 'ARCHIVED'\n ? []\n : [\n {\n key: 'publish',\n label: tr('cosmicintersection.common.publish', 'Publish'),\n onSelect: () => void handlePublish(dataset),\n },\n ]\n }\n onDelete={() => handleArchive(dataset)}\n deleteLabel={tr('cosmicintersection.pages.datasets.archive', 'Archive dataset')}\n />\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={tr('cosmicintersection.common.open', 'Open {{name}}', { name: 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-3 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 <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"font-medium text-text-primary\">{dataset.name}</p>\n {dataset.description ? (\n <p className=\"mt-0.5 line-clamp-2 text-xs text-text-secondary\">\n {dataset.description}\n </p>\n ) : null}\n </div>\n <div onClick={(event) => event.stopPropagation()}>\n <ActionMenu\n ariaLabel={tr('cosmicintersection.common.actionsFor', 'Actions for {{name}}', { name: dataset.name })}\n entityName={dataset.name}\n onView={() => navigate(`datasets/${dataset.id}`)}\n onEdit={() => navigate(`datasets/${dataset.id}/edit`)}\n extraItems={\n dataset.status === 'AVAILABLE' || dataset.status === 'ARCHIVED'\n ? []\n : [\n {\n key: 'publish',\n label: tr('cosmicintersection.common.publish', 'Publish'),\n onSelect: () => void handlePublish(dataset),\n },\n ]\n }\n onDelete={() => handleArchive(dataset)}\n deleteLabel={tr('cosmicintersection.pages.datasets.archive', 'Archive dataset')}\n />\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={dataset.status} />\n <StatusBadge status={dataset.accessLevel} />\n </div>\n\n <dl className=\"grid grid-cols-2 gap-x-4 gap-y-1 text-xs\">\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">{tr('cosmicintersection.common.format', 'Format')}</dt>\n <dd className=\"text-text-primary\">{dataset.format || '—'}</dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">{tr('cosmicintersection.common.size', 'Size')}</dt>\n <dd className=\"tabular-nums text-text-primary\">\n {formatBytes(dataset.sizeBytes)}\n </dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">{tr('cosmicintersection.common.updated', 'Updated')}</dt>\n <dd className=\"text-text-primary\">{formatDate(dataset.updatedAt)}</dd>\n </div>\n </dl>\n\n {dataset.tags.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {dataset.tags.slice(0, 4).map((tag) => (\n <Badge key={tag} variant=\"secondary\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n {dataset.tags.length > 4 ? (\n <span className=\"text-xs text-text-secondary\">\n +{dataset.tags.length - 4}\n </span>\n ) : null}\n </div>\n ) : null}\n </div>\n </li>\n ))}\n </ul>\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={datasets.length}\n hasMore={Boolean(pageInfo?.hasMore)}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel={tr('cosmicintersection.pages.datasets.itemLabel', 'datasets')}\n loading={loading}\n />\n </>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DatasetsPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/datasets/DatasetsPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { Compass, Database, Plus } from 'lucide-react';\nimport {\n Badge,\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 {\n useCosArchiveDatasetMutation,\n useCosCollectionsQuery,\n useCosDatasetsQuery,\n useCosPublishDatasetMutation,\n useCosSourcesQuery,\n type CosAccessLevel,\n type CosDataset,\n type CosDatasetStatus,\n} from '../../api/cosmicApi';\nimport {\n ActionMenu,\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n Pagination,\n StatusBadge,\n formatEnumLabel,\n usePagination,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\nimport { useTr } from '../../../shared/hooks/useTr';\n\nconst DATASET_STATUSES: CosDatasetStatus[] = [\n 'DRAFT',\n 'INGESTING',\n 'AVAILABLE',\n 'DEPRECATED',\n 'ARCHIVED',\n];\n\nconst ACCESS_LEVELS: CosAccessLevel[] = ['PUBLIC', 'WORKSPACE', 'RESTRICTED', 'EMBARGOED'];\n\nconst ALL = '__all__';\n\n/** Generous ceiling for deriving the distinct-format dropdown options. */\nconst FORMAT_POOL_LIMIT = 200;\n\n/** BigInt byte string → human-readable size, or an em dash when absent/zero. */\nfunction formatBytes(raw: string | null | undefined): string {\n if (!raw) {\n return '—';\n }\n const bytes = Number(raw);\n if (!Number.isFinite(bytes) || bytes <= 0) {\n return '—';\n }\n const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];\n let value = bytes;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit += 1;\n }\n return `${value >= 10 || unit === 0 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;\n}\n\nfunction formatDate(raw: string | null | undefined): string {\n if (!raw) {\n return '—';\n }\n const date = new Date(raw);\n return Number.isNaN(date.getTime()) ? '—' : date.toLocaleDateString();\n}\n\nexport default function DatasetsPage() {\n const { navigate } = useCosmicIntersection();\n const tr = useTr();\n\n const [status, setStatus] = useState<string>(ALL);\n const [sourceId, setSourceId] = useState<string>(ALL);\n const [collectionId, setCollectionId] = useState<string>(ALL);\n const [format, setFormat] = useState<string>(ALL);\n const [accessLevel, setAccessLevel] = useState<string>(ALL);\n\n const pagination = usePagination({\n pageSize: 20,\n resetKey: `${status}|${sourceId}|${collectionId}|${format}|${accessLevel}`,\n });\n\n const { data, loading, error, refetch } = useCosDatasetsQuery({\n status: status === ALL ? undefined : (status as CosDatasetStatus),\n sourceId: sourceId === ALL ? undefined : sourceId,\n collectionId: collectionId === ALL ? undefined : collectionId,\n format: format === ALL ? undefined : format,\n accessLevel: accessLevel === ALL ? undefined : (accessLevel as CosAccessLevel),\n pagination: { limit: pagination.limit, offset: pagination.offset },\n });\n\n const sourcesQuery = useCosSourcesQuery({ pagination: { limit: 100, offset: 0 } });\n const collectionsQuery = useCosCollectionsQuery();\n // Distinct format values are derived from a real sample of the catalog so the\n // Format dropdown reflects what actually exists (format is free-text, no enum).\n const formatsPool = useCosDatasetsQuery({\n pagination: { limit: FORMAT_POOL_LIMIT, offset: 0 },\n });\n\n const [publishDataset] = useCosPublishDatasetMutation();\n const [archiveDataset] = useCosArchiveDatasetMutation();\n\n const datasets = useMemo(() => data?.cosListDatasets?.items ?? [], [data]);\n const pageInfo = data?.cosListDatasets?.pageInfo;\n const sources = sourcesQuery.data?.cosListSources?.items ?? [];\n const collections = collectionsQuery.data?.cosListCollections ?? [];\n const formats = useMemo(() => {\n const seen = new Set<string>();\n for (const dataset of formatsPool.data?.cosListDatasets?.items ?? []) {\n if (dataset.format) {\n seen.add(dataset.format);\n }\n }\n return [...seen].sort((a, b) => a.localeCompare(b));\n }, [formatsPool.data]);\n\n const noFilters =\n status === ALL &&\n sourceId === ALL &&\n collectionId === ALL &&\n format === ALL &&\n accessLevel === ALL;\n\n const handlePublish = async (dataset: CosDataset) => {\n const response = await publishDataset({ variables: { id: dataset.id } });\n if (response.data?.cosPublishDataset) {\n notify.success(\n tr('cosmicintersection.pages.datasets.publishSuccess', 'Dataset published'),\n tr(\n 'cosmicintersection.pages.datasets.publishSuccessDescription',\n '“{{name}}” is now available.',\n { name: dataset.name }\n )\n );\n await refetch();\n }\n };\n\n const handleArchive = async (dataset: CosDataset) => {\n const response = await archiveDataset({ variables: { id: dataset.id } });\n if (response.error) {\n throw new Error(response.error.message);\n }\n if (!response.data?.cosArchiveDataset) {\n notify.error(\n tr('cosmicintersection.pages.datasets.archiveError', 'Could not archive dataset'),\n tr(\n 'cosmicintersection.pages.datasets.archiveErrorDescription',\n 'The server did not confirm the change.'\n )\n );\n throw new Error('Archive not confirmed');\n }\n notify.success(tr('cosmicintersection.pages.datasets.archiveSuccess', 'Dataset archived'));\n await refetch();\n };\n\n return (\n <PageLayout\n title={tr('cosmicintersection.pages.datasets.title', 'Datasets')}\n description={tr(\n 'cosmicintersection.pages.datasets.description',\n 'Federated science datasets — catalog, schema, lineage and access, all from live data.'\n )}\n actions={\n <>\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('discovery')}>\n <Compass className=\"size-4\" />\n {tr('nav.discover', 'Discover')}\n </Button>\n <Button type=\"button\" onClick={() => navigate('datasets/create')}>\n <Plus className=\"size-4\" />\n {tr('cosmicintersection.pages.datasets.create', 'New dataset')}\n </Button>\n </>\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-end gap-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">\n {tr('cosmicintersection.common.status', 'Status')}\n </span>\n <Select value={status} onValueChange={setStatus}>\n <SelectTrigger\n aria-label={tr('cosmicintersection.common.filterByStatus', 'Filter by status')}\n className=\"w-44\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>\n {tr('cosmicintersection.common.allStatuses', 'All statuses')}\n </SelectItem>\n {DATASET_STATUSES.map((value) => (\n <SelectItem key={value} value={value}>\n {formatEnumLabel(value)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">\n {tr('cosmicintersection.common.source', 'Source')}\n </span>\n <Select value={sourceId} onValueChange={setSourceId}>\n <SelectTrigger\n aria-label={tr('cosmicintersection.common.filterBySource', 'Filter by source')}\n className=\"w-48\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>\n {tr('cosmicintersection.common.allSources', 'All sources')}\n </SelectItem>\n {sources.map((source) => (\n <SelectItem key={source.id} value={source.id}>\n {source.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">\n {tr('cosmicintersection.common.collection', 'Collection')}\n </span>\n <Select value={collectionId} onValueChange={setCollectionId}>\n <SelectTrigger\n aria-label={tr(\n 'cosmicintersection.common.filterByCollection',\n 'Filter by collection'\n )}\n className=\"w-48\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>\n {tr('cosmicintersection.common.allCollections', 'All collections')}\n </SelectItem>\n {collections.map((collection) => (\n <SelectItem key={collection.id} value={collection.id}>\n {collection.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">\n {tr('cosmicintersection.common.format', 'Format')}\n </span>\n <Select value={format} onValueChange={setFormat} disabled={formats.length === 0}>\n <SelectTrigger\n aria-label={tr('cosmicintersection.common.filterByFormat', 'Filter by format')}\n className=\"w-40\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>\n {tr('cosmicintersection.common.allFormats', 'All formats')}\n </SelectItem>\n {formats.map((value) => (\n <SelectItem key={value} value={value}>\n {value}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">\n {tr('cosmicintersection.common.access', 'Access')}\n </span>\n <Select value={accessLevel} onValueChange={setAccessLevel}>\n <SelectTrigger\n aria-label={tr(\n 'cosmicintersection.common.filterByAccess',\n 'Filter by access level'\n )}\n className=\"w-44\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>\n {tr('cosmicintersection.common.allAccessLevels', 'All access levels')}\n </SelectItem>\n {ACCESS_LEVELS.map((value) => (\n <SelectItem key={value} value={value}>\n {formatEnumLabel(value)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {loading ? (\n <LoadingState\n variant=\"rows\"\n rows={6}\n label={tr('cosmicintersection.pages.datasets.loading', 'Loading datasets…')}\n />\n ) : error ? (\n <ErrorState\n message={tr(\n 'cosmicintersection.pages.datasets.error',\n 'We could not load datasets. Please try again.'\n )}\n onRetry={() => void refetch()}\n />\n ) : datasets.length === 0 ? (\n <EmptyState\n icon={Database}\n title={\n noFilters\n ? tr('cosmicintersection.pages.datasets.emptyTitle', 'No datasets yet.')\n : tr(\n 'cosmicintersection.pages.datasets.emptyFilterTitle',\n 'No datasets match these filters.'\n )\n }\n description={\n noFilters\n ? tr(\n 'cosmicintersection.pages.datasets.emptyDescription',\n 'Register your first dataset to start federating science data across sources.'\n )\n : tr(\n 'cosmicintersection.pages.datasets.emptyFilterDescription',\n 'Try a different status, source, collection, format or access level.'\n )\n }\n action={\n noFilters ? (\n <Button type=\"button\" onClick={() => navigate('datasets/create')}>\n <Plus className=\"size-4\" />\n {tr('cosmicintersection.pages.datasets.create', 'New dataset')}\n </Button>\n ) : undefined\n }\n />\n ) : (\n <>\n {/* Desktop / tablet: table */}\n <div className=\"hidden rounded-card border border-border-default bg-bg-elevated md:block\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>{tr('cosmicintersection.common.name', 'Name')}</TableHead>\n <TableHead>{tr('cosmicintersection.common.status', 'Status')}</TableHead>\n <TableHead>{tr('cosmicintersection.common.access', 'Access')}</TableHead>\n <TableHead>{tr('cosmicintersection.common.format', 'Format')}</TableHead>\n <TableHead className=\"text-right\">\n {tr('cosmicintersection.common.size', 'Size')}\n </TableHead>\n <TableHead>{tr('cosmicintersection.common.tags', 'Tags')}</TableHead>\n <TableHead>{tr('cosmicintersection.common.updated', 'Updated')}</TableHead>\n <TableHead className=\"text-right\">\n {tr('cosmicintersection.common.actions', 'Actions')}\n </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>\n <span className=\"font-medium text-text-primary\">{dataset.name}</span>\n {dataset.description ? (\n <p className=\"mt-0.5 line-clamp-1 max-w-xs text-xs text-text-secondary\">\n {dataset.description}\n </p>\n ) : null}\n </TableCell>\n <TableCell>\n <StatusBadge status={dataset.status} />\n </TableCell>\n <TableCell>\n <span className=\"text-sm text-text-secondary\">\n {formatEnumLabel(dataset.accessLevel)}\n </span>\n </TableCell>\n <TableCell>\n <span className=\"text-sm text-text-secondary\">{dataset.format || '—'}</span>\n </TableCell>\n <TableCell className=\"text-right text-sm tabular-nums text-text-secondary\">\n {formatBytes(dataset.sizeBytes)}\n </TableCell>\n <TableCell>\n <div className=\"flex max-w-[12rem] flex-wrap gap-1\">\n {dataset.tags.length === 0 ? (\n <span className=\"text-xs text-text-secondary\">—</span>\n ) : (\n <>\n {dataset.tags.slice(0, 2).map((tag) => (\n <Badge key={tag} variant=\"secondary\">\n {tag}\n </Badge>\n ))}\n {dataset.tags.length > 2 ? (\n <Badge variant=\"secondary\">+{dataset.tags.length - 2}</Badge>\n ) : null}\n </>\n )}\n </div>\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {formatDate(dataset.updatedAt)}\n </TableCell>\n <TableCell className=\"text-right\">\n <ActionMenu\n ariaLabel={tr(\n 'cosmicintersection.common.actionsFor',\n 'Actions for {{name}}',\n { name: dataset.name }\n )}\n entityName={dataset.name}\n onView={() => navigate(`datasets/${dataset.id}`)}\n onEdit={() => navigate(`datasets/${dataset.id}/edit`)}\n extraItems={\n dataset.status === 'AVAILABLE' || dataset.status === 'ARCHIVED'\n ? []\n : [\n {\n key: 'publish',\n label: tr('cosmicintersection.common.publish', 'Publish'),\n onSelect: () => void handlePublish(dataset),\n },\n ]\n }\n onDelete={() => handleArchive(dataset)}\n deleteLabel={tr(\n 'cosmicintersection.pages.datasets.archive',\n 'Archive dataset'\n )}\n />\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={tr('cosmicintersection.common.open', 'Open {{name}}', {\n name: dataset.name,\n })}\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-3 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 <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"font-medium text-text-primary\">{dataset.name}</p>\n {dataset.description ? (\n <p className=\"mt-0.5 line-clamp-2 text-xs text-text-secondary\">\n {dataset.description}\n </p>\n ) : null}\n </div>\n <div onClick={(event) => event.stopPropagation()}>\n <ActionMenu\n ariaLabel={tr(\n 'cosmicintersection.common.actionsFor',\n 'Actions for {{name}}',\n { name: dataset.name }\n )}\n entityName={dataset.name}\n onView={() => navigate(`datasets/${dataset.id}`)}\n onEdit={() => navigate(`datasets/${dataset.id}/edit`)}\n extraItems={\n dataset.status === 'AVAILABLE' || dataset.status === 'ARCHIVED'\n ? []\n : [\n {\n key: 'publish',\n label: tr('cosmicintersection.common.publish', 'Publish'),\n onSelect: () => void handlePublish(dataset),\n },\n ]\n }\n onDelete={() => handleArchive(dataset)}\n deleteLabel={tr(\n 'cosmicintersection.pages.datasets.archive',\n 'Archive dataset'\n )}\n />\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={dataset.status} />\n <StatusBadge status={dataset.accessLevel} />\n </div>\n\n <dl className=\"grid grid-cols-2 gap-x-4 gap-y-1 text-xs\">\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">\n {tr('cosmicintersection.common.format', 'Format')}\n </dt>\n <dd className=\"text-text-primary\">{dataset.format || '—'}</dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">\n {tr('cosmicintersection.common.size', 'Size')}\n </dt>\n <dd className=\"tabular-nums text-text-primary\">\n {formatBytes(dataset.sizeBytes)}\n </dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">\n {tr('cosmicintersection.common.updated', 'Updated')}\n </dt>\n <dd className=\"text-text-primary\">{formatDate(dataset.updatedAt)}</dd>\n </div>\n </dl>\n\n {dataset.tags.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {dataset.tags.slice(0, 4).map((tag) => (\n <Badge key={tag} variant=\"secondary\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n {dataset.tags.length > 4 ? (\n <span className=\"text-xs text-text-secondary\">\n +{dataset.tags.length - 4}\n </span>\n ) : null}\n </div>\n ) : null}\n </div>\n </li>\n ))}\n </ul>\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={datasets.length}\n hasMore={Boolean(pageInfo?.hasMore)}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel={tr('cosmicintersection.pages.datasets.itemLabel', 'datasets')}\n loading={loading}\n />\n </>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"DiscoveryPage.d.ts","sourceRoot":"","sources":["../../../../src/cosmicintersection/pages/discovery/DiscoveryPage.tsx"],"names":[],"mappings":"AAoGA,MAAM,CAAC,OAAO,UAAU,aAAa,4CAycpC"}
1
+ {"version":3,"file":"DiscoveryPage.d.ts","sourceRoot":"","sources":["../../../../src/cosmicintersection/pages/discovery/DiscoveryPage.tsx"],"names":[],"mappings":"AAoGA,MAAM,CAAC,OAAO,UAAU,aAAa,4CAgbpC"}
@@ -1 +1 @@
1
- {"version":3,"file":"DiscoveryPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/discovery/DiscoveryPage.tsx"],"sourcesContent":["/**\n * DiscoveryPage — faceted full-text discovery over the federated dataset catalog\n * (mockup 05).\n *\n * A prominent debounced search bar drives the backend `q` full-text argument on\n * `cosListDatasets`; the matched datasets form a \"pool\" (capped at a generous\n * limit) from which every facet count and the result grid are derived — so counts\n * and results always agree and nothing is fabricated. Facets are multi-select and\n * cross-filtered (each group's counts reflect the other selected groups), matching\n * the standard faceted-search behaviour:\n * - Instrument → distinct source types (via cosListSources), mapped from each\n * dataset's sourceId\n * - Access → CosAccessLevel enum\n * - Format → distinct formats present in the pool\n * - Mission/Survey → collections (via cosListCollections)\n *\n * Everything renders from live backend data with honest loading / empty / error\n * states. Tokens only — no raw palette colours.\n */\nimport { useEffect, useMemo, useState } from 'react';\nimport {\n ArrowUpDown,\n Database,\n FileText,\n Rows3,\n Search,\n SlidersHorizontal,\n Table as TableIcon,\n X,\n} from 'lucide-react';\nimport {\n Badge,\n Button,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosCollectionsQuery,\n useCosDatasetsQuery,\n useCosSourcesQuery,\n type CosAccessLevel,\n type CosDataset,\n} from '../../api/cosmicApi';\nimport {\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n StatusBadge,\n formatEnumLabel,\n} from '../../components';\nimport { FacetPanel, type FacetGroup } from '../../components/FacetPanel';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { cn } from '../../utils/cn';\nimport { useTr } from '../../../shared/hooks/useTr';\n\n/** Generous ceiling for the search pool — facet counts reflect this window. */\nconst POOL_LIMIT = 200;\nconst SEARCH_DEBOUNCE_MS = 300;\n\nconst ACCESS_LEVELS: CosAccessLevel[] = ['PUBLIC', 'WORKSPACE', 'RESTRICTED', 'EMBARGOED'];\n\ntype SortKey = 'relevance' | 'name' | 'updated' | 'size';\n\nconst SORT_LABELS: Record<SortKey, string> = {\n relevance: 'Best match',\n name: 'Name A–Z',\n updated: 'Recently updated',\n size: 'Largest first',\n};\n\n/** BigInt byte string → human-readable size, or an em dash when absent/zero. */\nfunction formatBytes(raw: string | null | undefined): string {\n if (!raw) {\n return '—';\n }\n const bytes = Number(raw);\n if (!Number.isFinite(bytes) || bytes <= 0) {\n return '—';\n }\n const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];\n let value = bytes;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit += 1;\n }\n return `${value >= 10 || unit === 0 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;\n}\n\nfunction formatRows(raw: string | null | undefined): string {\n const rows = Number(raw ?? 0);\n return Number.isFinite(rows) && rows > 0 ? `${rows.toLocaleString()} rows` : 'no rows';\n}\n\nexport default function DiscoveryPage() {\n const { navigate } = useCosmicIntersection();\n const tr = useTr();\n\n const [searchInput, setSearchInput] = useState('');\n const [q, setQ] = useState('');\n const [selected, setSelected] = useState<Record<string, string[]>>({});\n const [sort, setSort] = useState<SortKey>('relevance');\n const [facetsOpen, setFacetsOpen] = useState(false);\n\n // Debounce the raw input into the backend `q` argument.\n useEffect(() => {\n const handle = window.setTimeout(() => setQ(searchInput.trim()), SEARCH_DEBOUNCE_MS);\n return () => window.clearTimeout(handle);\n }, [searchInput]);\n\n const poolQuery = useCosDatasetsQuery({\n q: q || undefined,\n pagination: { limit: POOL_LIMIT, offset: 0 },\n });\n const sourcesQuery = useCosSourcesQuery({ pagination: { limit: POOL_LIMIT, offset: 0 } });\n const collectionsQuery = useCosCollectionsQuery();\n\n const pool = useMemo(() => poolQuery.data?.cosListDatasets?.items ?? [], [poolQuery.data]);\n const poolHasMore = Boolean(poolQuery.data?.cosListDatasets?.pageInfo?.hasMore);\n const sources = useMemo(\n () => sourcesQuery.data?.cosListSources?.items ?? [],\n [sourcesQuery.data]\n );\n const collections = useMemo(\n () => collectionsQuery.data?.cosListCollections ?? [],\n [collectionsQuery.data]\n );\n\n // sourceId → source type, so a dataset can be bucketed by instrument type.\n const sourceTypeById = useMemo(() => {\n const map = new Map<string, string>();\n for (const source of sources) {\n map.set(source.id, source.type);\n }\n return map;\n }, [sources]);\n\n const collectionNameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const collection of collections) {\n map.set(collection.id, collection.name);\n }\n return map;\n }, [collections]);\n\n // Per-group predicates — a dataset \"matches\" a group if the group has no\n // selection or the dataset's value is among the selected values.\n const matchers = useMemo(() => {\n const instrument = selected.instrument ?? [];\n const access = selected.access ?? [];\n const format = selected.format ?? [];\n const mission = selected.mission ?? [];\n return {\n instrument: (d: CosDataset) => {\n if (instrument.length === 0) return true;\n const type = d.sourceId ? sourceTypeById.get(d.sourceId) : undefined;\n return type ? instrument.includes(type) : false;\n },\n access: (d: CosDataset) => access.length === 0 || access.includes(d.accessLevel),\n format: (d: CosDataset) =>\n format.length === 0 || (d.format ? format.includes(d.format) : false),\n mission: (d: CosDataset) =>\n mission.length === 0 || (d.collectionId ? mission.includes(d.collectionId) : false),\n } as const;\n }, [selected, sourceTypeById]);\n\n // Cross-filtered count: items passing every OTHER group, then the predicate.\n const countFor = useMemo(() => {\n return (groupKey: keyof typeof matchers, predicate: (d: CosDataset) => boolean): number => {\n let total = 0;\n for (const dataset of pool) {\n let ok = true;\n for (const key of Object.keys(matchers) as (keyof typeof matchers)[]) {\n if (key === groupKey) continue;\n if (!matchers[key](dataset)) {\n ok = false;\n break;\n }\n }\n if (ok && predicate(dataset)) {\n total += 1;\n }\n }\n return total;\n };\n }, [pool, matchers]);\n\n const facetGroups = useMemo<FacetGroup[]>(() => {\n // Universe of option values from the unfiltered pool (stable while toggling).\n const instrumentTypes = new Set<string>();\n const formats = new Set<string>();\n const missionIds = new Set<string>();\n for (const dataset of pool) {\n const type = dataset.sourceId ? sourceTypeById.get(dataset.sourceId) : undefined;\n if (type) instrumentTypes.add(type);\n if (dataset.format) formats.add(dataset.format);\n if (dataset.collectionId) missionIds.add(dataset.collectionId);\n }\n\n const instrument: FacetGroup = {\n key: 'instrument',\n label: 'Instrument',\n options: [...instrumentTypes]\n .map((type) => ({\n value: type,\n label: formatEnumLabel(type),\n count: countFor('instrument', (d) =>\n d.sourceId ? sourceTypeById.get(d.sourceId) === type : false\n ),\n }))\n .sort((a, b) => b.count - a.count || a.label.localeCompare(b.label)),\n };\n\n const access: FacetGroup = {\n key: 'access',\n label: 'Access',\n options: ACCESS_LEVELS.map((level) => ({\n value: level,\n label: formatEnumLabel(level),\n count: countFor('access', (d) => d.accessLevel === level),\n })),\n };\n\n const format: FacetGroup = {\n key: 'format',\n label: 'Format',\n options: [...formats]\n .sort((a, b) => a.localeCompare(b))\n .map((fmt) => ({\n value: fmt,\n label: fmt,\n count: countFor('format', (d) => d.format === fmt),\n })),\n };\n\n const mission: FacetGroup = {\n key: 'mission',\n label: 'Mission / Survey',\n options: [...missionIds]\n .map((id) => ({\n value: id,\n label: collectionNameById.get(id) ?? 'Unknown collection',\n count: countFor('mission', (d) => d.collectionId === id),\n }))\n .sort((a, b) => b.count - a.count || a.label.localeCompare(b.label)),\n };\n\n return [instrument, access, format, mission];\n }, [pool, sourceTypeById, collectionNameById, countFor]);\n\n // Displayed results: the pool filtered by all selected facets, then sorted.\n const results = useMemo(() => {\n const filtered = pool.filter(\n (dataset) =>\n matchers.instrument(dataset) &&\n matchers.access(dataset) &&\n matchers.format(dataset) &&\n matchers.mission(dataset)\n );\n const sorted = [...filtered];\n switch (sort) {\n case 'name':\n sorted.sort((a, b) => a.name.localeCompare(b.name));\n break;\n case 'updated':\n sorted.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());\n break;\n case 'size':\n sorted.sort((a, b) => Number(b.sizeBytes) - Number(a.sizeBytes));\n break;\n case 'relevance':\n default:\n break; // preserve backend order (relevance when `q` is present)\n }\n return sorted;\n }, [pool, matchers, sort]);\n\n const totalSelected = Object.values(selected).reduce((sum, values) => sum + values.length, 0);\n\n const handleToggle = (groupKey: string, value: string) => {\n setSelected((prev) => {\n const current = prev[groupKey] ?? [];\n const next = current.includes(value)\n ? current.filter((item) => item !== value)\n : [...current, value];\n return { ...prev, [groupKey]: next };\n });\n };\n\n const handleClear = () => setSelected({});\n\n const loading = poolQuery.loading && pool.length === 0;\n const error = poolQuery.error;\n\n const facetPanel = (\n <FacetPanel\n groups={facetGroups}\n selected={selected}\n onToggle={handleToggle}\n onClear={handleClear}\n />\n );\n\n return (\n <PageLayout\n title={tr('cosmicintersection.pages.discovery.title', 'Discovery')}\n description={tr(\n 'cosmicintersection.pages.discovery.description',\n 'Search and facet the federated science catalog — full-text across datasets, refined by instrument, access, format and mission.',\n )}\n actions={\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('datasets')}>\n <TableIcon className=\"size-4\" />\n {tr('cosmicintersection.pages.discovery.tableView', 'Table view')}\n </Button>\n }\n >\n <div className=\"space-y-5\">\n {/* Search bar */}\n <div className=\"relative\">\n <Search\n className=\"pointer-events-none absolute left-4 top-1/2 size-5 -translate-y-1/2 text-text-secondary\"\n aria-hidden=\"true\"\n />\n <Input\n type=\"search\"\n value={searchInput}\n onChange={(event) => setSearchInput(event.target.value)}\n placeholder={tr(\n 'cosmicintersection.pages.discovery.searchPlaceholder',\n 'Search datasets by name, description or tag…',\n )}\n aria-label={tr(\n 'cosmicintersection.pages.discovery.searchAriaLabel',\n 'Search datasets',\n )}\n className=\"h-12 rounded-card pl-12 pr-11 text-base\"\n />\n {searchInput ? (\n <button\n type=\"button\"\n onClick={() => setSearchInput('')}\n aria-label={tr(\n 'cosmicintersection.pages.discovery.clearSearch',\n 'Clear search',\n )}\n className=\"absolute right-3 top-1/2 flex size-7 -translate-y-1/2 items-center justify-center rounded-full text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-focus\"\n >\n <X className=\"size-4\" />\n </button>\n ) : null}\n </div>\n\n {/* Mobile facet toggle */}\n <div className=\"flex items-center justify-between gap-3 lg:hidden\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => setFacetsOpen((open) => !open)}\n aria-expanded={facetsOpen}\n >\n <SlidersHorizontal className=\"size-4\" />\n {totalSelected > 0\n ? tr(\n 'cosmicintersection.pages.discovery.filtersWithCount',\n 'Filters ({{count}})',\n { count: totalSelected },\n )\n : tr('cosmicintersection.pages.discovery.filters', 'Filters')}\n </Button>\n </div>\n\n <div className=\"flex flex-col gap-6 lg:flex-row\">\n {/* Facets — sticky sidebar on desktop, collapsible on mobile */}\n <aside\n className={cn(\n 'shrink-0 lg:block lg:w-60 lg:border-r lg:border-border-seam lg:pr-6',\n facetsOpen ? 'block' : 'hidden'\n )}\n >\n <div className=\"lg:sticky lg:top-0\">{facetPanel}</div>\n </aside>\n\n {/* Results */}\n <div className=\"min-w-0 flex-1 space-y-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-lg font-semibold tabular-nums text-text-primary\">\n {results.length}\n </span>\n <span className=\"text-sm text-text-secondary\">\n {results.length === 1\n ? tr(\n 'cosmicintersection.pages.discovery.resultCountSingular',\n 'dataset',\n )\n : tr(\n 'cosmicintersection.pages.discovery.resultCountPlural',\n 'datasets',\n )}\n {q\n ? ` ${tr(\n 'cosmicintersection.pages.discovery.matching',\n 'matching “{{q}}”',\n { q },\n )}`\n : ''}\n </span>\n </div>\n <label className=\"flex items-center gap-2 text-sm\">\n <ArrowUpDown className=\"size-4 text-text-secondary\" aria-hidden=\"true\" />\n <Select value={sort} onValueChange={(value) => setSort(value as SortKey)}>\n <SelectTrigger\n aria-label={tr(\n 'cosmicintersection.pages.discovery.sortResults',\n 'Sort results',\n )}\n className=\"w-48\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {(Object.keys(SORT_LABELS) as SortKey[]).map((key) => (\n <SelectItem key={key} value={key}>\n {SORT_LABELS[key]}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {poolHasMore ? (\n <p className=\"rounded-card border border-border-default bg-bg-sunken px-3 py-2 text-xs text-text-secondary\">\n {tr(\n 'cosmicintersection.pages.discovery.poolLimitNotice',\n 'Showing facets and results for the first {{limit}} matches. Refine your search to narrow the set.',\n { limit: POOL_LIMIT },\n )}\n </p>\n ) : null}\n\n {loading ? (\n <LoadingState\n variant=\"rows\"\n rows={6}\n label={tr(\n 'cosmicintersection.pages.discovery.loading',\n 'Searching the catalog…',\n )}\n />\n ) : error ? (\n <ErrorState\n message={tr(\n 'cosmicintersection.pages.discovery.error',\n 'We could not run that search. Please try again.',\n )}\n onRetry={() => void poolQuery.refetch()}\n />\n ) : results.length === 0 ? (\n <EmptyState\n icon={Search}\n title={\n q || totalSelected > 0\n ? tr(\n 'cosmicintersection.pages.discovery.noDatasetsMatch',\n 'No datasets match',\n )\n : tr(\n 'cosmicintersection.pages.discovery.startExploring',\n 'Start exploring',\n )\n }\n description={\n q || totalSelected > 0\n ? tr(\n 'cosmicintersection.pages.discovery.noDatasetsMatchDescription',\n 'Try a broader search term or clear some facets.',\n )\n : tr(\n 'cosmicintersection.pages.discovery.startExploringDescription',\n 'Search by name, description or tag, or pick a facet to browse the catalog.',\n )\n }\n action={\n totalSelected > 0 ? (\n <Button type=\"button\" variant=\"outline\" onClick={handleClear}>\n {tr('cosmicintersection.pages.discovery.clearFilters', 'Clear filters')}\n </Button>\n ) : undefined\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3\">\n {results.map((dataset) => (\n <button\n key={dataset.id}\n type=\"button\"\n onClick={() => navigate(`datasets/${dataset.id}`)}\n className=\"group flex flex-col overflow-hidden rounded-card border border-border-default bg-bg-elevated text-left outline-none transition-colors hover:border-border-strong hover:bg-bg-sunken focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <div className=\"h-1 w-full bg-gradient-to-r from-accent-blue via-accent-purple to-accent-teal\" />\n <div className=\"flex flex-1 flex-col gap-3 p-4\">\n <h3 className=\"line-clamp-2 text-sm font-semibold text-text-primary\">\n {dataset.name}\n </h3>\n\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-text-secondary\">\n <span className=\"inline-flex items-center gap-1\">\n <FileText className=\"size-3.5\" aria-hidden=\"true\" />\n {dataset.format || 'No format'}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Database className=\"size-3.5\" aria-hidden=\"true\" />\n {formatBytes(dataset.sizeBytes)}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Rows3 className=\"size-3.5\" aria-hidden=\"true\" />\n {formatRows(dataset.rowCount)}\n </span>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={dataset.status} />\n <StatusBadge status={dataset.accessLevel} />\n </div>\n\n {dataset.tags.length > 0 ? (\n <div className=\"mt-auto flex flex-wrap gap-1 pt-1\">\n {dataset.tags.slice(0, 3).map((tag) => (\n <Badge key={tag} variant=\"secondary\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n {dataset.tags.length > 3 ? (\n <span className=\"text-xs text-text-secondary\">\n +{dataset.tags.length - 3}\n </span>\n ) : null}\n </div>\n ) : null}\n </div>\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA8DA,IAAM,IAAa,KACb,KAAqB,KAErB,KAAkC;CAAC;CAAU;CAAa;CAAc;AAAW,GAInF,IAAuC;CAC3C,WAAW;CACX,MAAM;CACN,SAAS;CACT,MAAM;AACR;AAGA,SAAS,EAAY,GAAwC;CAC3D,IAAI,CAAC,GACH,OAAO;CAET,IAAM,IAAQ,OAAO,CAAG;CACxB,IAAI,CAAC,OAAO,SAAS,CAAK,KAAK,KAAS,GACtC,OAAO;CAET,IAAM,IAAQ;EAAC;EAAK;EAAM;EAAM;EAAM;EAAM;CAAI,GAC5C,IAAQ,GACR,IAAO;CACX,OAAO,KAAS,QAAQ,IAAO,EAAM,SAAS,IAE5C,AADA,KAAS,MACT,KAAQ;CAEV,OAAO,GAAG,KAAS,MAAM,MAAS,IAAI,KAAK,MAAM,CAAK,IAAI,EAAM,QAAQ,CAAC,EAAE,GAAG,EAAM;AACtF;AAEA,SAAS,EAAW,GAAwC;CAC1D,IAAM,IAAO,OAAO,KAAO,CAAC;CAC5B,OAAO,OAAO,SAAS,CAAI,KAAK,IAAO,IAAI,GAAG,EAAK,eAAe,EAAE,SAAS;AAC/E;AAEA,SAAwB,IAAgB;CACtC,IAAM,EAAE,gBAAa,EAAsB,GACrC,IAAK,EAAM,GAEX,CAAC,GAAa,KAAkB,EAAS,EAAE,GAC3C,CAAC,GAAG,KAAQ,EAAS,EAAE,GACvB,CAAC,GAAU,KAAe,EAAmC,CAAC,CAAC,GAC/D,CAAC,GAAM,KAAW,EAAkB,WAAW,GAC/C,CAAC,GAAY,KAAiB,EAAS,EAAK;CAGlD,SAAgB;EACd,IAAM,IAAS,OAAO,iBAAiB,EAAK,EAAY,KAAK,CAAC,GAAG,EAAkB;EACnF,aAAa,OAAO,aAAa,CAAM;CACzC,GAAG,CAAC,CAAW,CAAC;CAEhB,IAAM,IAAY,EAAoB;EACpC,GAAG,KAAK,KAAA;EACR,YAAY;GAAE,OAAO;GAAY,QAAQ;EAAE;CAC7C,CAAC,GACK,IAAe,EAAmB,EAAE,YAAY;EAAE,OAAO;EAAY,QAAQ;CAAE,EAAE,CAAC,GAClF,IAAmB,EAAuB,GAE1C,IAAO,QAAc,EAAU,MAAM,iBAAiB,SAAS,CAAC,GAAG,CAAC,EAAU,IAAI,CAAC,GACnF,KAAc,EAAQ,EAAU,MAAM,iBAAiB,UAAU,SACjE,IAAU,QACR,EAAa,MAAM,gBAAgB,SAAS,CAAC,GACnD,CAAC,EAAa,IAAI,CACpB,GACM,IAAc,QACZ,EAAiB,MAAM,sBAAsB,CAAC,GACpD,CAAC,EAAiB,IAAI,CACxB,GAGM,IAAiB,QAAc;EACnC,IAAM,oBAAM,IAAI,IAAoB;EACpC,KAAK,IAAM,KAAU,GACnB,EAAI,IAAI,EAAO,IAAI,EAAO,IAAI;EAEhC,OAAO;CACT,GAAG,CAAC,CAAO,CAAC,GAEN,IAAqB,QAAc;EACvC,IAAM,oBAAM,IAAI,IAAoB;EACpC,KAAK,IAAM,KAAc,GACvB,EAAI,IAAI,EAAW,IAAI,EAAW,IAAI;EAExC,OAAO;CACT,GAAG,CAAC,CAAW,CAAC,GAIV,IAAW,QAAc;EAC7B,IAAM,IAAa,EAAS,cAAc,CAAC,GACrC,IAAS,EAAS,UAAU,CAAC,GAC7B,IAAS,EAAS,UAAU,CAAC,GAC7B,IAAU,EAAS,WAAW,CAAC;EACrC,OAAO;GACL,aAAa,MAAkB;IAC7B,IAAI,EAAW,WAAW,GAAG,OAAO;IACpC,IAAM,IAAO,EAAE,WAAW,EAAe,IAAI,EAAE,QAAQ,IAAI,KAAA;IAC3D,OAAO,IAAO,EAAW,SAAS,CAAI,IAAI;GAC5C;GACA,SAAS,MAAkB,EAAO,WAAW,KAAK,EAAO,SAAS,EAAE,WAAW;GAC/E,SAAS,MACP,EAAO,WAAW,MAAM,EAAE,SAAS,EAAO,SAAS,EAAE,MAAM,IAAI;GACjE,UAAU,MACR,EAAQ,WAAW,MAAM,EAAE,eAAe,EAAQ,SAAS,EAAE,YAAY,IAAI;EACjF;CACF,GAAG,CAAC,GAAU,CAAc,CAAC,GAGvB,IAAW,SACP,GAAiC,MAAkD;EACzF,IAAI,IAAQ;EACZ,KAAK,IAAM,KAAW,GAAM;GAC1B,IAAI,IAAK;GACT,KAAK,IAAM,KAAO,OAAO,KAAK,CAAQ,GAChC,UAAQ,KACR,CAAC,EAAS,GAAK,CAAO,GAAG;IAC3B,IAAK;IACL;GACF;GAEF,AAAI,KAAM,EAAU,CAAO,MACzB,KAAS;EAEb;EACA,OAAO;CACT,GACC,CAAC,GAAM,CAAQ,CAAC,GAEb,IAAc,QAA4B;EAE9C,IAAM,oBAAkB,IAAI,IAAY,GAClC,oBAAU,IAAI,IAAY,GAC1B,oBAAa,IAAI,IAAY;EACnC,KAAK,IAAM,KAAW,GAAM;GAC1B,IAAM,IAAO,EAAQ,WAAW,EAAe,IAAI,EAAQ,QAAQ,IAAI,KAAA;GAGvE,AAFI,KAAM,EAAgB,IAAI,CAAI,GAC9B,EAAQ,UAAQ,EAAQ,IAAI,EAAQ,MAAM,GAC1C,EAAQ,gBAAc,EAAW,IAAI,EAAQ,YAAY;EAC/D;EAkDA,OAAO;GAAC;IA/CN,KAAK;IACL,OAAO;IACP,SAAS,CAAC,GAAG,CAAe,EACzB,KAAK,OAAU;KACd,OAAO;KACP,OAAO,EAAgB,CAAI;KAC3B,OAAO,EAAS,eAAe,MAC7B,EAAE,WAAW,EAAe,IAAI,EAAE,QAAQ,MAAM,IAAO,EACzD;IACF,EAAE,EACD,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,EAAE,KAAK,CAAC;GAqC/D;GAAY;IAjClB,KAAK;IACL,OAAO;IACP,SAAS,GAAc,KAAK,OAAW;KACrC,OAAO;KACP,OAAO,EAAgB,CAAK;KAC5B,OAAO,EAAS,WAAW,MAAM,EAAE,gBAAgB,CAAK;IAC1D,EAAE;GA2BgB;GAAQ;IAvB1B,KAAK;IACL,OAAO;IACP,SAAS,CAAC,GAAG,CAAO,EACjB,MAAM,GAAG,MAAM,EAAE,cAAc,CAAC,CAAC,EACjC,KAAK,OAAS;KACb,OAAO;KACP,OAAO;KACP,OAAO,EAAS,WAAW,MAAM,EAAE,WAAW,CAAG;IACnD,EAAE;GAesB;GAAQ;IAXlC,KAAK;IACL,OAAO;IACP,SAAS,CAAC,GAAG,CAAU,EACpB,KAAK,OAAQ;KACZ,OAAO;KACP,OAAO,EAAmB,IAAI,CAAE,KAAK;KACrC,OAAO,EAAS,YAAY,MAAM,EAAE,iBAAiB,CAAE;IACzD,EAAE,EACD,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,EAAE,KAAK,CAAC;GAGnC;EAAO;CAC7C,GAAG;EAAC;EAAM;EAAgB;EAAoB;CAAQ,CAAC,GAGjD,IAAU,QAAc;EAQ5B,IAAM,IAAS,CAAC,GAPC,EAAK,QACnB,MACC,EAAS,WAAW,CAAO,KAC3B,EAAS,OAAO,CAAO,KACvB,EAAS,OAAO,CAAO,KACvB,EAAS,QAAQ,CAAO,CAET,CAAQ;EAC3B,QAAQ,GAAR;GACE,KAAK;IACH,EAAO,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;IAClD;GACF,KAAK;IACH,EAAO,MAAM,GAAG,MAAM,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC;IACvF;GACF,KAAK;IACH,EAAO,MAAM,GAAG,MAAM,OAAO,EAAE,SAAS,IAAI,OAAO,EAAE,SAAS,CAAC;IAC/D;GAEF,SACE;EACJ;EACA,OAAO;CACT,GAAG;EAAC;EAAM;EAAU;CAAI,CAAC,GAEnB,IAAgB,OAAO,OAAO,CAAQ,EAAE,QAAQ,GAAK,MAAW,IAAM,EAAO,QAAQ,CAAC,GAEtF,MAAgB,GAAkB,MAAkB;EACxD,GAAa,MAAS;GACpB,IAAM,IAAU,EAAK,MAAa,CAAC,GAC7B,IAAO,EAAQ,SAAS,CAAK,IAC/B,EAAQ,QAAQ,MAAS,MAAS,CAAK,IACvC,CAAC,GAAG,GAAS,CAAK;GACtB,OAAO;IAAE,GAAG;KAAO,IAAW;GAAK;EACrC,CAAC;CACH,GAEM,UAAoB,EAAY,CAAC,CAAC,GAElC,KAAU,EAAU,WAAW,EAAK,WAAW,GAC/C,KAAQ,EAAU,OAElB,KACJ,kBAAC,IAAD;EACE,QAAQ;EACE;EACV,UAAU;EACV,SAAS;CACV,CAAA;CAGH,OACE,kBAAC,GAAD;EACE,OAAO,EAAG,4CAA4C,WAAW;EACjE,aAAa,EACX,kDACA,gIACF;EACA,SACE,kBAAC,GAAD;GAAQ,MAAK;GAAS,SAAQ;GAAU,eAAe,EAAS,UAAU;aAA1E,CACE,kBAAC,GAAD,EAAW,WAAU,SAAU,CAAA,GAC9B,EAAG,gDAAgD,YAAY,CAC1D;;YAGV,kBAAC,OAAD;GAAK,WAAU;aAAf;IAEE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OACE,WAAU;OACV,eAAY;MACb,CAAA;MACD,kBAAC,IAAD;OACE,MAAK;OACL,OAAO;OACP,WAAW,MAAU,EAAe,EAAM,OAAO,KAAK;OACtD,aAAa,EACX,wDACA,8CACF;OACA,cAAY,EACV,sDACA,iBACF;OACA,WAAU;MACX,CAAA;MACA,IACC,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAe,EAAE;OAChC,cAAY,EACV,kDACA,cACF;OACA,WAAU;iBAEV,kBAAC,GAAD,EAAG,WAAU,SAAU,CAAA;MACjB,CAAA,IACN;KACD;;IAGL,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MACE,MAAK;MACL,SAAQ;MACR,eAAe,GAAe,MAAS,CAAC,CAAI;MAC5C,iBAAe;gBAJjB,CAME,kBAAC,GAAD,EAAmB,WAAU,SAAU,CAAA,GACtC,IAAgB,IACb,EACE,uDACA,uBACA,EAAE,OAAO,EAAc,CACzB,IACA,EAAG,8CAA8C,SAAS,CACxD;;IACL,CAAA;IAEL,kBAAC,OAAD;KAAK,WAAU;eAAf,CAEE,kBAAC,SAAD;MACE,WAAW,EACT,uEACA,IAAa,UAAU,QACzB;gBAEA,kBAAC,OAAD;OAAK,WAAU;iBAAsB;MAAgB,CAAA;KAChD,CAAA,GAGP,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAQ;SACL,CAAA,GACN,kBAAC,QAAD;UAAM,WAAU;oBAAhB,CACG,EAAQ,WAAW,IAChB,EACE,0DACA,SACF,IACA,EACE,wDACA,UACF,GACH,IACG,IAAI,EACF,+CACA,oBACA,EAAE,KAAE,CACN,MACA,EACA;WACH;YACL,kBAAC,SAAD;SAAO,WAAU;mBAAjB,CACE,kBAAC,GAAD;UAAa,WAAU;UAA6B,eAAY;SAAQ,CAAA,GACxE,kBAAC,IAAD;UAAQ,OAAO;UAAM,gBAAgB,MAAU,EAAQ,CAAgB;oBAAvE,CACE,kBAAC,IAAD;WACE,cAAY,EACV,kDACA,cACF;WACA,WAAU;qBAEV,kBAAC,IAAD,CAAc,CAAA;UACD,CAAA,GACf,kBAAC,IAAD,EAAA,UACI,OAAO,KAAK,CAAW,EAAgB,KAAK,MAC5C,kBAAC,IAAD;WAAsB,OAAO;qBAC1B,EAAY;UACH,GAFK,CAEL,CACb,EACY,CAAA,CACT;WACH;UACJ;;OAEJ,KACC,kBAAC,KAAD;QAAG,WAAU;kBACV,EACC,sDACA,qGACA,EAAE,OAAO,EAAW,CACtB;OACC,CAAA,IACD;OAEH,KACC,kBAAC,IAAD;QACE,SAAQ;QACR,MAAM;QACN,OAAO,EACL,8CACA,wBACF;OACD,CAAA,IACC,KACF,kBAAC,GAAD;QACE,SAAS,EACP,4CACA,iDACF;QACA,eAAe,KAAK,EAAU,QAAQ;OACvC,CAAA,IACC,EAAQ,WAAW,IACrB,kBAAC,GAAD;QACE,MAAM;QACN,OACE,KAAK,IAAgB,IACjB,EACE,sDACA,mBACF,IACA,EACE,qDACA,iBACF;QAEN,aACE,KAAK,IAAgB,IACjB,EACE,iEACA,iDACF,IACA,EACE,gEACA,4EACF;QAEN,QACE,IAAgB,IACd,kBAAC,GAAD;SAAQ,MAAK;SAAS,SAAQ;SAAU,SAAS;mBAC9C,EAAG,mDAAmD,eAAe;QAChE,CAAA,IACN,KAAA;OAEP,CAAA,IAED,kBAAC,OAAD;QAAK,WAAU;kBACZ,EAAQ,KAAK,MACZ,kBAAC,UAAD;SAEE,MAAK;SACL,eAAe,EAAS,YAAY,EAAQ,IAAI;SAChD,WAAU;mBAJZ,CAME,kBAAC,OAAD,EAAK,WAAU,gFAAiF,CAAA,GAChG,kBAAC,OAAD;UAAK,WAAU;oBAAf;WACE,kBAAC,MAAD;YAAI,WAAU;sBACX,EAAQ;WACP,CAAA;WAEJ,kBAAC,OAAD;YAAK,WAAU;sBAAf;aACE,kBAAC,QAAD;cAAM,WAAU;wBAAhB,CACE,kBAAC,GAAD;eAAU,WAAU;eAAW,eAAY;cAAQ,CAAA,GAClD,EAAQ,UAAU,WACf;;aACN,kBAAC,QAAD;cAAM,WAAU;wBAAhB,CACE,kBAAC,GAAD;eAAU,WAAU;eAAW,eAAY;cAAQ,CAAA,GAClD,EAAY,EAAQ,SAAS,CAC1B;;aACN,kBAAC,QAAD;cAAM,WAAU;wBAAhB,CACE,kBAAC,GAAD;eAAO,WAAU;eAAW,eAAY;cAAQ,CAAA,GAC/C,EAAW,EAAQ,QAAQ,CACxB;;YACH;;WAEL,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACE,kBAAC,GAAD,EAAa,QAAQ,EAAQ,OAAS,CAAA,GACtC,kBAAC,GAAD,EAAa,QAAQ,EAAQ,YAAc,CAAA,CACxC;;WAEJ,EAAQ,KAAK,SAAS,IACrB,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACG,EAAQ,KAAK,MAAM,GAAG,CAAC,EAAE,KAAK,MAC7B,kBAAC,IAAD;aAAiB,SAAQ;aAAY,WAAU;uBAC5C;YACI,GAFK,CAEL,CACR,GACA,EAAQ,KAAK,SAAS,IACrB,kBAAC,QAAD;aAAM,WAAU;uBAAhB,CAA8C,KAC1C,EAAQ,KAAK,SAAS,CACpB;iBACJ,IACD;gBACH;UACD;WACC;WA9CD,EAAQ,EA8CP,CACT;OACE,CAAA;MAEJ;OACF;;GACF;;CACK,CAAA;AAEhB"}
1
+ {"version":3,"file":"DiscoveryPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/discovery/DiscoveryPage.tsx"],"sourcesContent":["/**\n * DiscoveryPage — faceted full-text discovery over the federated dataset catalog\n * (mockup 05).\n *\n * A prominent debounced search bar drives the backend `q` full-text argument on\n * `cosListDatasets`; the matched datasets form a \"pool\" (capped at a generous\n * limit) from which every facet count and the result grid are derived — so counts\n * and results always agree and nothing is fabricated. Facets are multi-select and\n * cross-filtered (each group's counts reflect the other selected groups), matching\n * the standard faceted-search behaviour:\n * - Instrument → distinct source types (via cosListSources), mapped from each\n * dataset's sourceId\n * - Access → CosAccessLevel enum\n * - Format → distinct formats present in the pool\n * - Mission/Survey → collections (via cosListCollections)\n *\n * Everything renders from live backend data with honest loading / empty / error\n * states. Tokens only — no raw palette colours.\n */\nimport { useEffect, useMemo, useState } from 'react';\nimport {\n ArrowUpDown,\n Database,\n FileText,\n Rows3,\n Search,\n SlidersHorizontal,\n Table as TableIcon,\n X,\n} from 'lucide-react';\nimport {\n Badge,\n Button,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosCollectionsQuery,\n useCosDatasetsQuery,\n useCosSourcesQuery,\n type CosAccessLevel,\n type CosDataset,\n} from '../../api/cosmicApi';\nimport {\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n StatusBadge,\n formatEnumLabel,\n} from '../../components';\nimport { FacetPanel, type FacetGroup } from '../../components/FacetPanel';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { cn } from '../../utils/cn';\nimport { useTr } from '../../../shared/hooks/useTr';\n\n/** Generous ceiling for the search pool — facet counts reflect this window. */\nconst POOL_LIMIT = 200;\nconst SEARCH_DEBOUNCE_MS = 300;\n\nconst ACCESS_LEVELS: CosAccessLevel[] = ['PUBLIC', 'WORKSPACE', 'RESTRICTED', 'EMBARGOED'];\n\ntype SortKey = 'relevance' | 'name' | 'updated' | 'size';\n\nconst SORT_LABELS: Record<SortKey, string> = {\n relevance: 'Best match',\n name: 'Name A–Z',\n updated: 'Recently updated',\n size: 'Largest first',\n};\n\n/** BigInt byte string → human-readable size, or an em dash when absent/zero. */\nfunction formatBytes(raw: string | null | undefined): string {\n if (!raw) {\n return '—';\n }\n const bytes = Number(raw);\n if (!Number.isFinite(bytes) || bytes <= 0) {\n return '—';\n }\n const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];\n let value = bytes;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit += 1;\n }\n return `${value >= 10 || unit === 0 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;\n}\n\nfunction formatRows(raw: string | null | undefined): string {\n const rows = Number(raw ?? 0);\n return Number.isFinite(rows) && rows > 0 ? `${rows.toLocaleString()} rows` : 'no rows';\n}\n\nexport default function DiscoveryPage() {\n const { navigate } = useCosmicIntersection();\n const tr = useTr();\n\n const [searchInput, setSearchInput] = useState('');\n const [q, setQ] = useState('');\n const [selected, setSelected] = useState<Record<string, string[]>>({});\n const [sort, setSort] = useState<SortKey>('relevance');\n const [facetsOpen, setFacetsOpen] = useState(false);\n\n // Debounce the raw input into the backend `q` argument.\n useEffect(() => {\n const handle = window.setTimeout(() => setQ(searchInput.trim()), SEARCH_DEBOUNCE_MS);\n return () => window.clearTimeout(handle);\n }, [searchInput]);\n\n const poolQuery = useCosDatasetsQuery({\n q: q || undefined,\n pagination: { limit: POOL_LIMIT, offset: 0 },\n });\n const sourcesQuery = useCosSourcesQuery({ pagination: { limit: POOL_LIMIT, offset: 0 } });\n const collectionsQuery = useCosCollectionsQuery();\n\n const pool = useMemo(() => poolQuery.data?.cosListDatasets?.items ?? [], [poolQuery.data]);\n const poolHasMore = Boolean(poolQuery.data?.cosListDatasets?.pageInfo?.hasMore);\n const sources = useMemo(\n () => sourcesQuery.data?.cosListSources?.items ?? [],\n [sourcesQuery.data]\n );\n const collections = useMemo(\n () => collectionsQuery.data?.cosListCollections ?? [],\n [collectionsQuery.data]\n );\n\n // sourceId → source type, so a dataset can be bucketed by instrument type.\n const sourceTypeById = useMemo(() => {\n const map = new Map<string, string>();\n for (const source of sources) {\n map.set(source.id, source.type);\n }\n return map;\n }, [sources]);\n\n const collectionNameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const collection of collections) {\n map.set(collection.id, collection.name);\n }\n return map;\n }, [collections]);\n\n // Per-group predicates — a dataset \"matches\" a group if the group has no\n // selection or the dataset's value is among the selected values.\n const matchers = useMemo(() => {\n const instrument = selected.instrument ?? [];\n const access = selected.access ?? [];\n const format = selected.format ?? [];\n const mission = selected.mission ?? [];\n return {\n instrument: (d: CosDataset) => {\n if (instrument.length === 0) return true;\n const type = d.sourceId ? sourceTypeById.get(d.sourceId) : undefined;\n return type ? instrument.includes(type) : false;\n },\n access: (d: CosDataset) => access.length === 0 || access.includes(d.accessLevel),\n format: (d: CosDataset) =>\n format.length === 0 || (d.format ? format.includes(d.format) : false),\n mission: (d: CosDataset) =>\n mission.length === 0 || (d.collectionId ? mission.includes(d.collectionId) : false),\n } as const;\n }, [selected, sourceTypeById]);\n\n // Cross-filtered count: items passing every OTHER group, then the predicate.\n const countFor = useMemo(() => {\n return (groupKey: keyof typeof matchers, predicate: (d: CosDataset) => boolean): number => {\n let total = 0;\n for (const dataset of pool) {\n let ok = true;\n for (const key of Object.keys(matchers) as (keyof typeof matchers)[]) {\n if (key === groupKey) continue;\n if (!matchers[key](dataset)) {\n ok = false;\n break;\n }\n }\n if (ok && predicate(dataset)) {\n total += 1;\n }\n }\n return total;\n };\n }, [pool, matchers]);\n\n const facetGroups = useMemo<FacetGroup[]>(() => {\n // Universe of option values from the unfiltered pool (stable while toggling).\n const instrumentTypes = new Set<string>();\n const formats = new Set<string>();\n const missionIds = new Set<string>();\n for (const dataset of pool) {\n const type = dataset.sourceId ? sourceTypeById.get(dataset.sourceId) : undefined;\n if (type) instrumentTypes.add(type);\n if (dataset.format) formats.add(dataset.format);\n if (dataset.collectionId) missionIds.add(dataset.collectionId);\n }\n\n const instrument: FacetGroup = {\n key: 'instrument',\n label: 'Instrument',\n options: [...instrumentTypes]\n .map((type) => ({\n value: type,\n label: formatEnumLabel(type),\n count: countFor('instrument', (d) =>\n d.sourceId ? sourceTypeById.get(d.sourceId) === type : false\n ),\n }))\n .sort((a, b) => b.count - a.count || a.label.localeCompare(b.label)),\n };\n\n const access: FacetGroup = {\n key: 'access',\n label: 'Access',\n options: ACCESS_LEVELS.map((level) => ({\n value: level,\n label: formatEnumLabel(level),\n count: countFor('access', (d) => d.accessLevel === level),\n })),\n };\n\n const format: FacetGroup = {\n key: 'format',\n label: 'Format',\n options: [...formats]\n .sort((a, b) => a.localeCompare(b))\n .map((fmt) => ({\n value: fmt,\n label: fmt,\n count: countFor('format', (d) => d.format === fmt),\n })),\n };\n\n const mission: FacetGroup = {\n key: 'mission',\n label: 'Mission / Survey',\n options: [...missionIds]\n .map((id) => ({\n value: id,\n label: collectionNameById.get(id) ?? 'Unknown collection',\n count: countFor('mission', (d) => d.collectionId === id),\n }))\n .sort((a, b) => b.count - a.count || a.label.localeCompare(b.label)),\n };\n\n return [instrument, access, format, mission];\n }, [pool, sourceTypeById, collectionNameById, countFor]);\n\n // Displayed results: the pool filtered by all selected facets, then sorted.\n const results = useMemo(() => {\n const filtered = pool.filter(\n (dataset) =>\n matchers.instrument(dataset) &&\n matchers.access(dataset) &&\n matchers.format(dataset) &&\n matchers.mission(dataset)\n );\n const sorted = [...filtered];\n switch (sort) {\n case 'name':\n sorted.sort((a, b) => a.name.localeCompare(b.name));\n break;\n case 'updated':\n sorted.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());\n break;\n case 'size':\n sorted.sort((a, b) => Number(b.sizeBytes) - Number(a.sizeBytes));\n break;\n case 'relevance':\n default:\n break; // preserve backend order (relevance when `q` is present)\n }\n return sorted;\n }, [pool, matchers, sort]);\n\n const totalSelected = Object.values(selected).reduce((sum, values) => sum + values.length, 0);\n\n const handleToggle = (groupKey: string, value: string) => {\n setSelected((prev) => {\n const current = prev[groupKey] ?? [];\n const next = current.includes(value)\n ? current.filter((item) => item !== value)\n : [...current, value];\n return { ...prev, [groupKey]: next };\n });\n };\n\n const handleClear = () => setSelected({});\n\n const loading = poolQuery.loading && pool.length === 0;\n const error = poolQuery.error;\n\n const facetPanel = (\n <FacetPanel\n groups={facetGroups}\n selected={selected}\n onToggle={handleToggle}\n onClear={handleClear}\n />\n );\n\n return (\n <PageLayout\n title={tr('cosmicintersection.pages.discovery.title', 'Discovery')}\n description={tr(\n 'cosmicintersection.pages.discovery.description',\n 'Search and facet the federated science catalog — full-text across datasets, refined by instrument, access, format and mission.'\n )}\n actions={\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('datasets')}>\n <TableIcon className=\"size-4\" />\n {tr('cosmicintersection.pages.discovery.tableView', 'Table view')}\n </Button>\n }\n >\n <div className=\"space-y-5\">\n {/* Search bar */}\n <div className=\"relative\">\n <Search\n className=\"pointer-events-none absolute left-4 top-1/2 size-5 -translate-y-1/2 text-text-secondary\"\n aria-hidden=\"true\"\n />\n <Input\n type=\"search\"\n value={searchInput}\n onChange={(event) => setSearchInput(event.target.value)}\n placeholder={tr(\n 'cosmicintersection.pages.discovery.searchPlaceholder',\n 'Search datasets by name, description or tag…'\n )}\n aria-label={tr('cosmicintersection.pages.discovery.searchAriaLabel', 'Search datasets')}\n className=\"h-12 rounded-card pl-12 pr-11 text-base\"\n />\n {searchInput ? (\n <button\n type=\"button\"\n onClick={() => setSearchInput('')}\n aria-label={tr('cosmicintersection.pages.discovery.clearSearch', 'Clear search')}\n className=\"absolute right-3 top-1/2 flex size-7 -translate-y-1/2 items-center justify-center rounded-full text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-focus\"\n >\n <X className=\"size-4\" />\n </button>\n ) : null}\n </div>\n\n {/* Mobile facet toggle */}\n <div className=\"flex items-center justify-between gap-3 lg:hidden\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => setFacetsOpen((open) => !open)}\n aria-expanded={facetsOpen}\n >\n <SlidersHorizontal className=\"size-4\" />\n {totalSelected > 0\n ? tr('cosmicintersection.pages.discovery.filtersWithCount', 'Filters ({{count}})', {\n count: totalSelected,\n })\n : tr('cosmicintersection.pages.discovery.filters', 'Filters')}\n </Button>\n </div>\n\n <div className=\"flex flex-col gap-6 lg:flex-row\">\n {/* Facets — sticky sidebar on desktop, collapsible on mobile */}\n <aside\n className={cn(\n 'shrink-0 lg:block lg:w-60 lg:border-r lg:border-border-seam lg:pr-6',\n facetsOpen ? 'block' : 'hidden'\n )}\n >\n <div className=\"lg:sticky lg:top-0\">{facetPanel}</div>\n </aside>\n\n {/* Results */}\n <div className=\"min-w-0 flex-1 space-y-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-lg font-semibold tabular-nums text-text-primary\">\n {results.length}\n </span>\n <span className=\"text-sm text-text-secondary\">\n {results.length === 1\n ? tr('cosmicintersection.pages.discovery.resultCountSingular', 'dataset')\n : tr('cosmicintersection.pages.discovery.resultCountPlural', 'datasets')}\n {q\n ? ` ${tr('cosmicintersection.pages.discovery.matching', 'matching “{{q}}”', {\n q,\n })}`\n : ''}\n </span>\n </div>\n <label className=\"flex items-center gap-2 text-sm\">\n <ArrowUpDown className=\"size-4 text-text-secondary\" aria-hidden=\"true\" />\n <Select value={sort} onValueChange={(value) => setSort(value as SortKey)}>\n <SelectTrigger\n aria-label={tr(\n 'cosmicintersection.pages.discovery.sortResults',\n 'Sort results'\n )}\n className=\"w-48\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {(Object.keys(SORT_LABELS) as SortKey[]).map((key) => (\n <SelectItem key={key} value={key}>\n {SORT_LABELS[key]}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {poolHasMore ? (\n <p className=\"rounded-card border border-border-default bg-bg-sunken px-3 py-2 text-xs text-text-secondary\">\n {tr(\n 'cosmicintersection.pages.discovery.poolLimitNotice',\n 'Showing facets and results for the first {{limit}} matches. Refine your search to narrow the set.',\n { limit: POOL_LIMIT }\n )}\n </p>\n ) : null}\n\n {loading ? (\n <LoadingState\n variant=\"rows\"\n rows={6}\n label={tr('cosmicintersection.pages.discovery.loading', 'Searching the catalog…')}\n />\n ) : error ? (\n <ErrorState\n message={tr(\n 'cosmicintersection.pages.discovery.error',\n 'We could not run that search. Please try again.'\n )}\n onRetry={() => void poolQuery.refetch()}\n />\n ) : results.length === 0 ? (\n <EmptyState\n icon={Search}\n title={\n q || totalSelected > 0\n ? tr('cosmicintersection.pages.discovery.noDatasetsMatch', 'No datasets match')\n : tr('cosmicintersection.pages.discovery.startExploring', 'Start exploring')\n }\n description={\n q || totalSelected > 0\n ? tr(\n 'cosmicintersection.pages.discovery.noDatasetsMatchDescription',\n 'Try a broader search term or clear some facets.'\n )\n : tr(\n 'cosmicintersection.pages.discovery.startExploringDescription',\n 'Search by name, description or tag, or pick a facet to browse the catalog.'\n )\n }\n action={\n totalSelected > 0 ? (\n <Button type=\"button\" variant=\"outline\" onClick={handleClear}>\n {tr('cosmicintersection.pages.discovery.clearFilters', 'Clear filters')}\n </Button>\n ) : undefined\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3\">\n {results.map((dataset) => (\n <button\n key={dataset.id}\n type=\"button\"\n onClick={() => navigate(`datasets/${dataset.id}`)}\n className=\"group flex flex-col overflow-hidden rounded-card border border-border-default bg-bg-elevated text-left outline-none transition-colors hover:border-border-strong hover:bg-bg-sunken focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <div className=\"h-1 w-full bg-gradient-to-r from-accent-blue via-accent-purple to-accent-teal\" />\n <div className=\"flex flex-1 flex-col gap-3 p-4\">\n <h3 className=\"line-clamp-2 text-sm font-semibold text-text-primary\">\n {dataset.name}\n </h3>\n\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-text-secondary\">\n <span className=\"inline-flex items-center gap-1\">\n <FileText className=\"size-3.5\" aria-hidden=\"true\" />\n {dataset.format || 'No format'}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Database className=\"size-3.5\" aria-hidden=\"true\" />\n {formatBytes(dataset.sizeBytes)}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Rows3 className=\"size-3.5\" aria-hidden=\"true\" />\n {formatRows(dataset.rowCount)}\n </span>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={dataset.status} />\n <StatusBadge status={dataset.accessLevel} />\n </div>\n\n {dataset.tags.length > 0 ? (\n <div className=\"mt-auto flex flex-wrap gap-1 pt-1\">\n {dataset.tags.slice(0, 3).map((tag) => (\n <Badge key={tag} variant=\"secondary\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n {dataset.tags.length > 3 ? (\n <span className=\"text-xs text-text-secondary\">\n +{dataset.tags.length - 3}\n </span>\n ) : null}\n </div>\n ) : null}\n </div>\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA8DA,IAAM,IAAa,KACb,KAAqB,KAErB,KAAkC;CAAC;CAAU;CAAa;CAAc;AAAW,GAInF,IAAuC;CAC3C,WAAW;CACX,MAAM;CACN,SAAS;CACT,MAAM;AACR;AAGA,SAAS,EAAY,GAAwC;CAC3D,IAAI,CAAC,GACH,OAAO;CAET,IAAM,IAAQ,OAAO,CAAG;CACxB,IAAI,CAAC,OAAO,SAAS,CAAK,KAAK,KAAS,GACtC,OAAO;CAET,IAAM,IAAQ;EAAC;EAAK;EAAM;EAAM;EAAM;EAAM;CAAI,GAC5C,IAAQ,GACR,IAAO;CACX,OAAO,KAAS,QAAQ,IAAO,EAAM,SAAS,IAE5C,AADA,KAAS,MACT,KAAQ;CAEV,OAAO,GAAG,KAAS,MAAM,MAAS,IAAI,KAAK,MAAM,CAAK,IAAI,EAAM,QAAQ,CAAC,EAAE,GAAG,EAAM;AACtF;AAEA,SAAS,EAAW,GAAwC;CAC1D,IAAM,IAAO,OAAO,KAAO,CAAC;CAC5B,OAAO,OAAO,SAAS,CAAI,KAAK,IAAO,IAAI,GAAG,EAAK,eAAe,EAAE,SAAS;AAC/E;AAEA,SAAwB,IAAgB;CACtC,IAAM,EAAE,gBAAa,EAAsB,GACrC,IAAK,EAAM,GAEX,CAAC,GAAa,KAAkB,EAAS,EAAE,GAC3C,CAAC,GAAG,KAAQ,EAAS,EAAE,GACvB,CAAC,GAAU,KAAe,EAAmC,CAAC,CAAC,GAC/D,CAAC,GAAM,KAAW,EAAkB,WAAW,GAC/C,CAAC,GAAY,KAAiB,EAAS,EAAK;CAGlD,SAAgB;EACd,IAAM,IAAS,OAAO,iBAAiB,EAAK,EAAY,KAAK,CAAC,GAAG,EAAkB;EACnF,aAAa,OAAO,aAAa,CAAM;CACzC,GAAG,CAAC,CAAW,CAAC;CAEhB,IAAM,IAAY,EAAoB;EACpC,GAAG,KAAK,KAAA;EACR,YAAY;GAAE,OAAO;GAAY,QAAQ;EAAE;CAC7C,CAAC,GACK,IAAe,EAAmB,EAAE,YAAY;EAAE,OAAO;EAAY,QAAQ;CAAE,EAAE,CAAC,GAClF,IAAmB,EAAuB,GAE1C,IAAO,QAAc,EAAU,MAAM,iBAAiB,SAAS,CAAC,GAAG,CAAC,EAAU,IAAI,CAAC,GACnF,KAAc,EAAQ,EAAU,MAAM,iBAAiB,UAAU,SACjE,IAAU,QACR,EAAa,MAAM,gBAAgB,SAAS,CAAC,GACnD,CAAC,EAAa,IAAI,CACpB,GACM,IAAc,QACZ,EAAiB,MAAM,sBAAsB,CAAC,GACpD,CAAC,EAAiB,IAAI,CACxB,GAGM,IAAiB,QAAc;EACnC,IAAM,oBAAM,IAAI,IAAoB;EACpC,KAAK,IAAM,KAAU,GACnB,EAAI,IAAI,EAAO,IAAI,EAAO,IAAI;EAEhC,OAAO;CACT,GAAG,CAAC,CAAO,CAAC,GAEN,IAAqB,QAAc;EACvC,IAAM,oBAAM,IAAI,IAAoB;EACpC,KAAK,IAAM,KAAc,GACvB,EAAI,IAAI,EAAW,IAAI,EAAW,IAAI;EAExC,OAAO;CACT,GAAG,CAAC,CAAW,CAAC,GAIV,IAAW,QAAc;EAC7B,IAAM,IAAa,EAAS,cAAc,CAAC,GACrC,IAAS,EAAS,UAAU,CAAC,GAC7B,IAAS,EAAS,UAAU,CAAC,GAC7B,IAAU,EAAS,WAAW,CAAC;EACrC,OAAO;GACL,aAAa,MAAkB;IAC7B,IAAI,EAAW,WAAW,GAAG,OAAO;IACpC,IAAM,IAAO,EAAE,WAAW,EAAe,IAAI,EAAE,QAAQ,IAAI,KAAA;IAC3D,OAAO,IAAO,EAAW,SAAS,CAAI,IAAI;GAC5C;GACA,SAAS,MAAkB,EAAO,WAAW,KAAK,EAAO,SAAS,EAAE,WAAW;GAC/E,SAAS,MACP,EAAO,WAAW,MAAM,EAAE,SAAS,EAAO,SAAS,EAAE,MAAM,IAAI;GACjE,UAAU,MACR,EAAQ,WAAW,MAAM,EAAE,eAAe,EAAQ,SAAS,EAAE,YAAY,IAAI;EACjF;CACF,GAAG,CAAC,GAAU,CAAc,CAAC,GAGvB,IAAW,SACP,GAAiC,MAAkD;EACzF,IAAI,IAAQ;EACZ,KAAK,IAAM,KAAW,GAAM;GAC1B,IAAI,IAAK;GACT,KAAK,IAAM,KAAO,OAAO,KAAK,CAAQ,GAChC,UAAQ,KACR,CAAC,EAAS,GAAK,CAAO,GAAG;IAC3B,IAAK;IACL;GACF;GAEF,AAAI,KAAM,EAAU,CAAO,MACzB,KAAS;EAEb;EACA,OAAO;CACT,GACC,CAAC,GAAM,CAAQ,CAAC,GAEb,IAAc,QAA4B;EAE9C,IAAM,oBAAkB,IAAI,IAAY,GAClC,oBAAU,IAAI,IAAY,GAC1B,oBAAa,IAAI,IAAY;EACnC,KAAK,IAAM,KAAW,GAAM;GAC1B,IAAM,IAAO,EAAQ,WAAW,EAAe,IAAI,EAAQ,QAAQ,IAAI,KAAA;GAGvE,AAFI,KAAM,EAAgB,IAAI,CAAI,GAC9B,EAAQ,UAAQ,EAAQ,IAAI,EAAQ,MAAM,GAC1C,EAAQ,gBAAc,EAAW,IAAI,EAAQ,YAAY;EAC/D;EAkDA,OAAO;GAAC;IA/CN,KAAK;IACL,OAAO;IACP,SAAS,CAAC,GAAG,CAAe,EACzB,KAAK,OAAU;KACd,OAAO;KACP,OAAO,EAAgB,CAAI;KAC3B,OAAO,EAAS,eAAe,MAC7B,EAAE,WAAW,EAAe,IAAI,EAAE,QAAQ,MAAM,IAAO,EACzD;IACF,EAAE,EACD,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,EAAE,KAAK,CAAC;GAqC/D;GAAY;IAjClB,KAAK;IACL,OAAO;IACP,SAAS,GAAc,KAAK,OAAW;KACrC,OAAO;KACP,OAAO,EAAgB,CAAK;KAC5B,OAAO,EAAS,WAAW,MAAM,EAAE,gBAAgB,CAAK;IAC1D,EAAE;GA2BgB;GAAQ;IAvB1B,KAAK;IACL,OAAO;IACP,SAAS,CAAC,GAAG,CAAO,EACjB,MAAM,GAAG,MAAM,EAAE,cAAc,CAAC,CAAC,EACjC,KAAK,OAAS;KACb,OAAO;KACP,OAAO;KACP,OAAO,EAAS,WAAW,MAAM,EAAE,WAAW,CAAG;IACnD,EAAE;GAesB;GAAQ;IAXlC,KAAK;IACL,OAAO;IACP,SAAS,CAAC,GAAG,CAAU,EACpB,KAAK,OAAQ;KACZ,OAAO;KACP,OAAO,EAAmB,IAAI,CAAE,KAAK;KACrC,OAAO,EAAS,YAAY,MAAM,EAAE,iBAAiB,CAAE;IACzD,EAAE,EACD,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,EAAE,KAAK,CAAC;GAGnC;EAAO;CAC7C,GAAG;EAAC;EAAM;EAAgB;EAAoB;CAAQ,CAAC,GAGjD,IAAU,QAAc;EAQ5B,IAAM,IAAS,CAAC,GAPC,EAAK,QACnB,MACC,EAAS,WAAW,CAAO,KAC3B,EAAS,OAAO,CAAO,KACvB,EAAS,OAAO,CAAO,KACvB,EAAS,QAAQ,CAAO,CAET,CAAQ;EAC3B,QAAQ,GAAR;GACE,KAAK;IACH,EAAO,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;IAClD;GACF,KAAK;IACH,EAAO,MAAM,GAAG,MAAM,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC;IACvF;GACF,KAAK;IACH,EAAO,MAAM,GAAG,MAAM,OAAO,EAAE,SAAS,IAAI,OAAO,EAAE,SAAS,CAAC;IAC/D;GAEF,SACE;EACJ;EACA,OAAO;CACT,GAAG;EAAC;EAAM;EAAU;CAAI,CAAC,GAEnB,IAAgB,OAAO,OAAO,CAAQ,EAAE,QAAQ,GAAK,MAAW,IAAM,EAAO,QAAQ,CAAC,GAEtF,MAAgB,GAAkB,MAAkB;EACxD,GAAa,MAAS;GACpB,IAAM,IAAU,EAAK,MAAa,CAAC,GAC7B,IAAO,EAAQ,SAAS,CAAK,IAC/B,EAAQ,QAAQ,MAAS,MAAS,CAAK,IACvC,CAAC,GAAG,GAAS,CAAK;GACtB,OAAO;IAAE,GAAG;KAAO,IAAW;GAAK;EACrC,CAAC;CACH,GAEM,UAAoB,EAAY,CAAC,CAAC,GAElC,KAAU,EAAU,WAAW,EAAK,WAAW,GAC/C,KAAQ,EAAU,OAElB,KACJ,kBAAC,IAAD;EACE,QAAQ;EACE;EACV,UAAU;EACV,SAAS;CACV,CAAA;CAGH,OACE,kBAAC,GAAD;EACE,OAAO,EAAG,4CAA4C,WAAW;EACjE,aAAa,EACX,kDACA,gIACF;EACA,SACE,kBAAC,GAAD;GAAQ,MAAK;GAAS,SAAQ;GAAU,eAAe,EAAS,UAAU;aAA1E,CACE,kBAAC,GAAD,EAAW,WAAU,SAAU,CAAA,GAC9B,EAAG,gDAAgD,YAAY,CAC1D;;YAGV,kBAAC,OAAD;GAAK,WAAU;aAAf;IAEE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OACE,WAAU;OACV,eAAY;MACb,CAAA;MACD,kBAAC,IAAD;OACE,MAAK;OACL,OAAO;OACP,WAAW,MAAU,EAAe,EAAM,OAAO,KAAK;OACtD,aAAa,EACX,wDACA,8CACF;OACA,cAAY,EAAG,sDAAsD,iBAAiB;OACtF,WAAU;MACX,CAAA;MACA,IACC,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAe,EAAE;OAChC,cAAY,EAAG,kDAAkD,cAAc;OAC/E,WAAU;iBAEV,kBAAC,GAAD,EAAG,WAAU,SAAU,CAAA;MACjB,CAAA,IACN;KACD;;IAGL,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MACE,MAAK;MACL,SAAQ;MACR,eAAe,GAAe,MAAS,CAAC,CAAI;MAC5C,iBAAe;gBAJjB,CAME,kBAAC,GAAD,EAAmB,WAAU,SAAU,CAAA,GACtC,IAAgB,IACb,EAAG,uDAAuD,uBAAuB,EAC/E,OAAO,EACT,CAAC,IACD,EAAG,8CAA8C,SAAS,CACxD;;IACL,CAAA;IAEL,kBAAC,OAAD;KAAK,WAAU;eAAf,CAEE,kBAAC,SAAD;MACE,WAAW,EACT,uEACA,IAAa,UAAU,QACzB;gBAEA,kBAAC,OAAD;OAAK,WAAU;iBAAsB;MAAgB,CAAA;KAChD,CAAA,GAGP,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAQ;SACL,CAAA,GACN,kBAAC,QAAD;UAAM,WAAU;oBAAhB,CACG,EAAQ,WAAW,IAChB,EAAG,0DAA0D,SAAS,IACtE,EAAG,wDAAwD,UAAU,GACxE,IACG,IAAI,EAAG,+CAA+C,oBAAoB,EACxE,KACF,CAAC,MACD,EACA;WACH;YACL,kBAAC,SAAD;SAAO,WAAU;mBAAjB,CACE,kBAAC,GAAD;UAAa,WAAU;UAA6B,eAAY;SAAQ,CAAA,GACxE,kBAAC,IAAD;UAAQ,OAAO;UAAM,gBAAgB,MAAU,EAAQ,CAAgB;oBAAvE,CACE,kBAAC,IAAD;WACE,cAAY,EACV,kDACA,cACF;WACA,WAAU;qBAEV,kBAAC,IAAD,CAAc,CAAA;UACD,CAAA,GACf,kBAAC,IAAD,EAAA,UACI,OAAO,KAAK,CAAW,EAAgB,KAAK,MAC5C,kBAAC,IAAD;WAAsB,OAAO;qBAC1B,EAAY;UACH,GAFK,CAEL,CACb,EACY,CAAA,CACT;WACH;UACJ;;OAEJ,KACC,kBAAC,KAAD;QAAG,WAAU;kBACV,EACC,sDACA,qGACA,EAAE,OAAO,EAAW,CACtB;OACC,CAAA,IACD;OAEH,KACC,kBAAC,IAAD;QACE,SAAQ;QACR,MAAM;QACN,OAAO,EAAG,8CAA8C,wBAAwB;OACjF,CAAA,IACC,KACF,kBAAC,GAAD;QACE,SAAS,EACP,4CACA,iDACF;QACA,eAAe,KAAK,EAAU,QAAQ;OACvC,CAAA,IACC,EAAQ,WAAW,IACrB,kBAAC,GAAD;QACE,MAAM;QACN,OACE,KAAK,IAAgB,IACjB,EAAG,sDAAsD,mBAAmB,IAC5E,EAAG,qDAAqD,iBAAiB;QAE/E,aACE,KAAK,IAAgB,IACjB,EACE,iEACA,iDACF,IACA,EACE,gEACA,4EACF;QAEN,QACE,IAAgB,IACd,kBAAC,GAAD;SAAQ,MAAK;SAAS,SAAQ;SAAU,SAAS;mBAC9C,EAAG,mDAAmD,eAAe;QAChE,CAAA,IACN,KAAA;OAEP,CAAA,IAED,kBAAC,OAAD;QAAK,WAAU;kBACZ,EAAQ,KAAK,MACZ,kBAAC,UAAD;SAEE,MAAK;SACL,eAAe,EAAS,YAAY,EAAQ,IAAI;SAChD,WAAU;mBAJZ,CAME,kBAAC,OAAD,EAAK,WAAU,gFAAiF,CAAA,GAChG,kBAAC,OAAD;UAAK,WAAU;oBAAf;WACE,kBAAC,MAAD;YAAI,WAAU;sBACX,EAAQ;WACP,CAAA;WAEJ,kBAAC,OAAD;YAAK,WAAU;sBAAf;aACE,kBAAC,QAAD;cAAM,WAAU;wBAAhB,CACE,kBAAC,GAAD;eAAU,WAAU;eAAW,eAAY;cAAQ,CAAA,GAClD,EAAQ,UAAU,WACf;;aACN,kBAAC,QAAD;cAAM,WAAU;wBAAhB,CACE,kBAAC,GAAD;eAAU,WAAU;eAAW,eAAY;cAAQ,CAAA,GAClD,EAAY,EAAQ,SAAS,CAC1B;;aACN,kBAAC,QAAD;cAAM,WAAU;wBAAhB,CACE,kBAAC,GAAD;eAAO,WAAU;eAAW,eAAY;cAAQ,CAAA,GAC/C,EAAW,EAAQ,QAAQ,CACxB;;YACH;;WAEL,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACE,kBAAC,GAAD,EAAa,QAAQ,EAAQ,OAAS,CAAA,GACtC,kBAAC,GAAD,EAAa,QAAQ,EAAQ,YAAc,CAAA,CACxC;;WAEJ,EAAQ,KAAK,SAAS,IACrB,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACG,EAAQ,KAAK,MAAM,GAAG,CAAC,EAAE,KAAK,MAC7B,kBAAC,IAAD;aAAiB,SAAQ;aAAY,WAAU;uBAC5C;YACI,GAFK,CAEL,CACR,GACA,EAAQ,KAAK,SAAS,IACrB,kBAAC,QAAD;aAAM,WAAU;uBAAhB,CAA8C,KAC1C,EAAQ,KAAK,SAAS,CACpB;iBACJ,IACD;gBACH;UACD;WACC;WA9CD,EAAQ,EA8CP,CACT;OACE,CAAA;MAEJ;OACF;;GACF;;CACK,CAAA;AAEhB"}
@@ -1 +1 @@
1
- {"version":3,"file":"LineagePage.d.ts","sourceRoot":"","sources":["../../../../src/cosmicintersection/pages/lineage/LineagePage.tsx"],"names":[],"mappings":"AAmCA,MAAM,CAAC,OAAO,UAAU,WAAW,4CAoPlC"}
1
+ {"version":3,"file":"LineagePage.d.ts","sourceRoot":"","sources":["../../../../src/cosmicintersection/pages/lineage/LineagePage.tsx"],"names":[],"mappings":"AAmCA,MAAM,CAAC,OAAO,UAAU,WAAW,4CAqPlC"}
@@ -1 +1 @@
1
- {"version":3,"file":"LineagePage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/lineage/LineagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\nimport { Info, Share2, Workflow } from 'lucide-react';\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosCollectionsQuery,\n useCosDatasetsQuery,\n useCosNotebooksQuery,\n useCosPipelinesQuery,\n useCosReleasesQuery,\n useCosRunsQuery,\n useCosAnalysesQuery,\n useCosSourcesQuery,\n} from '../../api/cosmicApi';\nimport { EmptyState, ErrorState, LoadingState, PageLayout } from '../../components';\nimport {\n LineageGraph,\n buildDatasetLineage,\n buildReleaseLineage,\n type LineageDataPools,\n} from '../../components/LineageGraph';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ntype RootKind = 'dataset' | 'release';\n\nconst PAGE_LIMIT = 100;\n\nexport default function LineagePage() {\n const { navigate } = useCosmicIntersection();\n const tr = useTr();\n // Optional deep-link: /lineage/:datasetId preselects that dataset as the root.\n const { datasetId: routeDatasetId } = useParams<{ datasetId?: string }>();\n\n const datasetsQuery = useCosDatasetsQuery({ pagination: { limit: PAGE_LIMIT, offset: 0 } });\n const releasesQuery = useCosReleasesQuery({ pagination: { limit: PAGE_LIMIT, offset: 0 } });\n const sourcesQuery = useCosSourcesQuery({ pagination: { limit: PAGE_LIMIT, offset: 0 } });\n const collectionsQuery = useCosCollectionsQuery();\n const runsQuery = useCosRunsQuery({ pagination: { limit: PAGE_LIMIT, offset: 0 } });\n const notebooksQuery = useCosNotebooksQuery({ pagination: { limit: PAGE_LIMIT, offset: 0 } });\n const analysesQuery = useCosAnalysesQuery({});\n const pipelinesQuery = useCosPipelinesQuery({ pagination: { limit: PAGE_LIMIT, offset: 0 } });\n\n const datasets = useMemo(\n () => datasetsQuery.data?.cosListDatasets?.items ?? [],\n [datasetsQuery.data]\n );\n const releases = useMemo(\n () => releasesQuery.data?.cosListReleases?.items ?? [],\n [releasesQuery.data]\n );\n const sources = useMemo(\n () => sourcesQuery.data?.cosListSources?.items ?? [],\n [sourcesQuery.data]\n );\n const collections = useMemo(\n () => collectionsQuery.data?.cosListCollections ?? [],\n [collectionsQuery.data]\n );\n const runs = useMemo(() => runsQuery.data?.cosListRuns?.items ?? [], [runsQuery.data]);\n const notebooks = useMemo(\n () => notebooksQuery.data?.cosListNotebooks?.items ?? [],\n [notebooksQuery.data]\n );\n const analyses = useMemo(() => analysesQuery.data?.cosListAnalyses ?? [], [analysesQuery.data]);\n const pipelines = useMemo(\n () => pipelinesQuery.data?.cosListPipelines?.items ?? [],\n [pipelinesQuery.data]\n );\n\n const pools: LineageDataPools = useMemo(\n () => ({\n datasetsById: new Map(datasets.map((item) => [item.id, item])),\n sourcesById: new Map(sources.map((item) => [item.id, item])),\n collectionsById: new Map(collections.map((item) => [item.id, item])),\n runs,\n notebooks,\n analyses,\n releases,\n pipelinesById: new Map(pipelines.map((item) => [item.id, item])),\n }),\n [datasets, sources, collections, runs, notebooks, analyses, releases, pipelines]\n );\n\n const [rootKind, setRootKind] = useState<RootKind>('dataset');\n const [selectedId, setSelectedId] = useState<string>(routeDatasetId ?? '');\n\n // Auto-select the first available root of the current kind once lists load —\n // the documented derive-state-during-render pattern (no effect required).\n const options = rootKind === 'dataset' ? datasets : releases;\n const firstOption = options[0]?.id ?? '';\n const selectionValid = options.some((item) => item.id === selectedId);\n if (!selectionValid && firstOption) {\n setSelectedId(firstOption);\n }\n const activeId = selectionValid ? selectedId : firstOption;\n\n const model = useMemo(() => {\n if (!activeId) {\n return { nodes: [], edges: [] };\n }\n return rootKind === 'dataset'\n ? buildDatasetLineage(activeId, pools)\n : buildReleaseLineage(activeId, pools);\n }, [activeId, rootKind, pools]);\n\n const primaryLoading =\n (datasetsQuery.loading && datasets.length === 0) ||\n (releasesQuery.loading && releases.length === 0);\n const primaryError = datasetsQuery.error || releasesQuery.error;\n\n const handleKindChange = (value: string) => {\n setRootKind(value as RootKind);\n setSelectedId('');\n };\n\n const activeLabel =\n rootKind === 'dataset'\n ? datasets.find((item) => item.id === activeId)?.name\n : releases.find((item) => item.id === activeId)?.title;\n\n return (\n <PageLayout\n title={tr('cosmicintersection.pages.lineage.title', 'Lineage')}\n description={tr(\n 'cosmicintersection.pages.lineage.description',\n 'Trace any result from raw observation to citable release.',\n )}\n >\n <div className=\"space-y-6\">\n {/* Root picker */}\n <div className=\"flex flex-col gap-3 rounded-card border border-border-default bg-bg-elevated p-4 sm:flex-row sm:items-end\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">\n {tr('cosmicintersection.pages.lineage.traceFrom', 'Trace from')}\n </span>\n <Select value={rootKind} onValueChange={handleKindChange}>\n <SelectTrigger\n aria-label={tr('cosmicintersection.pages.lineage.rootEntityKind', 'Root entity kind')}\n className=\"w-full sm:w-44\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"dataset\">\n {tr('cosmicintersection.pages.lineage.dataset', 'Dataset')}\n </SelectItem>\n <SelectItem value=\"release\">\n {tr('cosmicintersection.pages.lineage.release', 'Release')}\n </SelectItem>\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm sm:flex-1\">\n <span className=\"block font-medium text-text-primary\">\n {rootKind === 'dataset'\n ? tr('cosmicintersection.pages.lineage.dataset', 'Dataset')\n : tr('cosmicintersection.pages.lineage.release', 'Release')}\n </span>\n <Select value={activeId} onValueChange={setSelectedId} disabled={options.length === 0}>\n <SelectTrigger\n aria-label={tr('cosmicintersection.pages.lineage.selectRoot', 'Select {{kind}}', {\n kind: rootKind,\n })}\n className=\"w-full sm:max-w-md\"\n >\n <SelectValue\n placeholder={\n options.length === 0\n ? tr(\n 'cosmicintersection.pages.lineage.noRootAvailable',\n 'No {{kind}}s available',\n { kind: rootKind },\n )\n : tr(\n 'cosmicintersection.pages.lineage.selectRoot',\n 'Select a {{kind}}',\n { kind: rootKind },\n )\n }\n />\n </SelectTrigger>\n <SelectContent>\n {options.map((item) => (\n <SelectItem key={item.id} value={item.id}>\n {rootKind === 'dataset'\n ? (item as (typeof datasets)[number]).name\n : (item as (typeof releases)[number]).title}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {primaryError ? (\n <ErrorState\n message={tr(\n 'cosmicintersection.pages.lineage.error',\n 'We could not load lineage data. Please try again.',\n )}\n onRetry={() => {\n void datasetsQuery.refetch();\n void releasesQuery.refetch();\n }}\n />\n ) : primaryLoading ? (\n <LoadingState label={tr('cosmicintersection.pages.lineage.loading', 'Loading lineage…')} />\n ) : datasets.length === 0 && releases.length === 0 ? (\n <EmptyState\n icon={Workflow}\n title={tr('cosmicintersection.pages.lineage.nothingToTrace', 'Nothing to trace yet.')}\n description={tr(\n 'cosmicintersection.pages.lineage.nothingToTraceDescription',\n 'Register a dataset or publish a release to build a provenance graph from raw observation to citable output.',\n )}\n action={\n <button\n type=\"button\"\n onClick={() => navigate('datasets')}\n className=\"text-sm font-medium text-text-link underline-offset-2 hover:underline\"\n >\n {tr('cosmicintersection.pages.lineage.goToDatasets', 'Go to datasets')}\n </button>\n }\n />\n ) : model.nodes.length === 0 ? (\n <EmptyState\n icon={Share2}\n title={tr(\n 'cosmicintersection.pages.lineage.selectToTrace',\n 'Select a {{kind}} to trace',\n { kind: rootKind },\n )}\n description={tr(\n 'cosmicintersection.pages.lineage.selectToTraceDescription',\n 'Choose a root above to render its provenance graph.',\n )}\n />\n ) : (\n <div className=\"space-y-4\">\n {activeLabel ? (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Share2 className=\"size-4 text-text-tertiary\" aria-hidden=\"true\" />\n <span>\n {tr(\n 'cosmicintersection.pages.lineage.provenanceFor',\n 'Provenance for {{name}}',\n { name: activeLabel },\n )}\n </span>\n </div>\n ) : null}\n\n <LineageGraph model={model} mode=\"full\" onNavigate={navigate} />\n\n <p className=\"flex items-start gap-2 text-xs text-text-tertiary\">\n <Info className=\"mt-0.5 size-3.5 shrink-0\" aria-hidden=\"true\" />\n <span>\n {tr(\n 'cosmicintersection.pages.lineage.graphDisclaimer',\n 'This graph is derived live from your workspace’s cos entities. Connected runs, notebooks, analyses and releases are matched across the {{limit}} most recent records of each type; older items may not appear.',\n { limit: PAGE_LIMIT },\n )}\n </span>\n </p>\n </div>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"LineagePage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/lineage/LineagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\nimport { Info, Share2, Workflow } from 'lucide-react';\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosCollectionsQuery,\n useCosDatasetsQuery,\n useCosNotebooksQuery,\n useCosPipelinesQuery,\n useCosReleasesQuery,\n useCosRunsQuery,\n useCosAnalysesQuery,\n useCosSourcesQuery,\n} from '../../api/cosmicApi';\nimport { EmptyState, ErrorState, LoadingState, PageLayout } from '../../components';\nimport {\n LineageGraph,\n buildDatasetLineage,\n buildReleaseLineage,\n type LineageDataPools,\n} from '../../components/LineageGraph';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ntype RootKind = 'dataset' | 'release';\n\nconst PAGE_LIMIT = 100;\n\nexport default function LineagePage() {\n const { navigate } = useCosmicIntersection();\n const tr = useTr();\n // Optional deep-link: /lineage/:datasetId preselects that dataset as the root.\n const { datasetId: routeDatasetId } = useParams<{ datasetId?: string }>();\n\n const datasetsQuery = useCosDatasetsQuery({ pagination: { limit: PAGE_LIMIT, offset: 0 } });\n const releasesQuery = useCosReleasesQuery({ pagination: { limit: PAGE_LIMIT, offset: 0 } });\n const sourcesQuery = useCosSourcesQuery({ pagination: { limit: PAGE_LIMIT, offset: 0 } });\n const collectionsQuery = useCosCollectionsQuery();\n const runsQuery = useCosRunsQuery({ pagination: { limit: PAGE_LIMIT, offset: 0 } });\n const notebooksQuery = useCosNotebooksQuery({ pagination: { limit: PAGE_LIMIT, offset: 0 } });\n const analysesQuery = useCosAnalysesQuery({});\n const pipelinesQuery = useCosPipelinesQuery({ pagination: { limit: PAGE_LIMIT, offset: 0 } });\n\n const datasets = useMemo(\n () => datasetsQuery.data?.cosListDatasets?.items ?? [],\n [datasetsQuery.data]\n );\n const releases = useMemo(\n () => releasesQuery.data?.cosListReleases?.items ?? [],\n [releasesQuery.data]\n );\n const sources = useMemo(\n () => sourcesQuery.data?.cosListSources?.items ?? [],\n [sourcesQuery.data]\n );\n const collections = useMemo(\n () => collectionsQuery.data?.cosListCollections ?? [],\n [collectionsQuery.data]\n );\n const runs = useMemo(() => runsQuery.data?.cosListRuns?.items ?? [], [runsQuery.data]);\n const notebooks = useMemo(\n () => notebooksQuery.data?.cosListNotebooks?.items ?? [],\n [notebooksQuery.data]\n );\n const analyses = useMemo(() => analysesQuery.data?.cosListAnalyses ?? [], [analysesQuery.data]);\n const pipelines = useMemo(\n () => pipelinesQuery.data?.cosListPipelines?.items ?? [],\n [pipelinesQuery.data]\n );\n\n const pools: LineageDataPools = useMemo(\n () => ({\n datasetsById: new Map(datasets.map((item) => [item.id, item])),\n sourcesById: new Map(sources.map((item) => [item.id, item])),\n collectionsById: new Map(collections.map((item) => [item.id, item])),\n runs,\n notebooks,\n analyses,\n releases,\n pipelinesById: new Map(pipelines.map((item) => [item.id, item])),\n }),\n [datasets, sources, collections, runs, notebooks, analyses, releases, pipelines]\n );\n\n const [rootKind, setRootKind] = useState<RootKind>('dataset');\n const [selectedId, setSelectedId] = useState<string>(routeDatasetId ?? '');\n\n // Auto-select the first available root of the current kind once lists load —\n // the documented derive-state-during-render pattern (no effect required).\n const options = rootKind === 'dataset' ? datasets : releases;\n const firstOption = options[0]?.id ?? '';\n const selectionValid = options.some((item) => item.id === selectedId);\n if (!selectionValid && firstOption) {\n setSelectedId(firstOption);\n }\n const activeId = selectionValid ? selectedId : firstOption;\n\n const model = useMemo(() => {\n if (!activeId) {\n return { nodes: [], edges: [] };\n }\n return rootKind === 'dataset'\n ? buildDatasetLineage(activeId, pools)\n : buildReleaseLineage(activeId, pools);\n }, [activeId, rootKind, pools]);\n\n const primaryLoading =\n (datasetsQuery.loading && datasets.length === 0) ||\n (releasesQuery.loading && releases.length === 0);\n const primaryError = datasetsQuery.error || releasesQuery.error;\n\n const handleKindChange = (value: string) => {\n setRootKind(value as RootKind);\n setSelectedId('');\n };\n\n const activeLabel =\n rootKind === 'dataset'\n ? datasets.find((item) => item.id === activeId)?.name\n : releases.find((item) => item.id === activeId)?.title;\n\n return (\n <PageLayout\n title={tr('cosmicintersection.pages.lineage.title', 'Lineage')}\n description={tr(\n 'cosmicintersection.pages.lineage.description',\n 'Trace any result from raw observation to citable release.'\n )}\n >\n <div className=\"space-y-6\">\n {/* Root picker */}\n <div className=\"flex flex-col gap-3 rounded-card border border-border-default bg-bg-elevated p-4 sm:flex-row sm:items-end\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">\n {tr('cosmicintersection.pages.lineage.traceFrom', 'Trace from')}\n </span>\n <Select value={rootKind} onValueChange={handleKindChange}>\n <SelectTrigger\n aria-label={tr(\n 'cosmicintersection.pages.lineage.rootEntityKind',\n 'Root entity kind'\n )}\n className=\"w-full sm:w-44\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"dataset\">\n {tr('cosmicintersection.pages.lineage.dataset', 'Dataset')}\n </SelectItem>\n <SelectItem value=\"release\">\n {tr('cosmicintersection.pages.lineage.release', 'Release')}\n </SelectItem>\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm sm:flex-1\">\n <span className=\"block font-medium text-text-primary\">\n {rootKind === 'dataset'\n ? tr('cosmicintersection.pages.lineage.dataset', 'Dataset')\n : tr('cosmicintersection.pages.lineage.release', 'Release')}\n </span>\n <Select value={activeId} onValueChange={setSelectedId} disabled={options.length === 0}>\n <SelectTrigger\n aria-label={tr('cosmicintersection.pages.lineage.selectRoot', 'Select {{kind}}', {\n kind: rootKind,\n })}\n className=\"w-full sm:max-w-md\"\n >\n <SelectValue\n placeholder={\n options.length === 0\n ? tr(\n 'cosmicintersection.pages.lineage.noRootAvailable',\n 'No {{kind}}s available',\n { kind: rootKind }\n )\n : tr('cosmicintersection.pages.lineage.selectRoot', 'Select a {{kind}}', {\n kind: rootKind,\n })\n }\n />\n </SelectTrigger>\n <SelectContent>\n {options.map((item) => (\n <SelectItem key={item.id} value={item.id}>\n {rootKind === 'dataset'\n ? (item as (typeof datasets)[number]).name\n : (item as (typeof releases)[number]).title}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {primaryError ? (\n <ErrorState\n message={tr(\n 'cosmicintersection.pages.lineage.error',\n 'We could not load lineage data. Please try again.'\n )}\n onRetry={() => {\n void datasetsQuery.refetch();\n void releasesQuery.refetch();\n }}\n />\n ) : primaryLoading ? (\n <LoadingState\n label={tr('cosmicintersection.pages.lineage.loading', 'Loading lineage…')}\n />\n ) : datasets.length === 0 && releases.length === 0 ? (\n <EmptyState\n icon={Workflow}\n title={tr('cosmicintersection.pages.lineage.nothingToTrace', 'Nothing to trace yet.')}\n description={tr(\n 'cosmicintersection.pages.lineage.nothingToTraceDescription',\n 'Register a dataset or publish a release to build a provenance graph from raw observation to citable output.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => navigate('datasets')}\n className=\"text-sm font-medium text-text-link underline-offset-2 hover:underline\"\n >\n {tr('cosmicintersection.pages.lineage.goToDatasets', 'Go to datasets')}\n </button>\n }\n />\n ) : model.nodes.length === 0 ? (\n <EmptyState\n icon={Share2}\n title={tr(\n 'cosmicintersection.pages.lineage.selectToTrace',\n 'Select a {{kind}} to trace',\n { kind: rootKind }\n )}\n description={tr(\n 'cosmicintersection.pages.lineage.selectToTraceDescription',\n 'Choose a root above to render its provenance graph.'\n )}\n />\n ) : (\n <div className=\"space-y-4\">\n {activeLabel ? (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Share2 className=\"size-4 text-text-tertiary\" aria-hidden=\"true\" />\n <span>\n {tr('cosmicintersection.pages.lineage.provenanceFor', 'Provenance for {{name}}', {\n name: activeLabel,\n })}\n </span>\n </div>\n ) : null}\n\n <LineageGraph model={model} mode=\"full\" onNavigate={navigate} />\n\n <p className=\"flex items-start gap-2 text-xs text-text-tertiary\">\n <Info className=\"mt-0.5 size-3.5 shrink-0\" aria-hidden=\"true\" />\n <span>\n {tr(\n 'cosmicintersection.pages.lineage.graphDisclaimer',\n 'This graph is derived live from your workspace’s cos entities. Connected runs, notebooks, analyses and releases are matched across the {{limit}} most recent records of each type; older items may not appear.',\n { limit: PAGE_LIMIT }\n )}\n </span>\n </p>\n </div>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"NotebooksPage.d.ts","sourceRoot":"","sources":["../../../../src/cosmicintersection/pages/notebooks/NotebooksPage.tsx"],"names":[],"mappings":"AAmEA,MAAM,CAAC,OAAO,UAAU,aAAa,4CA2SpC"}
1
+ {"version":3,"file":"NotebooksPage.d.ts","sourceRoot":"","sources":["../../../../src/cosmicintersection/pages/notebooks/NotebooksPage.tsx"],"names":[],"mappings":"AAmEA,MAAM,CAAC,OAAO,UAAU,aAAa,4CAqTpC"}
@@ -1 +1 @@
1
- {"version":3,"file":"NotebooksPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/notebooks/NotebooksPage.tsx"],"sourcesContent":["/**\n * NotebooksPage — paginated list of analysis notebooks.\n *\n * Notebooks are computational documents (title, runtime, environment, linked\n * datasets) that scientists run analyses inside. This surface lists them from\n * `cosListNotebooks` with a status filter, per-row View / Edit / Archive\n * actions, and offset pagination. Every value here comes from the backend —\n * there is no fabricated data.\n */\nimport { useState } from 'react';\nimport { NotebookText, Plus, Share2 } from 'lucide-react';\nimport {\n Badge,\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 {\n useCosArchiveNotebookMutation,\n useCosNotebooksQuery,\n type CosNotebook,\n type CosNotebookStatus,\n} from '../../api/cosmicApi';\nimport {\n ActionMenu,\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n Pagination,\n StatusBadge,\n usePagination,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\nimport { useTr } from '../../../shared/hooks/useTr';\n\nconst STATUS_OPTIONS: { value: CosNotebookStatus; label: string }[] = [\n { value: 'DRAFT', label: 'Draft' },\n { value: 'RUNNING', label: 'Running' },\n { value: 'PUBLISHED', label: 'Published' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nconst ALL = 'ALL';\n\nfunction formatDate(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) {\n return '—';\n }\n return date.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });\n}\n\nexport default function NotebooksPage() {\n const { navigate } = useCosmicIntersection();\n const tr = useTr();\n const [statusFilter, setStatusFilter] = useState<CosNotebookStatus | undefined>(undefined);\n const pagination = usePagination({ pageSize: 50, resetKey: statusFilter });\n\n const { data, loading, error, refetch } = useCosNotebooksQuery({\n status: statusFilter,\n pagination: { limit: pagination.limit, offset: pagination.offset },\n });\n const [archiveNotebook] = useCosArchiveNotebookMutation();\n\n const notebooks = data?.cosListNotebooks.items ?? [];\n const pageInfo = data?.cosListNotebooks.pageInfo;\n\n const handleArchive = async (notebook: CosNotebook) => {\n const response = await archiveNotebook({ variables: { id: notebook.id } });\n if (response.error) {\n // onError already surfaced the toast; rethrow so the confirm dialog stays open.\n throw new Error(response.error.message);\n }\n if (!response.data?.cosArchiveNotebook) {\n notify.error(\n tr('cosmicintersection.pages.notebooks.archiveError', 'Could not archive notebook'),\n tr(\n 'cosmicintersection.pages.notebooks.archiveErrorDescription',\n 'The server did not confirm the change.',\n ),\n );\n throw new Error('Archive not confirmed');\n }\n await refetch();\n notify.success(\n tr('cosmicintersection.pages.notebooks.archiveSuccess', 'Notebook archived'),\n tr(\n 'cosmicintersection.pages.notebooks.archiveSuccessDescription',\n '“{{name}}” was archived.',\n { name: notebook.title },\n ),\n );\n };\n\n const actions = (\n <Button type=\"button\" onClick={() => navigate('notebooks/create')}>\n <Plus className=\"size-4\" />\n {tr('cosmicintersection.pages.notebooks.create', 'New notebook')}\n </Button>\n );\n\n return (\n <PageLayout\n title={tr('cosmicintersection.pages.notebooks.title', 'Notebooks')}\n description={tr(\n 'cosmicintersection.pages.notebooks.description',\n 'Computational notebooks for running analyses over federated science datasets.',\n )}\n actions={actions}\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <label className=\"text-sm text-text-secondary\" htmlFor=\"notebook-status-filter\">\n Status\n </label>\n <Select\n value={statusFilter ?? ALL}\n onValueChange={(value) =>\n setStatusFilter(value === ALL ? undefined : (value as CosNotebookStatus))\n }\n >\n <SelectTrigger\n id=\"notebook-status-filter\"\n className=\"w-48\"\n aria-label=\"Filter by status\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All statuses</SelectItem>\n {STATUS_OPTIONS.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {loading ? (\n <LoadingState variant=\"rows\" rows={6} label={tr('cosmicintersection.pages.notebooks.loading', 'Loading notebooks…')} />\n ) : error ? (\n <ErrorState\n message={tr(\n 'cosmicintersection.pages.notebooks.error',\n 'We could not load your notebooks. Please try again.',\n )}\n onRetry={() => void refetch()}\n />\n ) : notebooks.length === 0 ? (\n <EmptyState\n icon={NotebookText}\n title={\n statusFilter\n ? tr(\n 'cosmicintersection.pages.notebooks.emptyFilterTitle',\n 'No notebooks match this filter.',\n )\n : tr('cosmicintersection.pages.notebooks.emptyTitle', 'No notebooks yet.')\n }\n description={\n statusFilter\n ? tr(\n 'cosmicintersection.pages.notebooks.emptyFilterDescription',\n 'Try a different status filter.',\n )\n : tr(\n 'cosmicintersection.pages.notebooks.emptyDescription',\n 'Create your first notebook to start analysing federated datasets.',\n )\n }\n action={\n statusFilter ? undefined : (\n <Button type=\"button\" onClick={() => navigate('notebooks/create')}>\n <Plus className=\"size-4\" />\n {tr('cosmicintersection.pages.notebooks.create', 'New notebook')}\n </Button>\n )\n }\n />\n ) : (\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>Status</TableHead>\n <TableHead>Runtime</TableHead>\n <TableHead className=\"text-right\">Datasets</TableHead>\n <TableHead>Sharing</TableHead>\n <TableHead>Tags</TableHead>\n <TableHead>Updated</TableHead>\n <TableHead className=\"w-12 text-right\">Actions</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {notebooks.map((notebook) => (\n <TableRow\n key={notebook.id}\n className=\"cursor-pointer\"\n onClick={() => navigate(`notebooks/${notebook.id}`)}\n >\n <TableCell className=\"font-medium text-text-primary\">\n {notebook.title}\n </TableCell>\n <TableCell>\n <StatusBadge status={notebook.status} />\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {notebook.runtime ?? '—'}\n </TableCell>\n <TableCell className=\"text-right tabular-nums text-text-secondary\">\n {notebook.datasetIds.length}\n </TableCell>\n <TableCell>\n {notebook.isShared ? (\n <Badge variant=\"secondary\" className=\"gap-1\">\n <Share2 className=\"size-3\" aria-hidden=\"true\" />\n {tr('cosmicintersection.common.shared', 'Shared')}\n </Badge>\n ) : (\n <span className=\"text-text-secondary\">\n {tr('cosmicintersection.common.private', 'Private')}\n </span>\n )}\n </TableCell>\n <TableCell>\n {notebook.tags.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {notebook.tags.slice(0, 3).map((tag) => (\n <Badge key={tag} variant=\"outline\">\n {tag}\n </Badge>\n ))}\n {notebook.tags.length > 3 ? (\n <span className=\"text-xs text-text-secondary\">\n +{notebook.tags.length - 3}\n </span>\n ) : null}\n </div>\n ) : (\n <span className=\"text-text-secondary\">—</span>\n )}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-text-secondary\">\n {formatDate(notebook.updatedAt)}\n </TableCell>\n <TableCell className=\"text-right\">\n <ActionMenu\n ariaLabel={`Actions for ${notebook.title}`}\n entityName={notebook.title}\n onView={() => navigate(`notebooks/${notebook.id}`)}\n onEdit={() => navigate(`notebooks/${notebook.id}/edit`)}\n onDelete={\n notebook.status === 'ARCHIVED'\n ? undefined\n : () => handleArchive(notebook)\n }\n deleteLabel={tr('cosmicintersection.pages.notebooks.archive', 'Archive notebook')}\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 {notebooks.map((notebook) => (\n <li\n key={notebook.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 <button\n type=\"button\"\n onClick={() => navigate(`notebooks/${notebook.id}`)}\n className=\"min-w-0 flex-1 space-y-2 text-left\"\n >\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"truncate font-medium text-text-primary\">\n {notebook.title}\n </span>\n <StatusBadge status={notebook.status} />\n </div>\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-text-secondary\">\n <span>Runtime: {notebook.runtime ?? '—'}</span>\n <span className=\"tabular-nums\">\n {notebook.datasetIds.length} dataset\n {notebook.datasetIds.length === 1 ? '' : 's'}\n </span>\n {notebook.isShared ? (\n <span className=\"inline-flex items-center gap-1\">\n <Share2 className=\"size-3\" aria-hidden=\"true\" />\n {tr('cosmicintersection.common.shared', 'Shared')}\n </span>\n ) : (\n <span>{tr('cosmicintersection.common.private', 'Private')}</span>\n )}\n <span>Updated {formatDate(notebook.updatedAt)}</span>\n </div>\n {notebook.tags.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {notebook.tags.slice(0, 4).map((tag) => (\n <Badge key={tag} variant=\"outline\">\n {tag}\n </Badge>\n ))}\n {notebook.tags.length > 4 ? (\n <span className=\"text-xs text-text-secondary\">\n +{notebook.tags.length - 4}\n </span>\n ) : null}\n </div>\n ) : null}\n </button>\n <ActionMenu\n ariaLabel={`Actions for ${notebook.title}`}\n entityName={notebook.title}\n onView={() => navigate(`notebooks/${notebook.id}`)}\n onEdit={() => navigate(`notebooks/${notebook.id}/edit`)}\n onDelete={\n notebook.status === 'ARCHIVED' ? undefined : () => handleArchive(notebook)\n }\n deleteLabel={tr('cosmicintersection.pages.notebooks.archive', 'Archive notebook')}\n />\n </div>\n </li>\n ))}\n </ul>\n </>\n )}\n\n {!loading && !error && notebooks.length > 0 ? (\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={notebooks.length}\n hasMore={pageInfo?.hasMore ?? false}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel={tr('cosmicintersection.pages.notebooks.itemLabel', 'notebooks')}\n loading={loading}\n />\n ) : null}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"NotebooksPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/notebooks/NotebooksPage.tsx"],"sourcesContent":["/**\n * NotebooksPage — paginated list of analysis notebooks.\n *\n * Notebooks are computational documents (title, runtime, environment, linked\n * datasets) that scientists run analyses inside. This surface lists them from\n * `cosListNotebooks` with a status filter, per-row View / Edit / Archive\n * actions, and offset pagination. Every value here comes from the backend —\n * there is no fabricated data.\n */\nimport { useState } from 'react';\nimport { NotebookText, Plus, Share2 } from 'lucide-react';\nimport {\n Badge,\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 {\n useCosArchiveNotebookMutation,\n useCosNotebooksQuery,\n type CosNotebook,\n type CosNotebookStatus,\n} from '../../api/cosmicApi';\nimport {\n ActionMenu,\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n Pagination,\n StatusBadge,\n usePagination,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\nimport { useTr } from '../../../shared/hooks/useTr';\n\nconst STATUS_OPTIONS: { value: CosNotebookStatus; label: string }[] = [\n { value: 'DRAFT', label: 'Draft' },\n { value: 'RUNNING', label: 'Running' },\n { value: 'PUBLISHED', label: 'Published' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nconst ALL = 'ALL';\n\nfunction formatDate(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) {\n return '—';\n }\n return date.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });\n}\n\nexport default function NotebooksPage() {\n const { navigate } = useCosmicIntersection();\n const tr = useTr();\n const [statusFilter, setStatusFilter] = useState<CosNotebookStatus | undefined>(undefined);\n const pagination = usePagination({ pageSize: 50, resetKey: statusFilter });\n\n const { data, loading, error, refetch } = useCosNotebooksQuery({\n status: statusFilter,\n pagination: { limit: pagination.limit, offset: pagination.offset },\n });\n const [archiveNotebook] = useCosArchiveNotebookMutation();\n\n const notebooks = data?.cosListNotebooks.items ?? [];\n const pageInfo = data?.cosListNotebooks.pageInfo;\n\n const handleArchive = async (notebook: CosNotebook) => {\n const response = await archiveNotebook({ variables: { id: notebook.id } });\n if (response.error) {\n // onError already surfaced the toast; rethrow so the confirm dialog stays open.\n throw new Error(response.error.message);\n }\n if (!response.data?.cosArchiveNotebook) {\n notify.error(\n tr('cosmicintersection.pages.notebooks.archiveError', 'Could not archive notebook'),\n tr(\n 'cosmicintersection.pages.notebooks.archiveErrorDescription',\n 'The server did not confirm the change.'\n )\n );\n throw new Error('Archive not confirmed');\n }\n await refetch();\n notify.success(\n tr('cosmicintersection.pages.notebooks.archiveSuccess', 'Notebook archived'),\n tr(\n 'cosmicintersection.pages.notebooks.archiveSuccessDescription',\n '“{{name}}” was archived.',\n { name: notebook.title }\n )\n );\n };\n\n const actions = (\n <Button type=\"button\" onClick={() => navigate('notebooks/create')}>\n <Plus className=\"size-4\" />\n {tr('cosmicintersection.pages.notebooks.create', 'New notebook')}\n </Button>\n );\n\n return (\n <PageLayout\n title={tr('cosmicintersection.pages.notebooks.title', 'Notebooks')}\n description={tr(\n 'cosmicintersection.pages.notebooks.description',\n 'Computational notebooks for running analyses over federated science datasets.'\n )}\n actions={actions}\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <label className=\"text-sm text-text-secondary\" htmlFor=\"notebook-status-filter\">\n Status\n </label>\n <Select\n value={statusFilter ?? ALL}\n onValueChange={(value) =>\n setStatusFilter(value === ALL ? undefined : (value as CosNotebookStatus))\n }\n >\n <SelectTrigger\n id=\"notebook-status-filter\"\n className=\"w-48\"\n aria-label=\"Filter by status\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All statuses</SelectItem>\n {STATUS_OPTIONS.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {loading ? (\n <LoadingState\n variant=\"rows\"\n rows={6}\n label={tr('cosmicintersection.pages.notebooks.loading', 'Loading notebooks…')}\n />\n ) : error ? (\n <ErrorState\n message={tr(\n 'cosmicintersection.pages.notebooks.error',\n 'We could not load your notebooks. Please try again.'\n )}\n onRetry={() => void refetch()}\n />\n ) : notebooks.length === 0 ? (\n <EmptyState\n icon={NotebookText}\n title={\n statusFilter\n ? tr(\n 'cosmicintersection.pages.notebooks.emptyFilterTitle',\n 'No notebooks match this filter.'\n )\n : tr('cosmicintersection.pages.notebooks.emptyTitle', 'No notebooks yet.')\n }\n description={\n statusFilter\n ? tr(\n 'cosmicintersection.pages.notebooks.emptyFilterDescription',\n 'Try a different status filter.'\n )\n : tr(\n 'cosmicintersection.pages.notebooks.emptyDescription',\n 'Create your first notebook to start analysing federated datasets.'\n )\n }\n action={\n statusFilter ? undefined : (\n <Button type=\"button\" onClick={() => navigate('notebooks/create')}>\n <Plus className=\"size-4\" />\n {tr('cosmicintersection.pages.notebooks.create', 'New notebook')}\n </Button>\n )\n }\n />\n ) : (\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>Status</TableHead>\n <TableHead>Runtime</TableHead>\n <TableHead className=\"text-right\">Datasets</TableHead>\n <TableHead>Sharing</TableHead>\n <TableHead>Tags</TableHead>\n <TableHead>Updated</TableHead>\n <TableHead className=\"w-12 text-right\">Actions</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {notebooks.map((notebook) => (\n <TableRow\n key={notebook.id}\n className=\"cursor-pointer\"\n onClick={() => navigate(`notebooks/${notebook.id}`)}\n >\n <TableCell className=\"font-medium text-text-primary\">\n {notebook.title}\n </TableCell>\n <TableCell>\n <StatusBadge status={notebook.status} />\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {notebook.runtime ?? '—'}\n </TableCell>\n <TableCell className=\"text-right tabular-nums text-text-secondary\">\n {notebook.datasetIds.length}\n </TableCell>\n <TableCell>\n {notebook.isShared ? (\n <Badge variant=\"secondary\" className=\"gap-1\">\n <Share2 className=\"size-3\" aria-hidden=\"true\" />\n {tr('cosmicintersection.common.shared', 'Shared')}\n </Badge>\n ) : (\n <span className=\"text-text-secondary\">\n {tr('cosmicintersection.common.private', 'Private')}\n </span>\n )}\n </TableCell>\n <TableCell>\n {notebook.tags.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {notebook.tags.slice(0, 3).map((tag) => (\n <Badge key={tag} variant=\"outline\">\n {tag}\n </Badge>\n ))}\n {notebook.tags.length > 3 ? (\n <span className=\"text-xs text-text-secondary\">\n +{notebook.tags.length - 3}\n </span>\n ) : null}\n </div>\n ) : (\n <span className=\"text-text-secondary\">—</span>\n )}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-text-secondary\">\n {formatDate(notebook.updatedAt)}\n </TableCell>\n <TableCell className=\"text-right\">\n <ActionMenu\n ariaLabel={`Actions for ${notebook.title}`}\n entityName={notebook.title}\n onView={() => navigate(`notebooks/${notebook.id}`)}\n onEdit={() => navigate(`notebooks/${notebook.id}/edit`)}\n onDelete={\n notebook.status === 'ARCHIVED'\n ? undefined\n : () => handleArchive(notebook)\n }\n deleteLabel={tr(\n 'cosmicintersection.pages.notebooks.archive',\n 'Archive notebook'\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 {notebooks.map((notebook) => (\n <li\n key={notebook.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 <button\n type=\"button\"\n onClick={() => navigate(`notebooks/${notebook.id}`)}\n className=\"min-w-0 flex-1 space-y-2 text-left\"\n >\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"truncate font-medium text-text-primary\">\n {notebook.title}\n </span>\n <StatusBadge status={notebook.status} />\n </div>\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-text-secondary\">\n <span>Runtime: {notebook.runtime ?? '—'}</span>\n <span className=\"tabular-nums\">\n {notebook.datasetIds.length} dataset\n {notebook.datasetIds.length === 1 ? '' : 's'}\n </span>\n {notebook.isShared ? (\n <span className=\"inline-flex items-center gap-1\">\n <Share2 className=\"size-3\" aria-hidden=\"true\" />\n {tr('cosmicintersection.common.shared', 'Shared')}\n </span>\n ) : (\n <span>{tr('cosmicintersection.common.private', 'Private')}</span>\n )}\n <span>Updated {formatDate(notebook.updatedAt)}</span>\n </div>\n {notebook.tags.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {notebook.tags.slice(0, 4).map((tag) => (\n <Badge key={tag} variant=\"outline\">\n {tag}\n </Badge>\n ))}\n {notebook.tags.length > 4 ? (\n <span className=\"text-xs text-text-secondary\">\n +{notebook.tags.length - 4}\n </span>\n ) : null}\n </div>\n ) : null}\n </button>\n <ActionMenu\n ariaLabel={`Actions for ${notebook.title}`}\n entityName={notebook.title}\n onView={() => navigate(`notebooks/${notebook.id}`)}\n onEdit={() => navigate(`notebooks/${notebook.id}/edit`)}\n onDelete={\n notebook.status === 'ARCHIVED' ? undefined : () => handleArchive(notebook)\n }\n deleteLabel={tr(\n 'cosmicintersection.pages.notebooks.archive',\n 'Archive notebook'\n )}\n />\n </div>\n </li>\n ))}\n </ul>\n </>\n )}\n\n {!loading && !error && notebooks.length > 0 ? (\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={notebooks.length}\n hasMore={pageInfo?.hasMore ?? false}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel={tr('cosmicintersection.pages.notebooks.itemLabel', 'notebooks')}\n loading={loading}\n />\n ) : null}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"PipelinesPage.d.ts","sourceRoot":"","sources":["../../../../src/cosmicintersection/pages/pipelines/PipelinesPage.tsx"],"names":[],"mappings":"AAmDA,MAAM,CAAC,OAAO,UAAU,aAAa,4CA8UpC"}
1
+ {"version":3,"file":"PipelinesPage.d.ts","sourceRoot":"","sources":["../../../../src/cosmicintersection/pages/pipelines/PipelinesPage.tsx"],"names":[],"mappings":"AAmDA,MAAM,CAAC,OAAO,UAAU,aAAa,4CA0XpC"}