@burdenoff/microfe-cosmicintersection 2026.805.1 → 2026.830.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/cosmicintersection/api/cosmicApi.d.ts +84 -2
  2. package/dist/cosmicintersection/api/cosmicApi.d.ts.map +1 -1
  3. package/dist/cosmicintersection/api/cosmicApi.js +135 -79
  4. package/dist/cosmicintersection/api/cosmicApi.js.map +1 -1
  5. package/dist/cosmicintersection/components/ActionMenu.d.ts +1 -1
  6. package/dist/cosmicintersection/components/ActionMenu.d.ts.map +1 -1
  7. package/dist/cosmicintersection/components/ActionMenu.js +51 -54
  8. package/dist/cosmicintersection/components/ActionMenu.js.map +1 -1
  9. package/dist/cosmicintersection/components/EntityFormShell.d.ts +1 -1
  10. package/dist/cosmicintersection/components/EntityFormShell.d.ts.map +1 -1
  11. package/dist/cosmicintersection/components/EntityFormShell.js +29 -27
  12. package/dist/cosmicintersection/components/EntityFormShell.js.map +1 -1
  13. package/dist/cosmicintersection/components/ErrorState.d.ts +1 -1
  14. package/dist/cosmicintersection/components/ErrorState.d.ts.map +1 -1
  15. package/dist/cosmicintersection/components/ErrorState.js +19 -17
  16. package/dist/cosmicintersection/components/ErrorState.js.map +1 -1
  17. package/dist/cosmicintersection/components/FileUploadField.d.ts.map +1 -1
  18. package/dist/cosmicintersection/components/FileUploadField.js +61 -60
  19. package/dist/cosmicintersection/components/FileUploadField.js.map +1 -1
  20. package/dist/cosmicintersection/components/LineageGraph.d.ts +6 -1
  21. package/dist/cosmicintersection/components/LineageGraph.d.ts.map +1 -1
  22. package/dist/cosmicintersection/components/LineageGraph.js.map +1 -1
  23. package/dist/cosmicintersection/components/LoadingState.d.ts +1 -1
  24. package/dist/cosmicintersection/components/LoadingState.d.ts.map +1 -1
  25. package/dist/cosmicintersection/components/LoadingState.js +15 -13
  26. package/dist/cosmicintersection/components/LoadingState.js.map +1 -1
  27. package/dist/cosmicintersection/components/PageLayout.d.ts.map +1 -1
  28. package/dist/cosmicintersection/components/PageLayout.js +28 -27
  29. package/dist/cosmicintersection/components/PageLayout.js.map +1 -1
  30. package/dist/cosmicintersection/components/Pagination.d.ts.map +1 -1
  31. package/dist/cosmicintersection/components/Pagination.js +38 -35
  32. package/dist/cosmicintersection/components/Pagination.js.map +1 -1
  33. package/dist/cosmicintersection/components/StatusBadge.d.ts.map +1 -1
  34. package/dist/cosmicintersection/components/StatusBadge.js +30 -9
  35. package/dist/cosmicintersection/components/StatusBadge.js.map +1 -1
  36. package/dist/cosmicintersection/components/StepDAG.d.ts.map +1 -1
  37. package/dist/cosmicintersection/components/StepDAG.js +22 -18
  38. package/dist/cosmicintersection/components/StepDAG.js.map +1 -1
  39. package/dist/cosmicintersection/pages/access/AccessPage.d.ts.map +1 -1
  40. package/dist/cosmicintersection/pages/access/AccessPage.js +147 -146
  41. package/dist/cosmicintersection/pages/access/AccessPage.js.map +1 -1
  42. package/dist/cosmicintersection/pages/analyses/AnalysesPage.d.ts.map +1 -1
  43. package/dist/cosmicintersection/pages/analyses/AnalysesPage.js +260 -259
  44. package/dist/cosmicintersection/pages/analyses/AnalysesPage.js.map +1 -1
  45. package/dist/cosmicintersection/pages/collections/CollectionsPage.d.ts.map +1 -1
  46. package/dist/cosmicintersection/pages/collections/CollectionsPage.js +80 -65
  47. package/dist/cosmicintersection/pages/collections/CollectionsPage.js.map +1 -1
  48. package/dist/cosmicintersection/pages/dashboard/DashboardPage.d.ts.map +1 -1
  49. package/dist/cosmicintersection/pages/dashboard/DashboardPage.js +132 -131
  50. package/dist/cosmicintersection/pages/dashboard/DashboardPage.js.map +1 -1
  51. package/dist/cosmicintersection/pages/datasets/DatasetDetailPage.d.ts.map +1 -1
  52. package/dist/cosmicintersection/pages/datasets/DatasetDetailPage.js +212 -164
  53. package/dist/cosmicintersection/pages/datasets/DatasetDetailPage.js.map +1 -1
  54. package/dist/cosmicintersection/pages/datasets/DatasetsPage.d.ts.map +1 -1
  55. package/dist/cosmicintersection/pages/datasets/DatasetsPage.js +178 -177
  56. package/dist/cosmicintersection/pages/datasets/DatasetsPage.js.map +1 -1
  57. package/dist/cosmicintersection/pages/discovery/DiscoveryPage.d.ts.map +1 -1
  58. package/dist/cosmicintersection/pages/discovery/DiscoveryPage.js +138 -145
  59. package/dist/cosmicintersection/pages/discovery/DiscoveryPage.js.map +1 -1
  60. package/dist/cosmicintersection/pages/lineage/LineagePage.d.ts.map +1 -1
  61. package/dist/cosmicintersection/pages/lineage/LineagePage.js +78 -88
  62. package/dist/cosmicintersection/pages/lineage/LineagePage.js.map +1 -1
  63. package/dist/cosmicintersection/pages/notebooks/NotebooksPage.d.ts.map +1 -1
  64. package/dist/cosmicintersection/pages/notebooks/NotebooksPage.js +125 -124
  65. package/dist/cosmicintersection/pages/notebooks/NotebooksPage.js.map +1 -1
  66. package/dist/cosmicintersection/pages/pipelines/PipelinesPage.d.ts.map +1 -1
  67. package/dist/cosmicintersection/pages/pipelines/PipelinesPage.js +152 -151
  68. package/dist/cosmicintersection/pages/pipelines/PipelinesPage.js.map +1 -1
  69. package/dist/cosmicintersection/pages/releases/ReleasesPage.d.ts.map +1 -1
  70. package/dist/cosmicintersection/pages/releases/ReleasesPage.js +175 -174
  71. package/dist/cosmicintersection/pages/releases/ReleasesPage.js.map +1 -1
  72. package/dist/cosmicintersection/pages/runs/RunDetailPage.d.ts.map +1 -1
  73. package/dist/cosmicintersection/pages/runs/RunDetailPage.js +252 -118
  74. package/dist/cosmicintersection/pages/runs/RunDetailPage.js.map +1 -1
  75. package/dist/cosmicintersection/pages/runs/RunsPage.d.ts.map +1 -1
  76. package/dist/cosmicintersection/pages/runs/RunsPage.js +197 -188
  77. package/dist/cosmicintersection/pages/runs/RunsPage.js.map +1 -1
  78. package/dist/cosmicintersection/pages/sources/SourcesPage.d.ts.map +1 -1
  79. package/dist/cosmicintersection/pages/sources/SourcesPage.js +65 -64
  80. package/dist/cosmicintersection/pages/sources/SourcesPage.js.map +1 -1
  81. package/dist/dev/main.d.ts.map +1 -1
  82. package/dist/shared/hooks/useTr.d.ts +12 -0
  83. package/dist/shared/hooks/useTr.d.ts.map +1 -0
  84. package/dist/shared/hooks/useTr.js +16 -0
  85. package/dist/shared/hooks/useTr.js.map +1 -0
  86. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"EntityFormShell.js","names":[],"sources":["../../../src/cosmicintersection/components/EntityFormShell.tsx"],"sourcesContent":["import type { FormEvent, ReactNode } from 'react';\nimport { ArrowLeft, Save } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\n\nexport interface EntityFormShellProps {\n title: string;\n description?: string;\n /** Guidance copy rendered in a token-styled banner above the form. */\n purpose?: ReactNode;\n backLabel: string;\n onBack: () => void;\n onSubmit: (event: FormEvent<HTMLFormElement>) => void;\n submitLabel: string;\n savingLabel?: string;\n saving?: boolean;\n submitDisabled?: boolean;\n children: ReactNode;\n}\n\n/**\n * Shared CRUD form scaffold: a back link + heading, an optional purpose banner,\n * and a token-styled panel wrapping the fields with a consistent Cancel / Save\n * footer. Error handling stays in the page via the `mutationSucceeded` + `notify`\n * helpers; this shell owns layout and the submit/cancel controls only.\n */\nexport function EntityFormShell({\n title,\n description,\n purpose,\n backLabel,\n onBack,\n onSubmit,\n submitLabel,\n savingLabel,\n saving = false,\n submitDisabled = false,\n children,\n}: EntityFormShellProps) {\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={onBack}>\n <ArrowLeft className=\"size-4\" />\n {backLabel}\n </Button>\n <h1 className=\"mt-3 text-2xl font-bold text-text-primary\">{title}</h1>\n {description ? <p className=\"mt-1 text-sm text-text-secondary\">{description}</p> : null}\n </div>\n\n {purpose ? (\n <div className=\"rounded-card border border-status-info-border bg-status-info-bg-subtle px-4 py-3 text-sm text-text-secondary\">\n {purpose}\n </div>\n ) : null}\n\n <div className=\"rounded-card border border-border-default bg-bg-elevated p-6\">\n <form onSubmit={onSubmit} className=\"space-y-4\">\n {children}\n <div className=\"flex justify-end gap-3\">\n <Button type=\"button\" variant=\"outline\" onClick={onBack} disabled={saving}>\n Cancel\n </Button>\n <Button type=\"submit\" disabled={saving || submitDisabled}>\n <Save className=\"size-4\" />\n {saving ? (savingLabel ?? 'Saving…') : submitLabel}\n </Button>\n </div>\n </form>\n </div>\n </div>\n );\n}\n\nexport default EntityFormShell;\n"],"mappings":";;;;AAyBA,SAAgB,EAAgB,EAC9B,UACA,gBACA,YACA,cACA,WACA,aACA,gBACA,gBACA,YAAS,IACT,oBAAiB,IACjB,eACuB;CACvB,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD,EAAA,UAAA;IACE,kBAAC,GAAD;KAAQ,MAAK;KAAS,SAAQ;KAAQ,MAAK;KAAK,SAAS;eAAzD,CACE,kBAAC,GAAD,EAAW,WAAU,SAAU,CAAA,GAC9B,CACK;;IACR,kBAAC,MAAD;KAAI,WAAU;eAA6C;IAAU,CAAA;IACpE,IAAc,kBAAC,KAAD;KAAG,WAAU;eAAoC;IAAe,CAAA,IAAI;GAChF,EAAA,CAAA;GAEJ,IACC,kBAAC,OAAD;IAAK,WAAU;cACZ;GACE,CAAA,IACH;GAEJ,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,QAAD;KAAgB;KAAU,WAAU;eAApC,CACG,GACD,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OAAQ,MAAK;OAAS,SAAQ;OAAU,SAAS;OAAQ,UAAU;iBAAQ;MAEnE,CAAA,GACR,kBAAC,GAAD;OAAQ,MAAK;OAAS,UAAU,KAAU;iBAA1C,CACE,kBAAC,GAAD,EAAM,WAAU,SAAU,CAAA,GACzB,IAAU,KAAe,YAAa,CACjC;QACL;OACD;;GACH,CAAA;EACF;;AAET"}
