@burdenoff/microfe-bigconsole 2026.924.3 → 2026.925.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.
@@ -8,75 +8,75 @@ import { BigConsoleProvider as s } from "./context/BigConsoleContext.js";
8
8
  import "./context/index.js";
9
9
  import { BigConsoleQuotaProvider as c } from "./providers/BigConsoleQuotaProvider.js";
10
10
  import l from "./hooks/useFilterUrlSync.js";
11
- import ee from "./components/DrilldownDashboardRenderer.js";
12
- import { AiPreviewPanel as u } from "./components/preview/AiPreviewPanel.js";
13
- import d from "./components/DrilldownModal.js";
14
- import { useCallback as f, useEffect as p, useLayoutEffect as m, useRef as h, useState as g } from "react";
15
- import { useSearchParams as _ } from "react-router";
16
- import { ErrorBoundary as v } from "@burdenoff/fe-libs/shared/components";
17
- import { FeatureFlagProvider as y } from "@burdenoff/fe-libs/shared/feature-flags";
18
- import { PermissionProvider as b } from "@burdenoff/fe-libs/shared/providers/shell";
19
- import { BarChart3 as x, Boxes as S, Database as C, LayoutDashboard as w, Lightbulb as te, Settings as T } from "lucide-react";
20
- import { jsx as E, jsxs as D } from "react/jsx-runtime";
21
- import { resetFluidGridsClient as O, resetFluidGridsConfig as k, resetGlobalStoreClient as A, setFluidGridsClient as j, setFluidGridsConfig as M, setGlobalStoreClient as N } from "@burdenoff/fluidgrids-fe-sdk";
22
- import { useGatewayClient as P } from "@burdenoff/fe-libs/shared/graphql";
11
+ import u from "./components/DrilldownDashboardRenderer.js";
12
+ import { AiPreviewPanel as d } from "./components/preview/AiPreviewPanel.js";
13
+ import f from "./components/DrilldownModal.js";
14
+ import { useCallback as p, useEffect as m, useLayoutEffect as h, useRef as g, useState as _ } from "react";
15
+ import { useSearchParams as v } from "react-router";
16
+ import { ErrorBoundary as y } from "@burdenoff/fe-libs/shared/components";
17
+ import { FeatureFlagProvider as b } from "@burdenoff/fe-libs/shared/feature-flags";
18
+ import { PermissionProvider as x } from "@burdenoff/fe-libs/shared/providers/shell";
19
+ import { BarChart3 as S, Boxes as C, Database as w, LayoutDashboard as T, Lightbulb as E, Settings as ee } from "lucide-react";
20
+ import { jsx as D, jsxs as O } from "react/jsx-runtime";
21
+ import { resetFluidGridsClient as k, resetFluidGridsConfig as A, resetGlobalStoreClient as j, setFluidGridsClient as M, setFluidGridsConfig as N, setGlobalStoreClient as P } from "@burdenoff/fluidgrids-fe-sdk";
22
+ import { useGatewayClient as F } from "@burdenoff/fe-libs/shared/graphql";
23
23
  //#region src/bigconsole/BigConsoleRoot.tsx
