@burdenoff/microfe-cosmicintersection 2026.827.1 → 2026.830.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/cosmicintersection/components/ActionMenu.d.ts +1 -1
  2. package/dist/cosmicintersection/components/ActionMenu.d.ts.map +1 -1
  3. package/dist/cosmicintersection/components/ActionMenu.js +51 -54
  4. package/dist/cosmicintersection/components/ActionMenu.js.map +1 -1
  5. package/dist/cosmicintersection/components/EntityFormShell.d.ts +1 -1
  6. package/dist/cosmicintersection/components/EntityFormShell.d.ts.map +1 -1
  7. package/dist/cosmicintersection/components/EntityFormShell.js +29 -27
  8. package/dist/cosmicintersection/components/EntityFormShell.js.map +1 -1
  9. package/dist/cosmicintersection/components/ErrorState.d.ts +1 -1
  10. package/dist/cosmicintersection/components/ErrorState.d.ts.map +1 -1
  11. package/dist/cosmicintersection/components/ErrorState.js +19 -17
  12. package/dist/cosmicintersection/components/ErrorState.js.map +1 -1
  13. package/dist/cosmicintersection/components/FileUploadField.d.ts.map +1 -1
  14. package/dist/cosmicintersection/components/FileUploadField.js +61 -60
  15. package/dist/cosmicintersection/components/FileUploadField.js.map +1 -1
  16. package/dist/cosmicintersection/components/LoadingState.d.ts +1 -1
  17. package/dist/cosmicintersection/components/LoadingState.d.ts.map +1 -1
  18. package/dist/cosmicintersection/components/LoadingState.js +15 -13
  19. package/dist/cosmicintersection/components/LoadingState.js.map +1 -1
  20. package/dist/cosmicintersection/components/PageLayout.d.ts.map +1 -1
  21. package/dist/cosmicintersection/components/PageLayout.js +28 -27
  22. package/dist/cosmicintersection/components/PageLayout.js.map +1 -1
  23. package/dist/cosmicintersection/components/Pagination.d.ts.map +1 -1
  24. package/dist/cosmicintersection/components/Pagination.js +38 -35
  25. package/dist/cosmicintersection/components/Pagination.js.map +1 -1
  26. package/dist/cosmicintersection/components/StatusBadge.d.ts.map +1 -1
  27. package/dist/cosmicintersection/components/StatusBadge.js +30 -9
  28. package/dist/cosmicintersection/components/StatusBadge.js.map +1 -1
  29. package/dist/cosmicintersection/pages/access/AccessPage.d.ts.map +1 -1
  30. package/dist/cosmicintersection/pages/access/AccessPage.js +147 -146
  31. package/dist/cosmicintersection/pages/access/AccessPage.js.map +1 -1
  32. package/dist/cosmicintersection/pages/analyses/AnalysesPage.d.ts.map +1 -1
  33. package/dist/cosmicintersection/pages/analyses/AnalysesPage.js +260 -259
  34. package/dist/cosmicintersection/pages/analyses/AnalysesPage.js.map +1 -1
  35. package/dist/cosmicintersection/pages/collections/CollectionsPage.d.ts.map +1 -1
  36. package/dist/cosmicintersection/pages/collections/CollectionsPage.js +63 -62
  37. package/dist/cosmicintersection/pages/collections/CollectionsPage.js.map +1 -1
  38. package/dist/cosmicintersection/pages/dashboard/DashboardPage.d.ts.map +1 -1
  39. package/dist/cosmicintersection/pages/dashboard/DashboardPage.js +132 -131
  40. package/dist/cosmicintersection/pages/dashboard/DashboardPage.js.map +1 -1
  41. package/dist/cosmicintersection/pages/datasets/DatasetsPage.d.ts.map +1 -1
  42. package/dist/cosmicintersection/pages/datasets/DatasetsPage.js +178 -177
  43. package/dist/cosmicintersection/pages/datasets/DatasetsPage.js.map +1 -1
  44. package/dist/cosmicintersection/pages/discovery/DiscoveryPage.d.ts.map +1 -1
  45. package/dist/cosmicintersection/pages/discovery/DiscoveryPage.js +138 -145
  46. package/dist/cosmicintersection/pages/discovery/DiscoveryPage.js.map +1 -1
  47. package/dist/cosmicintersection/pages/lineage/LineagePage.d.ts.map +1 -1
  48. package/dist/cosmicintersection/pages/lineage/LineagePage.js +78 -88
  49. package/dist/cosmicintersection/pages/lineage/LineagePage.js.map +1 -1
  50. package/dist/cosmicintersection/pages/notebooks/NotebooksPage.d.ts.map +1 -1
  51. package/dist/cosmicintersection/pages/notebooks/NotebooksPage.js +125 -124
  52. package/dist/cosmicintersection/pages/notebooks/NotebooksPage.js.map +1 -1
  53. package/dist/cosmicintersection/pages/pipelines/PipelinesPage.d.ts.map +1 -1
  54. package/dist/cosmicintersection/pages/pipelines/PipelinesPage.js +152 -151
  55. package/dist/cosmicintersection/pages/pipelines/PipelinesPage.js.map +1 -1
  56. package/dist/cosmicintersection/pages/releases/ReleasesPage.d.ts.map +1 -1
  57. package/dist/cosmicintersection/pages/releases/ReleasesPage.js +175 -174
  58. package/dist/cosmicintersection/pages/releases/ReleasesPage.js.map +1 -1
  59. package/dist/cosmicintersection/pages/runs/RunsPage.d.ts.map +1 -1
  60. package/dist/cosmicintersection/pages/runs/RunsPage.js +197 -188
  61. package/dist/cosmicintersection/pages/runs/RunsPage.js.map +1 -1
  62. package/dist/cosmicintersection/pages/sources/SourcesPage.d.ts.map +1 -1
  63. package/dist/cosmicintersection/pages/sources/SourcesPage.js +65 -64
  64. package/dist/cosmicintersection/pages/sources/SourcesPage.js.map +1 -1
  65. package/dist/dev/main.d.ts.map +1 -1
  66. package/dist/shared/hooks/useTr.d.ts +12 -0
  67. package/dist/shared/hooks/useTr.d.ts.map +1 -0
  68. package/dist/shared/hooks/useTr.js +16 -0
  69. package/dist/shared/hooks/useTr.js.map +1 -0
  70. package/package.json +1 -1
@@ -1,30 +1,31 @@
1
1
  import { useCosmicIntersection as e } from "../../context/CosmicIntersectionContext.js";
2
2
  import { useCosCollectionsQuery as t, useCosDatasetsQuery as n, useCosSourcesQuery as r } from "../../api/cosmicApi.js";
3
- import i from "../../components/EmptyState.js";
4
- import a from "../../components/ErrorState.js";
3
+ import { useTr as i } from "../../../shared/hooks/useTr.js";
4
+ import a from "../../components/EmptyState.js";
5
+ import o from "../../components/ErrorState.js";
5
6
  import ee from "../../components/LoadingState.js";
6
- import { cn as te } from "../../utils/cn.js";
7
- import o from "../../components/PageLayout.js";
8
- import s, { formatEnumLabel as c } from "../../components/StatusBadge.js";
7
+ import { cn as s } from "../../utils/cn.js";
8
+ import c from "../../components/PageLayout.js";
9
+ import l, { formatEnumLabel as u } from "../../components/StatusBadge.js";
9
10
  import "../../components/index.js";
10
- import ne from "../../components/FacetPanel.js";
11
- import { useEffect as re, useMemo as l, useState as u } from "react";
12
- import { jsx as d, jsxs as f } from "react/jsx-runtime";
13
- import { ArrowUpDown as p, Database as m, FileText as h, Rows3 as g, Search as _, SlidersHorizontal as ie, Table as ae, X as v } from "lucide-react";
14
- import { Badge as y, Button as b, Input as x, Select as S, SelectContent as C, SelectItem as w, SelectTrigger as T, SelectValue as E } from "@burdenoff/fe-libs/ui";
11
+ import te from "../../components/FacetPanel.js";
12
+ import { useEffect as ne, useMemo as d, useState as f } from "react";
13
+ import { jsx as p, jsxs as m } from "react/jsx-runtime";
14
+ import { ArrowUpDown as h, Database as g, FileText as _, Rows3 as v, Search as y, SlidersHorizontal as b, Table as x, X as S } from "lucide-react";
15
+ import { Badge as re, Button as C, Input as ie, Select as ae, SelectContent as oe, SelectItem as se, SelectTrigger as ce, SelectValue as le } from "@burdenoff/fe-libs/ui";
15
16
  //#region src/cosmicintersection/pages/discovery/DiscoveryPage.tsx
