@burdenoff/microfe-bigconsole 2026.716.4 → 2026.717.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.
@@ -1,89 +1,50 @@
1
1
  import { useBigConsoleNavigate as e } from "../../context/BigConsoleContext.js";
2
2
  import "../../context/index.js";
3
3
  import { PREVIEW_STEPS as t, useAssistantRunStore as n } from "../../assistant/assistantRunStore.js";
4
- import r from "../../hooks/useDashboardOperations.js";
5
- import i from "../../hooks/useDataSinkOperations.js";
6
- import a from "../../hooks/useParserOperations.js";
7
- import o from "../../hooks/useWidgetOperations.js";
8
- import { EmptyOrLoading as s, StepRail as ee, formatAgo as c, useAssistantSidebarWidth as te } from "./previewShared.js";
9
- import { useEffect as l, useMemo as u, useRef as d, useState as f } from "react";
10
- import { Loader2 as p, Sparkles as m, X as h } from "lucide-react";
11
- import { Badge as g, Button as _, Tabs as ne, TabsContent as v, TabsList as y, TabsTrigger as b } from "@burdenoff/fe-libs/ui";
4
+ import { StepRail as r, formatAgo as i, useAssistantSidebarWidth as a } from "./previewShared.js";
5
+ import { DashboardContent as o, DataSinkContent as s, ParserContent as c, WidgetsContent as l } from "./AiPreviewContent.js";
6
+ import { useEffect as u, useState as d } from "react";
7
+ import { ArrowUpRight as f, Loader2 as p, Sparkles as m, X as h } from "lucide-react";
8
+ import { Button as g, Tabs as _, TabsContent as v, TabsList as y, TabsTrigger as b } from "@burdenoff/fe-libs/ui";
12
9
  import { jsx as x, jsxs as S } from "react/jsx-runtime";
13
10
  //#region src/bigconsole/components/preview/AiPreviewPanel.tsx
