@burdenoff/microfe-cosmicintersection 2026.716.1 → 2026.720.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cosmicintersection/CosmicIntersectionRoot.js.map +1 -1
- package/dist/cosmicintersection/CosmicIntersectionRoutes.js.map +1 -1
- package/dist/cosmicintersection/api/cosmicApi.d.ts.map +1 -1
- package/dist/cosmicintersection/api/cosmicApi.js.map +1 -1
- package/dist/cosmicintersection/components/ActionMenu.js.map +1 -1
- package/dist/cosmicintersection/components/ActivityFeedPanel.js.map +1 -1
- package/dist/cosmicintersection/components/DiscussionPanel.js.map +1 -1
- package/dist/cosmicintersection/components/EmptyState.js.map +1 -1
- package/dist/cosmicintersection/components/EntityFormShell.js.map +1 -1
- package/dist/cosmicintersection/components/EntityTagsField.js.map +1 -1
- package/dist/cosmicintersection/components/ErrorState.js.map +1 -1
- package/dist/cosmicintersection/components/FacetPanel.js.map +1 -1
- package/dist/cosmicintersection/components/FileUploadField.js.map +1 -1
- package/dist/cosmicintersection/components/LineageGraph.d.ts.map +1 -1
- package/dist/cosmicintersection/components/LineageGraph.js.map +1 -1
- package/dist/cosmicintersection/components/LoadingState.js.map +1 -1
- package/dist/cosmicintersection/components/PageLayout.js.map +1 -1
- package/dist/cosmicintersection/components/Pagination.js.map +1 -1
- package/dist/cosmicintersection/components/ResultsChart.js.map +1 -1
- package/dist/cosmicintersection/components/SafeLink.js.map +1 -1
- package/dist/cosmicintersection/components/StatusBadge.js.map +1 -1
- package/dist/cosmicintersection/components/StepDAG.js.map +1 -1
- package/dist/cosmicintersection/context/CosmicIntersectionContext.js.map +1 -1
- package/dist/cosmicintersection/lib/mutationResult.js.map +1 -1
- package/dist/cosmicintersection/lib/toast.js.map +1 -1
- package/dist/cosmicintersection/pages/access/AccessPage.js.map +1 -1
- package/dist/cosmicintersection/pages/analyses/AnalysesPage.js.map +1 -1
- package/dist/cosmicintersection/pages/collections/CollectionDetailPage.js.map +1 -1
- package/dist/cosmicintersection/pages/collections/CollectionFormPage.js.map +1 -1
- package/dist/cosmicintersection/pages/collections/CollectionsPage.js.map +1 -1
- package/dist/cosmicintersection/pages/dashboard/DashboardPage.js.map +1 -1
- package/dist/cosmicintersection/pages/datasets/DatasetDetailPage.js.map +1 -1
- package/dist/cosmicintersection/pages/datasets/DatasetFormPage.js.map +1 -1
- package/dist/cosmicintersection/pages/datasets/DatasetsPage.js.map +1 -1
- package/dist/cosmicintersection/pages/discovery/DiscoveryPage.js.map +1 -1
- package/dist/cosmicintersection/pages/lineage/LineagePage.js.map +1 -1
- package/dist/cosmicintersection/pages/notebooks/NotebookDetailPage.js.map +1 -1
- package/dist/cosmicintersection/pages/notebooks/NotebookFormPage.js.map +1 -1
- package/dist/cosmicintersection/pages/notebooks/NotebooksPage.js.map +1 -1
- package/dist/cosmicintersection/pages/pipelines/PipelineDetailPage.js.map +1 -1
- package/dist/cosmicintersection/pages/pipelines/PipelineFormPage.js.map +1 -1
- package/dist/cosmicintersection/pages/pipelines/PipelinesPage.js.map +1 -1
- package/dist/cosmicintersection/pages/releases/ReleaseFormPage.js.map +1 -1
- package/dist/cosmicintersection/pages/releases/ReleasesPage.js.map +1 -1
- package/dist/cosmicintersection/pages/runs/RunDetailPage.js.map +1 -1
- package/dist/cosmicintersection/pages/runs/RunsPage.js.map +1 -1
- package/dist/cosmicintersection/pages/sources/SourceDetailPage.js.map +1 -1
- package/dist/cosmicintersection/pages/sources/SourceFormPage.js.map +1 -1
- package/dist/cosmicintersection/pages/sources/SourcesPage.js.map +1 -1
- package/dist/cosmicintersection/utils/cn.js.map +1 -1
- package/dist/node_modules/clsx/dist/clsx.js.map +1 -1
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1 -1
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -1
- package/dist/services/createSandboxAssistantTransport.js.map +1 -1
- package/dist/utils/pageContext.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StepDAG.js","names":[],"sources":["../../../src/cosmicintersection/components/StepDAG.tsx"],"sourcesContent":["/**\n * StepDAG — a lightweight, dependency-free step-flow visualization.\n *\n * Renders an ordered list of pipeline steps as connected rounded nodes: each\n * node shows a per-kind icon + label (and an optional custom step name), and an\n * optional run status tint. Nodes are joined by custom inline-SVG arrow\n * connectors — horizontal on md+ screens, vertical (stacked) on small screens\n * so it never forces a horizontal-scroll.\n *\n * Colour comes only from the semantic token families (bg-bg-*, text-text-*,\n * border-border-*, status-*) — never a raw palette colour — matching mockups\n * 11 & 14.\n */\nimport { Fragment } from 'react';\nimport type { LucideIcon } from 'lucide-react';\nimport { Bell, Database, Gauge, Shuffle, SlidersHorizontal, Upload } from 'lucide-react';\n\nimport type { CosRunStatus, CosStepKind } from '../api/cosmicApi';\nimport { formatEnumLabel } from './StatusBadge';\n\nexport interface StepDAGStep {\n /** Optional human-authored step name; shown under the kind label when it differs. */\n name?: string | null;\n kind: CosStepKind;\n /** Optional run status — tints the node when present. */\n status?: CosRunStatus | null;\n}\n\nexport interface StepDAGProps {\n steps: StepDAGStep[];\n className?: string;\n}\n\nconst KIND_META: Record<CosStepKind, { icon: LucideIcon }> = {\n INGEST: { icon: Database },\n CALIBRATE: { icon: SlidersHorizontal },\n TRANSFORM: { icon: Shuffle },\n ANALYZE: { icon: Gauge },\n EXPORT: { icon: Upload },\n NOTIFY: { icon: Bell },\n};\n\ninterface Accent {\n /** Node border. */\n border: string;\n /** Icon chip background + text. */\n icon: string;\n /** Optional focus ring (used to flag an in-flight node). */\n ring: string;\n}\n\nconst DEFAULT_ACCENT: Accent = {\n border: 'border-border-default',\n icon: 'bg-bg-sunken text-text-secondary',\n ring: '',\n};\n\nconst STATUS_ACCENT: Partial<Record<CosRunStatus, Accent>> = {\n SUCCEEDED: {\n border: 'border-status-success-border',\n icon: 'bg-status-success-bg-subtle text-status-success-text',\n ring: '',\n },\n RUNNING: {\n border: 'border-status-warning-border',\n icon: 'bg-status-warning-bg-subtle text-status-warning-text',\n ring: 'ring-2 ring-status-warning-border',\n },\n FAILED: {\n border: 'border-status-error-border',\n icon: 'bg-status-error-bg-subtle text-status-error-text',\n ring: '',\n },\n QUEUED: {\n border: 'border-status-info-border',\n icon: 'bg-status-info-bg-subtle text-status-info-text',\n ring: '',\n },\n CANCELLED: DEFAULT_ACCENT,\n};\n\n/** Custom inline-SVG connector: a line + arrowhead. Points right on md+, down below. */\nfunction StepConnector() {\n return (\n <span\n aria-hidden=\"true\"\n className=\"flex shrink-0 items-center justify-center text-border-strong\"\n >\n <svg\n viewBox=\"0 0 40 12\"\n className=\"h-4 w-6 rotate-90 md:h-3 md:w-9 md:rotate-0\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.75}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <line x1=\"2\" y1=\"6\" x2=\"33\" y2=\"6\" />\n <path d=\"M28 2 L34 6 L28 10\" />\n </svg>\n </span>\n );\n}\n\nfunction StepNode({ step, index }: { step: StepDAGStep; index: number }) {\n const meta = KIND_META[step.kind];\n const Icon = meta.icon;\n const accent = (step.status && STATUS_ACCENT[step.status]) || DEFAULT_ACCENT;\n const label = formatEnumLabel(step.kind);\n const showName = Boolean(\n step.name && step.name.trim() && step.name.trim().toLowerCase() !== label.toLowerCase()\n );\n\n return (\n <div\n role=\"listitem\"\n className={`flex w-full items-center gap-3 rounded-card border bg-bg-elevated px-3.5 py-3 md:w-auto md:min-w-[8.5rem] ${accent.border} ${accent.ring}`}\n >\n <span\n className={`flex size-9 shrink-0 items-center justify-center rounded-lg ${accent.icon}`}\n >\n <Icon className=\"size-4\" aria-hidden=\"true\" />\n </span>\n <span className=\"flex min-w-0 flex-col\">\n <span className=\"flex items-baseline gap-1.5\">\n <span className=\"text-[0.65rem] font-semibold tabular-nums text-text-secondary\">\n {index + 1}\n </span>\n <span className=\"truncate text-sm font-medium text-text-primary\">{label}</span>\n </span>\n {showName ? (\n <span className=\"truncate text-xs text-text-secondary\">{step.name}</span>\n ) : null}\n </span>\n </div>\n );\n}\n\n/**\n * Horizontal (md+) / vertical (mobile) step-flow diagram. Presentational only —\n * callers decide where the step list comes from (pipeline definition or a run).\n */\nexport function StepDAG({ steps, className }: StepDAGProps) {\n if (steps.length === 0) {\n return null;\n }\n return (\n <div\n role=\"list\"\n aria-label=\"Pipeline step flow\"\n className={`flex flex-col items-stretch gap-2 md:flex-row md:flex-wrap md:items-center ${className ?? ''}`}\n >\n {steps.map((step, index) => (\n <Fragment key={`${step.kind}-${index}`}>\n <StepNode step={step} index={index} />\n {index < steps.length - 1 ? <StepConnector /> : null}\n </Fragment>\n ))}\n </div>\n );\n}\n\nexport default StepDAG;\n"],"mappings":";;;;;AAiCA,IAAM,IAAuD;CAC3D,QAAQ,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"StepDAG.js","names":[],"sources":["../../../src/cosmicintersection/components/StepDAG.tsx"],"sourcesContent":["/**\n * StepDAG — a lightweight, dependency-free step-flow visualization.\n *\n * Renders an ordered list of pipeline steps as connected rounded nodes: each\n * node shows a per-kind icon + label (and an optional custom step name), and an\n * optional run status tint. Nodes are joined by custom inline-SVG arrow\n * connectors — horizontal on md+ screens, vertical (stacked) on small screens\n * so it never forces a horizontal-scroll.\n *\n * Colour comes only from the semantic token families (bg-bg-*, text-text-*,\n * border-border-*, status-*) — never a raw palette colour — matching mockups\n * 11 & 14.\n */\nimport { Fragment } from 'react';\nimport type { LucideIcon } from 'lucide-react';\nimport { Bell, Database, Gauge, Shuffle, SlidersHorizontal, Upload } from 'lucide-react';\n\nimport type { CosRunStatus, CosStepKind } from '../api/cosmicApi';\nimport { formatEnumLabel } from './StatusBadge';\n\nexport interface StepDAGStep {\n /** Optional human-authored step name; shown under the kind label when it differs. */\n name?: string | null;\n kind: CosStepKind;\n /** Optional run status — tints the node when present. */\n status?: CosRunStatus | null;\n}\n\nexport interface StepDAGProps {\n steps: StepDAGStep[];\n className?: string;\n}\n\nconst KIND_META: Record<CosStepKind, { icon: LucideIcon }> = {\n INGEST: { icon: Database },\n CALIBRATE: { icon: SlidersHorizontal },\n TRANSFORM: { icon: Shuffle },\n ANALYZE: { icon: Gauge },\n EXPORT: { icon: Upload },\n NOTIFY: { icon: Bell },\n};\n\ninterface Accent {\n /** Node border. */\n border: string;\n /** Icon chip background + text. */\n icon: string;\n /** Optional focus ring (used to flag an in-flight node). */\n ring: string;\n}\n\nconst DEFAULT_ACCENT: Accent = {\n border: 'border-border-default',\n icon: 'bg-bg-sunken text-text-secondary',\n ring: '',\n};\n\nconst STATUS_ACCENT: Partial<Record<CosRunStatus, Accent>> = {\n SUCCEEDED: {\n border: 'border-status-success-border',\n icon: 'bg-status-success-bg-subtle text-status-success-text',\n ring: '',\n },\n RUNNING: {\n border: 'border-status-warning-border',\n icon: 'bg-status-warning-bg-subtle text-status-warning-text',\n ring: 'ring-2 ring-status-warning-border',\n },\n FAILED: {\n border: 'border-status-error-border',\n icon: 'bg-status-error-bg-subtle text-status-error-text',\n ring: '',\n },\n QUEUED: {\n border: 'border-status-info-border',\n icon: 'bg-status-info-bg-subtle text-status-info-text',\n ring: '',\n },\n CANCELLED: DEFAULT_ACCENT,\n};\n\n/** Custom inline-SVG connector: a line + arrowhead. Points right on md+, down below. */\nfunction StepConnector() {\n return (\n <span\n aria-hidden=\"true\"\n className=\"flex shrink-0 items-center justify-center text-border-strong\"\n >\n <svg\n viewBox=\"0 0 40 12\"\n className=\"h-4 w-6 rotate-90 md:h-3 md:w-9 md:rotate-0\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.75}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <line x1=\"2\" y1=\"6\" x2=\"33\" y2=\"6\" />\n <path d=\"M28 2 L34 6 L28 10\" />\n </svg>\n </span>\n );\n}\n\nfunction StepNode({ step, index }: { step: StepDAGStep; index: number }) {\n const meta = KIND_META[step.kind];\n const Icon = meta.icon;\n const accent = (step.status && STATUS_ACCENT[step.status]) || DEFAULT_ACCENT;\n const label = formatEnumLabel(step.kind);\n const showName = Boolean(\n step.name && step.name.trim() && step.name.trim().toLowerCase() !== label.toLowerCase()\n );\n\n return (\n <div\n role=\"listitem\"\n className={`flex w-full items-center gap-3 rounded-card border bg-bg-elevated px-3.5 py-3 md:w-auto md:min-w-[8.5rem] ${accent.border} ${accent.ring}`}\n >\n <span\n className={`flex size-9 shrink-0 items-center justify-center rounded-lg ${accent.icon}`}\n >\n <Icon className=\"size-4\" aria-hidden=\"true\" />\n </span>\n <span className=\"flex min-w-0 flex-col\">\n <span className=\"flex items-baseline gap-1.5\">\n <span className=\"text-[0.65rem] font-semibold tabular-nums text-text-secondary\">\n {index + 1}\n </span>\n <span className=\"truncate text-sm font-medium text-text-primary\">{label}</span>\n </span>\n {showName ? (\n <span className=\"truncate text-xs text-text-secondary\">{step.name}</span>\n ) : null}\n </span>\n </div>\n );\n}\n\n/**\n * Horizontal (md+) / vertical (mobile) step-flow diagram. Presentational only —\n * callers decide where the step list comes from (pipeline definition or a run).\n */\nexport function StepDAG({ steps, className }: StepDAGProps) {\n if (steps.length === 0) {\n return null;\n }\n return (\n <div\n role=\"list\"\n aria-label=\"Pipeline step flow\"\n className={`flex flex-col items-stretch gap-2 md:flex-row md:flex-wrap md:items-center ${className ?? ''}`}\n >\n {steps.map((step, index) => (\n <Fragment key={`${step.kind}-${index}`}>\n <StepNode step={step} index={index} />\n {index < steps.length - 1 ? <StepConnector /> : null}\n </Fragment>\n ))}\n </div>\n );\n}\n\nexport default StepDAG;\n"],"mappings":";;;;;AAiCA,IAAM,IAAuD;CAC3D,QAAQ,EAAE,MAAM,EAAS;CACzB,WAAW,EAAE,MAAM,EAAkB;CACrC,WAAW,EAAE,MAAM,EAAQ;CAC3B,SAAS,EAAE,MAAM,EAAM;CACvB,QAAQ,EAAE,MAAM,EAAO;CACvB,QAAQ,EAAE,MAAM,EAAK;AACvB,GAWM,IAAyB;CAC7B,QAAQ;CACR,MAAM;CACN,MAAM;AACR,GAEM,IAAuD;CAC3D,WAAW;EACT,QAAQ;EACR,MAAM;EACN,MAAM;CACR;CACA,SAAS;EACP,QAAQ;EACR,MAAM;EACN,MAAM;CACR;CACA,QAAQ;EACN,QAAQ;EACR,MAAM;EACN,MAAM;CACR;CACA,QAAQ;EACN,QAAQ;EACR,MAAM;EACN,MAAM;CACR;CACA,WAAW;AACb;AAGA,SAAS,IAAgB;CACvB,OACE,kBAAC,QAAD;EACE,eAAY;EACZ,WAAU;YAEV,kBAAC,OAAD;GACE,SAAQ;GACR,WAAU;GACV,MAAK;GACL,QAAO;GACP,aAAa;GACb,eAAc;GACd,gBAAe;aAPjB,CASE,kBAAC,QAAD;IAAM,IAAG;IAAI,IAAG;IAAI,IAAG;IAAK,IAAG;GAAK,CAAA,GACpC,kBAAC,QAAD,EAAM,GAAE,qBAAsB,CAAA,CAC3B;;CACD,CAAA;AAEV;AAEA,SAAS,EAAS,EAAE,SAAM,YAA+C;CAEvE,IAAM,IADO,EAAU,EAAK,MACV,MACZ,IAAU,EAAK,UAAU,EAAc,EAAK,WAAY,GACxD,IAAQ,EAAgB,EAAK,IAAI,GACjC,IAAW,GACf,EAAK,QAAQ,EAAK,KAAK,KAAK,KAAK,EAAK,KAAK,KAAK,EAAE,YAAY,MAAM,EAAM,YAAY;CAGxF,OACE,kBAAC,OAAD;EACE,MAAK;EACL,WAAW,6GAA6G,EAAO,OAAO,GAAG,EAAO;YAFlJ,CAIE,kBAAC,QAAD;GACE,WAAW,+DAA+D,EAAO;aAEjF,kBAAC,GAAD;IAAM,WAAU;IAAS,eAAY;GAAQ,CAAA;EACzC,CAAA,GACN,kBAAC,QAAD;GAAM,WAAU;aAAhB,CACE,kBAAC,QAAD;IAAM,WAAU;cAAhB,CACE,kBAAC,QAAD;KAAM,WAAU;eACb,IAAQ;IACL,CAAA,GACN,kBAAC,QAAD;KAAM,WAAU;eAAkD;IAAY,CAAA,CAC1E;OACL,IACC,kBAAC,QAAD;IAAM,WAAU;cAAwC,EAAK;GAAW,CAAA,IACtE,IACA;IACH;;AAET;AAMA,SAAgB,EAAQ,EAAE,UAAO,gBAA2B;CAI1D,OAHI,EAAM,WAAW,IACZ,OAGP,kBAAC,OAAD;EACE,MAAK;EACL,cAAW;EACX,WAAW,8EAA8E,KAAa;YAErG,EAAM,KAAK,GAAM,MAChB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;GAAgB;GAAa;EAAQ,CAAA,GACpC,IAAQ,EAAM,SAAS,IAAI,kBAAC,GAAD,CAAgB,CAAA,IAAI,IACxC,EAAA,GAHK,GAAG,EAAK,KAAK,GAAG,GAGrB,CACX;CACE,CAAA;AAET"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CosmicIntersectionContext.js","names":[],"sources":["../../../src/cosmicintersection/context/CosmicIntersectionContext.tsx"],"sourcesContent":["/**\n * CosmicIntersectionContext\n *\n * Provides basePath-aware navigation and shared props throughout the\n * CosmicIntersection microfrontend. Uses the `navigate` function injected by the\n * host app shell — no react-router hooks are needed here, so the same tree works\n * whether the shell drives routing or the local dev harness does.\n */\n\nimport { createContext, useCallback, useContext, useMemo, type ReactNode } from 'react';\nimport type { CosmicIntersectionRootProps } from '../types';\n\ninterface CosmicIntersectionContextValue {\n basePath: string;\n navigate: (path: string) => void;\n currentUser: CosmicIntersectionRootProps['currentUser'];\n workspaceId?: string;\n tenantId?: string;\n apiGatewayUrl: string;\n authToken: string;\n}\n\nconst CosmicIntersectionContext = createContext<CosmicIntersectionContextValue | null>(null);\n\ninterface CosmicIntersectionProviderProps {\n children: ReactNode;\n props: CosmicIntersectionRootProps;\n}\n\nexport function CosmicIntersectionProvider({ children, props }: CosmicIntersectionProviderProps) {\n const {\n basePath = '',\n navigate: hostNavigate,\n currentUser,\n workspaceId,\n tenantId,\n apiGatewayUrl,\n authToken,\n } = props;\n\n // Normalize basePath (strip trailing slash) so joins never produce `//`.\n const normalizedBasePath = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;\n\n // basePath-aware navigation built on the host's navigate function.\n const navigate = useCallback(\n (path: string) => {\n if (!normalizedBasePath || normalizedBasePath === '/') {\n hostNavigate(path);\n return;\n }\n if (path.startsWith(normalizedBasePath)) {\n hostNavigate(path);\n return;\n }\n const fullPath = path.startsWith('/')\n ? `${normalizedBasePath}${path}`\n : `${normalizedBasePath}/${path}`;\n hostNavigate(fullPath);\n },\n [normalizedBasePath, hostNavigate]\n );\n\n const value = useMemo(\n () => ({\n basePath: normalizedBasePath,\n navigate,\n currentUser,\n workspaceId,\n tenantId,\n apiGatewayUrl: apiGatewayUrl || '',\n authToken: authToken || '',\n }),\n [normalizedBasePath, navigate, currentUser, workspaceId, tenantId, apiGatewayUrl, authToken]\n );\n\n return (\n <CosmicIntersectionContext.Provider value={value}>\n {children}\n </CosmicIntersectionContext.Provider>\n );\n}\n\nexport function useCosmicIntersection() {\n const context = useContext(CosmicIntersectionContext);\n if (!context) {\n throw new Error('useCosmicIntersection must be used within a CosmicIntersectionProvider');\n }\n return context;\n}\n\n/**\n * Hook for basePath-aware navigation. Returns a navigate function that\n * automatically prepends the microfrontend basePath. Falls back to a\n * `window.location`-based navigate if used outside the provider.\n */\nexport function useCosmicIntersectionNavigate() {\n const context = useContext(CosmicIntersectionContext);\n\n const fallbackNavigate = useCallback((path: string) => {\n const currentPath = window.location.pathname;\n const mfeMatch = currentPath.match(/^(\\/cosmicintersection)/);\n const basePath = mfeMatch ? mfeMatch[1] : '';\n\n let fullPath = path;\n if (basePath && !path.startsWith(basePath)) {\n fullPath = path.startsWith('/') ? `${basePath}${path}` : `${basePath}/${path}`;\n }\n window.location.href = fullPath;\n }, []);\n\n return context?.navigate ?? fallbackNavigate;\n}\n"],"mappings":";;;AAsBA,IAAM,IAA4B,EAAqD,
|
|
1
|
+
{"version":3,"file":"CosmicIntersectionContext.js","names":[],"sources":["../../../src/cosmicintersection/context/CosmicIntersectionContext.tsx"],"sourcesContent":["/**\n * CosmicIntersectionContext\n *\n * Provides basePath-aware navigation and shared props throughout the\n * CosmicIntersection microfrontend. Uses the `navigate` function injected by the\n * host app shell — no react-router hooks are needed here, so the same tree works\n * whether the shell drives routing or the local dev harness does.\n */\n\nimport { createContext, useCallback, useContext, useMemo, type ReactNode } from 'react';\nimport type { CosmicIntersectionRootProps } from '../types';\n\ninterface CosmicIntersectionContextValue {\n basePath: string;\n navigate: (path: string) => void;\n currentUser: CosmicIntersectionRootProps['currentUser'];\n workspaceId?: string;\n tenantId?: string;\n apiGatewayUrl: string;\n authToken: string;\n}\n\nconst CosmicIntersectionContext = createContext<CosmicIntersectionContextValue | null>(null);\n\ninterface CosmicIntersectionProviderProps {\n children: ReactNode;\n props: CosmicIntersectionRootProps;\n}\n\nexport function CosmicIntersectionProvider({ children, props }: CosmicIntersectionProviderProps) {\n const {\n basePath = '',\n navigate: hostNavigate,\n currentUser,\n workspaceId,\n tenantId,\n apiGatewayUrl,\n authToken,\n } = props;\n\n // Normalize basePath (strip trailing slash) so joins never produce `//`.\n const normalizedBasePath = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;\n\n // basePath-aware navigation built on the host's navigate function.\n const navigate = useCallback(\n (path: string) => {\n if (!normalizedBasePath || normalizedBasePath === '/') {\n hostNavigate(path);\n return;\n }\n if (path.startsWith(normalizedBasePath)) {\n hostNavigate(path);\n return;\n }\n const fullPath = path.startsWith('/')\n ? `${normalizedBasePath}${path}`\n : `${normalizedBasePath}/${path}`;\n hostNavigate(fullPath);\n },\n [normalizedBasePath, hostNavigate]\n );\n\n const value = useMemo(\n () => ({\n basePath: normalizedBasePath,\n navigate,\n currentUser,\n workspaceId,\n tenantId,\n apiGatewayUrl: apiGatewayUrl || '',\n authToken: authToken || '',\n }),\n [normalizedBasePath, navigate, currentUser, workspaceId, tenantId, apiGatewayUrl, authToken]\n );\n\n return (\n <CosmicIntersectionContext.Provider value={value}>\n {children}\n </CosmicIntersectionContext.Provider>\n );\n}\n\nexport function useCosmicIntersection() {\n const context = useContext(CosmicIntersectionContext);\n if (!context) {\n throw new Error('useCosmicIntersection must be used within a CosmicIntersectionProvider');\n }\n return context;\n}\n\n/**\n * Hook for basePath-aware navigation. Returns a navigate function that\n * automatically prepends the microfrontend basePath. Falls back to a\n * `window.location`-based navigate if used outside the provider.\n */\nexport function useCosmicIntersectionNavigate() {\n const context = useContext(CosmicIntersectionContext);\n\n const fallbackNavigate = useCallback((path: string) => {\n const currentPath = window.location.pathname;\n const mfeMatch = currentPath.match(/^(\\/cosmicintersection)/);\n const basePath = mfeMatch ? mfeMatch[1] : '';\n\n let fullPath = path;\n if (basePath && !path.startsWith(basePath)) {\n fullPath = path.startsWith('/') ? `${basePath}${path}` : `${basePath}/${path}`;\n }\n window.location.href = fullPath;\n }, []);\n\n return context?.navigate ?? fallbackNavigate;\n}\n"],"mappings":";;;AAsBA,IAAM,IAA4B,EAAqD,IAAI;AAO3F,SAAgB,EAA2B,EAAE,aAAU,YAA0C;CAC/F,IAAM,EACJ,cAAW,IACX,UAAU,GACV,gBACA,gBACA,aACA,kBACA,iBACE,GAGE,IAAqB,EAAS,SAAS,GAAG,IAAI,EAAS,MAAM,GAAG,EAAE,IAAI,GAGtE,IAAW,GACd,MAAiB;EAChB,IAAI,CAAC,KAAsB,MAAuB,KAAK;GACrD,EAAa,CAAI;GACjB;EACF;EACA,IAAI,EAAK,WAAW,CAAkB,GAAG;GACvC,EAAa,CAAI;GACjB;EACF;EAIA,EAHiB,EAAK,WAAW,GAAG,IAChC,GAAG,IAAqB,MACxB,GAAG,EAAmB,GAAG,GACR;CACvB,GACA,CAAC,GAAoB,CAAY,CACnC,GAEM,IAAQ,SACL;EACL,UAAU;EACV;EACA;EACA;EACA;EACA,eAAe,KAAiB;EAChC,WAAW,KAAa;CAC1B,IACA;EAAC;EAAoB;EAAU;EAAa;EAAa;EAAU;EAAe;CAAS,CAC7F;CAEA,OACE,kBAAC,EAA0B,UAA3B;EAA2C;EACxC;CACiC,CAAA;AAExC;AAEA,SAAgB,IAAwB;CACtC,IAAM,IAAU,EAAW,CAAyB;CACpD,IAAI,CAAC,GACH,MAAU,MAAM,wEAAwE;CAE1F,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mutationResult.js","names":[],"sources":["../../../src/cosmicintersection/lib/mutationResult.ts"],"sourcesContent":["import type { ErrorLike } from '@apollo/client';\n\nimport { notify } from './toast';\n\n/**\n * The shared gateway Apollo client runs mutations with `errorPolicy: 'all'`, so a\n * server-side failure is returned IN-BAND on `result.error` rather than thrown —\n * awaiting the mutation resolves successfully even when nothing was persisted.\n * Write forms must therefore inspect the resolved result before treating a call\n * as a success, or they silently report a fake success and drop the user's data.\n *\n * `mutationSucceeded` centralises that check: it returns `true` only when there is\n * no in-band error AND the expected success payload id is present. Otherwise it\n * surfaces the real reason via the platform toast and returns `false` so the\n * caller keeps the form inputs intact and does not navigate away.\n */\nexport function mutationSucceeded(\n result: { error?: ErrorLike } | null | undefined,\n successId: string | null | undefined | boolean,\n failureMessage: string\n): boolean {\n if (result?.error) {\n notify.error(failureMessage, result.error.message);\n return false;\n }\n // Boolean mutations (archive/delete) confirm with `true`; entity mutations\n // confirm with a returned id. Treat a missing/false confirmation as failure.\n if (successId === undefined || successId === null || successId === '' || successId === false) {\n notify.error(failureMessage, 'The server did not confirm the change. Please try again.');\n return false;\n }\n return true;\n}\n\n/**\n * Normalises a thrown error (network failure, or a client configured with\n * `errorPolicy: 'none'`) into a human-readable description for the toast.\n */\nexport function describeError(error: unknown): string | undefined {\n if (error instanceof Error && error.message.length > 0) {\n return error.message;\n }\n return undefined;\n}\n"],"mappings":";;AAgBA,SAAgB,EACd,GACA,GACA,GACS;
|
|
1
|
+
{"version":3,"file":"mutationResult.js","names":[],"sources":["../../../src/cosmicintersection/lib/mutationResult.ts"],"sourcesContent":["import type { ErrorLike } from '@apollo/client';\n\nimport { notify } from './toast';\n\n/**\n * The shared gateway Apollo client runs mutations with `errorPolicy: 'all'`, so a\n * server-side failure is returned IN-BAND on `result.error` rather than thrown —\n * awaiting the mutation resolves successfully even when nothing was persisted.\n * Write forms must therefore inspect the resolved result before treating a call\n * as a success, or they silently report a fake success and drop the user's data.\n *\n * `mutationSucceeded` centralises that check: it returns `true` only when there is\n * no in-band error AND the expected success payload id is present. Otherwise it\n * surfaces the real reason via the platform toast and returns `false` so the\n * caller keeps the form inputs intact and does not navigate away.\n */\nexport function mutationSucceeded(\n result: { error?: ErrorLike } | null | undefined,\n successId: string | null | undefined | boolean,\n failureMessage: string\n): boolean {\n if (result?.error) {\n notify.error(failureMessage, result.error.message);\n return false;\n }\n // Boolean mutations (archive/delete) confirm with `true`; entity mutations\n // confirm with a returned id. Treat a missing/false confirmation as failure.\n if (successId === undefined || successId === null || successId === '' || successId === false) {\n notify.error(failureMessage, 'The server did not confirm the change. Please try again.');\n return false;\n }\n return true;\n}\n\n/**\n * Normalises a thrown error (network failure, or a client configured with\n * `errorPolicy: 'none'`) into a human-readable description for the toast.\n */\nexport function describeError(error: unknown): string | undefined {\n if (error instanceof Error && error.message.length > 0) {\n return error.message;\n }\n return undefined;\n}\n"],"mappings":";;AAgBA,SAAgB,EACd,GACA,GACA,GACS;CAWT,OAVI,GAAQ,SACV,EAAO,MAAM,GAAgB,EAAO,MAAM,OAAO,GAC1C,MAIL,KAAyC,QAAQ,MAAc,MAAM,MAAc,MACrF,EAAO,MAAM,GAAgB,0DAA0D,GAChF,MAEF;AACT;AAMA,SAAgB,EAAc,GAAoC;CAChE,IAAI,aAAiB,SAAS,EAAM,QAAQ,SAAS,GACnD,OAAO,EAAM;AAGjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toast.js","names":[],"sources":["../../../src/cosmicintersection/lib/toast.ts"],"sourcesContent":["import { toast } from 'sonner';\n\n/**\n * Module-wide toast API. sonner is a global singleton; the matching <Toaster>\n * is owned by the HOST app shell (mounted once via AppShellProvider). Every\n * page/component surfaces feedback through these helpers instead of hand-rolling\n * fixed-position toast markup, so success/error notifications stay consistent\n * with the rest of the platform.\n *\n * Durations follow the notification spec: success/info auto-dismiss, errors stay\n * until the user dismisses them (sonner renders a close button and announces via\n * aria-live).\n */\nexport const notify = {\n success(message: string, description?: string): void {\n toast.success(message, { description, duration: 4000 });\n },\n info(message: string, description?: string): void {\n toast.info(message, { description, duration: 4000 });\n },\n warning(message: string, description?: string): void {\n toast.warning(message, { description, duration: 8000 });\n },\n error(message: string, description?: string): void {\n toast.error(message, { description, duration: Infinity, closeButton: true });\n },\n dismiss(id?: string | number): void {\n toast.dismiss(id);\n },\n};\n\nexport { toast };\n"],"mappings":";;AAaA,IAAa,IAAS;CACpB,QAAQ,GAAiB,GAA4B;
|
|
1
|
+
{"version":3,"file":"toast.js","names":[],"sources":["../../../src/cosmicintersection/lib/toast.ts"],"sourcesContent":["import { toast } from 'sonner';\n\n/**\n * Module-wide toast API. sonner is a global singleton; the matching <Toaster>\n * is owned by the HOST app shell (mounted once via AppShellProvider). Every\n * page/component surfaces feedback through these helpers instead of hand-rolling\n * fixed-position toast markup, so success/error notifications stay consistent\n * with the rest of the platform.\n *\n * Durations follow the notification spec: success/info auto-dismiss, errors stay\n * until the user dismisses them (sonner renders a close button and announces via\n * aria-live).\n */\nexport const notify = {\n success(message: string, description?: string): void {\n toast.success(message, { description, duration: 4000 });\n },\n info(message: string, description?: string): void {\n toast.info(message, { description, duration: 4000 });\n },\n warning(message: string, description?: string): void {\n toast.warning(message, { description, duration: 8000 });\n },\n error(message: string, description?: string): void {\n toast.error(message, { description, duration: Infinity, closeButton: true });\n },\n dismiss(id?: string | number): void {\n toast.dismiss(id);\n },\n};\n\nexport { toast };\n"],"mappings":";;AAaA,IAAa,IAAS;CACpB,QAAQ,GAAiB,GAA4B;EACnD,EAAM,QAAQ,GAAS;GAAE;GAAa,UAAU;EAAK,CAAC;CACxD;CACA,KAAK,GAAiB,GAA4B;EAChD,EAAM,KAAK,GAAS;GAAE;GAAa,UAAU;EAAK,CAAC;CACrD;CACA,QAAQ,GAAiB,GAA4B;EACnD,EAAM,QAAQ,GAAS;GAAE;GAAa,UAAU;EAAK,CAAC;CACxD;CACA,MAAM,GAAiB,GAA4B;EACjD,EAAM,MAAM,GAAS;GAAE;GAAa,UAAU;GAAU,aAAa;EAAK,CAAC;CAC7E;CACA,QAAQ,GAA4B;EAClC,EAAM,QAAQ,CAAE;CAClB;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccessPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/access/AccessPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { KeyRound, ShieldCheck } from 'lucide-react';\nimport {\n Badge,\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Input,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosAccessGrantsQuery,\n useCosDatasetsQuery,\n useCosGrantAccessMutation,\n useCosRevokeAccessMutation,\n type CosDataAccessGrant,\n type CosGrantAccessInput,\n type CosGrantStatus,\n} from '../../api/cosmicApi';\nimport {\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n StatusBadge,\n formatEnumLabel,\n} from '../../components';\nimport { ActionMenu } from '../../components/ActionMenu';\nimport { EntityTagsField } from '../../components/EntityTagsField';\nimport { notify } from '../../lib/toast';\n\nconst ALL = 'ALL';\nconst GRANT_STATUSES: CosGrantStatus[] = ['PENDING', 'ACTIVE', 'EXPIRED', 'REVOKED'];\n\n/** Only live grants can be revoked. */\nfunction canRevoke(status: CosGrantStatus): boolean {\n return status === 'ACTIVE' || status === 'PENDING';\n}\n\nfunction formatDate(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) {\n return '—';\n }\n return parsed.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });\n}\n\ninterface GrantFormState {\n datasetId: string;\n granteeId: string;\n scopes: string[];\n reason: string;\n expiresAt: string;\n}\n\nconst EMPTY_FORM: GrantFormState = {\n datasetId: '',\n granteeId: '',\n scopes: [],\n reason: '',\n expiresAt: '',\n};\n\nexport default function AccessPage() {\n const [datasetFilter, setDatasetFilter] = useState<string>(ALL);\n const [statusFilter, setStatusFilter] = useState<string>(ALL);\n\n const datasetsQuery = useCosDatasetsQuery({ pagination: { limit: 50, offset: 0 } });\n const datasets = useMemo(\n () => datasetsQuery.data?.cosListDatasets.items ?? [],\n [datasetsQuery.data]\n );\n const datasetName = useMemo(() => {\n const map = new Map<string, string>();\n for (const dataset of datasets) {\n map.set(dataset.id, dataset.name);\n }\n return map;\n }, [datasets]);\n\n const { data, loading, error, refetch } = useCosAccessGrantsQuery({\n datasetId: datasetFilter === ALL ? undefined : datasetFilter,\n status: statusFilter === ALL ? undefined : (statusFilter as CosGrantStatus),\n });\n const grants = useMemo(() => data?.cosListAccessGrants ?? [], [data]);\n\n const [grantAccess, { loading: granting }] = useCosGrantAccessMutation();\n const [revokeAccess] = useCosRevokeAccessMutation();\n\n const [dialogOpen, setDialogOpen] = useState(false);\n const [form, setForm] = useState<GrantFormState>(EMPTY_FORM);\n\n const openGrantDialog = () => {\n setForm({\n ...EMPTY_FORM,\n datasetId: datasetFilter === ALL ? '' : datasetFilter,\n });\n setDialogOpen(true);\n };\n\n const setField = <K extends keyof GrantFormState>(field: K, value: GrantFormState[K]) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const submitGrant = async () => {\n if (!form.datasetId) {\n notify.error('Dataset is required', 'Choose which dataset this grant applies to.');\n return;\n }\n if (!form.granteeId.trim()) {\n notify.error('Grantee is required', 'Enter the workspace User ID to grant access to.');\n return;\n }\n\n let expiresAt: string | undefined;\n if (form.expiresAt) {\n const parsed = new Date(form.expiresAt);\n if (Number.isNaN(parsed.getTime())) {\n notify.error('Invalid expiry', 'Enter a valid expiry date or leave it blank.');\n return;\n }\n expiresAt = parsed.toISOString();\n }\n\n const input: CosGrantAccessInput = {\n datasetId: form.datasetId,\n granteeId: form.granteeId.trim(),\n scopes: form.scopes.length ? form.scopes : undefined,\n reason: form.reason.trim() || undefined,\n expiresAt,\n };\n\n const response = await grantAccess({ variables: { input } });\n if (response.data?.cosGrantAccess?.id) {\n notify.success('Access granted');\n setDialogOpen(false);\n setForm(EMPTY_FORM);\n await refetch();\n }\n };\n\n const handleRevoke = async (grant: CosDataAccessGrant) => {\n const response = await revokeAccess({ variables: { id: grant.id } });\n if (response.error) {\n throw new Error(response.error.message);\n }\n if (!response.data?.cosRevokeAccess?.id) {\n notify.error('Could not revoke grant', 'The server did not confirm the change.');\n throw new Error('Revoke not confirmed');\n }\n notify.success('Access revoked');\n await refetch();\n };\n\n const grantButton = (\n <Button type=\"button\" onClick={openGrantDialog}>\n <KeyRound className=\"size-4\" />\n Grant access\n </Button>\n );\n\n const showDatasetColumn = datasetFilter === ALL;\n\n const renderBody = () => {\n if (loading) {\n return <LoadingState variant=\"rows\" rows={6} label=\"Loading access grants…\" />;\n }\n if (error) {\n return <ErrorState message={error.message} onRetry={() => void refetch()} />;\n }\n if (grants.length === 0) {\n const filtered = datasetFilter !== ALL || statusFilter !== ALL;\n return (\n <EmptyState\n icon={ShieldCheck}\n title={filtered ? 'No grants match these filters' : 'No access grants yet'}\n description={\n filtered\n ? 'Try clearing the dataset or status filter to see more grants.'\n : 'Grant a workspace member scoped access to a restricted or embargoed dataset.'\n }\n action={filtered ? undefined : grantButton}\n />\n );\n }\n\n return (\n <>\n {/* Desktop table */}\n <div className=\"hidden rounded-card border border-border-default bg-bg-elevated md:block\">\n <Table>\n <TableHeader>\n <TableRow>\n {showDatasetColumn ? <TableHead>Dataset</TableHead> : null}\n <TableHead>Grantee</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Scopes</TableHead>\n <TableHead>Reason</TableHead>\n <TableHead>Granted</TableHead>\n <TableHead>Expires</TableHead>\n <TableHead>Revoked</TableHead>\n <TableHead className=\"text-right\">Actions</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {grants.map((grant) => (\n <TableRow key={grant.id}>\n {showDatasetColumn ? (\n <TableCell className=\"max-w-[12rem]\">\n <span className=\"line-clamp-1 text-sm text-text-primary\">\n {datasetName.get(grant.datasetId) ?? (\n <span className=\"font-mono text-xs text-text-secondary\">\n {grant.datasetId}\n </span>\n )}\n </span>\n </TableCell>\n ) : null}\n <TableCell className=\"max-w-[12rem]\">\n <span className=\"block truncate font-mono text-xs text-text-primary\">\n {grant.granteeId}\n </span>\n </TableCell>\n <TableCell>\n <StatusBadge status={grant.status} />\n </TableCell>\n <TableCell>\n <div className=\"flex max-w-[12rem] flex-wrap gap-1\">\n {grant.scopes.length === 0 ? (\n <span className=\"text-xs text-text-secondary\">—</span>\n ) : (\n grant.scopes.map((scope) => (\n <Badge key={scope} variant=\"outline\" className=\"text-xs\">\n {scope}\n </Badge>\n ))\n )}\n </div>\n </TableCell>\n <TableCell className=\"max-w-[14rem]\">\n {grant.reason ? (\n <span className=\"line-clamp-1 text-sm text-text-secondary\">\n {grant.reason}\n </span>\n ) : (\n <span className=\"text-sm text-text-secondary\">—</span>\n )}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {formatDate(grant.grantedAt)}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {formatDate(grant.expiresAt)}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {formatDate(grant.revokedAt)}\n </TableCell>\n <TableCell className=\"text-right\">\n {canRevoke(grant.status) ? (\n <ActionMenu\n ariaLabel={`Actions for grant to ${grant.granteeId}`}\n entityName={grant.granteeId}\n onDelete={() => handleRevoke(grant)}\n deleteLabel=\"Revoke grant\"\n />\n ) : (\n <span className=\"text-xs text-text-secondary\">—</span>\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 {grants.map((grant) => (\n <li key={grant.id} className=\"rounded-card border border-border-default bg-bg-elevated\">\n <div className=\"flex items-start justify-between gap-2 p-4\">\n <div className=\"min-w-0 flex-1 space-y-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n {showDatasetColumn ? (\n <span className=\"truncate text-sm font-medium text-text-primary\">\n {datasetName.get(grant.datasetId) ?? 'Dataset'}\n </span>\n ) : null}\n <StatusBadge status={grant.status} />\n </div>\n\n <div>\n <p className=\"text-xs text-text-secondary\">Grantee (User ID)</p>\n <p className=\"truncate font-mono text-xs text-text-primary\">\n {grant.granteeId}\n </p>\n </div>\n\n {grant.scopes.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {grant.scopes.map((scope) => (\n <Badge key={scope} variant=\"outline\" className=\"text-xs\">\n {scope}\n </Badge>\n ))}\n </div>\n ) : null}\n\n {grant.reason ? (\n <p className=\"line-clamp-2 text-xs text-text-secondary\">{grant.reason}</p>\n ) : null}\n\n <div className=\"flex flex-wrap gap-x-3 gap-y-1 text-xs text-text-secondary\">\n <span>Granted {formatDate(grant.grantedAt)}</span>\n <span>Expires {formatDate(grant.expiresAt)}</span>\n {grant.revokedAt ? <span>Revoked {formatDate(grant.revokedAt)}</span> : null}\n </div>\n </div>\n\n {canRevoke(grant.status) ? (\n <ActionMenu\n ariaLabel={`Actions for grant to ${grant.granteeId}`}\n entityName={grant.granteeId}\n onDelete={() => handleRevoke(grant)}\n deleteLabel=\"Revoke grant\"\n />\n ) : null}\n </div>\n </li>\n ))}\n </ul>\n </>\n );\n };\n\n return (\n <PageLayout\n title=\"Data access\"\n description=\"Govern who can reach restricted and embargoed datasets, with scoped, expiring grants.\"\n actions={grantButton}\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"w-full max-w-[18rem]\">\n <Select value={datasetFilter} onValueChange={setDatasetFilter}>\n <SelectTrigger aria-label=\"Filter by dataset\">\n <SelectValue placeholder=\"All datasets\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All datasets</SelectItem>\n {datasets.map((dataset) => (\n <SelectItem key={dataset.id} value={dataset.id}>\n {dataset.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"w-full max-w-[14rem]\">\n <Select value={statusFilter} onValueChange={setStatusFilter}>\n <SelectTrigger aria-label=\"Filter by status\">\n <SelectValue placeholder=\"All statuses\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All statuses</SelectItem>\n {GRANT_STATUSES.map((status) => (\n <SelectItem key={status} value={status}>\n {formatEnumLabel(status)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n {renderBody()}\n </div>\n\n {/* Grant access dialog */}\n <Dialog\n open={dialogOpen}\n onOpenChange={(open) => (open || granting ? setDialogOpen(open) : setDialogOpen(false))}\n >\n <DialogContent className=\"max-h-[85vh] max-w-md overflow-y-auto\">\n <DialogHeader>\n <DialogTitle>Grant data access</DialogTitle>\n <DialogDescription>\n Give a workspace member scoped access to a dataset. Access is identified by the\n member's User ID.\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-4\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"grant-dataset\">Dataset</Label>\n <Select\n value={form.datasetId || undefined}\n onValueChange={(value) => setField('datasetId', value)}\n >\n <SelectTrigger id=\"grant-dataset\" aria-label=\"Dataset\">\n <SelectValue placeholder=\"Select a dataset\" />\n </SelectTrigger>\n <SelectContent>\n {datasets.map((dataset) => (\n <SelectItem key={dataset.id} value={dataset.id}>\n {dataset.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"grant-grantee\">Grantee (User ID)</Label>\n <Input\n id=\"grant-grantee\"\n value={form.granteeId}\n onChange={(event) => setField('granteeId', event.target.value)}\n placeholder=\"e.g. 4fe35990-…\"\n className=\"font-mono\"\n />\n <p className=\"text-xs text-text-secondary\">\n Paste the workspace member's User ID. You can copy it from the member's\n profile (“Get ID”) or the workspace member directory.\n </p>\n </div>\n\n <EntityTagsField\n value={form.scopes}\n onChange={(scopes) => setField('scopes', scopes)}\n label=\"Scopes\"\n placeholder=\"e.g. read, download\"\n />\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"grant-reason\">Reason</Label>\n <Textarea\n id=\"grant-reason\"\n value={form.reason}\n onChange={(event) => setField('reason', event.target.value)}\n rows={2}\n placeholder=\"Why this member needs access.\"\n />\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"grant-expires\">Expires (optional)</Label>\n <Input\n id=\"grant-expires\"\n type=\"datetime-local\"\n value={form.expiresAt}\n onChange={(event) => setField('expiresAt', event.target.value)}\n />\n <p className=\"text-xs text-text-secondary\">Leave blank for an open-ended grant.</p>\n </div>\n </div>\n\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => setDialogOpen(false)}\n disabled={granting}\n >\n Cancel\n </Button>\n <Button type=\"button\" onClick={() => void submitGrant()} disabled={granting}>\n <KeyRound className=\"size-4\" />\n {granting ? 'Granting…' : 'Grant access'}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AccessPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/access/AccessPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { KeyRound, ShieldCheck } from 'lucide-react';\nimport {\n Badge,\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Input,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosAccessGrantsQuery,\n useCosDatasetsQuery,\n useCosGrantAccessMutation,\n useCosRevokeAccessMutation,\n type CosDataAccessGrant,\n type CosGrantAccessInput,\n type CosGrantStatus,\n} from '../../api/cosmicApi';\nimport {\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n StatusBadge,\n formatEnumLabel,\n} from '../../components';\nimport { ActionMenu } from '../../components/ActionMenu';\nimport { EntityTagsField } from '../../components/EntityTagsField';\nimport { notify } from '../../lib/toast';\n\nconst ALL = 'ALL';\nconst GRANT_STATUSES: CosGrantStatus[] = ['PENDING', 'ACTIVE', 'EXPIRED', 'REVOKED'];\n\n/** Only live grants can be revoked. */\nfunction canRevoke(status: CosGrantStatus): boolean {\n return status === 'ACTIVE' || status === 'PENDING';\n}\n\nfunction formatDate(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) {\n return '—';\n }\n return parsed.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });\n}\n\ninterface GrantFormState {\n datasetId: string;\n granteeId: string;\n scopes: string[];\n reason: string;\n expiresAt: string;\n}\n\nconst EMPTY_FORM: GrantFormState = {\n datasetId: '',\n granteeId: '',\n scopes: [],\n reason: '',\n expiresAt: '',\n};\n\nexport default function AccessPage() {\n const [datasetFilter, setDatasetFilter] = useState<string>(ALL);\n const [statusFilter, setStatusFilter] = useState<string>(ALL);\n\n const datasetsQuery = useCosDatasetsQuery({ pagination: { limit: 50, offset: 0 } });\n const datasets = useMemo(\n () => datasetsQuery.data?.cosListDatasets.items ?? [],\n [datasetsQuery.data]\n );\n const datasetName = useMemo(() => {\n const map = new Map<string, string>();\n for (const dataset of datasets) {\n map.set(dataset.id, dataset.name);\n }\n return map;\n }, [datasets]);\n\n const { data, loading, error, refetch } = useCosAccessGrantsQuery({\n datasetId: datasetFilter === ALL ? undefined : datasetFilter,\n status: statusFilter === ALL ? undefined : (statusFilter as CosGrantStatus),\n });\n const grants = useMemo(() => data?.cosListAccessGrants ?? [], [data]);\n\n const [grantAccess, { loading: granting }] = useCosGrantAccessMutation();\n const [revokeAccess] = useCosRevokeAccessMutation();\n\n const [dialogOpen, setDialogOpen] = useState(false);\n const [form, setForm] = useState<GrantFormState>(EMPTY_FORM);\n\n const openGrantDialog = () => {\n setForm({\n ...EMPTY_FORM,\n datasetId: datasetFilter === ALL ? '' : datasetFilter,\n });\n setDialogOpen(true);\n };\n\n const setField = <K extends keyof GrantFormState>(field: K, value: GrantFormState[K]) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const submitGrant = async () => {\n if (!form.datasetId) {\n notify.error('Dataset is required', 'Choose which dataset this grant applies to.');\n return;\n }\n if (!form.granteeId.trim()) {\n notify.error('Grantee is required', 'Enter the workspace User ID to grant access to.');\n return;\n }\n\n let expiresAt: string | undefined;\n if (form.expiresAt) {\n const parsed = new Date(form.expiresAt);\n if (Number.isNaN(parsed.getTime())) {\n notify.error('Invalid expiry', 'Enter a valid expiry date or leave it blank.');\n return;\n }\n expiresAt = parsed.toISOString();\n }\n\n const input: CosGrantAccessInput = {\n datasetId: form.datasetId,\n granteeId: form.granteeId.trim(),\n scopes: form.scopes.length ? form.scopes : undefined,\n reason: form.reason.trim() || undefined,\n expiresAt,\n };\n\n const response = await grantAccess({ variables: { input } });\n if (response.data?.cosGrantAccess?.id) {\n notify.success('Access granted');\n setDialogOpen(false);\n setForm(EMPTY_FORM);\n await refetch();\n }\n };\n\n const handleRevoke = async (grant: CosDataAccessGrant) => {\n const response = await revokeAccess({ variables: { id: grant.id } });\n if (response.error) {\n throw new Error(response.error.message);\n }\n if (!response.data?.cosRevokeAccess?.id) {\n notify.error('Could not revoke grant', 'The server did not confirm the change.');\n throw new Error('Revoke not confirmed');\n }\n notify.success('Access revoked');\n await refetch();\n };\n\n const grantButton = (\n <Button type=\"button\" onClick={openGrantDialog}>\n <KeyRound className=\"size-4\" />\n Grant access\n </Button>\n );\n\n const showDatasetColumn = datasetFilter === ALL;\n\n const renderBody = () => {\n if (loading) {\n return <LoadingState variant=\"rows\" rows={6} label=\"Loading access grants…\" />;\n }\n if (error) {\n return <ErrorState message={error.message} onRetry={() => void refetch()} />;\n }\n if (grants.length === 0) {\n const filtered = datasetFilter !== ALL || statusFilter !== ALL;\n return (\n <EmptyState\n icon={ShieldCheck}\n title={filtered ? 'No grants match these filters' : 'No access grants yet'}\n description={\n filtered\n ? 'Try clearing the dataset or status filter to see more grants.'\n : 'Grant a workspace member scoped access to a restricted or embargoed dataset.'\n }\n action={filtered ? undefined : grantButton}\n />\n );\n }\n\n return (\n <>\n {/* Desktop table */}\n <div className=\"hidden rounded-card border border-border-default bg-bg-elevated md:block\">\n <Table>\n <TableHeader>\n <TableRow>\n {showDatasetColumn ? <TableHead>Dataset</TableHead> : null}\n <TableHead>Grantee</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Scopes</TableHead>\n <TableHead>Reason</TableHead>\n <TableHead>Granted</TableHead>\n <TableHead>Expires</TableHead>\n <TableHead>Revoked</TableHead>\n <TableHead className=\"text-right\">Actions</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {grants.map((grant) => (\n <TableRow key={grant.id}>\n {showDatasetColumn ? (\n <TableCell className=\"max-w-[12rem]\">\n <span className=\"line-clamp-1 text-sm text-text-primary\">\n {datasetName.get(grant.datasetId) ?? (\n <span className=\"font-mono text-xs text-text-secondary\">\n {grant.datasetId}\n </span>\n )}\n </span>\n </TableCell>\n ) : null}\n <TableCell className=\"max-w-[12rem]\">\n <span className=\"block truncate font-mono text-xs text-text-primary\">\n {grant.granteeId}\n </span>\n </TableCell>\n <TableCell>\n <StatusBadge status={grant.status} />\n </TableCell>\n <TableCell>\n <div className=\"flex max-w-[12rem] flex-wrap gap-1\">\n {grant.scopes.length === 0 ? (\n <span className=\"text-xs text-text-secondary\">—</span>\n ) : (\n grant.scopes.map((scope) => (\n <Badge key={scope} variant=\"outline\" className=\"text-xs\">\n {scope}\n </Badge>\n ))\n )}\n </div>\n </TableCell>\n <TableCell className=\"max-w-[14rem]\">\n {grant.reason ? (\n <span className=\"line-clamp-1 text-sm text-text-secondary\">\n {grant.reason}\n </span>\n ) : (\n <span className=\"text-sm text-text-secondary\">—</span>\n )}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {formatDate(grant.grantedAt)}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {formatDate(grant.expiresAt)}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {formatDate(grant.revokedAt)}\n </TableCell>\n <TableCell className=\"text-right\">\n {canRevoke(grant.status) ? (\n <ActionMenu\n ariaLabel={`Actions for grant to ${grant.granteeId}`}\n entityName={grant.granteeId}\n onDelete={() => handleRevoke(grant)}\n deleteLabel=\"Revoke grant\"\n />\n ) : (\n <span className=\"text-xs text-text-secondary\">—</span>\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 {grants.map((grant) => (\n <li key={grant.id} className=\"rounded-card border border-border-default bg-bg-elevated\">\n <div className=\"flex items-start justify-between gap-2 p-4\">\n <div className=\"min-w-0 flex-1 space-y-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n {showDatasetColumn ? (\n <span className=\"truncate text-sm font-medium text-text-primary\">\n {datasetName.get(grant.datasetId) ?? 'Dataset'}\n </span>\n ) : null}\n <StatusBadge status={grant.status} />\n </div>\n\n <div>\n <p className=\"text-xs text-text-secondary\">Grantee (User ID)</p>\n <p className=\"truncate font-mono text-xs text-text-primary\">\n {grant.granteeId}\n </p>\n </div>\n\n {grant.scopes.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {grant.scopes.map((scope) => (\n <Badge key={scope} variant=\"outline\" className=\"text-xs\">\n {scope}\n </Badge>\n ))}\n </div>\n ) : null}\n\n {grant.reason ? (\n <p className=\"line-clamp-2 text-xs text-text-secondary\">{grant.reason}</p>\n ) : null}\n\n <div className=\"flex flex-wrap gap-x-3 gap-y-1 text-xs text-text-secondary\">\n <span>Granted {formatDate(grant.grantedAt)}</span>\n <span>Expires {formatDate(grant.expiresAt)}</span>\n {grant.revokedAt ? <span>Revoked {formatDate(grant.revokedAt)}</span> : null}\n </div>\n </div>\n\n {canRevoke(grant.status) ? (\n <ActionMenu\n ariaLabel={`Actions for grant to ${grant.granteeId}`}\n entityName={grant.granteeId}\n onDelete={() => handleRevoke(grant)}\n deleteLabel=\"Revoke grant\"\n />\n ) : null}\n </div>\n </li>\n ))}\n </ul>\n </>\n );\n };\n\n return (\n <PageLayout\n title=\"Data access\"\n description=\"Govern who can reach restricted and embargoed datasets, with scoped, expiring grants.\"\n actions={grantButton}\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"w-full max-w-[18rem]\">\n <Select value={datasetFilter} onValueChange={setDatasetFilter}>\n <SelectTrigger aria-label=\"Filter by dataset\">\n <SelectValue placeholder=\"All datasets\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All datasets</SelectItem>\n {datasets.map((dataset) => (\n <SelectItem key={dataset.id} value={dataset.id}>\n {dataset.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"w-full max-w-[14rem]\">\n <Select value={statusFilter} onValueChange={setStatusFilter}>\n <SelectTrigger aria-label=\"Filter by status\">\n <SelectValue placeholder=\"All statuses\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All statuses</SelectItem>\n {GRANT_STATUSES.map((status) => (\n <SelectItem key={status} value={status}>\n {formatEnumLabel(status)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n {renderBody()}\n </div>\n\n {/* Grant access dialog */}\n <Dialog\n open={dialogOpen}\n onOpenChange={(open) => (open || granting ? setDialogOpen(open) : setDialogOpen(false))}\n >\n <DialogContent className=\"max-h-[85vh] max-w-md overflow-y-auto\">\n <DialogHeader>\n <DialogTitle>Grant data access</DialogTitle>\n <DialogDescription>\n Give a workspace member scoped access to a dataset. Access is identified by the\n member's User ID.\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-4\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"grant-dataset\">Dataset</Label>\n <Select\n value={form.datasetId || undefined}\n onValueChange={(value) => setField('datasetId', value)}\n >\n <SelectTrigger id=\"grant-dataset\" aria-label=\"Dataset\">\n <SelectValue placeholder=\"Select a dataset\" />\n </SelectTrigger>\n <SelectContent>\n {datasets.map((dataset) => (\n <SelectItem key={dataset.id} value={dataset.id}>\n {dataset.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"grant-grantee\">Grantee (User ID)</Label>\n <Input\n id=\"grant-grantee\"\n value={form.granteeId}\n onChange={(event) => setField('granteeId', event.target.value)}\n placeholder=\"e.g. 4fe35990-…\"\n className=\"font-mono\"\n />\n <p className=\"text-xs text-text-secondary\">\n Paste the workspace member's User ID. You can copy it from the member's\n profile (“Get ID”) or the workspace member directory.\n </p>\n </div>\n\n <EntityTagsField\n value={form.scopes}\n onChange={(scopes) => setField('scopes', scopes)}\n label=\"Scopes\"\n placeholder=\"e.g. read, download\"\n />\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"grant-reason\">Reason</Label>\n <Textarea\n id=\"grant-reason\"\n value={form.reason}\n onChange={(event) => setField('reason', event.target.value)}\n rows={2}\n placeholder=\"Why this member needs access.\"\n />\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"grant-expires\">Expires (optional)</Label>\n <Input\n id=\"grant-expires\"\n type=\"datetime-local\"\n value={form.expiresAt}\n onChange={(event) => setField('expiresAt', event.target.value)}\n />\n <p className=\"text-xs text-text-secondary\">Leave blank for an open-ended grant.</p>\n </div>\n </div>\n\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => setDialogOpen(false)}\n disabled={granting}\n >\n Cancel\n </Button>\n <Button type=\"button\" onClick={() => void submitGrant()} disabled={granting}>\n <KeyRound className=\"size-4\" />\n {granting ? 'Granting…' : 'Grant access'}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnalysesPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/analyses/AnalysesPage.tsx"],"sourcesContent":["/**\n * AnalysesPage — every analysis run across the workspace.\n *\n * Lists `cosListAnalyses` (a plain array, no pagination) with a status filter.\n * A \"Run analysis\" dialog launches a new `cosRunAnalysis` against EITHER a\n * notebook OR a dataset (the schema requires one), chosen via a small selector.\n * In-flight (PENDING / RUNNING) analyses expose a \"Complete\" action that calls\n * `cosCompleteAnalysis` with COMPLETED | FAILED, optional results JSON, and an\n * optional output artifact uploaded through FileUploadField. All values are read\n * from the backend — nothing is fabricated.\n */\nimport { useMemo, useState, type FormEvent } from 'react';\nimport { BarChart3, Beaker, Download, ExternalLink, Play, Sparkles } from 'lucide-react';\nimport {\n Badge,\n Button,\n Dialog,\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Input,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Switch,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosAnalysesQuery,\n useCosCompleteAnalysisMutation,\n useCosDatasetsQuery,\n useCosNotebooksQuery,\n useCosRunAnalysisMutation,\n type CosAnalysis,\n type CosAnalysisStatus,\n} from '../../api/cosmicApi';\nimport {\n ActionMenu,\n EmptyState,\n ErrorState,\n FileUploadField,\n LoadingState,\n PageLayout,\n StatusBadge,\n useFileDownloadUrl,\n} from '../../components';\nimport { ResultsChart } from '../../components/ResultsChart';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\n\n/** Anything worth opening the results viewer for. */\nfunction hasViewableResults(analysis: CosAnalysis): boolean {\n return (\n Boolean(analysis.outputFile?.id) ||\n Boolean(analysis.results && Object.keys(analysis.results).length > 0)\n );\n}\n\nconst STATUS_OPTIONS: { value: CosAnalysisStatus; label: string }[] = [\n { value: 'PENDING', label: 'Pending' },\n { value: 'RUNNING', label: 'Running' },\n { value: 'COMPLETED', label: 'Completed' },\n { value: 'FAILED', label: 'Failed' },\n];\n\nconst ALL = 'ALL';\n\nfunction formatDateTime(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.toLocaleString(undefined, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n });\n}\n\n/**\n * Presigned output artifact: attempts an inline image preview (astronomy output\n * is often a plot/spectrum PNG) and always offers a download link. If the file\n * is not an image the preview is dropped on the img error and only the link\n * remains — no assumption is made about the artifact's type.\n */\nfunction OutputArtifact({ fileId }: { fileId: string }) {\n const { data, loading } = useFileDownloadUrl(fileId);\n const [previewFailed, setPreviewFailed] = useState(false);\n const url = data?.getFileDownloadUrl ?? null;\n\n if (loading) {\n return <span className=\"text-xs text-text-secondary\">Resolving link…</span>;\n }\n if (!url) {\n return <span className=\"text-xs text-text-secondary\">File {fileId}</span>;\n }\n return (\n <div className=\"space-y-2\">\n {!previewFailed ? (\n <img\n src={url}\n alt=\"Analysis output preview\"\n onError={() => setPreviewFailed(true)}\n className=\"max-h-72 w-full rounded-card border border-border-default bg-bg-sunken object-contain\"\n />\n ) : null}\n <a\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1 text-sm text-text-primary underline-offset-2 hover:underline\"\n >\n <Download className=\"size-3.5\" aria-hidden=\"true\" />\n Download output\n </a>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Results viewer dialog\n// ---------------------------------------------------------------------------\n\n/**\n * Inline results viewer for a finished analysis: a rendered <ResultsChart> when\n * the payload is chartable (or its key/value fallback otherwise) plus an output\n * artifact preview/download. Everything is read straight from the analysis\n * record.\n */\nfunction ResultsViewerDialog({\n analysis,\n onOpenChange,\n}: {\n analysis: CosAnalysis | null;\n onOpenChange: (open: boolean) => void;\n}) {\n const results = analysis?.results ?? null;\n const hasResults = Boolean(results && Object.keys(results).length > 0);\n const hasOutput = Boolean(analysis?.outputFile?.id);\n\n return (\n <Dialog open={Boolean(analysis)} onOpenChange={onOpenChange}>\n <DialogContent className=\"max-h-[85vh] max-w-2xl overflow-y-auto\">\n {analysis ? (\n <>\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-2\">\n <BarChart3 className=\"size-5 text-text-secondary\" aria-hidden=\"true\" />\n Analysis results\n </DialogTitle>\n <DialogDescription>\n {analysis.title}\n {analysis.method ? ` · ${analysis.method}` : ''}\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-5\">\n {hasResults && results ? <ResultsChart results={results} /> : null}\n\n {hasOutput && analysis.outputFile ? (\n <div className=\"space-y-2\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Output artifact\n </p>\n <OutputArtifact fileId={analysis.outputFile.id} />\n </div>\n ) : null}\n\n {!hasResults && !hasOutput ? (\n <p className=\"rounded-card border border-border-default bg-bg-sunken px-4 py-6 text-center text-sm text-text-secondary\">\n No results or output artifact has been recorded for this analysis yet.\n </p>\n ) : null}\n </div>\n\n <DialogFooter>\n <DialogClose asChild>\n <Button type=\"button\" variant=\"outline\">\n Close\n </Button>\n </DialogClose>\n </DialogFooter>\n </>\n ) : null}\n </DialogContent>\n </Dialog>\n );\n}\n\n/** Parse optional results JSON; blank -> undefined, invalid -> error. */\nfunction parseResults(\n raw: string\n): { ok: true; value?: Record<string, unknown> } | { ok: false; error: string } {\n const trimmed = raw.trim();\n if (!trimmed) {\n return { ok: true, value: undefined };\n }\n try {\n const parsed: unknown = JSON.parse(trimmed);\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return { ok: false, error: 'Results must be a JSON object.' };\n }\n return { ok: true, value: parsed as Record<string, unknown> };\n } catch {\n return { ok: false, error: 'Results are not valid JSON.' };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Run analysis dialog\n// ---------------------------------------------------------------------------\n\ntype RunSourceKind = 'NOTEBOOK' | 'DATASET';\n\ninterface RunDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n onCompleted: () => void;\n}\n\nfunction RunAnalysisDialog({ open, onOpenChange, onCompleted }: RunDialogProps) {\n const [title, setTitle] = useState('');\n const [method, setMethod] = useState('');\n const [aiAssisted, setAiAssisted] = useState(false);\n const [sourceKind, setSourceKind] = useState<RunSourceKind>('NOTEBOOK');\n const [sourceId, setSourceId] = useState('');\n\n const { data: notebooksData, loading: notebooksLoading } = useCosNotebooksQuery({\n pagination: { limit: 100, offset: 0 },\n });\n const { data: datasetsData, loading: datasetsLoading } = useCosDatasetsQuery({\n pagination: { limit: 100, offset: 0 },\n });\n const [runAnalysis, { loading }] = useCosRunAnalysisMutation();\n\n const notebooks = notebooksData?.cosListNotebooks.items ?? [];\n const datasets = datasetsData?.cosListDatasets.items ?? [];\n\n const reset = () => {\n setTitle('');\n setMethod('');\n setAiAssisted(false);\n setSourceKind('NOTEBOOK');\n setSourceId('');\n };\n\n const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n const trimmed = title.trim();\n if (!trimmed) {\n notify.error('Title is required', 'Give the analysis a descriptive title.');\n return;\n }\n if (!sourceId) {\n notify.error(\n 'Select a source',\n `Choose a ${sourceKind === 'NOTEBOOK' ? 'notebook' : 'dataset'} to analyse.`\n );\n return;\n }\n const response = await runAnalysis({\n variables: {\n input: {\n title: trimmed,\n method: method.trim() || undefined,\n aiAssisted,\n ...(sourceKind === 'NOTEBOOK' ? { notebookId: sourceId } : { datasetId: sourceId }),\n },\n },\n });\n if (response.error || !response.data?.cosRunAnalysis?.id) {\n return;\n }\n notify.success('Analysis started', `“${trimmed}” is now running.`);\n reset();\n onOpenChange(false);\n onCompleted();\n };\n\n const sourceLoading = sourceKind === 'NOTEBOOK' ? notebooksLoading : datasetsLoading;\n\n return (\n <Dialog\n open={open}\n onOpenChange={(next) => {\n if (!loading) {\n onOpenChange(next);\n }\n }}\n >\n <DialogContent>\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n <DialogHeader>\n <DialogTitle>Run analysis</DialogTitle>\n <DialogDescription>\n Launch a new analysis against a notebook or a dataset. Exactly one source is required.\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"run-title\">Title</Label>\n <Input\n id=\"run-title\"\n value={title}\n onChange={(event) => setTitle(event.target.value)}\n placeholder=\"e.g. Spectral line fitting\"\n required\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"run-source-kind\">Source type</Label>\n <Select\n value={sourceKind}\n onValueChange={(value) => {\n setSourceKind(value as RunSourceKind);\n setSourceId('');\n }}\n >\n <SelectTrigger id=\"run-source-kind\" aria-label=\"Source type\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"NOTEBOOK\">Notebook</SelectItem>\n <SelectItem value=\"DATASET\">Dataset</SelectItem>\n </SelectContent>\n </Select>\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"run-source-id\">\n {sourceKind === 'NOTEBOOK' ? 'Notebook' : 'Dataset'}\n </Label>\n <Select value={sourceId} onValueChange={(value) => setSourceId(value)}>\n <SelectTrigger id=\"run-source-id\" aria-label=\"Analysis source\">\n <SelectValue\n placeholder={\n sourceLoading\n ? 'Loading…'\n : `Select a ${sourceKind === 'NOTEBOOK' ? 'notebook' : 'dataset'}`\n }\n />\n </SelectTrigger>\n <SelectContent>\n {sourceKind === 'NOTEBOOK'\n ? notebooks.map((notebook) => (\n <SelectItem key={notebook.id} value={notebook.id}>\n {notebook.title}\n </SelectItem>\n ))\n : datasets.map((dataset) => (\n <SelectItem key={dataset.id} value={dataset.id}>\n {dataset.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"run-method\">Method</Label>\n <Input\n id=\"run-method\"\n value={method}\n onChange={(event) => setMethod(event.target.value)}\n placeholder=\"e.g. gaussian-fit\"\n />\n </div>\n\n <div className=\"flex items-center gap-3\">\n <Switch\n id=\"run-ai\"\n checked={aiAssisted}\n onCheckedChange={(checked) => setAiAssisted(checked)}\n />\n <Label htmlFor=\"run-ai\" className=\"cursor-pointer\">\n AI-assisted analysis\n </Label>\n </div>\n\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n disabled={loading}\n >\n Cancel\n </Button>\n <Button type=\"submit\" disabled={loading}>\n <Play className=\"size-4\" />\n {loading ? 'Starting…' : 'Run analysis'}\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Complete analysis dialog\n// ---------------------------------------------------------------------------\n\ninterface CompleteDialogProps {\n analysis: CosAnalysis | null;\n onOpenChange: (open: boolean) => void;\n onCompleted: () => void;\n}\n\nfunction CompleteAnalysisDialog({ analysis, onOpenChange, onCompleted }: CompleteDialogProps) {\n const [status, setStatus] = useState<'COMPLETED' | 'FAILED'>('COMPLETED');\n const [results, setResults] = useState('');\n const [outputFileId, setOutputFileId] = useState('');\n const [completeAnalysis, { loading }] = useCosCompleteAnalysisMutation();\n\n const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n if (!analysis) {\n return;\n }\n const parsed = parseResults(results);\n if (!parsed.ok) {\n notify.error('Invalid results', parsed.error);\n return;\n }\n const response = await completeAnalysis({\n variables: {\n id: analysis.id,\n status,\n results: parsed.value,\n outputFileId: outputFileId || undefined,\n },\n });\n if (response.error || !response.data?.cosCompleteAnalysis) {\n return;\n }\n notify.success(\n status === 'COMPLETED' ? 'Analysis completed' : 'Analysis marked failed',\n `“${analysis.title}” was updated.`\n );\n onOpenChange(false);\n onCompleted();\n };\n\n return (\n <Dialog\n open={Boolean(analysis)}\n onOpenChange={(next) => {\n if (!loading) {\n onOpenChange(next);\n }\n }}\n >\n <DialogContent>\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n <DialogHeader>\n <DialogTitle>Complete analysis</DialogTitle>\n <DialogDescription>\n Record the outcome for {analysis ? `“${analysis.title}”` : 'this analysis'}. Attach\n results or an output artifact if you have them.\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"complete-status\">Outcome</Label>\n <Select\n value={status}\n onValueChange={(value) => setStatus(value as 'COMPLETED' | 'FAILED')}\n >\n <SelectTrigger id=\"complete-status\" aria-label=\"Outcome\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"COMPLETED\">Completed</SelectItem>\n <SelectItem value=\"FAILED\">Failed</SelectItem>\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"complete-results\">Results (JSON)</Label>\n <Textarea\n id=\"complete-results\"\n value={results}\n onChange={(event) => setResults(event.target.value)}\n rows={4}\n className=\"font-mono text-xs\"\n placeholder='{ \"snr\": 12.4, \"detections\": 3 }'\n />\n <p className=\"text-xs text-text-secondary\">\n Optional structured results as a JSON object.\n </p>\n </div>\n\n <FileUploadField\n value={outputFileId}\n onChange={(fileId) => setOutputFileId(fileId)}\n label=\"Attach output file\"\n hint=\"Optional output artifact (plots, tables, exports). Stored in workspace files.\"\n />\n\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n disabled={loading}\n >\n Cancel\n </Button>\n <Button type=\"submit\" disabled={loading}>\n {loading ? 'Saving…' : 'Save outcome'}\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Page\n// ---------------------------------------------------------------------------\n\nexport default function AnalysesPage() {\n const { navigate } = useCosmicIntersection();\n const [statusFilter, setStatusFilter] = useState<CosAnalysisStatus | undefined>(undefined);\n const [runOpen, setRunOpen] = useState(false);\n const [completing, setCompleting] = useState<CosAnalysis | null>(null);\n const [viewing, setViewing] = useState<CosAnalysis | null>(null);\n\n const { data, loading, error, refetch } = useCosAnalysesQuery({ status: statusFilter });\n const { data: notebooksData } = useCosNotebooksQuery({ pagination: { limit: 100, offset: 0 } });\n const { data: datasetsData } = useCosDatasetsQuery({ pagination: { limit: 100, offset: 0 } });\n\n const analyses = data?.cosListAnalyses ?? [];\n\n const notebookNames = useMemo(() => {\n const map = new Map<string, string>();\n for (const notebook of notebooksData?.cosListNotebooks.items ?? []) {\n map.set(notebook.id, notebook.title);\n }\n return map;\n }, [notebooksData]);\n\n const datasetNames = useMemo(() => {\n const map = new Map<string, string>();\n for (const dataset of datasetsData?.cosListDatasets.items ?? []) {\n map.set(dataset.id, dataset.name);\n }\n return map;\n }, [datasetsData]);\n\n /** Resolve a friendly label + route for an analysis' notebook OR dataset source. */\n const sourceInfo = (analysis: CosAnalysis): { label: string; path: string } | null => {\n if (analysis.notebookId) {\n return {\n label: notebookNames.get(analysis.notebookId) ?? 'Notebook',\n path: `notebooks/${analysis.notebookId}`,\n };\n }\n if (analysis.datasetId) {\n return {\n label: datasetNames.get(analysis.datasetId) ?? 'Dataset',\n path: `datasets/${analysis.datasetId}`,\n };\n }\n return null;\n };\n\n /** Extra ActionMenu items for a row/card (view results + complete). */\n const extraActions = (analysis: CosAnalysis) => {\n const inFlight = analysis.status === 'PENDING' || analysis.status === 'RUNNING';\n return [\n ...(hasViewableResults(analysis)\n ? [\n {\n key: 'view-results',\n label: 'View results',\n icon: BarChart3,\n onSelect: () => setViewing(analysis),\n },\n ]\n : []),\n ...(inFlight\n ? [\n {\n key: 'complete',\n label: 'Complete…',\n icon: Beaker,\n onSelect: () => setCompleting(analysis),\n },\n ]\n : []),\n ];\n };\n\n const actions = (\n <Button type=\"button\" onClick={() => setRunOpen(true)}>\n <Play className=\"size-4\" />\n Run analysis\n </Button>\n );\n\n return (\n <PageLayout\n title=\"Analyses\"\n description=\"Analysis runs executed over notebooks and federated datasets.\"\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=\"analysis-status-filter\">\n Status\n </label>\n <Select\n value={statusFilter ?? ALL}\n onValueChange={(value) =>\n setStatusFilter(value === ALL ? undefined : (value as CosAnalysisStatus))\n }\n >\n <SelectTrigger\n id=\"analysis-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=\"Loading analyses…\" />\n ) : error ? (\n <ErrorState\n message=\"We could not load analyses. Please try again.\"\n onRetry={() => void refetch()}\n />\n ) : analyses.length === 0 ? (\n <EmptyState\n icon={Beaker}\n title={statusFilter ? 'No analyses match this filter.' : 'No analyses yet.'}\n description={\n statusFilter\n ? 'Try a different status filter.'\n : 'Run an analysis against a notebook or dataset to see results here.'\n }\n action={\n statusFilter ? undefined : (\n <Button type=\"button\" onClick={() => setRunOpen(true)}>\n <Play className=\"size-4\" />\n Run analysis\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>Method</TableHead>\n <TableHead>AI</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Source</TableHead>\n <TableHead>Started</TableHead>\n <TableHead>Finished</TableHead>\n <TableHead>Output</TableHead>\n <TableHead className=\"w-12 text-right\">Actions</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {analyses.map((analysis) => {\n const source = sourceInfo(analysis);\n return (\n <TableRow key={analysis.id}>\n <TableCell className=\"font-medium text-text-primary\">\n {analysis.title}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {analysis.method ?? '—'}\n </TableCell>\n <TableCell>\n {analysis.aiAssisted ? (\n <Badge variant=\"secondary\" className=\"gap-1\">\n <Sparkles className=\"size-3\" aria-hidden=\"true\" />\n AI\n </Badge>\n ) : (\n <span className=\"text-text-secondary\">—</span>\n )}\n </TableCell>\n <TableCell>\n <StatusBadge status={analysis.status} />\n </TableCell>\n <TableCell>\n {source ? (\n <button\n type=\"button\"\n onClick={() => navigate(source.path)}\n className=\"inline-flex max-w-[12rem] items-center gap-1 truncate text-sm text-text-primary underline-offset-2 hover:underline\"\n >\n <ExternalLink className=\"size-3.5 shrink-0\" aria-hidden=\"true\" />\n <span className=\"truncate\">{source.label}</span>\n </button>\n ) : (\n <span className=\"text-text-secondary\">—</span>\n )}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-text-secondary\">\n {formatDateTime(analysis.startedAt)}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-text-secondary\">\n {formatDateTime(analysis.finishedAt)}\n </TableCell>\n <TableCell>\n {hasViewableResults(analysis) ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-auto gap-1 px-2 py-1\"\n onClick={() => setViewing(analysis)}\n >\n <BarChart3 className=\"size-3.5\" aria-hidden=\"true\" />\n View\n </Button>\n ) : (\n <span className=\"text-text-secondary\">—</span>\n )}\n </TableCell>\n <TableCell className=\"text-right\">\n <ActionMenu\n ariaLabel={`Actions for ${analysis.title}`}\n onView={source ? () => navigate(source.path) : undefined}\n extraItems={extraActions(analysis)}\n />\n </TableCell>\n </TableRow>\n );\n })}\n </TableBody>\n </Table>\n </div>\n\n {/* Mobile card list */}\n <ul className=\"space-y-3 md:hidden\">\n {analyses.map((analysis) => {\n const source = sourceInfo(analysis);\n const inFlight = analysis.status === 'PENDING' || analysis.status === 'RUNNING';\n return (\n <li\n key={analysis.id}\n className=\"space-y-3 rounded-card border border-border-default bg-bg-elevated p-4\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{analysis.title}</p>\n {analysis.method ? (\n <p className=\"truncate text-xs text-text-secondary\">{analysis.method}</p>\n ) : null}\n </div>\n <StatusBadge status={analysis.status} />\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n {analysis.aiAssisted ? (\n <Badge variant=\"secondary\" className=\"gap-1\">\n <Sparkles className=\"size-3\" aria-hidden=\"true\" />\n AI\n </Badge>\n ) : null}\n {source ? (\n <button\n type=\"button\"\n onClick={() => navigate(source.path)}\n className=\"inline-flex max-w-full items-center gap-1 truncate text-sm text-text-primary underline-offset-2 hover:underline\"\n >\n <ExternalLink className=\"size-3.5 shrink-0\" aria-hidden=\"true\" />\n <span className=\"truncate\">{source.label}</span>\n </button>\n ) : null}\n </div>\n\n <dl className=\"grid grid-cols-2 gap-2 text-xs\">\n <div>\n <dt className=\"text-text-secondary\">Started</dt>\n <dd className=\"text-text-primary\">{formatDateTime(analysis.startedAt)}</dd>\n </div>\n <div>\n <dt className=\"text-text-secondary\">Finished</dt>\n <dd className=\"text-text-primary\">{formatDateTime(analysis.finishedAt)}</dd>\n </div>\n </dl>\n\n {hasViewableResults(analysis) || inFlight ? (\n <div className=\"flex flex-wrap gap-2\">\n {hasViewableResults(analysis) ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setViewing(analysis)}\n >\n <BarChart3 className=\"size-4\" />\n View results\n </Button>\n ) : null}\n {inFlight ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setCompleting(analysis)}\n >\n <Beaker className=\"size-4\" />\n Complete\n </Button>\n ) : null}\n </div>\n ) : null}\n </li>\n );\n })}\n </ul>\n </>\n )}\n </div>\n\n <RunAnalysisDialog\n open={runOpen}\n onOpenChange={setRunOpen}\n onCompleted={() => void refetch()}\n />\n <CompleteAnalysisDialog\n key={completing?.id ?? 'none'}\n analysis={completing}\n onOpenChange={(open) => {\n if (!open) {\n setCompleting(null);\n }\n }}\n onCompleted={() => void refetch()}\n />\n <ResultsViewerDialog\n analysis={viewing}\n onOpenChange={(open) => {\n if (!open) {\n setViewing(null);\n }\n }}\n />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AnalysesPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/analyses/AnalysesPage.tsx"],"sourcesContent":["/**\n * AnalysesPage — every analysis run across the workspace.\n *\n * Lists `cosListAnalyses` (a plain array, no pagination) with a status filter.\n * A \"Run analysis\" dialog launches a new `cosRunAnalysis` against EITHER a\n * notebook OR a dataset (the schema requires one), chosen via a small selector.\n * In-flight (PENDING / RUNNING) analyses expose a \"Complete\" action that calls\n * `cosCompleteAnalysis` with COMPLETED | FAILED, optional results JSON, and an\n * optional output artifact uploaded through FileUploadField. All values are read\n * from the backend — nothing is fabricated.\n */\nimport { useMemo, useState, type FormEvent } from 'react';\nimport { BarChart3, Beaker, Download, ExternalLink, Play, Sparkles } from 'lucide-react';\nimport {\n Badge,\n Button,\n Dialog,\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Input,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Switch,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosAnalysesQuery,\n useCosCompleteAnalysisMutation,\n useCosDatasetsQuery,\n useCosNotebooksQuery,\n useCosRunAnalysisMutation,\n type CosAnalysis,\n type CosAnalysisStatus,\n} from '../../api/cosmicApi';\nimport {\n ActionMenu,\n EmptyState,\n ErrorState,\n FileUploadField,\n LoadingState,\n PageLayout,\n StatusBadge,\n useFileDownloadUrl,\n} from '../../components';\nimport { ResultsChart } from '../../components/ResultsChart';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\n\n/** Anything worth opening the results viewer for. */\nfunction hasViewableResults(analysis: CosAnalysis): boolean {\n return (\n Boolean(analysis.outputFile?.id) ||\n Boolean(analysis.results && Object.keys(analysis.results).length > 0)\n );\n}\n\nconst STATUS_OPTIONS: { value: CosAnalysisStatus; label: string }[] = [\n { value: 'PENDING', label: 'Pending' },\n { value: 'RUNNING', label: 'Running' },\n { value: 'COMPLETED', label: 'Completed' },\n { value: 'FAILED', label: 'Failed' },\n];\n\nconst ALL = 'ALL';\n\nfunction formatDateTime(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.toLocaleString(undefined, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n });\n}\n\n/**\n * Presigned output artifact: attempts an inline image preview (astronomy output\n * is often a plot/spectrum PNG) and always offers a download link. If the file\n * is not an image the preview is dropped on the img error and only the link\n * remains — no assumption is made about the artifact's type.\n */\nfunction OutputArtifact({ fileId }: { fileId: string }) {\n const { data, loading } = useFileDownloadUrl(fileId);\n const [previewFailed, setPreviewFailed] = useState(false);\n const url = data?.getFileDownloadUrl ?? null;\n\n if (loading) {\n return <span className=\"text-xs text-text-secondary\">Resolving link…</span>;\n }\n if (!url) {\n return <span className=\"text-xs text-text-secondary\">File {fileId}</span>;\n }\n return (\n <div className=\"space-y-2\">\n {!previewFailed ? (\n <img\n src={url}\n alt=\"Analysis output preview\"\n onError={() => setPreviewFailed(true)}\n className=\"max-h-72 w-full rounded-card border border-border-default bg-bg-sunken object-contain\"\n />\n ) : null}\n <a\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1 text-sm text-text-primary underline-offset-2 hover:underline\"\n >\n <Download className=\"size-3.5\" aria-hidden=\"true\" />\n Download output\n </a>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Results viewer dialog\n// ---------------------------------------------------------------------------\n\n/**\n * Inline results viewer for a finished analysis: a rendered <ResultsChart> when\n * the payload is chartable (or its key/value fallback otherwise) plus an output\n * artifact preview/download. Everything is read straight from the analysis\n * record.\n */\nfunction ResultsViewerDialog({\n analysis,\n onOpenChange,\n}: {\n analysis: CosAnalysis | null;\n onOpenChange: (open: boolean) => void;\n}) {\n const results = analysis?.results ?? null;\n const hasResults = Boolean(results && Object.keys(results).length > 0);\n const hasOutput = Boolean(analysis?.outputFile?.id);\n\n return (\n <Dialog open={Boolean(analysis)} onOpenChange={onOpenChange}>\n <DialogContent className=\"max-h-[85vh] max-w-2xl overflow-y-auto\">\n {analysis ? (\n <>\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-2\">\n <BarChart3 className=\"size-5 text-text-secondary\" aria-hidden=\"true\" />\n Analysis results\n </DialogTitle>\n <DialogDescription>\n {analysis.title}\n {analysis.method ? ` · ${analysis.method}` : ''}\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-5\">\n {hasResults && results ? <ResultsChart results={results} /> : null}\n\n {hasOutput && analysis.outputFile ? (\n <div className=\"space-y-2\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Output artifact\n </p>\n <OutputArtifact fileId={analysis.outputFile.id} />\n </div>\n ) : null}\n\n {!hasResults && !hasOutput ? (\n <p className=\"rounded-card border border-border-default bg-bg-sunken px-4 py-6 text-center text-sm text-text-secondary\">\n No results or output artifact has been recorded for this analysis yet.\n </p>\n ) : null}\n </div>\n\n <DialogFooter>\n <DialogClose asChild>\n <Button type=\"button\" variant=\"outline\">\n Close\n </Button>\n </DialogClose>\n </DialogFooter>\n </>\n ) : null}\n </DialogContent>\n </Dialog>\n );\n}\n\n/** Parse optional results JSON; blank -> undefined, invalid -> error. */\nfunction parseResults(\n raw: string\n): { ok: true; value?: Record<string, unknown> } | { ok: false; error: string } {\n const trimmed = raw.trim();\n if (!trimmed) {\n return { ok: true, value: undefined };\n }\n try {\n const parsed: unknown = JSON.parse(trimmed);\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return { ok: false, error: 'Results must be a JSON object.' };\n }\n return { ok: true, value: parsed as Record<string, unknown> };\n } catch {\n return { ok: false, error: 'Results are not valid JSON.' };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Run analysis dialog\n// ---------------------------------------------------------------------------\n\ntype RunSourceKind = 'NOTEBOOK' | 'DATASET';\n\ninterface RunDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n onCompleted: () => void;\n}\n\nfunction RunAnalysisDialog({ open, onOpenChange, onCompleted }: RunDialogProps) {\n const [title, setTitle] = useState('');\n const [method, setMethod] = useState('');\n const [aiAssisted, setAiAssisted] = useState(false);\n const [sourceKind, setSourceKind] = useState<RunSourceKind>('NOTEBOOK');\n const [sourceId, setSourceId] = useState('');\n\n const { data: notebooksData, loading: notebooksLoading } = useCosNotebooksQuery({\n pagination: { limit: 100, offset: 0 },\n });\n const { data: datasetsData, loading: datasetsLoading } = useCosDatasetsQuery({\n pagination: { limit: 100, offset: 0 },\n });\n const [runAnalysis, { loading }] = useCosRunAnalysisMutation();\n\n const notebooks = notebooksData?.cosListNotebooks.items ?? [];\n const datasets = datasetsData?.cosListDatasets.items ?? [];\n\n const reset = () => {\n setTitle('');\n setMethod('');\n setAiAssisted(false);\n setSourceKind('NOTEBOOK');\n setSourceId('');\n };\n\n const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n const trimmed = title.trim();\n if (!trimmed) {\n notify.error('Title is required', 'Give the analysis a descriptive title.');\n return;\n }\n if (!sourceId) {\n notify.error(\n 'Select a source',\n `Choose a ${sourceKind === 'NOTEBOOK' ? 'notebook' : 'dataset'} to analyse.`\n );\n return;\n }\n const response = await runAnalysis({\n variables: {\n input: {\n title: trimmed,\n method: method.trim() || undefined,\n aiAssisted,\n ...(sourceKind === 'NOTEBOOK' ? { notebookId: sourceId } : { datasetId: sourceId }),\n },\n },\n });\n if (response.error || !response.data?.cosRunAnalysis?.id) {\n return;\n }\n notify.success('Analysis started', `“${trimmed}” is now running.`);\n reset();\n onOpenChange(false);\n onCompleted();\n };\n\n const sourceLoading = sourceKind === 'NOTEBOOK' ? notebooksLoading : datasetsLoading;\n\n return (\n <Dialog\n open={open}\n onOpenChange={(next) => {\n if (!loading) {\n onOpenChange(next);\n }\n }}\n >\n <DialogContent>\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n <DialogHeader>\n <DialogTitle>Run analysis</DialogTitle>\n <DialogDescription>\n Launch a new analysis against a notebook or a dataset. Exactly one source is required.\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"run-title\">Title</Label>\n <Input\n id=\"run-title\"\n value={title}\n onChange={(event) => setTitle(event.target.value)}\n placeholder=\"e.g. Spectral line fitting\"\n required\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"run-source-kind\">Source type</Label>\n <Select\n value={sourceKind}\n onValueChange={(value) => {\n setSourceKind(value as RunSourceKind);\n setSourceId('');\n }}\n >\n <SelectTrigger id=\"run-source-kind\" aria-label=\"Source type\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"NOTEBOOK\">Notebook</SelectItem>\n <SelectItem value=\"DATASET\">Dataset</SelectItem>\n </SelectContent>\n </Select>\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"run-source-id\">\n {sourceKind === 'NOTEBOOK' ? 'Notebook' : 'Dataset'}\n </Label>\n <Select value={sourceId} onValueChange={(value) => setSourceId(value)}>\n <SelectTrigger id=\"run-source-id\" aria-label=\"Analysis source\">\n <SelectValue\n placeholder={\n sourceLoading\n ? 'Loading…'\n : `Select a ${sourceKind === 'NOTEBOOK' ? 'notebook' : 'dataset'}`\n }\n />\n </SelectTrigger>\n <SelectContent>\n {sourceKind === 'NOTEBOOK'\n ? notebooks.map((notebook) => (\n <SelectItem key={notebook.id} value={notebook.id}>\n {notebook.title}\n </SelectItem>\n ))\n : datasets.map((dataset) => (\n <SelectItem key={dataset.id} value={dataset.id}>\n {dataset.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"run-method\">Method</Label>\n <Input\n id=\"run-method\"\n value={method}\n onChange={(event) => setMethod(event.target.value)}\n placeholder=\"e.g. gaussian-fit\"\n />\n </div>\n\n <div className=\"flex items-center gap-3\">\n <Switch\n id=\"run-ai\"\n checked={aiAssisted}\n onCheckedChange={(checked) => setAiAssisted(checked)}\n />\n <Label htmlFor=\"run-ai\" className=\"cursor-pointer\">\n AI-assisted analysis\n </Label>\n </div>\n\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n disabled={loading}\n >\n Cancel\n </Button>\n <Button type=\"submit\" disabled={loading}>\n <Play className=\"size-4\" />\n {loading ? 'Starting…' : 'Run analysis'}\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Complete analysis dialog\n// ---------------------------------------------------------------------------\n\ninterface CompleteDialogProps {\n analysis: CosAnalysis | null;\n onOpenChange: (open: boolean) => void;\n onCompleted: () => void;\n}\n\nfunction CompleteAnalysisDialog({ analysis, onOpenChange, onCompleted }: CompleteDialogProps) {\n const [status, setStatus] = useState<'COMPLETED' | 'FAILED'>('COMPLETED');\n const [results, setResults] = useState('');\n const [outputFileId, setOutputFileId] = useState('');\n const [completeAnalysis, { loading }] = useCosCompleteAnalysisMutation();\n\n const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n if (!analysis) {\n return;\n }\n const parsed = parseResults(results);\n if (!parsed.ok) {\n notify.error('Invalid results', parsed.error);\n return;\n }\n const response = await completeAnalysis({\n variables: {\n id: analysis.id,\n status,\n results: parsed.value,\n outputFileId: outputFileId || undefined,\n },\n });\n if (response.error || !response.data?.cosCompleteAnalysis) {\n return;\n }\n notify.success(\n status === 'COMPLETED' ? 'Analysis completed' : 'Analysis marked failed',\n `“${analysis.title}” was updated.`\n );\n onOpenChange(false);\n onCompleted();\n };\n\n return (\n <Dialog\n open={Boolean(analysis)}\n onOpenChange={(next) => {\n if (!loading) {\n onOpenChange(next);\n }\n }}\n >\n <DialogContent>\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n <DialogHeader>\n <DialogTitle>Complete analysis</DialogTitle>\n <DialogDescription>\n Record the outcome for {analysis ? `“${analysis.title}”` : 'this analysis'}. Attach\n results or an output artifact if you have them.\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"complete-status\">Outcome</Label>\n <Select\n value={status}\n onValueChange={(value) => setStatus(value as 'COMPLETED' | 'FAILED')}\n >\n <SelectTrigger id=\"complete-status\" aria-label=\"Outcome\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"COMPLETED\">Completed</SelectItem>\n <SelectItem value=\"FAILED\">Failed</SelectItem>\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"complete-results\">Results (JSON)</Label>\n <Textarea\n id=\"complete-results\"\n value={results}\n onChange={(event) => setResults(event.target.value)}\n rows={4}\n className=\"font-mono text-xs\"\n placeholder='{ \"snr\": 12.4, \"detections\": 3 }'\n />\n <p className=\"text-xs text-text-secondary\">\n Optional structured results as a JSON object.\n </p>\n </div>\n\n <FileUploadField\n value={outputFileId}\n onChange={(fileId) => setOutputFileId(fileId)}\n label=\"Attach output file\"\n hint=\"Optional output artifact (plots, tables, exports). Stored in workspace files.\"\n />\n\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n disabled={loading}\n >\n Cancel\n </Button>\n <Button type=\"submit\" disabled={loading}>\n {loading ? 'Saving…' : 'Save outcome'}\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Page\n// ---------------------------------------------------------------------------\n\nexport default function AnalysesPage() {\n const { navigate } = useCosmicIntersection();\n const [statusFilter, setStatusFilter] = useState<CosAnalysisStatus | undefined>(undefined);\n const [runOpen, setRunOpen] = useState(false);\n const [completing, setCompleting] = useState<CosAnalysis | null>(null);\n const [viewing, setViewing] = useState<CosAnalysis | null>(null);\n\n const { data, loading, error, refetch } = useCosAnalysesQuery({ status: statusFilter });\n const { data: notebooksData } = useCosNotebooksQuery({ pagination: { limit: 100, offset: 0 } });\n const { data: datasetsData } = useCosDatasetsQuery({ pagination: { limit: 100, offset: 0 } });\n\n const analyses = data?.cosListAnalyses ?? [];\n\n const notebookNames = useMemo(() => {\n const map = new Map<string, string>();\n for (const notebook of notebooksData?.cosListNotebooks.items ?? []) {\n map.set(notebook.id, notebook.title);\n }\n return map;\n }, [notebooksData]);\n\n const datasetNames = useMemo(() => {\n const map = new Map<string, string>();\n for (const dataset of datasetsData?.cosListDatasets.items ?? []) {\n map.set(dataset.id, dataset.name);\n }\n return map;\n }, [datasetsData]);\n\n /** Resolve a friendly label + route for an analysis' notebook OR dataset source. */\n const sourceInfo = (analysis: CosAnalysis): { label: string; path: string } | null => {\n if (analysis.notebookId) {\n return {\n label: notebookNames.get(analysis.notebookId) ?? 'Notebook',\n path: `notebooks/${analysis.notebookId}`,\n };\n }\n if (analysis.datasetId) {\n return {\n label: datasetNames.get(analysis.datasetId) ?? 'Dataset',\n path: `datasets/${analysis.datasetId}`,\n };\n }\n return null;\n };\n\n /** Extra ActionMenu items for a row/card (view results + complete). */\n const extraActions = (analysis: CosAnalysis) => {\n const inFlight = analysis.status === 'PENDING' || analysis.status === 'RUNNING';\n return [\n ...(hasViewableResults(analysis)\n ? [\n {\n key: 'view-results',\n label: 'View results',\n icon: BarChart3,\n onSelect: () => setViewing(analysis),\n },\n ]\n : []),\n ...(inFlight\n ? [\n {\n key: 'complete',\n label: 'Complete…',\n icon: Beaker,\n onSelect: () => setCompleting(analysis),\n },\n ]\n : []),\n ];\n };\n\n const actions = (\n <Button type=\"button\" onClick={() => setRunOpen(true)}>\n <Play className=\"size-4\" />\n Run analysis\n </Button>\n );\n\n return (\n <PageLayout\n title=\"Analyses\"\n description=\"Analysis runs executed over notebooks and federated datasets.\"\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=\"analysis-status-filter\">\n Status\n </label>\n <Select\n value={statusFilter ?? ALL}\n onValueChange={(value) =>\n setStatusFilter(value === ALL ? undefined : (value as CosAnalysisStatus))\n }\n >\n <SelectTrigger\n id=\"analysis-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=\"Loading analyses…\" />\n ) : error ? (\n <ErrorState\n message=\"We could not load analyses. Please try again.\"\n onRetry={() => void refetch()}\n />\n ) : analyses.length === 0 ? (\n <EmptyState\n icon={Beaker}\n title={statusFilter ? 'No analyses match this filter.' : 'No analyses yet.'}\n description={\n statusFilter\n ? 'Try a different status filter.'\n : 'Run an analysis against a notebook or dataset to see results here.'\n }\n action={\n statusFilter ? undefined : (\n <Button type=\"button\" onClick={() => setRunOpen(true)}>\n <Play className=\"size-4\" />\n Run analysis\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>Method</TableHead>\n <TableHead>AI</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Source</TableHead>\n <TableHead>Started</TableHead>\n <TableHead>Finished</TableHead>\n <TableHead>Output</TableHead>\n <TableHead className=\"w-12 text-right\">Actions</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {analyses.map((analysis) => {\n const source = sourceInfo(analysis);\n return (\n <TableRow key={analysis.id}>\n <TableCell className=\"font-medium text-text-primary\">\n {analysis.title}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {analysis.method ?? '—'}\n </TableCell>\n <TableCell>\n {analysis.aiAssisted ? (\n <Badge variant=\"secondary\" className=\"gap-1\">\n <Sparkles className=\"size-3\" aria-hidden=\"true\" />\n AI\n </Badge>\n ) : (\n <span className=\"text-text-secondary\">—</span>\n )}\n </TableCell>\n <TableCell>\n <StatusBadge status={analysis.status} />\n </TableCell>\n <TableCell>\n {source ? (\n <button\n type=\"button\"\n onClick={() => navigate(source.path)}\n className=\"inline-flex max-w-[12rem] items-center gap-1 truncate text-sm text-text-primary underline-offset-2 hover:underline\"\n >\n <ExternalLink className=\"size-3.5 shrink-0\" aria-hidden=\"true\" />\n <span className=\"truncate\">{source.label}</span>\n </button>\n ) : (\n <span className=\"text-text-secondary\">—</span>\n )}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-text-secondary\">\n {formatDateTime(analysis.startedAt)}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-text-secondary\">\n {formatDateTime(analysis.finishedAt)}\n </TableCell>\n <TableCell>\n {hasViewableResults(analysis) ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-auto gap-1 px-2 py-1\"\n onClick={() => setViewing(analysis)}\n >\n <BarChart3 className=\"size-3.5\" aria-hidden=\"true\" />\n View\n </Button>\n ) : (\n <span className=\"text-text-secondary\">—</span>\n )}\n </TableCell>\n <TableCell className=\"text-right\">\n <ActionMenu\n ariaLabel={`Actions for ${analysis.title}`}\n onView={source ? () => navigate(source.path) : undefined}\n extraItems={extraActions(analysis)}\n />\n </TableCell>\n </TableRow>\n );\n })}\n </TableBody>\n </Table>\n </div>\n\n {/* Mobile card list */}\n <ul className=\"space-y-3 md:hidden\">\n {analyses.map((analysis) => {\n const source = sourceInfo(analysis);\n const inFlight = analysis.status === 'PENDING' || analysis.status === 'RUNNING';\n return (\n <li\n key={analysis.id}\n className=\"space-y-3 rounded-card border border-border-default bg-bg-elevated p-4\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{analysis.title}</p>\n {analysis.method ? (\n <p className=\"truncate text-xs text-text-secondary\">{analysis.method}</p>\n ) : null}\n </div>\n <StatusBadge status={analysis.status} />\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n {analysis.aiAssisted ? (\n <Badge variant=\"secondary\" className=\"gap-1\">\n <Sparkles className=\"size-3\" aria-hidden=\"true\" />\n AI\n </Badge>\n ) : null}\n {source ? (\n <button\n type=\"button\"\n onClick={() => navigate(source.path)}\n className=\"inline-flex max-w-full items-center gap-1 truncate text-sm text-text-primary underline-offset-2 hover:underline\"\n >\n <ExternalLink className=\"size-3.5 shrink-0\" aria-hidden=\"true\" />\n <span className=\"truncate\">{source.label}</span>\n </button>\n ) : null}\n </div>\n\n <dl className=\"grid grid-cols-2 gap-2 text-xs\">\n <div>\n <dt className=\"text-text-secondary\">Started</dt>\n <dd className=\"text-text-primary\">{formatDateTime(analysis.startedAt)}</dd>\n </div>\n <div>\n <dt className=\"text-text-secondary\">Finished</dt>\n <dd className=\"text-text-primary\">{formatDateTime(analysis.finishedAt)}</dd>\n </div>\n </dl>\n\n {hasViewableResults(analysis) || inFlight ? (\n <div className=\"flex flex-wrap gap-2\">\n {hasViewableResults(analysis) ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setViewing(analysis)}\n >\n <BarChart3 className=\"size-4\" />\n View results\n </Button>\n ) : null}\n {inFlight ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setCompleting(analysis)}\n >\n <Beaker className=\"size-4\" />\n Complete\n </Button>\n ) : null}\n </div>\n ) : null}\n </li>\n );\n })}\n </ul>\n </>\n )}\n </div>\n\n <RunAnalysisDialog\n open={runOpen}\n onOpenChange={setRunOpen}\n onCompleted={() => void refetch()}\n />\n <CompleteAnalysisDialog\n key={completing?.id ?? 'none'}\n analysis={completing}\n onOpenChange={(open) => {\n if (!open) {\n setCompleting(null);\n }\n }}\n onCompleted={() => void refetch()}\n />\n <ResultsViewerDialog\n analysis={viewing}\n onOpenChange={(open) => {\n if (!open) {\n setViewing(null);\n }\n }}\n />\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAgEA,SAAS,EAAmB,GAAgC;CAC1D,OACE,EAAQ,EAAS,YAAY,MAC7B,GAAQ,EAAS,WAAW,OAAO,KAAK,EAAS,OAAO,EAAE,SAAS;AAEvE;AAEA,IAAM,KAAgE;CACpE;EAAE,OAAO;EAAW,OAAO;CAAU;CACrC;EAAE,OAAO;EAAW,OAAO;CAAU;CACrC;EAAE,OAAO;EAAa,OAAO;CAAY;CACzC;EAAE,OAAO;EAAU,OAAO;CAAS;AACrC,GAEM,IAAM;AAEZ,SAAS,EAAe,GAA+B;CACrD,IAAI,CAAC,GACH,OAAO;CAET,IAAM,IAAO,IAAI,KAAK,CAAK;CAI3B,OAHI,OAAO,MAAM,EAAK,QAAQ,CAAC,IACtB,MAEF,EAAK,eAAe,KAAA,GAAW;EACpC,MAAM;EACN,OAAO;EACP,KAAK;EACL,MAAM;EACN,QAAQ;CACV,CAAC;AACH;AAQA,SAAS,EAAe,EAAE,aAA8B;CACtD,IAAM,EAAE,SAAM,eAAY,EAAmB,CAAM,GAC7C,CAAC,GAAe,KAAoB,EAAS,EAAK,GAClD,IAAM,GAAM,sBAAsB;CAQxC,OANI,IACK,kBAAC,QAAD;EAAM,WAAU;YAA8B;CAAqB,CAAA,IAEvE,IAIH,kBAAC,OAAD;EAAK,WAAU;YAAf,CACI,IAOE,OANF,kBAAC,OAAD;GACE,KAAK;GACL,KAAI;GACJ,eAAe,EAAiB,EAAI;GACpC,WAAU;EACX,CAAA,GAEH,kBAAC,KAAD;GACE,MAAM;GACN,QAAO;GACP,KAAI;GACJ,WAAU;aAJZ,CAME,kBAAC,GAAD;IAAU,WAAU;IAAW,eAAY;GAAQ,CAAA,GAAC,iBAEnD;IACA;MArBE,kBAAC,QAAD;EAAM,WAAU;YAAhB,CAA8C,SAAM,CAAa;;AAuB5E;AAYA,SAAS,GAAoB,EAC3B,aACA,mBAIC;CACD,IAAM,IAAU,GAAU,WAAW,MAC/B,IAAa,GAAQ,KAAW,OAAO,KAAK,CAAO,EAAE,SAAS,IAC9D,IAAY,EAAQ,GAAU,YAAY;CAEhD,OACE,kBAAC,GAAD;EAAQ,MAAM,EAAQ;EAAyB;YAC7C,kBAAC,GAAD;GAAe,WAAU;aACtB,IACC,kBAAA,GAAA,EAAA,UAAA;IACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;KAAa,WAAU;eAAvB,CACE,kBAAC,GAAD;MAAW,WAAU;MAA6B,eAAY;KAAQ,CAAA,GAAC,kBAE5D;QACb,kBAAC,GAAD,EAAA,UAAA,CACG,EAAS,OACT,EAAS,SAAS,MAAM,EAAS,WAAW,EAC5B,EAAA,CAAA,CACP,EAAA,CAAA;IAEd,kBAAC,OAAD;KAAK,WAAU;eAAf;MACG,KAAc,IAAU,kBAAC,GAAD,EAAuB,WAAU,CAAA,IAAI;MAE7D,KAAa,EAAS,aACrB,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,KAAD;QAAG,WAAU;kBAAkE;OAE5E,CAAA,GACH,kBAAC,GAAD,EAAgB,QAAQ,EAAS,WAAW,GAAK,CAAA,CAC9C;WACH;MAEH,CAAC,KAAc,CAAC,IACf,kBAAC,KAAD;OAAG,WAAU;iBAA2G;MAErH,CAAA,IACD;KACD;;IAEL,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;KAAa,SAAA;eACX,kBAAC,GAAD;MAAQ,MAAK;MAAS,SAAQ;gBAAU;KAEhC,CAAA;IACG,CAAA,EACD,CAAA;GACd,EAAA,CAAA,IACA;EACS,CAAA;CACT,CAAA;AAEZ;AAGA,SAAS,EACP,GAC8E;CAC9E,IAAM,IAAU,EAAI,KAAK;CACzB,IAAI,CAAC,GACH,OAAO;EAAE,IAAI;EAAM,OAAO,KAAA;CAAU;CAEtC,IAAI;EACF,IAAM,IAAkB,KAAK,MAAM,CAAO;EAI1C,OAHI,OAAO,KAAW,aAAY,KAAmB,MAAM,QAAQ,CAAM,IAChE;GAAE,IAAI;GAAO,OAAO;EAAiC,IAEvD;GAAE,IAAI;GAAM,OAAO;EAAkC;CAC9D,QAAQ;EACN,OAAO;GAAE,IAAI;GAAO,OAAO;EAA8B;CAC3D;AACF;AAcA,SAAS,GAAkB,EAAE,SAAM,iBAAc,kBAA+B;CAC9E,IAAM,CAAC,GAAO,KAAY,EAAS,EAAE,GAC/B,CAAC,GAAQ,KAAa,EAAS,EAAE,GACjC,CAAC,GAAY,KAAiB,EAAS,EAAK,GAC5C,CAAC,GAAY,KAAiB,EAAwB,UAAU,GAChE,CAAC,GAAU,KAAe,EAAS,EAAE,GAErC,EAAE,MAAM,GAAe,SAAS,MAAqB,EAAqB,EAC9E,YAAY;EAAE,OAAO;EAAK,QAAQ;CAAE,EACtC,CAAC,GACK,EAAE,MAAM,GAAc,SAAS,MAAoB,EAAoB,EAC3E,YAAY;EAAE,OAAO;EAAK,QAAQ;CAAE,EACtC,CAAC,GACK,CAAC,GAAa,EAAE,gBAAa,EAA0B,GAEvD,IAAY,GAAe,iBAAiB,SAAS,CAAC,GACtD,IAAW,GAAc,gBAAgB,SAAS,CAAC,GAEnD,UAAc;EAKlB,AAJA,EAAS,EAAE,GACX,EAAU,EAAE,GACZ,EAAc,EAAK,GACnB,EAAc,UAAU,GACxB,EAAY,EAAE;CAChB;CAqCA,OACE,kBAAC,GAAD;EACQ;EACN,eAAe,MAAS;GACtB,AAAK,KACH,EAAa,CAAI;EAErB;YAEA,kBAAC,GAAD,EAAA,UACE,kBAAC,QAAD;GAAM,UAAU,OA7CM,MAAsC;IAChE,EAAM,eAAe;IACrB,IAAM,IAAU,EAAM,KAAK;IAC3B,IAAI,CAAC,GAAS;KACZ,EAAO,MAAM,qBAAqB,wCAAwC;KAC1E;IACF;IACA,IAAI,CAAC,GAAU;KACb,EAAO,MACL,mBACA,YAAY,MAAe,aAAa,aAAa,UAAU,aACjE;KACA;IACF;IACA,IAAM,IAAW,MAAM,EAAY,EACjC,WAAW,EACT,OAAO;KACL,OAAO;KACP,QAAQ,EAAO,KAAK,KAAK,KAAA;KACzB;KACA,GAAI,MAAe,aAAa,EAAE,YAAY,EAAS,IAAI,EAAE,WAAW,EAAS;IACnF,EACF,EACF,CAAC;IACG,EAAS,SAAS,CAAC,EAAS,MAAM,gBAAgB,OAGtD,EAAO,QAAQ,oBAAoB,IAAI,EAAQ,kBAAkB,GACjE,EAAM,GACN,EAAa,EAAK,GAClB,EAAY;GACd;GAcoC,WAAU;aAAxC;IACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAa,eAAyB,CAAA,GACtC,kBAAC,GAAD,EAAA,UAAmB,yFAEA,CAAA,CACP,EAAA,CAAA;IAEd,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAO,SAAQ;gBAAY;KAAY,CAAA,GACvC,kBAAC,GAAD;MACE,IAAG;MACH,OAAO;MACP,WAAW,MAAU,EAAS,EAAM,OAAO,KAAK;MAChD,aAAY;MACZ,UAAA;KACD,CAAA,CACE;;IAEL,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OAAO,SAAQ;iBAAkB;MAAkB,CAAA,GACnD,kBAAC,GAAD;OACE,OAAO;OACP,gBAAgB,MAAU;QAExB,AADA,EAAc,CAAsB,GACpC,EAAY,EAAE;OAChB;iBALF,CAOE,kBAAC,GAAD;QAAe,IAAG;QAAkB,cAAW;kBAC7C,kBAAC,GAAD,CAAc,CAAA;OACD,CAAA,GACf,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;QAAY,OAAM;kBAAW;OAAoB,CAAA,GACjD,kBAAC,GAAD;QAAY,OAAM;kBAAU;OAAmB,CAAA,CAClC,EAAA,CAAA,CACT;QACL;SACL,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OAAO,SAAQ;iBACZ,MAAe,aAAa,aAAa;MACrC,CAAA,GACP,kBAAC,GAAD;OAAQ,OAAO;OAAU,gBAAgB,MAAU,EAAY,CAAK;iBAApE,CACE,kBAAC,GAAD;QAAe,IAAG;QAAgB,cAAW;kBAC3C,kBAAC,GAAD,EACE,cAzDI,MAAe,aAAa,IAAmB,KA2D7C,aACA,YAAY,MAAe,aAAa,aAAa,YAE5D,CAAA;OACY,CAAA,GACf,kBAAC,GAAD,EAAA,UACG,MAAe,aACZ,EAAU,KAAK,MACb,kBAAC,GAAD;QAA8B,OAAO,EAAS;kBAC3C,EAAS;OACA,GAFK,EAAS,EAEd,CACb,IACD,EAAS,KAAK,MACZ,kBAAC,GAAD;QAA6B,OAAO,EAAQ;kBACzC,EAAQ;OACC,GAFK,EAAQ,EAEb,CACb,EACQ,CAAA,CACT;QACL;OACF;;IAEL,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAO,SAAQ;gBAAa;KAAa,CAAA,GACzC,kBAAC,GAAD;MACE,IAAG;MACH,OAAO;MACP,WAAW,MAAU,EAAU,EAAM,OAAO,KAAK;MACjD,aAAY;KACb,CAAA,CACE;;IAEL,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MACE,IAAG;MACH,SAAS;MACT,kBAAkB,MAAY,EAAc,CAAO;KACpD,CAAA,GACD,kBAAC,GAAD;MAAO,SAAQ;MAAS,WAAU;gBAAiB;KAE5C,CAAA,CACJ;;IAEL,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;KACE,MAAK;KACL,SAAQ;KACR,eAAe,EAAa,EAAK;KACjC,UAAU;eACX;IAEO,CAAA,GACR,kBAAC,GAAD;KAAQ,MAAK;KAAS,UAAU;eAAhC,CACE,kBAAC,GAAD,EAAM,WAAU,SAAU,CAAA,GACzB,IAAU,cAAc,cACnB;MACI,EAAA,CAAA;GACV;KACO,CAAA;CACT,CAAA;AAEZ;AAYA,SAAS,GAAuB,EAAE,aAAU,iBAAc,kBAAoC;CAC5F,IAAM,CAAC,GAAQ,KAAa,EAAiC,WAAW,GAClE,CAAC,GAAS,KAAc,EAAS,EAAE,GACnC,CAAC,GAAc,KAAmB,EAAS,EAAE,GAC7C,CAAC,GAAkB,EAAE,gBAAa,EAA+B;CA+BvE,OACE,kBAAC,GAAD;EACE,MAAM,EAAQ;EACd,eAAe,MAAS;GACtB,AAAK,KACH,EAAa,CAAI;EAErB;YAEA,kBAAC,GAAD,EAAA,UACE,kBAAC,QAAD;GAAM,UAAU,OAvCM,MAAsC;IAEhE,IADA,EAAM,eAAe,GACjB,CAAC,GACH;IAEF,IAAM,IAAS,EAAa,CAAO;IACnC,IAAI,CAAC,EAAO,IAAI;KACd,EAAO,MAAM,mBAAmB,EAAO,KAAK;KAC5C;IACF;IACA,IAAM,IAAW,MAAM,EAAiB,EACtC,WAAW;KACT,IAAI,EAAS;KACb;KACA,SAAS,EAAO;KAChB,cAAc,KAAgB,KAAA;IAChC,EACF,CAAC;IACG,EAAS,SAAS,CAAC,EAAS,MAAM,wBAGtC,EAAO,QACL,MAAW,cAAc,uBAAuB,0BAChD,IAAI,EAAS,MAAM,eACrB,GACA,EAAa,EAAK,GAClB,EAAY;GACd;GAYoC,WAAU;aAAxC;IACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAa,oBAA8B,CAAA,GAC3C,kBAAC,GAAD,EAAA,UAAA;KAAmB;KACO,IAAW,IAAI,EAAS,MAAM,KAAK;KAAgB;IAE1D,EAAA,CAAA,CACP,EAAA,CAAA;IAEd,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAO,SAAQ;gBAAkB;KAAc,CAAA,GAC/C,kBAAC,GAAD;MACE,OAAO;MACP,gBAAgB,MAAU,EAAU,CAA+B;gBAFrE,CAIE,kBAAC,GAAD;OAAe,IAAG;OAAkB,cAAW;iBAC7C,kBAAC,GAAD,CAAc,CAAA;MACD,CAAA,GACf,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;OAAY,OAAM;iBAAY;MAAqB,CAAA,GACnD,kBAAC,GAAD;OAAY,OAAM;iBAAS;MAAkB,CAAA,CAChC,EAAA,CAAA,CACT;OACL;;IAEL,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OAAO,SAAQ;iBAAmB;MAAqB,CAAA;MACvD,kBAAC,GAAD;OACE,IAAG;OACH,OAAO;OACP,WAAW,MAAU,EAAW,EAAM,OAAO,KAAK;OAClD,MAAM;OACN,WAAU;OACV,aAAY;MACb,CAAA;MACD,kBAAC,KAAD;OAAG,WAAU;iBAA8B;MAExC,CAAA;KACA;;IAEL,kBAAC,GAAD;KACE,OAAO;KACP,WAAW,MAAW,EAAgB,CAAM;KAC5C,OAAM;KACN,MAAK;IACN,CAAA;IAED,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;KACE,MAAK;KACL,SAAQ;KACR,eAAe,EAAa,EAAK;KACjC,UAAU;eACX;IAEO,CAAA,GACR,kBAAC,GAAD;KAAQ,MAAK;KAAS,UAAU;eAC7B,IAAU,YAAY;IACjB,CAAA,CACI,EAAA,CAAA;GACV;KACO,CAAA;CACT,CAAA;AAEZ;AAMA,SAAwB,KAAe;CACrC,IAAM,EAAE,gBAAa,EAAsB,GACrC,CAAC,GAAc,KAAmB,EAAwC,KAAA,CAAS,GACnF,CAAC,GAAS,KAAc,EAAS,EAAK,GACtC,CAAC,GAAY,KAAiB,EAA6B,IAAI,GAC/D,CAAC,GAAS,KAAc,EAA6B,IAAI,GAEzD,EAAE,SAAM,YAAS,UAAO,eAAY,EAAoB,EAAE,QAAQ,EAAa,CAAC,GAChF,EAAE,MAAM,MAAkB,EAAqB,EAAE,YAAY;EAAE,OAAO;EAAK,QAAQ;CAAE,EAAE,CAAC,GACxF,EAAE,MAAM,MAAiB,EAAoB,EAAE,YAAY;EAAE,OAAO;EAAK,QAAQ;CAAE,EAAE,CAAC,GAEtF,IAAW,GAAM,mBAAmB,CAAC,GAErC,IAAgB,QAAc;EAClC,IAAM,oBAAM,IAAI,IAAoB;EACpC,KAAK,IAAM,KAAY,GAAe,iBAAiB,SAAS,CAAC,GAC/D,EAAI,IAAI,EAAS,IAAI,EAAS,KAAK;EAErC,OAAO;CACT,GAAG,CAAC,CAAa,CAAC,GAEZ,IAAe,QAAc;EACjC,IAAM,oBAAM,IAAI,IAAoB;EACpC,KAAK,IAAM,KAAW,GAAc,gBAAgB,SAAS,CAAC,GAC5D,EAAI,IAAI,EAAQ,IAAI,EAAQ,IAAI;EAElC,OAAO;CACT,GAAG,CAAC,CAAY,CAAC,GAGX,KAAc,MACd,EAAS,aACJ;EACL,OAAO,EAAc,IAAI,EAAS,UAAU,KAAK;EACjD,MAAM,aAAa,EAAS;CAC9B,IAEE,EAAS,YACJ;EACL,OAAO,EAAa,IAAI,EAAS,SAAS,KAAK;EAC/C,MAAM,YAAY,EAAS;CAC7B,IAEK,MAIH,KAAgB,MAA0B;EAC9C,IAAM,IAAW,EAAS,WAAW,aAAa,EAAS,WAAW;EACtE,OAAO,CACL,GAAI,EAAmB,CAAQ,IAC3B,CACE;GACE,KAAK;GACL,OAAO;GACP,MAAM;GACN,gBAAgB,EAAW,CAAQ;EACrC,CACF,IACA,CAAC,GACL,GAAI,IACA,CACE;GACE,KAAK;GACL,OAAO;GACP,MAAM;GACN,gBAAgB,EAAc,CAAQ;EACxC,CACF,IACA,CAAC,CACP;CACF;CASA,OACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACH,SAVX,kBAAC,GAAD;GAAQ,MAAK;GAAS,eAAe,EAAW,EAAI;aAApD,CACE,kBAAC,GAAD,EAAM,WAAU,SAAU,CAAA,GAAC,cAErB;GAOG;YAHX;GAKE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,SAAD;MAAO,WAAU;MAA8B,SAAQ;gBAAyB;KAEzE,CAAA,GACP,kBAAC,GAAD;MACE,OAAO,KAAgB;MACvB,gBAAgB,MACd,EAAgB,MAAU,IAAM,KAAA,IAAa,CAA2B;gBAH5E,CAME,kBAAC,GAAD;OACE,IAAG;OACH,WAAU;OACV,cAAW;iBAEX,kBAAC,GAAD,CAAc,CAAA;MACD,CAAA,GACf,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;OAAY,OAAO;iBAAK;MAAwB,CAAA,GAC/C,GAAe,KAAK,MACnB,kBAAC,GAAD;OAA+B,OAAO,EAAO;iBAC1C,EAAO;MACE,GAFK,EAAO,KAEZ,CACb,CACY,EAAA,CAAA,CACT;OACL;QAEJ,IACC,kBAAC,GAAD;KAAc,SAAQ;KAAO,MAAM;KAAG,OAAM;IAAqB,CAAA,IAC/D,IACF,kBAAC,GAAD;KACE,SAAQ;KACR,eAAe,KAAK,EAAQ;IAC7B,CAAA,IACC,EAAS,WAAW,IACtB,kBAAC,GAAD;KACE,MAAM;KACN,OAAO,IAAe,mCAAmC;KACzD,aACE,IACI,mCACA;KAEN,QACE,IAAe,KAAA,IACb,kBAAC,GAAD;MAAQ,MAAK;MAAS,eAAe,EAAW,EAAI;gBAApD,CACE,kBAAC,GAAD,EAAM,WAAU,SAAU,CAAA,GAAC,cAErB;;IAGb,CAAA,IAED,kBAAA,GAAA,EAAA,UAAA,CAEE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,IAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAA;MACE,kBAAC,GAAD,EAAA,UAAW,QAAgB,CAAA;MAC3B,kBAAC,GAAD,EAAA,UAAW,SAAiB,CAAA;MAC5B,kBAAC,GAAD,EAAA,UAAW,KAAa,CAAA;MACxB,kBAAC,GAAD,EAAA,UAAW,SAAiB,CAAA;MAC5B,kBAAC,GAAD,EAAA,UAAW,SAAiB,CAAA;MAC5B,kBAAC,GAAD,EAAA,UAAW,UAAkB,CAAA;MAC7B,kBAAC,GAAD,EAAA,UAAW,WAAmB,CAAA;MAC9B,kBAAC,GAAD,EAAA,UAAW,SAAiB,CAAA;MAC5B,kBAAC,GAAD;OAAW,WAAU;iBAAkB;MAAkB,CAAA;KACjD,EAAA,CAAA,EACC,CAAA,GACb,kBAAC,IAAD,EAAA,UACG,EAAS,KAAK,MAAa;MAC1B,IAAM,IAAS,EAAW,CAAQ;MAClC,OACE,kBAAC,GAAD,EAAA,UAAA;OACE,kBAAC,GAAD;QAAW,WAAU;kBAClB,EAAS;OACD,CAAA;OACX,kBAAC,GAAD;QAAW,WAAU;kBAClB,EAAS,UAAU;OACX,CAAA;OACX,kBAAC,GAAD,EAAA,UACG,EAAS,aACR,kBAAC,GAAD;QAAO,SAAQ;QAAY,WAAU;kBAArC,CACE,kBAAC,GAAD;SAAU,WAAU;SAAS,eAAY;QAAQ,CAAA,GAAC,IAE7C;YAEP,kBAAC,QAAD;QAAM,WAAU;kBAAsB;OAAO,CAAA,EAEtC,CAAA;OACX,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAa,QAAQ,EAAS,OAAS,CAAA,EAC9B,CAAA;OACX,kBAAC,GAAD,EAAA,UACG,IACC,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAS,EAAO,IAAI;QACnC,WAAU;kBAHZ,CAKE,kBAAC,GAAD;SAAc,WAAU;SAAoB,eAAY;QAAQ,CAAA,GAChE,kBAAC,QAAD;SAAM,WAAU;mBAAY,EAAO;QAAY,CAAA,CACzC;YAER,kBAAC,QAAD;QAAM,WAAU;kBAAsB;OAAO,CAAA,EAEtC,CAAA;OACX,kBAAC,GAAD;QAAW,WAAU;kBAClB,EAAe,EAAS,SAAS;OACzB,CAAA;OACX,kBAAC,GAAD;QAAW,WAAU;kBAClB,EAAe,EAAS,UAAU;OAC1B,CAAA;OACX,kBAAC,GAAD,EAAA,UACG,EAAmB,CAAQ,IAC1B,kBAAC,GAAD;QACE,MAAK;QACL,SAAQ;QACR,MAAK;QACL,WAAU;QACV,eAAe,EAAW,CAAQ;kBALpC,CAOE,kBAAC,GAAD;SAAW,WAAU;SAAW,eAAY;QAAQ,CAAA,GAAC,MAE/C;YAER,kBAAC,QAAD;QAAM,WAAU;kBAAsB;OAAO,CAAA,EAEtC,CAAA;OACX,kBAAC,GAAD;QAAW,WAAU;kBACnB,kBAAC,GAAD;SACE,WAAW,eAAe,EAAS;SACnC,QAAQ,UAAe,EAAS,EAAO,IAAI,IAAI,KAAA;SAC/C,YAAY,EAAa,CAAQ;QAClC,CAAA;OACQ,CAAA;MACH,EAAA,GA/DK,EAAS,EA+Dd;KAEd,CAAC,EACQ,CAAA,CACN,EAAA,CAAA;IACJ,CAAA,GAGL,kBAAC,MAAD;KAAI,WAAU;eACX,EAAS,KAAK,MAAa;MAC1B,IAAM,IAAS,EAAW,CAAQ,GAC5B,IAAW,EAAS,WAAW,aAAa,EAAS,WAAW;MACtE,OACE,kBAAC,MAAD;OAEE,WAAU;iBAFZ;QAIE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,KAAD;WAAG,WAAU;qBAA0C,EAAS;UAAS,CAAA,GACxE,EAAS,SACR,kBAAC,KAAD;WAAG,WAAU;qBAAwC,EAAS;UAAU,CAAA,IACtE,IACD;aACL,kBAAC,GAAD,EAAa,QAAQ,EAAS,OAAS,CAAA,CACpC;;QAEL,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACG,EAAS,aACR,kBAAC,GAAD;UAAO,SAAQ;UAAY,WAAU;oBAArC,CACE,kBAAC,GAAD;WAAU,WAAU;WAAS,eAAY;UAAQ,CAAA,GAAC,IAE7C;cACL,MACH,IACC,kBAAC,UAAD;UACE,MAAK;UACL,eAAe,EAAS,EAAO,IAAI;UACnC,WAAU;oBAHZ,CAKE,kBAAC,GAAD;WAAc,WAAU;WAAoB,eAAY;UAAQ,CAAA,GAChE,kBAAC,QAAD;WAAM,WAAU;qBAAY,EAAO;UAAY,CAAA,CACzC;cACN,IACD;;QAEL,kBAAC,MAAD;SAAI,WAAU;mBAAd,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;UAAI,WAAU;oBAAsB;SAAW,CAAA,GAC/C,kBAAC,MAAD;UAAI,WAAU;oBAAqB,EAAe,EAAS,SAAS;SAAM,CAAA,CACvE,EAAA,CAAA,GACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;UAAI,WAAU;oBAAsB;SAAY,CAAA,GAChD,kBAAC,MAAD;UAAI,WAAU;oBAAqB,EAAe,EAAS,UAAU;SAAM,CAAA,CACxE,EAAA,CAAA,CACH;;QAEH,EAAmB,CAAQ,KAAK,IAC/B,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACG,EAAmB,CAAQ,IAC1B,kBAAC,GAAD;UACE,MAAK;UACL,SAAQ;UACR,MAAK;UACL,eAAe,EAAW,CAAQ;oBAJpC,CAME,kBAAC,GAAD,EAAW,WAAU,SAAU,CAAA,GAAC,cAE1B;cACN,MACH,IACC,kBAAC,GAAD;UACE,MAAK;UACL,SAAQ;UACR,MAAK;UACL,eAAe,EAAc,CAAQ;oBAJvC,CAME,kBAAC,GAAD,EAAQ,WAAU,SAAU,CAAA,GAAC,UAEvB;cACN,IACD;aACH;OACF;SArEG,EAAS,EAqEZ;KAER,CAAC;IACC,CAAA,CACJ,EAAA,CAAA,CAED;;GAEL,kBAAC,IAAD;IACE,MAAM;IACN,cAAc;IACd,mBAAmB,KAAK,EAAQ;GACjC,CAAA;GACD,kBAAC,IAAD;IAEE,UAAU;IACV,eAAe,MAAS;KACtB,AAAK,KACH,EAAc,IAAI;IAEtB;IACA,mBAAmB,KAAK,EAAQ;GACjC,GARM,GAAY,MAAM,MAQxB;GACD,kBAAC,IAAD;IACE,UAAU;IACV,eAAe,MAAS;KACtB,AAAK,KACH,EAAW,IAAI;IAEnB;GACD,CAAA;EACS;;AAEhB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CollectionDetailPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/collections/CollectionDetailPage.tsx"],"sourcesContent":["import { useParams } from 'react-router-dom';\nimport { Database, FolderOpen, Globe, Lock, Pencil } from 'lucide-react';\nimport {\n Badge,\n Button,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosArchiveCollectionMutation,\n useCosCollectionQuery,\n useCosDatasetsQuery,\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';\n\nfunction formatDate(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) {\n return '—';\n }\n return parsed.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });\n}\n\nexport default function CollectionDetailPage() {\n const { id = '' } = useParams<{ id: string }>();\n const { navigate } = useCosmicIntersection();\n\n // Resolve the header from the dedicated single-collection query — works for any\n // collection regardless of list pagination.\n const {\n data: collectionData,\n loading: loadingCollection,\n error: collectionError,\n refetch: refetchCollection,\n } = useCosCollectionQuery(id);\n\n const collection = collectionData?.cosCollection ?? null;\n\n const pagination = usePagination({ pageSize: 20, resetKey: id });\n const {\n data: datasetsData,\n loading: loadingDatasets,\n error: datasetsError,\n refetch: refetchDatasets,\n } = useCosDatasetsQuery({\n collectionId: id,\n pagination: { limit: pagination.limit, offset: pagination.offset },\n });\n\n const datasets = datasetsData?.cosListDatasets.items ?? [];\n const pageInfo = datasetsData?.cosListDatasets.pageInfo;\n\n const [archiveCollection] = useCosArchiveCollectionMutation();\n\n const handleArchive = async () => {\n if (!collection) {\n return;\n }\n const response = await archiveCollection({ variables: { id: collection.id } });\n if (response.error) {\n throw new Error(response.error.message);\n }\n if (!response.data?.cosArchiveCollection) {\n notify.error('Could not archive collection', 'The server did not confirm the change.');\n throw new Error('Archive not confirmed');\n }\n notify.success('Collection archived');\n navigate('collections');\n };\n\n if (loadingCollection) {\n return (\n <PageLayout title=\"Collection\" backPath=\"collections\">\n <LoadingState label=\"Loading collection…\" />\n </PageLayout>\n );\n }\n\n if (collectionError) {\n return (\n <PageLayout title=\"Collection\" backPath=\"collections\">\n <ErrorState message={collectionError.message} onRetry={() => void refetchCollection()} />\n </PageLayout>\n );\n }\n\n if (!collection) {\n return (\n <PageLayout title=\"Collection\" backPath=\"collections\">\n <EmptyState\n icon={FolderOpen}\n title=\"Collection not found\"\n description=\"This collection may have been removed or you may not have access to it.\"\n />\n </PageLayout>\n );\n }\n\n const renderDatasets = () => {\n if (loadingDatasets) {\n return <LoadingState variant=\"rows\" rows={4} label=\"Loading datasets…\" />;\n }\n if (datasetsError) {\n return <ErrorState message={datasetsError.message} onRetry={() => void refetchDatasets()} />;\n }\n if (datasets.length === 0) {\n return (\n <EmptyState\n icon={Database}\n title=\"No datasets in this collection\"\n description=\"Assign a dataset to this collection to see it listed here.\"\n />\n );\n }\n\n return (\n <>\n {/* Desktop / tablet: table */}\n <div className=\"hidden overflow-hidden rounded-card border border-border-default bg-bg-elevated md:block\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Name</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Access</TableHead>\n <TableHead>Format</TableHead>\n <TableHead>Updated</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {datasets.map((dataset) => (\n <TableRow\n key={dataset.id}\n className=\"cursor-pointer\"\n onClick={() => navigate(`datasets/${dataset.id}`)}\n >\n <TableCell className=\"font-medium text-text-primary\">{dataset.name}</TableCell>\n <TableCell>\n <StatusBadge status={dataset.status} />\n </TableCell>\n <TableCell>\n <StatusBadge status={dataset.accessLevel} />\n </TableCell>\n <TableCell className=\"text-sm text-text-secondary\">\n {dataset.format || '—'}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {formatDate(dataset.updatedAt)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n\n {/* Mobile: stacked cards */}\n <ul className=\"space-y-3 md:hidden\">\n {datasets.map((dataset) => (\n <li key={dataset.id}>\n <div\n role=\"button\"\n tabIndex={0}\n aria-label={`Open ${dataset.name}`}\n onClick={() => navigate(`datasets/${dataset.id}`)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n navigate(`datasets/${dataset.id}`);\n }\n }}\n className=\"flex flex-col gap-2 rounded-card border border-border-default bg-bg-elevated p-4 outline-none transition-colors hover:bg-bg-sunken focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <p className=\"font-medium text-text-primary\">{dataset.name}</p>\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={dataset.status} />\n <StatusBadge status={dataset.accessLevel} />\n </div>\n <div className=\"flex items-center justify-between gap-2 text-xs text-text-secondary\">\n <span>{dataset.format || '—'}</span>\n <span>{formatDate(dataset.updatedAt)}</span>\n </div>\n </div>\n </li>\n ))}\n </ul>\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={datasets.length}\n hasMore={pageInfo?.hasMore ?? false}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"datasets\"\n loading={loadingDatasets}\n />\n </>\n );\n };\n\n return (\n <PageLayout\n title={collection.name}\n description={collection.slug ? `/${collection.slug}` : undefined}\n backPath=\"collections\"\n actions={\n <div className=\"flex items-center gap-2\">\n {collection.isPublic ? (\n <Badge\n variant=\"outline\"\n className=\"gap-1 border-status-success-border bg-status-success-bg-subtle text-status-success-text\"\n >\n <Globe className=\"size-3\" aria-hidden=\"true\" />\n Public\n </Badge>\n ) : (\n <Badge variant=\"secondary\" className=\"gap-1\">\n <Lock className=\"size-3\" aria-hidden=\"true\" />\n Private\n </Badge>\n )}\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => navigate(`collections/${collection.id}/edit`)}\n >\n <Pencil className=\"size-4\" />\n Edit\n </Button>\n <ActionMenu\n ariaLabel={`Actions for ${collection.name}`}\n entityName={collection.name}\n onDelete={() => handleArchive()}\n deleteLabel=\"Archive collection\"\n />\n </div>\n }\n >\n <div className=\"space-y-6\">\n <div className=\"rounded-card border border-border-default bg-bg-elevated p-5\">\n <h2 className=\"text-sm font-semibold text-text-primary\">About</h2>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {collection.description ?? 'No description provided.'}\n </p>\n {collection.tags.length > 0 ? (\n <div className=\"mt-4 flex flex-wrap gap-1\">\n {collection.tags.map((tag) => (\n <Badge key={tag} variant=\"outline\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n </div>\n ) : null}\n </div>\n\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <h2 className=\"text-sm font-semibold text-text-primary\">Datasets</h2>\n {!loadingDatasets && !datasetsError && pagination.offset === 0 && !pageInfo?.hasMore ? (\n <Badge variant=\"secondary\" className=\"text-xs\">\n {datasets.length} {datasets.length === 1 ? 'dataset' : 'datasets'}\n </Badge>\n ) : null}\n </div>\n {renderDatasets()}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CollectionDetailPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/collections/CollectionDetailPage.tsx"],"sourcesContent":["import { useParams } from 'react-router-dom';\nimport { Database, FolderOpen, Globe, Lock, Pencil } from 'lucide-react';\nimport {\n Badge,\n Button,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosArchiveCollectionMutation,\n useCosCollectionQuery,\n useCosDatasetsQuery,\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';\n\nfunction formatDate(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) {\n return '—';\n }\n return parsed.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });\n}\n\nexport default function CollectionDetailPage() {\n const { id = '' } = useParams<{ id: string }>();\n const { navigate } = useCosmicIntersection();\n\n // Resolve the header from the dedicated single-collection query — works for any\n // collection regardless of list pagination.\n const {\n data: collectionData,\n loading: loadingCollection,\n error: collectionError,\n refetch: refetchCollection,\n } = useCosCollectionQuery(id);\n\n const collection = collectionData?.cosCollection ?? null;\n\n const pagination = usePagination({ pageSize: 20, resetKey: id });\n const {\n data: datasetsData,\n loading: loadingDatasets,\n error: datasetsError,\n refetch: refetchDatasets,\n } = useCosDatasetsQuery({\n collectionId: id,\n pagination: { limit: pagination.limit, offset: pagination.offset },\n });\n\n const datasets = datasetsData?.cosListDatasets.items ?? [];\n const pageInfo = datasetsData?.cosListDatasets.pageInfo;\n\n const [archiveCollection] = useCosArchiveCollectionMutation();\n\n const handleArchive = async () => {\n if (!collection) {\n return;\n }\n const response = await archiveCollection({ variables: { id: collection.id } });\n if (response.error) {\n throw new Error(response.error.message);\n }\n if (!response.data?.cosArchiveCollection) {\n notify.error('Could not archive collection', 'The server did not confirm the change.');\n throw new Error('Archive not confirmed');\n }\n notify.success('Collection archived');\n navigate('collections');\n };\n\n if (loadingCollection) {\n return (\n <PageLayout title=\"Collection\" backPath=\"collections\">\n <LoadingState label=\"Loading collection…\" />\n </PageLayout>\n );\n }\n\n if (collectionError) {\n return (\n <PageLayout title=\"Collection\" backPath=\"collections\">\n <ErrorState message={collectionError.message} onRetry={() => void refetchCollection()} />\n </PageLayout>\n );\n }\n\n if (!collection) {\n return (\n <PageLayout title=\"Collection\" backPath=\"collections\">\n <EmptyState\n icon={FolderOpen}\n title=\"Collection not found\"\n description=\"This collection may have been removed or you may not have access to it.\"\n />\n </PageLayout>\n );\n }\n\n const renderDatasets = () => {\n if (loadingDatasets) {\n return <LoadingState variant=\"rows\" rows={4} label=\"Loading datasets…\" />;\n }\n if (datasetsError) {\n return <ErrorState message={datasetsError.message} onRetry={() => void refetchDatasets()} />;\n }\n if (datasets.length === 0) {\n return (\n <EmptyState\n icon={Database}\n title=\"No datasets in this collection\"\n description=\"Assign a dataset to this collection to see it listed here.\"\n />\n );\n }\n\n return (\n <>\n {/* Desktop / tablet: table */}\n <div className=\"hidden overflow-hidden rounded-card border border-border-default bg-bg-elevated md:block\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Name</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Access</TableHead>\n <TableHead>Format</TableHead>\n <TableHead>Updated</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {datasets.map((dataset) => (\n <TableRow\n key={dataset.id}\n className=\"cursor-pointer\"\n onClick={() => navigate(`datasets/${dataset.id}`)}\n >\n <TableCell className=\"font-medium text-text-primary\">{dataset.name}</TableCell>\n <TableCell>\n <StatusBadge status={dataset.status} />\n </TableCell>\n <TableCell>\n <StatusBadge status={dataset.accessLevel} />\n </TableCell>\n <TableCell className=\"text-sm text-text-secondary\">\n {dataset.format || '—'}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {formatDate(dataset.updatedAt)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n\n {/* Mobile: stacked cards */}\n <ul className=\"space-y-3 md:hidden\">\n {datasets.map((dataset) => (\n <li key={dataset.id}>\n <div\n role=\"button\"\n tabIndex={0}\n aria-label={`Open ${dataset.name}`}\n onClick={() => navigate(`datasets/${dataset.id}`)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n navigate(`datasets/${dataset.id}`);\n }\n }}\n className=\"flex flex-col gap-2 rounded-card border border-border-default bg-bg-elevated p-4 outline-none transition-colors hover:bg-bg-sunken focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <p className=\"font-medium text-text-primary\">{dataset.name}</p>\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={dataset.status} />\n <StatusBadge status={dataset.accessLevel} />\n </div>\n <div className=\"flex items-center justify-between gap-2 text-xs text-text-secondary\">\n <span>{dataset.format || '—'}</span>\n <span>{formatDate(dataset.updatedAt)}</span>\n </div>\n </div>\n </li>\n ))}\n </ul>\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={datasets.length}\n hasMore={pageInfo?.hasMore ?? false}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"datasets\"\n loading={loadingDatasets}\n />\n </>\n );\n };\n\n return (\n <PageLayout\n title={collection.name}\n description={collection.slug ? `/${collection.slug}` : undefined}\n backPath=\"collections\"\n actions={\n <div className=\"flex items-center gap-2\">\n {collection.isPublic ? (\n <Badge\n variant=\"outline\"\n className=\"gap-1 border-status-success-border bg-status-success-bg-subtle text-status-success-text\"\n >\n <Globe className=\"size-3\" aria-hidden=\"true\" />\n Public\n </Badge>\n ) : (\n <Badge variant=\"secondary\" className=\"gap-1\">\n <Lock className=\"size-3\" aria-hidden=\"true\" />\n Private\n </Badge>\n )}\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => navigate(`collections/${collection.id}/edit`)}\n >\n <Pencil className=\"size-4\" />\n Edit\n </Button>\n <ActionMenu\n ariaLabel={`Actions for ${collection.name}`}\n entityName={collection.name}\n onDelete={() => handleArchive()}\n deleteLabel=\"Archive collection\"\n />\n </div>\n }\n >\n <div className=\"space-y-6\">\n <div className=\"rounded-card border border-border-default bg-bg-elevated p-5\">\n <h2 className=\"text-sm font-semibold text-text-primary\">About</h2>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {collection.description ?? 'No description provided.'}\n </p>\n {collection.tags.length > 0 ? (\n <div className=\"mt-4 flex flex-wrap gap-1\">\n {collection.tags.map((tag) => (\n <Badge key={tag} variant=\"outline\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n </div>\n ) : null}\n </div>\n\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <h2 className=\"text-sm font-semibold text-text-primary\">Datasets</h2>\n {!loadingDatasets && !datasetsError && pagination.offset === 0 && !pageInfo?.hasMore ? (\n <Badge variant=\"secondary\" className=\"text-xs\">\n {datasets.length} {datasets.length === 1 ? 'dataset' : 'datasets'}\n </Badge>\n ) : null}\n </div>\n {renderDatasets()}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|