16
- var D = 200, oe = 300, se = [
17
+ var w = 200, ue = 300, de = [
17
18
  "PUBLIC",
18
19
  "WORKSPACE",
19
20
  "RESTRICTED",
20
21
  "EMBARGOED"
21
- ], O = {
22
+ ], T = {
22
23
  relevance: "Best match",
23
24
  name: "Name A–Z",
24
25
  updated: "Recently updated",
25
26
  size: "Largest first"
26
27
  };
27
- function ce(e) {
28
+ function E(e) {
28
29
  if (!e) return "—";
29
30
  let t = Number(e);
30
31
  if (!Number.isFinite(t) || t <= 0) return "—";
@@ -39,60 +40,60 @@ function ce(e) {
39
40
  for (; r >= 1024 && i < n.length - 1;) r /= 1024, i += 1;
40
41
  return `${r >= 10 || i === 0 ? Math.round(r) : r.toFixed(1)} ${n[i]}`;
41
42
  }
42
- function k(e) {
43
+ function D(e) {
43
44
  let t = Number(e ?? 0);
44
45
  return Number.isFinite(t) && t > 0 ? `${t.toLocaleString()} rows` : "no rows";
45
46
  }
46
- function A() {
47
- let { navigate: A } = e(), [j, M] = u(""), [N, P] = u(""), [F, I] = u({}), [L, R] = u("relevance"), [z, B] = u(!1);
48
- re(() => {
49
- let e = window.setTimeout(() => P(j.trim()), oe);
47
+ function O() {
48
+ let { navigate: O } = e(), k = i(), [A, j] = f(""), [M, N] = f(""), [P, F] = f({}), [I, L] = f("relevance"), [R, z] = f(!1);
49
+ ne(() => {
50
+ let e = window.setTimeout(() => N(A.trim()), ue);
50
51
  return () => window.clearTimeout(e);
51
- }, [j]);
52
- let V = n({
53
- q: N || void 0,
52
+ }, [A]);
53
+ let B = n({
54
+ q: M || void 0,
54
55
  pagination: {
55
- limit: D,
56
+ limit: w,
56
57
  offset: 0
57
58
  }
58
- }), H = r({ pagination: {
59
- limit: D,
59
+ }), V = r({ pagination: {
60
+ limit: w,
60
61
  offset: 0
61
- } }), U = t(), W = l(() => V.data?.cosListDatasets?.items ?? [], [V.data]), le = !!V.data?.cosListDatasets?.pageInfo?.hasMore, G = l(() => H.data?.cosListSources?.items ?? [], [H.data]), K = l(() => U.data?.cosListCollections ?? [], [U.data]), q = l(() => {
62
+ } }), H = t(), U = d(() => B.data?.cosListDatasets?.items ?? [], [B.data]), fe = !!B.data?.cosListDatasets?.pageInfo?.hasMore, W = d(() => V.data?.cosListSources?.items ?? [], [V.data]), G = d(() => H.data?.cosListCollections ?? [], [H.data]), K = d(() => {
62
63
  let e = /* @__PURE__ */ new Map();
63
- for (let t of G) e.set(t.id, t.type);
64
+ for (let t of W) e.set(t.id, t.type);
64
65
  return e;
65
- }, [G]), J = l(() => {
66
+ }, [W]), q = d(() => {
66
67
  let e = /* @__PURE__ */ new Map();
67
- for (let t of K) e.set(t.id, t.name);
68
+ for (let t of G) e.set(t.id, t.name);
68
69
  return e;
69
- }, [K]), Y = l(() => {
70
- let e = F.instrument ?? [], t = F.access ?? [], n = F.format ?? [], r = F.mission ?? [];
70
+ }, [G]), J = d(() => {
71
+ let e = P.instrument ?? [], t = P.access ?? [], n = P.format ?? [], r = P.mission ?? [];
71
72
  return {
72
73
  instrument: (t) => {
73
74
  if (e.length === 0) return !0;
74
- let n = t.sourceId ? q.get(t.sourceId) : void 0;
75
+ let n = t.sourceId ? K.get(t.sourceId) : void 0;
75
76
  return n ? e.includes(n) : !1;
76
77
  },
77
78
  access: (e) => t.length === 0 || t.includes(e.accessLevel),
78
79
  format: (e) => n.length === 0 || (e.format ? n.includes(e.format) : !1),
79
80
  mission: (e) => r.length === 0 || (e.collectionId ? r.includes(e.collectionId) : !1)
80
81
  };
81
- }, [F, q]), X = l(() => (e, t) => {
82
+ }, [P, K]), Y = d(() => (e, t) => {
82
83
  let n = 0;
83
- for (let r of W) {
84
+ for (let r of U) {
84
85
  let i = !0;
85
- for (let t of Object.keys(Y)) if (t !== e && !Y[t](r)) {
86
+ for (let t of Object.keys(J)) if (t !== e && !J[t](r)) {
86
87
  i = !1;
87
88
  break;
88
89
  }
89
90
  i && t(r) && (n += 1);
90
91
  }
91
92
  return n;
92
- }, [W, Y]), ue = l(() => {
93
+ }, [U, J]), X = d(() => {
93
94
  let e = /* @__PURE__ */ new Set(), t = /* @__PURE__ */ new Set(), n = /* @__PURE__ */ new Set();
94
- for (let r of W) {
95
- let i = r.sourceId ? q.get(r.sourceId) : void 0;
95
+ for (let r of U) {
96
+ let i = r.sourceId ? K.get(r.sourceId) : void 0;
96
97
  i && e.add(i), r.format && t.add(r.format), r.collectionId && n.add(r.collectionId);
97
98
  }
98
99
  return [
@@ -101,17 +102,17 @@ function A() {
101
102
  label: "Instrument",
102
103
  options: [...e].map((e) => ({
103
104
  value: e,
104
- label: c(e),
105
- count: X("instrument", (t) => t.sourceId ? q.get(t.sourceId) === e : !1)
105
+ label: u(e),
106
+ count: Y("instrument", (t) => t.sourceId ? K.get(t.sourceId) === e : !1)
106
107
  })).sort((e, t) => t.count - e.count || e.label.localeCompare(t.label))
107
108
  },
108
109
  {
109
110
  key: "access",
110
111
  label: "Access",
111
- options: se.map((e) => ({
112
+ options: de.map((e) => ({
112
113
  value: e,
113
- label: c(e),
114
- count: X("access", (t) => t.accessLevel === e)
114
+ label: u(e),
115
+ count: Y("access", (t) => t.accessLevel === e)
115
116
  }))
116
117
  },
117
118
  {
@@ -120,7 +121,7 @@ function A() {
120
121
  options: [...t].sort((e, t) => e.localeCompare(t)).map((e) => ({
121
122
  value: e,
122
123
  label: e,
123
- count: X("format", (t) => t.format === e)
124
+ count: Y("format", (t) => t.format === e)
124
125
  }))
125
126
  },
126
127
  {
@@ -128,19 +129,19 @@ function A() {
128
129
  label: "Mission / Survey",
129
130
  options: [...n].map((e) => ({
130
131
  value: e,
131
- label: J.get(e) ?? "Unknown collection",
132
- count: X("mission", (t) => t.collectionId === e)
132
+ label: q.get(e) ?? "Unknown collection",
133
+ count: Y("mission", (t) => t.collectionId === e)
133
134
  })).sort((e, t) => t.count - e.count || e.label.localeCompare(t.label))
134
135
  }
135
136
  ];
136
137
  }, [
137
- W,
138
+ U,
139
+ K,
138
140
  q,
139
- J,
140
- X
141
- ]), Z = l(() => {
142
- let e = [...W.filter((e) => Y.instrument(e) && Y.access(e) && Y.format(e) && Y.mission(e))];
143
- switch (L) {
141
+ Y
142
+ ]), Z = d(() => {
143
+ let e = [...U.filter((e) => J.instrument(e) && J.access(e) && J.format(e) && J.mission(e))];
144
+ switch (I) {
144
145
  case "name":
145
146
  e.sort((e, t) => e.name.localeCompare(t.name));
146
147
  break;
@@ -154,189 +155,181 @@ function A() {
154
155
  }
155
156
  return e;
156
157
  }, [
157
- W,
158
- Y,
159
- L
160
- ]), Q = Object.values(F).reduce((e, t) => e + t.length, 0), de = (e, t) => {
161
- I((n) => {
158
+ U,
159
+ J,
160
+ I
161
+ ]), Q = Object.values(P).reduce((e, t) => e + t.length, 0), pe = (e, t) => {
162
+ F((n) => {
162
163
  let r = n[e] ?? [], i = r.includes(t) ? r.filter((e) => e !== t) : [...r, t];
163
164
  return {
164
165
  ...n,
165
166
  [e]: i
166
167
  };
167
168
  });
168
- }, $ = () => I({}), fe = V.loading && W.length === 0, pe = V.error, me = /* @__PURE__ */ d(ne, {
169
- groups: ue,
170
- selected: F,
171
- onToggle: de,
169
+ }, $ = () => F({}), me = B.loading && U.length === 0, he = B.error, ge = /* @__PURE__ */ p(te, {
170
+ groups: X,
171
+ selected: P,
172
+ onToggle: pe,
172
173
  onClear: $
173
174
  });
174
- return /* @__PURE__ */ d(o, {
175
- title: "Discovery",
176
- description: "Search and facet the federated science catalog — full-text across datasets, refined by instrument, access, format and mission.",
177
- actions: /* @__PURE__ */ f(b, {
175
+ return /* @__PURE__ */ p(c, {
176
+ title: k("cosmicintersection.pages.discovery.title", "Discovery"),
177
+ description: k("cosmicintersection.pages.discovery.description", "Search and facet the federated science catalog — full-text across datasets, refined by instrument, access, format and mission."),
178
+ actions: /* @__PURE__ */ m(C, {
178
179
  type: "button",
179
180
  variant: "outline",
180
- onClick: () => A("datasets"),
181
- children: [/* @__PURE__ */ d(ae, { className: "size-4" }), "Table view"]
181
+ onClick: () => O("datasets"),
182
+ children: [/* @__PURE__ */ p(x, { className: "size-4" }), k("cosmicintersection.pages.discovery.tableView", "Table view")]
182
183
  }),
183
- children: /* @__PURE__ */ f("div", {
184
+ children: /* @__PURE__ */ m("div", {
184
185
  className: "space-y-5",
185
186
  children: [
186
- /* @__PURE__ */ f("div", {
187
+ /* @__PURE__ */ m("div", {
187
188
  className: "relative",
188
189
  children: [
189
- /* @__PURE__ */ d(_, {
190
+ /* @__PURE__ */ p(y, {
190
191
  className: "pointer-events-none absolute left-4 top-1/2 size-5 -translate-y-1/2 text-text-secondary",
191
192
  "aria-hidden": "true"
192
193
  }),
193
- /* @__PURE__ */ d(x, {
194
+ /* @__PURE__ */ p(ie, {
194
195
  type: "search",
195
- value: j,
196
- onChange: (e) => M(e.target.value),
197
- placeholder: "Search datasets by name, description or tag…",
198
- "aria-label": "Search datasets",
196
+ value: A,
197
+ onChange: (e) => j(e.target.value),
198
+ placeholder: k("cosmicintersection.pages.discovery.searchPlaceholder", "Search datasets by name, description or tag…"),
199
+ "aria-label": k("cosmicintersection.pages.discovery.searchAriaLabel", "Search datasets"),
199
200
  className: "h-12 rounded-card pl-12 pr-11 text-base"
200
201
  }),
201
- j ? /* @__PURE__ */ d("button", {
202
+ A ? /* @__PURE__ */ p("button", {
202
203
  type: "button",
203
- onClick: () => M(""),
204
- "aria-label": "Clear search",
204
+ onClick: () => j(""),
205
+ "aria-label": k("cosmicintersection.pages.discovery.clearSearch", "Clear search"),
205
206
  className: "absolute right-3 top-1/2 flex size-7 -translate-y-1/2 items-center justify-center rounded-full text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-focus",
206
- children: /* @__PURE__ */ d(v, { className: "size-4" })
207
+ children: /* @__PURE__ */ p(S, { className: "size-4" })
207
208
  }) : null
208
209
  ]
209
210
  }),
210
- /* @__PURE__ */ d("div", {
211
+ /* @__PURE__ */ p("div", {
211
212
  className: "flex items-center justify-between gap-3 lg:hidden",
212
- children: /* @__PURE__ */ f(b, {
213
+ children: /* @__PURE__ */ m(C, {
213
214
  type: "button",
214
215
  variant: "outline",
215
- onClick: () => B((e) => !e),
216
- "aria-expanded": z,
217
- children: [
218
- /* @__PURE__ */ d(ie, { className: "size-4" }),
219
- "Filters",
220
- Q > 0 ? ` (${Q})` : ""
221
- ]
216
+ onClick: () => z((e) => !e),
217
+ "aria-expanded": R,
218
+ children: [/* @__PURE__ */ p(b, { className: "size-4" }), Q > 0 ? k("cosmicintersection.pages.discovery.filtersWithCount", "Filters ({{count}})", { count: Q }) : k("cosmicintersection.pages.discovery.filters", "Filters")]
222
219
  })
223
220
  }),
224
- /* @__PURE__ */ f("div", {
221
+ /* @__PURE__ */ m("div", {
225
222
  className: "flex flex-col gap-6 lg:flex-row",
226
- children: [/* @__PURE__ */ d("aside", {
227
- className: te("shrink-0 lg:block lg:w-60 lg:border-r lg:border-border-seam lg:pr-6", z ? "block" : "hidden"),
228
- children: /* @__PURE__ */ d("div", {
223
+ children: [/* @__PURE__ */ p("aside", {
224
+ className: s("shrink-0 lg:block lg:w-60 lg:border-r lg:border-border-seam lg:pr-6", R ? "block" : "hidden"),
225
+ children: /* @__PURE__ */ p("div", {
229
226
  className: "lg:sticky lg:top-0",
230
- children: me
227
+ children: ge
231
228
  })
232
- }), /* @__PURE__ */ f("div", {
229
+ }), /* @__PURE__ */ m("div", {
233
230
  className: "min-w-0 flex-1 space-y-4",
234
231
  children: [
235
- /* @__PURE__ */ f("div", {
232
+ /* @__PURE__ */ m("div", {
236
233
  className: "flex flex-wrap items-center justify-between gap-3",
237
- children: [/* @__PURE__ */ f("div", {
234
+ children: [/* @__PURE__ */ m("div", {
238
235
  className: "flex items-baseline gap-2",
239
- children: [/* @__PURE__ */ d("span", {
236
+ children: [/* @__PURE__ */ p("span", {
240
237
  className: "text-lg font-semibold tabular-nums text-text-primary",
241
238
  children: Z.length
242
- }), /* @__PURE__ */ f("span", {
239
+ }), /* @__PURE__ */ m("span", {
243
240
  className: "text-sm text-text-secondary",
244
- children: [Z.length === 1 ? "dataset" : "datasets", N ? ` matching “${N}”` : ""]
241
+ children: [Z.length === 1 ? k("cosmicintersection.pages.discovery.resultCountSingular", "dataset") : k("cosmicintersection.pages.discovery.resultCountPlural", "datasets"), M ? ` ${k("cosmicintersection.pages.discovery.matching", "matching “{{q}}”", { q: M })}` : ""]
245
242
  })]
246
- }), /* @__PURE__ */ f("label", {
243
+ }), /* @__PURE__ */ m("label", {
247
244
  className: "flex items-center gap-2 text-sm",
248
- children: [/* @__PURE__ */ d(p, {
245
+ children: [/* @__PURE__ */ p(h, {
249
246
  className: "size-4 text-text-secondary",
250
247
  "aria-hidden": "true"
251
- }), /* @__PURE__ */ f(S, {
252
- value: L,
253
- onValueChange: (e) => R(e),
254
- children: [/* @__PURE__ */ d(T, {
255
- "aria-label": "Sort results",
248
+ }), /* @__PURE__ */ m(ae, {
249
+ value: I,
250
+ onValueChange: (e) => L(e),
251
+ children: [/* @__PURE__ */ p(ce, {
252
+ "aria-label": k("cosmicintersection.pages.discovery.sortResults", "Sort results"),
256
253
  className: "w-48",
257
- children: /* @__PURE__ */ d(E, {})
258
- }), /* @__PURE__ */ d(C, { children: Object.keys(O).map((e) => /* @__PURE__ */ d(w, {
254
+ children: /* @__PURE__ */ p(le, {})
255
+ }), /* @__PURE__ */ p(oe, { children: Object.keys(T).map((e) => /* @__PURE__ */ p(se, {
259
256
  value: e,
260
- children: O[e]
257
+ children: T[e]
261
258
  }, e)) })]
262
259
  })]
263
260
  })]
264
261
  }),
265
- le ? /* @__PURE__ */ f("p", {
262
+ fe ? /* @__PURE__ */ p("p", {
266
263
  className: "rounded-card border border-border-default bg-bg-sunken px-3 py-2 text-xs text-text-secondary",
267
- children: [
268
- "Showing facets and results for the first ",
269
- D,
270
- " matches. Refine your search to narrow the set."
271
- ]
264
+ children: k("cosmicintersection.pages.discovery.poolLimitNotice", "Showing facets and results for the first {{limit}} matches. Refine your search to narrow the set.", { limit: w })
272
265
  }) : null,
273
- fe ? /* @__PURE__ */ d(ee, {
266
+ me ? /* @__PURE__ */ p(ee, {
274
267
  variant: "rows",
275
268
  rows: 6,
276
- label: "Searching the catalog…"
277
- }) : pe ? /* @__PURE__ */ d(a, {
278
- message: "We could not run that search. Please try again.",
279
- onRetry: () => void V.refetch()
280
- }) : Z.length === 0 ? /* @__PURE__ */ d(i, {
281
- icon: _,
282
- title: N || Q > 0 ? "No datasets match" : "Start exploring",
283
- description: N || Q > 0 ? "Try a broader search term or clear some facets." : "Search by name, description or tag, or pick a facet to browse the catalog.",
284
- action: Q > 0 ? /* @__PURE__ */ d(b, {
269
+ label: k("cosmicintersection.pages.discovery.loading", "Searching the catalog…")
270
+ }) : he ? /* @__PURE__ */ p(o, {
271
+ message: k("cosmicintersection.pages.discovery.error", "We could not run that search. Please try again."),
272
+ onRetry: () => void B.refetch()
273
+ }) : Z.length === 0 ? /* @__PURE__ */ p(a, {
274
+ icon: y,
275
+ title: M || Q > 0 ? k("cosmicintersection.pages.discovery.noDatasetsMatch", "No datasets match") : k("cosmicintersection.pages.discovery.startExploring", "Start exploring"),
276
+ description: M || Q > 0 ? k("cosmicintersection.pages.discovery.noDatasetsMatchDescription", "Try a broader search term or clear some facets.") : k("cosmicintersection.pages.discovery.startExploringDescription", "Search by name, description or tag, or pick a facet to browse the catalog."),
277
+ action: Q > 0 ? /* @__PURE__ */ p(C, {
285
278
  type: "button",
286
279
  variant: "outline",
287
280
  onClick: $,
288
- children: "Clear filters"
281
+ children: k("cosmicintersection.pages.discovery.clearFilters", "Clear filters")
289
282
  }) : void 0
290
- }) : /* @__PURE__ */ d("div", {
283
+ }) : /* @__PURE__ */ p("div", {
291
284
  className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3",
292
- children: Z.map((e) => /* @__PURE__ */ f("button", {
285
+ children: Z.map((e) => /* @__PURE__ */ m("button", {
293
286
  type: "button",
294
- onClick: () => A(`datasets/${e.id}`),
287
+ onClick: () => O(`datasets/${e.id}`),
295
288
  className: "group flex flex-col overflow-hidden rounded-card border border-border-default bg-bg-elevated text-left outline-none transition-colors hover:border-border-strong hover:bg-bg-sunken focus-visible:ring-2 focus-visible:ring-border-focus",
296
- children: [/* @__PURE__ */ d("div", { className: "h-1 w-full bg-gradient-to-r from-accent-blue via-accent-purple to-accent-teal" }), /* @__PURE__ */ f("div", {
289
+ children: [/* @__PURE__ */ p("div", { className: "h-1 w-full bg-gradient-to-r from-accent-blue via-accent-purple to-accent-teal" }), /* @__PURE__ */ m("div", {
297
290
  className: "flex flex-1 flex-col gap-3 p-4",
298
291
  children: [
299
- /* @__PURE__ */ d("h3", {
292
+ /* @__PURE__ */ p("h3", {
300
293
  className: "line-clamp-2 text-sm font-semibold text-text-primary",
301
294
  children: e.name
302
295
  }),
303
- /* @__PURE__ */ f("div", {
296
+ /* @__PURE__ */ m("div", {
304
297
  className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-text-secondary",
305
298
  children: [
306
- /* @__PURE__ */ f("span", {
299
+ /* @__PURE__ */ m("span", {
307
300
  className: "inline-flex items-center gap-1",
308
- children: [/* @__PURE__ */ d(h, {
301
+ children: [/* @__PURE__ */ p(_, {
309
302
  className: "size-3.5",
310
303
  "aria-hidden": "true"
311
304
  }), e.format || "No format"]
312
305
  }),
313
- /* @__PURE__ */ f("span", {
306
+ /* @__PURE__ */ m("span", {
314
307
  className: "inline-flex items-center gap-1",
315
- children: [/* @__PURE__ */ d(m, {
308
+ children: [/* @__PURE__ */ p(g, {
316
309
  className: "size-3.5",
317
310
  "aria-hidden": "true"
318
- }), ce(e.sizeBytes)]
311
+ }), E(e.sizeBytes)]
319
312
  }),
320
- /* @__PURE__ */ f("span", {
313
+ /* @__PURE__ */ m("span", {
321
314
  className: "inline-flex items-center gap-1",
322
- children: [/* @__PURE__ */ d(g, {
315
+ children: [/* @__PURE__ */ p(v, {
323
316
  className: "size-3.5",
324
317
  "aria-hidden": "true"
325
- }), k(e.rowCount)]
318
+ }), D(e.rowCount)]
326
319
  })
327
320
  ]
328
321
  }),
329
- /* @__PURE__ */ f("div", {
322
+ /* @__PURE__ */ m("div", {
330
323
  className: "flex flex-wrap items-center gap-1.5",
331
- children: [/* @__PURE__ */ d(s, { status: e.status }), /* @__PURE__ */ d(s, { status: e.accessLevel })]
324
+ children: [/* @__PURE__ */ p(l, { status: e.status }), /* @__PURE__ */ p(l, { status: e.accessLevel })]
332
325
  }),
333
- e.tags.length > 0 ? /* @__PURE__ */ f("div", {
326
+ e.tags.length > 0 ? /* @__PURE__ */ m("div", {
334
327
  className: "mt-auto flex flex-wrap gap-1 pt-1",
335
- children: [e.tags.slice(0, 3).map((e) => /* @__PURE__ */ d(y, {
328
+ children: [e.tags.slice(0, 3).map((e) => /* @__PURE__ */ p(re, {
336
329
  variant: "secondary",
337
330
  className: "text-xs",
338
331
  children: e
339
- }, e)), e.tags.length > 3 ? /* @__PURE__ */ f("span", {
332
+ }, e)), e.tags.length > 3 ? /* @__PURE__ */ m("span", {
340
333
  className: "text-xs text-text-secondary",
341
334
  children: ["+", e.tags.length - 3]
342
335
  }) : null]
@@ -353,6 +346,6 @@ function A() {
353
346
  });
354
347
  }
355
348
  //#endregion
356
- export { A as default };
349
+ export { O as default };
357
350
 
358
351
  //# sourceMappingURL=DiscoveryPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DiscoveryPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/discovery/DiscoveryPage.tsx"],"sourcesContent":["/**\n * DiscoveryPage — faceted full-text discovery over the federated dataset catalog\n * (mockup 05).\n *\n * A prominent debounced search bar drives the backend `q` full-text argument on\n * `cosListDatasets`; the matched datasets form a \"pool\" (capped at a generous\n * limit) from which every facet count and the result grid are derived — so counts\n * and results always agree and nothing is fabricated. Facets are multi-select and\n * cross-filtered (each group's counts reflect the other selected groups), matching\n * the standard faceted-search behaviour:\n * - Instrument → distinct source types (via cosListSources), mapped from each\n * dataset's sourceId\n * - Access → CosAccessLevel enum\n * - Format → distinct formats present in the pool\n * - Mission/Survey → collections (via cosListCollections)\n *\n * Everything renders from live backend data with honest loading / empty / error\n * states. Tokens only — no raw palette colours.\n */\nimport { useEffect, useMemo, useState } from 'react';\nimport {\n ArrowUpDown,\n Database,\n FileText,\n Rows3,\n Search,\n SlidersHorizontal,\n Table as TableIcon,\n X,\n} from 'lucide-react';\nimport {\n Badge,\n Button,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosCollectionsQuery,\n useCosDatasetsQuery,\n useCosSourcesQuery,\n type CosAccessLevel,\n type CosDataset,\n} from '../../api/cosmicApi';\nimport {\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n StatusBadge,\n formatEnumLabel,\n} from '../../components';\nimport { FacetPanel, type FacetGroup } from '../../components/FacetPanel';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { cn } from '../../utils/cn';\n\n/** Generous ceiling for the search pool — facet counts reflect this window. */\nconst POOL_LIMIT = 200;\nconst SEARCH_DEBOUNCE_MS = 300;\n\nconst ACCESS_LEVELS: CosAccessLevel[] = ['PUBLIC', 'WORKSPACE', 'RESTRICTED', 'EMBARGOED'];\n\ntype SortKey = 'relevance' | 'name' | 'updated' | 'size';\n\nconst SORT_LABELS: Record<SortKey, string> = {\n relevance: 'Best match',\n name: 'Name A–Z',\n updated: 'Recently updated',\n size: 'Largest first',\n};\n\n/** BigInt byte string → human-readable size, or an em dash when absent/zero. */\nfunction formatBytes(raw: string | null | undefined): string {\n if (!raw) {\n return '—';\n }\n const bytes = Number(raw);\n if (!Number.isFinite(bytes) || bytes <= 0) {\n return '—';\n }\n const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];\n let value = bytes;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit += 1;\n }\n return `${value >= 10 || unit === 0 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;\n}\n\nfunction formatRows(raw: string | null | undefined): string {\n const rows = Number(raw ?? 0);\n return Number.isFinite(rows) && rows > 0 ? `${rows.toLocaleString()} rows` : 'no rows';\n}\n\nexport default function DiscoveryPage() {\n const { navigate } = useCosmicIntersection();\n\n const [searchInput, setSearchInput] = useState('');\n const [q, setQ] = useState('');\n const [selected, setSelected] = useState<Record<string, string[]>>({});\n const [sort, setSort] = useState<SortKey>('relevance');\n const [facetsOpen, setFacetsOpen] = useState(false);\n\n // Debounce the raw input into the backend `q` argument.\n useEffect(() => {\n const handle = window.setTimeout(() => setQ(searchInput.trim()), SEARCH_DEBOUNCE_MS);\n return () => window.clearTimeout(handle);\n }, [searchInput]);\n\n const poolQuery = useCosDatasetsQuery({\n q: q || undefined,\n pagination: { limit: POOL_LIMIT, offset: 0 },\n });\n const sourcesQuery = useCosSourcesQuery({ pagination: { limit: POOL_LIMIT, offset: 0 } });\n const collectionsQuery = useCosCollectionsQuery();\n\n const pool = useMemo(() => poolQuery.data?.cosListDatasets?.items ?? [], [poolQuery.data]);\n const poolHasMore = Boolean(poolQuery.data?.cosListDatasets?.pageInfo?.hasMore);\n const sources = useMemo(\n () => sourcesQuery.data?.cosListSources?.items ?? [],\n [sourcesQuery.data]\n );\n const collections = useMemo(\n () => collectionsQuery.data?.cosListCollections ?? [],\n [collectionsQuery.data]\n );\n\n // sourceId → source type, so a dataset can be bucketed by instrument type.\n const sourceTypeById = useMemo(() => {\n const map = new Map<string, string>();\n for (const source of sources) {\n map.set(source.id, source.type);\n }\n return map;\n }, [sources]);\n\n const collectionNameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const collection of collections) {\n map.set(collection.id, collection.name);\n }\n return map;\n }, [collections]);\n\n // Per-group predicates — a dataset \"matches\" a group if the group has no\n // selection or the dataset's value is among the selected values.\n const matchers = useMemo(() => {\n const instrument = selected.instrument ?? [];\n const access = selected.access ?? [];\n const format = selected.format ?? [];\n const mission = selected.mission ?? [];\n return {\n instrument: (d: CosDataset) => {\n if (instrument.length === 0) return true;\n const type = d.sourceId ? sourceTypeById.get(d.sourceId) : undefined;\n return type ? instrument.includes(type) : false;\n },\n access: (d: CosDataset) => access.length === 0 || access.includes(d.accessLevel),\n format: (d: CosDataset) =>\n format.length === 0 || (d.format ? format.includes(d.format) : false),\n mission: (d: CosDataset) =>\n mission.length === 0 || (d.collectionId ? mission.includes(d.collectionId) : false),\n } as const;\n }, [selected, sourceTypeById]);\n\n // Cross-filtered count: items passing every OTHER group, then the predicate.\n const countFor = useMemo(() => {\n return (groupKey: keyof typeof matchers, predicate: (d: CosDataset) => boolean): number => {\n let total = 0;\n for (const dataset of pool) {\n let ok = true;\n for (const key of Object.keys(matchers) as (keyof typeof matchers)[]) {\n if (key === groupKey) continue;\n if (!matchers[key](dataset)) {\n ok = false;\n break;\n }\n }\n if (ok && predicate(dataset)) {\n total += 1;\n }\n }\n return total;\n };\n }, [pool, matchers]);\n\n const facetGroups = useMemo<FacetGroup[]>(() => {\n // Universe of option values from the unfiltered pool (stable while toggling).\n const instrumentTypes = new Set<string>();\n const formats = new Set<string>();\n const missionIds = new Set<string>();\n for (const dataset of pool) {\n const type = dataset.sourceId ? sourceTypeById.get(dataset.sourceId) : undefined;\n if (type) instrumentTypes.add(type);\n if (dataset.format) formats.add(dataset.format);\n if (dataset.collectionId) missionIds.add(dataset.collectionId);\n }\n\n const instrument: FacetGroup = {\n key: 'instrument',\n label: 'Instrument',\n options: [...instrumentTypes]\n .map((type) => ({\n value: type,\n label: formatEnumLabel(type),\n count: countFor('instrument', (d) =>\n d.sourceId ? sourceTypeById.get(d.sourceId) === type : false\n ),\n }))\n .sort((a, b) => b.count - a.count || a.label.localeCompare(b.label)),\n };\n\n const access: FacetGroup = {\n key: 'access',\n label: 'Access',\n options: ACCESS_LEVELS.map((level) => ({\n value: level,\n label: formatEnumLabel(level),\n count: countFor('access', (d) => d.accessLevel === level),\n })),\n };\n\n const format: FacetGroup = {\n key: 'format',\n label: 'Format',\n options: [...formats]\n .sort((a, b) => a.localeCompare(b))\n .map((fmt) => ({\n value: fmt,\n label: fmt,\n count: countFor('format', (d) => d.format === fmt),\n })),\n };\n\n const mission: FacetGroup = {\n key: 'mission',\n label: 'Mission / Survey',\n options: [...missionIds]\n .map((id) => ({\n value: id,\n label: collectionNameById.get(id) ?? 'Unknown collection',\n count: countFor('mission', (d) => d.collectionId === id),\n }))\n .sort((a, b) => b.count - a.count || a.label.localeCompare(b.label)),\n };\n\n return [instrument, access, format, mission];\n }, [pool, sourceTypeById, collectionNameById, countFor]);\n\n // Displayed results: the pool filtered by all selected facets, then sorted.\n const results = useMemo(() => {\n const filtered = pool.filter(\n (dataset) =>\n matchers.instrument(dataset) &&\n matchers.access(dataset) &&\n matchers.format(dataset) &&\n matchers.mission(dataset)\n );\n const sorted = [...filtered];\n switch (sort) {\n case 'name':\n sorted.sort((a, b) => a.name.localeCompare(b.name));\n break;\n case 'updated':\n sorted.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());\n break;\n case 'size':\n sorted.sort((a, b) => Number(b.sizeBytes) - Number(a.sizeBytes));\n break;\n case 'relevance':\n default:\n break; // preserve backend order (relevance when `q` is present)\n }\n return sorted;\n }, [pool, matchers, sort]);\n\n const totalSelected = Object.values(selected).reduce((sum, values) => sum + values.length, 0);\n\n const handleToggle = (groupKey: string, value: string) => {\n setSelected((prev) => {\n const current = prev[groupKey] ?? [];\n const next = current.includes(value)\n ? current.filter((item) => item !== value)\n : [...current, value];\n return { ...prev, [groupKey]: next };\n });\n };\n\n const handleClear = () => setSelected({});\n\n const loading = poolQuery.loading && pool.length === 0;\n const error = poolQuery.error;\n\n const facetPanel = (\n <FacetPanel\n groups={facetGroups}\n selected={selected}\n onToggle={handleToggle}\n onClear={handleClear}\n />\n );\n\n return (\n <PageLayout\n title=\"Discovery\"\n description=\"Search and facet the federated science catalog — full-text across datasets, refined by instrument, access, format and mission.\"\n actions={\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('datasets')}>\n <TableIcon className=\"size-4\" />\n Table view\n </Button>\n }\n >\n <div className=\"space-y-5\">\n {/* Search bar */}\n <div className=\"relative\">\n <Search\n className=\"pointer-events-none absolute left-4 top-1/2 size-5 -translate-y-1/2 text-text-secondary\"\n aria-hidden=\"true\"\n />\n <Input\n type=\"search\"\n value={searchInput}\n onChange={(event) => setSearchInput(event.target.value)}\n placeholder=\"Search datasets by name, description or tag…\"\n aria-label=\"Search datasets\"\n className=\"h-12 rounded-card pl-12 pr-11 text-base\"\n />\n {searchInput ? (\n <button\n type=\"button\"\n onClick={() => setSearchInput('')}\n aria-label=\"Clear search\"\n className=\"absolute right-3 top-1/2 flex size-7 -translate-y-1/2 items-center justify-center rounded-full text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-focus\"\n >\n <X className=\"size-4\" />\n </button>\n ) : null}\n </div>\n\n {/* Mobile facet toggle */}\n <div className=\"flex items-center justify-between gap-3 lg:hidden\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => setFacetsOpen((open) => !open)}\n aria-expanded={facetsOpen}\n >\n <SlidersHorizontal className=\"size-4\" />\n Filters{totalSelected > 0 ? ` (${totalSelected})` : ''}\n </Button>\n </div>\n\n <div className=\"flex flex-col gap-6 lg:flex-row\">\n {/* Facets — sticky sidebar on desktop, collapsible on mobile */}\n <aside\n className={cn(\n 'shrink-0 lg:block lg:w-60 lg:border-r lg:border-border-seam lg:pr-6',\n facetsOpen ? 'block' : 'hidden'\n )}\n >\n <div className=\"lg:sticky lg:top-0\">{facetPanel}</div>\n </aside>\n\n {/* Results */}\n <div className=\"min-w-0 flex-1 space-y-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-lg font-semibold tabular-nums text-text-primary\">\n {results.length}\n </span>\n <span className=\"text-sm text-text-secondary\">\n {results.length === 1 ? 'dataset' : 'datasets'}\n {q ? ` matching “${q}”` : ''}\n </span>\n </div>\n <label className=\"flex items-center gap-2 text-sm\">\n <ArrowUpDown className=\"size-4 text-text-secondary\" aria-hidden=\"true\" />\n <Select value={sort} onValueChange={(value) => setSort(value as SortKey)}>\n <SelectTrigger aria-label=\"Sort results\" className=\"w-48\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {(Object.keys(SORT_LABELS) as SortKey[]).map((key) => (\n <SelectItem key={key} value={key}>\n {SORT_LABELS[key]}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {poolHasMore ? (\n <p className=\"rounded-card border border-border-default bg-bg-sunken px-3 py-2 text-xs text-text-secondary\">\n Showing facets and results for the first {POOL_LIMIT} matches. Refine your search to\n narrow the set.\n </p>\n ) : null}\n\n {loading ? (\n <LoadingState variant=\"rows\" rows={6} label=\"Searching the catalog…\" />\n ) : error ? (\n <ErrorState\n message=\"We could not run that search. Please try again.\"\n onRetry={() => void poolQuery.refetch()}\n />\n ) : results.length === 0 ? (\n <EmptyState\n icon={Search}\n title={q || totalSelected > 0 ? 'No datasets match' : 'Start exploring'}\n description={\n q || totalSelected > 0\n ? 'Try a broader search term or clear some facets.'\n : 'Search by name, description or tag, or pick a facet to browse the catalog.'\n }\n action={\n totalSelected > 0 ? (\n <Button type=\"button\" variant=\"outline\" onClick={handleClear}>\n Clear filters\n </Button>\n ) : undefined\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3\">\n {results.map((dataset) => (\n <button\n key={dataset.id}\n type=\"button\"\n onClick={() => navigate(`datasets/${dataset.id}`)}\n className=\"group flex flex-col overflow-hidden rounded-card border border-border-default bg-bg-elevated text-left outline-none transition-colors hover:border-border-strong hover:bg-bg-sunken focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <div className=\"h-1 w-full bg-gradient-to-r from-accent-blue via-accent-purple to-accent-teal\" />\n <div className=\"flex flex-1 flex-col gap-3 p-4\">\n <h3 className=\"line-clamp-2 text-sm font-semibold text-text-primary\">\n {dataset.name}\n </h3>\n\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-text-secondary\">\n <span className=\"inline-flex items-center gap-1\">\n <FileText className=\"size-3.5\" aria-hidden=\"true\" />\n {dataset.format || 'No format'}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Database className=\"size-3.5\" aria-hidden=\"true\" />\n {formatBytes(dataset.sizeBytes)}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Rows3 className=\"size-3.5\" aria-hidden=\"true\" />\n {formatRows(dataset.rowCount)}\n </span>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={dataset.status} />\n <StatusBadge status={dataset.accessLevel} />\n </div>\n\n {dataset.tags.length > 0 ? (\n <div className=\"mt-auto flex flex-wrap gap-1 pt-1\">\n {dataset.tags.slice(0, 3).map((tag) => (\n <Badge key={tag} variant=\"secondary\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n {dataset.tags.length > 3 ? (\n <span className=\"text-xs text-text-secondary\">\n +{dataset.tags.length - 3}\n </span>\n ) : null}\n </div>\n ) : null}\n </div>\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DiscoveryPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/discovery/DiscoveryPage.tsx"],"sourcesContent":["/**\n * DiscoveryPage — faceted full-text discovery over the federated dataset catalog\n * (mockup 05).\n *\n * A prominent debounced search bar drives the backend `q` full-text argument on\n * `cosListDatasets`; the matched datasets form a \"pool\" (capped at a generous\n * limit) from which every facet count and the result grid are derived — so counts\n * and results always agree and nothing is fabricated. Facets are multi-select and\n * cross-filtered (each group's counts reflect the other selected groups), matching\n * the standard faceted-search behaviour:\n * - Instrument → distinct source types (via cosListSources), mapped from each\n * dataset's sourceId\n * - Access → CosAccessLevel enum\n * - Format → distinct formats present in the pool\n * - Mission/Survey → collections (via cosListCollections)\n *\n * Everything renders from live backend data with honest loading / empty / error\n * states. Tokens only — no raw palette colours.\n */\nimport { useEffect, useMemo, useState } from 'react';\nimport {\n ArrowUpDown,\n Database,\n FileText,\n Rows3,\n Search,\n SlidersHorizontal,\n Table as TableIcon,\n X,\n} from 'lucide-react';\nimport {\n Badge,\n Button,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosCollectionsQuery,\n useCosDatasetsQuery,\n useCosSourcesQuery,\n type CosAccessLevel,\n type CosDataset,\n} from '../../api/cosmicApi';\nimport {\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n StatusBadge,\n formatEnumLabel,\n} from '../../components';\nimport { FacetPanel, type FacetGroup } from '../../components/FacetPanel';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { cn } from '../../utils/cn';\nimport { useTr } from '../../../shared/hooks/useTr';\n\n/** Generous ceiling for the search pool — facet counts reflect this window. */\nconst POOL_LIMIT = 200;\nconst SEARCH_DEBOUNCE_MS = 300;\n\nconst ACCESS_LEVELS: CosAccessLevel[] = ['PUBLIC', 'WORKSPACE', 'RESTRICTED', 'EMBARGOED'];\n\ntype SortKey = 'relevance' | 'name' | 'updated' | 'size';\n\nconst SORT_LABELS: Record<SortKey, string> = {\n relevance: 'Best match',\n name: 'Name A–Z',\n updated: 'Recently updated',\n size: 'Largest first',\n};\n\n/** BigInt byte string → human-readable size, or an em dash when absent/zero. */\nfunction formatBytes(raw: string | null | undefined): string {\n if (!raw) {\n return '—';\n }\n const bytes = Number(raw);\n if (!Number.isFinite(bytes) || bytes <= 0) {\n return '—';\n }\n const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];\n let value = bytes;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit += 1;\n }\n return `${value >= 10 || unit === 0 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;\n}\n\nfunction formatRows(raw: string | null | undefined): string {\n const rows = Number(raw ?? 0);\n return Number.isFinite(rows) && rows > 0 ? `${rows.toLocaleString()} rows` : 'no rows';\n}\n\nexport default function DiscoveryPage() {\n const { navigate } = useCosmicIntersection();\n const tr = useTr();\n\n const [searchInput, setSearchInput] = useState('');\n const [q, setQ] = useState('');\n const [selected, setSelected] = useState<Record<string, string[]>>({});\n const [sort, setSort] = useState<SortKey>('relevance');\n const [facetsOpen, setFacetsOpen] = useState(false);\n\n // Debounce the raw input into the backend `q` argument.\n useEffect(() => {\n const handle = window.setTimeout(() => setQ(searchInput.trim()), SEARCH_DEBOUNCE_MS);\n return () => window.clearTimeout(handle);\n }, [searchInput]);\n\n const poolQuery = useCosDatasetsQuery({\n q: q || undefined,\n pagination: { limit: POOL_LIMIT, offset: 0 },\n });\n const sourcesQuery = useCosSourcesQuery({ pagination: { limit: POOL_LIMIT, offset: 0 } });\n const collectionsQuery = useCosCollectionsQuery();\n\n const pool = useMemo(() => poolQuery.data?.cosListDatasets?.items ?? [], [poolQuery.data]);\n const poolHasMore = Boolean(poolQuery.data?.cosListDatasets?.pageInfo?.hasMore);\n const sources = useMemo(\n () => sourcesQuery.data?.cosListSources?.items ?? [],\n [sourcesQuery.data]\n );\n const collections = useMemo(\n () => collectionsQuery.data?.cosListCollections ?? [],\n [collectionsQuery.data]\n );\n\n // sourceId → source type, so a dataset can be bucketed by instrument type.\n const sourceTypeById = useMemo(() => {\n const map = new Map<string, string>();\n for (const source of sources) {\n map.set(source.id, source.type);\n }\n return map;\n }, [sources]);\n\n const collectionNameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const collection of collections) {\n map.set(collection.id, collection.name);\n }\n return map;\n }, [collections]);\n\n // Per-group predicates — a dataset \"matches\" a group if the group has no\n // selection or the dataset's value is among the selected values.\n const matchers = useMemo(() => {\n const instrument = selected.instrument ?? [];\n const access = selected.access ?? [];\n const format = selected.format ?? [];\n const mission = selected.mission ?? [];\n return {\n instrument: (d: CosDataset) => {\n if (instrument.length === 0) return true;\n const type = d.sourceId ? sourceTypeById.get(d.sourceId) : undefined;\n return type ? instrument.includes(type) : false;\n },\n access: (d: CosDataset) => access.length === 0 || access.includes(d.accessLevel),\n format: (d: CosDataset) =>\n format.length === 0 || (d.format ? format.includes(d.format) : false),\n mission: (d: CosDataset) =>\n mission.length === 0 || (d.collectionId ? mission.includes(d.collectionId) : false),\n } as const;\n }, [selected, sourceTypeById]);\n\n // Cross-filtered count: items passing every OTHER group, then the predicate.\n const countFor = useMemo(() => {\n return (groupKey: keyof typeof matchers, predicate: (d: CosDataset) => boolean): number => {\n let total = 0;\n for (const dataset of pool) {\n let ok = true;\n for (const key of Object.keys(matchers) as (keyof typeof matchers)[]) {\n if (key === groupKey) continue;\n if (!matchers[key](dataset)) {\n ok = false;\n break;\n }\n }\n if (ok && predicate(dataset)) {\n total += 1;\n }\n }\n return total;\n };\n }, [pool, matchers]);\n\n const facetGroups = useMemo<FacetGroup[]>(() => {\n // Universe of option values from the unfiltered pool (stable while toggling).\n const instrumentTypes = new Set<string>();\n const formats = new Set<string>();\n const missionIds = new Set<string>();\n for (const dataset of pool) {\n const type = dataset.sourceId ? sourceTypeById.get(dataset.sourceId) : undefined;\n if (type) instrumentTypes.add(type);\n if (dataset.format) formats.add(dataset.format);\n if (dataset.collectionId) missionIds.add(dataset.collectionId);\n }\n\n const instrument: FacetGroup = {\n key: 'instrument',\n label: 'Instrument',\n options: [...instrumentTypes]\n .map((type) => ({\n value: type,\n label: formatEnumLabel(type),\n count: countFor('instrument', (d) =>\n d.sourceId ? sourceTypeById.get(d.sourceId) === type : false\n ),\n }))\n .sort((a, b) => b.count - a.count || a.label.localeCompare(b.label)),\n };\n\n const access: FacetGroup = {\n key: 'access',\n label: 'Access',\n options: ACCESS_LEVELS.map((level) => ({\n value: level,\n label: formatEnumLabel(level),\n count: countFor('access', (d) => d.accessLevel === level),\n })),\n };\n\n const format: FacetGroup = {\n key: 'format',\n label: 'Format',\n options: [...formats]\n .sort((a, b) => a.localeCompare(b))\n .map((fmt) => ({\n value: fmt,\n label: fmt,\n count: countFor('format', (d) => d.format === fmt),\n })),\n };\n\n const mission: FacetGroup = {\n key: 'mission',\n label: 'Mission / Survey',\n options: [...missionIds]\n .map((id) => ({\n value: id,\n label: collectionNameById.get(id) ?? 'Unknown collection',\n count: countFor('mission', (d) => d.collectionId === id),\n }))\n .sort((a, b) => b.count - a.count || a.label.localeCompare(b.label)),\n };\n\n return [instrument, access, format, mission];\n }, [pool, sourceTypeById, collectionNameById, countFor]);\n\n // Displayed results: the pool filtered by all selected facets, then sorted.\n const results = useMemo(() => {\n const filtered = pool.filter(\n (dataset) =>\n matchers.instrument(dataset) &&\n matchers.access(dataset) &&\n matchers.format(dataset) &&\n matchers.mission(dataset)\n );\n const sorted = [...filtered];\n switch (sort) {\n case 'name':\n sorted.sort((a, b) => a.name.localeCompare(b.name));\n break;\n case 'updated':\n sorted.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());\n break;\n case 'size':\n sorted.sort((a, b) => Number(b.sizeBytes) - Number(a.sizeBytes));\n break;\n case 'relevance':\n default:\n break; // preserve backend order (relevance when `q` is present)\n }\n return sorted;\n }, [pool, matchers, sort]);\n\n const totalSelected = Object.values(selected).reduce((sum, values) => sum + values.length, 0);\n\n const handleToggle = (groupKey: string, value: string) => {\n setSelected((prev) => {\n const current = prev[groupKey] ?? [];\n const next = current.includes(value)\n ? current.filter((item) => item !== value)\n : [...current, value];\n return { ...prev, [groupKey]: next };\n });\n };\n\n const handleClear = () => setSelected({});\n\n const loading = poolQuery.loading && pool.length === 0;\n const error = poolQuery.error;\n\n const facetPanel = (\n <FacetPanel\n groups={facetGroups}\n selected={selected}\n onToggle={handleToggle}\n onClear={handleClear}\n />\n );\n\n return (\n <PageLayout\n title={tr('cosmicintersection.pages.discovery.title', 'Discovery')}\n description={tr(\n 'cosmicintersection.pages.discovery.description',\n 'Search and facet the federated science catalog — full-text across datasets, refined by instrument, access, format and mission.'\n )}\n actions={\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('datasets')}>\n <TableIcon className=\"size-4\" />\n {tr('cosmicintersection.pages.discovery.tableView', 'Table view')}\n </Button>\n }\n >\n <div className=\"space-y-5\">\n {/* Search bar */}\n <div className=\"relative\">\n <Search\n className=\"pointer-events-none absolute left-4 top-1/2 size-5 -translate-y-1/2 text-text-secondary\"\n aria-hidden=\"true\"\n />\n <Input\n type=\"search\"\n value={searchInput}\n onChange={(event) => setSearchInput(event.target.value)}\n placeholder={tr(\n 'cosmicintersection.pages.discovery.searchPlaceholder',\n 'Search datasets by name, description or tag…'\n )}\n aria-label={tr('cosmicintersection.pages.discovery.searchAriaLabel', 'Search datasets')}\n className=\"h-12 rounded-card pl-12 pr-11 text-base\"\n />\n {searchInput ? (\n <button\n type=\"button\"\n onClick={() => setSearchInput('')}\n aria-label={tr('cosmicintersection.pages.discovery.clearSearch', 'Clear search')}\n className=\"absolute right-3 top-1/2 flex size-7 -translate-y-1/2 items-center justify-center rounded-full text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-focus\"\n >\n <X className=\"size-4\" />\n </button>\n ) : null}\n </div>\n\n {/* Mobile facet toggle */}\n <div className=\"flex items-center justify-between gap-3 lg:hidden\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => setFacetsOpen((open) => !open)}\n aria-expanded={facetsOpen}\n >\n <SlidersHorizontal className=\"size-4\" />\n {totalSelected > 0\n ? tr('cosmicintersection.pages.discovery.filtersWithCount', 'Filters ({{count}})', {\n count: totalSelected,\n })\n : tr('cosmicintersection.pages.discovery.filters', 'Filters')}\n </Button>\n </div>\n\n <div className=\"flex flex-col gap-6 lg:flex-row\">\n {/* Facets — sticky sidebar on desktop, collapsible on mobile */}\n <aside\n className={cn(\n 'shrink-0 lg:block lg:w-60 lg:border-r lg:border-border-seam lg:pr-6',\n facetsOpen ? 'block' : 'hidden'\n )}\n >\n <div className=\"lg:sticky lg:top-0\">{facetPanel}</div>\n </aside>\n\n {/* Results */}\n <div className=\"min-w-0 flex-1 space-y-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-lg font-semibold tabular-nums text-text-primary\">\n {results.length}\n </span>\n <span className=\"text-sm text-text-secondary\">\n {results.length === 1\n ? tr('cosmicintersection.pages.discovery.resultCountSingular', 'dataset')\n : tr('cosmicintersection.pages.discovery.resultCountPlural', 'datasets')}\n {q\n ? ` ${tr('cosmicintersection.pages.discovery.matching', 'matching “{{q}}”', {\n q,\n })}`\n : ''}\n </span>\n </div>\n <label className=\"flex items-center gap-2 text-sm\">\n <ArrowUpDown className=\"size-4 text-text-secondary\" aria-hidden=\"true\" />\n <Select value={sort} onValueChange={(value) => setSort(value as SortKey)}>\n <SelectTrigger\n aria-label={tr(\n 'cosmicintersection.pages.discovery.sortResults',\n 'Sort results'\n )}\n className=\"w-48\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {(Object.keys(SORT_LABELS) as SortKey[]).map((key) => (\n <SelectItem key={key} value={key}>\n {SORT_LABELS[key]}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {poolHasMore ? (\n <p className=\"rounded-card border border-border-default bg-bg-sunken px-3 py-2 text-xs text-text-secondary\">\n {tr(\n 'cosmicintersection.pages.discovery.poolLimitNotice',\n 'Showing facets and results for the first {{limit}} matches. Refine your search to narrow the set.',\n { limit: POOL_LIMIT }\n )}\n </p>\n ) : null}\n\n {loading ? (\n <LoadingState\n variant=\"rows\"\n rows={6}\n label={tr('cosmicintersection.pages.discovery.loading', 'Searching the catalog…')}\n />\n ) : error ? (\n <ErrorState\n message={tr(\n 'cosmicintersection.pages.discovery.error',\n 'We could not run that search. Please try again.'\n )}\n onRetry={() => void poolQuery.refetch()}\n />\n ) : results.length === 0 ? (\n <EmptyState\n icon={Search}\n title={\n q || totalSelected > 0\n ? tr('cosmicintersection.pages.discovery.noDatasetsMatch', 'No datasets match')\n : tr('cosmicintersection.pages.discovery.startExploring', 'Start exploring')\n }\n description={\n q || totalSelected > 0\n ? tr(\n 'cosmicintersection.pages.discovery.noDatasetsMatchDescription',\n 'Try a broader search term or clear some facets.'\n )\n : tr(\n 'cosmicintersection.pages.discovery.startExploringDescription',\n 'Search by name, description or tag, or pick a facet to browse the catalog.'\n )\n }\n action={\n totalSelected > 0 ? (\n <Button type=\"button\" variant=\"outline\" onClick={handleClear}>\n {tr('cosmicintersection.pages.discovery.clearFilters', 'Clear filters')}\n </Button>\n ) : undefined\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3\">\n {results.map((dataset) => (\n <button\n key={dataset.id}\n type=\"button\"\n onClick={() => navigate(`datasets/${dataset.id}`)}\n className=\"group flex flex-col overflow-hidden rounded-card border border-border-default bg-bg-elevated text-left outline-none transition-colors hover:border-border-strong hover:bg-bg-sunken focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <div className=\"h-1 w-full bg-gradient-to-r from-accent-blue via-accent-purple to-accent-teal\" />\n <div className=\"flex flex-1 flex-col gap-3 p-4\">\n <h3 className=\"line-clamp-2 text-sm font-semibold text-text-primary\">\n {dataset.name}\n </h3>\n\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-text-secondary\">\n <span className=\"inline-flex items-center gap-1\">\n <FileText className=\"size-3.5\" aria-hidden=\"true\" />\n {dataset.format || 'No format'}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Database className=\"size-3.5\" aria-hidden=\"true\" />\n {formatBytes(dataset.sizeBytes)}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Rows3 className=\"size-3.5\" aria-hidden=\"true\" />\n {formatRows(dataset.rowCount)}\n </span>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={dataset.status} />\n <StatusBadge status={dataset.accessLevel} />\n </div>\n\n {dataset.tags.length > 0 ? (\n <div className=\"mt-auto flex flex-wrap gap-1 pt-1\">\n {dataset.tags.slice(0, 3).map((tag) => (\n <Badge key={tag} variant=\"secondary\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n {dataset.tags.length > 3 ? (\n <span className=\"text-xs text-text-secondary\">\n +{dataset.tags.length - 3}\n </span>\n ) : null}\n </div>\n ) : null}\n </div>\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"LineagePage.d.ts","sourceRoot":"","sources":["../../../../src/cosmicintersection/pages/lineage/LineagePage.tsx"],"names":[],"mappings":"AAkCA,MAAM,CAAC,OAAO,UAAU,WAAW,4CAoMlC"}
1
+ {"version":3,"file":"LineagePage.d.ts","sourceRoot":"","sources":["../../../../src/cosmicintersection/pages/lineage/LineagePage.tsx"],"names":[],"mappings":"AAmCA,MAAM,CAAC,OAAO,UAAU,WAAW,4CAqPlC"}