24
- var F = i, I = r, L = a, R = /* @__PURE__ */ new Set(), z = (r) => {
25
- let { registerNavItems: i, registerFooterItems: a, basePath: z = "/bigconsole", navigate: B } = r, V = P("workspace"), H = P("global"), [U, W] = g(!1), [G, K] = g(null), q = (e, t) => (e.endsWith("/") ? e.slice(0, -1) : e) + (t.startsWith("/") ? t : `/${t}`);
26
- p(() => {
24
+ var I = i, L = r, R = a, z = /* @__PURE__ */ new Set(), B = (r) => {
25
+ let { registerNavItems: i, registerFooterItems: a, basePath: B = "/bigconsole", navigate: V } = r, H = F("workspace"), U = F("global"), [W, G] = _(!1), [K, q] = _(null), J = (e, t) => (e.endsWith("/") ? e.slice(0, -1) : e) + (t.startsWith("/") ? t : `/${t}`);
26
+ m(() => {
27
27
  if (i) return i({
28
28
  id: "bigconsole",
29
29
  label: "Analytics",
30
- path: z,
31
- icon: /* @__PURE__ */ E(x, { size: 20 }),
30
+ path: B,
31
+ icon: /* @__PURE__ */ D(S, { size: 20 }),
32
32
  order: 15,
33
33
  group: "bigconsole",
34
34
  children: [
35
35
  {
36
36
  id: "bigconsole-home",
37
37
  label: "Overview",
38
- path: z,
39
- icon: /* @__PURE__ */ E(x, { size: 16 }),
38
+ path: B,
39
+ icon: /* @__PURE__ */ D(S, { size: 16 }),
40
40
  order: 10
41
41
  },
42
42
  {
43
43
  id: "bigconsole-dashboards",
44
44
  label: "Dashboards",
45
- path: q(z, "dashboards"),
46
- icon: /* @__PURE__ */ E(w, { size: 16 }),
45
+ path: J(B, "dashboards"),
46
+ icon: /* @__PURE__ */ D(T, { size: 16 }),
47
47
  order: 20
48
48
  },
49
49
  {
50
50
  id: "bigconsole-datasources",
51
51
  label: "Data Sources",
52
- path: q(z, "datasources"),
53
- icon: /* @__PURE__ */ E(C, { size: 16 }),
52
+ path: J(B, "datasources"),
53
+ icon: /* @__PURE__ */ D(w, { size: 16 }),
54
54
  order: 30
55
55
  },
56
56
  {
57
57
  id: "bigconsole-insights",
58
58
  label: "AI Insights",
59
- path: q(z, "insights"),
60
- icon: /* @__PURE__ */ E(te, { size: 16 }),
59
+ path: J(B, "insights"),
60
+ icon: /* @__PURE__ */ D(E, { size: 16 }),
61
61
  order: 40
62
62
  },
63
63
  {
64
64
  id: "bigconsole-widgets",
65
65
  label: "Widget Demo",
66
- path: q(z, "widgets/demo"),
67
- icon: /* @__PURE__ */ E(S, { size: 16 }),
66
+ path: J(B, "widgets/demo"),
67
+ icon: /* @__PURE__ */ D(C, { size: 16 }),
68
68
  order: 50
69
69
  },
70
70
  {
71
71
  id: "bigconsole-settings",
72
72
  label: "Settings",
73
- path: q(z, "settings"),
74
- icon: /* @__PURE__ */ E(T, { size: 16 }),
73
+ path: J(B, "settings"),
74
+ icon: /* @__PURE__ */ D(ee, { size: 16 }),
75
75
  order: 60
76
76
  }
77
77
  ]
78
78
  });
79
- }, [i, z]), p(() => {
79
+ }, [i, B]), m(() => {
80
80
  if (a) return a([{
81
81
  id: "bigconsole-status",
82
82
  label: "Analytics Ready",
@@ -84,50 +84,50 @@ var F = i, I = r, L = a, R = /* @__PURE__ */ new Set(), z = (r) => {
84
84
  order: 15,
85
85
  section: "left"
86
86
  }]);
87
- }, [a]), p(() => (V && H && (j(V), N(H), W(!0)), () => {
88
- O(), A();
89
- }), [V, H]), p(() => {
90
- if (U) return;
87
+ }, [a]), m(() => (H && U && (M(H), P(U), G(!0)), () => {
88
+ k(), j();
89
+ }), [H, U]), m(() => {
90
+ if (W) return;
91
91
  let e = setTimeout(() => {
92
- U || K("BigConsole could not connect to the gateway. Please refresh the page or check your configuration.");
92
+ W || q("BigConsole could not connect to the gateway. Please refresh the page or check your configuration.");
93
93
  }, 1e4);
94
94
  return () => clearTimeout(e);
95
- }, [U]);
96
- let J = f(async (e) => {
95
+ }, [W]);
96
+ let Y = p(async (e) => {
97
97
  if (e.operation === "created") {
98
- if (!V) {
98
+ if (!H) {
99
99
  console.warn("[BigConsole] onWorkflowSaved: workspace client not ready");
100
100
  return;
101
101
  }
102
- if (!R.has(e.workflowKey)) {
103
- R.add(e.workflowKey);
102
+ if (!z.has(e.workflowKey)) {
103
+ z.add(e.workflowKey);
104
104
  try {
105
- if ((await V.query({
106
- query: F,
105
+ if ((await H.query({
106
+ query: I,
107
107
  variables: { key: e.workflowKey },
108
108
  fetchPolicy: "network-only"
109
109
  })).data?.workflowByKey) return;
110
- await V.mutate({
111
- mutation: I,
110
+ await H.mutate({
111
+ mutation: L,
112
112
  variables: { input: {
113
113
  name: e.name,
114
114
  key: e.workflowKey,
115
115
  description: e.description,
116
116
  externalWorkflowId: e.dbId
117
117
  } },
118
- refetchQueries: [{ query: L }]
118
+ refetchQueries: [{ query: R }]
119
119
  });
120
120
  } catch (e) {
121
121
  console.warn("[BigConsole] onWorkflowSaved: failed to create record:", e);
122
122
  } finally {
123
- R.delete(e.workflowKey);
123
+ z.delete(e.workflowKey);
124
124
  }
125
125
  }
126
126
  }
127
- }, [V]), Y = h(J);
128
- m(() => {
129
- Y.current = J;
130
- }, [J]), p(() => {
127
+ }, [H]), X = g(Y);
128
+ h(() => {
129
+ X.current = Y;
130
+ }, [Y]), m(() => {
131
131
  if (r.apiGatewayUrl) {
132
132
  let e = r.apiGatewayUrl.endsWith("/") ? r.apiGatewayUrl.slice(0, -1) : r.apiGatewayUrl;
133
133
  console.log("[BigConsole] Setting FluidGrids config", {
@@ -136,18 +136,18 @@ var F = i, I = r, L = a, R = /* @__PURE__ */ new Set(), z = (r) => {
136
136
  tenantId: r.tenantId,
137
137
  productId: r.productId,
138
138
  productName: r.productName
139
- }), M({
139
+ }), N({
140
140
  apiGatewayUrl: e,
141
141
  authToken: r.authToken,
142
142
  workspaceId: r.workspaceId ?? "",
143
143
  tenantId: r.tenantId,
144
144
  productId: r.productId ?? "bigconsole",
145
145
  productName: r.productName ?? "BigConsole",
146
- onWorkflowSaved: (e) => Y.current(e)
146
+ onWorkflowSaved: (e) => X.current(e)
147
147
  });
148
148
  }
149
149
  return () => {
150
- k();
150
+ A();
151
151
  };
152
152
  }, [
153
153
  r.apiGatewayUrl,
@@ -157,50 +157,60 @@ var F = i, I = r, L = a, R = /* @__PURE__ */ new Set(), z = (r) => {
157
157
  r.productId,
158
158
  r.productName
159
159
  ]);
160
- let [X, Z] = _(), Q = e((e) => e.isOpen), $ = e((e) => e.closeModal);
160
+ let [Z, Q] = v(), te = e((e) => e.isOpen), $ = e((e) => e.closeModal);
161
161
  l();
162
- let ne = f(() => {
162
+ let ne = p(() => {
163
163
  $();
164
- let e = new URLSearchParams(X);
164
+ let e = new URLSearchParams(Z);
165
165
  Array.from(e.keys()).forEach((t) => {
166
166
  (t.startsWith("filter_") || t.startsWith("ctx_")) && e.delete(t);
167
- }), Z(e, { replace: !0 }), document.activeElement instanceof HTMLElement && document.activeElement.blur();
167
+ }), Q(e, { replace: !0 }), document.activeElement instanceof HTMLElement && document.activeElement.blur();
168
168
  }, [
169
169
  $,
170
- X,
171
- Z
172
- ]), re = f(() => {
173
- if (B) {
174
- B("/billing");
170
+ Z,
171
+ Q
172
+ ]), re = p((e) => {
173
+ let t = new URLSearchParams();
174
+ for (let e of [
175
+ "org",
176
+ "workspace",
177
+ "project"
178
+ ]) {
179
+ let n = Z.get(e);
180
+ n && t.set(e, n);
181
+ }
182
+ let n = t.toString(), r = n ? `${e}?${n}` : e;
183
+ if (V) {
184
+ V(r);
175
185
  return;
176
186
  }
177
- window.location.assign("/billing");
178
- }, [B]), ie = f((e, t) => /* @__PURE__ */ E(ee, {
187
+ window.location.assign(r);
188
+ }, [V, Z]), ie = p((e, t) => /* @__PURE__ */ D(u, {
179
189
  dashboardId: e,
180
190
  params: t
181
191
  }), []);
182
- return U ? /* @__PURE__ */ E(v, {
183
- fallback: /* @__PURE__ */ E("div", {
192
+ return W ? /* @__PURE__ */ D(y, {
193
+ fallback: /* @__PURE__ */ D("div", {
184
194
  className: "p-6 text-center text-text-secondary",
185
195
  children: "Something went wrong loading BigConsole. Please refresh the page."
186
196
  }),
187
- children: /* @__PURE__ */ E(y, {
197
+ children: /* @__PURE__ */ D(b, {
188
198
  workspaceId: null,
189
199
  gateway: "global",
190
200
  defaultEnabled: !0,
191
- children: /* @__PURE__ */ E(b, {
201
+ children: /* @__PURE__ */ D(x, {
192
202
  scopeId: r.workspaceId ?? null,
193
203
  gateway: "workspace",
194
- children: /* @__PURE__ */ E(s, {
204
+ children: /* @__PURE__ */ D(s, {
195
205
  props: r,
196
- children: /* @__PURE__ */ E(c, {
206
+ children: /* @__PURE__ */ D(c, {
197
207
  onViewBilling: re,
198
- children: /* @__PURE__ */ E(n, {
199
- basePath: z,
200
- children: /* @__PURE__ */ D(t, { children: [
201
- /* @__PURE__ */ E(u, {}),
202
- /* @__PURE__ */ E(o, { ...r }),
203
- Q && /* @__PURE__ */ E(d, {
208
+ children: /* @__PURE__ */ D(n, {
209
+ basePath: B,
210
+ children: /* @__PURE__ */ O(t, { children: [
211
+ /* @__PURE__ */ D(d, {}),
212
+ /* @__PURE__ */ D(o, { ...r }),
213
+ te && /* @__PURE__ */ D(f, {
204
214
  onClose: ne,
205
215
  renderDashboard: ie
206
216
  })
@@ -210,26 +220,26 @@ var F = i, I = r, L = a, R = /* @__PURE__ */ new Set(), z = (r) => {
210
220
  })
211
221
  })
212
222
  })
213
- }) : G ? /* @__PURE__ */ E("div", {
223
+ }) : K ? /* @__PURE__ */ D("div", {
214
224
  className: "flex h-full min-h-[200px] w-full items-center justify-center p-6",
215
- children: /* @__PURE__ */ D("div", {
225
+ children: /* @__PURE__ */ O("div", {
216
226
  className: "text-center",
217
- children: [/* @__PURE__ */ E("p", {
227
+ children: [/* @__PURE__ */ D("p", {
218
228
  className: "text-sm text-text-secondary mb-4",
219
- children: G
220
- }), /* @__PURE__ */ E("button", {
229
+ children: K
230
+ }), /* @__PURE__ */ D("button", {
221
231
  type: "button",
222
232
  onClick: () => window.location.reload(),
223
233
  className: "px-4 py-2 text-sm font-medium text-white bg-action-primary-bg hover:bg-action-primary-bg/90 rounded-md transition-colors",
224
234
  children: "Reload Page"
225
235
  })]
226
236
  })
227
- }) : /* @__PURE__ */ E("div", {
237
+ }) : /* @__PURE__ */ D("div", {
228
238
  className: "flex h-full min-h-[200px] w-full items-center justify-center",
229
- children: /* @__PURE__ */ E("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-blue-600" })
239
+ children: /* @__PURE__ */ D("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-blue-600" })
230
240
  });
231
241
  };
232
242
  //#endregion
233
- export { z as BigConsoleRoot };
243
+ export { B as BigConsoleRoot };
234
244
 
235
245
  //# sourceMappingURL=BigConsoleRoot.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"BigConsoleRoot.js","names":[],"sources":["../../src/bigconsole/BigConsoleRoot.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useEffect, useCallback, useLayoutEffect, useRef, useState } from 'react';\nimport { useSearchParams } from 'react-router';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { ErrorBoundary } from '@burdenoff/fe-libs/shared/components';\nimport { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { PermissionProvider } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { BarChart3, Database, LayoutDashboard, Lightbulb, Settings, Boxes } from 'lucide-react';\nimport type { BigConsoleRootProps } from './types';\nimport { BigConsoleRoutes } from './BigConsoleRoutes';\nimport { BigConsoleProvider } from './context';\nimport { NavigationProvider, CrossFilterProvider } from './contexts';\nimport { BigConsoleQuotaProvider } from './providers/BigConsoleQuotaProvider';\nimport { AiPreviewPanel } from './components/preview/AiPreviewPanel';\nimport { DrilldownModal } from './components/DrilldownModal';\nimport { DrilldownDashboardRenderer } from './components/DrilldownDashboardRenderer';\nimport { useFilterUrlSync } from './hooks/useFilterUrlSync';\nimport { useDrilldownStore } from './store/drilldownStore';\n\n// FluidGrids SDK client injection (needed for workflow components)\nimport {\n setFluidGridsClient,\n resetFluidGridsClient,\n setFluidGridsConfig,\n resetFluidGridsConfig,\n setGlobalStoreClient,\n resetGlobalStoreClient,\n} from '@burdenoff/fluidgrids-fe-sdk';\n// Use the shared gateway Apollo clients from the shell\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\nimport {\n CreateWorkflowDocument,\n GetWorkflowByKeyDocument,\n ListWorkflowsDocument,\n type GetWorkflowByKeyQuery,\n type GetWorkflowByKeyQueryVariables,\n type CreateWorkflowMutation,\n type CreateWorkflowMutationVariables,\n type ListWorkflowsQuery,\n type ListWorkflowsQueryVariables,\n} from '../generated/wspace-operations';\n\nconst getWorkflowByKeyDocument = GetWorkflowByKeyDocument as unknown as TypedDocumentNode<\n GetWorkflowByKeyQuery,\n GetWorkflowByKeyQueryVariables\n>;\nconst createWorkflowDocument = CreateWorkflowDocument as unknown as TypedDocumentNode<\n CreateWorkflowMutation,\n CreateWorkflowMutationVariables\n>;\nconst listWorkflowsDocument = ListWorkflowsDocument as unknown as TypedDocumentNode<\n ListWorkflowsQuery,\n ListWorkflowsQueryVariables\n>;\n\n/**\n * Local contract for the workflow-save payload emitted by the embedded FluidGrids builder.\n * Kept explicit here to maintain strict typing at the BigConsole boundary.\n */\ninterface WorkflowSavedResult {\n dbId: string;\n workflowKey: string;\n version: string;\n name: string;\n description?: string;\n operation: 'created' | 'updated' | 'versioned';\n}\n\n// In-flight guard: prevents duplicate BigConsole records when the callback\n// fires twice in rapid succession (React StrictMode, retry, double-click).\n// Server unique constraint on `key` is the ultimate backstop.\nconst inFlightKeys = new Set<string>();\n\n/**\n * Root component for the BigConsole microfrontend.\n *\n * This is the main entry point that host applications import and render.\n * It sets up the necessary providers and renders the internal routing.\n * Automatically registers navigation items with the shell if registration functions are provided.\n *\n * @example\n * ```tsx\n * import { BigConsoleRoot, type BigConsoleRootProps } from '@burdenoff/microfe-bigconsole';\n *\n * function App() {\n * const props: BigConsoleRootProps = {\n * basePath: '/analytics',\n * navigate: (path) => router.push(path),\n * currentUser: { id: '1', email: 'user@example.com' },\n * apiGatewayUrl: 'https://api.example.com',\n * authToken: 'token',\n * };\n *\n * return <BigConsoleRoot {...props} />;\n * }\n * ```\n */\nexport const BigConsoleRoot: FC<BigConsoleRootProps> = (props) => {\n const { registerNavItems, registerFooterItems, basePath = '/bigconsole', navigate: hostNavigate } = props;\n\n // Use the shared Apollo clients from the shell (both workspace and global)\n const workspaceClient = useGatewayClient('workspace');\n const globalClient = useGatewayClient('global');\n const [clientReady, setClientReady] = useState(false);\n const [clientError, setClientError] = useState<string | null>(null);\n\n // Helper to join paths correctly, avoiding double slashes\n const joinPath = (base: string, path: string): string => {\n const cleanBase = base.endsWith('/') ? base.slice(0, -1) : base;\n const cleanPath = path.startsWith('/') ? path : `/${path}`;\n return cleanBase + cleanPath;\n };\n\n // Register navigation items with the shell\n useEffect(() => {\n if (!registerNavItems) return;\n\n const bigConsoleNavItem = {\n id: 'bigconsole',\n label: 'Analytics',\n path: basePath,\n icon: <BarChart3 size={20} />,\n order: 15,\n group: 'bigconsole',\n children: [\n {\n id: 'bigconsole-home',\n label: 'Overview',\n path: basePath,\n icon: <BarChart3 size={16} />,\n order: 10,\n },\n {\n id: 'bigconsole-dashboards',\n label: 'Dashboards',\n path: joinPath(basePath, 'dashboards'),\n icon: <LayoutDashboard size={16} />,\n order: 20,\n },\n {\n id: 'bigconsole-datasources',\n label: 'Data Sources',\n path: joinPath(basePath, 'datasources'),\n icon: <Database size={16} />,\n order: 30,\n },\n {\n id: 'bigconsole-insights',\n label: 'AI Insights',\n path: joinPath(basePath, 'insights'),\n icon: <Lightbulb size={16} />,\n order: 40,\n },\n {\n id: 'bigconsole-widgets',\n label: 'Widget Demo',\n path: joinPath(basePath, 'widgets/demo'),\n icon: <Boxes size={16} />,\n order: 50,\n },\n {\n id: 'bigconsole-settings',\n label: 'Settings',\n path: joinPath(basePath, 'settings'),\n icon: <Settings size={16} />,\n order: 60,\n },\n ],\n };\n\n const cleanup = registerNavItems(bigConsoleNavItem);\n return cleanup;\n }, [registerNavItems, basePath]);\n\n // Register footer items\n useEffect(() => {\n if (!registerFooterItems) return;\n\n const footerItems = [\n {\n id: 'bigconsole-status',\n label: 'Analytics Ready',\n type: 'status' as const,\n order: 15,\n section: 'left' as const,\n },\n ];\n\n const cleanup = registerFooterItems(footerItems);\n return cleanup;\n }, [registerFooterItems]);\n\n // Inject Apollo clients into FluidGrids SDK (for workflow components)\n // This synchronizes external stores (module-level client singletons in the SDK)\n // with React's lifecycle. The setState call is intentional — it gates rendering\n // so children don't mount before the SDK clients are available.\n //\n // KNOWN TRADE-OFF: clientReady is NOT reset in cleanup. When workspaceClient/globalClient\n // change references (e.g. token refresh), children continue rendering against a briefly\n // uninitialized SDK client until the next effect run re-injects the new client. This avoids\n // a visible spinner flash mid-session. Failed SDK requests during this short window will\n // be retried by React Query / Apollo's retry policies. If this becomes a real issue,\n // consider using startTransition to reset clientReady without the visual disruption.\n useEffect(() => {\n if (workspaceClient && globalClient) {\n setFluidGridsClient(workspaceClient);\n setGlobalStoreClient(globalClient);\n // eslint-disable-next-line react-hooks/set-state-in-effect -- intentional: gate children until external SDK clients are injected\n setClientReady(true);\n }\n return () => {\n resetFluidGridsClient();\n resetGlobalStoreClient();\n };\n }, [workspaceClient, globalClient]);\n\n // Timeout fallback: if gateway clients are never provided (test env, shell\n // misconfiguration, auth failure before hydration), show an error rather\n // than spinning indefinitely.\n useEffect(() => {\n if (clientReady) return;\n const timer = setTimeout(() => {\n if (!clientReady) {\n setClientError(\n 'BigConsole could not connect to the gateway. Please refresh the page or check your configuration.'\n );\n }\n }, 10_000);\n return () => clearTimeout(timer);\n }, [clientReady]);\n\n // Callback: sync workflow metadata to BigConsole after FluidGrids SDK saves\n const handleWorkflowSaved = useCallback(\n async (result: WorkflowSavedResult) => {\n // Only create a BigConsole record for brand-new workflows.\n // 'updated' = edit of existing, 'versioned' = new snapshot of same key — both already have records.\n if (result.operation !== 'created') return;\n\n if (!workspaceClient) {\n console.warn('[BigConsole] onWorkflowSaved: workspace client not ready');\n return;\n }\n\n // In-flight guard: prevent duplicate records from rapid double-fire\n // (React StrictMode, retry, fast double-click).\n if (inFlightKeys.has(result.workflowKey)) return;\n inFlightKeys.add(result.workflowKey);\n\n try {\n // Idempotency check — avoid creating duplicates if callback fires twice\n const existing = await workspaceClient.query<GetWorkflowByKeyQuery>({\n query: getWorkflowByKeyDocument,\n variables: { key: result.workflowKey },\n fetchPolicy: 'network-only',\n });\n\n if (existing.data?.workflowByKey) {\n return;\n }\n\n // Create BigConsole metadata record linked to the SDK workflow\n await workspaceClient.mutate<CreateWorkflowMutation>({\n mutation: createWorkflowDocument,\n variables: {\n input: {\n name: result.name,\n key: result.workflowKey,\n description: result.description,\n externalWorkflowId: result.dbId,\n },\n },\n refetchQueries: [{ query: listWorkflowsDocument }],\n });\n } catch (err) {\n // Possible: network error, or duplicate-key constraint (race condition).\n // SDK save already succeeded — never block the user.\n console.warn('[BigConsole] onWorkflowSaved: failed to create record:', err);\n } finally {\n inFlightKeys.delete(result.workflowKey);\n }\n },\n [workspaceClient]\n );\n\n // BOFF-1870 follow-up: keep this ref bridge explicit as the breadcrumb for\n // why this callback is decoupled from SDK config updates.\n // Ref-forwarding: decouple the callback from the SDK config effect so that\n // workspaceClient reference changes don't trigger resetFluidGridsConfig() + re-init.\n const handleWorkflowSavedRef = useRef(handleWorkflowSaved);\n // eslint-disable-next-line react-hooks/set-state-in-effect -- intentional: keep ref in sync with latest callback\n useLayoutEffect(() => {\n handleWorkflowSavedRef.current = handleWorkflowSaved;\n }, [handleWorkflowSaved]);\n\n // Configure FluidGrids SDK settings\n useEffect(() => {\n if (props.apiGatewayUrl) {\n const normalizedBase = props.apiGatewayUrl.endsWith('/') ? props.apiGatewayUrl.slice(0, -1) : props.apiGatewayUrl;\n\n console.log('[BigConsole] Setting FluidGrids config', {\n apiGatewayUrl: normalizedBase,\n workspaceId: props.workspaceId,\n tenantId: props.tenantId,\n productId: props.productId,\n productName: props.productName,\n });\n\n setFluidGridsConfig({\n apiGatewayUrl: normalizedBase,\n authToken: props.authToken,\n workspaceId: props.workspaceId ?? '',\n tenantId: props.tenantId,\n // Default to 'bigconsole' when unset — the SDK uses productId for workflow isolation\n // (filtering workflows by product). undefined would show all products' workflows.\n productId: props.productId ?? 'bigconsole',\n productName: props.productName ?? 'BigConsole',\n // Stable wrapper via ref — avoids SDK re-init when workspaceClient changes reference\n onWorkflowSaved: (result: WorkflowSavedResult) => handleWorkflowSavedRef.current(result),\n });\n }\n return () => {\n resetFluidGridsConfig();\n };\n // handleWorkflowSavedRef is stable (useRef) — intentionally excluded to prevent SDK re-init on client change\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [props.apiGatewayUrl, props.authToken, props.workspaceId, props.tenantId, props.productId, props.productName]);\n\n // Drilldown modal state from store\n const [searchParams, setSearchParams] = useSearchParams();\n const isModalOpen = useDrilldownStore((state) => state.isOpen);\n const closeModal = useDrilldownStore((state) => state.closeModal);\n useFilterUrlSync();\n\n const handleDrilldownClose = useCallback(() => {\n closeModal();\n // Clear any cross-filter URL params when closing drilldown modal\n const newParams = new URLSearchParams(searchParams);\n Array.from(newParams.keys()).forEach((key) => {\n if (key.startsWith('filter_') || key.startsWith('ctx_')) {\n newParams.delete(key);\n }\n });\n setSearchParams(newParams, { replace: true });\n // Remove focus from any element to prevent blue border\n if (document.activeElement instanceof HTMLElement) {\n document.activeElement.blur();\n }\n }, [closeModal, searchParams, setSearchParams]);\n\n const handleViewBilling = useCallback(() => {\n if (hostNavigate) {\n hostNavigate('/billing');\n return;\n }\n window.location.assign('/billing');\n }, [hostNavigate]);\n\n // Render a DrilldownDashboardRenderer inside the modal when a drilldown is active.\n // This callback is only invoked by DrilldownModal when isOpen && dashboardId are set,\n // so the renderer always receives a valid dashboardId.\n // NOTE: Must be declared before the early return below to satisfy React's rules of hooks.\n const renderDrilldownDashboard = useCallback(\n (dashboardId: string, params: Record<string, unknown>) => (\n <DrilldownDashboardRenderer dashboardId={dashboardId} params={params} />\n ),\n []\n );\n\n // Don't render until client is ready (same pattern as microfe-workflows)\n if (!clientReady) {\n if (clientError) {\n return (\n <div className=\"flex h-full min-h-[200px] w-full items-center justify-center p-6\">\n <div className=\"text-center\">\n <p className=\"text-sm text-text-secondary mb-4\">{clientError}</p>\n <button\n type=\"button\"\n onClick={() => window.location.reload()}\n className=\"px-4 py-2 text-sm font-medium text-white bg-action-primary-bg hover:bg-action-primary-bg/90 rounded-md transition-colors\"\n >\n Reload Page\n </button>\n </div>\n </div>\n );\n }\n return (\n <div className=\"flex h-full min-h-[200px] w-full items-center justify-center\">\n <div className=\"h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-blue-600\" />\n </div>\n );\n }\n\n return (\n <ErrorBoundary\n fallback={\n <div className=\"p-6 text-center text-text-secondary\">\n Something went wrong loading BigConsole. Please refresh the page.\n </div>\n }\n >\n <FeatureFlagProvider workspaceId={null} gateway=\"global\" defaultEnabled={true}>\n {/* PermissionProvider fetches myPermissions from wspace-rbac-svc via the\n workspace gateway (requires the shell's AuthProvider). Scoped to the\n active workspace so UI gating matches what the gateway enforces. */}\n <PermissionProvider scopeId={props.workspaceId ?? null} gateway=\"workspace\">\n <BigConsoleProvider props={props}>\n <BigConsoleQuotaProvider onViewBilling={handleViewBilling}>\n <NavigationProvider basePath={basePath}>\n <CrossFilterProvider>\n {/* Sits ABOVE the routed page, so when the assistant starts building\n you watch it happen on whatever screen you were already on —\n rather than having to guess which list to go and refresh. */}\n <AiPreviewPanel />\n <BigConsoleRoutes {...props} />\n {isModalOpen && (\n <DrilldownModal onClose={handleDrilldownClose} renderDashboard={renderDrilldownDashboard} />\n )}\n </CrossFilterProvider>\n </NavigationProvider>\n </BigConsoleQuotaProvider>\n </BigConsoleProvider>\n </PermissionProvider>\n </FeatureFlagProvider>\n </ErrorBoundary>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"BigConsoleRoot.js","names":[],"sources":["../../src/bigconsole/BigConsoleRoot.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useEffect, useCallback, useLayoutEffect, useRef, useState } from 'react';\nimport { useSearchParams } from 'react-router';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { ErrorBoundary } from '@burdenoff/fe-libs/shared/components';\nimport { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { PermissionProvider } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { BarChart3, Database, LayoutDashboard, Lightbulb, Settings, Boxes } from 'lucide-react';\nimport type { BigConsoleRootProps } from './types';\nimport { BigConsoleRoutes } from './BigConsoleRoutes';\nimport { BigConsoleProvider } from './context';\nimport { NavigationProvider, CrossFilterProvider } from './contexts';\nimport { BigConsoleQuotaProvider } from './providers/BigConsoleQuotaProvider';\nimport { AiPreviewPanel } from './components/preview/AiPreviewPanel';\nimport { DrilldownModal } from './components/DrilldownModal';\nimport { DrilldownDashboardRenderer } from './components/DrilldownDashboardRenderer';\nimport { useFilterUrlSync } from './hooks/useFilterUrlSync';\nimport { useDrilldownStore } from './store/drilldownStore';\n\n// FluidGrids SDK client injection (needed for workflow components)\nimport {\n setFluidGridsClient,\n resetFluidGridsClient,\n setFluidGridsConfig,\n resetFluidGridsConfig,\n setGlobalStoreClient,\n resetGlobalStoreClient,\n} from '@burdenoff/fluidgrids-fe-sdk';\n// Use the shared gateway Apollo clients from the shell\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\nimport {\n CreateWorkflowDocument,\n GetWorkflowByKeyDocument,\n ListWorkflowsDocument,\n type GetWorkflowByKeyQuery,\n type GetWorkflowByKeyQueryVariables,\n type CreateWorkflowMutation,\n type CreateWorkflowMutationVariables,\n type ListWorkflowsQuery,\n type ListWorkflowsQueryVariables,\n} from '../generated/wspace-operations';\n\nconst getWorkflowByKeyDocument = GetWorkflowByKeyDocument as unknown as TypedDocumentNode<\n GetWorkflowByKeyQuery,\n GetWorkflowByKeyQueryVariables\n>;\nconst createWorkflowDocument = CreateWorkflowDocument as unknown as TypedDocumentNode<\n CreateWorkflowMutation,\n CreateWorkflowMutationVariables\n>;\nconst listWorkflowsDocument = ListWorkflowsDocument as unknown as TypedDocumentNode<\n ListWorkflowsQuery,\n ListWorkflowsQueryVariables\n>;\n\n/**\n * Local contract for the workflow-save payload emitted by the embedded FluidGrids builder.\n * Kept explicit here to maintain strict typing at the BigConsole boundary.\n */\ninterface WorkflowSavedResult {\n dbId: string;\n workflowKey: string;\n version: string;\n name: string;\n description?: string;\n operation: 'created' | 'updated' | 'versioned';\n}\n\n// In-flight guard: prevents duplicate BigConsole records when the callback\n// fires twice in rapid succession (React StrictMode, retry, double-click).\n// Server unique constraint on `key` is the ultimate backstop.\nconst inFlightKeys = new Set<string>();\n\n/**\n * Root component for the BigConsole microfrontend.\n *\n * This is the main entry point that host applications import and render.\n * It sets up the necessary providers and renders the internal routing.\n * Automatically registers navigation items with the shell if registration functions are provided.\n *\n * @example\n * ```tsx\n * import { BigConsoleRoot, type BigConsoleRootProps } from '@burdenoff/microfe-bigconsole';\n *\n * function App() {\n * const props: BigConsoleRootProps = {\n * basePath: '/analytics',\n * navigate: (path) => router.push(path),\n * currentUser: { id: '1', email: 'user@example.com' },\n * apiGatewayUrl: 'https://api.example.com',\n * authToken: 'token',\n * };\n *\n * return <BigConsoleRoot {...props} />;\n * }\n * ```\n */\nexport const BigConsoleRoot: FC<BigConsoleRootProps> = (props) => {\n const { registerNavItems, registerFooterItems, basePath = '/bigconsole', navigate: hostNavigate } = props;\n\n // Use the shared Apollo clients from the shell (both workspace and global)\n const workspaceClient = useGatewayClient('workspace');\n const globalClient = useGatewayClient('global');\n const [clientReady, setClientReady] = useState(false);\n const [clientError, setClientError] = useState<string | null>(null);\n\n // Helper to join paths correctly, avoiding double slashes\n const joinPath = (base: string, path: string): string => {\n const cleanBase = base.endsWith('/') ? base.slice(0, -1) : base;\n const cleanPath = path.startsWith('/') ? path : `/${path}`;\n return cleanBase + cleanPath;\n };\n\n // Register navigation items with the shell\n useEffect(() => {\n if (!registerNavItems) return;\n\n const bigConsoleNavItem = {\n id: 'bigconsole',\n label: 'Analytics',\n path: basePath,\n icon: <BarChart3 size={20} />,\n order: 15,\n group: 'bigconsole',\n children: [\n {\n id: 'bigconsole-home',\n label: 'Overview',\n path: basePath,\n icon: <BarChart3 size={16} />,\n order: 10,\n },\n {\n id: 'bigconsole-dashboards',\n label: 'Dashboards',\n path: joinPath(basePath, 'dashboards'),\n icon: <LayoutDashboard size={16} />,\n order: 20,\n },\n {\n id: 'bigconsole-datasources',\n label: 'Data Sources',\n path: joinPath(basePath, 'datasources'),\n icon: <Database size={16} />,\n order: 30,\n },\n {\n id: 'bigconsole-insights',\n label: 'AI Insights',\n path: joinPath(basePath, 'insights'),\n icon: <Lightbulb size={16} />,\n order: 40,\n },\n {\n id: 'bigconsole-widgets',\n label: 'Widget Demo',\n path: joinPath(basePath, 'widgets/demo'),\n icon: <Boxes size={16} />,\n order: 50,\n },\n {\n id: 'bigconsole-settings',\n label: 'Settings',\n path: joinPath(basePath, 'settings'),\n icon: <Settings size={16} />,\n order: 60,\n },\n ],\n };\n\n const cleanup = registerNavItems(bigConsoleNavItem);\n return cleanup;\n }, [registerNavItems, basePath]);\n\n // Register footer items\n useEffect(() => {\n if (!registerFooterItems) return;\n\n const footerItems = [\n {\n id: 'bigconsole-status',\n label: 'Analytics Ready',\n type: 'status' as const,\n order: 15,\n section: 'left' as const,\n },\n ];\n\n const cleanup = registerFooterItems(footerItems);\n return cleanup;\n }, [registerFooterItems]);\n\n // Inject Apollo clients into FluidGrids SDK (for workflow components)\n // This synchronizes external stores (module-level client singletons in the SDK)\n // with React's lifecycle. The setState call is intentional — it gates rendering\n // so children don't mount before the SDK clients are available.\n //\n // KNOWN TRADE-OFF: clientReady is NOT reset in cleanup. When workspaceClient/globalClient\n // change references (e.g. token refresh), children continue rendering against a briefly\n // uninitialized SDK client until the next effect run re-injects the new client. This avoids\n // a visible spinner flash mid-session. Failed SDK requests during this short window will\n // be retried by React Query / Apollo's retry policies. If this becomes a real issue,\n // consider using startTransition to reset clientReady without the visual disruption.\n useEffect(() => {\n if (workspaceClient && globalClient) {\n setFluidGridsClient(workspaceClient);\n setGlobalStoreClient(globalClient);\n // eslint-disable-next-line react-hooks/set-state-in-effect -- intentional: gate children until external SDK clients are injected\n setClientReady(true);\n }\n return () => {\n resetFluidGridsClient();\n resetGlobalStoreClient();\n };\n }, [workspaceClient, globalClient]);\n\n // Timeout fallback: if gateway clients are never provided (test env, shell\n // misconfiguration, auth failure before hydration), show an error rather\n // than spinning indefinitely.\n useEffect(() => {\n if (clientReady) return;\n const timer = setTimeout(() => {\n if (!clientReady) {\n setClientError(\n 'BigConsole could not connect to the gateway. Please refresh the page or check your configuration.'\n );\n }\n }, 10_000);\n return () => clearTimeout(timer);\n }, [clientReady]);\n\n // Callback: sync workflow metadata to BigConsole after FluidGrids SDK saves\n const handleWorkflowSaved = useCallback(\n async (result: WorkflowSavedResult) => {\n // Only create a BigConsole record for brand-new workflows.\n // 'updated' = edit of existing, 'versioned' = new snapshot of same key — both already have records.\n if (result.operation !== 'created') return;\n\n if (!workspaceClient) {\n console.warn('[BigConsole] onWorkflowSaved: workspace client not ready');\n return;\n }\n\n // In-flight guard: prevent duplicate records from rapid double-fire\n // (React StrictMode, retry, fast double-click).\n if (inFlightKeys.has(result.workflowKey)) return;\n inFlightKeys.add(result.workflowKey);\n\n try {\n // Idempotency check — avoid creating duplicates if callback fires twice\n const existing = await workspaceClient.query<GetWorkflowByKeyQuery>({\n query: getWorkflowByKeyDocument,\n variables: { key: result.workflowKey },\n fetchPolicy: 'network-only',\n });\n\n if (existing.data?.workflowByKey) {\n return;\n }\n\n // Create BigConsole metadata record linked to the SDK workflow\n await workspaceClient.mutate<CreateWorkflowMutation>({\n mutation: createWorkflowDocument,\n variables: {\n input: {\n name: result.name,\n key: result.workflowKey,\n description: result.description,\n externalWorkflowId: result.dbId,\n },\n },\n refetchQueries: [{ query: listWorkflowsDocument }],\n });\n } catch (err) {\n // Possible: network error, or duplicate-key constraint (race condition).\n // SDK save already succeeded — never block the user.\n console.warn('[BigConsole] onWorkflowSaved: failed to create record:', err);\n } finally {\n inFlightKeys.delete(result.workflowKey);\n }\n },\n [workspaceClient]\n );\n\n // BOFF-1870 follow-up: keep this ref bridge explicit as the breadcrumb for\n // why this callback is decoupled from SDK config updates.\n // Ref-forwarding: decouple the callback from the SDK config effect so that\n // workspaceClient reference changes don't trigger resetFluidGridsConfig() + re-init.\n const handleWorkflowSavedRef = useRef(handleWorkflowSaved);\n // eslint-disable-next-line react-hooks/set-state-in-effect -- intentional: keep ref in sync with latest callback\n useLayoutEffect(() => {\n handleWorkflowSavedRef.current = handleWorkflowSaved;\n }, [handleWorkflowSaved]);\n\n // Configure FluidGrids SDK settings\n useEffect(() => {\n if (props.apiGatewayUrl) {\n const normalizedBase = props.apiGatewayUrl.endsWith('/') ? props.apiGatewayUrl.slice(0, -1) : props.apiGatewayUrl;\n\n console.log('[BigConsole] Setting FluidGrids config', {\n apiGatewayUrl: normalizedBase,\n workspaceId: props.workspaceId,\n tenantId: props.tenantId,\n productId: props.productId,\n productName: props.productName,\n });\n\n setFluidGridsConfig({\n apiGatewayUrl: normalizedBase,\n authToken: props.authToken,\n workspaceId: props.workspaceId ?? '',\n tenantId: props.tenantId,\n // Default to 'bigconsole' when unset — the SDK uses productId for workflow isolation\n // (filtering workflows by product). undefined would show all products' workflows.\n productId: props.productId ?? 'bigconsole',\n productName: props.productName ?? 'BigConsole',\n // Stable wrapper via ref — avoids SDK re-init when workspaceClient changes reference\n onWorkflowSaved: (result: WorkflowSavedResult) => handleWorkflowSavedRef.current(result),\n });\n }\n return () => {\n resetFluidGridsConfig();\n };\n // handleWorkflowSavedRef is stable (useRef) — intentionally excluded to prevent SDK re-init on client change\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [props.apiGatewayUrl, props.authToken, props.workspaceId, props.tenantId, props.productId, props.productName]);\n\n // Drilldown modal state from store\n const [searchParams, setSearchParams] = useSearchParams();\n const isModalOpen = useDrilldownStore((state) => state.isOpen);\n const closeModal = useDrilldownStore((state) => state.closeModal);\n useFilterUrlSync();\n\n const handleDrilldownClose = useCallback(() => {\n closeModal();\n // Clear any cross-filter URL params when closing drilldown modal\n const newParams = new URLSearchParams(searchParams);\n Array.from(newParams.keys()).forEach((key) => {\n if (key.startsWith('filter_') || key.startsWith('ctx_')) {\n newParams.delete(key);\n }\n });\n setSearchParams(newParams, { replace: true });\n // Remove focus from any element to prevent blue border\n if (document.activeElement instanceof HTMLElement) {\n document.activeElement.blur();\n }\n }, [closeModal, searchParams, setSearchParams]);\n\n const handleViewBilling = useCallback(\n (path: string) => {\n // Billing lives outside the /bigconsole basePath. Carry the active\n // org/workspace/project so it opens on the same context (BOFF-7913);\n // BigConsole's own params (filters, cross-filter ctx) stay behind.\n const context = new URLSearchParams();\n for (const key of ['org', 'workspace', 'project']) {\n const value = searchParams.get(key);\n if (value) context.set(key, value);\n }\n const query = context.toString();\n const target = query ? `${path}?${query}` : path;\n if (hostNavigate) {\n hostNavigate(target);\n return;\n }\n window.location.assign(target);\n },\n [hostNavigate, searchParams]\n );\n\n // Render a DrilldownDashboardRenderer inside the modal when a drilldown is active.\n // This callback is only invoked by DrilldownModal when isOpen && dashboardId are set,\n // so the renderer always receives a valid dashboardId.\n // NOTE: Must be declared before the early return below to satisfy React's rules of hooks.\n const renderDrilldownDashboard = useCallback(\n (dashboardId: string, params: Record<string, unknown>) => (\n <DrilldownDashboardRenderer dashboardId={dashboardId} params={params} />\n ),\n []\n );\n\n // Don't render until client is ready (same pattern as microfe-workflows)\n if (!clientReady) {\n if (clientError) {\n return (\n <div className=\"flex h-full min-h-[200px] w-full items-center justify-center p-6\">\n <div className=\"text-center\">\n <p className=\"text-sm text-text-secondary mb-4\">{clientError}</p>\n <button\n type=\"button\"\n onClick={() => window.location.reload()}\n className=\"px-4 py-2 text-sm font-medium text-white bg-action-primary-bg hover:bg-action-primary-bg/90 rounded-md transition-colors\"\n >\n Reload Page\n </button>\n </div>\n </div>\n );\n }\n return (\n <div className=\"flex h-full min-h-[200px] w-full items-center justify-center\">\n <div className=\"h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-blue-600\" />\n </div>\n );\n }\n\n return (\n <ErrorBoundary\n fallback={\n <div className=\"p-6 text-center text-text-secondary\">\n Something went wrong loading BigConsole. Please refresh the page.\n </div>\n }\n >\n <FeatureFlagProvider workspaceId={null} gateway=\"global\" defaultEnabled={true}>\n {/* PermissionProvider fetches myPermissions from wspace-rbac-svc via the\n workspace gateway (requires the shell's AuthProvider). Scoped to the\n active workspace so UI gating matches what the gateway enforces. */}\n <PermissionProvider scopeId={props.workspaceId ?? null} gateway=\"workspace\">\n <BigConsoleProvider props={props}>\n <BigConsoleQuotaProvider onViewBilling={handleViewBilling}>\n <NavigationProvider basePath={basePath}>\n <CrossFilterProvider>\n {/* Sits ABOVE the routed page, so when the assistant starts building\n you watch it happen on whatever screen you were already on —\n rather than having to guess which list to go and refresh. */}\n <AiPreviewPanel />\n <BigConsoleRoutes {...props} />\n {isModalOpen && (\n <DrilldownModal onClose={handleDrilldownClose} renderDashboard={renderDrilldownDashboard} />\n )}\n </CrossFilterProvider>\n </NavigationProvider>\n </BigConsoleQuotaProvider>\n </BigConsoleProvider>\n </PermissionProvider>\n </FeatureFlagProvider>\n </ErrorBoundary>\n );\n};\n"],"mappings":"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"}
@@ -3,20 +3,20 @@ import { PreviewMeta as n, STEP_LABEL as r, formatAgo as i, previewLabels as a }
3
3
  import { previewRunAction as o } from "./previewRunAction.js";
4
4
  import { useMemo as s } from "react";
5
5
  import { jsx as c } from "react/jsx-runtime";
6
- import { AssistantEntityPreview as l, useAssistantStore as u } from "@burdenoff/fe-libs/shared/assistant";
7
- import { useI18n as d } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
6
+ import { useI18n as l } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
7
+ import { AssistantEntityPreview as u, useAssistantStore as d } from "@burdenoff/fe-libs/shared/assistant";
8
8
  //#region src/bigconsole/assistant/previewController.tsx
9
9
  function f() {
10
- let { t: o } = d(), s = t((e) => e.isRunning), u = t((e) => e.steps), f = t((e) => e.caption), p = t((e) => e.error), m = t((e) => e.updatedAt), h = e.map((e) => ({
10
+ let { t: o } = l(), s = t((e) => e.isRunning), d = t((e) => e.steps), f = t((e) => e.caption), p = t((e) => e.error), m = t((e) => e.updatedAt), h = e.map((e) => ({
11
11
  id: e,
12
12
  label: o(r[e].key, r[e].fallback),
13
- status: u[e]
13
+ status: d[e]
14
14
  }));
15
15
  return /* @__PURE__ */ c("div", {
16
16
  "data-testid": "ai-preview-panel",
17
17
  "data-variant": "assistant",
18
18
  className: "min-w-0 p-3",
19
- children: /* @__PURE__ */ c(l, {
19
+ children: /* @__PURE__ */ c(u, {
20
20
  title: o("bigconsole.widget.aiPreviewMini.title", "AI Preview"),
21
21
  steps: h,
22
22
  caption: s ? f || o("bigconsole.widget.aiPreviewMini.working", "Working…") : o("bigconsole.widget.aiPreviewMini.done", "Done"),
@@ -34,7 +34,7 @@ function f() {
34
34
  }
35
35
  var p = /* @__PURE__ */ new Set();
36
36
  function m(e) {
37
- let n = u.getState(), r = n.conversations.find((t) => t.id === e.run.conversationId)?.mode ?? n.mode;
37
+ let n = d.getState(), r = n.conversations.find((t) => t.id === e.run.conversationId)?.mode ?? n.mode;
38
38
  if (o(e.kind, e.run.runId, r, p) === "ignore") return;
39
39
  let i = t.getState();
40
40
  if (e.kind === "start") {