14
- var C = 3500, w = 3e4, T = "default";
15
- function E({ id: e, title: t, meta: n, isNew: r, onOpen: i }) {
11
+ function C({ label: e, onOpen: t }) {
16
12
  return /* @__PURE__ */ S("div", {
17
- "data-testid": "ai-preview-row",
18
- "data-entity-id": e,
19
- className: ["flex items-center justify-between gap-3 rounded-md border px-3 py-2", r ? "border-status-success-border bg-status-success-bg/30" : "border-border-subtle bg-bg-surface"].join(" "),
20
- children: [/* @__PURE__ */ S("div", {
21
- className: "min-w-0",
22
- children: [/* @__PURE__ */ x("p", {
23
- className: "truncate text-sm font-medium text-text-primary",
24
- children: t
25
- }), n ? /* @__PURE__ */ x("p", {
26
- className: "truncate text-xs text-text-muted",
27
- children: n
28
- }) : null]
29
- }), /* @__PURE__ */ S("div", {
30
- className: "flex shrink-0 items-center gap-2",
31
- children: [r ? /* @__PURE__ */ S(g, {
32
- className: "bg-status-success-bg text-status-success-text",
33
- children: [/* @__PURE__ */ x(m, { className: "mr-1 size-3" }), "just built"]
34
- }) : null, i ? /* @__PURE__ */ x(_, {
35
- size: "sm",
36
- variant: "ghost",
37
- onClick: i,
38
- children: "Open"
39
- }) : null]
40
- })]
13
+ className: "mb-2 flex items-center justify-between gap-2",
14
+ children: [/* @__PURE__ */ x("span", {
15
+ className: "text-xs font-semibold uppercase tracking-wide text-text-muted",
16
+ children: e
17
+ }), t ? /* @__PURE__ */ S(g, {
18
+ size: "sm",
19
+ variant: "outline",
20
+ onClick: t,
21
+ "data-testid": "ai-preview-open-entity",
22
+ children: ["Open", /* @__PURE__ */ x(f, { className: "ml-1 size-3.5" })]
23
+ }) : null]
41
24
  });
42
25
  }
43
- function D() {
44
- let g = e(), D = n((e) => e.isRunning), O = n((e) => e.previewOpen), k = n((e) => e.hasRun), A = n((e) => e.steps), j = n((e) => e.entityIds), M = n((e) => e.caption), N = n((e) => e.error), P = n((e) => e.updatedAt), F = n((e) => e.openPreview), I = n((e) => e.closePreview), L = n((e) => e.setRichMounted);
45
- l(() => (L(!0), () => L(!1)), [L]);
46
- let [R, z] = f("datasink"), [B, V] = f(() => Date.now()), H = j.dashboard, U = i({ skipFetch: !0 }), W = r(), G = a({
47
- consoleId: H ?? T,
48
- skipFetch: !0
49
- }), K = o(void 0, H, { skipFetch: !H }), q = d({
50
- sinks: U.refetch,
51
- dashboards: W.refetch,
52
- parsers: G.refetch,
53
- widgets: K.refetch
54
- });
55
- q.current = {
56
- sinks: U.refetch,
57
- dashboards: W.refetch,
58
- parsers: G.refetch,
59
- widgets: K.refetch
60
- }, l(() => {
61
- if (!k) return;
62
- let e = Infinity;
63
- D || (e = Date.now() + w);
64
- let t = () => {
65
- typeof document < "u" && document.visibilityState !== "visible" || Date.now() > e || (q.current.sinks(), q.current.dashboards(), H && (q.current.parsers(), q.current.widgets()), V(Date.now()));
66
- };
67
- t();
68
- let n = window.setInterval(t, C), r = () => {
69
- document.visibilityState === "visible" && t();
70
- };
71
- return document.addEventListener("visibilitychange", r), () => {
72
- window.clearInterval(n), document.removeEventListener("visibilitychange", r);
73
- };
74
- }, [
75
- k,
76
- D,
77
- H
78
- ]), l(() => {
79
- let e = t.find((e) => A[e] === "running");
80
- e && z(e);
81
- }, [A]);
82
- let J = P ? Math.max(0, Math.floor((B - P) / 1e3)) : 0, Y = te(), X = u(() => U.dataSinks.slice(0, 25), [U.dataSinks]), Z = u(() => W.dashboards.slice(0, 25), [W.dashboards]), Q = u(() => G.parsers.slice(0, 25), [G.parsers]), $ = u(() => K.widgets.slice(0, 25), [K.widgets]);
83
- return k ? O ? /* @__PURE__ */ S("section", {
26
+ function w() {
27
+ let f = e(), w = n((e) => e.isRunning), T = n((e) => e.previewOpen), E = n((e) => e.hasRun), D = n((e) => e.steps), O = n((e) => e.entityIds), k = n((e) => e.caption), A = n((e) => e.error), j = n((e) => e.updatedAt), M = n((e) => e.openPreview), N = n((e) => e.closePreview), P = n((e) => e.setRichMounted);
28
+ u(() => (P(!0), () => P(!1)), [P]);
29
+ let [F, I] = d("datasink"), [L, R] = d(() => Date.now()), z = O.dashboard;
30
+ u(() => {
31
+ if (!w) return;
32
+ let e = window.setInterval(() => R(Date.now()), 1e3);
33
+ return () => window.clearInterval(e);
34
+ }, [w]), u(() => {
35
+ let e = t.filter((e) => D[e] === "done"), n = e[e.length - 1];
36
+ if (n) {
37
+ I(n);
38
+ return;
39
+ }
40
+ let r = t.find((e) => D[e] === "running");
41
+ r && I(r);
42
+ }, [D]);
43
+ let B = j ? Math.max(0, Math.floor((L - j) / 1e3)) : 0, V = a();
44
+ return E ? T ? /* @__PURE__ */ S("section", {
84
45
  "data-testid": "ai-preview-panel",
85
46
  "aria-label": "AI preview",
86
- style: { marginRight: Y ? Y + 16 : void 0 },
47
+ style: { marginRight: V ? V + 16 : void 0 },
87
48
  className: "mx-4 mt-3 overflow-hidden rounded-lg border border-border-default bg-bg-sunken shadow-sm md:mx-6",
88
49
  children: [
89
50
  /* @__PURE__ */ S("div", {
@@ -96,7 +57,7 @@ function D() {
96
57
  className: "truncate text-sm font-semibold text-text-primary",
97
58
  children: "AI Preview"
98
59
  }),
99
- D ? /* @__PURE__ */ S("span", {
60
+ w ? /* @__PURE__ */ S("span", {
100
61
  className: "inline-flex items-center gap-1.5 rounded-full bg-status-success-bg px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-status-success-text",
101
62
  "data-testid": "ai-preview-live",
102
63
  children: [/* @__PURE__ */ S("span", {
@@ -108,17 +69,17 @@ function D() {
108
69
  className: "truncate text-xs text-text-muted",
109
70
  "data-testid": "ai-preview-ago",
110
71
  children: [
111
- D ? M || "Working…" : N ? "Stopped" : "Done",
72
+ w ? k || "Working…" : A ? "Stopped" : "Done",
112
73
  " · updated",
113
74
  " ",
114
- c(J)
75
+ i(B)
115
76
  ]
116
77
  })
117
78
  ]
118
- }), /* @__PURE__ */ x(_, {
79
+ }), /* @__PURE__ */ x(g, {
119
80
  size: "sm",
120
81
  variant: "ghost",
121
- onClick: I,
82
+ onClick: N,
122
83
  "aria-label": "Close preview",
123
84
  "data-testid": "ai-preview-close",
124
85
  children: /* @__PURE__ */ x(h, { className: "size-4" })
@@ -126,126 +87,101 @@ function D() {
126
87
  }),
127
88
  /* @__PURE__ */ x("div", {
128
89
  className: "border-b border-border-subtle px-3 py-2",
129
- children: /* @__PURE__ */ x(ee, { steps: A })
90
+ children: /* @__PURE__ */ x(r, { steps: D })
130
91
  }),
131
- /* @__PURE__ */ S(ne, {
132
- value: R,
133
- onValueChange: (e) => z(e),
92
+ /* @__PURE__ */ S(_, {
93
+ value: F,
94
+ onValueChange: (e) => I(e),
134
95
  children: [/* @__PURE__ */ S(y, {
135
96
  className: "mx-3 mt-2",
136
97
  children: [
137
98
  /* @__PURE__ */ x(b, {
138
99
  value: "datasink",
139
100
  "data-testid": "ai-preview-tab-datasink",
140
- children: "Data Sinks"
101
+ children: "Data Sink"
141
102
  }),
142
103
  /* @__PURE__ */ x(b, {
143
104
  value: "dashboard",
144
105
  "data-testid": "ai-preview-tab-dashboard",
145
- children: "Dashboards"
106
+ children: "Dashboard"
146
107
  }),
147
108
  /* @__PURE__ */ x(b, {
148
109
  value: "parser",
149
110
  "data-testid": "ai-preview-tab-parser",
150
- children: "Parsers"
111
+ children: "Parser"
151
112
  }),
152
113
  /* @__PURE__ */ x(b, {
153
114
  value: "widget",
154
115
  "data-testid": "ai-preview-tab-widget",
155
- children: "Widgets"
116
+ children: "Widget"
156
117
  })
157
118
  ]
158
119
  }), /* @__PURE__ */ S("div", {
159
- className: "max-h-[320px] overflow-y-auto p-3",
120
+ className: "max-h-[440px] overflow-y-auto p-3",
160
121
  children: [
161
- /* @__PURE__ */ x(v, {
122
+ /* @__PURE__ */ S(v, {
162
123
  value: "datasink",
163
- className: "space-y-2",
164
- children: X.length === 0 ? /* @__PURE__ */ x(s, {
165
- loading: U.loading,
166
- empty: "No data sinks yet."
167
- }) : X.map((e) => /* @__PURE__ */ x(E, {
168
- id: e.id,
169
- title: e.name ?? e.key,
170
- meta: e.status,
171
- isNew: e.id === j.datasink,
172
- onOpen: () => g(`/datasinks/${e.id}`)
173
- }, e.id))
124
+ children: [/* @__PURE__ */ x(C, {
125
+ label: "Data Sink",
126
+ onOpen: O.datasink ? () => f(`/datasinks/${O.datasink}`) : void 0
127
+ }), /* @__PURE__ */ x(s, {
128
+ id: O.datasink,
129
+ live: w
130
+ })]
174
131
  }),
175
- /* @__PURE__ */ x(v, {
132
+ /* @__PURE__ */ S(v, {
176
133
  value: "dashboard",
177
- className: "space-y-2",
178
- children: Z.length === 0 ? /* @__PURE__ */ x(s, {
179
- loading: W.loading,
180
- empty: "No dashboards yet."
181
- }) : Z.map((e) => /* @__PURE__ */ x(E, {
182
- id: e.id,
183
- title: e.name,
184
- meta: `${String(e.widgetCount ?? 0)} widget(s)`,
185
- isNew: e.id === j.dashboard,
186
- onOpen: () => g(`/dashboards/${e.id}`)
187
- }, e.id))
134
+ children: [/* @__PURE__ */ x(C, {
135
+ label: "Dashboard",
136
+ onOpen: O.dashboard ? () => f(`/dashboards/${O.dashboard}`) : void 0
137
+ }), /* @__PURE__ */ x(o, { dashboardId: O.dashboard })]
188
138
  }),
189
- /* @__PURE__ */ x(v, {
139
+ /* @__PURE__ */ S(v, {
190
140
  value: "parser",
191
- className: "space-y-2",
192
- children: H ? Q.length === 0 ? /* @__PURE__ */ x(s, {
193
- loading: G.loading,
194
- empty: "No parsers on this dashboard yet."
195
- }) : Q.map((e) => /* @__PURE__ */ x(E, {
196
- id: e.id,
197
- title: e.name,
198
- meta: `${e.type} · ${e.status}`,
199
- isNew: e.id === j.parser,
200
- onOpen: () => g(`/parsers/${e.id}`)
201
- }, e.id)) : /* @__PURE__ */ x("p", {
202
- className: "px-1 py-6 text-sm text-text-muted",
203
- children: "Parsers are listed per dashboard — waiting for the dashboard to be created."
204
- })
141
+ children: [/* @__PURE__ */ x(C, {
142
+ label: "Parser",
143
+ onOpen: O.parser ? () => f(`/parsers/${O.parser}`) : void 0
144
+ }), /* @__PURE__ */ x(c, {
145
+ parserId: O.parser,
146
+ live: w
147
+ })]
205
148
  }),
206
- /* @__PURE__ */ x(v, {
149
+ /* @__PURE__ */ S(v, {
207
150
  value: "widget",
208
- className: "space-y-2",
209
- children: H ? $.length === 0 ? /* @__PURE__ */ x(s, {
210
- loading: K.loading,
211
- empty: "No widgets on this dashboard yet."
212
- }) : $.map((e) => /* @__PURE__ */ x(E, {
213
- id: e.id,
214
- title: e.title,
215
- meta: e.type,
216
- isNew: e.id === $[$.length - 1]?.id && A.widget === "done",
217
- onOpen: () => g(`/dashboards/${H}`)
218
- }, e.id)) : /* @__PURE__ */ x("p", {
219
- className: "px-1 py-6 text-sm text-text-muted",
220
- children: "Widgets live on a dashboard — waiting for the dashboard to be created."
221
- })
151
+ children: [/* @__PURE__ */ x(C, {
152
+ label: "Widget",
153
+ onOpen: z ? () => f(`/dashboards/${z}`) : void 0
154
+ }), /* @__PURE__ */ x(l, {
155
+ dashboardId: z,
156
+ live: w
157
+ })]
222
158
  })
223
159
  ]
224
160
  })]
225
161
  }),
226
- N ? /* @__PURE__ */ x("p", {
162
+ A ? /* @__PURE__ */ x("p", {
227
163
  className: "border-t border-border-subtle px-3 py-2 text-xs text-status-error-text",
228
164
  "data-testid": "ai-preview-error",
229
- children: N
165
+ children: A
230
166
  }) : null
231
167
  ]
232
168
  }) : /* @__PURE__ */ x("div", {
233
169
  className: "flex justify-end px-4 pt-3 md:px-6",
234
- style: { marginRight: Y },
235
- children: /* @__PURE__ */ S(_, {
170
+ style: { marginRight: V },
171
+ children: /* @__PURE__ */ S(g, {
236
172
  size: "sm",
237
173
  variant: "outline",
238
- onClick: F,
174
+ onClick: M,
239
175
  "data-testid": "ai-preview-open",
240
176
  children: [
241
177
  /* @__PURE__ */ x(m, { className: "mr-1.5 size-4" }),
242
178
  "Preview",
243
- D ? /* @__PURE__ */ x(p, { className: "ml-1.5 size-3.5 animate-spin" }) : null
179
+ w ? /* @__PURE__ */ x(p, { className: "ml-1.5 size-3.5 animate-spin" }) : null
244
180
  ]
245
181
  })
246
182
  }) : null;
247
183
  }
248
184
  //#endregion
249
- export { D as default };
185
+ export { w as default };
250
186
 
251
187
  //# sourceMappingURL=AiPreviewPanel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AiPreviewPanel.js","names":[],"sources":["../../../../src/bigconsole/components/preview/AiPreviewPanel.tsx"],"sourcesContent":["/**\n * AI Preview — watch the assistant build.\n *\n * Opens by itself the moment a prompt starts, and shows the DataSink → Dashboard\n * → Parser → Widget chain filling in live: a step rail driven by the agent's own\n * narration, and four tabs listing the real entities as they land.\n *\n * Why polling and not a subscription: the four entity types have no working\n * GraphQL subscriptions (DataSink's are declared in the SDL but have no\n * resolvers; the others have none at all). The same 3.5s poll is already used by\n * DashboardViewPage for exactly this reason. Note the real freshness floor is\n * fe-libs' ~15s shared read cache, not this interval.\n */\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { Badge, Button, Tabs, TabsContent, TabsList, TabsTrigger } from '@burdenoff/fe-libs/ui';\nimport { Loader2, Sparkles, X } from 'lucide-react';\n\nimport { PREVIEW_STEPS, type PreviewStepKey, useAssistantRunStore } from '../../assistant/assistantRunStore';\nimport { useBigConsoleNavigate } from '../../context';\nimport { useDashboardOperations } from '../../hooks/useDashboardOperations';\nimport { useDataSinkOperations } from '../../hooks/useDataSinkOperations';\nimport { useParserOperations } from '../../hooks/useParserOperations';\nimport { useWidgetOperations } from '../../hooks/useWidgetOperations';\nimport { EmptyOrLoading, StepRail, formatAgo, useAssistantSidebarWidth } from './previewShared';\n\n/** Matches DashboardViewPage's live cadence. */\nconst LIVE_POLL_MS = 3500;\n/** Keep polling briefly after the turn ends — the last write can land late. */\nconst TRAILING_POLL_MS = 30_000;\n/** Parsers are listed per console; a console IS a dashboard. */\nconst DEFAULT_CONSOLE_ID = 'default';\n\ninterface RowProps {\n id: string;\n title: string;\n meta?: string;\n isNew: boolean;\n onOpen?: () => void;\n}\n\nfunction EntityRow({ id, title, meta, isNew, onOpen }: RowProps) {\n return (\n <div\n data-testid=\"ai-preview-row\"\n data-entity-id={id}\n className={[\n 'flex items-center justify-between gap-3 rounded-md border px-3 py-2',\n isNew ? 'border-status-success-border bg-status-success-bg/30' : 'border-border-subtle bg-bg-surface',\n ].join(' ')}\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{title}</p>\n {meta ? <p className=\"truncate text-xs text-text-muted\">{meta}</p> : null}\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n {isNew ? (\n <Badge className=\"bg-status-success-bg text-status-success-text\">\n <Sparkles className=\"mr-1 size-3\" />\n just built\n </Badge>\n ) : null}\n {onOpen ? (\n <Button size=\"sm\" variant=\"ghost\" onClick={onOpen}>\n Open\n </Button>\n ) : null}\n </div>\n </div>\n );\n}\n\nexport function AiPreviewPanel() {\n const navigate = useBigConsoleNavigate();\n\n const isRunning = useAssistantRunStore((state) => state.isRunning);\n const previewOpen = useAssistantRunStore((state) => state.previewOpen);\n const hasRun = useAssistantRunStore((state) => state.hasRun);\n const steps = useAssistantRunStore((state) => state.steps);\n const entityIds = useAssistantRunStore((state) => state.entityIds);\n const caption = useAssistantRunStore((state) => state.caption);\n const error = useAssistantRunStore((state) => state.error);\n const updatedAt = useAssistantRunStore((state) => state.updatedAt);\n const openPreview = useAssistantRunStore((state) => state.openPreview);\n const closePreview = useAssistantRunStore((state) => state.closePreview);\n const setRichMounted = useAssistantRunStore((state) => state.setRichMounted);\n\n // Tell the app-shell mini panel to stand down while this full panel is on a\n // /bigconsole page. On unmount (e.g. navigating to /workflows mid-run) this\n // flips false and the mini panel takes over so the build stays visible.\n useEffect(() => {\n setRichMounted(true);\n return () => setRichMounted(false);\n }, [setRichMounted]);\n\n const [tab, setTab] = useState<PreviewStepKey>('datasink');\n const [now, setNow] = useState(() => Date.now());\n\n const dashboardId = entityIds.dashboard;\n\n // The panel does its own fetching, so the tabs are live even before the user\n // opens them — the \"just built\" highlight must be right the instant they look.\n const sinks = useDataSinkOperations({ skipFetch: true });\n const dashboards = useDashboardOperations();\n const parsers = useParserOperations({ consoleId: dashboardId ?? DEFAULT_CONSOLE_ID, skipFetch: true });\n const widgets = useWidgetOperations(undefined, dashboardId, { skipFetch: !dashboardId });\n\n const refetchRef = useRef({\n sinks: sinks.refetch,\n dashboards: dashboards.refetch,\n parsers: parsers.refetch,\n widgets: widgets.refetch,\n });\n refetchRef.current = {\n sinks: sinks.refetch,\n dashboards: dashboards.refetch,\n parsers: parsers.refetch,\n widgets: widgets.refetch,\n };\n\n // Follow the build. Keeps polling for a short while after the turn settles so\n // the final widget does not miss the panel by a second.\n useEffect(() => {\n if (!hasRun) return;\n\n let stopAt = Number.POSITIVE_INFINITY;\n if (!isRunning) stopAt = Date.now() + TRAILING_POLL_MS;\n\n const poll = () => {\n if (typeof document !== 'undefined' && document.visibilityState !== 'visible') return;\n if (Date.now() > stopAt) return;\n void refetchRef.current.sinks();\n void refetchRef.current.dashboards();\n if (dashboardId) {\n void refetchRef.current.parsers();\n void refetchRef.current.widgets();\n }\n setNow(Date.now());\n };\n\n poll();\n const intervalId = window.setInterval(poll, LIVE_POLL_MS);\n const onVisible = () => {\n if (document.visibilityState === 'visible') poll();\n };\n document.addEventListener('visibilitychange', onVisible);\n return () => {\n window.clearInterval(intervalId);\n document.removeEventListener('visibilitychange', onVisible);\n };\n }, [hasRun, isRunning, dashboardId]);\n\n // Jump the user to whatever is being worked on, so they are not left staring\n // at the Data Sinks tab while the parser is the thing actually happening.\n useEffect(() => {\n const active = PREVIEW_STEPS.find((key) => steps[key] === 'running');\n if (active) setTab(active);\n }, [steps]);\n\n const agoSeconds = updatedAt ? Math.max(0, Math.floor((now - updatedAt) / 1000)) : 0;\n\n // Keep clear of the assistant.\n //\n // The assistant sidebar is `position: fixed` — it OVERLAYS the page instead of\n // reflowing it. A panel that spans the full content width therefore runs\n // underneath it, and everything in that overlapped strip (tabs, Open buttons)\n // silently stops taking clicks. Measure the real element rather than hardcoding\n // 560px: the widget is responsive, and it can be closed mid-run.\n const assistantWidth = useAssistantSidebarWidth();\n\n const sinkRows = useMemo(() => sinks.dataSinks.slice(0, 25), [sinks.dataSinks]);\n const dashboardRows = useMemo(() => dashboards.dashboards.slice(0, 25), [dashboards.dashboards]);\n const parserRows = useMemo(() => parsers.parsers.slice(0, 25), [parsers.parsers]);\n const widgetRows = useMemo(() => widgets.widgets.slice(0, 25), [widgets.widgets]);\n\n if (!hasRun) return null;\n\n if (!previewOpen) {\n return (\n <div className=\"flex justify-end px-4 pt-3 md:px-6\" style={{ marginRight: assistantWidth }}>\n <Button size=\"sm\" variant=\"outline\" onClick={openPreview} data-testid=\"ai-preview-open\">\n <Sparkles className=\"mr-1.5 size-4\" />\n Preview\n {isRunning ? <Loader2 className=\"ml-1.5 size-3.5 animate-spin\" /> : null}\n </Button>\n </div>\n );\n }\n\n return (\n <section\n data-testid=\"ai-preview-panel\"\n aria-label=\"AI preview\"\n style={{ marginRight: assistantWidth ? assistantWidth + 16 : undefined }}\n className=\"mx-4 mt-3 overflow-hidden rounded-lg border border-border-default bg-bg-sunken shadow-sm md:mx-6\"\n >\n {/* Header + live strip */}\n <div className=\"flex items-center justify-between gap-3 border-b border-border-subtle bg-bg-surface px-3 py-2\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <Sparkles className=\"size-4 shrink-0 text-action-primary-bg\" />\n <h2 className=\"truncate text-sm font-semibold text-text-primary\">AI Preview</h2>\n {isRunning ? (\n <span\n className=\"inline-flex items-center gap-1.5 rounded-full bg-status-success-bg px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-status-success-text\"\n data-testid=\"ai-preview-live\"\n >\n <span className=\"relative flex size-2\">\n <span className=\"absolute inline-flex size-full animate-ping rounded-full bg-status-success-text opacity-75\" />\n <span className=\"relative inline-flex size-2 rounded-full bg-status-success-text\" />\n </span>\n Live\n </span>\n ) : null}\n <span className=\"truncate text-xs text-text-muted\" data-testid=\"ai-preview-ago\">\n {isRunning ? (caption ? caption : 'Working…') : error ? 'Stopped' : 'Done'} · updated{' '}\n {formatAgo(agoSeconds)}\n </span>\n </div>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={closePreview}\n aria-label=\"Close preview\"\n data-testid=\"ai-preview-close\"\n >\n <X className=\"size-4\" />\n </Button>\n </div>\n\n {/* Step rail */}\n <div className=\"border-b border-border-subtle px-3 py-2\">\n <StepRail steps={steps} />\n </div>\n\n {/* Tabs */}\n <Tabs value={tab} onValueChange={(value) => setTab(value as PreviewStepKey)}>\n <TabsList className=\"mx-3 mt-2\">\n <TabsTrigger value=\"datasink\" data-testid=\"ai-preview-tab-datasink\">\n Data Sinks\n </TabsTrigger>\n <TabsTrigger value=\"dashboard\" data-testid=\"ai-preview-tab-dashboard\">\n Dashboards\n </TabsTrigger>\n <TabsTrigger value=\"parser\" data-testid=\"ai-preview-tab-parser\">\n Parsers\n </TabsTrigger>\n <TabsTrigger value=\"widget\" data-testid=\"ai-preview-tab-widget\">\n Widgets\n </TabsTrigger>\n </TabsList>\n\n <div className=\"max-h-[320px] overflow-y-auto p-3\">\n <TabsContent value=\"datasink\" className=\"space-y-2\">\n {sinkRows.length === 0 ? (\n <EmptyOrLoading loading={sinks.loading} empty=\"No data sinks yet.\" />\n ) : (\n sinkRows.map((sink) => (\n <EntityRow\n key={sink.id}\n id={sink.id}\n title={sink.name ?? sink.key}\n meta={sink.status}\n isNew={sink.id === entityIds.datasink}\n onOpen={() => navigate(`/datasinks/${sink.id}`)}\n />\n ))\n )}\n </TabsContent>\n\n <TabsContent value=\"dashboard\" className=\"space-y-2\">\n {dashboardRows.length === 0 ? (\n <EmptyOrLoading loading={dashboards.loading} empty=\"No dashboards yet.\" />\n ) : (\n dashboardRows.map((dashboard) => (\n <EntityRow\n key={dashboard.id}\n id={dashboard.id}\n title={dashboard.name}\n meta={`${String(dashboard.widgetCount ?? 0)} widget(s)`}\n isNew={dashboard.id === entityIds.dashboard}\n onOpen={() => navigate(`/dashboards/${dashboard.id}`)}\n />\n ))\n )}\n </TabsContent>\n\n <TabsContent value=\"parser\" className=\"space-y-2\">\n {!dashboardId ? (\n <p className=\"px-1 py-6 text-sm text-text-muted\">\n Parsers are listed per dashboard — waiting for the dashboard to be created.\n </p>\n ) : parserRows.length === 0 ? (\n <EmptyOrLoading loading={parsers.loading} empty=\"No parsers on this dashboard yet.\" />\n ) : (\n parserRows.map((parser) => (\n <EntityRow\n key={parser.id}\n id={parser.id}\n title={parser.name}\n meta={`${parser.type} · ${parser.status}`}\n isNew={parser.id === entityIds.parser}\n onOpen={() => navigate(`/parsers/${parser.id}`)}\n />\n ))\n )}\n </TabsContent>\n\n <TabsContent value=\"widget\" className=\"space-y-2\">\n {!dashboardId ? (\n <p className=\"px-1 py-6 text-sm text-text-muted\">\n Widgets live on a dashboard — waiting for the dashboard to be created.\n </p>\n ) : widgetRows.length === 0 ? (\n <EmptyOrLoading loading={widgets.loading} empty=\"No widgets on this dashboard yet.\" />\n ) : (\n widgetRows.map((widget) => (\n <EntityRow\n key={widget.id}\n id={widget.id}\n title={widget.title}\n meta={widget.type}\n // A widget has no link of its own in the narration, so the most\n // recent one on the dashboard being built is the new one.\n isNew={widget.id === widgetRows[widgetRows.length - 1]?.id && steps.widget === 'done'}\n onOpen={() => navigate(`/dashboards/${dashboardId}`)}\n />\n ))\n )}\n </TabsContent>\n </div>\n </Tabs>\n\n {error ? (\n <p\n className=\"border-t border-border-subtle px-3 py-2 text-xs text-status-error-text\"\n data-testid=\"ai-preview-error\"\n >\n {error}\n </p>\n ) : null}\n </section>\n );\n}\n\nexport default AiPreviewPanel;\n"],"mappings":"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"}
1
+ {"version":3,"file":"AiPreviewPanel.js","names":[],"sources":["../../../../src/bigconsole/components/preview/AiPreviewPanel.tsx"],"sourcesContent":["/**\n * AI Preview — watch the assistant build, with the real content inline.\n *\n * Opens by itself the moment a prompt starts. A step rail (driven by the agent's\n * own narration) shows DataSink → Dashboard → Parser → Widget filling in, and\n * the four tabs show the ACTUAL content of each entity as it lands — the data\n * sink's rows, the live dashboard, the parser's transformed output, the rendered\n * widget charts — not a list of links. The tab auto-switches to whatever step is\n * being worked on, and each tab has an \"Open\" button (top-right) that jumps to\n * the real page.\n *\n * The content renderers need Apollo + the BigConsole providers, which is why\n * this full panel lives inside BigConsoleRoot; the store-only app-shell mini\n * panel (AiPreviewMiniPanel) keeps the lighter link view for other pages.\n */\nimport { useEffect, useState } from 'react';\nimport { Button, Tabs, TabsContent, TabsList, TabsTrigger } from '@burdenoff/fe-libs/ui';\nimport { ArrowUpRight, Loader2, Sparkles, X } from 'lucide-react';\n\nimport { PREVIEW_STEPS, type PreviewStepKey, useAssistantRunStore } from '../../assistant/assistantRunStore';\nimport { useBigConsoleNavigate } from '../../context';\nimport { StepRail, formatAgo, useAssistantSidebarWidth } from './previewShared';\nimport { DashboardContent, DataSinkContent, ParserContent, WidgetsContent } from './AiPreviewContent';\n\n/** A tab's top bar: what this tab is, plus a jump-to-the-real-page button. */\nfunction TabHeader({ label, onOpen }: { label: string; onOpen?: () => void }) {\n return (\n <div className=\"mb-2 flex items-center justify-between gap-2\">\n <span className=\"text-xs font-semibold uppercase tracking-wide text-text-muted\">{label}</span>\n {onOpen ? (\n <Button size=\"sm\" variant=\"outline\" onClick={onOpen} data-testid=\"ai-preview-open-entity\">\n Open\n <ArrowUpRight className=\"ml-1 size-3.5\" />\n </Button>\n ) : null}\n </div>\n );\n}\n\nexport function AiPreviewPanel() {\n const navigate = useBigConsoleNavigate();\n\n const isRunning = useAssistantRunStore((state) => state.isRunning);\n const previewOpen = useAssistantRunStore((state) => state.previewOpen);\n const hasRun = useAssistantRunStore((state) => state.hasRun);\n const steps = useAssistantRunStore((state) => state.steps);\n const entityIds = useAssistantRunStore((state) => state.entityIds);\n const caption = useAssistantRunStore((state) => state.caption);\n const error = useAssistantRunStore((state) => state.error);\n const updatedAt = useAssistantRunStore((state) => state.updatedAt);\n const openPreview = useAssistantRunStore((state) => state.openPreview);\n const closePreview = useAssistantRunStore((state) => state.closePreview);\n const setRichMounted = useAssistantRunStore((state) => state.setRichMounted);\n\n // Tell the app-shell mini panel to stand down while this full panel is on a\n // /bigconsole page. On unmount (e.g. navigating to /workflows mid-run) this\n // flips false and the mini panel takes over so the build stays visible.\n useEffect(() => {\n setRichMounted(true);\n return () => setRichMounted(false);\n }, [setRichMounted]);\n\n const [tab, setTab] = useState<PreviewStepKey>('datasink');\n const [now, setNow] = useState(() => Date.now());\n\n const dashboardId = entityIds.dashboard;\n\n // Tick the \"updated Xs ago\" label while a turn is live.\n useEffect(() => {\n if (!isRunning) return;\n const intervalId = window.setInterval(() => setNow(Date.now()), 1000);\n return () => window.clearInterval(intervalId);\n }, [isRunning]);\n\n // Follow the build by showing the LATEST thing that actually landed — so the\n // moment the data sink is created you see its rows, not an empty \"waiting for\n // the dashboard\" tab. (The rail still shows the spinner on the next step.) If\n // nothing has landed yet, sit on whatever is being worked on.\n useEffect(() => {\n const done = PREVIEW_STEPS.filter((key) => steps[key] === 'done');\n const latestDone = done[done.length - 1];\n if (latestDone) {\n setTab(latestDone);\n return;\n }\n const running = PREVIEW_STEPS.find((key) => steps[key] === 'running');\n if (running) setTab(running);\n }, [steps]);\n\n const agoSeconds = updatedAt ? Math.max(0, Math.floor((now - updatedAt) / 1000)) : 0;\n\n // Keep clear of the assistant.\n //\n // The assistant sidebar is `position: fixed` — it OVERLAYS the page instead of\n // reflowing it. A panel that spans the full content width therefore runs\n // underneath it, and everything in that overlapped strip (tabs, Open buttons)\n // silently stops taking clicks. Measure the real element rather than hardcoding\n // 560px: the widget is responsive, and it can be closed mid-run.\n const assistantWidth = useAssistantSidebarWidth();\n\n if (!hasRun) return null;\n\n if (!previewOpen) {\n return (\n <div className=\"flex justify-end px-4 pt-3 md:px-6\" style={{ marginRight: assistantWidth }}>\n <Button size=\"sm\" variant=\"outline\" onClick={openPreview} data-testid=\"ai-preview-open\">\n <Sparkles className=\"mr-1.5 size-4\" />\n Preview\n {isRunning ? <Loader2 className=\"ml-1.5 size-3.5 animate-spin\" /> : null}\n </Button>\n </div>\n );\n }\n\n return (\n <section\n data-testid=\"ai-preview-panel\"\n aria-label=\"AI preview\"\n style={{ marginRight: assistantWidth ? assistantWidth + 16 : undefined }}\n className=\"mx-4 mt-3 overflow-hidden rounded-lg border border-border-default bg-bg-sunken shadow-sm md:mx-6\"\n >\n {/* Header + live strip */}\n <div className=\"flex items-center justify-between gap-3 border-b border-border-subtle bg-bg-surface px-3 py-2\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <Sparkles className=\"size-4 shrink-0 text-action-primary-bg\" />\n <h2 className=\"truncate text-sm font-semibold text-text-primary\">AI Preview</h2>\n {isRunning ? (\n <span\n className=\"inline-flex items-center gap-1.5 rounded-full bg-status-success-bg px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-status-success-text\"\n data-testid=\"ai-preview-live\"\n >\n <span className=\"relative flex size-2\">\n <span className=\"absolute inline-flex size-full animate-ping rounded-full bg-status-success-text opacity-75\" />\n <span className=\"relative inline-flex size-2 rounded-full bg-status-success-text\" />\n </span>\n Live\n </span>\n ) : null}\n <span className=\"truncate text-xs text-text-muted\" data-testid=\"ai-preview-ago\">\n {isRunning ? (caption ? caption : 'Working…') : error ? 'Stopped' : 'Done'} · updated{' '}\n {formatAgo(agoSeconds)}\n </span>\n </div>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={closePreview}\n aria-label=\"Close preview\"\n data-testid=\"ai-preview-close\"\n >\n <X className=\"size-4\" />\n </Button>\n </div>\n\n {/* Step rail */}\n <div className=\"border-b border-border-subtle px-3 py-2\">\n <StepRail steps={steps} />\n </div>\n\n {/* Tabs — each shows the real content of the built entity */}\n <Tabs value={tab} onValueChange={(value) => setTab(value as PreviewStepKey)}>\n <TabsList className=\"mx-3 mt-2\">\n <TabsTrigger value=\"datasink\" data-testid=\"ai-preview-tab-datasink\">\n Data Sink\n </TabsTrigger>\n <TabsTrigger value=\"dashboard\" data-testid=\"ai-preview-tab-dashboard\">\n Dashboard\n </TabsTrigger>\n <TabsTrigger value=\"parser\" data-testid=\"ai-preview-tab-parser\">\n Parser\n </TabsTrigger>\n <TabsTrigger value=\"widget\" data-testid=\"ai-preview-tab-widget\">\n Widget\n </TabsTrigger>\n </TabsList>\n\n <div className=\"max-h-[440px] overflow-y-auto p-3\">\n <TabsContent value=\"datasink\">\n <TabHeader\n label=\"Data Sink\"\n onOpen={entityIds.datasink ? () => navigate(`/datasinks/${entityIds.datasink}`) : undefined}\n />\n <DataSinkContent id={entityIds.datasink} live={isRunning} />\n </TabsContent>\n\n <TabsContent value=\"dashboard\">\n <TabHeader\n label=\"Dashboard\"\n onOpen={entityIds.dashboard ? () => navigate(`/dashboards/${entityIds.dashboard}`) : undefined}\n />\n <DashboardContent dashboardId={entityIds.dashboard} />\n </TabsContent>\n\n <TabsContent value=\"parser\">\n <TabHeader\n label=\"Parser\"\n onOpen={entityIds.parser ? () => navigate(`/parsers/${entityIds.parser}`) : undefined}\n />\n <ParserContent parserId={entityIds.parser} live={isRunning} />\n </TabsContent>\n\n <TabsContent value=\"widget\">\n <TabHeader label=\"Widget\" onOpen={dashboardId ? () => navigate(`/dashboards/${dashboardId}`) : undefined} />\n <WidgetsContent dashboardId={dashboardId} live={isRunning} />\n </TabsContent>\n </div>\n </Tabs>\n\n {error ? (\n <p\n className=\"border-t border-border-subtle px-3 py-2 text-xs text-status-error-text\"\n data-testid=\"ai-preview-error\"\n >\n {error}\n </p>\n ) : null}\n </section>\n );\n}\n\nexport default AiPreviewPanel;\n"],"mappings":"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"}
@@ -1,47 +1,37 @@
1
1
  import { PREVIEW_STEPS as e, PREVIEW_STEP_LABEL as t } from "../../assistant/assistantRunStore.js";
2
2
  import { useEffect as n, useState as r } from "react";
3
3
  import { Check as i, ChevronRight as a, Loader2 as o, Minus as s } from "lucide-react";
4
- import { Spinner as c } from "@burdenoff/fe-libs/ui";
5
- import { jsx as l, jsxs as u } from "react/jsx-runtime";
4
+ import { jsx as c, jsxs as l } from "react/jsx-runtime";
6
5
  //#region src/bigconsole/components/preview/previewShared.tsx
7
- function d({ status: e }) {
8
- return e === "done" ? /* @__PURE__ */ l(i, { className: "size-3.5 text-status-success-text" }) : e === "running" ? /* @__PURE__ */ l(o, { className: "size-3.5 animate-spin text-action-primary-bg" }) : e === "skipped" ? /* @__PURE__ */ l(s, { className: "size-3.5 text-text-muted" }) : /* @__PURE__ */ l("span", { className: "size-1.5 rounded-full bg-border-strong" });
6
+ function u({ status: e }) {
7
+ return e === "done" ? /* @__PURE__ */ c(i, { className: "size-3.5 text-status-success-text" }) : e === "running" ? /* @__PURE__ */ c(o, { className: "size-3.5 animate-spin text-action-primary-bg" }) : e === "skipped" ? /* @__PURE__ */ c(s, { className: "size-3.5 text-text-muted" }) : /* @__PURE__ */ c("span", { className: "size-1.5 rounded-full bg-border-strong" });
9
8
  }
10
- function f({ steps: n }) {
11
- return /* @__PURE__ */ l("div", {
9
+ function d({ steps: n }) {
10
+ return /* @__PURE__ */ c("div", {
12
11
  className: "flex flex-wrap items-center gap-1",
13
12
  "data-testid": "ai-preview-step-rail",
14
- children: e.map((r, i) => /* @__PURE__ */ u("div", {
13
+ children: e.map((r, i) => /* @__PURE__ */ l("div", {
15
14
  className: "flex items-center gap-1",
16
- children: [/* @__PURE__ */ u("span", {
15
+ children: [/* @__PURE__ */ l("span", {
17
16
  "data-testid": `ai-preview-step-${r}`,
18
17
  "data-status": n[r],
19
18
  className: ["inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-medium", n[r] === "done" ? "border-status-success-border bg-status-success-bg text-status-success-text" : n[r] === "running" ? "border-border-strong bg-bg-surface text-text-primary" : "border-border-default text-text-muted"].join(" "),
20
19
  children: [
21
- /* @__PURE__ */ l(d, { status: n[r] }),
20
+ /* @__PURE__ */ c(u, { status: n[r] }),
22
21
  t[r],
23
- n[r] === "skipped" ? /* @__PURE__ */ l("span", {
22
+ n[r] === "skipped" ? /* @__PURE__ */ c("span", {
24
23
  className: "text-[10px]",
25
24
  children: "skipped"
26
25
  }) : null
27
26
  ]
28
- }), i < e.length - 1 ? /* @__PURE__ */ l(a, { className: "size-3.5 text-text-muted" }) : null]
27
+ }), i < e.length - 1 ? /* @__PURE__ */ c(a, { className: "size-3.5 text-text-muted" }) : null]
29
28
  }, r))
30
29
  });
31
30
  }
32
- function p(e) {
31
+ function f(e) {
33
32
  return e < 5 ? "just now" : e < 60 ? `${String(e)}s ago` : `${String(Math.floor(e / 60))}m ago`;
34
33
  }
35
- function m({ loading: e, empty: t }) {
36
- return e ? /* @__PURE__ */ u("div", {
37
- className: "flex items-center gap-2 px-1 py-6 text-sm text-text-muted",
38
- children: [/* @__PURE__ */ l(c, { className: "size-4" }), "Loading…"]
39
- }) : /* @__PURE__ */ l("p", {
40
- className: "px-1 py-6 text-sm text-text-muted",
41
- children: t
42
- });
43
- }
44
- function h() {
34
+ function p() {
45
35
  let [e, t] = r(0);
46
36
  return n(() => {
47
37
  let e = () => {
@@ -56,6 +46,6 @@ function h() {
56
46
  }, []), e;
57
47
  }
58
48
  //#endregion
59
- export { m as EmptyOrLoading, f as StepRail, p as formatAgo, h as useAssistantSidebarWidth };
49
+ export { d as StepRail, f as formatAgo, p as useAssistantSidebarWidth };
60
50
 
61
51
  //# sourceMappingURL=previewShared.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"previewShared.js","names":[],"sources":["../../../../src/bigconsole/components/preview/previewShared.tsx"],"sourcesContent":["/**\n * Presentational bits shared by the two AI-preview surfaces:\n * - AiPreviewPanel — the full panel with live entity tabs, mounted inside\n * BigConsoleRoot (needs the BigConsole Apollo context).\n * - AiPreviewMiniPanel — the lightweight, store-only panel mounted by the app\n * shell so the build stays visible on ANY page.\n *\n * Nothing here touches Apollo, React Query, or BigConsole context — it is pure\n * store-shape + DOM, which is exactly why the mini panel can live in the app\n * shell without dragging the data layer along.\n */\nimport { useEffect, useState } from 'react';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { Check, ChevronRight, Loader2, Minus } from 'lucide-react';\n\nimport {\n PREVIEW_STEP_LABEL,\n PREVIEW_STEPS,\n type PreviewStepKey,\n type PreviewStepStatus,\n} from '../../assistant/assistantRunStore';\n\nexport function StepPip({ status }: { status: PreviewStepStatus }) {\n if (status === 'done') return <Check className=\"size-3.5 text-status-success-text\" />;\n if (status === 'running') return <Loader2 className=\"size-3.5 animate-spin text-action-primary-bg\" />;\n if (status === 'skipped') return <Minus className=\"size-3.5 text-text-muted\" />;\n return <span className=\"size-1.5 rounded-full bg-border-strong\" />;\n}\n\nexport function StepRail({ steps }: { steps: Record<PreviewStepKey, PreviewStepStatus> }) {\n return (\n <div className=\"flex flex-wrap items-center gap-1\" data-testid=\"ai-preview-step-rail\">\n {PREVIEW_STEPS.map((key, index) => (\n <div key={key} className=\"flex items-center gap-1\">\n <span\n data-testid={`ai-preview-step-${key}`}\n data-status={steps[key]}\n className={[\n 'inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-medium',\n steps[key] === 'done'\n ? 'border-status-success-border bg-status-success-bg text-status-success-text'\n : steps[key] === 'running'\n ? 'border-border-strong bg-bg-surface text-text-primary'\n : 'border-border-default text-text-muted',\n ].join(' ')}\n >\n <StepPip status={steps[key]} />\n {PREVIEW_STEP_LABEL[key]}\n {steps[key] === 'skipped' ? <span className=\"text-[10px]\">skipped</span> : null}\n </span>\n {index < PREVIEW_STEPS.length - 1 ? <ChevronRight className=\"size-3.5 text-text-muted\" /> : null}\n </div>\n ))}\n </div>\n );\n}\n\nexport function formatAgo(seconds: number): string {\n if (seconds < 5) return 'just now';\n if (seconds < 60) return `${String(seconds)}s ago`;\n return `${String(Math.floor(seconds / 60))}m ago`;\n}\n\nexport function EmptyOrLoading({ loading, empty }: { loading: boolean; empty: string }) {\n if (loading) {\n return (\n <div className=\"flex items-center gap-2 px-1 py-6 text-sm text-text-muted\">\n <Spinner className=\"size-4\" />\n Loading…\n </div>\n );\n }\n return <p className=\"px-1 py-6 text-sm text-text-muted\">{empty}</p>;\n}\n\n/**\n * Width of the assistant sidebar, or 0 when it is closed.\n *\n * Polled rather than subscribed: the widget lives in fe-libs, is mounted by the\n * app shell, and emits nothing when it opens or closes. Reading the DOM is the\n * only honest signal available, and it is cheap.\n */\nexport function useAssistantSidebarWidth(): number {\n const [width, setWidth] = useState(0);\n\n useEffect(() => {\n const measure = () => {\n const el = document.querySelector('[data-testid=\"assistant-widget-window\"][data-layout=\"sidebar\"]');\n const rect = el?.getBoundingClientRect();\n // Below `sm` the sidebar is full-width; reserving that would leave no room\n // for the preview at all, so on small screens let it stack underneath.\n const next = rect && rect.width < window.innerWidth * 0.75 ? Math.round(rect.width) : 0;\n setWidth((current) => (current === next ? current : next));\n };\n measure();\n const intervalId = window.setInterval(measure, 500);\n window.addEventListener('resize', measure);\n return () => {\n window.clearInterval(intervalId);\n window.removeEventListener('resize', measure);\n };\n }, []);\n\n return width;\n}\n"],"mappings":";;;;;;AAsBA,SAAgB,EAAQ,EAAE,aAAyC;AAIjE,QAHI,MAAW,SAAe,kBAAC,GAAD,EAAO,WAAU,qCAAsC,CAAA,GACjF,MAAW,YAAkB,kBAAC,GAAD,EAAS,WAAU,gDAAiD,CAAA,GACjG,MAAW,YAAkB,kBAAC,GAAD,EAAO,WAAU,4BAA6B,CAAA,GACxE,kBAAC,QAAD,EAAM,WAAU,0CAA2C,CAAA;;AAGpE,SAAgB,EAAS,EAAE,YAA+D;AACxF,QACE,kBAAC,OAAD;EAAK,WAAU;EAAoC,eAAY;YAC5D,EAAc,KAAK,GAAK,MACvB,kBAAC,OAAD;GAAe,WAAU;aAAzB,CACE,kBAAC,QAAD;IACE,eAAa,mBAAmB;IAChC,eAAa,EAAM;IACnB,WAAW,CACT,wFACA,EAAM,OAAS,SACX,+EACA,EAAM,OAAS,YACb,yDACA,wCACP,CAAC,KAAK,IAAI;cAVb;KAYE,kBAAC,GAAD,EAAS,QAAQ,EAAM,IAAQ,CAAA;KAC9B,EAAmB;KACnB,EAAM,OAAS,YAAY,kBAAC,QAAD;MAAM,WAAU;gBAAc;MAAc,CAAA,GAAG;KACtE;OACN,IAAQ,EAAc,SAAS,IAAI,kBAAC,GAAD,EAAc,WAAU,4BAA6B,CAAA,GAAG,KACxF;KAlBI,EAkBJ,CACN;EACE,CAAA;;AAIV,SAAgB,EAAU,GAAyB;AAGjD,QAFI,IAAU,IAAU,aACpB,IAAU,KAAW,GAAG,OAAO,EAAQ,CAAC,SACrC,GAAG,OAAO,KAAK,MAAM,IAAU,GAAG,CAAC,CAAC;;AAG7C,SAAgB,EAAe,EAAE,YAAS,YAA8C;AAStF,QARI,IAEA,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD,EAAS,WAAU,UAAW,CAAA,EAAA,WAE1B;MAGH,kBAAC,KAAD;EAAG,WAAU;YAAqC;EAAU,CAAA;;AAUrE,SAAgB,IAAmC;CACjD,IAAM,CAAC,GAAO,KAAY,EAAS,EAAE;AAoBrC,QAlBA,QAAgB;EACd,IAAM,UAAgB;GAEpB,IAAM,IADK,SAAS,cAAc,qEAAiE,EAClF,uBAAuB,EAGlC,IAAO,KAAQ,EAAK,QAAQ,OAAO,aAAa,MAAO,KAAK,MAAM,EAAK,MAAM,GAAG;AACtF,MAAU,MAAa,MAAY,IAAO,IAAU,EAAM;;AAE5D,KAAS;EACT,IAAM,IAAa,OAAO,YAAY,GAAS,IAAI;AAEnD,SADA,OAAO,iBAAiB,UAAU,EAAQ,QAC7B;AAEX,GADA,OAAO,cAAc,EAAW,EAChC,OAAO,oBAAoB,UAAU,EAAQ;;IAE9C,EAAE,CAAC,EAEC"}
1
+ {"version":3,"file":"previewShared.js","names":[],"sources":["../../../../src/bigconsole/components/preview/previewShared.tsx"],"sourcesContent":["/**\n * Presentational bits shared by the two AI-preview surfaces:\n * - AiPreviewPanel — the full panel with live entity tabs, mounted inside\n * BigConsoleRoot (needs the BigConsole Apollo context).\n * - AiPreviewMiniPanel — the lightweight, store-only panel mounted by the app\n * shell so the build stays visible on ANY page.\n *\n * Nothing here touches Apollo, React Query, or BigConsole context — it is pure\n * store-shape + DOM, which is exactly why the mini panel can live in the app\n * shell without dragging the data layer along.\n */\nimport { useEffect, useState } from 'react';\nimport { Check, ChevronRight, Loader2, Minus } from 'lucide-react';\n\nimport {\n PREVIEW_STEP_LABEL,\n PREVIEW_STEPS,\n type PreviewStepKey,\n type PreviewStepStatus,\n} from '../../assistant/assistantRunStore';\n\nexport function StepPip({ status }: { status: PreviewStepStatus }) {\n if (status === 'done') return <Check className=\"size-3.5 text-status-success-text\" />;\n if (status === 'running') return <Loader2 className=\"size-3.5 animate-spin text-action-primary-bg\" />;\n if (status === 'skipped') return <Minus className=\"size-3.5 text-text-muted\" />;\n return <span className=\"size-1.5 rounded-full bg-border-strong\" />;\n}\n\nexport function StepRail({ steps }: { steps: Record<PreviewStepKey, PreviewStepStatus> }) {\n return (\n <div className=\"flex flex-wrap items-center gap-1\" data-testid=\"ai-preview-step-rail\">\n {PREVIEW_STEPS.map((key, index) => (\n <div key={key} className=\"flex items-center gap-1\">\n <span\n data-testid={`ai-preview-step-${key}`}\n data-status={steps[key]}\n className={[\n 'inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-medium',\n steps[key] === 'done'\n ? 'border-status-success-border bg-status-success-bg text-status-success-text'\n : steps[key] === 'running'\n ? 'border-border-strong bg-bg-surface text-text-primary'\n : 'border-border-default text-text-muted',\n ].join(' ')}\n >\n <StepPip status={steps[key]} />\n {PREVIEW_STEP_LABEL[key]}\n {steps[key] === 'skipped' ? <span className=\"text-[10px]\">skipped</span> : null}\n </span>\n {index < PREVIEW_STEPS.length - 1 ? <ChevronRight className=\"size-3.5 text-text-muted\" /> : null}\n </div>\n ))}\n </div>\n );\n}\n\nexport function formatAgo(seconds: number): string {\n if (seconds < 5) return 'just now';\n if (seconds < 60) return `${String(seconds)}s ago`;\n return `${String(Math.floor(seconds / 60))}m ago`;\n}\n\n/**\n * Width of the assistant sidebar, or 0 when it is closed.\n *\n * Polled rather than subscribed: the widget lives in fe-libs, is mounted by the\n * app shell, and emits nothing when it opens or closes. Reading the DOM is the\n * only honest signal available, and it is cheap.\n */\nexport function useAssistantSidebarWidth(): number {\n const [width, setWidth] = useState(0);\n\n useEffect(() => {\n const measure = () => {\n const el = document.querySelector('[data-testid=\"assistant-widget-window\"][data-layout=\"sidebar\"]');\n const rect = el?.getBoundingClientRect();\n // Below `sm` the sidebar is full-width; reserving that would leave no room\n // for the preview at all, so on small screens let it stack underneath.\n const next = rect && rect.width < window.innerWidth * 0.75 ? Math.round(rect.width) : 0;\n setWidth((current) => (current === next ? current : next));\n };\n measure();\n const intervalId = window.setInterval(measure, 500);\n window.addEventListener('resize', measure);\n return () => {\n window.clearInterval(intervalId);\n window.removeEventListener('resize', measure);\n };\n }, []);\n\n return width;\n}\n"],"mappings":";;;;;AAqBA,SAAgB,EAAQ,EAAE,aAAyC;AAIjE,QAHI,MAAW,SAAe,kBAAC,GAAD,EAAO,WAAU,qCAAsC,CAAA,GACjF,MAAW,YAAkB,kBAAC,GAAD,EAAS,WAAU,gDAAiD,CAAA,GACjG,MAAW,YAAkB,kBAAC,GAAD,EAAO,WAAU,4BAA6B,CAAA,GACxE,kBAAC,QAAD,EAAM,WAAU,0CAA2C,CAAA;;AAGpE,SAAgB,EAAS,EAAE,YAA+D;AACxF,QACE,kBAAC,OAAD;EAAK,WAAU;EAAoC,eAAY;YAC5D,EAAc,KAAK,GAAK,MACvB,kBAAC,OAAD;GAAe,WAAU;aAAzB,CACE,kBAAC,QAAD;IACE,eAAa,mBAAmB;IAChC,eAAa,EAAM;IACnB,WAAW,CACT,wFACA,EAAM,OAAS,SACX,+EACA,EAAM,OAAS,YACb,yDACA,wCACP,CAAC,KAAK,IAAI;cAVb;KAYE,kBAAC,GAAD,EAAS,QAAQ,EAAM,IAAQ,CAAA;KAC9B,EAAmB;KACnB,EAAM,OAAS,YAAY,kBAAC,QAAD;MAAM,WAAU;gBAAc;MAAc,CAAA,GAAG;KACtE;OACN,IAAQ,EAAc,SAAS,IAAI,kBAAC,GAAD,EAAc,WAAU,4BAA6B,CAAA,GAAG,KACxF;KAlBI,EAkBJ,CACN;EACE,CAAA;;AAIV,SAAgB,EAAU,GAAyB;AAGjD,QAFI,IAAU,IAAU,aACpB,IAAU,KAAW,GAAG,OAAO,EAAQ,CAAC,SACrC,GAAG,OAAO,KAAK,MAAM,IAAU,GAAG,CAAC,CAAC;;AAU7C,SAAgB,IAAmC;CACjD,IAAM,CAAC,GAAO,KAAY,EAAS,EAAE;AAoBrC,QAlBA,QAAgB;EACd,IAAM,UAAgB;GAEpB,IAAM,IADK,SAAS,cAAc,qEAAiE,EAClF,uBAAuB,EAGlC,IAAO,KAAQ,EAAK,QAAQ,OAAO,aAAa,MAAO,KAAK,MAAM,EAAK,MAAM,GAAG;AACtF,MAAU,MAAa,MAAY,IAAO,IAAU,EAAM;;AAE5D,KAAS;EACT,IAAM,IAAa,OAAO,YAAY,GAAS,IAAI;AAEnD,SADA,OAAO,iBAAiB,UAAU,EAAQ,QAC7B;AAEX,GADA,OAAO,cAAc,EAAW,EAChC,OAAO,oBAAoB,UAAU,EAAQ;;IAE9C,EAAE,CAAC,EAEC"}
@@ -1,11 +1,11 @@
1
1
  import { useWidgetStore as e } from "../../store/widgetStore.js";
2
2
  import "../../store/index.js";
3
- import t from "../../hooks/useDataSinkOperations.js";
4
- import n from "../../hooks/useParserOperations.js";
5
- import r from "../../hooks/useWidgetOperations.js";
6
- import { getWidgetDefinition as i } from "./WidgetRegistry.js";
7
- import a from "../../hooks/useDrilldownOperations.js";
8
- import o from "../../hooks/useWidgetActions.js";
3
+ import { getWidgetDefinition as t } from "./WidgetRegistry.js";
4
+ import n from "../../hooks/useWidgetOperations.js";
5
+ import r from "../../hooks/useDrilldownOperations.js";
6
+ import i from "../../hooks/useWidgetActions.js";
7
+ import a from "../../hooks/useDataSinkOperations.js";
8
+ import o from "../../hooks/useParserOperations.js";
9
9
  import "../../hooks/index.js";
10
10
  import { getWidgetBinding as s, getWidgetBindingStatus as c, serializeWidgetBinding as ee } from "../../utils/widgetBinding.js";
11
11
  import te from "./ActionConfigDialog.js";
@@ -339,14 +339,14 @@ var je = l(function({ isOpen: l, onClose: m }) {
339
339
  z,
340
340
  F
341
341
  ]);
342
- let { updateWidget: Pe } = r(z?.pageId, z?.dashboardId, { skipFetch: !0 }), { createDrilldown: Fe, updateDrilldown: Ie, deleteDrilldown: Le } = a(), { dataSinks: V } = t({ skipFetch: !1 }), [Re, H] = f(null), { executeParser: ze } = n({
342
+ let { updateWidget: Pe } = n(z?.pageId, z?.dashboardId, { skipFetch: !0 }), { createDrilldown: Fe, updateDrilldown: Ie, deleteDrilldown: Le } = r(), { dataSinks: V } = a({ skipFetch: !1 }), [Re, H] = f(null), { executeParser: ze } = o({
343
343
  consoleId: z?.dashboardId,
344
344
  skipFetch: !0
345
345
  }), [Be, U] = f(!1), [Ve, W] = f(void 0), [He, G] = f(!1), [Ue, K] = f(null);
346
346
  d(() => {
347
347
  W(void 0), G(!1), K(null), U(!1);
348
348
  }, [L]);
349
- let { actions: q, loading: J, createAction: We, updateAction: Ge, deleteAction: Ke, reorderActions: Y } = o(L || void 0), [qe, X] = f(!1), [Je, Z] = f(void 0), [Ye, Xe] = f(!1), Ze = u(() => {
349
+ let { actions: q, loading: J, createAction: We, updateAction: Ge, deleteAction: Ke, reorderActions: Y } = i(L || void 0), [qe, X] = f(!1), [Je, Z] = f(void 0), [Ye, Xe] = f(!1), Ze = u(() => {
350
350
  Z(void 0), X(!0);
351
351
  }, []), Qe = u((e) => {
352
352
  Z(e), X(!0);
@@ -570,7 +570,7 @@ var je = l(function({ isOpen: l, onClose: m }) {
570
570
  ...t,
571
571
  drilldownConfig: e
572
572
  }), I(!0);
573
- }, []), lt = z ? i(z.type) : null;
573
+ }, []), lt = z ? t(z.type) : null;
574
574
  return /* @__PURE__ */ k(_e, {
575
575
  open: l,
576
576
  onOpenChange: (e) => !e && m(),
@@ -1,15 +1,13 @@
1
1
  import "./useCrossFilter.js";
2
2
  import "./useWorkflowOperations.js";
3
- import "./useDashboardOperations.js";
4
- import "./useDataSinkOperations.js";
5
- import "./useParserOperations.js";
6
- import "./useBigConsoleAnalytics.js";
7
- import "./useWidgetOperations.js";
8
3
  import "./useWidgetData.js";
9
4
  import "./useWidgetCatalog.js";
10
5
  import "./useAutoRefresh.js";
6
+ import "./useBigConsoleAnalytics.js";
7
+ import "./useWidgetOperations.js";
11
8
  import "./useWidgetComments.js";
12
9
  import "./useWidgetConfigPersist.js";
10
+ import "./useDashboardOperations.js";
13
11
  import "./useWidgetExport.js";
14
12
  import "./useDashboardImageExport.js";
15
13
  import "./useDrilldown.js";
@@ -20,6 +18,8 @@ import "./useExportDashboard.js";
20
18
  import "./useImportDashboard.js";
21
19
  import "./useEventWebhooks.js";
22
20
  import "./useEventLogs.js";
21
+ import "./useDataSinkOperations.js";
22
+ import "./useParserOperations.js";
23
23
  import "./usePipelineOperations.js";
24
24
  import "./useGlobalFilters.js";
25
25
  import "./useDashboardSharing.js";