@burdenoff/microfe-bigconsole 2026.924.1 → 2026.924.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.
@@ -0,0 +1,25 @@
1
+ import { useEffect as e, useState as t } from "react";
2
+ //#region src/bigconsole/assistant/useAssistantPanelShowsPreview.ts
3
+ var n = "[data-assistant-panel]", r = "data-assistant-overlay", i = "preview";
4
+ function a(e = document) {
5
+ return e.querySelector(n)?.getAttribute(r) === i;
6
+ }
7
+ function o() {
8
+ let [n, i] = t(() => typeof document > "u" ? !1 : a());
9
+ return e(() => {
10
+ if (typeof document > "u") return;
11
+ let e = () => i(a());
12
+ e();
13
+ let t = new MutationObserver(e);
14
+ return t.observe(document.body, {
15
+ subtree: !0,
16
+ childList: !0,
17
+ attributes: !0,
18
+ attributeFilter: [r]
19
+ }), () => t.disconnect();
20
+ }, []), n;
21
+ }
22
+ //#endregion
23
+ export { o as useAssistantPanelShowsPreview };
24
+
25
+ //# sourceMappingURL=useAssistantPanelShowsPreview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAssistantPanelShowsPreview.js","names":[],"sources":["../../../src/bigconsole/assistant/useAssistantPanelShowsPreview.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\n\n/**\n * Is the shared assistant panel currently showing the preview ITSELF?\n *\n * ★★★ WHY THIS EXISTS. The preview has three surfaces — the rich page panel,\n * the mini page panel, and fe-libs' in-chat overlay — and only one may show per\n * run. BigConsole already arbitrates its own two (`richMounted`), but nothing\n * arbitrated against the third: fe-libs renders the SAME controller via\n * `controller.render()` inside its overlay, driven by its own local state, so\n * opening the overlay left the page surface up as well.\n *\n * Measured on app.bigconsole.com 2026-09-24, one Build run, one click of the\n * panel's \"Build preview\" toggle:\n * before -> page surface [283,145,1109,270], no overlay\n * after -> page surface STILL [283,145,1109,270]\n * plus the same preview again at [897,166,518,160] inside the\n * overlay, inside the assistant panel\n * Two live surfaces for one run, side by side, showing the same build.\n *\n * ★★ IT READS THE ATTRIBUTE fe-libs ALREADY PUBLISHES, rather than adding a\n * contract. `AIAssistantPanel` writes `data-assistant-overlay=\"preview\"` on its\n * root for exactly this kind of cross-package coordination — the same mechanism\n * `assistantPanelOwnsEscape` reads to decide who owns the Escape key. Verified\n * live before building on it: the attribute is absent before the toggle and\n * `\"preview\"` after.\n *\n * ★ A MutationObserver, not a poll or a render-time read. The attribute changes\n * from inside fe-libs' own state, on a node this package does not own and does\n * not re-render with, so there is no React signal to hang it on.\n */\nconst PANEL_SELECTOR = '[data-assistant-panel]';\nconst OVERLAY_ATTR = 'data-assistant-overlay';\nconst PREVIEW = 'preview';\n\n/** Reads the current answer from the DOM. Safe to call before any panel mounts. */\nexport function panelShowsPreviewNow(doc: Pick<Document, 'querySelector'> = document): boolean {\n return doc.querySelector(PANEL_SELECTOR)?.getAttribute(OVERLAY_ATTR) === PREVIEW;\n}\n\nexport function useAssistantPanelShowsPreview(): boolean {\n const [showing, setShowing] = useState(() => (typeof document === 'undefined' ? false : panelShowsPreviewNow()));\n\n useEffect(() => {\n if (typeof document === 'undefined') return;\n const read = () => setShowing(panelShowsPreviewNow());\n read();\n // ★ `subtree` is required: the panel mounts and unmounts below <body>, so an\n // observer bound to attributes alone would never see a panel that appears\n // after this hook runs — which is the common case, since the page surface\n // exists before the user opens the assistant.\n const observer = new MutationObserver(read);\n observer.observe(document.body, {\n subtree: true,\n childList: true,\n attributes: true,\n attributeFilter: [OVERLAY_ATTR],\n });\n return () => observer.disconnect();\n }, []);\n\n return showing;\n}\n"],"mappings":";;AA+BA,IAAM,IAAiB,0BACjB,IAAe,0BACf,IAAU;AAGhB,SAAgB,EAAqB,IAAuC,UAAmB;AAC7F,QAAO,EAAI,cAAc,EAAe,EAAE,aAAa,EAAa,KAAK;;AAG3E,SAAgB,IAAyC;CACvD,IAAM,CAAC,GAAS,KAAc,QAAgB,OAAO,WAAa,MAAc,KAAQ,GAAsB,CAAE;AAoBhH,QAlBA,QAAgB;AACd,MAAI,OAAO,WAAa,IAAa;EACrC,IAAM,UAAa,EAAW,GAAsB,CAAC;AACrD,KAAM;EAKN,IAAM,IAAW,IAAI,iBAAiB,EAAK;AAO3C,SANA,EAAS,QAAQ,SAAS,MAAM;GAC9B,SAAS;GACT,WAAW;GACX,YAAY;GACZ,iBAAiB,CAAC,EAAa;GAChC,CAAC,QACW,EAAS,YAAY;IACjC,EAAE,CAAC,EAEC"}
@@ -1,130 +1,131 @@
1
1
  import { Section as e } from "../common/Section.js";
2
2
  import "../common/index.js";
3
- import { PREVIEW_STEPS as t, useAssistantRunStore as n } from "../../assistant/assistantRunStore.js";
4
- import { DOCK_CLEARANCE as r, PreviewMeta as i, STEP_LABEL as a, formatAgo as o, previewLabels as s } from "./previewShared.js";
5
- import { ArrowUpRight as c, Braces as l, Database as u, LayoutDashboard as d, Loader2 as f, Sparkles as p, X as m } from "lucide-react";
6
- import { useI18n as h } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
7
- import { Button as g } from "@burdenoff/fe-libs/ui";
8
- import { jsx as _, jsxs as v } from "react/jsx-runtime";
9
- import { AssistantEntityPreview as y } from "@burdenoff/fe-libs/shared/assistant";
3
+ import { useAssistantPanelShowsPreview as t } from "../../assistant/useAssistantPanelShowsPreview.js";
4
+ import { PREVIEW_STEPS as n, useAssistantRunStore as r } from "../../assistant/assistantRunStore.js";
5
+ import { DOCK_CLEARANCE as i, PreviewMeta as a, STEP_LABEL as o, formatAgo as s, previewLabels as c } from "./previewShared.js";
6
+ import { ArrowUpRight as l, Braces as u, Database as d, LayoutDashboard as f, Loader2 as p, Sparkles as m, X as h } from "lucide-react";
7
+ import { useI18n as g } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
8
+ import { Button as _ } from "@burdenoff/fe-libs/ui";
9
+ import { jsx as v, jsxs as y } from "react/jsx-runtime";
10
+ import { AssistantEntityPreview as b } from "@burdenoff/fe-libs/shared/assistant";
10
11
  //#region src/bigconsole/components/preview/AiPreviewMiniPanel.tsx