1
+ {"version":3,"file":"EntityFormShell.js","names":[],"sources":["../../../src/cosmicintersection/components/EntityFormShell.tsx"],"sourcesContent":["import type { FormEvent, ReactNode } from 'react';\nimport { ArrowLeft, Save } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../../shared/hooks/useTr';\n\nexport interface EntityFormShellProps {\n title: string;\n description?: string;\n /** Guidance copy rendered in a token-styled banner above the form. */\n purpose?: ReactNode;\n backLabel: string;\n onBack: () => void;\n onSubmit: (event: FormEvent<HTMLFormElement>) => void;\n submitLabel: string;\n savingLabel?: string;\n saving?: boolean;\n submitDisabled?: boolean;\n children: ReactNode;\n}\n\n/**\n * Shared CRUD form scaffold: a back link + heading, an optional purpose banner,\n * and a token-styled panel wrapping the fields with a consistent Cancel / Save\n * footer. Error handling stays in the page via the `mutationSucceeded` + `notify`\n * helpers; this shell owns layout and the submit/cancel controls only.\n */\nexport function EntityFormShell({\n title,\n description,\n purpose,\n backLabel,\n onBack,\n onSubmit,\n submitLabel,\n savingLabel: savingLabelProp,\n saving = false,\n submitDisabled = false,\n children,\n}: EntityFormShellProps) {\n const tr = useTr();\n const savingLabel = savingLabelProp ?? tr('cosmicintersection.common.saving', 'Saving…');\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={onBack}>\n <ArrowLeft className=\"size-4\" />\n {backLabel}\n </Button>\n <h1 className=\"mt-3 text-2xl font-bold text-text-primary\">{title}</h1>\n {description ? <p className=\"mt-1 text-sm text-text-secondary\">{description}</p> : null}\n </div>\n\n {purpose ? (\n <div className=\"rounded-card border border-status-info-border bg-status-info-bg-subtle px-4 py-3 text-sm text-text-secondary\">\n {purpose}\n </div>\n ) : null}\n\n <div className=\"rounded-card border border-border-default bg-bg-elevated p-6\">\n <form onSubmit={onSubmit} className=\"space-y-4\">\n {children}\n <div className=\"flex justify-end gap-3\">\n <Button type=\"button\" variant=\"outline\" onClick={onBack} disabled={saving}>\n {tr('cosmicintersection.common.cancel', 'Cancel')}\n </Button>\n <Button type=\"submit\" disabled={saving || submitDisabled}>\n <Save className=\"size-4\" />\n {saving ? savingLabel : submitLabel}\n </Button>\n </div>\n </form>\n </div>\n </div>\n );\n}\n\nexport default EntityFormShell;\n"],"mappings":";;;;;AA0BA,SAAgB,EAAgB,EAC9B,UACA,gBACA,YACA,cACA,WACA,aACA,gBACA,aAAa,GACb,YAAS,IACT,oBAAiB,IACjB,eACuB;CACvB,IAAM,IAAK,EAAM,GACX,IAAc,KAAmB,EAAG,oCAAoC,SAAS;CACvF,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD,EAAA,UAAA;IACE,kBAAC,GAAD;KAAQ,MAAK;KAAS,SAAQ;KAAQ,MAAK;KAAK,SAAS;eAAzD,CACE,kBAAC,GAAD,EAAW,WAAU,SAAU,CAAA,GAC9B,CACK;;IACR,kBAAC,MAAD;KAAI,WAAU;eAA6C;IAAU,CAAA;IACpE,IAAc,kBAAC,KAAD;KAAG,WAAU;eAAoC;IAAe,CAAA,IAAI;GAChF,EAAA,CAAA;GAEJ,IACC,kBAAC,OAAD;IAAK,WAAU;cACZ;GACE,CAAA,IACH;GAEJ,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,QAAD;KAAgB;KAAU,WAAU;eAApC,CACG,GACD,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OAAQ,MAAK;OAAS,SAAQ;OAAU,SAAS;OAAQ,UAAU;iBAChE,EAAG,oCAAoC,QAAQ;MAC1C,CAAA,GACR,kBAAC,GAAD;OAAQ,MAAK;OAAS,UAAU,KAAU;iBAA1C,CACE,kBAAC,GAAD,EAAM,WAAU,SAAU,CAAA,GACzB,IAAS,IAAc,CAClB;QACL;OACD;;GACH,CAAA;EACF;;AAET"}
@@ -9,6 +9,6 @@ export interface ErrorStateProps {
9
9
  * Token-styled error surface for failed queries. Uses the semantic status-error
10
10
  * token family (never raw red) so it stays theme-correct and accessible.
11
11
  */
12
- export declare function ErrorState({ title, message, onRetry, retryLabel, className, }: ErrorStateProps): import("react/jsx-runtime").JSX.Element;
12
+ export declare function ErrorState({ title: titleProp, message: messageProp, onRetry, retryLabel: retryLabelProp, className, }: ErrorStateProps): import("react/jsx-runtime").JSX.Element;
13
13
  export default ErrorState;
14
14
  //# sourceMappingURL=ErrorState.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ErrorState.d.ts","sourceRoot":"","sources":["../../../src/cosmicintersection/components/ErrorState.tsx"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAA8B,EAC9B,OAA0D,EAC1D,OAAO,EACP,UAAoB,EACpB,SAAS,GACV,EAAE,eAAe,2CAoBjB;AAED,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"ErrorState.d.ts","sourceRoot":"","sources":["../../../src/cosmicintersection/components/ErrorState.tsx"],"names":[],"mappings":"AAIA,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,WAAW,EACpB,OAAO,EACP,UAAU,EAAE,cAAc,EAC1B,SAAS,GACV,EAAE,eAAe,2CAwBjB;AAED,eAAe,UAAU,CAAC"}
@@ -1,40 +1,42 @@
1
- import { jsx as e, jsxs as t } from "react/jsx-runtime";
2
- import { AlertTriangle as n } from "lucide-react";
3
- import { Button as r } from "@burdenoff/fe-libs/ui";
1
+ import { useTr as e } from "../../shared/hooks/useTr.js";
2
+ import { jsx as t, jsxs as n } from "react/jsx-runtime";
3
+ import { AlertTriangle as r } from "lucide-react";
4
+ import { Button as i } from "@burdenoff/fe-libs/ui";
4
5
  //#region src/cosmicintersection/components/ErrorState.tsx
5
- function i({ title: i = "Something went wrong", message: a = "We could not load this data. Please try again.", onRetry: o, retryLabel: s = "Retry", className: c }) {
6
- return /* @__PURE__ */ t("div", {
6
+ function a({ title: a, message: o, onRetry: s, retryLabel: c, className: l }) {
7
+ let u = e(), d = a ?? u("cosmicintersection.common.error", "Something went wrong"), f = o ?? u("cosmicintersection.common.errorDescription", "We could not load this data. Please try again."), p = c ?? u("cosmicintersection.common.retry", "Retry");
8
+ return /* @__PURE__ */ n("div", {
7
9
  role: "alert",
8
- className: `flex min-h-[200px] flex-col items-center justify-center gap-3 rounded-card border border-status-error-border bg-status-error-bg-subtle px-6 py-10 text-center ${c ?? ""}`,
10
+ className: `flex min-h-[200px] flex-col items-center justify-center gap-3 rounded-card border border-status-error-border bg-status-error-bg-subtle px-6 py-10 text-center ${l ?? ""}`,
9
11
  children: [
10
- /* @__PURE__ */ e("div", {
12
+ /* @__PURE__ */ t("div", {
11
13
  className: "flex size-12 items-center justify-center rounded-full bg-status-error-bg text-status-error-text",
12
- children: /* @__PURE__ */ e(n, {
14
+ children: /* @__PURE__ */ t(r, {
13
15
  className: "size-6",
14
16
  "aria-hidden": "true"
15
17
  })
16
18
  }),
17
- /* @__PURE__ */ t("div", {
19
+ /* @__PURE__ */ n("div", {
18
20
  className: "space-y-1",
19
- children: [/* @__PURE__ */ e("p", {
21
+ children: [/* @__PURE__ */ t("p", {
20
22
  className: "text-sm font-semibold text-status-error-text",
21
- children: i
22
- }), /* @__PURE__ */ e("p", {
23
+ children: d
24
+ }), /* @__PURE__ */ t("p", {
23
25
  className: "mx-auto max-w-md text-sm text-text-secondary",
24
- children: a
26
+ children: f
25
27
  })]
26
28
  }),
27
- o ? /* @__PURE__ */ e(r, {
29
+ s ? /* @__PURE__ */ t(i, {
28
30
  type: "button",
29
31
  variant: "outline",
30
32
  size: "sm",
31
- onClick: o,
32
- children: s
33
+ onClick: s,
34
+ children: p
33
35
  }) : null
34
36
  ]
35
37
  });
36
38
  }
37
39
  //#endregion
38
- export { i as default };
40
+ export { a as default };
39
41
 
40
42
  //# sourceMappingURL=ErrorState.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ErrorState.js","names":[],"sources":["../../../src/cosmicintersection/components/ErrorState.tsx"],"sourcesContent":["import { AlertTriangle } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\n\nexport interface ErrorStateProps {\n title?: string;\n message?: string;\n onRetry?: () => void;\n retryLabel?: string;\n className?: string;\n}\n\n/**\n * Token-styled error surface for failed queries. Uses the semantic status-error\n * token family (never raw red) so it stays theme-correct and accessible.\n */\nexport function ErrorState({\n title = 'Something went wrong',\n message = 'We could not load this data. Please try again.',\n onRetry,\n retryLabel = 'Retry',\n className,\n}: ErrorStateProps) {\n return (\n <div\n role=\"alert\"\n className={`flex min-h-[200px] flex-col items-center justify-center gap-3 rounded-card border border-status-error-border bg-status-error-bg-subtle px-6 py-10 text-center ${className ?? ''}`}\n >\n <div className=\"flex size-12 items-center justify-center rounded-full bg-status-error-bg text-status-error-text\">\n <AlertTriangle className=\"size-6\" aria-hidden=\"true\" />\n </div>\n <div className=\"space-y-1\">\n <p className=\"text-sm font-semibold text-status-error-text\">{title}</p>\n <p className=\"mx-auto max-w-md text-sm text-text-secondary\">{message}</p>\n </div>\n {onRetry ? (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={onRetry}>\n {retryLabel}\n </Button>\n ) : null}\n </div>\n );\n}\n\nexport default ErrorState;\n"],"mappings":";;;;AAeA,SAAgB,EAAW,EACzB,WAAQ,wBACR,aAAU,kDACV,YACA,gBAAa,SACb,gBACkB;CAClB,OACE,kBAAC,OAAD;EACE,MAAK;EACL,WAAW,iKAAiK,KAAa;YAF3L;GAIE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KAAe,WAAU;KAAS,eAAY;IAAQ,CAAA;GACnD,CAAA;GACL,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAU;eAAgD;IAAS,CAAA,GACtE,kBAAC,KAAD;KAAG,WAAU;eAAgD;IAAW,CAAA,CACrE;;GACJ,IACC,kBAAC,GAAD;IAAQ,MAAK;IAAS,SAAQ;IAAU,MAAK;IAAK,SAAS;cACxD;GACK,CAAA,IACN;EACD;;AAET"}
1
+ {"version":3,"file":"ErrorState.js","names":[],"sources":["../../../src/cosmicintersection/components/ErrorState.tsx"],"sourcesContent":["import { AlertTriangle } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../../shared/hooks/useTr';\n\nexport interface ErrorStateProps {\n title?: string;\n message?: string;\n onRetry?: () => void;\n retryLabel?: string;\n className?: string;\n}\n\n/**\n * Token-styled error surface for failed queries. Uses the semantic status-error\n * token family (never raw red) so it stays theme-correct and accessible.\n */\nexport function ErrorState({\n title: titleProp,\n message: messageProp,\n onRetry,\n retryLabel: retryLabelProp,\n className,\n}: ErrorStateProps) {\n const tr = useTr();\n const title = titleProp ?? tr('cosmicintersection.common.error', 'Something went wrong');\n const message = messageProp ?? tr('cosmicintersection.common.errorDescription', 'We could not load this data. Please try again.');\n const retryLabel = retryLabelProp ?? tr('cosmicintersection.common.retry', 'Retry');\n return (\n <div\n role=\"alert\"\n className={`flex min-h-[200px] flex-col items-center justify-center gap-3 rounded-card border border-status-error-border bg-status-error-bg-subtle px-6 py-10 text-center ${className ?? ''}`}\n >\n <div className=\"flex size-12 items-center justify-center rounded-full bg-status-error-bg text-status-error-text\">\n <AlertTriangle className=\"size-6\" aria-hidden=\"true\" />\n </div>\n <div className=\"space-y-1\">\n <p className=\"text-sm font-semibold text-status-error-text\">{title}</p>\n <p className=\"mx-auto max-w-md text-sm text-text-secondary\">{message}</p>\n </div>\n {onRetry ? (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={onRetry}>\n {retryLabel}\n </Button>\n ) : null}\n </div>\n );\n}\n\nexport default ErrorState;\n"],"mappings":";;;;;AAgBA,SAAgB,EAAW,EACzB,OAAO,GACP,SAAS,GACT,YACA,YAAY,GACZ,gBACkB;CAClB,IAAM,IAAK,EAAM,GACX,IAAQ,KAAa,EAAG,mCAAmC,sBAAsB,GACjF,IAAU,KAAe,EAAG,8CAA8C,gDAAgD,GAC1H,IAAa,KAAkB,EAAG,mCAAmC,OAAO;CAClF,OACE,kBAAC,OAAD;EACE,MAAK;EACL,WAAW,iKAAiK,KAAa;YAF3L;GAIE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KAAe,WAAU;KAAS,eAAY;IAAQ,CAAA;GACnD,CAAA;GACL,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAU;eAAgD;IAAS,CAAA,GACtE,kBAAC,KAAD;KAAG,WAAU;eAAgD;IAAW,CAAA,CACrE;;GACJ,IACC,kBAAC,GAAD;IAAQ,MAAK;IAAS,SAAQ;IAAU,MAAK;IAAK,SAAS;cACxD;GACK,CAAA,IACN;EACD;;AAET"}
@@ -1 +1 @@
1
- {"version":3,"file":"FileUploadField.d.ts","sourceRoot":"","sources":["../../../src/cosmicintersection/components/FileUploadField.tsx"],"names":[],"mappings":"AACA,OAAO,EAAmB,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AA6CjE,eAAO,MAAM,qBAAqB,gCAIjC,CAAC;AAaF,UAAU,mBAAmB;IAC3B,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;QACvB,MAAM;eAAa,MAAM;;QAAzB,MAAM;eAAa,MAAM;GAKrE;AAED,MAAM,WAAW,oBAAoB;IACnC,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,gFAAgF;IAChF,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,QAAQ,EACR,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,QAAgB,EAChB,SAAS,GACV,EAAE,oBAAoB,2CA2JtB;AAED,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"FileUploadField.d.ts","sourceRoot":"","sources":["../../../src/cosmicintersection/components/FileUploadField.tsx"],"names":[],"mappings":"AACA,OAAO,EAAmB,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AA8CjE,eAAO,MAAM,qBAAqB,gCAIjC,CAAC;AAaF,UAAU,mBAAmB;IAC3B,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;QACvB,MAAM;eAAa,MAAM;;QAAzB,MAAM;eAAa,MAAM;GAKrE;AAED,MAAM,WAAW,oBAAoB;IACnC,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,gFAAgF;IAChF,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,QAAQ,EACR,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,QAAgB,EAChB,SAAS,GACV,EAAE,oBAAoB,2CA+JtB;AAED,eAAe,eAAe,CAAC"}
@@ -1,12 +1,13 @@
1
1
  import { notify as e } from "../lib/toast.js";
2
- import { useId as t, useRef as n, useState as r } from "react";
3
- import { jsx as i, jsxs as a } from "react/jsx-runtime";
4
- import { Download as o, FileUp as s, Loader2 as c, Paperclip as l, X as u } from "lucide-react";
5
- import { Button as d } from "@burdenoff/fe-libs/ui";
6
- import { gql as f } from "@apollo/client";
7
- import { useApolloClient as p, useQuery as m } from "@apollo/client/react";
2
+ import { useTr as t } from "../../shared/hooks/useTr.js";
3
+ import { useId as n, useRef as r, useState as i } from "react";
4
+ import { jsx as a, jsxs as o } from "react/jsx-runtime";
5
+ import { Download as s, FileUp as c, Loader2 as l, Paperclip as u, X as d } from "lucide-react";
6
+ import { Button as f } from "@burdenoff/fe-libs/ui";
7
+ import { gql as p } from "@apollo/client";
8
+ import { useApolloClient as m, useQuery as h } from "@apollo/client/react";
8
9
  //#region src/cosmicintersection/components/FileUploadField.tsx
9
- var h = f`
10
+ var g = p`
10
11
  mutation CosInitiateUpload($input: InitiateUploadInput!) {
11
12
  initiateUpload(input: $input) {
12
13
  session {
@@ -15,7 +16,7 @@ var h = f`
15
16
  uploadUrl
16
17
  }
17
18
  }
18
- `, g = f`
19
+ `, _ = p`
19
20
  mutation CosCompleteUpload($input: CompleteUploadInput!) {
20
21
  completeUpload(input: $input) {
21
22
  fileId
@@ -24,123 +25,123 @@ var h = f`
24
25
  }
25
26
  }
26
27
  }
27
- `, _ = f`
28
+ `, v = p`
28
29
  query CosGetFileDownloadUrl($id: ID!, $expiresIn: Int) {
29
30
  getFileDownloadUrl(id: $id, expiresIn: $expiresIn)
30
31
  }
31
- `, v = 3600;
32
- function y(e) {
33
- return m(_, {
32
+ `, y = 3600;
33
+ function b(e) {
34
+ return h(v, {
34
35
  variables: {
35
36
  id: e ?? "",
36
- expiresIn: v
37
+ expiresIn: y
37
38
  },
38
39
  skip: !e,
39
40
  fetchPolicy: "no-cache"
40
41
  });
41
42
  }
42
- function b({ value: f, onChange: m, label: _, hint: v, folderId: b, accept: x, disabled: S = !1, className: C }) {
43
- let w = p(), T = n(null), E = t(), [D, O] = r(!1), [k, A] = r(null), j = y(D ? null : f).data?.getFileDownloadUrl ?? null, M = async (t) => {
43
+ function x({ value: p, onChange: h, label: v, hint: y, folderId: x, accept: S, disabled: C = !1, className: w }) {
44
+ let T = t(), E = m(), D = r(null), O = n(), [k, A] = i(!1), [j, M] = i(null), N = b(k ? null : p).data?.getFileDownloadUrl ?? null, P = async (t) => {
44
45
  let n = t.target.files?.[0];
45
46
  if (t.target.value = "", n) {
46
- O(!0);
47
+ A(!0);
47
48
  try {
48
- let t = n.name.includes(".") ? n.name.substring(n.name.lastIndexOf(".") + 1) : "", r = n.type || "application/octet-stream", i = await w.mutate({
49
- mutation: h,
49
+ let t = n.name.includes(".") ? n.name.substring(n.name.lastIndexOf(".") + 1) : "", r = n.type || "application/octet-stream", i = await E.mutate({
50
+ mutation: g,
50
51
  variables: { input: {
51
52
  fileName: n.name,
52
53
  fileSize: String(n.size),
53
54
  extension: t,
54
55
  mimeType: r,
55
- ...b ? { folderId: b } : {}
56
+ ...x ? { folderId: x } : {}
56
57
  } }
57
58
  }), a = i.data?.initiateUpload?.session, o = i.data?.initiateUpload?.uploadUrl;
58
- if (!a || !o) throw Error(i.error?.message ?? "Could not start the upload.");
59
+ if (!a || !o) throw Error(i.error?.message ?? T("cosmicintersection.common.uploadStartError", "Could not start the upload."));
59
60
  await new Promise((e, t) => {
60
61
  let i = new XMLHttpRequest();
61
62
  i.addEventListener("load", () => {
62
63
  i.status >= 200 && i.status < 300 ? e() : t(/* @__PURE__ */ Error(`Upload failed with status ${i.status}`));
63
64
  }), i.addEventListener("error", () => t(/* @__PURE__ */ Error("Upload network error"))), i.addEventListener("abort", () => t(/* @__PURE__ */ Error("Upload aborted"))), i.open("PUT", o), i.setRequestHeader("Content-Type", r), i.send(n);
64
65
  });
65
- let s = await w.mutate({
66
- mutation: g,
66
+ let s = await E.mutate({
67
+ mutation: _,
67
68
  variables: { input: {
68
69
  sessionId: a.id,
69
- ...b ? { folderId: b } : {}
70
+ ...x ? { folderId: x } : {}
70
71
  } }
71
72
  }), c = s.data?.completeUpload?.fileId ?? s.data?.completeUpload?.file?.id;
72
- if (!c) throw Error(s.error?.message ?? "The file was uploaded but not confirmed.");
73
- A(n.name), await m(c), e.success("File uploaded");
73
+ if (!c) throw Error(s.error?.message ?? T("cosmicintersection.common.uploadConfirmError", "The file was uploaded but not confirmed."));
74
+ M(n.name), await h(c), e.success(T("cosmicintersection.common.uploadSuccess", "File uploaded"));
74
75
  } catch (t) {
75
- e.error("Upload failed", t instanceof Error ? t.message : "Please try again.");
76
+ e.error(T("cosmicintersection.common.uploadFailed", "Upload failed"), t instanceof Error ? t.message : T("cosmicintersection.common.uploadTryAgain", "Please try again."));
76
77
  } finally {
77
- O(!1);
78
+ A(!1);
78
79
  }
79
80
  }
80
- }, N = async () => {
81
- A(null), await m("");
81
+ }, F = async () => {
82
+ M(null), await h("");
82
83
  };
83
- return /* @__PURE__ */ a("div", {
84
- className: C,
85
- children: [/* @__PURE__ */ a("div", {
84
+ return /* @__PURE__ */ o("div", {
85
+ className: w,
86
+ children: [/* @__PURE__ */ o("div", {
86
87
  className: "flex flex-wrap items-center gap-3",
87
- children: [/* @__PURE__ */ i("div", {
88
+ children: [/* @__PURE__ */ a("div", {
88
89
  className: "flex size-12 shrink-0 items-center justify-center rounded-card border border-dashed border-border-default bg-bg-sunken text-text-secondary",
89
- children: /* @__PURE__ */ i(l, {
90
+ children: /* @__PURE__ */ a(u, {
90
91
  className: "size-5",
91
92
  "aria-hidden": "true"
92
93
  })
93
- }), /* @__PURE__ */ a("div", {
94
+ }), /* @__PURE__ */ o("div", {
94
95
  className: "space-y-1",
95
- children: [/* @__PURE__ */ a("div", {
96
+ children: [/* @__PURE__ */ o("div", {
96
97
  className: "flex items-center gap-2",
97
- children: [/* @__PURE__ */ a(d, {
98
+ children: [/* @__PURE__ */ o(f, {
98
99
  type: "button",
99
100
  variant: "secondary",
100
101
  size: "sm",
101
- disabled: S || D,
102
- onClick: () => T.current?.click(),
103
- children: [D ? /* @__PURE__ */ i(c, { className: "size-4 animate-spin" }) : /* @__PURE__ */ i(s, { className: "size-4" }), D ? "Uploading…" : f ? "Replace file" : _]
104
- }), f && !D ? /* @__PURE__ */ a(d, {
102
+ disabled: C || k,
103
+ onClick: () => D.current?.click(),
104
+ children: [k ? /* @__PURE__ */ a(l, { className: "size-4 animate-spin" }) : /* @__PURE__ */ a(c, { className: "size-4" }), k ? T("cosmicintersection.common.uploading", "Uploading…") : p ? T("cosmicintersection.common.replaceFile", "Replace file") : v]
105
+ }), p && !k ? /* @__PURE__ */ o(f, {
105
106
  type: "button",
106
107
  variant: "ghost",
107
108
  size: "sm",
108
- onClick: () => void N(),
109
- disabled: S,
110
- children: [/* @__PURE__ */ i(u, { className: "size-4" }), "Remove"]
109
+ onClick: () => void F(),
110
+ disabled: C,
111
+ children: [/* @__PURE__ */ a(d, { className: "size-4" }), T("cosmicintersection.common.remove", "Remove")]
111
112
  }) : null]
112
- }), f ? /* @__PURE__ */ a("div", {
113
+ }), p ? /* @__PURE__ */ o("div", {
113
114
  className: "flex flex-wrap items-center gap-2 text-xs text-text-secondary",
114
- children: [/* @__PURE__ */ i("span", {
115
+ children: [/* @__PURE__ */ a("span", {
115
116
  className: "max-w-[16rem] truncate",
116
- children: k ?? `File ${f}`
117
- }), j ? /* @__PURE__ */ a("a", {
118
- href: j,
117
+ children: j ?? `File ${p}`
118
+ }), N ? /* @__PURE__ */ o("a", {
119
+ href: N,
119
120
  target: "_blank",
120
121
  rel: "noopener noreferrer",
121
122
  className: "inline-flex items-center gap-1 text-text-primary underline-offset-2 hover:underline",
122
- children: [/* @__PURE__ */ i(o, {
123
+ children: [/* @__PURE__ */ a(s, {
123
124
  className: "size-3.5",
124
125
  "aria-hidden": "true"
125
- }), "Download"]
126
+ }), T("cosmicintersection.common.download", "Download")]
126
127
  }) : null]
127
- }) : v ? /* @__PURE__ */ i("p", {
128
+ }) : y ? /* @__PURE__ */ a("p", {
128
129
  className: "text-xs text-text-secondary",
129
- children: v
130
+ children: y
130
131
  }) : null]
131
132
  })]
132
- }), /* @__PURE__ */ i("input", {
133
- ref: T,
134
- id: E,
133
+ }), /* @__PURE__ */ a("input", {
134
+ ref: D,
135
+ id: O,
135
136
  type: "file",
136
- accept: x,
137
+ accept: S,
137
138
  className: "sr-only",
138
- "aria-label": _,
139
- onChange: (e) => void M(e)
139
+ "aria-label": v,
140
+ onChange: (e) => void P(e)
140
141
  })]
141
142
  });
142
143
  }
143
144
  //#endregion
144
- export { _ as GET_FILE_DOWNLOAD_URL, b as default, y as useFileDownloadUrl };
145
+ export { v as GET_FILE_DOWNLOAD_URL, x as default, b as useFileDownloadUrl };
145
146
 
146
147
  //# sourceMappingURL=FileUploadField.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"FileUploadField.js","names":[],"sources":["../../../src/cosmicintersection/components/FileUploadField.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useApolloClient, useQuery } from '@apollo/client/react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Download, FileUp, Loader2, Paperclip, X } from 'lucide-react';\nimport { useId, useRef, useState } from 'react';\n\nimport { notify } from '../lib/toast';\n\n/**\n * Generic artifact uploader backed by wspace-files-svc.\n *\n * Dataset/notebook/analysis blobs are NOT stored by this MFE — they live in\n * wspace-files as `File` records, and the Cos entities only hold the returned\n * `fileId` (CosDataset.fileId / CosNotebook.fileId / cosCompleteAnalysis.outputFileId).\n * This control performs the shared 3-step presigned upload (initiateUpload → PUT\n * → completeUpload) inline — the same protocol microfe-files uses — and hands the\n * resulting `fileId` back via `onChange`. It never keeps a local blob store and\n * accepts any file type.\n */\n\n// initiateUpload returns a presigned PUT URL + the upload session id.\nconst INITIATE_UPLOAD = gql`\n mutation CosInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n session {\n id\n }\n uploadUrl\n }\n }\n`;\n\n// completeUpload finalizes the session and returns the committed File id.\nconst COMPLETE_UPLOAD = gql`\n mutation CosCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file {\n id\n }\n }\n }\n`;\n\n// Presigned, time-limited download URL for a stored File — used to render a\n// \"Download\" affordance for an already-attached artifact.\nexport const GET_FILE_DOWNLOAD_URL = gql`\n query CosGetFileDownloadUrl($id: ID!, $expiresIn: Int) {\n getFileDownloadUrl(id: $id, expiresIn: $expiresIn)\n }\n`;\n\n// One hour — artifact links are resolved on demand when a detail page renders.\nconst DOWNLOAD_URL_TTL_SECONDS = 3_600;\n\ninterface InitiateUploadResponse {\n initiateUpload?: { session?: { id: string } | null; uploadUrl?: string | null } | null;\n}\n\ninterface CompleteUploadResponse {\n completeUpload?: { fileId?: string | null; file?: { id: string } | null } | null;\n}\n\ninterface DownloadUrlResponse {\n getFileDownloadUrl?: string | null;\n}\n\n/**\n * Resolve a presigned download URL for an attached File id. Skips when no id is\n * present. Detail pages use this to render a \"Download\" link for a dataset/\n * notebook/analysis artifact.\n */\nexport function useFileDownloadUrl(fileId: string | null | undefined) {\n return useQuery<DownloadUrlResponse, { id: string; expiresIn: number }>(GET_FILE_DOWNLOAD_URL, {\n variables: { id: fileId ?? '', expiresIn: DOWNLOAD_URL_TTL_SECONDS },\n skip: !fileId,\n fetchPolicy: 'no-cache',\n });\n}\n\nexport interface FileUploadFieldProps {\n /** Current attached wspace-files File id, if any. */\n value?: string | null;\n /** Called with the new File id once an upload completes, or '' when cleared. */\n onChange: (fileId: string) => void | Promise<void>;\n label: string;\n hint?: string;\n /** Optional wspace-files folder id; when omitted the file lands in the root. */\n folderId?: string;\n /** `accept` attribute for the file input; defaults to any file type. */\n accept?: string;\n disabled?: boolean;\n className?: string;\n}\n\nexport function FileUploadField({\n value,\n onChange,\n label,\n hint,\n folderId,\n accept,\n disabled = false,\n className,\n}: FileUploadFieldProps) {\n const client = useApolloClient();\n const inputRef = useRef<HTMLInputElement | null>(null);\n const inputId = useId();\n const [uploading, setUploading] = useState(false);\n const [fileName, setFileName] = useState<string | null>(null);\n\n const download = useFileDownloadUrl(uploading ? null : value);\n const downloadUrl = download.data?.getFileDownloadUrl ?? null;\n\n const handleSelect = async (event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0];\n // Reset the input so selecting the same file again re-triggers change.\n event.target.value = '';\n if (!file) {\n return;\n }\n\n setUploading(true);\n try {\n const extension = file.name.includes('.')\n ? file.name.substring(file.name.lastIndexOf('.') + 1)\n : '';\n const mimeType = file.type || 'application/octet-stream';\n\n const initiate = await client.mutate<InitiateUploadResponse>({\n mutation: INITIATE_UPLOAD,\n variables: {\n input: {\n fileName: file.name,\n fileSize: String(file.size),\n extension,\n mimeType,\n ...(folderId ? { folderId } : {}),\n },\n },\n });\n const session = initiate.data?.initiateUpload?.session;\n const uploadUrl = initiate.data?.initiateUpload?.uploadUrl;\n if (!session || !uploadUrl) {\n throw new Error(initiate.error?.message ?? 'Could not start the upload.');\n }\n\n await new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.addEventListener('load', () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve();\n } else {\n reject(new Error(`Upload failed with status ${xhr.status}`));\n }\n });\n xhr.addEventListener('error', () => reject(new Error('Upload network error')));\n xhr.addEventListener('abort', () => reject(new Error('Upload aborted')));\n xhr.open('PUT', uploadUrl);\n xhr.setRequestHeader('Content-Type', mimeType);\n xhr.send(file);\n });\n\n const complete = await client.mutate<CompleteUploadResponse>({\n mutation: COMPLETE_UPLOAD,\n variables: {\n input: {\n sessionId: session.id,\n ...(folderId ? { folderId } : {}),\n },\n },\n });\n const uploadedId =\n complete.data?.completeUpload?.fileId ?? complete.data?.completeUpload?.file?.id;\n if (!uploadedId) {\n throw new Error(complete.error?.message ?? 'The file was uploaded but not confirmed.');\n }\n\n setFileName(file.name);\n await onChange(uploadedId);\n notify.success('File uploaded');\n } catch (error) {\n notify.error('Upload failed', error instanceof Error ? error.message : 'Please try again.');\n } finally {\n setUploading(false);\n }\n };\n\n const clear = async () => {\n setFileName(null);\n await onChange('');\n };\n\n return (\n <div className={className}>\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"flex size-12 shrink-0 items-center justify-center rounded-card border border-dashed border-border-default bg-bg-sunken text-text-secondary\">\n <Paperclip className=\"size-5\" aria-hidden=\"true\" />\n </div>\n <div className=\"space-y-1\">\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n disabled={disabled || uploading}\n onClick={() => inputRef.current?.click()}\n >\n {uploading ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <FileUp className=\"size-4\" />\n )}\n {uploading ? 'Uploading…' : value ? 'Replace file' : label}\n </Button>\n {value && !uploading ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => void clear()}\n disabled={disabled}\n >\n <X className=\"size-4\" />\n Remove\n </Button>\n ) : null}\n </div>\n {value ? (\n <div className=\"flex flex-wrap items-center gap-2 text-xs text-text-secondary\">\n <span className=\"max-w-[16rem] truncate\">{fileName ?? `File ${value}`}</span>\n {downloadUrl ? (\n <a\n href={downloadUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1 text-text-primary underline-offset-2 hover:underline\"\n >\n <Download className=\"size-3.5\" aria-hidden=\"true\" />\n Download\n </a>\n ) : null}\n </div>\n ) : hint ? (\n <p className=\"text-xs text-text-secondary\">{hint}</p>\n ) : null}\n </div>\n </div>\n <input\n ref={inputRef}\n id={inputId}\n type=\"file\"\n accept={accept}\n className=\"sr-only\"\n aria-label={label}\n onChange={(event) => void handleSelect(event)}\n />\n </div>\n );\n}\n\nexport default FileUploadField;\n"],"mappings":"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"}
1
+ {"version":3,"file":"FileUploadField.js","names":[],"sources":["../../../src/cosmicintersection/components/FileUploadField.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useApolloClient, useQuery } from '@apollo/client/react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Download, FileUp, Loader2, Paperclip, X } from 'lucide-react';\nimport { useId, useRef, useState } from 'react';\n\nimport { notify } from '../lib/toast';\nimport { useTr } from '../../shared/hooks/useTr';\n\n/**\n * Generic artifact uploader backed by wspace-files-svc.\n *\n * Dataset/notebook/analysis blobs are NOT stored by this MFE — they live in\n * wspace-files as `File` records, and the Cos entities only hold the returned\n * `fileId` (CosDataset.fileId / CosNotebook.fileId / cosCompleteAnalysis.outputFileId).\n * This control performs the shared 3-step presigned upload (initiateUpload → PUT\n * → completeUpload) inline — the same protocol microfe-files uses — and hands the\n * resulting `fileId` back via `onChange`. It never keeps a local blob store and\n * accepts any file type.\n */\n\n// initiateUpload returns a presigned PUT URL + the upload session id.\nconst INITIATE_UPLOAD = gql`\n mutation CosInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n session {\n id\n }\n uploadUrl\n }\n }\n`;\n\n// completeUpload finalizes the session and returns the committed File id.\nconst COMPLETE_UPLOAD = gql`\n mutation CosCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file {\n id\n }\n }\n }\n`;\n\n// Presigned, time-limited download URL for a stored File — used to render a\n// \"Download\" affordance for an already-attached artifact.\nexport const GET_FILE_DOWNLOAD_URL = gql`\n query CosGetFileDownloadUrl($id: ID!, $expiresIn: Int) {\n getFileDownloadUrl(id: $id, expiresIn: $expiresIn)\n }\n`;\n\n// One hour — artifact links are resolved on demand when a detail page renders.\nconst DOWNLOAD_URL_TTL_SECONDS = 3_600;\n\ninterface InitiateUploadResponse {\n initiateUpload?: { session?: { id: string } | null; uploadUrl?: string | null } | null;\n}\n\ninterface CompleteUploadResponse {\n completeUpload?: { fileId?: string | null; file?: { id: string } | null } | null;\n}\n\ninterface DownloadUrlResponse {\n getFileDownloadUrl?: string | null;\n}\n\n/**\n * Resolve a presigned download URL for an attached File id. Skips when no id is\n * present. Detail pages use this to render a \"Download\" link for a dataset/\n * notebook/analysis artifact.\n */\nexport function useFileDownloadUrl(fileId: string | null | undefined) {\n return useQuery<DownloadUrlResponse, { id: string; expiresIn: number }>(GET_FILE_DOWNLOAD_URL, {\n variables: { id: fileId ?? '', expiresIn: DOWNLOAD_URL_TTL_SECONDS },\n skip: !fileId,\n fetchPolicy: 'no-cache',\n });\n}\n\nexport interface FileUploadFieldProps {\n /** Current attached wspace-files File id, if any. */\n value?: string | null;\n /** Called with the new File id once an upload completes, or '' when cleared. */\n onChange: (fileId: string) => void | Promise<void>;\n label: string;\n hint?: string;\n /** Optional wspace-files folder id; when omitted the file lands in the root. */\n folderId?: string;\n /** `accept` attribute for the file input; defaults to any file type. */\n accept?: string;\n disabled?: boolean;\n className?: string;\n}\n\nexport function FileUploadField({\n value,\n onChange,\n label,\n hint,\n folderId,\n accept,\n disabled = false,\n className,\n}: FileUploadFieldProps) {\n const tr = useTr();\n const client = useApolloClient();\n const inputRef = useRef<HTMLInputElement | null>(null);\n const inputId = useId();\n const [uploading, setUploading] = useState(false);\n const [fileName, setFileName] = useState<string | null>(null);\n\n const download = useFileDownloadUrl(uploading ? null : value);\n const downloadUrl = download.data?.getFileDownloadUrl ?? null;\n\n const handleSelect = async (event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0];\n // Reset the input so selecting the same file again re-triggers change.\n event.target.value = '';\n if (!file) {\n return;\n }\n\n setUploading(true);\n try {\n const extension = file.name.includes('.')\n ? file.name.substring(file.name.lastIndexOf('.') + 1)\n : '';\n const mimeType = file.type || 'application/octet-stream';\n\n const initiate = await client.mutate<InitiateUploadResponse>({\n mutation: INITIATE_UPLOAD,\n variables: {\n input: {\n fileName: file.name,\n fileSize: String(file.size),\n extension,\n mimeType,\n ...(folderId ? { folderId } : {}),\n },\n },\n });\n const session = initiate.data?.initiateUpload?.session;\n const uploadUrl = initiate.data?.initiateUpload?.uploadUrl;\n if (!session || !uploadUrl) {\n throw new Error(initiate.error?.message ?? tr('cosmicintersection.common.uploadStartError', 'Could not start the upload.'));\n }\n\n await new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.addEventListener('load', () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve();\n } else {\n reject(new Error(`Upload failed with status ${xhr.status}`));\n }\n });\n xhr.addEventListener('error', () => reject(new Error('Upload network error')));\n xhr.addEventListener('abort', () => reject(new Error('Upload aborted')));\n xhr.open('PUT', uploadUrl);\n xhr.setRequestHeader('Content-Type', mimeType);\n xhr.send(file);\n });\n\n const complete = await client.mutate<CompleteUploadResponse>({\n mutation: COMPLETE_UPLOAD,\n variables: {\n input: {\n sessionId: session.id,\n ...(folderId ? { folderId } : {}),\n },\n },\n });\n const uploadedId =\n complete.data?.completeUpload?.fileId ?? complete.data?.completeUpload?.file?.id;\n if (!uploadedId) {\n throw new Error(complete.error?.message ?? tr('cosmicintersection.common.uploadConfirmError', 'The file was uploaded but not confirmed.'));\n }\n\n setFileName(file.name);\n await onChange(uploadedId);\n notify.success(tr('cosmicintersection.common.uploadSuccess', 'File uploaded'));\n } catch (error) {\n notify.error(\n tr('cosmicintersection.common.uploadFailed', 'Upload failed'),\n error instanceof Error ? error.message : tr('cosmicintersection.common.uploadTryAgain', 'Please try again.'),\n );\n } finally {\n setUploading(false);\n }\n };\n\n const clear = async () => {\n setFileName(null);\n await onChange('');\n };\n\n return (\n <div className={className}>\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"flex size-12 shrink-0 items-center justify-center rounded-card border border-dashed border-border-default bg-bg-sunken text-text-secondary\">\n <Paperclip className=\"size-5\" aria-hidden=\"true\" />\n </div>\n <div className=\"space-y-1\">\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n disabled={disabled || uploading}\n onClick={() => inputRef.current?.click()}\n >\n {uploading ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <FileUp className=\"size-4\" />\n )}\n {uploading ? tr('cosmicintersection.common.uploading', 'Uploading…') : value ? tr('cosmicintersection.common.replaceFile', 'Replace file') : label}\n </Button>\n {value && !uploading ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => void clear()}\n disabled={disabled}\n >\n <X className=\"size-4\" />\n {tr('cosmicintersection.common.remove', 'Remove')}\n </Button>\n ) : null}\n </div>\n {value ? (\n <div className=\"flex flex-wrap items-center gap-2 text-xs text-text-secondary\">\n <span className=\"max-w-[16rem] truncate\">{fileName ?? `File ${value}`}</span>\n {downloadUrl ? (\n <a\n href={downloadUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1 text-text-primary underline-offset-2 hover:underline\"\n >\n <Download className=\"size-3.5\" aria-hidden=\"true\" />\n {tr('cosmicintersection.common.download', 'Download')}\n </a>\n ) : null}\n </div>\n ) : hint ? (\n <p className=\"text-xs text-text-secondary\">{hint}</p>\n ) : null}\n </div>\n </div>\n <input\n ref={inputRef}\n id={inputId}\n type=\"file\"\n accept={accept}\n className=\"sr-only\"\n aria-label={label}\n onChange={(event) => void handleSelect(event)}\n />\n </div>\n );\n}\n\nexport default FileUploadField;\n"],"mappings":"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"}
@@ -29,7 +29,12 @@ export interface LineageModel {
29
29
  export interface LineageDataPools {
30
30
  datasetsById: Map<string, CosDataset>;
31
31
  sourcesById: Map<string, CosSource>;
32
- collectionsById: Map<string, CosCollection>;
32
+ /**
33
+ * Only the collection's display name is read, so this accepts any collection
34
+ * shape carrying it — including the id-only-`datasets` rows the collections
35
+ * list query returns.
36
+ */
37
+ collectionsById: Map<string, Pick<CosCollection, 'id' | 'name'>>;
33
38
  runs: CosPipelineRun[];
34
39
  notebooks: CosNotebook[];
35
40
  analyses: CosAnalysis[];
@@ -1 +1 @@
1
- {"version":3,"file":"LineageGraph.d.ts","sourceRoot":"","sources":["../../../src/cosmicintersection/components/LineageGraph.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EACV,WAAW,EACX,aAAa,EACb,UAAU,EACV,WAAW,EACX,WAAW,EACX,cAAc,EACd,UAAU,EACV,SAAS,EACV,MAAM,kBAAkB,CAAC;AAO1B,MAAM,MAAM,eAAe,GACzB,QAAQ,GAAG,YAAY,GAAG,SAAS,GAAG,KAAK,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;AAEpF,MAAM,WAAW,WAAW;IAC1B,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,eAAe,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wFAAwF;IACxF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,MAAM,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAED,kFAAkF;AAClF,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACtC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACpC,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC5C,IAAI,EAAE,cAAc,EAAE,CAAC;IACvB,SAAS,EAAE,WAAW,EAAE,CAAC;IACzB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CAC1C;AA+MD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,YAAY,CAS5F;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,YAAY,CAyC5F;AAsJD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,YAAY,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,IAAa,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,iBAAiB,kDAsL9F;AAED,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"LineageGraph.d.ts","sourceRoot":"","sources":["../../../src/cosmicintersection/components/LineageGraph.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EACV,WAAW,EACX,aAAa,EACb,UAAU,EACV,WAAW,EACX,WAAW,EACX,cAAc,EACd,UAAU,EACV,SAAS,EACV,MAAM,kBAAkB,CAAC;AAO1B,MAAM,MAAM,eAAe,GACzB,QAAQ,GAAG,YAAY,GAAG,SAAS,GAAG,KAAK,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;AAEpF,MAAM,WAAW,WAAW;IAC1B,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,eAAe,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wFAAwF;IACxF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,MAAM,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAED,kFAAkF;AAClF,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACtC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACpC;;;;OAIG;IACH,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC;IACjE,IAAI,EAAE,cAAc,EAAE,CAAC;IACvB,SAAS,EAAE,WAAW,EAAE,CAAC;IACzB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CAC1C;AA+MD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,YAAY,CAS5F;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,YAAY,CAyC5F;AAsJD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,YAAY,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,IAAa,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,iBAAiB,kDAsL9F;AAED,eAAe,YAAY,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"LineageGraph.js","names":[],"sources":["../../../src/cosmicintersection/components/LineageGraph.tsx"],"sourcesContent":["/**\n * LineageGraph — a reusable, dependency-free provenance DAG for CosmicIntersection.\n *\n * Renders a typed, left-to-right lineage graph (Source → Dataset → Pipeline Run →\n * Notebook / Analysis → Release) as custom inline SVG edges + absolutely-positioned\n * token-styled node cards. No graph library — the layout is a simple column/row\n * solver, edges are cubic-bezier `<path>`s. Every colour comes from fe-libs semantic\n * tokens (bg-*, text-*, border-*, status-*, accent-*) so it renders in every theme.\n *\n * The graph MODEL is derived client-side from existing cos entities by the pure\n * `buildDatasetLineage` / `buildReleaseLineage` builders exported below — the\n * component itself never fetches. Nodes with a `route` are clickable and call\n * `onNavigate` (basePath-relative), matching the rest of the MFE's navigation.\n *\n * Modes:\n * - `full` — page mode (larger cards, legend, dotted canvas). See mockup 20.\n * - `compact` — detail-panel mini map (smaller cards, no legend). See mockup 03.\n */\nimport { useId, useMemo } from 'react';\nimport {\n Boxes,\n Cog,\n Database,\n LineChart,\n NotebookText,\n Radar,\n Tag,\n type LucideIcon,\n} from 'lucide-react';\n\nimport type {\n CosAnalysis,\n CosCollection,\n CosDataset,\n CosNotebook,\n CosPipeline,\n CosPipelineRun,\n CosRelease,\n CosSource,\n} from '../api/cosmicApi';\nimport { cn } from '../utils/cn';\n\n// ============================================================================\n// MODEL\n// ============================================================================\n\nexport type LineageNodeType =\n 'source' | 'collection' | 'dataset' | 'run' | 'notebook' | 'analysis' | 'release';\n\nexport interface LineageNode {\n /** Stable, unique within a model (e.g. `ds:<uuid>`). */\n id: string;\n type: LineageNodeType;\n title: string;\n /** Overrides the default type label (e.g. \"Pipeline Run #340\"). */\n typeLabel?: string;\n /** Small third line — status, version, DOI hint (hidden in compact mode). */\n subtitle?: string;\n /** basePath-relative route; when set (and `onNavigate` given) the node is clickable. */\n route?: string;\n /** Semantic column / layer index, 0 = leftmost (upstream). */\n column: number;\n /** Highlight the graph root (the entity the lineage was requested for). */\n emphasis?: boolean;\n}\n\nexport interface LineageEdge {\n from: string;\n to: string;\n}\n\nexport interface LineageModel {\n nodes: LineageNode[];\n edges: LineageEdge[];\n}\n\n/** Data pools the builders read from — resolved maps + already-filtered lists. */\nexport interface LineageDataPools {\n datasetsById: Map<string, CosDataset>;\n sourcesById: Map<string, CosSource>;\n collectionsById: Map<string, CosCollection>;\n runs: CosPipelineRun[];\n notebooks: CosNotebook[];\n analyses: CosAnalysis[];\n releases: CosRelease[];\n pipelinesById?: Map<string, CosPipeline>;\n}\n\n// ============================================================================\n// BUILDERS (pure — no hooks, no fetching)\n// ============================================================================\n\nconst COLUMN: Record<LineageNodeType, number> = {\n source: 0,\n collection: 0,\n dataset: 1,\n run: 2,\n notebook: 3,\n analysis: 4,\n release: 5,\n};\n\nfunction humanEnum(value: string): string {\n const lower = value.replace(/_/g, ' ').toLowerCase();\n return lower.charAt(0).toUpperCase() + lower.slice(1);\n}\n\n/** Incrementally assembles a de-duplicated node/edge model. */\nclass LineageBuilder {\n private readonly nodes = new Map<string, LineageNode>();\n private readonly edgeKeys = new Set<string>();\n private readonly edges: LineageEdge[] = [];\n\n addNode(node: LineageNode): void {\n const existing = this.nodes.get(node.id);\n if (!existing) {\n this.nodes.set(node.id, node);\n return;\n }\n // Preserve an emphasis flag once set (root wins over a later plain add).\n if (node.emphasis && !existing.emphasis) {\n this.nodes.set(node.id, { ...existing, emphasis: true });\n }\n }\n\n link(from: string, to: string): void {\n if (from === to) {\n return;\n }\n // Only connect endpoints that actually exist in the graph.\n if (!this.nodes.has(from) || !this.nodes.has(to)) {\n return;\n }\n const key = `${from}»${to}`;\n if (this.edgeKeys.has(key)) {\n return;\n }\n this.edgeKeys.add(key);\n this.edges.push({ from, to });\n }\n\n has(id: string): boolean {\n return this.nodes.has(id);\n }\n\n build(): LineageModel {\n return { nodes: [...this.nodes.values()], edges: this.edges };\n }\n}\n\nconst nid = {\n source: (id: string) => `src:${id}`,\n collection: (id: string) => `col:${id}`,\n dataset: (id: string) => `ds:${id}`,\n run: (id: string) => `run:${id}`,\n notebook: (id: string) => `nb:${id}`,\n analysis: (id: string) => `an:${id}`,\n release: (id: string) => `rel:${id}`,\n};\n\n/** Add a dataset node plus its upstream source/collection to the builder. */\nfunction addDatasetUpstream(\n builder: LineageBuilder,\n dataset: CosDataset,\n pools: LineageDataPools,\n emphasis: boolean\n): void {\n builder.addNode({\n id: nid.dataset(dataset.id),\n type: 'dataset',\n title: dataset.name,\n subtitle: humanEnum(dataset.status),\n route: `datasets/${dataset.id}`,\n column: COLUMN.dataset,\n emphasis,\n });\n\n if (dataset.sourceId) {\n const source = pools.sourcesById.get(dataset.sourceId);\n builder.addNode({\n id: nid.source(dataset.sourceId),\n type: 'source',\n title: source?.name ?? 'Linked source',\n subtitle: source ? humanEnum(source.type) : undefined,\n route: `sources/${dataset.sourceId}`,\n column: COLUMN.source,\n });\n builder.link(nid.source(dataset.sourceId), nid.dataset(dataset.id));\n }\n\n if (dataset.collectionId) {\n const collection = pools.collectionsById.get(dataset.collectionId);\n builder.addNode({\n id: nid.collection(dataset.collectionId),\n type: 'collection',\n title: collection?.name ?? 'Collection',\n route: `collections/${dataset.collectionId}`,\n column: COLUMN.collection,\n });\n builder.link(nid.collection(dataset.collectionId), nid.dataset(dataset.id));\n }\n}\n\n/** Add the downstream fan for a dataset: runs, notebooks, analyses, releases. */\nfunction addDatasetDownstream(\n builder: LineageBuilder,\n dataset: CosDataset,\n pools: LineageDataPools\n): void {\n const datasetNode = nid.dataset(dataset.id);\n\n for (const run of pools.runs) {\n if (run.datasetId !== dataset.id) {\n continue;\n }\n const pipelineName = run.pipelineId\n ? pools.pipelinesById?.get(run.pipelineId)?.name\n : undefined;\n builder.addNode({\n id: nid.run(run.id),\n type: 'run',\n typeLabel: `Pipeline Run #${run.runNumber}`,\n title: pipelineName ?? `Run #${run.runNumber}`,\n subtitle: humanEnum(run.status),\n route: `runs/${run.id}`,\n column: COLUMN.run,\n });\n builder.link(datasetNode, nid.run(run.id));\n }\n\n for (const notebook of pools.notebooks) {\n if (!notebook.datasetIds.includes(dataset.id)) {\n continue;\n }\n builder.addNode({\n id: nid.notebook(notebook.id),\n type: 'notebook',\n title: notebook.title,\n subtitle: humanEnum(notebook.status),\n route: `notebooks/${notebook.id}`,\n column: COLUMN.notebook,\n });\n builder.link(datasetNode, nid.notebook(notebook.id));\n }\n\n for (const analysis of pools.analyses) {\n const linkedToNotebook =\n analysis.notebookId != null && builder.has(nid.notebook(analysis.notebookId));\n if (analysis.datasetId !== dataset.id && !linkedToNotebook) {\n continue;\n }\n builder.addNode({\n id: nid.analysis(analysis.id),\n type: 'analysis',\n title: analysis.title,\n subtitle: humanEnum(analysis.status),\n // No per-analysis detail route exists — land on the analyses list.\n route: 'analyses',\n column: COLUMN.analysis,\n });\n if (linkedToNotebook && analysis.notebookId != null) {\n builder.link(nid.notebook(analysis.notebookId), nid.analysis(analysis.id));\n } else {\n builder.link(datasetNode, nid.analysis(analysis.id));\n }\n }\n\n for (const release of pools.releases) {\n if (!release.datasetIds.includes(dataset.id)) {\n continue;\n }\n addReleaseNode(builder, release);\n builder.link(datasetNode, nid.release(release.id));\n if (release.notebookId != null && builder.has(nid.notebook(release.notebookId))) {\n builder.link(nid.notebook(release.notebookId), nid.release(release.id));\n }\n }\n}\n\nfunction addReleaseNode(builder: LineageBuilder, release: CosRelease, emphasis = false): void {\n const doiSuffix = release.doi ? ' · DOI' : '';\n builder.addNode({\n id: nid.release(release.id),\n type: 'release',\n title: release.title,\n subtitle: `v${release.version}${doiSuffix}`,\n // No per-release detail route exists — land on the releases list.\n route: 'releases',\n column: COLUMN.release,\n emphasis,\n });\n}\n\n/**\n * Provenance for a single dataset: its upstream (source + collection) and its\n * full downstream fan (runs, notebooks, analyses, releases). The dataset is the\n * emphasised root.\n */\nexport function buildDatasetLineage(datasetId: string, pools: LineageDataPools): LineageModel {\n const builder = new LineageBuilder();\n const dataset = pools.datasetsById.get(datasetId);\n if (!dataset) {\n return builder.build();\n }\n addDatasetUpstream(builder, dataset, pools, true);\n addDatasetDownstream(builder, dataset, pools);\n return builder.build();\n}\n\n/**\n * Provenance for a release: the release plus its input datasets and notebook,\n * each expanded UPSTREAM (source/collection for datasets; the notebook's own\n * datasets). The release is the emphasised root.\n */\nexport function buildReleaseLineage(releaseId: string, pools: LineageDataPools): LineageModel {\n const builder = new LineageBuilder();\n const release = pools.releases.find((item) => item.id === releaseId);\n if (!release) {\n return builder.build();\n }\n\n addReleaseNode(builder, release, true);\n\n for (const datasetId of release.datasetIds) {\n const dataset = pools.datasetsById.get(datasetId);\n if (!dataset) {\n continue;\n }\n addDatasetUpstream(builder, dataset, pools, false);\n builder.link(nid.dataset(dataset.id), nid.release(release.id));\n }\n\n if (release.notebookId != null) {\n const notebook = pools.notebooks.find((item) => item.id === release.notebookId);\n builder.addNode({\n id: nid.notebook(release.notebookId),\n type: 'notebook',\n title: notebook?.title ?? 'Notebook',\n subtitle: notebook ? humanEnum(notebook.status) : undefined,\n route: `notebooks/${release.notebookId}`,\n column: COLUMN.notebook,\n });\n builder.link(nid.notebook(release.notebookId), nid.release(release.id));\n\n for (const datasetId of notebook?.datasetIds ?? []) {\n const dataset = pools.datasetsById.get(datasetId);\n if (!dataset) {\n continue;\n }\n addDatasetUpstream(builder, dataset, pools, false);\n builder.link(nid.dataset(dataset.id), nid.notebook(release.notebookId));\n }\n }\n\n return builder.build();\n}\n\n// ============================================================================\n// PRESENTATION\n// ============================================================================\n\ninterface NodeMeta {\n label: string;\n Icon: LucideIcon;\n /** Icon-chip classes (semantic accent tokens only). */\n chip: string;\n /** Card border colour class. */\n border: string;\n}\n\nconst NODE_META: Record<LineageNodeType, NodeMeta> = {\n source: {\n label: 'Source',\n Icon: Radar,\n chip: 'bg-accent-blue-subtle text-accent-blue',\n border: 'border-accent-blue',\n },\n collection: {\n label: 'Collection',\n Icon: Boxes,\n chip: 'bg-accent-teal-subtle text-accent-teal',\n border: 'border-accent-teal',\n },\n dataset: {\n label: 'Dataset',\n Icon: Database,\n chip: 'bg-accent-indigo-subtle text-accent-indigo',\n border: 'border-accent-indigo',\n },\n run: {\n label: 'Pipeline Run',\n Icon: Cog,\n chip: 'bg-accent-purple-subtle text-accent-purple',\n border: 'border-accent-purple',\n },\n notebook: {\n label: 'Notebook',\n Icon: NotebookText,\n chip: 'bg-accent-teal-subtle text-accent-teal',\n border: 'border-accent-teal',\n },\n analysis: {\n label: 'Analysis',\n Icon: LineChart,\n chip: 'bg-accent-purple-subtle text-accent-purple',\n border: 'border-accent-purple',\n },\n release: {\n label: 'Release',\n Icon: Tag,\n chip: 'bg-status-success-bg-subtle text-status-success-text',\n border: 'border-status-success-border',\n },\n};\n\ninterface Dims {\n nodeW: number;\n nodeH: number;\n colGap: number;\n rowGap: number;\n pad: number;\n iconSize: string;\n showSubtitle: boolean;\n}\n\nconst DIMS: Record<'full' | 'compact', Dims> = {\n full: {\n nodeW: 190,\n nodeH: 78,\n colGap: 74,\n rowGap: 30,\n pad: 32,\n iconSize: 'size-8',\n showSubtitle: true,\n },\n compact: {\n nodeW: 150,\n nodeH: 54,\n colGap: 46,\n rowGap: 18,\n pad: 18,\n iconSize: 'size-6',\n showSubtitle: false,\n },\n};\n\ninterface Positioned {\n node: LineageNode;\n x: number;\n y: number;\n}\n\ninterface Layout {\n positions: Map<string, Positioned>;\n width: number;\n height: number;\n}\n\nfunction computeLayout(model: LineageModel, dims: Dims): Layout {\n const positions = new Map<string, Positioned>();\n if (model.nodes.length === 0) {\n return { positions, width: dims.pad * 2, height: dims.pad * 2 };\n }\n\n // Present columns, remapped to contiguous indices so empty layers collapse.\n const presentCols = [...new Set(model.nodes.map((n) => n.column))].sort((a, b) => a - b);\n const colOrder = new Map(presentCols.map((c, i) => [c, i]));\n\n const byColumn = new Map<number, LineageNode[]>();\n for (const node of model.nodes) {\n const list = byColumn.get(node.column);\n if (list) {\n list.push(node);\n } else {\n byColumn.set(node.column, [node]);\n }\n }\n\n let contentH = dims.nodeH;\n for (const list of byColumn.values()) {\n const h = list.length * dims.nodeH + (list.length - 1) * dims.rowGap;\n contentH = Math.max(contentH, h);\n }\n\n for (const col of presentCols) {\n const list = byColumn.get(col) ?? [];\n const colH = list.length * dims.nodeH + (list.length - 1) * dims.rowGap;\n const startY = dims.pad + (contentH - colH) / 2;\n const x = dims.pad + (colOrder.get(col) ?? 0) * (dims.nodeW + dims.colGap);\n list.forEach((node, i) => {\n positions.set(node.id, { node, x, y: startY + i * (dims.nodeH + dims.rowGap) });\n });\n }\n\n const width =\n dims.pad * 2 + presentCols.length * dims.nodeW + (presentCols.length - 1) * dims.colGap;\n const height = dims.pad * 2 + contentH;\n return { positions, width, height };\n}\n\nfunction edgePath(x1: number, y1: number, x2: number, y2: number): string {\n const dx = Math.max(24, (x2 - x1) * 0.5);\n return `M ${x1} ${y1} C ${x1 + dx} ${y1}, ${x2 - dx} ${y2}, ${x2} ${y2}`;\n}\n\nexport interface LineageGraphProps {\n model: LineageModel;\n mode?: 'full' | 'compact';\n onNavigate?: (route: string) => void;\n className?: string;\n}\n\nexport function LineageGraph({ model, mode = 'full', onNavigate, className }: LineageGraphProps) {\n const markerId = `cos-lineage-arrow-${useId().replace(/:/g, '')}`;\n const dims = DIMS[mode];\n\n const layout = useMemo(() => computeLayout(model, dims), [model, dims]);\n\n const legendTypes = useMemo(() => {\n const seen = new Set<LineageNodeType>();\n const ordered: LineageNodeType[] = [];\n for (const node of [...model.nodes].sort((a, b) => a.column - b.column)) {\n if (!seen.has(node.type)) {\n seen.add(node.type);\n ordered.push(node.type);\n }\n }\n return ordered;\n }, [model.nodes]);\n\n if (model.nodes.length === 0) {\n return null;\n }\n\n return (\n <div className={cn('flex flex-col gap-3', className)}>\n {mode === 'full' && legendTypes.length > 0 ? (\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-2\">\n {legendTypes.map((type) => {\n const meta = NODE_META[type];\n return (\n <span\n key={type}\n className=\"inline-flex items-center gap-1.5 text-xs text-text-secondary\"\n >\n <span\n className={cn(\n 'flex size-5 items-center justify-center rounded-button',\n meta.chip\n )}\n >\n <meta.Icon className=\"size-3\" aria-hidden=\"true\" />\n </span>\n {meta.label}\n </span>\n );\n })}\n </div>\n ) : null}\n\n <div\n className={cn(\n 'overflow-x-auto rounded-card border border-border-seam bg-bg-canvas',\n mode === 'full' ? 'p-1' : 'p-0.5'\n )}\n >\n <div\n className=\"relative\"\n style={{\n width: layout.width,\n height: layout.height,\n minWidth: '100%',\n backgroundImage: 'radial-gradient(var(--color-border-subtle) 1px, transparent 1px)',\n backgroundSize: mode === 'full' ? '20px 20px' : '16px 16px',\n }}\n >\n {/* Edges (behind the node cards). */}\n <svg\n className=\"pointer-events-none absolute inset-0 text-border-strong\"\n width={layout.width}\n height={layout.height}\n viewBox={`0 0 ${layout.width} ${layout.height}`}\n aria-hidden=\"true\"\n >\n <defs>\n <marker\n id={markerId}\n viewBox=\"0 0 8 8\"\n refX=\"6.5\"\n refY=\"4\"\n markerWidth=\"6\"\n markerHeight=\"6\"\n orient=\"auto-start-reverse\"\n >\n <path d=\"M1 1 L7 4 L1 7 z\" fill=\"currentColor\" />\n </marker>\n </defs>\n {model.edges.map((edge) => {\n const from = layout.positions.get(edge.from);\n const to = layout.positions.get(edge.to);\n if (!from || !to) {\n return null;\n }\n const x1 = from.x + dims.nodeW;\n const y1 = from.y + dims.nodeH / 2;\n const x2 = to.x;\n const y2 = to.y + dims.nodeH / 2;\n return (\n <path\n key={`${edge.from}»${edge.to}`}\n d={edgePath(x1, y1, x2, y2)}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={mode === 'full' ? 1.5 : 1.25}\n strokeLinecap=\"round\"\n markerEnd={`url(#${markerId})`}\n opacity={0.7}\n />\n );\n })}\n </svg>\n\n {/* Node cards. */}\n {[...layout.positions.values()].map(({ node, x, y }) => {\n const meta = NODE_META[node.type];\n const clickable = Boolean(node.route && onNavigate);\n const content = (\n <div\n className={cn(\n 'flex h-full w-full items-center gap-2.5 rounded-card border bg-bg-elevated px-3 shadow-sm transition-shadow',\n meta.border,\n clickable && 'group-hover:shadow-md',\n node.emphasis && 'ring-1 ring-border-strong'\n )}\n >\n <span\n className={cn(\n 'flex shrink-0 items-center justify-center rounded-button',\n dims.iconSize,\n meta.chip\n )}\n aria-hidden=\"true\"\n >\n <meta.Icon className={mode === 'full' ? 'size-4' : 'size-3.5'} />\n </span>\n <span className=\"flex min-w-0 flex-1 flex-col text-left\">\n <span className=\"truncate text-[10px] font-medium uppercase tracking-wide text-text-tertiary\">\n {node.typeLabel ?? meta.label}\n </span>\n <span\n className={cn(\n 'truncate font-semibold text-text-primary',\n mode === 'full' ? 'text-sm' : 'text-xs'\n )}\n >\n {node.title}\n </span>\n {dims.showSubtitle && node.subtitle ? (\n <span className=\"truncate text-[11px] text-text-secondary\">\n {node.subtitle}\n </span>\n ) : null}\n </span>\n </div>\n );\n\n const style = { left: x, top: y, width: dims.nodeW, height: dims.nodeH } as const;\n\n if (clickable && node.route) {\n const route = node.route;\n return (\n <button\n key={node.id}\n type=\"button\"\n onClick={() => onNavigate?.(route)}\n className=\"group absolute rounded-card focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n style={style}\n title={`${node.typeLabel ?? meta.label}: ${node.title}`}\n >\n {content}\n </button>\n );\n }\n\n return (\n <div key={node.id} className=\"absolute\" style={style}>\n {content}\n </div>\n );\n })}\n </div>\n </div>\n </div>\n );\n}\n\nexport default LineageGraph;\n"],"mappings":"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"}
1
+ {"version":3,"file":"LineageGraph.js","names":[],"sources":["../../../src/cosmicintersection/components/LineageGraph.tsx"],"sourcesContent":["/**\n * LineageGraph — a reusable, dependency-free provenance DAG for CosmicIntersection.\n *\n * Renders a typed, left-to-right lineage graph (Source → Dataset → Pipeline Run →\n * Notebook / Analysis → Release) as custom inline SVG edges + absolutely-positioned\n * token-styled node cards. No graph library — the layout is a simple column/row\n * solver, edges are cubic-bezier `<path>`s. Every colour comes from fe-libs semantic\n * tokens (bg-*, text-*, border-*, status-*, accent-*) so it renders in every theme.\n *\n * The graph MODEL is derived client-side from existing cos entities by the pure\n * `buildDatasetLineage` / `buildReleaseLineage` builders exported below — the\n * component itself never fetches. Nodes with a `route` are clickable and call\n * `onNavigate` (basePath-relative), matching the rest of the MFE's navigation.\n *\n * Modes:\n * - `full` — page mode (larger cards, legend, dotted canvas). See mockup 20.\n * - `compact` — detail-panel mini map (smaller cards, no legend). See mockup 03.\n */\nimport { useId, useMemo } from 'react';\nimport {\n Boxes,\n Cog,\n Database,\n LineChart,\n NotebookText,\n Radar,\n Tag,\n type LucideIcon,\n} from 'lucide-react';\n\nimport type {\n CosAnalysis,\n CosCollection,\n CosDataset,\n CosNotebook,\n CosPipeline,\n CosPipelineRun,\n CosRelease,\n CosSource,\n} from '../api/cosmicApi';\nimport { cn } from '../utils/cn';\n\n// ============================================================================\n// MODEL\n// ============================================================================\n\nexport type LineageNodeType =\n 'source' | 'collection' | 'dataset' | 'run' | 'notebook' | 'analysis' | 'release';\n\nexport interface LineageNode {\n /** Stable, unique within a model (e.g. `ds:<uuid>`). */\n id: string;\n type: LineageNodeType;\n title: string;\n /** Overrides the default type label (e.g. \"Pipeline Run #340\"). */\n typeLabel?: string;\n /** Small third line — status, version, DOI hint (hidden in compact mode). */\n subtitle?: string;\n /** basePath-relative route; when set (and `onNavigate` given) the node is clickable. */\n route?: string;\n /** Semantic column / layer index, 0 = leftmost (upstream). */\n column: number;\n /** Highlight the graph root (the entity the lineage was requested for). */\n emphasis?: boolean;\n}\n\nexport interface LineageEdge {\n from: string;\n to: string;\n}\n\nexport interface LineageModel {\n nodes: LineageNode[];\n edges: LineageEdge[];\n}\n\n/** Data pools the builders read from — resolved maps + already-filtered lists. */\nexport interface LineageDataPools {\n datasetsById: Map<string, CosDataset>;\n sourcesById: Map<string, CosSource>;\n /**\n * Only the collection's display name is read, so this accepts any collection\n * shape carrying it — including the id-only-`datasets` rows the collections\n * list query returns.\n */\n collectionsById: Map<string, Pick<CosCollection, 'id' | 'name'>>;\n runs: CosPipelineRun[];\n notebooks: CosNotebook[];\n analyses: CosAnalysis[];\n releases: CosRelease[];\n pipelinesById?: Map<string, CosPipeline>;\n}\n\n// ============================================================================\n// BUILDERS (pure — no hooks, no fetching)\n// ============================================================================\n\nconst COLUMN: Record<LineageNodeType, number> = {\n source: 0,\n collection: 0,\n dataset: 1,\n run: 2,\n notebook: 3,\n analysis: 4,\n release: 5,\n};\n\nfunction humanEnum(value: string): string {\n const lower = value.replace(/_/g, ' ').toLowerCase();\n return lower.charAt(0).toUpperCase() + lower.slice(1);\n}\n\n/** Incrementally assembles a de-duplicated node/edge model. */\nclass LineageBuilder {\n private readonly nodes = new Map<string, LineageNode>();\n private readonly edgeKeys = new Set<string>();\n private readonly edges: LineageEdge[] = [];\n\n addNode(node: LineageNode): void {\n const existing = this.nodes.get(node.id);\n if (!existing) {\n this.nodes.set(node.id, node);\n return;\n }\n // Preserve an emphasis flag once set (root wins over a later plain add).\n if (node.emphasis && !existing.emphasis) {\n this.nodes.set(node.id, { ...existing, emphasis: true });\n }\n }\n\n link(from: string, to: string): void {\n if (from === to) {\n return;\n }\n // Only connect endpoints that actually exist in the graph.\n if (!this.nodes.has(from) || !this.nodes.has(to)) {\n return;\n }\n const key = `${from}»${to}`;\n if (this.edgeKeys.has(key)) {\n return;\n }\n this.edgeKeys.add(key);\n this.edges.push({ from, to });\n }\n\n has(id: string): boolean {\n return this.nodes.has(id);\n }\n\n build(): LineageModel {\n return { nodes: [...this.nodes.values()], edges: this.edges };\n }\n}\n\nconst nid = {\n source: (id: string) => `src:${id}`,\n collection: (id: string) => `col:${id}`,\n dataset: (id: string) => `ds:${id}`,\n run: (id: string) => `run:${id}`,\n notebook: (id: string) => `nb:${id}`,\n analysis: (id: string) => `an:${id}`,\n release: (id: string) => `rel:${id}`,\n};\n\n/** Add a dataset node plus its upstream source/collection to the builder. */\nfunction addDatasetUpstream(\n builder: LineageBuilder,\n dataset: CosDataset,\n pools: LineageDataPools,\n emphasis: boolean\n): void {\n builder.addNode({\n id: nid.dataset(dataset.id),\n type: 'dataset',\n title: dataset.name,\n subtitle: humanEnum(dataset.status),\n route: `datasets/${dataset.id}`,\n column: COLUMN.dataset,\n emphasis,\n });\n\n if (dataset.sourceId) {\n const source = pools.sourcesById.get(dataset.sourceId);\n builder.addNode({\n id: nid.source(dataset.sourceId),\n type: 'source',\n title: source?.name ?? 'Linked source',\n subtitle: source ? humanEnum(source.type) : undefined,\n route: `sources/${dataset.sourceId}`,\n column: COLUMN.source,\n });\n builder.link(nid.source(dataset.sourceId), nid.dataset(dataset.id));\n }\n\n if (dataset.collectionId) {\n const collection = pools.collectionsById.get(dataset.collectionId);\n builder.addNode({\n id: nid.collection(dataset.collectionId),\n type: 'collection',\n title: collection?.name ?? 'Collection',\n route: `collections/${dataset.collectionId}`,\n column: COLUMN.collection,\n });\n builder.link(nid.collection(dataset.collectionId), nid.dataset(dataset.id));\n }\n}\n\n/** Add the downstream fan for a dataset: runs, notebooks, analyses, releases. */\nfunction addDatasetDownstream(\n builder: LineageBuilder,\n dataset: CosDataset,\n pools: LineageDataPools\n): void {\n const datasetNode = nid.dataset(dataset.id);\n\n for (const run of pools.runs) {\n if (run.datasetId !== dataset.id) {\n continue;\n }\n const pipelineName = run.pipelineId\n ? pools.pipelinesById?.get(run.pipelineId)?.name\n : undefined;\n builder.addNode({\n id: nid.run(run.id),\n type: 'run',\n typeLabel: `Pipeline Run #${run.runNumber}`,\n title: pipelineName ?? `Run #${run.runNumber}`,\n subtitle: humanEnum(run.status),\n route: `runs/${run.id}`,\n column: COLUMN.run,\n });\n builder.link(datasetNode, nid.run(run.id));\n }\n\n for (const notebook of pools.notebooks) {\n if (!notebook.datasetIds.includes(dataset.id)) {\n continue;\n }\n builder.addNode({\n id: nid.notebook(notebook.id),\n type: 'notebook',\n title: notebook.title,\n subtitle: humanEnum(notebook.status),\n route: `notebooks/${notebook.id}`,\n column: COLUMN.notebook,\n });\n builder.link(datasetNode, nid.notebook(notebook.id));\n }\n\n for (const analysis of pools.analyses) {\n const linkedToNotebook =\n analysis.notebookId != null && builder.has(nid.notebook(analysis.notebookId));\n if (analysis.datasetId !== dataset.id && !linkedToNotebook) {\n continue;\n }\n builder.addNode({\n id: nid.analysis(analysis.id),\n type: 'analysis',\n title: analysis.title,\n subtitle: humanEnum(analysis.status),\n // No per-analysis detail route exists — land on the analyses list.\n route: 'analyses',\n column: COLUMN.analysis,\n });\n if (linkedToNotebook && analysis.notebookId != null) {\n builder.link(nid.notebook(analysis.notebookId), nid.analysis(analysis.id));\n } else {\n builder.link(datasetNode, nid.analysis(analysis.id));\n }\n }\n\n for (const release of pools.releases) {\n if (!release.datasetIds.includes(dataset.id)) {\n continue;\n }\n addReleaseNode(builder, release);\n builder.link(datasetNode, nid.release(release.id));\n if (release.notebookId != null && builder.has(nid.notebook(release.notebookId))) {\n builder.link(nid.notebook(release.notebookId), nid.release(release.id));\n }\n }\n}\n\nfunction addReleaseNode(builder: LineageBuilder, release: CosRelease, emphasis = false): void {\n const doiSuffix = release.doi ? ' · DOI' : '';\n builder.addNode({\n id: nid.release(release.id),\n type: 'release',\n title: release.title,\n subtitle: `v${release.version}${doiSuffix}`,\n // No per-release detail route exists — land on the releases list.\n route: 'releases',\n column: COLUMN.release,\n emphasis,\n });\n}\n\n/**\n * Provenance for a single dataset: its upstream (source + collection) and its\n * full downstream fan (runs, notebooks, analyses, releases). The dataset is the\n * emphasised root.\n */\nexport function buildDatasetLineage(datasetId: string, pools: LineageDataPools): LineageModel {\n const builder = new LineageBuilder();\n const dataset = pools.datasetsById.get(datasetId);\n if (!dataset) {\n return builder.build();\n }\n addDatasetUpstream(builder, dataset, pools, true);\n addDatasetDownstream(builder, dataset, pools);\n return builder.build();\n}\n\n/**\n * Provenance for a release: the release plus its input datasets and notebook,\n * each expanded UPSTREAM (source/collection for datasets; the notebook's own\n * datasets). The release is the emphasised root.\n */\nexport function buildReleaseLineage(releaseId: string, pools: LineageDataPools): LineageModel {\n const builder = new LineageBuilder();\n const release = pools.releases.find((item) => item.id === releaseId);\n if (!release) {\n return builder.build();\n }\n\n addReleaseNode(builder, release, true);\n\n for (const datasetId of release.datasetIds) {\n const dataset = pools.datasetsById.get(datasetId);\n if (!dataset) {\n continue;\n }\n addDatasetUpstream(builder, dataset, pools, false);\n builder.link(nid.dataset(dataset.id), nid.release(release.id));\n }\n\n if (release.notebookId != null) {\n const notebook = pools.notebooks.find((item) => item.id === release.notebookId);\n builder.addNode({\n id: nid.notebook(release.notebookId),\n type: 'notebook',\n title: notebook?.title ?? 'Notebook',\n subtitle: notebook ? humanEnum(notebook.status) : undefined,\n route: `notebooks/${release.notebookId}`,\n column: COLUMN.notebook,\n });\n builder.link(nid.notebook(release.notebookId), nid.release(release.id));\n\n for (const datasetId of notebook?.datasetIds ?? []) {\n const dataset = pools.datasetsById.get(datasetId);\n if (!dataset) {\n continue;\n }\n addDatasetUpstream(builder, dataset, pools, false);\n builder.link(nid.dataset(dataset.id), nid.notebook(release.notebookId));\n }\n }\n\n return builder.build();\n}\n\n// ============================================================================\n// PRESENTATION\n// ============================================================================\n\ninterface NodeMeta {\n label: string;\n Icon: LucideIcon;\n /** Icon-chip classes (semantic accent tokens only). */\n chip: string;\n /** Card border colour class. */\n border: string;\n}\n\nconst NODE_META: Record<LineageNodeType, NodeMeta> = {\n source: {\n label: 'Source',\n Icon: Radar,\n chip: 'bg-accent-blue-subtle text-accent-blue',\n border: 'border-accent-blue',\n },\n collection: {\n label: 'Collection',\n Icon: Boxes,\n chip: 'bg-accent-teal-subtle text-accent-teal',\n border: 'border-accent-teal',\n },\n dataset: {\n label: 'Dataset',\n Icon: Database,\n chip: 'bg-accent-indigo-subtle text-accent-indigo',\n border: 'border-accent-indigo',\n },\n run: {\n label: 'Pipeline Run',\n Icon: Cog,\n chip: 'bg-accent-purple-subtle text-accent-purple',\n border: 'border-accent-purple',\n },\n notebook: {\n label: 'Notebook',\n Icon: NotebookText,\n chip: 'bg-accent-teal-subtle text-accent-teal',\n border: 'border-accent-teal',\n },\n analysis: {\n label: 'Analysis',\n Icon: LineChart,\n chip: 'bg-accent-purple-subtle text-accent-purple',\n border: 'border-accent-purple',\n },\n release: {\n label: 'Release',\n Icon: Tag,\n chip: 'bg-status-success-bg-subtle text-status-success-text',\n border: 'border-status-success-border',\n },\n};\n\ninterface Dims {\n nodeW: number;\n nodeH: number;\n colGap: number;\n rowGap: number;\n pad: number;\n iconSize: string;\n showSubtitle: boolean;\n}\n\nconst DIMS: Record<'full' | 'compact', Dims> = {\n full: {\n nodeW: 190,\n nodeH: 78,\n colGap: 74,\n rowGap: 30,\n pad: 32,\n iconSize: 'size-8',\n showSubtitle: true,\n },\n compact: {\n nodeW: 150,\n nodeH: 54,\n colGap: 46,\n rowGap: 18,\n pad: 18,\n iconSize: 'size-6',\n showSubtitle: false,\n },\n};\n\ninterface Positioned {\n node: LineageNode;\n x: number;\n y: number;\n}\n\ninterface Layout {\n positions: Map<string, Positioned>;\n width: number;\n height: number;\n}\n\nfunction computeLayout(model: LineageModel, dims: Dims): Layout {\n const positions = new Map<string, Positioned>();\n if (model.nodes.length === 0) {\n return { positions, width: dims.pad * 2, height: dims.pad * 2 };\n }\n\n // Present columns, remapped to contiguous indices so empty layers collapse.\n const presentCols = [...new Set(model.nodes.map((n) => n.column))].sort((a, b) => a - b);\n const colOrder = new Map(presentCols.map((c, i) => [c, i]));\n\n const byColumn = new Map<number, LineageNode[]>();\n for (const node of model.nodes) {\n const list = byColumn.get(node.column);\n if (list) {\n list.push(node);\n } else {\n byColumn.set(node.column, [node]);\n }\n }\n\n let contentH = dims.nodeH;\n for (const list of byColumn.values()) {\n const h = list.length * dims.nodeH + (list.length - 1) * dims.rowGap;\n contentH = Math.max(contentH, h);\n }\n\n for (const col of presentCols) {\n const list = byColumn.get(col) ?? [];\n const colH = list.length * dims.nodeH + (list.length - 1) * dims.rowGap;\n const startY = dims.pad + (contentH - colH) / 2;\n const x = dims.pad + (colOrder.get(col) ?? 0) * (dims.nodeW + dims.colGap);\n list.forEach((node, i) => {\n positions.set(node.id, { node, x, y: startY + i * (dims.nodeH + dims.rowGap) });\n });\n }\n\n const width =\n dims.pad * 2 + presentCols.length * dims.nodeW + (presentCols.length - 1) * dims.colGap;\n const height = dims.pad * 2 + contentH;\n return { positions, width, height };\n}\n\nfunction edgePath(x1: number, y1: number, x2: number, y2: number): string {\n const dx = Math.max(24, (x2 - x1) * 0.5);\n return `M ${x1} ${y1} C ${x1 + dx} ${y1}, ${x2 - dx} ${y2}, ${x2} ${y2}`;\n}\n\nexport interface LineageGraphProps {\n model: LineageModel;\n mode?: 'full' | 'compact';\n onNavigate?: (route: string) => void;\n className?: string;\n}\n\nexport function LineageGraph({ model, mode = 'full', onNavigate, className }: LineageGraphProps) {\n const markerId = `cos-lineage-arrow-${useId().replace(/:/g, '')}`;\n const dims = DIMS[mode];\n\n const layout = useMemo(() => computeLayout(model, dims), [model, dims]);\n\n const legendTypes = useMemo(() => {\n const seen = new Set<LineageNodeType>();\n const ordered: LineageNodeType[] = [];\n for (const node of [...model.nodes].sort((a, b) => a.column - b.column)) {\n if (!seen.has(node.type)) {\n seen.add(node.type);\n ordered.push(node.type);\n }\n }\n return ordered;\n }, [model.nodes]);\n\n if (model.nodes.length === 0) {\n return null;\n }\n\n return (\n <div className={cn('flex flex-col gap-3', className)}>\n {mode === 'full' && legendTypes.length > 0 ? (\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-2\">\n {legendTypes.map((type) => {\n const meta = NODE_META[type];\n return (\n <span\n key={type}\n className=\"inline-flex items-center gap-1.5 text-xs text-text-secondary\"\n >\n <span\n className={cn(\n 'flex size-5 items-center justify-center rounded-button',\n meta.chip\n )}\n >\n <meta.Icon className=\"size-3\" aria-hidden=\"true\" />\n </span>\n {meta.label}\n </span>\n );\n })}\n </div>\n ) : null}\n\n <div\n className={cn(\n 'overflow-x-auto rounded-card border border-border-seam bg-bg-canvas',\n mode === 'full' ? 'p-1' : 'p-0.5'\n )}\n >\n <div\n className=\"relative\"\n style={{\n width: layout.width,\n height: layout.height,\n minWidth: '100%',\n backgroundImage: 'radial-gradient(var(--color-border-subtle) 1px, transparent 1px)',\n backgroundSize: mode === 'full' ? '20px 20px' : '16px 16px',\n }}\n >\n {/* Edges (behind the node cards). */}\n <svg\n className=\"pointer-events-none absolute inset-0 text-border-strong\"\n width={layout.width}\n height={layout.height}\n viewBox={`0 0 ${layout.width} ${layout.height}`}\n aria-hidden=\"true\"\n >\n <defs>\n <marker\n id={markerId}\n viewBox=\"0 0 8 8\"\n refX=\"6.5\"\n refY=\"4\"\n markerWidth=\"6\"\n markerHeight=\"6\"\n orient=\"auto-start-reverse\"\n >\n <path d=\"M1 1 L7 4 L1 7 z\" fill=\"currentColor\" />\n </marker>\n </defs>\n {model.edges.map((edge) => {\n const from = layout.positions.get(edge.from);\n const to = layout.positions.get(edge.to);\n if (!from || !to) {\n return null;\n }\n const x1 = from.x + dims.nodeW;\n const y1 = from.y + dims.nodeH / 2;\n const x2 = to.x;\n const y2 = to.y + dims.nodeH / 2;\n return (\n <path\n key={`${edge.from}»${edge.to}`}\n d={edgePath(x1, y1, x2, y2)}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={mode === 'full' ? 1.5 : 1.25}\n strokeLinecap=\"round\"\n markerEnd={`url(#${markerId})`}\n opacity={0.7}\n />\n );\n })}\n </svg>\n\n {/* Node cards. */}\n {[...layout.positions.values()].map(({ node, x, y }) => {\n const meta = NODE_META[node.type];\n const clickable = Boolean(node.route && onNavigate);\n const content = (\n <div\n className={cn(\n 'flex h-full w-full items-center gap-2.5 rounded-card border bg-bg-elevated px-3 shadow-sm transition-shadow',\n meta.border,\n clickable && 'group-hover:shadow-md',\n node.emphasis && 'ring-1 ring-border-strong'\n )}\n >\n <span\n className={cn(\n 'flex shrink-0 items-center justify-center rounded-button',\n dims.iconSize,\n meta.chip\n )}\n aria-hidden=\"true\"\n >\n <meta.Icon className={mode === 'full' ? 'size-4' : 'size-3.5'} />\n </span>\n <span className=\"flex min-w-0 flex-1 flex-col text-left\">\n <span className=\"truncate text-[10px] font-medium uppercase tracking-wide text-text-tertiary\">\n {node.typeLabel ?? meta.label}\n </span>\n <span\n className={cn(\n 'truncate font-semibold text-text-primary',\n mode === 'full' ? 'text-sm' : 'text-xs'\n )}\n >\n {node.title}\n </span>\n {dims.showSubtitle && node.subtitle ? (\n <span className=\"truncate text-[11px] text-text-secondary\">\n {node.subtitle}\n </span>\n ) : null}\n </span>\n </div>\n );\n\n const style = { left: x, top: y, width: dims.nodeW, height: dims.nodeH } as const;\n\n if (clickable && node.route) {\n const route = node.route;\n return (\n <button\n key={node.id}\n type=\"button\"\n onClick={() => onNavigate?.(route)}\n className=\"group absolute rounded-card focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n style={style}\n title={`${node.typeLabel ?? meta.label}: ${node.title}`}\n >\n {content}\n </button>\n );\n }\n\n return (\n <div key={node.id} className=\"absolute\" style={style}>\n {content}\n </div>\n );\n })}\n </div>\n </div>\n </div>\n );\n}\n\nexport default LineageGraph;\n"],"mappings":"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"}
@@ -10,6 +10,6 @@ export interface LoadingStateProps {
10
10
  * Consistent loading affordance for every CosmicIntersection query surface.
11
11
  * Token-styled only — no raw palette colours.
12
12
  */
13
- export declare function LoadingState({ variant, rows, label, className, }: LoadingStateProps): import("react/jsx-runtime").JSX.Element;
13
+ export declare function LoadingState({ variant, rows, label: labelProp, className, }: LoadingStateProps): import("react/jsx-runtime").JSX.Element;
14
14
  export default LoadingState;
15
15
  //# sourceMappingURL=LoadingState.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"LoadingState.d.ts","sourceRoot":"","sources":["../../../src/cosmicintersection/components/LoadingState.tsx"],"names":[],"mappings":"AAEA,MAAM,WAAW,iBAAiB;IAChC,gFAAgF;IAChF,OAAO,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAC7B,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,EAC3B,OAAmB,EACnB,IAAQ,EACR,KAAkB,EAClB,SAAS,GACV,EAAE,iBAAiB,2CAqBnB;AAED,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"LoadingState.d.ts","sourceRoot":"","sources":["../../../src/cosmicintersection/components/LoadingState.tsx"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,gFAAgF;IAChF,OAAO,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAC7B,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,EAC3B,OAAmB,EACnB,IAAQ,EACR,KAAK,EAAE,SAAS,EAChB,SAAS,GACV,EAAE,iBAAiB,2CAuBnB;AAED,eAAe,YAAY,CAAC"}