@burdenoff/microfe-bigconsole 2026.929.1 → 2026.930.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/dist/bigconsole/assistant/assistantRunPersistence.js +111 -0
  2. package/dist/bigconsole/assistant/assistantRunPersistence.js.map +1 -0
  3. package/dist/bigconsole/assistant/assistantRunStore.js +87 -48
  4. package/dist/bigconsole/assistant/assistantRunStore.js.map +1 -1
  5. package/dist/bigconsole/assistant/previewController.js +19 -50
  6. package/dist/bigconsole/assistant/previewController.js.map +1 -1
  7. package/dist/bigconsole/assistant/useAssistantRunWorkspace.js +14 -0
  8. package/dist/bigconsole/assistant/useAssistantRunWorkspace.js.map +1 -0
  9. package/dist/bigconsole/components/preview/AiPreviewMiniPanel.js +32 -30
  10. package/dist/bigconsole/components/preview/AiPreviewMiniPanel.js.map +1 -1
  11. package/dist/bigconsole/components/preview/AiPreviewPanel.js +38 -36
  12. package/dist/bigconsole/components/preview/AiPreviewPanel.js.map +1 -1
  13. package/dist/bigconsole/components/preview/previewShared.js +1 -1
  14. package/dist/bigconsole/components/preview/previewShared.js.map +1 -1
  15. package/dist/bigconsole/components/widgets/ActionConfigDialog.js +410 -674
  16. package/dist/bigconsole/components/widgets/ActionConfigDialog.js.map +1 -1
  17. package/dist/bigconsole/components/widgets/PropertiesPanel.js +100 -97
  18. package/dist/bigconsole/components/widgets/PropertiesPanel.js.map +1 -1
  19. package/dist/bigconsole/components/widgets/WidgetWrapper.js +1 -5
  20. package/dist/bigconsole/components/widgets/WidgetWrapper.js.map +1 -1
  21. package/dist/bigconsole/components/widgets/actionConfig.js +56 -0
  22. package/dist/bigconsole/components/widgets/actionConfig.js.map +1 -0
  23. package/package.json +3 -3
  24. package/dist/bigconsole/assistant/useAssistantPanelShowsPreview.js +0 -25
  25. package/dist/bigconsole/assistant/useAssistantPanelShowsPreview.js.map +0 -1
@@ -1,5 +1,5 @@
1
- import { useAssistantPanelShowsPreview as e } from "../../assistant/useAssistantPanelShowsPreview.js";
2
- import { PREVIEW_STEPS as t, useAssistantRunStore as n } from "../../assistant/assistantRunStore.js";
1
+ import { PREVIEW_STEPS as e, useAssistantRunStore as t } from "../../assistant/assistantRunStore.js";
2
+ import { useAssistantRunWorkspace as n } from "../../assistant/useAssistantRunWorkspace.js";
3
3
  import { Section as r } from "../common/Section.js";
4
4
  import "../common/index.js";
5
5
  import { DOCK_CLEARANCE as i, PreviewMeta as a, STEP_LABEL as o, formatAgo as s, previewLabels as c } from "./previewShared.js";
@@ -10,58 +10,60 @@ import { useI18n as y } from "@burdenoff/fe-libs/shared/providers/shell/I18nProv
10
10
  import { AssistantEntityPreview as b } from "@burdenoff/fe-libs/shared/assistant";
11
11
  //#region src/bigconsole/components/preview/AiPreviewMiniPanel.tsx