11
- function b({ onNavigate: b }) {
12
- let { t: x } = h(), S = n((e) => e.isRunning), C = n((e) => e.previewOpen), w = n((e) => e.hasRun), T = n((e) => e.richMounted), E = n((e) => e.steps), D = n((e) => e.entityIds), O = n((e) => e.caption), k = n((e) => e.error), A = n((e) => e.updatedAt), j = n((e) => e.openPreview), M = n((e) => e.closePreview), N = (e) => {
13
- b ? b(e) : typeof window < "u" && window.location.assign(e);
12
+ function x({ onNavigate: x }) {
13
+ let { t: S } = g(), C = t(), w = r((e) => e.isRunning), T = r((e) => e.previewOpen), E = r((e) => e.hasRun), D = r((e) => e.richMounted), O = r((e) => e.steps), k = r((e) => e.entityIds), A = r((e) => e.caption), j = r((e) => e.error), M = r((e) => e.updatedAt), N = r((e) => e.openPreview), P = r((e) => e.closePreview), F = (e) => {
14
+ x ? x(e) : typeof window < "u" && window.location.assign(e);
14
15
  };
15
- if (!w || T) return null;
16
- if (!C) return /* @__PURE__ */ v("div", {
16
+ if (!E || D || C) return null;
17
+ if (!T) return /* @__PURE__ */ y("div", {
17
18
  className: "flex flex-wrap items-center justify-end gap-control-gap px-4 pt-3 md:px-6",
18
- style: { marginRight: r },
19
- children: [/* @__PURE__ */ _("span", {
19
+ style: { marginRight: i },
20
+ children: [/* @__PURE__ */ v("span", {
20
21
  className: "text-xs text-text-muted",
21
- children: S ? x("bigconsole.widget.aiPreview.buildRunning", "AI build running") : x("bigconsole.widget.aiPreview.buildFinished", "AI build finished")
22
- }), /* @__PURE__ */ v(g, {
22
+ children: w ? S("bigconsole.widget.aiPreview.buildRunning", "AI build running") : S("bigconsole.widget.aiPreview.buildFinished", "AI build finished")
23
+ }), /* @__PURE__ */ y(_, {
23
24
  size: "sm",
24
25
  variant: "outline",
25
- onClick: j,
26
+ onClick: N,
26
27
  "data-testid": "ai-preview-open",
27
28
  children: [
28
- /* @__PURE__ */ _(p, { className: "mr-1.5 size-4" }),
29
- x("bigconsole.widget.aiPreviewMini.previewButton", "Preview"),
30
- S ? /* @__PURE__ */ _(f, { className: "ml-1.5 size-3.5 animate-spin" }) : null
29
+ /* @__PURE__ */ v(m, { className: "mr-1.5 size-4" }),
30
+ S("bigconsole.widget.aiPreviewMini.previewButton", "Preview"),
31
+ w ? /* @__PURE__ */ v(p, { className: "ml-1.5 size-3.5 animate-spin" }) : null
31
32
  ]
32
33
  })]
33
34
  });
34
- let P = [];
35
- D.datasink && P.push({
35
+ let I = [];
36
+ k.datasink && I.push({
36
37
  key: "datasink",
37
- label: x("bigconsole.widget.aiPreviewMini.link.dataSink", "Data Sink"),
38
- path: `/bigconsole/datasinks/${D.datasink}`,
39
- icon: /* @__PURE__ */ _(u, {
38
+ label: S("bigconsole.widget.aiPreviewMini.link.dataSink", "Data Sink"),
39
+ path: `/bigconsole/datasinks/${k.datasink}`,
40
+ icon: /* @__PURE__ */ v(d, {
40
41
  className: "size-4 shrink-0 text-text-muted",
41
42
  "aria-hidden": "true"
42
43
  })
43
- }), D.dashboard && P.push({
44
+ }), k.dashboard && I.push({
44
45
  key: "dashboard",
45
- label: x("bigconsole.widget.aiPreviewMini.link.dashboard", "Dashboard"),
46
- path: `/bigconsole/dashboards/${D.dashboard}`,
47
- icon: /* @__PURE__ */ _(d, {
46
+ label: S("bigconsole.widget.aiPreviewMini.link.dashboard", "Dashboard"),
47
+ path: `/bigconsole/dashboards/${k.dashboard}`,
48
+ icon: /* @__PURE__ */ v(f, {
48
49
  className: "size-4 shrink-0 text-text-muted",
49
50
  "aria-hidden": "true"
50
51
  })
51
- }), D.parser && P.push({
52
+ }), k.parser && I.push({
52
53
  key: "parser",
53
- label: x("bigconsole.widget.aiPreviewMini.link.parser", "Parser"),
54
- path: `/bigconsole/parsers/${D.parser}`,
55
- icon: /* @__PURE__ */ _(l, {
54
+ label: S("bigconsole.widget.aiPreviewMini.link.parser", "Parser"),
55
+ path: `/bigconsole/parsers/${k.parser}`,
56
+ icon: /* @__PURE__ */ v(u, {
56
57
  className: "size-4 shrink-0 text-text-muted",
57
58
  "aria-hidden": "true"
58
59
  })
59
60
  });
60
- let F = t.map((e) => ({
61
+ let L = n.map((e) => ({
61
62
  id: e,
62
- label: x(a[e].key, a[e].fallback),
63
- status: E[e]
63
+ label: S(o[e].key, o[e].fallback),
64
+ status: O[e]
64
65
  }));
65
- return /* @__PURE__ */ v("div", {
66
+ return /* @__PURE__ */ y("div", {
66
67
  "data-testid": "ai-preview-panel",
67
68
  "data-variant": "mini",
68
- style: { paddingRight: r },
69
+ style: { paddingRight: i },
69
70
  className: "mx-4 mb-3 mt-3 min-w-0 md:mx-6",
70
- children: [/* @__PURE__ */ _("h2", {
71
+ children: [/* @__PURE__ */ v("h2", {
71
72
  className: "sr-only",
72
- children: x("bigconsole.widget.aiPreviewMini.title", "AI Preview")
73
- }), /* @__PURE__ */ _(y, {
74
- title: x("bigconsole.widget.aiPreviewMini.title", "AI Preview"),
75
- steps: F,
76
- caption: S ? O || x("bigconsole.widget.aiPreviewMini.working", "Working…") : x("bigconsole.widget.aiPreviewMini.done", "Done"),
77
- error: k,
78
- meta: /* @__PURE__ */ _(i, {
79
- state: S ? "running" : k ? "stopped" : "idle",
80
- stateLabel: S ? x("bigconsole.widget.aiPreviewMini.live", "Live") : k ? x("bigconsole.widget.aiPreviewMini.stopped", "Stopped") : null,
81
- updatedLabel: x("bigconsole.widget.aiPreviewMini.updated", "updated"),
82
- updatedAt: A,
83
- ago: (e) => o(e, x)
73
+ children: S("bigconsole.widget.aiPreviewMini.title", "AI Preview")
74
+ }), /* @__PURE__ */ v(b, {
75
+ title: S("bigconsole.widget.aiPreviewMini.title", "AI Preview"),
76
+ steps: L,
77
+ caption: w ? A || S("bigconsole.widget.aiPreviewMini.working", "Working…") : S("bigconsole.widget.aiPreviewMini.done", "Done"),
78
+ error: j,
79
+ meta: /* @__PURE__ */ v(a, {
80
+ state: w ? "running" : j ? "stopped" : "idle",
81
+ stateLabel: w ? S("bigconsole.widget.aiPreviewMini.live", "Live") : j ? S("bigconsole.widget.aiPreviewMini.stopped", "Stopped") : null,
82
+ updatedLabel: S("bigconsole.widget.aiPreviewMini.updated", "updated"),
83
+ updatedAt: M,
84
+ ago: (e) => s(e, S)
84
85
  }),
85
- headerActions: /* @__PURE__ */ _(g, {
86
+ headerActions: /* @__PURE__ */ v(_, {
86
87
  size: "sm",
87
88
  variant: "ghost",
88
- onClick: M,
89
- "aria-label": x("bigconsole.widget.aiPreviewMini.closeAriaLabel", "Close preview"),
89
+ onClick: P,
90
+ "aria-label": S("bigconsole.widget.aiPreviewMini.closeAriaLabel", "Close preview"),
90
91
  "data-testid": "ai-preview-close",
91
- children: /* @__PURE__ */ _(m, { className: "size-4" })
92
+ children: /* @__PURE__ */ v(h, { className: "size-4" })
92
93
  }),
93
- labels: s(x),
94
- children: /* @__PURE__ */ _("div", {
94
+ labels: c(S),
95
+ children: /* @__PURE__ */ v("div", {
95
96
  className: "p-3",
96
- children: /* @__PURE__ */ v(e, {
97
+ children: /* @__PURE__ */ y(e, {
97
98
  emphasis: "standard",
98
99
  padding: "compact",
99
100
  headingLevel: 3,
100
- title: x("bigconsole.widget.aiPreviewMini.builtSoFar", "Built so far"),
101
- description: x("bigconsole.widget.aiPreviewMini.builtSoFarHint", "Open any piece to see the real thing."),
101
+ title: S("bigconsole.widget.aiPreviewMini.builtSoFar", "Built so far"),
102
+ description: S("bigconsole.widget.aiPreviewMini.builtSoFarHint", "Open any piece to see the real thing."),
102
103
  bodyClassName: "flex flex-col gap-list-gap",
103
- children: [P.length === 0 ? /* @__PURE__ */ _("p", {
104
+ children: [I.length === 0 ? /* @__PURE__ */ v("p", {
104
105
  className: "text-sm text-text-muted",
105
- children: S ? x("bigconsole.widget.aiPreviewMini.building", "Building… links appear here as each piece is created.") : x("bigconsole.widget.aiPreviewMini.nothingBuilt", "Nothing built yet.")
106
- }) : P.map((e) => /* @__PURE__ */ v("div", {
106
+ children: w ? S("bigconsole.widget.aiPreviewMini.building", "Building… links appear here as each piece is created.") : S("bigconsole.widget.aiPreviewMini.nothingBuilt", "Nothing built yet.")
107
+ }) : I.map((e) => /* @__PURE__ */ y("div", {
107
108
  "data-testid": "ai-preview-row",
108
109
  className: "flex min-w-0 items-center justify-between gap-control-gap rounded-card border border-[var(--color-border-subtle)] bg-bg-canvas px-3 py-2",
109
- children: [/* @__PURE__ */ v("p", {
110
+ children: [/* @__PURE__ */ y("p", {
110
111
  className: "flex min-w-0 items-center gap-icon-gap text-sm font-medium text-text-primary",
111
- children: [e.icon, /* @__PURE__ */ _("span", {
112
+ children: [e.icon, /* @__PURE__ */ v("span", {
112
113
  className: "truncate",
113
- children: x("bigconsole.widget.aiPreviewMini.linkReady", "{{label}} ready", { label: e.label })
114
+ children: S("bigconsole.widget.aiPreviewMini.linkReady", "{{label}} ready", { label: e.label })
114
115
  })]
115
- }), /* @__PURE__ */ v(g, {
116
+ }), /* @__PURE__ */ y(_, {
116
117
  size: "sm",
117
118
  variant: "ghost",
118
- onClick: () => N(e.path),
119
- children: [x("bigconsole.widget.aiPreviewMini.open", "Open"), /* @__PURE__ */ _(c, { className: "ml-1 size-3.5" })]
119
+ onClick: () => F(e.path),
120
+ children: [S("bigconsole.widget.aiPreviewMini.open", "Open"), /* @__PURE__ */ v(l, { className: "ml-1 size-3.5" })]
120
121
  })]
121
- }, e.key)), D.dashboard ? /* @__PURE__ */ v(g, {
122
+ }, e.key)), k.dashboard ? /* @__PURE__ */ y(_, {
122
123
  size: "sm",
123
124
  variant: "outline",
124
125
  className: "h-auto w-full whitespace-normal py-1.5 text-center",
125
126
  "data-testid": "ai-preview-open-in-bigconsole",
126
- onClick: () => N(`/bigconsole/dashboards/${D.dashboard ?? ""}`),
127
- children: [x("bigconsole.widget.aiPreviewMini.openInBigConsole", "Open in BigConsole for the live view"), /* @__PURE__ */ _(c, { className: "ml-1 size-3.5" })]
127
+ onClick: () => F(`/bigconsole/dashboards/${k.dashboard ?? ""}`),
128
+ children: [S("bigconsole.widget.aiPreviewMini.openInBigConsole", "Open in BigConsole for the live view"), /* @__PURE__ */ v(l, { className: "ml-1 size-3.5" })]
128
129
  }) : null]
129
130
  })
130
131
  })
@@ -132,6 +133,6 @@ function b({ onNavigate: b }) {
132
133
  });
133
134
  }
134
135
  //#endregion
135
- export { b as AiPreviewMiniPanel };
136
+ export { x as AiPreviewMiniPanel };
136
137
 
137
138
  //# sourceMappingURL=AiPreviewMiniPanel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AiPreviewMiniPanel.js","names":[],"sources":["../../../../src/bigconsole/components/preview/AiPreviewMiniPanel.tsx"],"sourcesContent":["/**\n * AI Preview — the app-shell copy.\n *\n * The full <AiPreviewPanel> lives inside BigConsoleRoot and only exists on\n * /bigconsole pages, but the assistant widget is global: you can start a build\n * from anywhere. This lightweight panel is mounted by the app shell so the build\n * is visible on WHATEVER page you are on — workflows, store, workspaces, search.\n *\n * It is deliberately store-only: no Apollo, no BigConsole context, no data\n * fetching. That is what lets it mount above the router. It shows the same live\n * step rail and caption as the full panel, plus click-through links to each\n * entity as it lands. The rich per-entity tabs stay on the /bigconsole pages,\n * where the data layer actually exists.\n *\n * It renders nothing while the full panel is up (`richMounted`), so the two\n * never stack. Leave a /bigconsole page mid-run and the full panel unmounts,\n * `richMounted` flips false, and this one takes over without a flicker.\n *\n * Section emphasis (BOFF-5639): the chrome (header, progress) is recessed and\n * the \"Built so far\" list is the one raised surface — the same ladder as the\n * full panel, so the two copies read as one component. Spacing is identical to\n * the full panel too; they used to differ (`pt-1` vs `pt-3`), which shifted the\n * page under the user as they navigated.\n */\nimport type { ReactNode } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { AssistantEntityPreview, type AssistantPreviewStep } from '@burdenoff/fe-libs/shared/assistant';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowUpRight, Braces, Database, LayoutDashboard, Loader2, Sparkles, X } from 'lucide-react';\n\nimport { PREVIEW_STEPS, useAssistantRunStore } from '../../assistant/assistantRunStore';\nimport { Section } from '../common';\nimport { DOCK_CLEARANCE, PreviewMeta, STEP_LABEL, formatAgo, previewLabels } from './previewShared';\n\ninterface AiPreviewMiniPanelProps {\n /**\n * Route to an in-app path. The links are absolute (`/bigconsole/...`) because\n * this panel renders outside the BigConsole router. Falls back to a full\n * navigation when the host does not supply a handler.\n */\n onNavigate?: (path: string) => void;\n}\n\ninterface QuickLink {\n key: string;\n label: string;\n path: string;\n icon: ReactNode;\n}\n\nexport function AiPreviewMiniPanel({ onNavigate }: AiPreviewMiniPanelProps) {\n const { t } = useI18n();\n const isRunning = useAssistantRunStore((state) => state.isRunning);\n const previewOpen = useAssistantRunStore((state) => state.previewOpen);\n const hasRun = useAssistantRunStore((state) => state.hasRun);\n const richMounted = useAssistantRunStore((state) => state.richMounted);\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\n const go = (path: string) => {\n if (onNavigate) onNavigate(path);\n else if (typeof window !== 'undefined') window.location.assign(path);\n };\n\n // Nothing has run, or the full in-context panel is already on screen.\n if (!hasRun || richMounted) return null;\n\n if (!previewOpen) {\n return (\n <div\n className=\"flex flex-wrap items-center justify-end gap-control-gap px-4 pt-3 md:px-6\"\n style={{ marginRight: DOCK_CLEARANCE }}\n >\n <span className=\"text-xs text-text-muted\">\n {isRunning\n ? t('bigconsole.widget.aiPreview.buildRunning', 'AI build running')\n : t('bigconsole.widget.aiPreview.buildFinished', 'AI build finished')}\n </span>\n <Button size=\"sm\" variant=\"outline\" onClick={openPreview} data-testid=\"ai-preview-open\">\n <Sparkles className=\"mr-1.5 size-4\" />\n {t('bigconsole.widget.aiPreviewMini.previewButton', 'Preview')}\n {isRunning ? <Loader2 className=\"ml-1.5 size-3.5 animate-spin\" /> : null}\n </Button>\n </div>\n );\n }\n\n const links: QuickLink[] = [];\n if (entityIds.datasink)\n links.push({\n key: 'datasink',\n label: t('bigconsole.widget.aiPreviewMini.link.dataSink', 'Data Sink'),\n path: `/bigconsole/datasinks/${entityIds.datasink}`,\n icon: <Database className=\"size-4 shrink-0 text-text-muted\" aria-hidden=\"true\" />,\n });\n if (entityIds.dashboard)\n links.push({\n key: 'dashboard',\n label: t('bigconsole.widget.aiPreviewMini.link.dashboard', 'Dashboard'),\n path: `/bigconsole/dashboards/${entityIds.dashboard}`,\n icon: <LayoutDashboard className=\"size-4 shrink-0 text-text-muted\" aria-hidden=\"true\" />,\n });\n if (entityIds.parser)\n links.push({\n key: 'parser',\n label: t('bigconsole.widget.aiPreviewMini.link.parser', 'Parser'),\n path: `/bigconsole/parsers/${entityIds.parser}`,\n icon: <Braces className=\"size-4 shrink-0 text-text-muted\" aria-hidden=\"true\" />,\n });\n\n const railSteps: AssistantPreviewStep[] = PREVIEW_STEPS.map((key) => ({\n id: key,\n label: t(STEP_LABEL[key].key, STEP_LABEL[key].fallback),\n status: steps[key],\n }));\n\n return (\n // ★ The wrapper owns POSITIONING only — clearance for the assistant dock,\n // page gutters, and the testids this panel already had. Everything visual\n // belongs to the shared surface, which is what keeps this copy and the full\n // panel from drifting apart now that `previewShared` no longer holds a shell\n // recipe for them both.\n //\n // ★★ PADDING, not margin: `mx-4 md:mx-6` is the page gutter and it is\n // RESPONSIVE. See DOCK_CLEARANCE's docblock.\n <div\n data-testid=\"ai-preview-panel\"\n data-variant=\"mini\"\n style={{ paddingRight: DOCK_CLEARANCE }}\n className=\"mx-4 mb-3 mt-3 min-w-0 md:mx-6\"\n >\n {/* ★★ The panel's own heading, restored. The shared surface renders its\n title as a styled <span> inside a <section aria-label>, which labels\n the region but leaves NO heading — while it still renders an <h3> for\n the step rail. main had <h2>AI Preview</h2> above that <h3>, so the\n migration both removed the panel from screen-reader heading navigation\n and orphaned the h3 under whatever <h2> the page beneath happens to\n end with. Visually hidden because the surface already shows the title;\n this only restores the structure. */}\n <h2 className=\"sr-only\">{t('bigconsole.widget.aiPreviewMini.title', 'AI Preview')}</h2>\n <AssistantEntityPreview\n title={t('bigconsole.widget.aiPreviewMini.title', 'AI Preview')}\n steps={railSteps}\n // ★★ TWO branches, not three, and deliberately so: the shared surface\n // renders `{error ? '' : caption}` — it BLANKS the caption whenever\n // `error` is set, so a failure branch here would compute a string that\n // is then discarded. The failure is named in `meta` instead, which\n // `PreviewHeader` renders without consulting `error`.\n caption={\n isRunning\n ? caption || t('bigconsole.widget.aiPreviewMini.working', 'Working…')\n : t('bigconsole.widget.aiPreviewMini.done', 'Done')\n }\n error={error}\n meta={\n <PreviewMeta\n state={isRunning ? 'running' : error ? 'stopped' : 'idle'}\n stateLabel={\n isRunning\n ? t('bigconsole.widget.aiPreviewMini.live', 'Live')\n : error\n ? t('bigconsole.widget.aiPreviewMini.stopped', 'Stopped')\n : null\n }\n updatedLabel={t('bigconsole.widget.aiPreviewMini.updated', 'updated')}\n updatedAt={updatedAt}\n ago={(seconds) => formatAgo(seconds, t)}\n />\n }\n headerActions={\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={closePreview}\n aria-label={t('bigconsole.widget.aiPreviewMini.closeAriaLabel', 'Close preview')}\n data-testid=\"ai-preview-close\"\n >\n <X className=\"size-4\" />\n </Button>\n }\n labels={previewLabels(t)}\n >\n {/* ★ No tabs here, by design: the rich per-entity views need Apollo and\n the BigConsole providers, which do not exist above the router. Links\n are what this copy can honestly offer. */}\n <div className=\"p-3\">\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title={t('bigconsole.widget.aiPreviewMini.builtSoFar', 'Built so far')}\n description={t('bigconsole.widget.aiPreviewMini.builtSoFarHint', 'Open any piece to see the real thing.')}\n bodyClassName=\"flex flex-col gap-list-gap\"\n >\n {links.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\n {isRunning\n ? t(\n 'bigconsole.widget.aiPreviewMini.building',\n 'Building… links appear here as each piece is created.'\n )\n : t('bigconsole.widget.aiPreviewMini.nothingBuilt', 'Nothing built yet.')}\n </p>\n ) : (\n links.map((link) => (\n <div\n key={link.key}\n data-testid=\"ai-preview-row\"\n className=\"flex min-w-0 items-center justify-between gap-control-gap rounded-card border border-[var(--color-border-subtle)] bg-bg-canvas px-3 py-2\"\n >\n <p className=\"flex min-w-0 items-center gap-icon-gap text-sm font-medium text-text-primary\">\n {link.icon}\n <span className=\"truncate\">\n {t('bigconsole.widget.aiPreviewMini.linkReady', '{{label}} ready', { label: link.label })}\n </span>\n </p>\n <Button size=\"sm\" variant=\"ghost\" onClick={() => go(link.path)}>\n {t('bigconsole.widget.aiPreviewMini.open', 'Open')}\n <ArrowUpRight className=\"ml-1 size-3.5\" />\n </Button>\n </div>\n ))\n )}\n {entityIds.dashboard ? (\n // fe-libs `Button` is `whitespace-nowrap` with a fixed `h-8` at\n // `size=\"sm\"`, so this long label overflowed the panel (which is\n // `overflow-hidden`) at 390px — and the hi/ta strings are longer\n // still. `h-auto whitespace-normal` lets it wrap to two lines\n // instead of being clipped.\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"h-auto w-full whitespace-normal py-1.5 text-center\"\n data-testid=\"ai-preview-open-in-bigconsole\"\n onClick={() => go(`/bigconsole/dashboards/${entityIds.dashboard ?? ''}`)}\n >\n {t('bigconsole.widget.aiPreviewMini.openInBigConsole', 'Open in BigConsole for the live view')}\n <ArrowUpRight className=\"ml-1 size-3.5\" />\n </Button>\n ) : null}\n </Section>\n </div>\n </AssistantEntityPreview>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AiPreviewMiniPanel.js","names":[],"sources":["../../../../src/bigconsole/components/preview/AiPreviewMiniPanel.tsx"],"sourcesContent":["/**\n * AI Preview — the app-shell copy.\n *\n * The full <AiPreviewPanel> lives inside BigConsoleRoot and only exists on\n * /bigconsole pages, but the assistant widget is global: you can start a build\n * from anywhere. This lightweight panel is mounted by the app shell so the build\n * is visible on WHATEVER page you are on — workflows, store, workspaces, search.\n *\n * It is deliberately store-only: no Apollo, no BigConsole context, no data\n * fetching. That is what lets it mount above the router. It shows the same live\n * step rail and caption as the full panel, plus click-through links to each\n * entity as it lands. The rich per-entity tabs stay on the /bigconsole pages,\n * where the data layer actually exists.\n *\n * It renders nothing while the full panel is up (`richMounted`), so the two\n * never stack. Leave a /bigconsole page mid-run and the full panel unmounts,\n * `richMounted` flips false, and this one takes over without a flicker.\n *\n * Section emphasis (BOFF-5639): the chrome (header, progress) is recessed and\n * the \"Built so far\" list is the one raised surface — the same ladder as the\n * full panel, so the two copies read as one component. Spacing is identical to\n * the full panel too; they used to differ (`pt-1` vs `pt-3`), which shifted the\n * page under the user as they navigated.\n */\nimport type { ReactNode } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { AssistantEntityPreview, type AssistantPreviewStep } from '@burdenoff/fe-libs/shared/assistant';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useAssistantPanelShowsPreview } from '../../assistant/useAssistantPanelShowsPreview';\nimport { ArrowUpRight, Braces, Database, LayoutDashboard, Loader2, Sparkles, X } from 'lucide-react';\n\nimport { PREVIEW_STEPS, useAssistantRunStore } from '../../assistant/assistantRunStore';\nimport { Section } from '../common';\nimport { DOCK_CLEARANCE, PreviewMeta, STEP_LABEL, formatAgo, previewLabels } from './previewShared';\n\ninterface AiPreviewMiniPanelProps {\n /**\n * Route to an in-app path. The links are absolute (`/bigconsole/...`) because\n * this panel renders outside the BigConsole router. Falls back to a full\n * navigation when the host does not supply a handler.\n */\n onNavigate?: (path: string) => void;\n}\n\ninterface QuickLink {\n key: string;\n label: string;\n path: string;\n icon: ReactNode;\n}\n\nexport function AiPreviewMiniPanel({ onNavigate }: AiPreviewMiniPanelProps) {\n const { t } = useI18n();\n // ★★★ ONE SURFACE PER RUN. fe-libs renders this same controller inside its own\n // in-chat overlay; without this the page surface stayed up beside it and one\n // run was drawn twice, side by side (measured in prod, BOFF-7799).\n const panelShowsPreview = useAssistantPanelShowsPreview();\n const isRunning = useAssistantRunStore((state) => state.isRunning);\n const previewOpen = useAssistantRunStore((state) => state.previewOpen);\n const hasRun = useAssistantRunStore((state) => state.hasRun);\n const richMounted = useAssistantRunStore((state) => state.richMounted);\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\n const go = (path: string) => {\n if (onNavigate) onNavigate(path);\n else if (typeof window !== 'undefined') window.location.assign(path);\n };\n\n // Nothing has run, or the full in-context panel is already on screen.\n if (!hasRun || richMounted) return null;\n // The shared panel's overlay is already showing this run — stand down rather\n // than draw it a second time beside itself.\n if (panelShowsPreview) return null;\n\n if (!previewOpen) {\n return (\n <div\n className=\"flex flex-wrap items-center justify-end gap-control-gap px-4 pt-3 md:px-6\"\n style={{ marginRight: DOCK_CLEARANCE }}\n >\n <span className=\"text-xs text-text-muted\">\n {isRunning\n ? t('bigconsole.widget.aiPreview.buildRunning', 'AI build running')\n : t('bigconsole.widget.aiPreview.buildFinished', 'AI build finished')}\n </span>\n <Button size=\"sm\" variant=\"outline\" onClick={openPreview} data-testid=\"ai-preview-open\">\n <Sparkles className=\"mr-1.5 size-4\" />\n {t('bigconsole.widget.aiPreviewMini.previewButton', 'Preview')}\n {isRunning ? <Loader2 className=\"ml-1.5 size-3.5 animate-spin\" /> : null}\n </Button>\n </div>\n );\n }\n\n const links: QuickLink[] = [];\n if (entityIds.datasink)\n links.push({\n key: 'datasink',\n label: t('bigconsole.widget.aiPreviewMini.link.dataSink', 'Data Sink'),\n path: `/bigconsole/datasinks/${entityIds.datasink}`,\n icon: <Database className=\"size-4 shrink-0 text-text-muted\" aria-hidden=\"true\" />,\n });\n if (entityIds.dashboard)\n links.push({\n key: 'dashboard',\n label: t('bigconsole.widget.aiPreviewMini.link.dashboard', 'Dashboard'),\n path: `/bigconsole/dashboards/${entityIds.dashboard}`,\n icon: <LayoutDashboard className=\"size-4 shrink-0 text-text-muted\" aria-hidden=\"true\" />,\n });\n if (entityIds.parser)\n links.push({\n key: 'parser',\n label: t('bigconsole.widget.aiPreviewMini.link.parser', 'Parser'),\n path: `/bigconsole/parsers/${entityIds.parser}`,\n icon: <Braces className=\"size-4 shrink-0 text-text-muted\" aria-hidden=\"true\" />,\n });\n\n const railSteps: AssistantPreviewStep[] = PREVIEW_STEPS.map((key) => ({\n id: key,\n label: t(STEP_LABEL[key].key, STEP_LABEL[key].fallback),\n status: steps[key],\n }));\n\n return (\n // ★ The wrapper owns POSITIONING only — clearance for the assistant dock,\n // page gutters, and the testids this panel already had. Everything visual\n // belongs to the shared surface, which is what keeps this copy and the full\n // panel from drifting apart now that `previewShared` no longer holds a shell\n // recipe for them both.\n //\n // ★★ PADDING, not margin: `mx-4 md:mx-6` is the page gutter and it is\n // RESPONSIVE. See DOCK_CLEARANCE's docblock.\n <div\n data-testid=\"ai-preview-panel\"\n data-variant=\"mini\"\n style={{ paddingRight: DOCK_CLEARANCE }}\n className=\"mx-4 mb-3 mt-3 min-w-0 md:mx-6\"\n >\n {/* ★★ The panel's own heading, restored. The shared surface renders its\n title as a styled <span> inside a <section aria-label>, which labels\n the region but leaves NO heading — while it still renders an <h3> for\n the step rail. main had <h2>AI Preview</h2> above that <h3>, so the\n migration both removed the panel from screen-reader heading navigation\n and orphaned the h3 under whatever <h2> the page beneath happens to\n end with. Visually hidden because the surface already shows the title;\n this only restores the structure. */}\n <h2 className=\"sr-only\">{t('bigconsole.widget.aiPreviewMini.title', 'AI Preview')}</h2>\n <AssistantEntityPreview\n title={t('bigconsole.widget.aiPreviewMini.title', 'AI Preview')}\n steps={railSteps}\n // ★★ TWO branches, not three, and deliberately so: the shared surface\n // renders `{error ? '' : caption}` — it BLANKS the caption whenever\n // `error` is set, so a failure branch here would compute a string that\n // is then discarded. The failure is named in `meta` instead, which\n // `PreviewHeader` renders without consulting `error`.\n caption={\n isRunning\n ? caption || t('bigconsole.widget.aiPreviewMini.working', 'Working…')\n : t('bigconsole.widget.aiPreviewMini.done', 'Done')\n }\n error={error}\n meta={\n <PreviewMeta\n state={isRunning ? 'running' : error ? 'stopped' : 'idle'}\n stateLabel={\n isRunning\n ? t('bigconsole.widget.aiPreviewMini.live', 'Live')\n : error\n ? t('bigconsole.widget.aiPreviewMini.stopped', 'Stopped')\n : null\n }\n updatedLabel={t('bigconsole.widget.aiPreviewMini.updated', 'updated')}\n updatedAt={updatedAt}\n ago={(seconds) => formatAgo(seconds, t)}\n />\n }\n headerActions={\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={closePreview}\n aria-label={t('bigconsole.widget.aiPreviewMini.closeAriaLabel', 'Close preview')}\n data-testid=\"ai-preview-close\"\n >\n <X className=\"size-4\" />\n </Button>\n }\n labels={previewLabels(t)}\n >\n {/* ★ No tabs here, by design: the rich per-entity views need Apollo and\n the BigConsole providers, which do not exist above the router. Links\n are what this copy can honestly offer. */}\n <div className=\"p-3\">\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title={t('bigconsole.widget.aiPreviewMini.builtSoFar', 'Built so far')}\n description={t('bigconsole.widget.aiPreviewMini.builtSoFarHint', 'Open any piece to see the real thing.')}\n bodyClassName=\"flex flex-col gap-list-gap\"\n >\n {links.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\n {isRunning\n ? t(\n 'bigconsole.widget.aiPreviewMini.building',\n 'Building… links appear here as each piece is created.'\n )\n : t('bigconsole.widget.aiPreviewMini.nothingBuilt', 'Nothing built yet.')}\n </p>\n ) : (\n links.map((link) => (\n <div\n key={link.key}\n data-testid=\"ai-preview-row\"\n className=\"flex min-w-0 items-center justify-between gap-control-gap rounded-card border border-[var(--color-border-subtle)] bg-bg-canvas px-3 py-2\"\n >\n <p className=\"flex min-w-0 items-center gap-icon-gap text-sm font-medium text-text-primary\">\n {link.icon}\n <span className=\"truncate\">\n {t('bigconsole.widget.aiPreviewMini.linkReady', '{{label}} ready', { label: link.label })}\n </span>\n </p>\n <Button size=\"sm\" variant=\"ghost\" onClick={() => go(link.path)}>\n {t('bigconsole.widget.aiPreviewMini.open', 'Open')}\n <ArrowUpRight className=\"ml-1 size-3.5\" />\n </Button>\n </div>\n ))\n )}\n {entityIds.dashboard ? (\n // fe-libs `Button` is `whitespace-nowrap` with a fixed `h-8` at\n // `size=\"sm\"`, so this long label overflowed the panel (which is\n // `overflow-hidden`) at 390px — and the hi/ta strings are longer\n // still. `h-auto whitespace-normal` lets it wrap to two lines\n // instead of being clipped.\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"h-auto w-full whitespace-normal py-1.5 text-center\"\n data-testid=\"ai-preview-open-in-bigconsole\"\n onClick={() => go(`/bigconsole/dashboards/${entityIds.dashboard ?? ''}`)}\n >\n {t('bigconsole.widget.aiPreviewMini.openInBigConsole', 'Open in BigConsole for the live view')}\n <ArrowUpRight className=\"ml-1 size-3.5\" />\n </Button>\n ) : null}\n </Section>\n </div>\n </AssistantEntityPreview>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;AAmDA,SAAgB,EAAmB,EAAE,iBAAuC;CAC1E,IAAM,EAAE,SAAM,GAAS,EAIjB,IAAoB,GAA+B,EACnD,IAAY,GAAsB,MAAU,EAAM,UAAU,EAC5D,IAAc,GAAsB,MAAU,EAAM,YAAY,EAChE,IAAS,GAAsB,MAAU,EAAM,OAAO,EACtD,IAAc,GAAsB,MAAU,EAAM,YAAY,EAChE,IAAQ,GAAsB,MAAU,EAAM,MAAM,EACpD,IAAY,GAAsB,MAAU,EAAM,UAAU,EAC5D,IAAU,GAAsB,MAAU,EAAM,QAAQ,EACxD,IAAQ,GAAsB,MAAU,EAAM,MAAM,EACpD,IAAY,GAAsB,MAAU,EAAM,UAAU,EAC5D,IAAc,GAAsB,MAAU,EAAM,YAAY,EAChE,IAAe,GAAsB,MAAU,EAAM,aAAa,EAElE,KAAM,MAAiB;AAC3B,EAAI,IAAY,EAAW,EAAK,GACvB,OAAO,SAAW,OAAa,OAAO,SAAS,OAAO,EAAK;;AAOtE,KAHI,CAAC,KAAU,KAGX,EAAmB,QAAO;AAE9B,KAAI,CAAC,EACH,QACE,kBAAC,OAAD;EACE,WAAU;EACV,OAAO,EAAE,aAAa,GAAgB;YAFxC,CAIE,kBAAC,QAAD;GAAM,WAAU;aACb,IACG,EAAE,4CAA4C,mBAAmB,GACjE,EAAE,6CAA6C,oBAAoB;GAClE,CAAA,EACP,kBAAC,GAAD;GAAQ,MAAK;GAAK,SAAQ;GAAU,SAAS;GAAa,eAAY;aAAtE;IACE,kBAAC,GAAD,EAAU,WAAU,iBAAkB,CAAA;IACrC,EAAE,iDAAiD,UAAU;IAC7D,IAAY,kBAAC,GAAD,EAAS,WAAU,gCAAiC,CAAA,GAAG;IAC7D;KACL;;CAIV,IAAM,IAAqB,EAAE;AAe7B,CAdI,EAAU,YACZ,EAAM,KAAK;EACT,KAAK;EACL,OAAO,EAAE,iDAAiD,YAAY;EACtE,MAAM,yBAAyB,EAAU;EACzC,MAAM,kBAAC,GAAD;GAAU,WAAU;GAAkC,eAAY;GAAS,CAAA;EAClF,CAAC,EACA,EAAU,aACZ,EAAM,KAAK;EACT,KAAK;EACL,OAAO,EAAE,kDAAkD,YAAY;EACvE,MAAM,0BAA0B,EAAU;EAC1C,MAAM,kBAAC,GAAD;GAAiB,WAAU;GAAkC,eAAY;GAAS,CAAA;EACzF,CAAC,EACA,EAAU,UACZ,EAAM,KAAK;EACT,KAAK;EACL,OAAO,EAAE,+CAA+C,SAAS;EACjE,MAAM,uBAAuB,EAAU;EACvC,MAAM,kBAAC,GAAD;GAAQ,WAAU;GAAkC,eAAY;GAAS,CAAA;EAChF,CAAC;CAEJ,IAAM,IAAoC,EAAc,KAAK,OAAS;EACpE,IAAI;EACJ,OAAO,EAAE,EAAW,GAAK,KAAK,EAAW,GAAK,SAAS;EACvD,QAAQ,EAAM;EACf,EAAE;AAEH,QASE,kBAAC,OAAD;EACE,eAAY;EACZ,gBAAa;EACb,OAAO,EAAE,cAAc,GAAgB;EACvC,WAAU;YAJZ,CAcE,kBAAC,MAAD;GAAI,WAAU;aAAW,EAAE,yCAAyC,aAAa;GAAM,CAAA,EACvF,kBAAC,GAAD;GACE,OAAO,EAAE,yCAAyC,aAAa;GAC/D,OAAO;GAMP,SACE,IACI,KAAW,EAAE,2CAA2C,WAAW,GACnE,EAAE,wCAAwC,OAAO;GAEhD;GACP,MACE,kBAAC,GAAD;IACE,OAAO,IAAY,YAAY,IAAQ,YAAY;IACnD,YACE,IACI,EAAE,wCAAwC,OAAO,GACjD,IACE,EAAE,2CAA2C,UAAU,GACvD;IAER,cAAc,EAAE,2CAA2C,UAAU;IAC1D;IACX,MAAM,MAAY,EAAU,GAAS,EAAE;IACvC,CAAA;GAEJ,eACE,kBAAC,GAAD;IACE,MAAK;IACL,SAAQ;IACR,SAAS;IACT,cAAY,EAAE,kDAAkD,gBAAgB;IAChF,eAAY;cAEZ,kBAAC,GAAD,EAAG,WAAU,UAAW,CAAA;IACjB,CAAA;GAEX,QAAQ,EAAc,EAAE;aAKxB,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KACE,UAAS;KACT,SAAQ;KACR,cAAc;KACd,OAAO,EAAE,8CAA8C,eAAe;KACtE,aAAa,EAAE,kDAAkD,wCAAwC;KACzG,eAAc;eANhB,CAQG,EAAM,WAAW,IAChB,kBAAC,KAAD;MAAG,WAAU;gBACV,IACG,EACE,4CACA,wDACD,GACD,EAAE,gDAAgD,qBAAqB;MACzE,CAAA,GAEJ,EAAM,KAAK,MACT,kBAAC,OAAD;MAEE,eAAY;MACZ,WAAU;gBAHZ,CAKE,kBAAC,KAAD;OAAG,WAAU;iBAAb,CACG,EAAK,MACN,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAE,6CAA6C,mBAAmB,EAAE,OAAO,EAAK,OAAO,CAAC;QACpF,CAAA,CACL;UACJ,kBAAC,GAAD;OAAQ,MAAK;OAAK,SAAQ;OAAQ,eAAe,EAAG,EAAK,KAAK;iBAA9D,CACG,EAAE,wCAAwC,OAAO,EAClD,kBAAC,GAAD,EAAc,WAAU,iBAAkB,CAAA,CACnC;SACL;QAdC,EAAK,IAcN,CACN,EAEH,EAAU,YAMT,kBAAC,GAAD;MACE,MAAK;MACL,SAAQ;MACR,WAAU;MACV,eAAY;MACZ,eAAe,EAAG,0BAA0B,EAAU,aAAa,KAAK;gBAL1E,CAOG,EAAE,oDAAoD,uCAAuC,EAC9F,kBAAC,GAAD,EAAc,WAAU,iBAAkB,CAAA,CACnC;UACP,KACI;;IACN,CAAA;GACiB,CAAA,CACrB"}
@@ -2,162 +2,163 @@ import { Section as e } from "../common/Section.js";
2
2
  import "../common/index.js";
3
3
  import { useBigConsoleNavigate as t } from "../../context/BigConsoleContext.js";
4
4
  import "../../context/index.js";
5
- import { PREVIEW_STEPS as n, useAssistantRunStore as r } from "../../assistant/assistantRunStore.js";
6
- import { DOCK_CLEARANCE as i, PreviewMeta as a, STEP_LABEL as o, formatAgo as s, previewLabels as c } from "./previewShared.js";
7
- import { DashboardContent as l, DataSinkContent as u, ParserContent as d, WidgetsContent as f } from "./AiPreviewContent.js";
8
- import { useEffect as p, useRef as m, useState as h } from "react";
9
- import { ArrowUpRight as g, Loader2 as _, Sparkles as v, X as y } from "lucide-react";
10
- import { useI18n as b } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
11
- import { Button as x } from "@burdenoff/fe-libs/ui";
12
- import { jsx as S, jsxs as C } from "react/jsx-runtime";
13
- import { AssistantEntityPreview as w } from "@burdenoff/fe-libs/shared/assistant";
5
+ import { useAssistantPanelShowsPreview as n } from "../../assistant/useAssistantPanelShowsPreview.js";
6
+ import { PREVIEW_STEPS as r, useAssistantRunStore as i } from "../../assistant/assistantRunStore.js";
7
+ import { DOCK_CLEARANCE as a, PreviewMeta as o, STEP_LABEL as s, formatAgo as c, previewLabels as l } from "./previewShared.js";
8
+ import { DashboardContent as u, DataSinkContent as d, ParserContent as f, WidgetsContent as p } from "./AiPreviewContent.js";
9
+ import { useEffect as m, useRef as h, useState as g } from "react";
10
+ import { ArrowUpRight as _, Loader2 as v, Sparkles as y, X as b } from "lucide-react";
11
+ import { useI18n as x } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
12
+ import { Button as S } from "@burdenoff/fe-libs/ui";
13
+ import { jsx as C, jsxs as w } from "react/jsx-runtime";
14
+ import { AssistantEntityPreview as T } from "@burdenoff/fe-libs/shared/assistant";
14
15
  //#region src/bigconsole/components/preview/AiPreviewPanel.tsx
15
- function T({ children: e }) {
16
- return /* @__PURE__ */ S("div", {
16
+ function E({ children: e }) {
17
+ return /* @__PURE__ */ C("div", {
17
18
  className: "min-w-0 overflow-y-auto p-3",
18
19
  style: { maxHeight: "min(440px, 60vh)" },
19
20
  children: e
20
21
  });
21
22
  }
22
- function E({ onOpen: e }) {
23
- let { t } = b();
24
- return /* @__PURE__ */ C(x, {
23
+ function D({ onOpen: e }) {
24
+ let { t } = x();
25
+ return /* @__PURE__ */ w(S, {
25
26
  size: "sm",
26
27
  variant: "outline",
27
28
  onClick: e,
28
29
  "data-testid": "ai-preview-open-entity",
29
- children: [t("bigconsole.widget.aiPreview.open", "Open"), /* @__PURE__ */ S(g, { className: "ml-1 size-3.5" })]
30
+ children: [t("bigconsole.widget.aiPreview.open", "Open"), /* @__PURE__ */ C(_, { className: "ml-1 size-3.5" })]
30
31
  });
31
32
  }
32
- function D() {
33
- let { t: g } = b(), D = t(), O = r((e) => e.isRunning), k = r((e) => e.previewOpen), A = r((e) => e.hasRun), j = r((e) => e.steps), M = r((e) => e.entityIds), N = r((e) => e.caption), P = r((e) => e.error), F = r((e) => e.updatedAt), I = r((e) => e.openPreview), L = r((e) => e.closePreview), R = r((e) => e.setRichMounted);
34
- p(() => (R(!0), () => {
35
- R(!1);
36
- }), [R]);
37
- let [z, B] = h(0), V = m(!1);
38
- if (p(() => {
39
- O && !V.current && B((e) => e + 1), V.current = O;
40
- }, [O]), !A) return null;
41
- if (!k) return /* @__PURE__ */ C("div", {
33
+ function O() {
34
+ let { t: _ } = x(), O = n(), k = t(), A = i((e) => e.isRunning), j = i((e) => e.previewOpen), M = i((e) => e.hasRun), N = i((e) => e.steps), P = i((e) => e.entityIds), F = i((e) => e.caption), I = i((e) => e.error), L = i((e) => e.updatedAt), R = i((e) => e.openPreview), z = i((e) => e.closePreview), B = i((e) => e.setRichMounted);
35
+ m(() => (B(!0), () => {
36
+ B(!1);
37
+ }), [B]);
38
+ let [V, H] = g(0), U = h(!1);
39
+ if (m(() => {
40
+ A && !U.current && H((e) => e + 1), U.current = A;
41
+ }, [A]), !M || O) return null;
42
+ if (!j) return /* @__PURE__ */ w("div", {
42
43
  className: "flex flex-wrap items-center justify-end gap-control-gap px-4 pt-3 md:px-6",
43
- style: { marginRight: i },
44
- children: [/* @__PURE__ */ S("span", {
44
+ style: { marginRight: a },
45
+ children: [/* @__PURE__ */ C("span", {
45
46
  className: "text-xs text-text-muted",
46
- children: O ? g("bigconsole.widget.aiPreview.buildRunning", "AI build running") : g("bigconsole.widget.aiPreview.buildFinished", "AI build finished")
47
- }), /* @__PURE__ */ C(x, {
47
+ children: A ? _("bigconsole.widget.aiPreview.buildRunning", "AI build running") : _("bigconsole.widget.aiPreview.buildFinished", "AI build finished")
48
+ }), /* @__PURE__ */ w(S, {
48
49
  size: "sm",
49
50
  variant: "outline",
50
- onClick: I,
51
+ onClick: R,
51
52
  "data-testid": "ai-preview-open",
52
53
  children: [
53
- /* @__PURE__ */ S(v, { className: "mr-1.5 size-4" }),
54
- g("bigconsole.widget.aiPreview.previewButton", "Preview"),
55
- O ? /* @__PURE__ */ S(_, { className: "ml-1.5 size-3.5 animate-spin" }) : null
54
+ /* @__PURE__ */ C(y, { className: "mr-1.5 size-4" }),
55
+ _("bigconsole.widget.aiPreview.previewButton", "Preview"),
56
+ A ? /* @__PURE__ */ C(v, { className: "ml-1.5 size-3.5 animate-spin" }) : null
56
57
  ]
57
58
  })]
58
59
  });
59
- let H = M.dashboard, U = n.map((e) => ({
60
+ let W = P.dashboard, G = r.map((e) => ({
60
61
  id: e,
61
- label: g(o[e].key, o[e].fallback),
62
- status: j[e]
63
- })), W = [
62
+ label: _(s[e].key, s[e].fallback),
63
+ status: N[e]
64
+ })), K = [
64
65
  {
65
66
  id: "datasink",
66
- label: g("bigconsole.widget.aiPreview.tab.dataSink", "Data Sink"),
67
- render: () => /* @__PURE__ */ S(T, { children: /* @__PURE__ */ S(e, {
67
+ label: _("bigconsole.widget.aiPreview.tab.dataSink", "Data Sink"),
68
+ render: () => /* @__PURE__ */ C(E, { children: /* @__PURE__ */ C(e, {
68
69
  emphasis: "standard",
69
70
  padding: "compact",
70
71
  headingLevel: 3,
71
- title: g("bigconsole.widget.aiPreview.dataSinkRows", "Data sink rows"),
72
- description: g("bigconsole.widget.aiPreview.dataSinkRowsHint", "The rows the assistant just landed, straight from the sink."),
73
- actions: M.datasink ? /* @__PURE__ */ S(E, { onOpen: () => D(`/datasinks/${M.datasink}`) }) : void 0,
74
- children: /* @__PURE__ */ S(u, {
75
- id: M.datasink,
76
- live: O
72
+ title: _("bigconsole.widget.aiPreview.dataSinkRows", "Data sink rows"),
73
+ description: _("bigconsole.widget.aiPreview.dataSinkRowsHint", "The rows the assistant just landed, straight from the sink."),
74
+ actions: P.datasink ? /* @__PURE__ */ C(D, { onOpen: () => k(`/datasinks/${P.datasink}`) }) : void 0,
75
+ children: /* @__PURE__ */ C(d, {
76
+ id: P.datasink,
77
+ live: A
77
78
  })
78
79
  }) })
79
80
  },
80
81
  {
81
82
  id: "dashboard",
82
- label: g("bigconsole.widget.aiPreview.tab.dashboard", "Dashboard"),
83
- render: () => /* @__PURE__ */ S(T, { children: /* @__PURE__ */ S(e, {
83
+ label: _("bigconsole.widget.aiPreview.tab.dashboard", "Dashboard"),
84
+ render: () => /* @__PURE__ */ C(E, { children: /* @__PURE__ */ C(e, {
84
85
  emphasis: "standard",
85
86
  padding: "compact",
86
87
  headingLevel: 3,
87
- title: g("bigconsole.widget.aiPreview.tab.dashboard", "Dashboard"),
88
- description: g("bigconsole.widget.aiPreview.dashboardHint", "The live board, rendered exactly as the real page renders it."),
89
- actions: H ? /* @__PURE__ */ S(E, { onOpen: () => D(`/dashboards/${H}`) }) : void 0,
90
- children: /* @__PURE__ */ S(l, { dashboardId: H })
88
+ title: _("bigconsole.widget.aiPreview.tab.dashboard", "Dashboard"),
89
+ description: _("bigconsole.widget.aiPreview.dashboardHint", "The live board, rendered exactly as the real page renders it."),
90
+ actions: W ? /* @__PURE__ */ C(D, { onOpen: () => k(`/dashboards/${W}`) }) : void 0,
91
+ children: /* @__PURE__ */ C(u, { dashboardId: W })
91
92
  }) })
92
93
  },
93
94
  {
94
95
  id: "parser",
95
- label: g("bigconsole.widget.aiPreview.tab.parser", "Parser"),
96
- render: () => /* @__PURE__ */ S(T, { children: /* @__PURE__ */ S(e, {
96
+ label: _("bigconsole.widget.aiPreview.tab.parser", "Parser"),
97
+ render: () => /* @__PURE__ */ C(E, { children: /* @__PURE__ */ C(e, {
97
98
  emphasis: "standard",
98
99
  padding: "compact",
99
100
  headingLevel: 3,
100
- title: g("bigconsole.widget.aiPreview.parserOutput", "Parser output"),
101
- description: g("bigconsole.widget.aiPreview.parserOutputHint", "The rows after the parser's transform, so you can check the shape."),
102
- actions: M.parser ? /* @__PURE__ */ S(E, { onOpen: () => D(`/parsers/${M.parser}`) }) : void 0,
103
- children: /* @__PURE__ */ S(d, {
104
- parserId: M.parser,
105
- live: O
101
+ title: _("bigconsole.widget.aiPreview.parserOutput", "Parser output"),
102
+ description: _("bigconsole.widget.aiPreview.parserOutputHint", "The rows after the parser's transform, so you can check the shape."),
103
+ actions: P.parser ? /* @__PURE__ */ C(D, { onOpen: () => k(`/parsers/${P.parser}`) }) : void 0,
104
+ children: /* @__PURE__ */ C(f, {
105
+ parserId: P.parser,
106
+ live: A
106
107
  })
107
108
  }) })
108
109
  },
109
110
  {
110
111
  id: "widget",
111
- label: g("bigconsole.widget.aiPreview.tab.widget", "Widget"),
112
- render: () => /* @__PURE__ */ S(T, { children: /* @__PURE__ */ S(e, {
112
+ label: _("bigconsole.widget.aiPreview.tab.widget", "Widget"),
113
+ render: () => /* @__PURE__ */ C(E, { children: /* @__PURE__ */ C(e, {
113
114
  emphasis: "standard",
114
115
  padding: "compact",
115
116
  headingLevel: 3,
116
- title: g("bigconsole.widget.aiPreview.widgets", "Widgets"),
117
- description: g("bigconsole.widget.aiPreview.widgetsHint", "Every widget on the new dashboard, rendered live."),
118
- actions: H ? /* @__PURE__ */ S(E, { onOpen: () => D(`/dashboards/${H}`) }) : void 0,
119
- children: /* @__PURE__ */ S(f, {
120
- dashboardId: H,
121
- live: O
117
+ title: _("bigconsole.widget.aiPreview.widgets", "Widgets"),
118
+ description: _("bigconsole.widget.aiPreview.widgetsHint", "Every widget on the new dashboard, rendered live."),
119
+ actions: W ? /* @__PURE__ */ C(D, { onOpen: () => k(`/dashboards/${W}`) }) : void 0,
120
+ children: /* @__PURE__ */ C(p, {
121
+ dashboardId: W,
122
+ live: A
122
123
  })
123
124
  }) })
124
125
  }
125
126
  ];
126
- return /* @__PURE__ */ C("div", {
127
+ return /* @__PURE__ */ w("div", {
127
128
  "data-testid": "ai-preview-panel",
128
- style: { paddingRight: i },
129
+ style: { paddingRight: a },
129
130
  className: "mx-4 mt-3 min-w-0 md:mx-6",
130
- children: [/* @__PURE__ */ S("h2", {
131
+ children: [/* @__PURE__ */ C("h2", {
131
132
  className: "sr-only",
132
- children: g("bigconsole.widget.aiPreview.title", "AI Preview")
133
- }), /* @__PURE__ */ S(w, {
134
- title: g("bigconsole.widget.aiPreview.title", "AI Preview"),
135
- runKey: String(z),
136
- steps: U,
137
- tabs: W,
133
+ children: _("bigconsole.widget.aiPreview.title", "AI Preview")
134
+ }), /* @__PURE__ */ C(T, {
135
+ title: _("bigconsole.widget.aiPreview.title", "AI Preview"),
136
+ runKey: String(V),
137
+ steps: G,
138
+ tabs: K,
138
139
  follow: "produced",
139
- caption: O ? N || g("bigconsole.widget.aiPreview.working", "Working…") : g("bigconsole.widget.aiPreview.done", "Done"),
140
- error: P,
141
- meta: /* @__PURE__ */ S(a, {
142
- state: O ? "running" : P ? "stopped" : "idle",
143
- stateLabel: O ? g("bigconsole.widget.aiPreview.live", "Live") : P ? g("bigconsole.widget.aiPreview.stopped", "Stopped") : null,
144
- updatedLabel: g("bigconsole.widget.aiPreview.updated", "updated"),
145
- updatedAt: F,
146
- ago: (e) => s(e, g)
140
+ caption: A ? F || _("bigconsole.widget.aiPreview.working", "Working…") : _("bigconsole.widget.aiPreview.done", "Done"),
141
+ error: I,
142
+ meta: /* @__PURE__ */ C(o, {
143
+ state: A ? "running" : I ? "stopped" : "idle",
144
+ stateLabel: A ? _("bigconsole.widget.aiPreview.live", "Live") : I ? _("bigconsole.widget.aiPreview.stopped", "Stopped") : null,
145
+ updatedLabel: _("bigconsole.widget.aiPreview.updated", "updated"),
146
+ updatedAt: L,
147
+ ago: (e) => c(e, _)
147
148
  }),
148
- headerActions: /* @__PURE__ */ S(x, {
149
+ headerActions: /* @__PURE__ */ C(S, {
149
150
  size: "sm",
150
151
  variant: "ghost",
151
- onClick: L,
152
- "aria-label": g("bigconsole.widget.aiPreview.closeAriaLabel", "Close preview"),
152
+ onClick: z,
153
+ "aria-label": _("bigconsole.widget.aiPreview.closeAriaLabel", "Close preview"),
153
154
  "data-testid": "ai-preview-close",
154
- children: /* @__PURE__ */ S(y, { className: "size-4" })
155
+ children: /* @__PURE__ */ C(b, { className: "size-4" })
155
156
  }),
156
- labels: c(g)
157
+ labels: l(_)
157
158
  })]
158
159
  });
159
160
  }
160
161
  //#endregion
161
- export { D as AiPreviewPanel };
162
+ export { O as AiPreviewPanel };
162
163
 
163
164
  //# 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, 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. Each tab has an \"Open\" button that jumps\n * to 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 *\n * ★★★ THE SHELL, RAIL AND TABS ARE fe-libs' NOW (`AssistantEntityPreview`).\n * What stays here is what is actually BigConsole's: which entities exist, how to\n * render each one, and where the panel sits relative to the assistant sidebar.\n * The shared surface brought three things this file could not have on its own:\n *\n * - a viewer's tab choice is HONOURED. The local `useEffect` re-selected a tab\n * on every store tick, so clicking one held for about 1.5 seconds before the\n * next progress update took it back — you could not read a tab while the\n * build continued.\n * - the rail can show a FAILED step instead of drawing a failure as \"not\n * started\" (the store still cannot produce one — see `assistantRunStore` —\n * but the surface no longer loses it if it ever does).\n * - keyboard navigation across the tabs, which the Radix tablist here never\n * had overflow handling for below ~430px.\n */\nimport { useEffect, useRef, useState, type ReactNode } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport {\n AssistantEntityPreview,\n type AssistantPreviewStep,\n type AssistantPreviewTab,\n} from '@burdenoff/fe-libs/shared/assistant';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowUpRight, Loader2, Sparkles, X } from 'lucide-react';\n\nimport { PREVIEW_STEPS, useAssistantRunStore } from '../../assistant/assistantRunStore';\nimport { useBigConsoleNavigate } from '../../context';\nimport { Section } from '../common';\nimport { DOCK_CLEARANCE, PreviewMeta, STEP_LABEL, formatAgo, previewLabels } from './previewShared';\nimport { DashboardContent, DataSinkContent, ParserContent, WidgetsContent } from './AiPreviewContent';\n\n/**\n * The scroll box each tab's content sits in.\n *\n * ★★ The cap is NOT cosmetic and does not belong in fe-libs: the shared surface\n * is laid out by whatever contains it, and this panel is injected ABOVE a\n * routed page — so without a cap a long data-sink preview pushes the whole page\n * down. `min(440px, 60vh)` is the original value; the hard 440px alone ate most\n * of the fold on a short laptop screen.\n */\nfunction TabBody({ children }: { children: ReactNode }) {\n return (\n <div className=\"min-w-0 overflow-y-auto p-3\" style={{ maxHeight: 'min(440px, 60vh)' }}>\n {children}\n </div>\n );\n}\n\n/** Jump-to-the-real-page button, shared by all four tabs. */\nfunction OpenEntityButton({ onOpen }: { onOpen: () => void }) {\n const { t } = useI18n();\n return (\n <Button size=\"sm\" variant=\"outline\" onClick={onOpen} data-testid=\"ai-preview-open-entity\">\n {t('bigconsole.widget.aiPreview.open', 'Open')}\n <ArrowUpRight className=\"ml-1 size-3.5\" />\n </Button>\n );\n}\n\nexport function AiPreviewPanel() {\n const { t } = useI18n();\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 useEffect(() => {\n setRichMounted(true);\n return () => {\n setRichMounted(false);\n };\n }, [setRichMounted]);\n\n /**\n * ★★★ WHAT COUNTS AS A \"RUN\" HERE, for the purpose of releasing a pinned tab.\n *\n * The store has no run id and deliberately never resets — a new prompt\n * CONTINUES the same build, preserving every step and entity id already made\n * (`assistantRunStore.startRun`). So the key is minted here, on the rising\n * edge of `isRunning`: each new turn is a new run, which releases a tab the\n * viewer pinned during the previous one. That is the right side to err on —\n * asking for something new and being shown the old tab is worse than losing a\n * pin you made a turn ago.\n */\n const [runKey, setRunKey] = useState(0);\n const wasRunningRef = useRef(false);\n useEffect(() => {\n if (isRunning && !wasRunningRef.current) setRunKey((n) => n + 1);\n wasRunningRef.current = isRunning;\n }, [isRunning]);\n\n if (!hasRun) return null;\n\n if (!previewOpen) {\n return (\n <div\n className=\"flex flex-wrap items-center justify-end gap-control-gap px-4 pt-3 md:px-6\"\n style={{ marginRight: DOCK_CLEARANCE }}\n >\n {/* The lone button gave no clue what it belonged to. */}\n <span className=\"text-xs text-text-muted\">\n {isRunning\n ? t('bigconsole.widget.aiPreview.buildRunning', 'AI build running')\n : t('bigconsole.widget.aiPreview.buildFinished', 'AI build finished')}\n </span>\n <Button size=\"sm\" variant=\"outline\" onClick={openPreview} data-testid=\"ai-preview-open\">\n <Sparkles className=\"mr-1.5 size-4\" />\n {t('bigconsole.widget.aiPreview.previewButton', 'Preview')}\n {isRunning ? <Loader2 className=\"ml-1.5 size-3.5 animate-spin\" /> : null}\n </Button>\n </div>\n );\n }\n\n const dashboardId = entityIds.dashboard;\n\n const railSteps: AssistantPreviewStep[] = PREVIEW_STEPS.map((key) => ({\n id: key,\n label: t(STEP_LABEL[key].key, STEP_LABEL[key].fallback),\n status: steps[key],\n }));\n\n const tabs: AssistantPreviewTab[] = [\n {\n id: 'datasink',\n label: t('bigconsole.widget.aiPreview.tab.dataSink', 'Data Sink'),\n render: () => (\n <TabBody>\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title={t('bigconsole.widget.aiPreview.dataSinkRows', 'Data sink rows')}\n description={t(\n 'bigconsole.widget.aiPreview.dataSinkRowsHint',\n 'The rows the assistant just landed, straight from the sink.'\n )}\n actions={\n entityIds.datasink ? (\n <OpenEntityButton onOpen={() => navigate(`/datasinks/${entityIds.datasink}`)} />\n ) : undefined\n }\n >\n <DataSinkContent id={entityIds.datasink} live={isRunning} />\n </Section>\n </TabBody>\n ),\n },\n {\n id: 'dashboard',\n label: t('bigconsole.widget.aiPreview.tab.dashboard', 'Dashboard'),\n render: () => (\n <TabBody>\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title={t('bigconsole.widget.aiPreview.tab.dashboard', 'Dashboard')}\n description={t(\n 'bigconsole.widget.aiPreview.dashboardHint',\n 'The live board, rendered exactly as the real page renders it.'\n )}\n actions={\n dashboardId ? <OpenEntityButton onOpen={() => navigate(`/dashboards/${dashboardId}`)} /> : undefined\n }\n >\n <DashboardContent dashboardId={dashboardId} />\n </Section>\n </TabBody>\n ),\n },\n {\n id: 'parser',\n label: t('bigconsole.widget.aiPreview.tab.parser', 'Parser'),\n render: () => (\n <TabBody>\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title={t('bigconsole.widget.aiPreview.parserOutput', 'Parser output')}\n description={t(\n 'bigconsole.widget.aiPreview.parserOutputHint',\n \"The rows after the parser's transform, so you can check the shape.\"\n )}\n actions={\n entityIds.parser ? (\n <OpenEntityButton onOpen={() => navigate(`/parsers/${entityIds.parser}`)} />\n ) : undefined\n }\n >\n <ParserContent parserId={entityIds.parser} live={isRunning} />\n </Section>\n </TabBody>\n ),\n },\n {\n id: 'widget',\n label: t('bigconsole.widget.aiPreview.tab.widget', 'Widget'),\n render: () => (\n <TabBody>\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title={t('bigconsole.widget.aiPreview.widgets', 'Widgets')}\n description={t(\n 'bigconsole.widget.aiPreview.widgetsHint',\n 'Every widget on the new dashboard, rendered live.'\n )}\n actions={\n dashboardId ? <OpenEntityButton onOpen={() => navigate(`/dashboards/${dashboardId}`)} /> : undefined\n }\n >\n <WidgetsContent dashboardId={dashboardId} live={isRunning} />\n </Section>\n </TabBody>\n ),\n },\n ];\n\n return (\n // ★ The wrapper owns POSITIONING only — clearance for the assistant dock,\n // page gutters, and the testid BigConsole already had. Everything visual\n // belongs to the shared surface.\n //\n // ★★ PADDING, not margin: `mx-4 md:mx-6` is the page gutter and it is\n // RESPONSIVE. See DOCK_CLEARANCE's docblock.\n <div data-testid=\"ai-preview-panel\" style={{ paddingRight: DOCK_CLEARANCE }} className=\"mx-4 mt-3 min-w-0 md:mx-6\">\n {/* ★★ The panel's own heading, restored. The shared surface renders its\n title as a styled <span> inside a <section aria-label>, which labels\n the region but leaves NO heading — while it still renders an <h3> for\n the step rail. main had <h2>AI Preview</h2> above that <h3>, so the\n migration both removed the panel from screen-reader heading navigation\n and orphaned the h3 under whatever <h2> the page beneath happens to\n end with. Visually hidden because the surface already shows the title;\n this only restores the structure. */}\n <h2 className=\"sr-only\">{t('bigconsole.widget.aiPreview.title', 'AI Preview')}</h2>\n <AssistantEntityPreview\n title={t('bigconsole.widget.aiPreview.title', 'AI Preview')}\n runKey={String(runKey)}\n steps={railSteps}\n tabs={tabs}\n // ★ Explicit, though it is the default: this panel's own rule was\n // \"show the LATEST thing that actually landed — so the moment the data\n // sink is created you see its rows, not an empty 'waiting for the\n // dashboard' tab\". Stating it keeps that reasoning attached to the\n // consumer that discovered it, not only to the library that adopted it.\n follow=\"produced\"\n // ★★ TWO branches, not three, and deliberately so: the shared surface\n // renders `{error ? '' : caption}` — it BLANKS the caption whenever\n // `error` is set, so a failure branch here would compute a string that\n // is then discarded. The failure is named in `meta` instead, which\n // `PreviewHeader` renders without consulting `error`.\n caption={\n isRunning\n ? caption || t('bigconsole.widget.aiPreview.working', 'Working…')\n : t('bigconsole.widget.aiPreview.done', 'Done')\n }\n error={error}\n meta={\n <PreviewMeta\n state={isRunning ? 'running' : error ? 'stopped' : 'idle'}\n stateLabel={\n isRunning\n ? t('bigconsole.widget.aiPreview.live', 'Live')\n : error\n ? t('bigconsole.widget.aiPreview.stopped', 'Stopped')\n : null\n }\n updatedLabel={t('bigconsole.widget.aiPreview.updated', 'updated')}\n updatedAt={updatedAt}\n ago={(seconds) => formatAgo(seconds, t)}\n />\n }\n headerActions={\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={closePreview}\n aria-label={t('bigconsole.widget.aiPreview.closeAriaLabel', 'Close preview')}\n data-testid=\"ai-preview-close\"\n >\n <X className=\"size-4\" />\n </Button>\n }\n labels={previewLabels(t)}\n />\n </div>\n );\n}\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. Each tab has an \"Open\" button that jumps\n * to 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 *\n * ★★★ THE SHELL, RAIL AND TABS ARE fe-libs' NOW (`AssistantEntityPreview`).\n * What stays here is what is actually BigConsole's: which entities exist, how to\n * render each one, and where the panel sits relative to the assistant sidebar.\n * The shared surface brought three things this file could not have on its own:\n *\n * - a viewer's tab choice is HONOURED. The local `useEffect` re-selected a tab\n * on every store tick, so clicking one held for about 1.5 seconds before the\n * next progress update took it back — you could not read a tab while the\n * build continued.\n * - the rail can show a FAILED step instead of drawing a failure as \"not\n * started\" (the store still cannot produce one — see `assistantRunStore` —\n * but the surface no longer loses it if it ever does).\n * - keyboard navigation across the tabs, which the Radix tablist here never\n * had overflow handling for below ~430px.\n */\nimport { useEffect, useRef, useState, type ReactNode } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport {\n AssistantEntityPreview,\n type AssistantPreviewStep,\n type AssistantPreviewTab,\n} from '@burdenoff/fe-libs/shared/assistant';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useAssistantPanelShowsPreview } from '../../assistant/useAssistantPanelShowsPreview';\nimport { ArrowUpRight, Loader2, Sparkles, X } from 'lucide-react';\n\nimport { PREVIEW_STEPS, useAssistantRunStore } from '../../assistant/assistantRunStore';\nimport { useBigConsoleNavigate } from '../../context';\nimport { Section } from '../common';\nimport { DOCK_CLEARANCE, PreviewMeta, STEP_LABEL, formatAgo, previewLabels } from './previewShared';\nimport { DashboardContent, DataSinkContent, ParserContent, WidgetsContent } from './AiPreviewContent';\n\n/**\n * The scroll box each tab's content sits in.\n *\n * ★★ The cap is NOT cosmetic and does not belong in fe-libs: the shared surface\n * is laid out by whatever contains it, and this panel is injected ABOVE a\n * routed page — so without a cap a long data-sink preview pushes the whole page\n * down. `min(440px, 60vh)` is the original value; the hard 440px alone ate most\n * of the fold on a short laptop screen.\n */\nfunction TabBody({ children }: { children: ReactNode }) {\n return (\n <div className=\"min-w-0 overflow-y-auto p-3\" style={{ maxHeight: 'min(440px, 60vh)' }}>\n {children}\n </div>\n );\n}\n\n/** Jump-to-the-real-page button, shared by all four tabs. */\nfunction OpenEntityButton({ onOpen }: { onOpen: () => void }) {\n const { t } = useI18n();\n return (\n <Button size=\"sm\" variant=\"outline\" onClick={onOpen} data-testid=\"ai-preview-open-entity\">\n {t('bigconsole.widget.aiPreview.open', 'Open')}\n <ArrowUpRight className=\"ml-1 size-3.5\" />\n </Button>\n );\n}\n\nexport function AiPreviewPanel() {\n const { t } = useI18n();\n // ★★★ ONE SURFACE PER RUN. fe-libs renders this same controller inside its own\n // in-chat overlay; without this the page surface stayed up beside it and one\n // run was drawn twice, side by side (measured in prod, BOFF-7799).\n const panelShowsPreview = useAssistantPanelShowsPreview();\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 useEffect(() => {\n setRichMounted(true);\n return () => {\n setRichMounted(false);\n };\n }, [setRichMounted]);\n\n /**\n * ★★★ WHAT COUNTS AS A \"RUN\" HERE, for the purpose of releasing a pinned tab.\n *\n * The store has no run id and deliberately never resets — a new prompt\n * CONTINUES the same build, preserving every step and entity id already made\n * (`assistantRunStore.startRun`). So the key is minted here, on the rising\n * edge of `isRunning`: each new turn is a new run, which releases a tab the\n * viewer pinned during the previous one. That is the right side to err on —\n * asking for something new and being shown the old tab is worse than losing a\n * pin you made a turn ago.\n */\n const [runKey, setRunKey] = useState(0);\n const wasRunningRef = useRef(false);\n useEffect(() => {\n if (isRunning && !wasRunningRef.current) setRunKey((n) => n + 1);\n wasRunningRef.current = isRunning;\n }, [isRunning]);\n\n if (!hasRun) return null;\n // The shared panel's overlay is already showing this run — stand down rather\n // than draw it a second time beside itself.\n if (panelShowsPreview) return null;\n\n if (!previewOpen) {\n return (\n <div\n className=\"flex flex-wrap items-center justify-end gap-control-gap px-4 pt-3 md:px-6\"\n style={{ marginRight: DOCK_CLEARANCE }}\n >\n {/* The lone button gave no clue what it belonged to. */}\n <span className=\"text-xs text-text-muted\">\n {isRunning\n ? t('bigconsole.widget.aiPreview.buildRunning', 'AI build running')\n : t('bigconsole.widget.aiPreview.buildFinished', 'AI build finished')}\n </span>\n <Button size=\"sm\" variant=\"outline\" onClick={openPreview} data-testid=\"ai-preview-open\">\n <Sparkles className=\"mr-1.5 size-4\" />\n {t('bigconsole.widget.aiPreview.previewButton', 'Preview')}\n {isRunning ? <Loader2 className=\"ml-1.5 size-3.5 animate-spin\" /> : null}\n </Button>\n </div>\n );\n }\n\n const dashboardId = entityIds.dashboard;\n\n const railSteps: AssistantPreviewStep[] = PREVIEW_STEPS.map((key) => ({\n id: key,\n label: t(STEP_LABEL[key].key, STEP_LABEL[key].fallback),\n status: steps[key],\n }));\n\n const tabs: AssistantPreviewTab[] = [\n {\n id: 'datasink',\n label: t('bigconsole.widget.aiPreview.tab.dataSink', 'Data Sink'),\n render: () => (\n <TabBody>\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title={t('bigconsole.widget.aiPreview.dataSinkRows', 'Data sink rows')}\n description={t(\n 'bigconsole.widget.aiPreview.dataSinkRowsHint',\n 'The rows the assistant just landed, straight from the sink.'\n )}\n actions={\n entityIds.datasink ? (\n <OpenEntityButton onOpen={() => navigate(`/datasinks/${entityIds.datasink}`)} />\n ) : undefined\n }\n >\n <DataSinkContent id={entityIds.datasink} live={isRunning} />\n </Section>\n </TabBody>\n ),\n },\n {\n id: 'dashboard',\n label: t('bigconsole.widget.aiPreview.tab.dashboard', 'Dashboard'),\n render: () => (\n <TabBody>\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title={t('bigconsole.widget.aiPreview.tab.dashboard', 'Dashboard')}\n description={t(\n 'bigconsole.widget.aiPreview.dashboardHint',\n 'The live board, rendered exactly as the real page renders it.'\n )}\n actions={\n dashboardId ? <OpenEntityButton onOpen={() => navigate(`/dashboards/${dashboardId}`)} /> : undefined\n }\n >\n <DashboardContent dashboardId={dashboardId} />\n </Section>\n </TabBody>\n ),\n },\n {\n id: 'parser',\n label: t('bigconsole.widget.aiPreview.tab.parser', 'Parser'),\n render: () => (\n <TabBody>\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title={t('bigconsole.widget.aiPreview.parserOutput', 'Parser output')}\n description={t(\n 'bigconsole.widget.aiPreview.parserOutputHint',\n \"The rows after the parser's transform, so you can check the shape.\"\n )}\n actions={\n entityIds.parser ? (\n <OpenEntityButton onOpen={() => navigate(`/parsers/${entityIds.parser}`)} />\n ) : undefined\n }\n >\n <ParserContent parserId={entityIds.parser} live={isRunning} />\n </Section>\n </TabBody>\n ),\n },\n {\n id: 'widget',\n label: t('bigconsole.widget.aiPreview.tab.widget', 'Widget'),\n render: () => (\n <TabBody>\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title={t('bigconsole.widget.aiPreview.widgets', 'Widgets')}\n description={t(\n 'bigconsole.widget.aiPreview.widgetsHint',\n 'Every widget on the new dashboard, rendered live.'\n )}\n actions={\n dashboardId ? <OpenEntityButton onOpen={() => navigate(`/dashboards/${dashboardId}`)} /> : undefined\n }\n >\n <WidgetsContent dashboardId={dashboardId} live={isRunning} />\n </Section>\n </TabBody>\n ),\n },\n ];\n\n return (\n // ★ The wrapper owns POSITIONING only — clearance for the assistant dock,\n // page gutters, and the testid BigConsole already had. Everything visual\n // belongs to the shared surface.\n //\n // ★★ PADDING, not margin: `mx-4 md:mx-6` is the page gutter and it is\n // RESPONSIVE. See DOCK_CLEARANCE's docblock.\n <div data-testid=\"ai-preview-panel\" style={{ paddingRight: DOCK_CLEARANCE }} className=\"mx-4 mt-3 min-w-0 md:mx-6\">\n {/* ★★ The panel's own heading, restored. The shared surface renders its\n title as a styled <span> inside a <section aria-label>, which labels\n the region but leaves NO heading — while it still renders an <h3> for\n the step rail. main had <h2>AI Preview</h2> above that <h3>, so the\n migration both removed the panel from screen-reader heading navigation\n and orphaned the h3 under whatever <h2> the page beneath happens to\n end with. Visually hidden because the surface already shows the title;\n this only restores the structure. */}\n <h2 className=\"sr-only\">{t('bigconsole.widget.aiPreview.title', 'AI Preview')}</h2>\n <AssistantEntityPreview\n title={t('bigconsole.widget.aiPreview.title', 'AI Preview')}\n runKey={String(runKey)}\n steps={railSteps}\n tabs={tabs}\n // ★ Explicit, though it is the default: this panel's own rule was\n // \"show the LATEST thing that actually landed — so the moment the data\n // sink is created you see its rows, not an empty 'waiting for the\n // dashboard' tab\". Stating it keeps that reasoning attached to the\n // consumer that discovered it, not only to the library that adopted it.\n follow=\"produced\"\n // ★★ TWO branches, not three, and deliberately so: the shared surface\n // renders `{error ? '' : caption}` — it BLANKS the caption whenever\n // `error` is set, so a failure branch here would compute a string that\n // is then discarded. The failure is named in `meta` instead, which\n // `PreviewHeader` renders without consulting `error`.\n caption={\n isRunning\n ? caption || t('bigconsole.widget.aiPreview.working', 'Working…')\n : t('bigconsole.widget.aiPreview.done', 'Done')\n }\n error={error}\n meta={\n <PreviewMeta\n state={isRunning ? 'running' : error ? 'stopped' : 'idle'}\n stateLabel={\n isRunning\n ? t('bigconsole.widget.aiPreview.live', 'Live')\n : error\n ? t('bigconsole.widget.aiPreview.stopped', 'Stopped')\n : null\n }\n updatedLabel={t('bigconsole.widget.aiPreview.updated', 'updated')}\n updatedAt={updatedAt}\n ago={(seconds) => formatAgo(seconds, t)}\n />\n }\n headerActions={\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={closePreview}\n aria-label={t('bigconsole.widget.aiPreview.closeAriaLabel', 'Close preview')}\n data-testid=\"ai-preview-close\"\n >\n <X className=\"size-4\" />\n </Button>\n }\n labels={previewLabels(t)}\n />\n </div>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-bigconsole",
3
- "version": "2026.924.1",
3
+ "version": "2026.924.2",
4
4
  "description": "BigConsole - AI-powered analytics and dashboard platform",
5
5
  "type": "module",
6
6
  "files": [