12
12
  function x({ onNavigate: x }) {
13
- let { t: S } = y(), C = e(), w = n((e) => e.isRunning), T = n((e) => e.previewOpen), E = n((e) => e.hasRun), D = n((e) => e.richMounted), O = n((e) => e.steps), k = n((e) => e.entityIds), A = n((e) => e.caption), j = n((e) => e.error), M = n((e) => e.updatedAt), N = n((e) => e.openPreview), P = n((e) => e.closePreview), F = (e) => {
13
+ let { t: S } = y();
14
+ n();
15
+ let C = t((e) => e.isRunning), w = t((e) => e.previewOpen), T = t((e) => e.hasRun), E = t((e) => e.richMounted), D = t((e) => e.steps), O = t((e) => e.entityIds), k = t((e) => e.caption), A = t((e) => e.error), j = t((e) => e.updatedAt), M = t((e) => e.openPreview), N = t((e) => e.closePreview), P = (e) => {
14
16
  x ? x(e) : typeof window < "u" && window.location.assign(e);
15
17
  };
16
- if (!E || D || C) return null;
17
- if (!T) return /* @__PURE__ */ v("div", {
18
+ if (!T || E) return null;
19
+ if (!w) return /* @__PURE__ */ v("div", {
18
20
  className: "flex flex-wrap items-center justify-end gap-control-gap px-4 pt-3 md:px-6",
19
21
  style: { marginRight: i },
20
22
  children: [/* @__PURE__ */ _("span", {
21
23
  className: "text-xs text-text-muted",
22
- children: w ? S("bigconsole.widget.aiPreview.buildRunning", "AI build running") : S("bigconsole.widget.aiPreview.buildFinished", "AI build finished")
24
+ children: C ? S("bigconsole.widget.aiPreview.buildRunning", "AI build running") : S("bigconsole.widget.aiPreview.buildFinished", "AI build finished")
23
25
  }), /* @__PURE__ */ v(g, {
24
26
  size: "sm",
25
27
  variant: "outline",
26
- onClick: N,
28
+ onClick: M,
27
29
  "data-testid": "ai-preview-open",
28
30
  children: [
29
31
  /* @__PURE__ */ _(m, { className: "mr-1.5 size-4" }),
30
32
  S("bigconsole.widget.aiPreviewMini.previewButton", "Preview"),
31
- w ? /* @__PURE__ */ _(p, { className: "ml-1.5 size-3.5 animate-spin" }) : null
33
+ C ? /* @__PURE__ */ _(p, { className: "ml-1.5 size-3.5 animate-spin" }) : null
32
34
  ]
33
35
  })]
34
36
  });
35
- let I = [];
36
- k.datasink && I.push({
37
+ let F = [];
38
+ O.datasink && F.push({
37
39
  key: "datasink",
38
40
  label: S("bigconsole.widget.aiPreviewMini.link.dataSink", "Data Sink"),
39
- path: `/bigconsole/datasinks/${k.datasink}`,
41
+ path: `/bigconsole/datasinks/${O.datasink}`,
40
42
  icon: /* @__PURE__ */ _(d, {
41
43
  className: "size-4 shrink-0 text-text-muted",
42
44
  "aria-hidden": "true"
43
45
  })
44
- }), k.dashboard && I.push({
46
+ }), O.dashboard && F.push({
45
47
  key: "dashboard",
46
48
  label: S("bigconsole.widget.aiPreviewMini.link.dashboard", "Dashboard"),
47
- path: `/bigconsole/dashboards/${k.dashboard}`,
49
+ path: `/bigconsole/dashboards/${O.dashboard}`,
48
50
  icon: /* @__PURE__ */ _(f, {
49
51
  className: "size-4 shrink-0 text-text-muted",
50
52
  "aria-hidden": "true"
51
53
  })
52
- }), k.parser && I.push({
54
+ }), O.parser && F.push({
53
55
  key: "parser",
54
56
  label: S("bigconsole.widget.aiPreviewMini.link.parser", "Parser"),
55
- path: `/bigconsole/parsers/${k.parser}`,
57
+ path: `/bigconsole/parsers/${O.parser}`,
56
58
  icon: /* @__PURE__ */ _(u, {
57
59
  className: "size-4 shrink-0 text-text-muted",
58
60
  "aria-hidden": "true"
59
61
  })
60
62
  });
61
- let L = t.map((e) => ({
63
+ let I = e.map((e) => ({
62
64
  id: e,
63
65
  label: S(o[e].key, o[e].fallback),
64
- status: O[e]
66
+ status: D[e]
65
67
  }));
66
68
  return /* @__PURE__ */ v("div", {
67
69
  "data-testid": "ai-preview-panel",
@@ -73,20 +75,20 @@ function x({ onNavigate: x }) {
73
75
  children: S("bigconsole.widget.aiPreviewMini.title", "AI Preview")
74
76
  }), /* @__PURE__ */ _(b, {
75
77
  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,
78
+ steps: I,
79
+ caption: C ? k || S("bigconsole.widget.aiPreviewMini.working", "Working…") : S("bigconsole.widget.aiPreviewMini.done", "Done"),
80
+ error: A,
79
81
  meta: /* @__PURE__ */ _(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
+ state: C ? "running" : A ? "stopped" : "idle",
83
+ stateLabel: C ? S("bigconsole.widget.aiPreviewMini.live", "Live") : A ? S("bigconsole.widget.aiPreviewMini.stopped", "Stopped") : null,
82
84
  updatedLabel: S("bigconsole.widget.aiPreviewMini.updated", "updated"),
83
- updatedAt: M,
85
+ updatedAt: j,
84
86
  ago: (e) => s(e, S)
85
87
  }),
86
88
  headerActions: /* @__PURE__ */ _(g, {
87
89
  size: "sm",
88
90
  variant: "ghost",
89
- onClick: P,
91
+ onClick: N,
90
92
  "aria-label": S("bigconsole.widget.aiPreviewMini.closeAriaLabel", "Close preview"),
91
93
  "data-testid": "ai-preview-close",
92
94
  children: /* @__PURE__ */ _(h, { className: "size-4" })
@@ -101,10 +103,10 @@ function x({ onNavigate: x }) {
101
103
  title: S("bigconsole.widget.aiPreviewMini.builtSoFar", "Built so far"),
102
104
  description: S("bigconsole.widget.aiPreviewMini.builtSoFarHint", "Open any piece to see the real thing."),
103
105
  bodyClassName: "flex flex-col gap-list-gap",
104
- children: [I.length === 0 ? /* @__PURE__ */ _("p", {
106
+ children: [F.length === 0 ? /* @__PURE__ */ _("p", {
105
107
  className: "text-sm text-text-muted",
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__ */ v("div", {
108
+ children: C ? S("bigconsole.widget.aiPreviewMini.building", "Building… links appear here as each piece is created.") : S("bigconsole.widget.aiPreviewMini.nothingBuilt", "Nothing built yet.")
109
+ }) : F.map((e) => /* @__PURE__ */ v("div", {
108
110
  "data-testid": "ai-preview-row",
109
111
  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",
110
112
  children: [/* @__PURE__ */ v("p", {
@@ -116,15 +118,15 @@ function x({ onNavigate: x }) {
116
118
  }), /* @__PURE__ */ v(g, {
117
119
  size: "sm",
118
120
  variant: "ghost",
119
- onClick: () => F(e.path),
121
+ onClick: () => P(e.path),
120
122
  children: [S("bigconsole.widget.aiPreviewMini.open", "Open"), /* @__PURE__ */ _(l, { className: "ml-1 size-3.5" })]
121
123
  })]
122
- }, e.key)), k.dashboard ? /* @__PURE__ */ v(g, {
124
+ }, e.key)), O.dashboard ? /* @__PURE__ */ v(g, {
123
125
  size: "sm",
124
126
  variant: "outline",
125
127
  className: "h-auto w-full whitespace-normal py-1.5 text-center",
126
128
  "data-testid": "ai-preview-open-in-bigconsole",
127
- onClick: () => F(`/bigconsole/dashboards/${k.dashboard ?? ""}`),
129
+ onClick: () => P(`/bigconsole/dashboards/${O.dashboard ?? ""}`),
128
130
  children: [S("bigconsole.widget.aiPreviewMini.openInBigConsole", "Open in BigConsole for the live view"), /* @__PURE__ */ _(l, { className: "ml-1 size-3.5" })]
129
131
  }) : null]
130
132
  })
@@ -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 { 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":"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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 { ArrowUpRight, Braces, Database, LayoutDashboard, Loader2, Sparkles, X } from 'lucide-react';\n\nimport { PREVIEW_STEPS, useAssistantRunStore } from '../../assistant/assistantRunStore';\nimport { useAssistantRunWorkspace } from '../../assistant/useAssistantRunWorkspace';\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 // ★★★ THE PAGE SURFACES ARE THE ONLY SURFACES (BOFF-7799). The assistant's\n // controller is headless, so nothing inside the panel competes with this one\n // and there is nothing to stand down for. Binding the workspace restores the\n // run after a reload in this tab and clears it on a workspace switch.\n useAssistantRunWorkspace();\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,7 +1,7 @@
1
1
  import { useBigConsoleNavigate as e } from "../../context/BigConsoleContext.js";
2
2
  import "../../context/index.js";
3
- import { useAssistantPanelShowsPreview as t } from "../../assistant/useAssistantPanelShowsPreview.js";
4
- import { PREVIEW_STEPS as n, useAssistantRunStore as r } from "../../assistant/assistantRunStore.js";
3
+ import { PREVIEW_STEPS as t, useAssistantRunStore as n } from "../../assistant/assistantRunStore.js";
4
+ import { useAssistantRunWorkspace as r } from "../../assistant/useAssistantRunWorkspace.js";
5
5
  import { Section as i } from "../common/Section.js";
6
6
  import "../common/index.js";
7
7
  import { DOCK_CLEARANCE as a, PreviewMeta as o, STEP_LABEL as s, formatAgo as c, previewLabels as l } from "./previewShared.js";
@@ -31,37 +31,39 @@ function D({ onOpen: e }) {
31
31
  });
32
32
  }
33
33
  function O() {
34
- let { t: _ } = w(), O = t(), k = e(), A = r((e) => e.isRunning), j = r((e) => e.previewOpen), M = r((e) => e.hasRun), N = r((e) => e.steps), P = r((e) => e.entityIds), F = r((e) => e.caption), I = r((e) => e.error), L = r((e) => e.updatedAt), R = r((e) => e.openPreview), z = r((e) => e.closePreview), B = r((e) => e.setRichMounted);
35
- m(() => (B(!0), () => {
36
- B(!1);
37
- }), [B]);
38
- let [V, H] = g(0), U = h(!1);
34
+ let { t: _ } = w();
35
+ r();
36
+ let O = e(), k = n((e) => e.isRunning), A = n((e) => e.previewOpen), j = n((e) => e.hasRun), M = n((e) => e.steps), N = n((e) => e.entityIds), P = n((e) => e.caption), F = n((e) => e.error), I = n((e) => e.updatedAt), L = n((e) => e.openPreview), R = n((e) => e.closePreview), z = n((e) => e.setRichMounted);
37
+ m(() => (z(!0), () => {
38
+ z(!1);
39
+ }), [z]);
40
+ let [B, V] = g(0), H = h(!1);
39
41
  if (m(() => {
40
- A && !U.current && H((e) => e + 1), U.current = A;
41
- }, [A]), !M || O) return null;
42
- if (!j) return /* @__PURE__ */ C("div", {
42
+ k && !H.current && V((e) => e + 1), H.current = k;
43
+ }, [k]), !j) return null;
44
+ if (!A) return /* @__PURE__ */ C("div", {
43
45
  className: "flex flex-wrap items-center justify-end gap-control-gap px-4 pt-3 md:px-6",
44
46
  style: { marginRight: a },
45
47
  children: [/* @__PURE__ */ S("span", {
46
48
  className: "text-xs text-text-muted",
47
- children: A ? _("bigconsole.widget.aiPreview.buildRunning", "AI build running") : _("bigconsole.widget.aiPreview.buildFinished", "AI build finished")
49
+ children: k ? _("bigconsole.widget.aiPreview.buildRunning", "AI build running") : _("bigconsole.widget.aiPreview.buildFinished", "AI build finished")
48
50
  }), /* @__PURE__ */ C(x, {
49
51
  size: "sm",
50
52
  variant: "outline",
51
- onClick: R,
53
+ onClick: L,
52
54
  "data-testid": "ai-preview-open",
53
55
  children: [
54
56
  /* @__PURE__ */ S(y, { className: "mr-1.5 size-4" }),
55
57
  _("bigconsole.widget.aiPreview.previewButton", "Preview"),
56
- A ? /* @__PURE__ */ S(v, { className: "ml-1.5 size-3.5 animate-spin" }) : null
58
+ k ? /* @__PURE__ */ S(v, { className: "ml-1.5 size-3.5 animate-spin" }) : null
57
59
  ]
58
60
  })]
59
61
  });
60
- let W = P.dashboard, G = n.map((e) => ({
62
+ let U = N.dashboard, W = t.map((e) => ({
61
63
  id: e,
62
64
  label: _(s[e].key, s[e].fallback),
63
- status: N[e]
64
- })), K = [
65
+ status: M[e]
66
+ })), G = [
65
67
  {
66
68
  id: "datasink",
67
69
  label: _("bigconsole.widget.aiPreview.tab.dataSink", "Data Sink"),
@@ -71,10 +73,10 @@ function O() {
71
73
  headingLevel: 3,
72
74
  title: _("bigconsole.widget.aiPreview.dataSinkRows", "Data sink rows"),
73
75
  description: _("bigconsole.widget.aiPreview.dataSinkRowsHint", "The rows the assistant just landed, straight from the sink."),
74
- actions: P.datasink ? /* @__PURE__ */ S(D, { onOpen: () => k(`/datasinks/${P.datasink}`) }) : void 0,
76
+ actions: N.datasink ? /* @__PURE__ */ S(D, { onOpen: () => O(`/datasinks/${N.datasink}`) }) : void 0,
75
77
  children: /* @__PURE__ */ S(d, {
76
- id: P.datasink,
77
- live: A
78
+ id: N.datasink,
79
+ live: k
78
80
  })
79
81
  }) })
80
82
  },
@@ -87,8 +89,8 @@ function O() {
87
89
  headingLevel: 3,
88
90
  title: _("bigconsole.widget.aiPreview.tab.dashboard", "Dashboard"),
89
91
  description: _("bigconsole.widget.aiPreview.dashboardHint", "The live board, rendered exactly as the real page renders it."),
90
- actions: W ? /* @__PURE__ */ S(D, { onOpen: () => k(`/dashboards/${W}`) }) : void 0,
91
- children: /* @__PURE__ */ S(u, { dashboardId: W })
92
+ actions: U ? /* @__PURE__ */ S(D, { onOpen: () => O(`/dashboards/${U}`) }) : void 0,
93
+ children: /* @__PURE__ */ S(u, { dashboardId: U })
92
94
  }) })
93
95
  },
94
96
  {
@@ -100,10 +102,10 @@ function O() {
100
102
  headingLevel: 3,
101
103
  title: _("bigconsole.widget.aiPreview.parserOutput", "Parser output"),
102
104
  description: _("bigconsole.widget.aiPreview.parserOutputHint", "The rows after the parser's transform, so you can check the shape."),
103
- actions: P.parser ? /* @__PURE__ */ S(D, { onOpen: () => k(`/parsers/${P.parser}`) }) : void 0,
105
+ actions: N.parser ? /* @__PURE__ */ S(D, { onOpen: () => O(`/parsers/${N.parser}`) }) : void 0,
104
106
  children: /* @__PURE__ */ S(f, {
105
- parserId: P.parser,
106
- live: A
107
+ parserId: N.parser,
108
+ live: k
107
109
  })
108
110
  }) })
109
111
  },
@@ -116,10 +118,10 @@ function O() {
116
118
  headingLevel: 3,
117
119
  title: _("bigconsole.widget.aiPreview.widgets", "Widgets"),
118
120
  description: _("bigconsole.widget.aiPreview.widgetsHint", "Every widget on the new dashboard, rendered live."),
119
- actions: W ? /* @__PURE__ */ S(D, { onOpen: () => k(`/dashboards/${W}`) }) : void 0,
121
+ actions: U ? /* @__PURE__ */ S(D, { onOpen: () => O(`/dashboards/${U}`) }) : void 0,
120
122
  children: /* @__PURE__ */ S(p, {
121
- dashboardId: W,
122
- live: A
123
+ dashboardId: U,
124
+ live: k
123
125
  })
124
126
  }) })
125
127
  }
@@ -133,23 +135,23 @@ function O() {
133
135
  children: _("bigconsole.widget.aiPreview.title", "AI Preview")
134
136
  }), /* @__PURE__ */ S(T, {
135
137
  title: _("bigconsole.widget.aiPreview.title", "AI Preview"),
136
- runKey: String(V),
137
- steps: G,
138
- tabs: K,
138
+ runKey: String(B),
139
+ steps: W,
140
+ tabs: G,
139
141
  follow: "produced",
140
- caption: A ? F || _("bigconsole.widget.aiPreview.working", "Working…") : _("bigconsole.widget.aiPreview.done", "Done"),
141
- error: I,
142
+ caption: k ? P || _("bigconsole.widget.aiPreview.working", "Working…") : _("bigconsole.widget.aiPreview.done", "Done"),
143
+ error: F,
142
144
  meta: /* @__PURE__ */ S(o, {
143
- state: A ? "running" : I ? "stopped" : "idle",
144
- stateLabel: A ? _("bigconsole.widget.aiPreview.live", "Live") : I ? _("bigconsole.widget.aiPreview.stopped", "Stopped") : null,
145
+ state: k ? "running" : F ? "stopped" : "idle",
146
+ stateLabel: k ? _("bigconsole.widget.aiPreview.live", "Live") : F ? _("bigconsole.widget.aiPreview.stopped", "Stopped") : null,
145
147
  updatedLabel: _("bigconsole.widget.aiPreview.updated", "updated"),
146
- updatedAt: L,
148
+ updatedAt: I,
147
149
  ago: (e) => c(e, _)
148
150
  }),
149
151
  headerActions: /* @__PURE__ */ S(x, {
150
152
  size: "sm",
151
153
  variant: "ghost",
152
- onClick: z,
154
+ onClick: R,
153
155
  "aria-label": _("bigconsole.widget.aiPreview.closeAriaLabel", "Close preview"),
154
156
  "data-testid": "ai-preview-close",
155
157
  children: /* @__PURE__ */ S(b, { className: "size-4" })
@@ -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 { 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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"}
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 { useAssistantRunWorkspace } from '../../assistant/useAssistantRunWorkspace';\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 // ★★★ THE PAGE SURFACES ARE THE ONLY SURFACES (BOFF-7799). The assistant's\n // controller is headless, so nothing inside the panel competes with this one\n // and there is nothing to stand down for. Binding the workspace restores the\n // run after a reload in this tab and clears it on a workspace switch.\n useAssistantRunWorkspace();\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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"}
@@ -4,7 +4,7 @@ import { jsx as n, jsxs as r } from "react/jsx-runtime";
4
4
  function i(e, t) {
5
5
  return e < 5 ? t("bigconsole.widget.aiPreview.agoJustNow", "just now") : e < 60 ? t("bigconsole.widget.aiPreview.agoSeconds", "{{n}}s ago", { n: e }) : e < 3600 ? t("bigconsole.widget.aiPreview.agoMinutes", "{{n}}m ago", { n: Math.floor(e / 60) }) : t("bigconsole.widget.aiPreview.agoHours", "{{n}}h ago", { n: Math.floor(e / 3600) });
6
6
  }
7
- var a = "var(--assistant-dock-width, 0px)", o = {
7
+ var a = "var(--assistant-panel-inset, var(--assistant-dock-width, 0px))", o = {
8
8
  datasink: {
9
9
  key: "bigconsole.widget.aiPreview.tab.dataSink",
10
10
  fallback: "Data Sink"
@@ -1 +1 @@
1
- {"version":3,"file":"previewShared.js","names":[],"sources":["../../../../src/bigconsole/components/preview/previewShared.tsx"],"sourcesContent":["/**\n * What is still BigConsole's about the AI preview surfaces.\n *\n * ★★★ THE SHELL, DIVIDERS AND STEP RAIL HAVE MOVED TO fe-libs\n * (`AssistantEntityPreview` / `AssistantStepRail`). They existed here so the\n * full panel and the mini panel could not drift apart; the shared component now\n * guarantees that for both, and for every other product that adopts it.\n *\n * What remains is genuinely local: BigConsole's relationship to the assistant\n * DOCK, and how it words a relative timestamp.\n *\n * Nothing here touches Apollo, React Query, BigConsole context — or, since BC4,\n * the DOM: the dock clearance is a CSS custom property fe-libs publishes, so\n * what is left is one constant plus formatting, which is why the mini panel can\n * live in the app shell without dragging the data layer along.\n */\nimport { useEffect, useState } from 'react';\n\nimport type { AssistantEntityPreviewProps } from '@burdenoff/fe-libs/shared/assistant';\n\nimport type { PreviewStepKey } from '../../assistant/assistantRunStore';\n\n/** The `t` both panels already hold — accepted rather than imported, so this\n * file keeps no provider requirement of its own. */\ntype Translate = (key: string, fallback: string, params?: Record<string, string | number>) => string;\n\n/**\n * \"just now\" / \"40s ago\" / \"12m ago\" / \"3h ago\".\n *\n * ★★ TRANSLATED, and it now has an hour bucket. It used to return English in\n * all three locales (en, hi, ta), and a panel left open past an hour read \"240m ago\" rather\n * than \"4h ago\" — both of which were only visible on the surface that outlives\n * a build, which is exactly the one people leave sitting there.\n */\nexport function formatAgo(seconds: number, t: Translate): string {\n if (seconds < 5) return t('bigconsole.widget.aiPreview.agoJustNow', 'just now');\n if (seconds < 60) {\n return t('bigconsole.widget.aiPreview.agoSeconds', '{{n}}s ago', { n: seconds });\n }\n if (seconds < 3600) {\n return t('bigconsole.widget.aiPreview.agoMinutes', '{{n}}m ago', { n: Math.floor(seconds / 60) });\n }\n // ★ `{{n}}`, not `{n}` — fe-libs' single-brace interpolation was broken until\n // 2026.910.7 and shipped raw braces to users across the fleet. Double braces\n // are what this repo already uses (see `…aiPreviewMini.linkReady`).\n return t('bigconsole.widget.aiPreview.agoHours', '{{n}}h ago', { n: Math.floor(seconds / 3600) });\n}\n\n/**\n * Clearance for the assistant dock, as a CSS length.\n *\n * ★★★ THIS REPLACED A 500ms `querySelector` POLL (BC4, BOFF-7337). The old hook\n * measured `[data-testid=\"assistant-widget-window\"][data-layout=\"sidebar\"]` on an\n * interval because the legacy widget lived in fe-libs, was mounted by the app\n * shell, and emitted nothing when it opened or closed — reading the DOM really\n * was the only signal available.\n *\n * The shared overlay publishes its width instead. fe-libs sets\n * `--assistant-dock-width` on `<html>` from\n * `publishedAssistantDockWidth({ isOpen, docked, isDesktop })` — 440px only when\n * the panel is open AND docked AND the viewport is at least 640px — and REMOVES\n * the property on unmount. So the `0px` fallback covers closed, floating,\n * phone-width and absent alike, which is exactly the set of cases the poll\n * hand-rolled, including its `rect.width < innerWidth * 0.75` phone guard.\n *\n * ★★★ THE FALLBACK FAILS OPEN, SO THE VERSIONS ARE COUPLED. A host shell that\n * pins this package but still mounts the LEGACY `AssistantWidget` publishes no\n * variable at all, this resolves to `0px`, and the preview renders underneath a\n * fixed 440px dock where tabs and Open buttons silently stop taking clicks.\n * Nothing throws and no test fails. bigconsole-app therefore bumps this\n * dependency and mounts `AssistantOverlay` in the SAME commit.\n *\n * ★★ A STRING, NOT A HOOK. It needs no React context and no provider ancestry —\n * which matters because the mini panel is mounted by the app shell, outside the\n * assistant's own tree — and the browser recomputes the margin on open/close\n * with no re-render at all.\n *\n * ★ APPLIED TWO WAYS, DELIBERATELY. The collapsed bars carry only `px-*`, so\n * they take it as `marginRight` and a closed dock reproduces the old `0`\n * exactly. The open panels carry `mx-4 md:mx-6`, so they take it as\n * `paddingRight` and leave those margins alone: the old\n * `assistantWidth ? assistantWidth + 16 : undefined` fell through to the\n * RESPONSIVE 24px gutter at md when closed, and folding a flat `+ 16px` into a\n * calc would have quietly shrunk it on every desktop width. Padding keeps the\n * closed state pixel-identical and only adds clearance while docked.\n */\nexport const DOCK_CLEARANCE = 'var(--assistant-dock-width, 0px)';\n\n/**\n * Step labels, reusing the TAB keys.\n *\n * ★★ They used to be English constants in the store (`PREVIEW_STEP_LABEL`), so\n * the rail rendered \"Data Sink / Dashboard / Parser / Widget\" in every locale —\n * directly beside the SAME four names rendered translated as tab labels. The\n * keys already exist and are already seeded; nothing new was needed to fix it.\n *\n * ★ Lives here because BOTH panels render the rail. It was defined twice, which\n * is the exact duplication this migration exists to remove.\n */\nexport const STEP_LABEL: Record<PreviewStepKey, { key: string; fallback: string }> = {\n datasink: { key: 'bigconsole.widget.aiPreview.tab.dataSink', fallback: 'Data Sink' },\n dashboard: { key: 'bigconsole.widget.aiPreview.tab.dashboard', fallback: 'Dashboard' },\n parser: { key: 'bigconsole.widget.aiPreview.tab.parser', fallback: 'Parser' },\n widget: { key: 'bigconsole.widget.aiPreview.tab.widget', fallback: 'Widget' },\n};\n\n/**\n * The shared surface's chrome strings, already translated.\n *\n * ★★ ONE copy, deliberately. BOTH defects this migration produced were the same\n * bug written twice — the failure caption and the reopen clock each had to be\n * fixed in the full panel AND the mini panel — so anything the two pass\n * identically belongs here rather than inlined in each. That is the same reason\n * the shell lived in this file before it moved to fe-libs.\n *\n * ★ Both surfaces use the `aiPreview` namespace, not `aiPreviewMini`: the rail\n * says the same thing wherever it is rendered, and splitting the keys would mean\n * translating \"running\" and \"failed\" twice per locale for no difference.\n *\n * `AssistantEntityPreviewProps['labels']` rather than a hand-written shape, so a\n * new label added upstream is a type error here instead of a silently missing\n * string.\n */\nexport function previewLabels(t: Translate): AssistantEntityPreviewProps['labels'] {\n const buildProgress = t('bigconsole.widget.aiPreview.buildProgress', 'Build progress');\n const skipped = t('bigconsole.widget.aiPreview.stepSkipped', 'skipped');\n return {\n // ★ main named the failure \"Build stopped\" in a hardcoded English <h3>. The\n // shared surface cannot know that word — it takes it as a label — so this\n // both restores the name AND makes it translatable for the first time.\n errorHeading: t('bigconsole.widget.aiPreview.errorHeading', 'Build stopped'),\n steps: buildProgress,\n stepsHeading: buildProgress,\n skipped,\n stepStatus: {\n pending: t('bigconsole.widget.aiPreview.stepPending', 'not started'),\n running: t('bigconsole.widget.aiPreview.stepRunning', 'running'),\n done: t('bigconsole.widget.aiPreview.stepDone', 'completed'),\n failed: t('bigconsole.widget.aiPreview.stepFailed', 'failed'),\n skipped,\n },\n };\n}\n\n/**\n * The status strip both panels hand to the shared surface as `meta`.\n *\n * ★★★ ONE copy, and this one is not a tidiness argument — the SAME DEFECT was\n * written into both panels twice over. The migration changed this chip from\n * `bg-status-success-bg` to `bg-status-success-bgSubtle` in both files, and that\n * utility DOES NOT EXIST: the token is `--color-status-success-bgSubtle` but the\n * Tailwind preset exposes it as `status-success-bg-subtle`. The app shell emits\n * `.bg-status-success-bg-subtle` and never `.bg-status-success-bgSubtle`, so the\n * \"Live\" pill lost its background entirely and became transparent text.\n * Nothing failed: an unknown utility is simply absent from the stylesheet.\n *\n * ★ Fixed with the ARBITRARY-VALUE form, `bg-[var(--color-status-…-bgSubtle)]`,\n * which is what the other ~30 sites in this repo use. It names the token\n * directly, so it cannot be broken by a preset key being spelled differently\n * from the variable — which is the entire mechanism of the bug above.\n *\n * ★★ It also carries the FAILED state, which the caption cannot. The shared\n * surface blanks `caption` whenever `error` is set — deliberately, so a rotating\n * \"Creating…\" cannot sit under a dead run — so a product that wants the failure\n * NAMED (as this one did on main, with \"Stopped\") has to say it somewhere the\n * error does not suppress. `meta` is that place: `PreviewHeader` renders it\n * without consulting `error`.\n *\n * Labels are passed in already translated, so the `t('literal.key', …)` calls\n * stay greppable at each call site and each panel keeps its own namespace.\n */\nexport function PreviewMeta({\n state,\n stateLabel,\n updatedLabel,\n updatedAt,\n ago,\n}: {\n state: 'running' | 'stopped' | 'idle';\n stateLabel: string | null;\n updatedLabel: string;\n /** When the run last changed, or null when there is nothing to age. */\n updatedAt: number | null;\n /** Renders the elapsed seconds — `formatAgo` bound to the caller's `t`. */\n ago: (seconds: number) => string;\n}) {\n /**\n * ★★★ THE CLOCK LIVES HERE, in the one element that displays it.\n *\n * It used to live in each panel, and that made a 1 Hz `setState` on the PANEL\n * — which re-rendered everything below it. `AssistantEntityPreview` calls\n * `activeTab.render()` on every render with no memo, so the Widgets tab's live\n * dashboard and every Recharts widget inside it re-rendered once a second, for\n * as long as the preview stayed open. main did not: its ticker was gated on\n * `isRunning`, so it stopped when the build did. Ungating it to fix the frozen\n * timestamp is right, but it has to cost one <span>, not the whole subtree.\n *\n * ★★ Mounting is also the gate now. The mini panel is mounted on EVERY route\n * and returns null when the full panel is up — but a hook cannot sit below an\n * early return, so its interval ran on every page regardless of whether\n * anything was shown. Owning the timer here means \"not rendered\" is \"not\n * ticking\", by construction rather than by remembering to pass a flag.\n */\n const [now, setNow] = useState(() => Date.now());\n useEffect(() => {\n if (updatedAt === null) return;\n // ★ Re-read on mount so a reopened panel cannot show the previous open's\n // reading for up to a second. See the note on lint in the git history:\n // reading the clock in render is an ESLint error (Date.now is impure).\n setNow(Date.now());\n const id = window.setInterval(() => setNow(Date.now()), 1000);\n return () => window.clearInterval(id);\n }, [updatedAt]);\n\n const agoSeconds = updatedAt === null ? 0 : Math.max(0, Math.floor((now - updatedAt) / 1000));\n\n const chip =\n 'inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide';\n return (\n <span className=\"inline-flex items-center gap-2\" data-testid=\"ai-preview-ago\">\n {state !== 'idle' && stateLabel ? (\n <span\n className={\n state === 'running'\n ? `${chip} bg-[var(--color-status-success-bgSubtle)] text-status-success-text`\n : `${chip} bg-[var(--color-status-error-bgSubtle)] text-status-error-text`\n }\n data-testid={state === 'running' ? 'ai-preview-live' : 'ai-preview-stopped'}\n >\n {state === 'running' ? (\n <span className=\"relative flex size-2\">\n <span className=\"absolute inline-flex size-full rounded-full bg-status-success-text opacity-75 motion-safe:animate-ping\" />\n <span className=\"relative inline-flex size-2 rounded-full bg-status-success-text\" />\n </span>\n ) : (\n /* ★ Still, not pulsing: the ping says \"this is happening now\", which\n is the opposite of what a stopped run means. */\n <span className=\"relative inline-flex size-2 rounded-full bg-status-error-text\" />\n )}\n {stateLabel}\n </span>\n ) : null}\n <span>\n {updatedLabel} {ago(agoSeconds)}\n </span>\n </span>\n );\n}\n"],"mappings":";;;AAkCA,SAAgB,EAAU,GAAiB,GAAsB;AAW/D,QAVI,IAAU,IAAU,EAAE,0CAA0C,WAAW,GAC3E,IAAU,KACL,EAAE,0CAA0C,cAAc,EAAE,GAAG,GAAS,CAAC,GAE9E,IAAU,OACL,EAAE,0CAA0C,cAAc,EAAE,GAAG,KAAK,MAAM,IAAU,GAAG,EAAE,CAAC,GAK5F,EAAE,wCAAwC,cAAc,EAAE,GAAG,KAAK,MAAM,IAAU,KAAK,EAAE,CAAC;;AAyCnG,IAAa,IAAiB,oCAajB,IAAwE;CACnF,UAAU;EAAE,KAAK;EAA4C,UAAU;EAAa;CACpF,WAAW;EAAE,KAAK;EAA6C,UAAU;EAAa;CACtF,QAAQ;EAAE,KAAK;EAA0C,UAAU;EAAU;CAC7E,QAAQ;EAAE,KAAK;EAA0C,UAAU;EAAU;CAC9E;AAmBD,SAAgB,EAAc,GAAqD;CACjF,IAAM,IAAgB,EAAE,6CAA6C,iBAAiB,EAChF,IAAU,EAAE,2CAA2C,UAAU;AACvE,QAAO;EAIL,cAAc,EAAE,4CAA4C,gBAAgB;EAC5E,OAAO;EACP,cAAc;EACd;EACA,YAAY;GACV,SAAS,EAAE,2CAA2C,cAAc;GACpE,SAAS,EAAE,2CAA2C,UAAU;GAChE,MAAM,EAAE,wCAAwC,YAAY;GAC5D,QAAQ,EAAE,0CAA0C,SAAS;GAC7D;GACD;EACF;;AA8BH,SAAgB,EAAY,EAC1B,UACA,eACA,iBACA,cACA,UASC;CAkBD,IAAM,CAAC,GAAK,KAAU,QAAe,KAAK,KAAK,CAAC;AAChD,SAAgB;AACd,MAAI,MAAc,KAAM;AAIxB,IAAO,KAAK,KAAK,CAAC;EAClB,IAAM,IAAK,OAAO,kBAAkB,EAAO,KAAK,KAAK,CAAC,EAAE,IAAK;AAC7D,eAAa,OAAO,cAAc,EAAG;IACpC,CAAC,EAAU,CAAC;CAEf,IAAM,IAAa,MAAc,OAAO,IAAI,KAAK,IAAI,GAAG,KAAK,OAAO,IAAM,KAAa,IAAK,CAAC,EAEvF,IACJ;AACF,QACE,kBAAC,QAAD;EAAM,WAAU;EAAiC,eAAY;YAA7D,CACG,MAAU,UAAU,IACnB,kBAAC,QAAD;GACE,WACE,MAAU,YACN,GAAG,EAAK,uEACR,GAAG,EAAK;GAEd,eAAa,MAAU,YAAY,oBAAoB;aANzD,CAQG,MAAU,YACT,kBAAC,QAAD;IAAM,WAAU;cAAhB,CACE,kBAAC,QAAD,EAAM,WAAU,0GAA2G,CAAA,EAC3H,kBAAC,QAAD,EAAM,WAAU,mEAAoE,CAAA,CAC/E;QAIP,kBAAC,QAAD,EAAM,WAAU,iEAAkE,CAAA,EAEnF,EACI;OACL,MACJ,kBAAC,QAAD,EAAA,UAAA;GACG;GAAa;GAAE,EAAI,EAAW;GAC1B,EAAA,CAAA,CACF"}
1
+ {"version":3,"file":"previewShared.js","names":[],"sources":["../../../../src/bigconsole/components/preview/previewShared.tsx"],"sourcesContent":["/**\n * What is still BigConsole's about the AI preview surfaces.\n *\n * ★★★ THE SHELL, DIVIDERS AND STEP RAIL HAVE MOVED TO fe-libs\n * (`AssistantEntityPreview` / `AssistantStepRail`). They existed here so the\n * full panel and the mini panel could not drift apart; the shared component now\n * guarantees that for both, and for every other product that adopts it.\n *\n * What remains is genuinely local: BigConsole's relationship to the assistant\n * DOCK, and how it words a relative timestamp.\n *\n * Nothing here touches Apollo, React Query, BigConsole context — or, since BC4,\n * the DOM: the dock clearance is a CSS custom property fe-libs publishes, so\n * what is left is one constant plus formatting, which is why the mini panel can\n * live in the app shell without dragging the data layer along.\n */\nimport { useEffect, useState } from 'react';\n\nimport type { AssistantEntityPreviewProps } from '@burdenoff/fe-libs/shared/assistant';\n\nimport type { PreviewStepKey } from '../../assistant/assistantRunStore';\n\n/** The `t` both panels already hold — accepted rather than imported, so this\n * file keeps no provider requirement of its own. */\ntype Translate = (key: string, fallback: string, params?: Record<string, string | number>) => string;\n\n/**\n * \"just now\" / \"40s ago\" / \"12m ago\" / \"3h ago\".\n *\n * ★★ TRANSLATED, and it now has an hour bucket. It used to return English in\n * all three locales (en, hi, ta), and a panel left open past an hour read \"240m ago\" rather\n * than \"4h ago\" — both of which were only visible on the surface that outlives\n * a build, which is exactly the one people leave sitting there.\n */\nexport function formatAgo(seconds: number, t: Translate): string {\n if (seconds < 5) return t('bigconsole.widget.aiPreview.agoJustNow', 'just now');\n if (seconds < 60) {\n return t('bigconsole.widget.aiPreview.agoSeconds', '{{n}}s ago', { n: seconds });\n }\n if (seconds < 3600) {\n return t('bigconsole.widget.aiPreview.agoMinutes', '{{n}}m ago', { n: Math.floor(seconds / 60) });\n }\n // ★ `{{n}}`, not `{n}` — fe-libs' single-brace interpolation was broken until\n // 2026.910.7 and shipped raw braces to users across the fleet. Double braces\n // are what this repo already uses (see `…aiPreviewMini.linkReady`).\n return t('bigconsole.widget.aiPreview.agoHours', '{{n}}h ago', { n: Math.floor(seconds / 3600) });\n}\n\n/**\n * Clearance for the assistant dock, as a CSS length.\n *\n * ★★★ THIS REPLACED A 500ms `querySelector` POLL (BC4, BOFF-7337). The old hook\n * measured `[data-testid=\"assistant-widget-window\"][data-layout=\"sidebar\"]` on an\n * interval because the legacy widget lived in fe-libs, was mounted by the app\n * shell, and emitted nothing when it opened or closed — reading the DOM really\n * was the only signal available.\n *\n * The shared overlay publishes its width instead. fe-libs sets\n * `--assistant-dock-width` on `<html>` from\n * `publishedAssistantDockWidth({ isOpen, docked, isDesktop })` — 440px only when\n * the panel is open AND docked AND the viewport is at least 640px — and REMOVES\n * the property on unmount.\n *\n * ★★★ THE FLOATING PANEL NEEDS CLEARANCE TOO (BOFF-7799, fe-libs 2026.930.3).\n * The dock width is `0px` while the panel FLOATS, which is the default, so\n * reading it alone left the right 45 % of the preview (its close button, its\n * \"Live · updated\" status and the tabs' Open buttons) under the open chat.\n * fe-libs now also publishes `--assistant-panel-inset` from\n * `publishedAssistantPanelInset`: 440px docked, 544px floating (the 520px\n * window plus its 24px right gap), and `0px` closed or below 640px, where the\n * panel is a bottom sheet. It is read FIRST, with the dock width as the fallback\n * for an fe-libs that does not publish it, and `0px` after that for a host with\n * no overlay mounted at all.\n *\n * ★★★ THE FALLBACK FAILS OPEN, SO THE VERSIONS ARE COUPLED. A host shell that\n * pins this package but still mounts the LEGACY `AssistantWidget` publishes no\n * variable at all, this resolves to `0px`, and the preview renders underneath a\n * fixed 440px dock where tabs and Open buttons silently stop taking clicks.\n * Nothing throws and no test fails. bigconsole-app therefore bumps this\n * dependency and mounts `AssistantOverlay` in the SAME commit.\n *\n * ★★ A STRING, NOT A HOOK. It needs no React context and no provider ancestry —\n * which matters because the mini panel is mounted by the app shell, outside the\n * assistant's own tree — and the browser recomputes the margin on open/close\n * with no re-render at all.\n *\n * ★ APPLIED TWO WAYS, DELIBERATELY. The collapsed bars carry only `px-*`, so\n * they take it as `marginRight` and a closed dock reproduces the old `0`\n * exactly. The open panels carry `mx-4 md:mx-6`, so they take it as\n * `paddingRight` and leave those margins alone: the old\n * `assistantWidth ? assistantWidth + 16 : undefined` fell through to the\n * RESPONSIVE 24px gutter at md when closed, and folding a flat `+ 16px` into a\n * calc would have quietly shrunk it on every desktop width. Padding keeps the\n * closed state pixel-identical and only adds clearance while the panel is open.\n */\nexport const DOCK_CLEARANCE = 'var(--assistant-panel-inset, var(--assistant-dock-width, 0px))';\n\n/**\n * Step labels, reusing the TAB keys.\n *\n * ★★ They used to be English constants in the store (`PREVIEW_STEP_LABEL`), so\n * the rail rendered \"Data Sink / Dashboard / Parser / Widget\" in every locale —\n * directly beside the SAME four names rendered translated as tab labels. The\n * keys already exist and are already seeded; nothing new was needed to fix it.\n *\n * ★ Lives here because BOTH panels render the rail. It was defined twice, which\n * is the exact duplication this migration exists to remove.\n */\nexport const STEP_LABEL: Record<PreviewStepKey, { key: string; fallback: string }> = {\n datasink: { key: 'bigconsole.widget.aiPreview.tab.dataSink', fallback: 'Data Sink' },\n dashboard: { key: 'bigconsole.widget.aiPreview.tab.dashboard', fallback: 'Dashboard' },\n parser: { key: 'bigconsole.widget.aiPreview.tab.parser', fallback: 'Parser' },\n widget: { key: 'bigconsole.widget.aiPreview.tab.widget', fallback: 'Widget' },\n};\n\n/**\n * The shared surface's chrome strings, already translated.\n *\n * ★★ ONE copy, deliberately. BOTH defects this migration produced were the same\n * bug written twice — the failure caption and the reopen clock each had to be\n * fixed in the full panel AND the mini panel — so anything the two pass\n * identically belongs here rather than inlined in each. That is the same reason\n * the shell lived in this file before it moved to fe-libs.\n *\n * ★ Both surfaces use the `aiPreview` namespace, not `aiPreviewMini`: the rail\n * says the same thing wherever it is rendered, and splitting the keys would mean\n * translating \"running\" and \"failed\" twice per locale for no difference.\n *\n * `AssistantEntityPreviewProps['labels']` rather than a hand-written shape, so a\n * new label added upstream is a type error here instead of a silently missing\n * string.\n */\nexport function previewLabels(t: Translate): AssistantEntityPreviewProps['labels'] {\n const buildProgress = t('bigconsole.widget.aiPreview.buildProgress', 'Build progress');\n const skipped = t('bigconsole.widget.aiPreview.stepSkipped', 'skipped');\n return {\n // ★ main named the failure \"Build stopped\" in a hardcoded English <h3>. The\n // shared surface cannot know that word — it takes it as a label — so this\n // both restores the name AND makes it translatable for the first time.\n errorHeading: t('bigconsole.widget.aiPreview.errorHeading', 'Build stopped'),\n steps: buildProgress,\n stepsHeading: buildProgress,\n skipped,\n stepStatus: {\n pending: t('bigconsole.widget.aiPreview.stepPending', 'not started'),\n running: t('bigconsole.widget.aiPreview.stepRunning', 'running'),\n done: t('bigconsole.widget.aiPreview.stepDone', 'completed'),\n failed: t('bigconsole.widget.aiPreview.stepFailed', 'failed'),\n skipped,\n },\n };\n}\n\n/**\n * The status strip both panels hand to the shared surface as `meta`.\n *\n * ★★★ ONE copy, and this one is not a tidiness argument — the SAME DEFECT was\n * written into both panels twice over. The migration changed this chip from\n * `bg-status-success-bg` to `bg-status-success-bgSubtle` in both files, and that\n * utility DOES NOT EXIST: the token is `--color-status-success-bgSubtle` but the\n * Tailwind preset exposes it as `status-success-bg-subtle`. The app shell emits\n * `.bg-status-success-bg-subtle` and never `.bg-status-success-bgSubtle`, so the\n * \"Live\" pill lost its background entirely and became transparent text.\n * Nothing failed: an unknown utility is simply absent from the stylesheet.\n *\n * ★ Fixed with the ARBITRARY-VALUE form, `bg-[var(--color-status-…-bgSubtle)]`,\n * which is what the other ~30 sites in this repo use. It names the token\n * directly, so it cannot be broken by a preset key being spelled differently\n * from the variable — which is the entire mechanism of the bug above.\n *\n * ★★ It also carries the FAILED state, which the caption cannot. The shared\n * surface blanks `caption` whenever `error` is set — deliberately, so a rotating\n * \"Creating…\" cannot sit under a dead run — so a product that wants the failure\n * NAMED (as this one did on main, with \"Stopped\") has to say it somewhere the\n * error does not suppress. `meta` is that place: `PreviewHeader` renders it\n * without consulting `error`.\n *\n * Labels are passed in already translated, so the `t('literal.key', …)` calls\n * stay greppable at each call site and each panel keeps its own namespace.\n */\nexport function PreviewMeta({\n state,\n stateLabel,\n updatedLabel,\n updatedAt,\n ago,\n}: {\n state: 'running' | 'stopped' | 'idle';\n stateLabel: string | null;\n updatedLabel: string;\n /** When the run last changed, or null when there is nothing to age. */\n updatedAt: number | null;\n /** Renders the elapsed seconds — `formatAgo` bound to the caller's `t`. */\n ago: (seconds: number) => string;\n}) {\n /**\n * ★★★ THE CLOCK LIVES HERE, in the one element that displays it.\n *\n * It used to live in each panel, and that made a 1 Hz `setState` on the PANEL\n * — which re-rendered everything below it. `AssistantEntityPreview` calls\n * `activeTab.render()` on every render with no memo, so the Widgets tab's live\n * dashboard and every Recharts widget inside it re-rendered once a second, for\n * as long as the preview stayed open. main did not: its ticker was gated on\n * `isRunning`, so it stopped when the build did. Ungating it to fix the frozen\n * timestamp is right, but it has to cost one <span>, not the whole subtree.\n *\n * ★★ Mounting is also the gate now. The mini panel is mounted on EVERY route\n * and returns null when the full panel is up — but a hook cannot sit below an\n * early return, so its interval ran on every page regardless of whether\n * anything was shown. Owning the timer here means \"not rendered\" is \"not\n * ticking\", by construction rather than by remembering to pass a flag.\n */\n const [now, setNow] = useState(() => Date.now());\n useEffect(() => {\n if (updatedAt === null) return;\n // ★ Re-read on mount so a reopened panel cannot show the previous open's\n // reading for up to a second. See the note on lint in the git history:\n // reading the clock in render is an ESLint error (Date.now is impure).\n setNow(Date.now());\n const id = window.setInterval(() => setNow(Date.now()), 1000);\n return () => window.clearInterval(id);\n }, [updatedAt]);\n\n const agoSeconds = updatedAt === null ? 0 : Math.max(0, Math.floor((now - updatedAt) / 1000));\n\n const chip =\n 'inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide';\n return (\n <span className=\"inline-flex items-center gap-2\" data-testid=\"ai-preview-ago\">\n {state !== 'idle' && stateLabel ? (\n <span\n className={\n state === 'running'\n ? `${chip} bg-[var(--color-status-success-bgSubtle)] text-status-success-text`\n : `${chip} bg-[var(--color-status-error-bgSubtle)] text-status-error-text`\n }\n data-testid={state === 'running' ? 'ai-preview-live' : 'ai-preview-stopped'}\n >\n {state === 'running' ? (\n <span className=\"relative flex size-2\">\n <span className=\"absolute inline-flex size-full rounded-full bg-status-success-text opacity-75 motion-safe:animate-ping\" />\n <span className=\"relative inline-flex size-2 rounded-full bg-status-success-text\" />\n </span>\n ) : (\n /* ★ Still, not pulsing: the ping says \"this is happening now\", which\n is the opposite of what a stopped run means. */\n <span className=\"relative inline-flex size-2 rounded-full bg-status-error-text\" />\n )}\n {stateLabel}\n </span>\n ) : null}\n <span>\n {updatedLabel} {ago(agoSeconds)}\n </span>\n </span>\n );\n}\n"],"mappings":";;;AAkCA,SAAgB,EAAU,GAAiB,GAAsB;AAW/D,QAVI,IAAU,IAAU,EAAE,0CAA0C,WAAW,GAC3E,IAAU,KACL,EAAE,0CAA0C,cAAc,EAAE,GAAG,GAAS,CAAC,GAE9E,IAAU,OACL,EAAE,0CAA0C,cAAc,EAAE,GAAG,KAAK,MAAM,IAAU,GAAG,EAAE,CAAC,GAK5F,EAAE,wCAAwC,cAAc,EAAE,GAAG,KAAK,MAAM,IAAU,KAAK,EAAE,CAAC;;AAkDnG,IAAa,IAAiB,kEAajB,IAAwE;CACnF,UAAU;EAAE,KAAK;EAA4C,UAAU;EAAa;CACpF,WAAW;EAAE,KAAK;EAA6C,UAAU;EAAa;CACtF,QAAQ;EAAE,KAAK;EAA0C,UAAU;EAAU;CAC7E,QAAQ;EAAE,KAAK;EAA0C,UAAU;EAAU;CAC9E;AAmBD,SAAgB,EAAc,GAAqD;CACjF,IAAM,IAAgB,EAAE,6CAA6C,iBAAiB,EAChF,IAAU,EAAE,2CAA2C,UAAU;AACvE,QAAO;EAIL,cAAc,EAAE,4CAA4C,gBAAgB;EAC5E,OAAO;EACP,cAAc;EACd;EACA,YAAY;GACV,SAAS,EAAE,2CAA2C,cAAc;GACpE,SAAS,EAAE,2CAA2C,UAAU;GAChE,MAAM,EAAE,wCAAwC,YAAY;GAC5D,QAAQ,EAAE,0CAA0C,SAAS;GAC7D;GACD;EACF;;AA8BH,SAAgB,EAAY,EAC1B,UACA,eACA,iBACA,cACA,UASC;CAkBD,IAAM,CAAC,GAAK,KAAU,QAAe,KAAK,KAAK,CAAC;AAChD,SAAgB;AACd,MAAI,MAAc,KAAM;AAIxB,IAAO,KAAK,KAAK,CAAC;EAClB,IAAM,IAAK,OAAO,kBAAkB,EAAO,KAAK,KAAK,CAAC,EAAE,IAAK;AAC7D,eAAa,OAAO,cAAc,EAAG;IACpC,CAAC,EAAU,CAAC;CAEf,IAAM,IAAa,MAAc,OAAO,IAAI,KAAK,IAAI,GAAG,KAAK,OAAO,IAAM,KAAa,IAAK,CAAC,EAEvF,IACJ;AACF,QACE,kBAAC,QAAD;EAAM,WAAU;EAAiC,eAAY;YAA7D,CACG,MAAU,UAAU,IACnB,kBAAC,QAAD;GACE,WACE,MAAU,YACN,GAAG,EAAK,uEACR,GAAG,EAAK;GAEd,eAAa,MAAU,YAAY,oBAAoB;aANzD,CAQG,MAAU,YACT,kBAAC,QAAD;IAAM,WAAU;cAAhB,CACE,kBAAC,QAAD,EAAM,WAAU,0GAA2G,CAAA,EAC3H,kBAAC,QAAD,EAAM,WAAU,mEAAoE,CAAA,CAC/E;QAIP,kBAAC,QAAD,EAAM,WAAU,iEAAkE,CAAA,EAEnF,EACI;OACL,MACJ,kBAAC,QAAD,EAAA,UAAA;GACG;GAAa;GAAE,EAAI,EAAW;GAC1B,EAAA,CAAA,CACF"}