@burdenoff/microfe-bigconsole 2026.830.1 → 2026.912.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.
@@ -9,7 +9,7 @@ import "./context/index.js";
9
9
  import { BigConsoleQuotaProvider as c } from "./providers/BigConsoleQuotaProvider.js";
10
10
  import l from "./hooks/useFilterUrlSync.js";
11
11
  import ee from "./components/DrilldownDashboardRenderer.js";
12
- import u from "./components/preview/AiPreviewPanel.js";
12
+ import { AiPreviewPanel as u } from "./components/preview/AiPreviewPanel.js";
13
13
  import d from "./components/DrilldownModal.js";
14
14
  import { useCallback as f, useEffect as p, useLayoutEffect as m, useRef as h, useState as g } from "react";
15
15
  import { useSearchParams as _ } from "react-router";
@@ -6,20 +6,15 @@ var t = [
6
6
  "parser",
7
7
  "widget"
8
8
  ], n = {
9
- datasink: "Data Sink",
10
- dashboard: "Dashboard",
11
- parser: "Parser",
12
- widget: "Widget"
13
- }, r = {
14
9
  datasink: "pending",
15
10
  dashboard: "pending",
16
11
  parser: "pending",
17
12
  widget: "pending"
18
- }, i = /\/bigconsole\/(datasinks|dashboards|parsers)\/([A-Za-z0-9_-]+)/g, a = /widget\s+(added|created)/i, o = /^⏳\s*(.+?)…?$/, s = {
13
+ }, r = /\/bigconsole\/(datasinks|dashboards|parsers)\/([A-Za-z0-9_-]+)/g, i = /widget\s+(added|created)/i, a = /^⏳\s*(.+?)…?$/, o = {
19
14
  datasinks: "datasink",
20
15
  dashboards: "dashboard",
21
16
  parsers: "parser"
22
- }, c = [
17
+ }, s = [
23
18
  {
24
19
  step: "datasink",
25
20
  re: /"createDataSink"\s*:\s*\{[^{}]*?"id"\s*:\s*"([A-Za-z0-9_-]+)"/
@@ -32,8 +27,8 @@ var t = [
32
27
  step: "parser",
33
28
  re: /"createParser"\s*:\s*\{[^{}]*?"id"\s*:\s*"([A-Za-z0-9_-]+)"/
34
29
  }
35
- ], l = /"createBigConsoleWidget"\s*:\s*\{/;
36
- function u(e, t) {
30
+ ], c = /"createBigConsoleWidget"\s*:\s*\{/;
31
+ function l(e, t) {
37
32
  let n = {
38
33
  pending: 0,
39
34
  running: 1,
@@ -42,13 +37,13 @@ function u(e, t) {
42
37
  };
43
38
  return n[t] > n[e] ? t : e;
44
39
  }
45
- var d = e()((e) => ({
40
+ var u = e()((e) => ({
46
41
  isRunning: !1,
47
42
  previewOpen: !1,
48
43
  hasRun: !1,
49
44
  richMounted: !1,
50
45
  prompt: "",
51
- steps: { ...r },
46
+ steps: { ...n },
52
47
  entityIds: {},
53
48
  caption: "",
54
49
  error: null,
@@ -69,23 +64,23 @@ var d = e()((e) => ({
69
64
  };
70
65
  }),
71
66
  applyProgress: (n) => e((e) => {
72
- let r = { ...e.steps }, d = { ...e.entityIds }, f = (e, t) => {
73
- r[e] = u(r[e], "done"), t && e !== "widget" && (d[e] = t);
67
+ let u = { ...e.steps }, d = { ...e.entityIds }, f = (e, t) => {
68
+ u[e] = l(u[e], "done"), t && e !== "widget" && (d[e] = t);
74
69
  };
75
- for (let e of n.matchAll(i)) {
76
- let t = s[e[1] ?? ""];
70
+ for (let e of n.matchAll(r)) {
71
+ let t = o[e[1] ?? ""];
77
72
  t && e[2] && f(t, e[2]);
78
73
  }
79
- for (let { step: e, re: t } of c) {
74
+ for (let { step: e, re: t } of s) {
80
75
  let r = t.exec(n);
81
76
  r?.[1] && f(e, r[1]);
82
77
  }
83
- (l.test(n) || a.test(n)) && (r.widget = u(r.widget, "done"), r.parser !== "done" && (r.parser = u(r.parser, "skipped")));
84
- let p = t.find((e) => r[e] === "pending" || r[e] === "running");
85
- return p && (r[p] = u(r[p], "running")), {
86
- steps: r,
78
+ (c.test(n) || i.test(n)) && (u.widget = l(u.widget, "done"), u.parser !== "done" && (u.parser = l(u.parser, "skipped")));
79
+ let p = t.find((e) => u[e] === "pending" || u[e] === "running");
80
+ return p && (u[p] = l(u[p], "running")), {
81
+ steps: u,
87
82
  entityIds: d,
88
- caption: n.split("\n").map((e) => e.trim()).reverse().map((e) => o.exec(e)?.[1]).find((e) => !!e) ?? e.caption,
83
+ caption: n.split("\n").map((e) => e.trim()).reverse().map((e) => a.exec(e)?.[1]).find((e) => !!e) ?? e.caption,
89
84
  updatedAt: Date.now()
90
85
  };
91
86
  }),
@@ -105,6 +100,6 @@ var d = e()((e) => ({
105
100
  setRichMounted: (t) => e({ richMounted: t })
106
101
  }));
107
102
  //#endregion
108
- export { t as PREVIEW_STEPS, n as PREVIEW_STEP_LABEL, d as useAssistantRunStore };
103
+ export { t as PREVIEW_STEPS, u as useAssistantRunStore };
109
104
 
110
105
  //# sourceMappingURL=assistantRunStore.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"assistantRunStore.js","names":[],"sources":["../../../src/bigconsole/assistant/assistantRunStore.ts"],"sourcesContent":["/**\n * What the assistant is building, right now.\n *\n * The agent's narration is the only live signal the frontend gets — there is no\n * event stream — so this store turns it into something a UI can render: a\n * monotone DataSink → Dashboard → Parser → Widget rail, plus the id of each\n * entity as it appears.\n *\n * Three rules make the parsing survive a model that phrases things differently:\n *\n * 1. Read the id from BOTH the link path (`/bigconsole/datasinks/<id>`) AND the\n * raw mutation JSON (`{ \"createDataSink\": { \"id\": \"<uuid>\", … } }`). The agent\n * freely prints one, the other, or both — if we only watched for the link a\n * turn that printed just JSON would leave the tab stuck on \"Waiting…\".\n * 2. Never regress. `progress.content` is REPLACED on every poll (not appended),\n * so a shorter string arriving later must not rewind the rail.\n * 3. Accumulate across an incremental conversation. Each prompt builds one piece;\n * a new prompt must NOT wipe the pieces already built, or the earlier tabs go\n * blank. `startRun` preserves prior steps/ids and only lights up the next one.\n */\nimport { create } from 'zustand';\n\nexport type PreviewStepKey = 'datasink' | 'dashboard' | 'parser' | 'widget';\nexport type PreviewStepStatus = 'pending' | 'running' | 'done' | 'skipped';\n\nexport const PREVIEW_STEPS: readonly PreviewStepKey[] = ['datasink', 'dashboard', 'parser', 'widget'];\n\nexport const PREVIEW_STEP_LABEL: Record<PreviewStepKey, string> = {\n datasink: 'Data Sink',\n dashboard: 'Dashboard',\n parser: 'Parser',\n widget: 'Widget',\n};\n\ninterface AssistantRunState {\n /** A turn is in flight. */\n isRunning: boolean;\n /** The panel is on screen. Auto-opens when a turn starts; the user can close it. */\n previewOpen: boolean;\n /** True once any turn has run — gates the \"Preview\" button. */\n hasRun: boolean;\n /**\n * The full, context-rich panel (mounted inside BigConsoleRoot, with live entity\n * tabs) is on screen right now. The lightweight app-shell panel reads this and\n * steps aside so the two never render at once. It flips false the instant you\n * leave a /bigconsole page — that is what lets the mini panel take over and keep\n * the build visible on whatever screen you moved to.\n */\n richMounted: boolean;\n prompt: string;\n steps: Record<PreviewStepKey, PreviewStepStatus>;\n /** Ids harvested from the agent's links. A widget has no link of its own. */\n entityIds: { datasink?: string; dashboard?: string; parser?: string };\n /** Newest \"⏳ …\" line — what the agent says it is doing this second. */\n caption: string;\n /** Set when the turn fails, so the panel can stop pretending to be live. */\n error: string | null;\n /** Epoch ms of the last progress tick — drives the \"updated 3s ago\" strip. */\n updatedAt: number | null;\n\n startRun: (prompt: string) => void;\n applyProgress: (content: string) => void;\n finishRun: (error: string | null) => void;\n openPreview: () => void;\n closePreview: () => void;\n setRichMounted: (mounted: boolean) => void;\n}\n\nconst IDLE_STEPS: Record<PreviewStepKey, PreviewStepStatus> = {\n datasink: 'pending',\n dashboard: 'pending',\n parser: 'pending',\n widget: 'pending',\n};\n\n/** `/bigconsole/datasinks/<id>` — the trustworthy part of the narration. */\nconst ENTITY_LINK = /\\/bigconsole\\/(datasinks|dashboards|parsers)\\/([A-Za-z0-9_-]+)/g;\n/** The widget links to its dashboard, so it can only be detected from the prose. */\nconst WIDGET_DONE = /widget\\s+(added|created)/i;\nconst RUNNING_LINE = /^⏳\\s*(.+?)…?$/;\n\nconst LINK_TO_STEP: Record<string, PreviewStepKey> = {\n datasinks: 'datasink',\n dashboards: 'dashboard',\n parsers: 'parser',\n};\n\n/**\n * The id straight out of a raw mutation response the agent printed instead of\n * (or as well as) a link, e.g. `{ \"createDataSink\": { \"id\": \"<uuid>\", … } }`.\n * `[^{}]` keeps each match inside its own object so we grab that mutation's id,\n * not a neighbour's. This is what stops \"Waiting…\" when no link is narrated.\n */\nconst JSON_ID_STEPS: ReadonlyArray<{ step: Exclude<PreviewStepKey, 'widget'>; re: RegExp }> = [\n { step: 'datasink', re: /\"createDataSink\"\\s*:\\s*\\{[^{}]*?\"id\"\\s*:\\s*\"([A-Za-z0-9_-]+)\"/ },\n { step: 'dashboard', re: /\"createBigConsoleDashboard\"\\s*:\\s*\\{[^{}]*?\"id\"\\s*:\\s*\"([A-Za-z0-9_-]+)\"/ },\n { step: 'parser', re: /\"createParser\"\\s*:\\s*\\{[^{}]*?\"id\"\\s*:\\s*\"([A-Za-z0-9_-]+)\"/ },\n];\n/** The widget has no page of its own; its create response is enough to mark it done. */\nconst WIDGET_CREATE_JSON = /\"createBigConsoleWidget\"\\s*:\\s*\\{/;\n\n/** Only ever moves a step forward. */\nfunction advance(current: PreviewStepStatus, next: PreviewStepStatus): PreviewStepStatus {\n const rank: Record<PreviewStepStatus, number> = { pending: 0, running: 1, skipped: 2, done: 3 };\n return rank[next] > rank[current] ? next : current;\n}\n\nexport const useAssistantRunStore = create<AssistantRunState>()((set) => ({\n isRunning: false,\n previewOpen: false,\n hasRun: false,\n richMounted: false,\n prompt: '',\n steps: { ...IDLE_STEPS },\n entityIds: {},\n caption: '',\n error: null,\n updatedAt: null,\n\n startRun: (prompt) =>\n set((state) => {\n // A new prompt CONTINUES the same use case (incremental flow) — keep every\n // step and id already built. Clear any stale spinner from the prior turn,\n // then light up the earliest step that has not landed yet so the \"working\n // on it\" indicator sits on the right tab.\n const steps = { ...state.steps };\n for (const key of PREVIEW_STEPS) {\n if (steps[key] === 'running') steps[key] = 'pending';\n }\n const next = PREVIEW_STEPS.find((key) => steps[key] === 'pending');\n if (next) steps[next] = 'running';\n return {\n isRunning: true,\n // Auto-open: the whole point is that you SEE the build happen without\n // having to go looking for it.\n previewOpen: true,\n hasRun: true,\n prompt,\n steps,\n // entityIds intentionally preserved (merged) so earlier tabs keep content.\n caption: '',\n error: null,\n updatedAt: Date.now(),\n };\n }),\n\n applyProgress: (content) =>\n set((state) => {\n const steps = { ...state.steps };\n const entityIds = { ...state.entityIds };\n\n const markDone = (step: PreviewStepKey, id?: string) => {\n steps[step] = advance(steps[step], 'done');\n // Always record the freshest id seen this turn (never \"first wins\"): an\n // incremental prompt that rebuilds a piece must point the tab at the new\n // one. `step !== 'widget'` narrows to the keys entityIds actually holds.\n if (id && step !== 'widget') entityIds[step] = id;\n };\n\n // 1) Markdown link paths.\n for (const match of content.matchAll(ENTITY_LINK)) {\n const step = LINK_TO_STEP[match[1] ?? ''];\n if (step && match[2]) markDone(step, match[2]);\n }\n // 2) Raw mutation JSON (the agent often prints this instead of a link).\n for (const { step, re } of JSON_ID_STEPS) {\n const match = re.exec(content);\n if (match?.[1]) markDone(step, match[1]);\n }\n\n if (WIDGET_CREATE_JSON.test(content) || WIDGET_DONE.test(content)) {\n steps.widget = advance(steps.widget, 'done');\n // The parser is optional — the agent skips it when the rows are already\n // shaped. If the widget landed and the parser was never built, it was\n // skipped, not stuck.\n if (steps.parser !== 'done') steps.parser = advance(steps.parser, 'skipped');\n }\n\n // The first step that has not finished is the one being worked on.\n const active = PREVIEW_STEPS.find((key) => steps[key] === 'pending' || steps[key] === 'running');\n if (active) steps[active] = advance(steps[active], 'running');\n\n const caption =\n content\n .split('\\n')\n .map((line) => line.trim())\n .reverse()\n .map((line) => RUNNING_LINE.exec(line)?.[1])\n .find((label): label is string => Boolean(label)) ?? state.caption;\n\n return { steps, entityIds, caption, updatedAt: Date.now() };\n }),\n\n finishRun: (error) =>\n set((state) => {\n // The turn is over — stop the live spinner. A step still 'running' didn't\n // land this turn, so drop it back to 'pending' (a later prompt can pick it\n // up). NEVER blanket-skip pending steps: in the incremental flow the later\n // steps simply have not been asked for yet, and 'done' steps must stay done.\n const steps = { ...state.steps };\n for (const key of PREVIEW_STEPS) {\n if (steps[key] === 'running') steps[key] = 'pending';\n }\n return { isRunning: false, error, steps, caption: '', updatedAt: Date.now() };\n }),\n\n openPreview: () => set({ previewOpen: true }),\n closePreview: () => set({ previewOpen: false }),\n setRichMounted: (mounted) => set({ richMounted: mounted }),\n}));\n"],"mappings":"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"}
1
+ {"version":3,"file":"assistantRunStore.js","names":[],"sources":["../../../src/bigconsole/assistant/assistantRunStore.ts"],"sourcesContent":["/**\n * What the assistant is building, right now.\n *\n * The agent's narration is the only live signal the frontend gets — there is no\n * event stream — so this store turns it into something a UI can render: a\n * monotone DataSink → Dashboard → Parser → Widget rail, plus the id of each\n * entity as it appears.\n *\n * Three rules make the parsing survive a model that phrases things differently:\n *\n * 1. Read the id from BOTH the link path (`/bigconsole/datasinks/<id>`) AND the\n * raw mutation JSON (`{ \"createDataSink\": { \"id\": \"<uuid>\", … } }`). The agent\n * freely prints one, the other, or both — if we only watched for the link a\n * turn that printed just JSON would leave the tab stuck on \"Waiting…\".\n * 2. Never regress. `progress.content` is REPLACED on every poll (not appended),\n * so a shorter string arriving later must not rewind the rail.\n * 3. Accumulate across an incremental conversation. Each prompt builds one piece;\n * a new prompt must NOT wipe the pieces already built, or the earlier tabs go\n * blank. `startRun` preserves prior steps/ids and only lights up the next one.\n */\nimport { create } from 'zustand';\n\nexport type PreviewStepKey = 'datasink' | 'dashboard' | 'parser' | 'widget';\nexport type PreviewStepStatus = 'pending' | 'running' | 'done' | 'skipped';\n\nexport const PREVIEW_STEPS: readonly PreviewStepKey[] = ['datasink', 'dashboard', 'parser', 'widget'];\n\ninterface AssistantRunState {\n /** A turn is in flight. */\n isRunning: boolean;\n /** The panel is on screen. Auto-opens when a turn starts; the user can close it. */\n previewOpen: boolean;\n /** True once any turn has run — gates the \"Preview\" button. */\n hasRun: boolean;\n /**\n * The full, context-rich panel (mounted inside BigConsoleRoot, with live entity\n * tabs) is on screen right now. The lightweight app-shell panel reads this and\n * steps aside so the two never render at once. It flips false the instant you\n * leave a /bigconsole page — that is what lets the mini panel take over and keep\n * the build visible on whatever screen you moved to.\n */\n richMounted: boolean;\n prompt: string;\n steps: Record<PreviewStepKey, PreviewStepStatus>;\n /** Ids harvested from the agent's links. A widget has no link of its own. */\n entityIds: { datasink?: string; dashboard?: string; parser?: string };\n /** Newest \"⏳ …\" line — what the agent says it is doing this second. */\n caption: string;\n /** Set when the turn fails, so the panel can stop pretending to be live. */\n error: string | null;\n /** Epoch ms of the last progress tick — drives the \"updated 3s ago\" strip. */\n updatedAt: number | null;\n\n startRun: (prompt: string) => void;\n applyProgress: (content: string) => void;\n finishRun: (error: string | null) => void;\n openPreview: () => void;\n closePreview: () => void;\n setRichMounted: (mounted: boolean) => void;\n}\n\nconst IDLE_STEPS: Record<PreviewStepKey, PreviewStepStatus> = {\n datasink: 'pending',\n dashboard: 'pending',\n parser: 'pending',\n widget: 'pending',\n};\n\n/** `/bigconsole/datasinks/<id>` — the trustworthy part of the narration. */\nconst ENTITY_LINK = /\\/bigconsole\\/(datasinks|dashboards|parsers)\\/([A-Za-z0-9_-]+)/g;\n/** The widget links to its dashboard, so it can only be detected from the prose. */\nconst WIDGET_DONE = /widget\\s+(added|created)/i;\nconst RUNNING_LINE = /^⏳\\s*(.+?)…?$/;\n\nconst LINK_TO_STEP: Record<string, PreviewStepKey> = {\n datasinks: 'datasink',\n dashboards: 'dashboard',\n parsers: 'parser',\n};\n\n/**\n * The id straight out of a raw mutation response the agent printed instead of\n * (or as well as) a link, e.g. `{ \"createDataSink\": { \"id\": \"<uuid>\", … } }`.\n * `[^{}]` keeps each match inside its own object so we grab that mutation's id,\n * not a neighbour's. This is what stops \"Waiting…\" when no link is narrated.\n */\nconst JSON_ID_STEPS: ReadonlyArray<{ step: Exclude<PreviewStepKey, 'widget'>; re: RegExp }> = [\n { step: 'datasink', re: /\"createDataSink\"\\s*:\\s*\\{[^{}]*?\"id\"\\s*:\\s*\"([A-Za-z0-9_-]+)\"/ },\n { step: 'dashboard', re: /\"createBigConsoleDashboard\"\\s*:\\s*\\{[^{}]*?\"id\"\\s*:\\s*\"([A-Za-z0-9_-]+)\"/ },\n { step: 'parser', re: /\"createParser\"\\s*:\\s*\\{[^{}]*?\"id\"\\s*:\\s*\"([A-Za-z0-9_-]+)\"/ },\n];\n/** The widget has no page of its own; its create response is enough to mark it done. */\nconst WIDGET_CREATE_JSON = /\"createBigConsoleWidget\"\\s*:\\s*\\{/;\n\n/** Only ever moves a step forward. */\nfunction advance(current: PreviewStepStatus, next: PreviewStepStatus): PreviewStepStatus {\n const rank: Record<PreviewStepStatus, number> = { pending: 0, running: 1, skipped: 2, done: 3 };\n return rank[next] > rank[current] ? next : current;\n}\n\nexport const useAssistantRunStore = create<AssistantRunState>()((set) => ({\n isRunning: false,\n previewOpen: false,\n hasRun: false,\n richMounted: false,\n prompt: '',\n steps: { ...IDLE_STEPS },\n entityIds: {},\n caption: '',\n error: null,\n updatedAt: null,\n\n startRun: (prompt) =>\n set((state) => {\n // A new prompt CONTINUES the same use case (incremental flow) — keep every\n // step and id already built. Clear any stale spinner from the prior turn,\n // then light up the earliest step that has not landed yet so the \"working\n // on it\" indicator sits on the right tab.\n const steps = { ...state.steps };\n for (const key of PREVIEW_STEPS) {\n if (steps[key] === 'running') steps[key] = 'pending';\n }\n const next = PREVIEW_STEPS.find((key) => steps[key] === 'pending');\n if (next) steps[next] = 'running';\n return {\n isRunning: true,\n // Auto-open: the whole point is that you SEE the build happen without\n // having to go looking for it.\n previewOpen: true,\n hasRun: true,\n prompt,\n steps,\n // entityIds intentionally preserved (merged) so earlier tabs keep content.\n caption: '',\n error: null,\n updatedAt: Date.now(),\n };\n }),\n\n applyProgress: (content) =>\n set((state) => {\n const steps = { ...state.steps };\n const entityIds = { ...state.entityIds };\n\n const markDone = (step: PreviewStepKey, id?: string) => {\n steps[step] = advance(steps[step], 'done');\n // Always record the freshest id seen this turn (never \"first wins\"): an\n // incremental prompt that rebuilds a piece must point the tab at the new\n // one. `step !== 'widget'` narrows to the keys entityIds actually holds.\n if (id && step !== 'widget') entityIds[step] = id;\n };\n\n // 1) Markdown link paths.\n for (const match of content.matchAll(ENTITY_LINK)) {\n const step = LINK_TO_STEP[match[1] ?? ''];\n if (step && match[2]) markDone(step, match[2]);\n }\n // 2) Raw mutation JSON (the agent often prints this instead of a link).\n for (const { step, re } of JSON_ID_STEPS) {\n const match = re.exec(content);\n if (match?.[1]) markDone(step, match[1]);\n }\n\n if (WIDGET_CREATE_JSON.test(content) || WIDGET_DONE.test(content)) {\n steps.widget = advance(steps.widget, 'done');\n // The parser is optional — the agent skips it when the rows are already\n // shaped. If the widget landed and the parser was never built, it was\n // skipped, not stuck.\n if (steps.parser !== 'done') steps.parser = advance(steps.parser, 'skipped');\n }\n\n // The first step that has not finished is the one being worked on.\n const active = PREVIEW_STEPS.find((key) => steps[key] === 'pending' || steps[key] === 'running');\n if (active) steps[active] = advance(steps[active], 'running');\n\n const caption =\n content\n .split('\\n')\n .map((line) => line.trim())\n .reverse()\n .map((line) => RUNNING_LINE.exec(line)?.[1])\n .find((label): label is string => Boolean(label)) ?? state.caption;\n\n return { steps, entityIds, caption, updatedAt: Date.now() };\n }),\n\n finishRun: (error) =>\n set((state) => {\n // The turn is over — stop the live spinner. A step still 'running' didn't\n // land this turn, so drop it back to 'pending' (a later prompt can pick it\n // up). NEVER blanket-skip pending steps: in the incremental flow the later\n // steps simply have not been asked for yet, and 'done' steps must stay done.\n const steps = { ...state.steps };\n for (const key of PREVIEW_STEPS) {\n if (steps[key] === 'running') steps[key] = 'pending';\n }\n return { isRunning: false, error, steps, caption: '', updatedAt: Date.now() };\n }),\n\n openPreview: () => set({ previewOpen: true }),\n closePreview: () => set({ previewOpen: false }),\n setRichMounted: (mounted) => set({ richMounted: mounted }),\n}));\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
1
  import { gatherPageContext as e, resolveBigConsoleConversationContext as t } from "./pageContext.js";
2
2
  import { useSandboxAssistantTransport as n } from "./createSandboxAssistantTransport.js";
3
- import r from "../components/preview/AiPreviewMiniPanel.js";
3
+ import { AiPreviewMiniPanel as r } from "../components/preview/AiPreviewMiniPanel.js";
4
4
  export { r as AiPreviewMiniPanel, e as gatherPageContext, t as resolveBigConsoleConversationContext, n as useSandboxAssistantTransport };
@@ -1,182 +1,144 @@
1
1
  import { Section as e } from "../common/Section.js";
2
2
  import "../common/index.js";
3
- import { useAssistantRunStore as t } from "../../assistant/assistantRunStore.js";
4
- import { PREVIEW_CHROME_DIVIDER as n, PREVIEW_PANEL_SHELL as r, StepRail as i, formatAgo as a, useAssistantSidebarWidth as o } from "./previewShared.js";
5
- import { useEffect as s, useState as c } from "react";
6
- import { AlertCircle as l, ArrowUpRight as u, Braces as d, Database as f, LayoutDashboard as p, Loader2 as m, Sparkles as h, X as g } from "lucide-react";
3
+ import { PREVIEW_STEPS as t, useAssistantRunStore as n } from "../../assistant/assistantRunStore.js";
4
+ import { PreviewMeta as r, STEP_LABEL as i, formatAgo as a, previewLabels as o, useAssistantSidebarWidth as s } from "./previewShared.js";
5
+ import { useEffect as c, useState as l } from "react";
6
+ import { ArrowUpRight as u, Braces as d, Database as f, LayoutDashboard as p, Loader2 as m, Sparkles as h, X as g } from "lucide-react";
7
7
  import { useI18n as _ } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
8
8
  import { Button as v } from "@burdenoff/fe-libs/ui";
9
9
  import { jsx as y, jsxs as b } from "react/jsx-runtime";
10
+ import { AssistantEntityPreview as x } from "@burdenoff/fe-libs/shared/assistant";
10
11
  //#region src/bigconsole/components/preview/AiPreviewMiniPanel.tsx
11
- function x({ onNavigate: x }) {
12
- let { t: S } = _(), 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, F] = c(() => Date.now());
13
- s(() => {
14
- if (!C) return;
15
- let e = window.setInterval(() => F(Date.now()), 1e3);
12
+ function S({ onNavigate: S }) {
13
+ let { t: C } = _(), 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, I] = l(() => Date.now());
14
+ c(() => {
15
+ if (!T) return;
16
+ I(Date.now());
17
+ let e = window.setInterval(() => I(Date.now()), 1e3);
16
18
  return () => window.clearInterval(e);
17
- }, [C]);
18
- let I = o(), L = (e) => {
19
- x ? x(e) : typeof window < "u" && window.location.assign(e);
19
+ }, [T]);
20
+ let L = s(E && !D), R = (e) => {
21
+ S ? S(e) : typeof window < "u" && window.location.assign(e);
20
22
  };
21
- if (!T || E) return null;
22
- if (!w) return /* @__PURE__ */ b("div", {
23
+ if (!E || D) return null;
24
+ if (!T) return /* @__PURE__ */ b("div", {
23
25
  className: "flex flex-wrap items-center justify-end gap-control-gap px-4 pt-3 md:px-6",
24
- style: { marginRight: I },
26
+ style: { marginRight: L },
25
27
  children: [/* @__PURE__ */ y("span", {
26
28
  className: "text-xs text-text-muted",
27
- children: C ? "AI build running" : "AI build finished"
29
+ children: w ? C("bigconsole.widget.aiPreview.buildRunning", "AI build running") : C("bigconsole.widget.aiPreview.buildFinished", "AI build finished")
28
30
  }), /* @__PURE__ */ b(v, {
29
31
  size: "sm",
30
32
  variant: "outline",
31
- onClick: M,
33
+ onClick: N,
32
34
  "data-testid": "ai-preview-open",
33
35
  children: [
34
36
  /* @__PURE__ */ y(h, { className: "mr-1.5 size-4" }),
35
- S("bigconsole.widget.aiPreviewMini.previewButton", "Preview"),
36
- C ? /* @__PURE__ */ y(m, { className: "ml-1.5 size-3.5 animate-spin" }) : null
37
+ C("bigconsole.widget.aiPreviewMini.previewButton", "Preview"),
38
+ w ? /* @__PURE__ */ y(m, { className: "ml-1.5 size-3.5 animate-spin" }) : null
37
39
  ]
38
40
  })]
39
41
  });
40
- let R = j ? Math.max(0, Math.floor((P - j) / 1e3)) : 0, z = [];
41
- return O.datasink && z.push({
42
+ let z = M ? Math.max(0, Math.floor((F - M) / 1e3)) : 0, B = [];
43
+ k.datasink && B.push({
42
44
  key: "datasink",
43
- label: S("bigconsole.widget.aiPreviewMini.link.dataSink", "Data Sink"),
44
- path: `/bigconsole/datasinks/${O.datasink}`,
45
+ label: C("bigconsole.widget.aiPreviewMini.link.dataSink", "Data Sink"),
46
+ path: `/bigconsole/datasinks/${k.datasink}`,
45
47
  icon: /* @__PURE__ */ y(f, {
46
48
  className: "size-4 shrink-0 text-text-muted",
47
49
  "aria-hidden": "true"
48
50
  })
49
- }), O.dashboard && z.push({
51
+ }), k.dashboard && B.push({
50
52
  key: "dashboard",
51
- label: S("bigconsole.widget.aiPreviewMini.link.dashboard", "Dashboard"),
52
- path: `/bigconsole/dashboards/${O.dashboard}`,
53
+ label: C("bigconsole.widget.aiPreviewMini.link.dashboard", "Dashboard"),
54
+ path: `/bigconsole/dashboards/${k.dashboard}`,
53
55
  icon: /* @__PURE__ */ y(p, {
54
56
  className: "size-4 shrink-0 text-text-muted",
55
57
  "aria-hidden": "true"
56
58
  })
57
- }), O.parser && z.push({
59
+ }), k.parser && B.push({
58
60
  key: "parser",
59
- label: S("bigconsole.widget.aiPreviewMini.link.parser", "Parser"),
60
- path: `/bigconsole/parsers/${O.parser}`,
61
+ label: C("bigconsole.widget.aiPreviewMini.link.parser", "Parser"),
62
+ path: `/bigconsole/parsers/${k.parser}`,
61
63
  icon: /* @__PURE__ */ y(d, {
62
64
  className: "size-4 shrink-0 text-text-muted",
63
65
  "aria-hidden": "true"
64
66
  })
65
- }), /* @__PURE__ */ b("section", {
67
+ });
68
+ let V = t.map((e) => ({
69
+ id: e,
70
+ label: C(i[e].key, i[e].fallback),
71
+ status: O[e]
72
+ }));
73
+ return /* @__PURE__ */ b("div", {
66
74
  "data-testid": "ai-preview-panel",
67
75
  "data-variant": "mini",
68
- "aria-label": S("bigconsole.widget.aiPreviewMini.ariaLabel", "AI preview"),
69
- style: { marginRight: I ? I + 16 : void 0 },
70
- className: `${r} mx-4 mt-3 mb-3 md:mx-6`,
71
- children: [
72
- /* @__PURE__ */ b("div", {
73
- className: `flex items-start justify-between gap-control-gap bg-bg-surface px-3 py-2 ${n}`,
74
- children: [/* @__PURE__ */ b("div", {
75
- className: "flex min-w-0 flex-col gap-0.5",
76
- children: [/* @__PURE__ */ b("div", {
77
- className: "flex min-w-0 flex-wrap items-center gap-2",
78
- children: [
79
- /* @__PURE__ */ y(h, { className: "size-4 shrink-0 text-action-primary-bg" }),
80
- /* @__PURE__ */ y("h2", {
81
- className: "text-sm font-semibold text-text-primary",
82
- children: S("bigconsole.widget.aiPreviewMini.title", "AI Preview")
83
- }),
84
- C ? /* @__PURE__ */ b("span", {
85
- className: "inline-flex items-center gap-1.5 rounded-full bg-status-success-bg px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-status-success-text",
86
- "data-testid": "ai-preview-live",
87
- children: [/* @__PURE__ */ b("span", {
88
- className: "relative flex size-2",
89
- children: [/* @__PURE__ */ y("span", { className: "absolute inline-flex size-full rounded-full bg-status-success-text opacity-75 motion-safe:animate-ping" }), /* @__PURE__ */ y("span", { className: "relative inline-flex size-2 rounded-full bg-status-success-text" })]
90
- }), S("bigconsole.widget.aiPreviewMini.live", "Live")]
91
- }) : null
92
- ]
93
- }), /* @__PURE__ */ b("span", {
94
- className: "min-w-0 break-words text-xs text-text-muted",
95
- "data-testid": "ai-preview-ago",
96
- children: [
97
- C ? k || S("bigconsole.widget.aiPreviewMini.working", "Working…") : A ? S("bigconsole.widget.aiPreviewMini.stopped", "Stopped") : S("bigconsole.widget.aiPreviewMini.done", "Done"),
98
- " ",
99
- "· ",
100
- S("bigconsole.widget.aiPreviewMini.updated", "updated"),
101
- " ",
102
- a(R)
103
- ]
104
- })]
105
- }), /* @__PURE__ */ y(v, {
106
- size: "sm",
107
- variant: "ghost",
108
- onClick: N,
109
- "aria-label": S("bigconsole.widget.aiPreviewMini.closeAriaLabel", "Close preview"),
110
- "data-testid": "ai-preview-close",
111
- children: /* @__PURE__ */ y(g, { className: "size-4" })
112
- })]
76
+ style: { marginRight: L ? L + 16 : void 0 },
77
+ className: "mx-4 mb-3 mt-3 min-w-0 md:mx-6",
78
+ children: [/* @__PURE__ */ y("h2", {
79
+ className: "sr-only",
80
+ children: C("bigconsole.widget.aiPreviewMini.title", "AI Preview")
81
+ }), /* @__PURE__ */ y(x, {
82
+ title: C("bigconsole.widget.aiPreviewMini.title", "AI Preview"),
83
+ steps: V,
84
+ caption: w ? A || C("bigconsole.widget.aiPreviewMini.working", "Working…") : C("bigconsole.widget.aiPreviewMini.done", "Done"),
85
+ error: j,
86
+ meta: /* @__PURE__ */ y(r, {
87
+ state: w ? "running" : j ? "stopped" : "idle",
88
+ stateLabel: w ? C("bigconsole.widget.aiPreviewMini.live", "Live") : j ? C("bigconsole.widget.aiPreviewMini.stopped", "Stopped") : null,
89
+ updatedLabel: C("bigconsole.widget.aiPreviewMini.updated", "updated"),
90
+ ago: a(z, C)
113
91
  }),
114
- /* @__PURE__ */ b("div", {
115
- className: `px-3 py-2 ${n}`,
116
- children: [/* @__PURE__ */ y("h3", {
117
- className: "mb-1 text-[11px] font-semibold uppercase tracking-wide text-text-muted",
118
- children: "Build progress"
119
- }), /* @__PURE__ */ y(i, { steps: D })]
92
+ headerActions: /* @__PURE__ */ y(v, {
93
+ size: "sm",
94
+ variant: "ghost",
95
+ onClick: P,
96
+ "aria-label": C("bigconsole.widget.aiPreviewMini.closeAriaLabel", "Close preview"),
97
+ "data-testid": "ai-preview-close",
98
+ children: /* @__PURE__ */ y(g, { className: "size-4" })
120
99
  }),
121
- /* @__PURE__ */ y("div", {
100
+ labels: o(C),
101
+ children: /* @__PURE__ */ y("div", {
122
102
  className: "p-3",
123
103
  children: /* @__PURE__ */ b(e, {
124
104
  emphasis: "standard",
125
105
  padding: "compact",
126
106
  headingLevel: 3,
127
- title: "Built so far",
128
- description: "Open any piece to see the real thing.",
107
+ title: C("bigconsole.widget.aiPreviewMini.builtSoFar", "Built so far"),
108
+ description: C("bigconsole.widget.aiPreviewMini.builtSoFarHint", "Open any piece to see the real thing."),
129
109
  bodyClassName: "flex flex-col gap-list-gap",
130
- children: [z.length === 0 ? /* @__PURE__ */ y("p", {
110
+ children: [B.length === 0 ? /* @__PURE__ */ y("p", {
131
111
  className: "text-sm text-text-muted",
132
- children: C ? S("bigconsole.widget.aiPreviewMini.building", "Building… links appear here as each piece is created.") : S("bigconsole.widget.aiPreviewMini.nothingBuilt", "Nothing built yet.")
133
- }) : z.map((e) => /* @__PURE__ */ b("div", {
112
+ children: w ? C("bigconsole.widget.aiPreviewMini.building", "Building… links appear here as each piece is created.") : C("bigconsole.widget.aiPreviewMini.nothingBuilt", "Nothing built yet.")
113
+ }) : B.map((e) => /* @__PURE__ */ b("div", {
134
114
  "data-testid": "ai-preview-row",
135
115
  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",
136
116
  children: [/* @__PURE__ */ b("p", {
137
117
  className: "flex min-w-0 items-center gap-icon-gap text-sm font-medium text-text-primary",
138
118
  children: [e.icon, /* @__PURE__ */ y("span", {
139
119
  className: "truncate",
140
- children: S("bigconsole.widget.aiPreviewMini.linkReady", "{{label}} ready", { label: e.label })
120
+ children: C("bigconsole.widget.aiPreviewMini.linkReady", "{{label}} ready", { label: e.label })
141
121
  })]
142
122
  }), /* @__PURE__ */ b(v, {
143
123
  size: "sm",
144
124
  variant: "ghost",
145
- onClick: () => L(e.path),
146
- children: [S("bigconsole.widget.aiPreviewMini.open", "Open"), /* @__PURE__ */ y(u, { className: "ml-1 size-3.5" })]
125
+ onClick: () => R(e.path),
126
+ children: [C("bigconsole.widget.aiPreviewMini.open", "Open"), /* @__PURE__ */ y(u, { className: "ml-1 size-3.5" })]
147
127
  })]
148
- }, e.key)), O.dashboard ? /* @__PURE__ */ b(v, {
128
+ }, e.key)), k.dashboard ? /* @__PURE__ */ b(v, {
149
129
  size: "sm",
150
130
  variant: "outline",
151
131
  className: "h-auto w-full whitespace-normal py-1.5 text-center",
152
132
  "data-testid": "ai-preview-open-in-bigconsole",
153
- onClick: () => L(`/bigconsole/dashboards/${O.dashboard ?? ""}`),
154
- children: [S("bigconsole.widget.aiPreviewMini.openInBigConsole", "Open in BigConsole for the live view"), /* @__PURE__ */ y(u, { className: "ml-1 size-3.5" })]
133
+ onClick: () => R(`/bigconsole/dashboards/${k.dashboard ?? ""}`),
134
+ children: [C("bigconsole.widget.aiPreviewMini.openInBigConsole", "Open in BigConsole for the live view"), /* @__PURE__ */ y(u, { className: "ml-1 size-3.5" })]
155
135
  }) : null]
156
136
  })
157
- }),
158
- A ? /* @__PURE__ */ b("div", {
159
- role: "alert",
160
- className: "border-t border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] px-3 py-2",
161
- children: [/* @__PURE__ */ y("h3", {
162
- className: "text-[11px] font-semibold uppercase tracking-wide text-[var(--color-status-error-text)]",
163
- children: "Build stopped"
164
- }), /* @__PURE__ */ b("p", {
165
- className: "mt-0.5 flex items-start gap-icon-gap text-xs text-[var(--color-status-error-text)]",
166
- "data-testid": "ai-preview-error",
167
- children: [/* @__PURE__ */ y(l, {
168
- className: "mt-0.5 size-3.5 shrink-0",
169
- "aria-hidden": "true"
170
- }), /* @__PURE__ */ y("span", {
171
- className: "min-w-0 break-words",
172
- children: A
173
- })]
174
- })]
175
- }) : null
176
- ]
137
+ })
138
+ })]
177
139
  });
178
140
  }
179
141
  //#endregion
180
- export { x as default };
142
+ export { S as AiPreviewMiniPanel };
181
143
 
182
144
  //# sourceMappingURL=AiPreviewMiniPanel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AiPreviewMiniPanel.js","names":[],"sources":["../../../../src/bigconsole/components/preview/AiPreviewMiniPanel.tsx"],"sourcesContent":["/**\n * AI Preview — the app-shell copy.\n *\n * The full <AiPreviewPanel> lives inside BigConsoleRoot and only exists on\n * /bigconsole pages, but the assistant widget is global: you can start a build\n * from anywhere. This lightweight panel is mounted by the app shell so the build\n * is visible on WHATEVER page you are on — workflows, store, workspaces, search.\n *\n * It is deliberately store-only: no Apollo, no BigConsole context, no data\n * fetching. That is what lets it mount above the router. It shows the same live\n * step rail and caption as the full panel, plus click-through links to each\n * entity as it lands. The rich per-entity tabs stay on the /bigconsole pages,\n * where the data layer actually exists.\n *\n * It renders nothing while the full panel is up (`richMounted`), so the two\n * never stack. Leave a /bigconsole page mid-run and the full panel unmounts,\n * `richMounted` flips false, and this one takes over without a flicker.\n *\n * Section emphasis (BOFF-5639): the chrome (header, progress) is recessed and\n * the \"Built so far\" list is the one raised surface — the same ladder as the\n * full panel, so the two copies read as one component. Spacing is identical to\n * the full panel too; they used to differ (`pt-1` vs `pt-3`), which shifted the\n * page under the user as they navigated.\n */\nimport { useEffect, useState, type ReactNode } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { AlertCircle, ArrowUpRight, Braces, Database, LayoutDashboard, Loader2, Sparkles, X } from 'lucide-react';\n\nimport { useAssistantRunStore } from '../../assistant/assistantRunStore';\nimport { Section } from '../common';\nimport {\n PREVIEW_CHROME_DIVIDER,\n PREVIEW_PANEL_SHELL,\n StepRail,\n formatAgo,\n useAssistantSidebarWidth,\n} from './previewShared';\n\ninterface AiPreviewMiniPanelProps {\n /**\n * Route to an in-app path. The links are absolute (`/bigconsole/...`) because\n * this panel renders outside the BigConsole router. Falls back to a full\n * navigation when the host does not supply a handler.\n */\n onNavigate?: (path: string) => void;\n}\n\ninterface QuickLink {\n key: string;\n label: string;\n path: string;\n icon: ReactNode;\n}\n\nexport function AiPreviewMiniPanel({ onNavigate }: AiPreviewMiniPanelProps) {\n const { t } = useI18n();\n const isRunning = useAssistantRunStore((state) => state.isRunning);\n const previewOpen = useAssistantRunStore((state) => state.previewOpen);\n const hasRun = useAssistantRunStore((state) => state.hasRun);\n const richMounted = useAssistantRunStore((state) => state.richMounted);\n const steps = useAssistantRunStore((state) => state.steps);\n const entityIds = useAssistantRunStore((state) => state.entityIds);\n const caption = useAssistantRunStore((state) => state.caption);\n const error = useAssistantRunStore((state) => state.error);\n const updatedAt = useAssistantRunStore((state) => state.updatedAt);\n const openPreview = useAssistantRunStore((state) => state.openPreview);\n const closePreview = useAssistantRunStore((state) => state.closePreview);\n\n const [now, setNow] = useState(() => Date.now());\n useEffect(() => {\n if (!isRunning) return;\n const id = window.setInterval(() => setNow(Date.now()), 1000);\n return () => window.clearInterval(id);\n }, [isRunning]);\n\n const assistantWidth = useAssistantSidebarWidth();\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: assistantWidth }}\n >\n <span className=\"text-xs text-text-muted\">{isRunning ? 'AI build running' : 'AI build finished'}</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 agoSeconds = updatedAt ? Math.max(0, Math.floor((now - updatedAt) / 1000)) : 0;\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 return (\n <section\n data-testid=\"ai-preview-panel\"\n data-variant=\"mini\"\n aria-label={t('bigconsole.widget.aiPreviewMini.ariaLabel', 'AI preview')}\n style={{ marginRight: assistantWidth ? assistantWidth + 16 : undefined }}\n className={`${PREVIEW_PANEL_SHELL} mx-4 mt-3 mb-3 md:mx-6`}\n >\n {/* Header + live strip */}\n <div\n className={`flex items-start justify-between gap-control-gap bg-bg-surface px-3 py-2 ${PREVIEW_CHROME_DIVIDER}`}\n >\n <div className=\"flex min-w-0 flex-col gap-0.5\">\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n <Sparkles className=\"size-4 shrink-0 text-action-primary-bg\" />\n <h2 className=\"text-sm font-semibold text-text-primary\">\n {t('bigconsole.widget.aiPreviewMini.title', 'AI Preview')}\n </h2>\n {isRunning ? (\n <span\n className=\"inline-flex items-center gap-1.5 rounded-full bg-status-success-bg px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-status-success-text\"\n data-testid=\"ai-preview-live\"\n >\n <span className=\"relative flex size-2\">\n <span className=\"absolute inline-flex size-full 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 {t('bigconsole.widget.aiPreviewMini.live', 'Live')}\n </span>\n ) : null}\n </div>\n <span className=\"min-w-0 break-words text-xs text-text-muted\" data-testid=\"ai-preview-ago\">\n {isRunning\n ? caption\n ? caption\n : t('bigconsole.widget.aiPreviewMini.working', 'Working…')\n : error\n ? t('bigconsole.widget.aiPreviewMini.stopped', 'Stopped')\n : t('bigconsole.widget.aiPreviewMini.done', 'Done')}{' '}\n · {t('bigconsole.widget.aiPreviewMini.updated', 'updated')} {formatAgo(agoSeconds)}\n </span>\n </div>\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 </div>\n\n {/* Step rail */}\n <div className={`px-3 py-2 ${PREVIEW_CHROME_DIVIDER}`}>\n <h3 className=\"mb-1 text-[11px] font-semibold uppercase tracking-wide text-text-muted\">Build progress</h3>\n <StepRail steps={steps} />\n </div>\n\n {/* Quick links to what has been built so far. The full per-entity tabs live\n on the /bigconsole pages; here we surface the direct links instead. */}\n <div className=\"p-3\">\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title=\"Built so far\"\n description=\"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('bigconsole.widget.aiPreviewMini.building', 'Building… links appear here as each piece is created.')\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\n {error ? (\n <div\n role=\"alert\"\n className=\"border-t border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] px-3 py-2\"\n >\n <h3 className=\"text-[11px] font-semibold uppercase tracking-wide text-[var(--color-status-error-text)]\">\n Build stopped\n </h3>\n <p\n className=\"mt-0.5 flex items-start gap-icon-gap text-xs text-[var(--color-status-error-text)]\"\n data-testid=\"ai-preview-error\"\n >\n <AlertCircle className=\"mt-0.5 size-3.5 shrink-0\" aria-hidden=\"true\" />\n <span className=\"min-w-0 break-words\">{error}</span>\n </p>\n </div>\n ) : null}\n </section>\n );\n}\n\nexport default AiPreviewMiniPanel;\n"],"mappings":";;;;;;;;;;AAuDA,SAAgB,EAAmB,EAAE,iBAAuC;CAC1E,IAAM,EAAE,SAAM,GAAS,EACjB,IAAY,GAAsB,MAAU,EAAM,UAAU,EAC5D,IAAc,GAAsB,MAAU,EAAM,YAAY,EAChE,IAAS,GAAsB,MAAU,EAAM,OAAO,EACtD,IAAc,GAAsB,MAAU,EAAM,YAAY,EAChE,IAAQ,GAAsB,MAAU,EAAM,MAAM,EACpD,IAAY,GAAsB,MAAU,EAAM,UAAU,EAC5D,IAAU,GAAsB,MAAU,EAAM,QAAQ,EACxD,IAAQ,GAAsB,MAAU,EAAM,MAAM,EACpD,IAAY,GAAsB,MAAU,EAAM,UAAU,EAC5D,IAAc,GAAsB,MAAU,EAAM,YAAY,EAChE,IAAe,GAAsB,MAAU,EAAM,aAAa,EAElE,CAAC,GAAK,KAAU,QAAe,KAAK,KAAK,CAAC;AAChD,SAAgB;AACd,MAAI,CAAC,EAAW;EAChB,IAAM,IAAK,OAAO,kBAAkB,EAAO,KAAK,KAAK,CAAC,EAAE,IAAK;AAC7D,eAAa,OAAO,cAAc,EAAG;IACpC,CAAC,EAAU,CAAC;CAEf,IAAM,IAAiB,GAA0B,EAE3C,KAAM,MAAiB;AAC3B,EAAI,IAAY,EAAW,EAAK,GACvB,OAAO,SAAW,OAAa,OAAO,SAAS,OAAO,EAAK;;AAItE,KAAI,CAAC,KAAU,EAAa,QAAO;AAEnC,KAAI,CAAC,EACH,QACE,kBAAC,OAAD;EACE,WAAU;EACV,OAAO,EAAE,aAAa,GAAgB;YAFxC,CAIE,kBAAC,QAAD;GAAM,WAAU;aAA2B,IAAY,qBAAqB;GAA2B,CAAA,EACvG,kBAAC,GAAD;GAAQ,MAAK;GAAK,SAAQ;GAAU,SAAS;GAAa,eAAY;aAAtE;IACE,kBAAC,GAAD,EAAU,WAAU,iBAAkB,CAAA;IACrC,EAAE,iDAAiD,UAAU;IAC7D,IAAY,kBAAC,GAAD,EAAS,WAAU,gCAAiC,CAAA,GAAG;IAC7D;KACL;;CAIV,IAAM,IAAa,IAAY,KAAK,IAAI,GAAG,KAAK,OAAO,IAAM,KAAa,IAAK,CAAC,GAAG,GAE7E,IAAqB,EAAE;AAuB7B,QAtBI,EAAU,YACZ,EAAM,KAAK;EACT,KAAK;EACL,OAAO,EAAE,iDAAiD,YAAY;EACtE,MAAM,yBAAyB,EAAU;EACzC,MAAM,kBAAC,GAAD;GAAU,WAAU;GAAkC,eAAY;GAAS,CAAA;EAClF,CAAC,EACA,EAAU,aACZ,EAAM,KAAK;EACT,KAAK;EACL,OAAO,EAAE,kDAAkD,YAAY;EACvE,MAAM,0BAA0B,EAAU;EAC1C,MAAM,kBAAC,GAAD;GAAiB,WAAU;GAAkC,eAAY;GAAS,CAAA;EACzF,CAAC,EACA,EAAU,UACZ,EAAM,KAAK;EACT,KAAK;EACL,OAAO,EAAE,+CAA+C,SAAS;EACjE,MAAM,uBAAuB,EAAU;EACvC,MAAM,kBAAC,GAAD;GAAQ,WAAU;GAAkC,eAAY;GAAS,CAAA;EAChF,CAAC,EAGF,kBAAC,WAAD;EACE,eAAY;EACZ,gBAAa;EACb,cAAY,EAAE,6CAA6C,aAAa;EACxE,OAAO,EAAE,aAAa,IAAiB,IAAiB,KAAK,KAAA,GAAW;EACxE,WAAW,GAAG,EAAoB;YALpC;GAQE,kBAAC,OAAD;IACE,WAAW,4EAA4E;cADzF,CAGE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,GAAD,EAAU,WAAU,0CAA2C,CAAA;OAC/D,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAE,yCAAyC,aAAa;QACtD,CAAA;OACJ,IACC,kBAAC,QAAD;QACE,WAAU;QACV,eAAY;kBAFd,CAIE,kBAAC,QAAD;SAAM,WAAU;mBAAhB,CACE,kBAAC,QAAD,EAAM,WAAU,0GAA2G,CAAA,EAC3H,kBAAC,QAAD,EAAM,WAAU,mEAAoE,CAAA,CAC/E;YACN,EAAE,wCAAwC,OAAO,CAC7C;YACL;OACA;SACN,kBAAC,QAAD;MAAM,WAAU;MAA8C,eAAY;gBAA1E;OACG,IACG,KAEE,EAAE,2CAA2C,WAAW,GAC1D,IACE,EAAE,2CAA2C,UAAU,GACvD,EAAE,wCAAwC,OAAO;OAAE;OAAI;OAC1D,EAAE,2CAA2C,UAAU;OAAC;OAAE,EAAU,EAAW;OAC7E;QACH;QACN,kBAAC,GAAD;KACE,MAAK;KACL,SAAQ;KACR,SAAS;KACT,cAAY,EAAE,kDAAkD,gBAAgB;KAChF,eAAY;eAEZ,kBAAC,GAAD,EAAG,WAAU,UAAW,CAAA;KACjB,CAAA,CACL;;GAGN,kBAAC,OAAD;IAAK,WAAW,aAAa;cAA7B,CACE,kBAAC,MAAD;KAAI,WAAU;eAAyE;KAAmB,CAAA,EAC1G,kBAAC,GAAD,EAAiB,UAAS,CAAA,CACtB;;GAIN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KACE,UAAS;KACT,SAAQ;KACR,cAAc;KACd,OAAM;KACN,aAAY;KACZ,eAAc;eANhB,CAQG,EAAM,WAAW,IAChB,kBAAC,KAAD;MAAG,WAAU;gBACV,IACG,EAAE,4CAA4C,wDAAwD,GACtG,EAAE,gDAAgD,qBAAqB;MACzE,CAAA,GAEJ,EAAM,KAAK,MACT,kBAAC,OAAD;MAEE,eAAY;MACZ,WAAU;gBAHZ,CAKE,kBAAC,KAAD;OAAG,WAAU;iBAAb,CACG,EAAK,MACN,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAE,6CAA6C,mBAAmB,EAAE,OAAO,EAAK,OAAO,CAAC;QACpF,CAAA,CACL;UACJ,kBAAC,GAAD;OAAQ,MAAK;OAAK,SAAQ;OAAQ,eAAe,EAAG,EAAK,KAAK;iBAA9D,CACG,EAAE,wCAAwC,OAAO,EAClD,kBAAC,GAAD,EAAc,WAAU,iBAAkB,CAAA,CACnC;SACL;QAdC,EAAK,IAcN,CACN,EAEH,EAAU,YAMT,kBAAC,GAAD;MACE,MAAK;MACL,SAAQ;MACR,WAAU;MACV,eAAY;MACZ,eAAe,EAAG,0BAA0B,EAAU,aAAa,KAAK;gBAL1E,CAOG,EAAE,oDAAoD,uCAAuC,EAC9F,kBAAC,GAAD,EAAc,WAAU,iBAAkB,CAAA,CACnC;UACP,KACI;;IACN,CAAA;GAEL,IACC,kBAAC,OAAD;IACE,MAAK;IACL,WAAU;cAFZ,CAIE,kBAAC,MAAD;KAAI,WAAU;eAA0F;KAEnG,CAAA,EACL,kBAAC,KAAD;KACE,WAAU;KACV,eAAY;eAFd,CAIE,kBAAC,GAAD;MAAa,WAAU;MAA2B,eAAY;MAAS,CAAA,EACvE,kBAAC,QAAD;MAAM,WAAU;gBAAuB;MAAa,CAAA,CAClD;OACA;QACJ;GACI"}
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 { useEffect, useState, type ReactNode } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { AssistantEntityPreview, type AssistantPreviewStep } from '@burdenoff/fe-libs/shared/assistant';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowUpRight, Braces, Database, LayoutDashboard, Loader2, Sparkles, X } from 'lucide-react';\n\nimport { PREVIEW_STEPS, useAssistantRunStore } from '../../assistant/assistantRunStore';\nimport { Section } from '../common';\nimport { PreviewMeta, STEP_LABEL, formatAgo, previewLabels, useAssistantSidebarWidth } from './previewShared';\n\ninterface AiPreviewMiniPanelProps {\n /**\n * Route to an in-app path. The links are absolute (`/bigconsole/...`) because\n * this panel renders outside the BigConsole router. Falls back to a full\n * navigation when the host does not supply a handler.\n */\n onNavigate?: (path: string) => void;\n}\n\ninterface QuickLink {\n key: string;\n label: string;\n path: string;\n icon: ReactNode;\n}\n\nexport function AiPreviewMiniPanel({ onNavigate }: AiPreviewMiniPanelProps) {\n const { t } = useI18n();\n const isRunning = useAssistantRunStore((state) => state.isRunning);\n const previewOpen = useAssistantRunStore((state) => state.previewOpen);\n const hasRun = useAssistantRunStore((state) => state.hasRun);\n const richMounted = useAssistantRunStore((state) => state.richMounted);\n const steps = useAssistantRunStore((state) => state.steps);\n const entityIds = useAssistantRunStore((state) => state.entityIds);\n const caption = useAssistantRunStore((state) => state.caption);\n const error = useAssistantRunStore((state) => state.error);\n const updatedAt = useAssistantRunStore((state) => state.updatedAt);\n const openPreview = useAssistantRunStore((state) => state.openPreview);\n const closePreview = useAssistantRunStore((state) => state.closePreview);\n\n const [now, setNow] = useState(() => Date.now());\n /**\n * ★★ Ticks while the panel is OPEN, not only while a turn is running. Gating\n * on `isRunning` froze the \"updated …\" strip the moment a build finished, so\n * a panel left open for an hour still claimed it was updated \"just now\".\n */\n useEffect(() => {\n if (!previewOpen) return;\n // ★ See AiPreviewPanel: re-read on OPEN so a reopen cannot show the previous\n // open's stale reading, and why the lint warning here is the right trade.\n setNow(Date.now());\n const id = window.setInterval(() => setNow(Date.now()), 1000);\n return () => window.clearInterval(id);\n }, [previewOpen]);\n\n // ★ Gated: this panel is mounted on EVERY page, and a hook cannot live below\n // an early return — so without this the 500ms poll ran for the lifetime of\n // the tab whether or not the assistant had ever been opened.\n const assistantWidth = useAssistantSidebarWidth(hasRun && !richMounted);\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: assistantWidth }}\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 agoSeconds = updatedAt ? Math.max(0, Math.floor((now - updatedAt) / 1000)) : 0;\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 — margin against the assistant\n // sidebar, page gutters, and the testids this panel already had. Everything\n // visual belongs to the shared surface, which is what keeps this copy and\n // the full panel from drifting apart now that `previewShared` no longer\n // holds a shell recipe for them both.\n <div\n data-testid=\"ai-preview-panel\"\n data-variant=\"mini\"\n style={{ marginRight: assistantWidth ? assistantWidth + 16 : undefined }}\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 ago={formatAgo(agoSeconds, 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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"}
@@ -3,230 +3,169 @@ import "../common/index.js";
3
3
  import { useBigConsoleNavigate as t } from "../../context/BigConsoleContext.js";
4
4
  import "../../context/index.js";
5
5
  import { PREVIEW_STEPS as n, useAssistantRunStore as r } from "../../assistant/assistantRunStore.js";
6
- import { PREVIEW_CHROME_DIVIDER as i, PREVIEW_PANEL_SHELL as a, StepRail as o, formatAgo as s, useAssistantSidebarWidth as c } from "./previewShared.js";
6
+ import { PreviewMeta as i, STEP_LABEL as a, formatAgo as o, previewLabels as s, useAssistantSidebarWidth as c } from "./previewShared.js";
7
7
  import { DashboardContent as l, DataSinkContent as u, ParserContent as d, WidgetsContent as f } from "./AiPreviewContent.js";
8
- import { useEffect as p, useState as m } from "react";
9
- import { AlertCircle as h, ArrowUpRight as g, Loader2 as _, Sparkles as v, X as y } from "lucide-react";
8
+ import { useEffect as p, useRef as m, useState as h } from "react";
9
+ import { ArrowUpRight as g, Loader2 as _, Sparkles as v, X as y } from "lucide-react";
10
10
  import { useI18n as b } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
11
- import { Button as x, Tabs as S, TabsContent as C, TabsList as w, TabsTrigger as T } from "@burdenoff/fe-libs/ui";
12
- import { jsx as E, jsxs as D } from "react/jsx-runtime";
11
+ import { Button as x } from "@burdenoff/fe-libs/ui";
12
+ import { jsx as S, jsxs as C } from "react/jsx-runtime";
13
+ import { AssistantEntityPreview as w } from "@burdenoff/fe-libs/shared/assistant";
13
14
  //#region src/bigconsole/components/preview/AiPreviewPanel.tsx
14
- function O({ onOpen: e }) {
15
+ function T({ children: e }) {
16
+ return /* @__PURE__ */ S("div", {
17
+ className: "min-w-0 overflow-y-auto p-3",
18
+ style: { maxHeight: "min(440px, 60vh)" },
19
+ children: e
20
+ });
21
+ }
22
+ function E({ onOpen: e }) {
15
23
  let { t } = b();
16
- return /* @__PURE__ */ D(x, {
24
+ return /* @__PURE__ */ C(x, {
17
25
  size: "sm",
18
26
  variant: "outline",
19
27
  onClick: e,
20
28
  "data-testid": "ai-preview-open-entity",
21
- children: [t("bigconsole.widget.aiPreview.open", "Open"), /* @__PURE__ */ E(g, { className: "ml-1 size-3.5" })]
29
+ children: [t("bigconsole.widget.aiPreview.open", "Open"), /* @__PURE__ */ S(g, { className: "ml-1 size-3.5" })]
22
30
  });
23
31
  }
24
- function k() {
25
- let { t: g } = b(), k = t(), 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);
26
- p(() => (B(!0), () => B(!1)), [B]);
27
- let [V, H] = m("datasink"), [U, W] = m(() => Date.now()), G = P.dashboard;
32
+ function D() {
33
+ let { t: g } = b(), D = t(), O = r((e) => e.isRunning), k = r((e) => e.previewOpen), A = r((e) => e.hasRun), j = r((e) => e.steps), M = r((e) => e.entityIds), N = r((e) => e.caption), P = r((e) => e.error), F = r((e) => e.updatedAt), I = r((e) => e.openPreview), L = r((e) => e.closePreview), R = r((e) => e.setRichMounted);
34
+ p(() => (R(!0), () => {
35
+ R(!1);
36
+ }), [R]);
37
+ let [z, B] = h(() => Date.now());
28
38
  p(() => {
29
- if (!A) return;
30
- let e = window.setInterval(() => W(Date.now()), 1e3);
39
+ if (!k) return;
40
+ B(Date.now());
41
+ let e = window.setInterval(() => B(Date.now()), 1e3);
31
42
  return () => window.clearInterval(e);
32
- }, [A]), p(() => {
33
- let e = n.filter((e) => N[e] === "done"), t = e[e.length - 1];
34
- if (t) {
35
- H(t);
36
- return;
37
- }
38
- let r = n.find((e) => N[e] === "running");
39
- r && H(r);
40
- }, [N]);
41
- let K = L ? Math.max(0, Math.floor((U - L) / 1e3)) : 0, q = c();
42
- return M ? j ? /* @__PURE__ */ D("section", {
43
- "data-testid": "ai-preview-panel",
44
- "aria-label": g("bigconsole.widget.aiPreview.ariaLabel", "AI preview"),
45
- style: { marginRight: q ? q + 16 : void 0 },
46
- className: `${a} mx-4 mt-3 md:mx-6`,
47
- children: [
48
- /* @__PURE__ */ D("div", {
49
- className: `flex items-start justify-between gap-control-gap bg-bg-surface px-3 py-2 ${i}`,
50
- children: [/* @__PURE__ */ D("div", {
51
- className: "flex min-w-0 flex-col gap-0.5",
52
- children: [/* @__PURE__ */ D("div", {
53
- className: "flex min-w-0 flex-wrap items-center gap-2",
54
- children: [
55
- /* @__PURE__ */ E(v, { className: "size-4 shrink-0 text-action-primary-bg" }),
56
- /* @__PURE__ */ E("h2", {
57
- className: "text-sm font-semibold text-text-primary",
58
- children: g("bigconsole.widget.aiPreview.title", "AI Preview")
59
- }),
60
- A ? /* @__PURE__ */ D("span", {
61
- className: "inline-flex items-center gap-1.5 rounded-full bg-status-success-bg px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-status-success-text",
62
- "data-testid": "ai-preview-live",
63
- children: [/* @__PURE__ */ D("span", {
64
- className: "relative flex size-2",
65
- children: [/* @__PURE__ */ E("span", { className: "absolute inline-flex size-full rounded-full bg-status-success-text opacity-75 motion-safe:animate-ping" }), /* @__PURE__ */ E("span", { className: "relative inline-flex size-2 rounded-full bg-status-success-text" })]
66
- }), g("bigconsole.widget.aiPreview.live", "Live")]
67
- }) : null
68
- ]
69
- }), /* @__PURE__ */ D("span", {
70
- className: "min-w-0 break-words text-xs text-text-muted",
71
- "data-testid": "ai-preview-ago",
72
- children: [
73
- A ? F || g("bigconsole.widget.aiPreview.working", "Working…") : I ? g("bigconsole.widget.aiPreview.stopped", "Stopped") : g("bigconsole.widget.aiPreview.done", "Done"),
74
- " ",
75
- "· ",
76
- g("bigconsole.widget.aiPreview.updated", "updated"),
77
- " ",
78
- s(K)
79
- ]
80
- })]
81
- }), /* @__PURE__ */ E(x, {
82
- size: "sm",
83
- variant: "ghost",
84
- onClick: z,
85
- "aria-label": g("bigconsole.widget.aiPreview.closeAriaLabel", "Close preview"),
86
- "data-testid": "ai-preview-close",
87
- children: /* @__PURE__ */ E(y, { className: "size-4" })
88
- })]
89
- }),
90
- /* @__PURE__ */ D("div", {
91
- className: `px-3 py-2 ${i}`,
92
- children: [/* @__PURE__ */ E("h3", {
93
- className: "mb-1 text-[11px] font-semibold uppercase tracking-wide text-text-muted",
94
- children: "Build progress"
95
- }), /* @__PURE__ */ E(o, { steps: N })]
96
- }),
97
- /* @__PURE__ */ D(S, {
98
- value: V,
99
- onValueChange: (e) => H(e),
100
- children: [/* @__PURE__ */ E("div", {
101
- className: "min-w-0 overflow-x-auto px-3 pt-2",
102
- children: /* @__PURE__ */ D(w, {
103
- className: "w-max",
104
- children: [
105
- /* @__PURE__ */ E(T, {
106
- value: "datasink",
107
- "data-testid": "ai-preview-tab-datasink",
108
- children: g("bigconsole.widget.aiPreview.tab.dataSink", "Data Sink")
109
- }),
110
- /* @__PURE__ */ E(T, {
111
- value: "dashboard",
112
- "data-testid": "ai-preview-tab-dashboard",
113
- children: g("bigconsole.widget.aiPreview.tab.dashboard", "Dashboard")
114
- }),
115
- /* @__PURE__ */ E(T, {
116
- value: "parser",
117
- "data-testid": "ai-preview-tab-parser",
118
- children: g("bigconsole.widget.aiPreview.tab.parser", "Parser")
119
- }),
120
- /* @__PURE__ */ E(T, {
121
- value: "widget",
122
- "data-testid": "ai-preview-tab-widget",
123
- children: g("bigconsole.widget.aiPreview.tab.widget", "Widget")
124
- })
125
- ]
126
- })
127
- }), /* @__PURE__ */ D("div", {
128
- className: "min-w-0 overflow-y-auto p-3",
129
- style: { maxHeight: "min(440px, 60vh)" },
130
- children: [
131
- /* @__PURE__ */ E(C, {
132
- value: "datasink",
133
- children: /* @__PURE__ */ E(e, {
134
- emphasis: "standard",
135
- padding: "compact",
136
- headingLevel: 3,
137
- title: "Data sink rows",
138
- description: "The rows the assistant just landed, straight from the sink.",
139
- actions: P.datasink ? /* @__PURE__ */ E(O, { onOpen: () => k(`/datasinks/${P.datasink ?? ""}`) }) : void 0,
140
- children: /* @__PURE__ */ E(u, {
141
- id: P.datasink,
142
- live: A
143
- })
144
- })
145
- }),
146
- /* @__PURE__ */ E(C, {
147
- value: "dashboard",
148
- children: /* @__PURE__ */ E(e, {
149
- emphasis: "standard",
150
- padding: "compact",
151
- headingLevel: 3,
152
- title: g("bigconsole.widget.aiPreview.tab.dashboard", "Dashboard"),
153
- description: "The live board, rendered exactly as the real page renders it.",
154
- actions: P.dashboard ? /* @__PURE__ */ E(O, { onOpen: () => k(`/dashboards/${P.dashboard ?? ""}`) }) : void 0,
155
- children: /* @__PURE__ */ E(l, { dashboardId: P.dashboard })
156
- })
157
- }),
158
- /* @__PURE__ */ E(C, {
159
- value: "parser",
160
- children: /* @__PURE__ */ E(e, {
161
- emphasis: "standard",
162
- padding: "compact",
163
- headingLevel: 3,
164
- title: "Parser output",
165
- description: "The rows after the parser's transform, so you can check the shape.",
166
- actions: P.parser ? /* @__PURE__ */ E(O, { onOpen: () => k(`/parsers/${P.parser ?? ""}`) }) : void 0,
167
- children: /* @__PURE__ */ E(d, {
168
- parserId: P.parser,
169
- live: A
170
- })
171
- })
172
- }),
173
- /* @__PURE__ */ E(C, {
174
- value: "widget",
175
- children: /* @__PURE__ */ E(e, {
176
- emphasis: "standard",
177
- padding: "compact",
178
- headingLevel: 3,
179
- title: "Widgets",
180
- description: "Every widget on the new dashboard, rendered live.",
181
- actions: G ? /* @__PURE__ */ E(O, { onOpen: () => k(`/dashboards/${G}`) }) : void 0,
182
- children: /* @__PURE__ */ E(f, {
183
- dashboardId: G,
184
- live: A
185
- })
186
- })
187
- })
188
- ]
189
- })]
190
- }),
191
- I ? /* @__PURE__ */ D("div", {
192
- role: "alert",
193
- className: "border-t border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] px-3 py-2",
194
- children: [/* @__PURE__ */ E("h3", {
195
- className: "text-[11px] font-semibold uppercase tracking-wide text-[var(--color-status-error-text)]",
196
- children: "Build stopped"
197
- }), /* @__PURE__ */ D("p", {
198
- className: "mt-0.5 flex items-start gap-icon-gap text-xs text-[var(--color-status-error-text)]",
199
- "data-testid": "ai-preview-error",
200
- children: [/* @__PURE__ */ E(h, {
201
- className: "mt-0.5 size-3.5 shrink-0",
202
- "aria-hidden": "true"
203
- }), /* @__PURE__ */ E("span", {
204
- className: "min-w-0 break-words",
205
- children: I
206
- })]
207
- })]
208
- }) : null
209
- ]
210
- }) : /* @__PURE__ */ D("div", {
43
+ }, [k]);
44
+ let [V, H] = h(0), U = m(!1);
45
+ p(() => {
46
+ O && !U.current && H((e) => e + 1), U.current = O;
47
+ }, [O]);
48
+ let W = c(A);
49
+ if (!A) return null;
50
+ if (!k) return /* @__PURE__ */ C("div", {
211
51
  className: "flex flex-wrap items-center justify-end gap-control-gap px-4 pt-3 md:px-6",
212
- style: { marginRight: q },
213
- children: [/* @__PURE__ */ E("span", {
52
+ style: { marginRight: W },
53
+ children: [/* @__PURE__ */ S("span", {
214
54
  className: "text-xs text-text-muted",
215
- children: A ? "AI build running" : "AI build finished"
216
- }), /* @__PURE__ */ D(x, {
55
+ children: O ? g("bigconsole.widget.aiPreview.buildRunning", "AI build running") : g("bigconsole.widget.aiPreview.buildFinished", "AI build finished")
56
+ }), /* @__PURE__ */ C(x, {
217
57
  size: "sm",
218
58
  variant: "outline",
219
- onClick: R,
59
+ onClick: I,
220
60
  "data-testid": "ai-preview-open",
221
61
  children: [
222
- /* @__PURE__ */ E(v, { className: "mr-1.5 size-4" }),
62
+ /* @__PURE__ */ S(v, { className: "mr-1.5 size-4" }),
223
63
  g("bigconsole.widget.aiPreview.previewButton", "Preview"),
224
- A ? /* @__PURE__ */ E(_, { className: "ml-1.5 size-3.5 animate-spin" }) : null
64
+ O ? /* @__PURE__ */ S(_, { className: "ml-1.5 size-3.5 animate-spin" }) : null
225
65
  ]
226
66
  })]
227
- }) : null;
67
+ });
68
+ let G = F ? Math.max(0, Math.floor((z - F) / 1e3)) : 0, K = M.dashboard, q = n.map((e) => ({
69
+ id: e,
70
+ label: g(a[e].key, a[e].fallback),
71
+ status: j[e]
72
+ })), J = [
73
+ {
74
+ id: "datasink",
75
+ label: g("bigconsole.widget.aiPreview.tab.dataSink", "Data Sink"),
76
+ render: () => /* @__PURE__ */ S(T, { children: /* @__PURE__ */ S(e, {
77
+ emphasis: "standard",
78
+ padding: "compact",
79
+ headingLevel: 3,
80
+ title: g("bigconsole.widget.aiPreview.dataSinkRows", "Data sink rows"),
81
+ description: g("bigconsole.widget.aiPreview.dataSinkRowsHint", "The rows the assistant just landed, straight from the sink."),
82
+ actions: M.datasink ? /* @__PURE__ */ S(E, { onOpen: () => D(`/datasinks/${M.datasink}`) }) : void 0,
83
+ children: /* @__PURE__ */ S(u, {
84
+ id: M.datasink,
85
+ live: O
86
+ })
87
+ }) })
88
+ },
89
+ {
90
+ id: "dashboard",
91
+ label: g("bigconsole.widget.aiPreview.tab.dashboard", "Dashboard"),
92
+ render: () => /* @__PURE__ */ S(T, { children: /* @__PURE__ */ S(e, {
93
+ emphasis: "standard",
94
+ padding: "compact",
95
+ headingLevel: 3,
96
+ title: g("bigconsole.widget.aiPreview.tab.dashboard", "Dashboard"),
97
+ description: g("bigconsole.widget.aiPreview.dashboardHint", "The live board, rendered exactly as the real page renders it."),
98
+ actions: K ? /* @__PURE__ */ S(E, { onOpen: () => D(`/dashboards/${K}`) }) : void 0,
99
+ children: /* @__PURE__ */ S(l, { dashboardId: K })
100
+ }) })
101
+ },
102
+ {
103
+ id: "parser",
104
+ label: g("bigconsole.widget.aiPreview.tab.parser", "Parser"),
105
+ render: () => /* @__PURE__ */ S(T, { children: /* @__PURE__ */ S(e, {
106
+ emphasis: "standard",
107
+ padding: "compact",
108
+ headingLevel: 3,
109
+ title: g("bigconsole.widget.aiPreview.parserOutput", "Parser output"),
110
+ description: g("bigconsole.widget.aiPreview.parserOutputHint", "The rows after the parser's transform, so you can check the shape."),
111
+ actions: M.parser ? /* @__PURE__ */ S(E, { onOpen: () => D(`/parsers/${M.parser}`) }) : void 0,
112
+ children: /* @__PURE__ */ S(d, {
113
+ parserId: M.parser,
114
+ live: O
115
+ })
116
+ }) })
117
+ },
118
+ {
119
+ id: "widget",
120
+ label: g("bigconsole.widget.aiPreview.tab.widget", "Widget"),
121
+ render: () => /* @__PURE__ */ S(T, { children: /* @__PURE__ */ S(e, {
122
+ emphasis: "standard",
123
+ padding: "compact",
124
+ headingLevel: 3,
125
+ title: g("bigconsole.widget.aiPreview.widgets", "Widgets"),
126
+ description: g("bigconsole.widget.aiPreview.widgetsHint", "Every widget on the new dashboard, rendered live."),
127
+ actions: K ? /* @__PURE__ */ S(E, { onOpen: () => D(`/dashboards/${K}`) }) : void 0,
128
+ children: /* @__PURE__ */ S(f, {
129
+ dashboardId: K,
130
+ live: O
131
+ })
132
+ }) })
133
+ }
134
+ ];
135
+ return /* @__PURE__ */ C("div", {
136
+ "data-testid": "ai-preview-panel",
137
+ style: { marginRight: W ? W + 16 : void 0 },
138
+ className: "mx-4 mt-3 min-w-0 md:mx-6",
139
+ children: [/* @__PURE__ */ S("h2", {
140
+ className: "sr-only",
141
+ children: g("bigconsole.widget.aiPreview.title", "AI Preview")
142
+ }), /* @__PURE__ */ S(w, {
143
+ title: g("bigconsole.widget.aiPreview.title", "AI Preview"),
144
+ runKey: String(V),
145
+ steps: q,
146
+ tabs: J,
147
+ follow: "produced",
148
+ caption: O ? N || g("bigconsole.widget.aiPreview.working", "Working…") : g("bigconsole.widget.aiPreview.done", "Done"),
149
+ error: P,
150
+ meta: /* @__PURE__ */ S(i, {
151
+ state: O ? "running" : P ? "stopped" : "idle",
152
+ stateLabel: O ? g("bigconsole.widget.aiPreview.live", "Live") : P ? g("bigconsole.widget.aiPreview.stopped", "Stopped") : null,
153
+ updatedLabel: g("bigconsole.widget.aiPreview.updated", "updated"),
154
+ ago: o(G, g)
155
+ }),
156
+ headerActions: /* @__PURE__ */ S(x, {
157
+ size: "sm",
158
+ variant: "ghost",
159
+ onClick: L,
160
+ "aria-label": g("bigconsole.widget.aiPreview.closeAriaLabel", "Close preview"),
161
+ "data-testid": "ai-preview-close",
162
+ children: /* @__PURE__ */ S(y, { className: "size-4" })
163
+ }),
164
+ labels: s(g)
165
+ })]
166
+ });
228
167
  }
229
168
  //#endregion
230
- export { k as default };
169
+ export { D as AiPreviewPanel };
231
170
 
232
171
  //# sourceMappingURL=AiPreviewPanel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AiPreviewPanel.js","names":[],"sources":["../../../../src/bigconsole/components/preview/AiPreviewPanel.tsx"],"sourcesContent":["/**\n * AI Preview — watch the assistant build, with the real content inline.\n *\n * Opens by itself the moment a prompt starts. A step rail (driven by the agent's\n * own narration) shows DataSink → Dashboard → Parser → Widget filling in, and\n * the four tabs show the ACTUAL content of each entity as it lands — the data\n * sink's rows, the live dashboard, the parser's transformed output, the rendered\n * widget charts — not a list of links. The tab auto-switches to whatever step is\n * being worked on, and each tab has an \"Open\" button that jumps 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 * Section emphasis (BOFF-5639): the panel is a recessed shell (chrome, progress\n * and tabs sit on `bg-bg-sunken`) wrapped around ONE raised result surface — the\n * live entity for the selected tab. That keeps the built thing the loudest part\n * of the panel without adding a second page-level `primary` on every route the\n * panel is injected into.\n */\nimport { useEffect, useState } from 'react';\nimport { Button, Tabs, TabsContent, TabsList, TabsTrigger } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { AlertCircle, ArrowUpRight, Loader2, Sparkles, X } from 'lucide-react';\n\nimport { PREVIEW_STEPS, type PreviewStepKey, useAssistantRunStore } from '../../assistant/assistantRunStore';\nimport { useBigConsoleNavigate } from '../../context';\nimport { Section } from '../common';\nimport {\n PREVIEW_CHROME_DIVIDER,\n PREVIEW_PANEL_SHELL,\n StepRail,\n formatAgo,\n useAssistantSidebarWidth,\n} from './previewShared';\nimport { DashboardContent, DataSinkContent, ParserContent, WidgetsContent } from './AiPreviewContent';\n\n/** Jump-to-the-real-page button, shared by all four tabs. */\nfunction OpenEntityButton({ onOpen }: { onOpen: () => void }) {\n const { t } = useI18n();\n return (\n <Button size=\"sm\" variant=\"outline\" onClick={onOpen} data-testid=\"ai-preview-open-entity\">\n {t('bigconsole.widget.aiPreview.open', 'Open')}\n <ArrowUpRight className=\"ml-1 size-3.5\" />\n </Button>\n );\n}\n\nexport function AiPreviewPanel() {\n const { t } = useI18n();\n const navigate = useBigConsoleNavigate();\n\n const isRunning = useAssistantRunStore((state) => state.isRunning);\n const previewOpen = useAssistantRunStore((state) => state.previewOpen);\n const hasRun = useAssistantRunStore((state) => state.hasRun);\n const steps = useAssistantRunStore((state) => state.steps);\n const entityIds = useAssistantRunStore((state) => state.entityIds);\n const caption = useAssistantRunStore((state) => state.caption);\n const error = useAssistantRunStore((state) => state.error);\n const updatedAt = useAssistantRunStore((state) => state.updatedAt);\n const openPreview = useAssistantRunStore((state) => state.openPreview);\n const closePreview = useAssistantRunStore((state) => state.closePreview);\n const setRichMounted = useAssistantRunStore((state) => state.setRichMounted);\n\n // Tell the app-shell mini panel to stand down while this full panel is on a\n // /bigconsole page. On unmount (e.g. navigating to /workflows mid-run) this\n // flips false and the mini panel takes over so the build stays visible.\n useEffect(() => {\n setRichMounted(true);\n return () => setRichMounted(false);\n }, [setRichMounted]);\n\n const [tab, setTab] = useState<PreviewStepKey>('datasink');\n const [now, setNow] = useState(() => Date.now());\n\n const dashboardId = entityIds.dashboard;\n\n // Tick the \"updated Xs ago\" label while a turn is live.\n useEffect(() => {\n if (!isRunning) return;\n const intervalId = window.setInterval(() => setNow(Date.now()), 1000);\n return () => window.clearInterval(intervalId);\n }, [isRunning]);\n\n // Follow the build by showing the LATEST thing that actually landed — so the\n // moment the data sink is created you see its rows, not an empty \"waiting for\n // the dashboard\" tab. (The rail still shows the spinner on the next step.) If\n // nothing has landed yet, sit on whatever is being worked on.\n useEffect(() => {\n const done = PREVIEW_STEPS.filter((key) => steps[key] === 'done');\n const latestDone = done[done.length - 1];\n if (latestDone) {\n setTab(latestDone);\n return;\n }\n const running = PREVIEW_STEPS.find((key) => steps[key] === 'running');\n if (running) setTab(running);\n }, [steps]);\n\n const agoSeconds = updatedAt ? Math.max(0, Math.floor((now - updatedAt) / 1000)) : 0;\n\n // Keep clear of the assistant.\n //\n // The assistant sidebar is `position: fixed` — it OVERLAYS the page instead of\n // reflowing it. A panel that spans the full content width therefore runs\n // underneath it, and everything in that overlapped strip (tabs, Open buttons)\n // silently stops taking clicks. Measure the real element rather than hardcoding\n // 560px: the widget is responsive, and it can be closed mid-run.\n const assistantWidth = useAssistantSidebarWidth();\n\n if (!hasRun) return null;\n\n if (!previewOpen) {\n return (\n <div\n className=\"flex flex-wrap items-center justify-end gap-control-gap px-4 pt-3 md:px-6\"\n style={{ marginRight: assistantWidth }}\n >\n {/* The lone button gave no clue what it belonged to. */}\n <span className=\"text-xs text-text-muted\">{isRunning ? 'AI build running' : 'AI build finished'}</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 return (\n <section\n data-testid=\"ai-preview-panel\"\n aria-label={t('bigconsole.widget.aiPreview.ariaLabel', 'AI preview')}\n style={{ marginRight: assistantWidth ? assistantWidth + 16 : undefined }}\n className={`${PREVIEW_PANEL_SHELL} mx-4 mt-3 md:mx-6`}\n >\n {/* Header + live strip. Title/badge on the first line, the caption on its\n own line: it is the most useful string during a run and it used to be\n the first thing to truncate away. */}\n <div\n className={`flex items-start justify-between gap-control-gap bg-bg-surface px-3 py-2 ${PREVIEW_CHROME_DIVIDER}`}\n >\n <div className=\"flex min-w-0 flex-col gap-0.5\">\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n <Sparkles className=\"size-4 shrink-0 text-action-primary-bg\" />\n <h2 className=\"text-sm font-semibold text-text-primary\">\n {t('bigconsole.widget.aiPreview.title', 'AI Preview')}\n </h2>\n {isRunning ? (\n <span\n className=\"inline-flex items-center gap-1.5 rounded-full bg-status-success-bg px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-status-success-text\"\n data-testid=\"ai-preview-live\"\n >\n <span className=\"relative flex size-2\">\n <span className=\"absolute inline-flex size-full 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 {t('bigconsole.widget.aiPreview.live', 'Live')}\n </span>\n ) : null}\n </div>\n <span className=\"min-w-0 break-words text-xs text-text-muted\" data-testid=\"ai-preview-ago\">\n {isRunning\n ? caption\n ? caption\n : t('bigconsole.widget.aiPreview.working', 'Working…')\n : error\n ? t('bigconsole.widget.aiPreview.stopped', 'Stopped')\n : t('bigconsole.widget.aiPreview.done', 'Done')}{' '}\n · {t('bigconsole.widget.aiPreview.updated', 'updated')} {formatAgo(agoSeconds)}\n </span>\n </div>\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 </div>\n\n {/* Step rail — the panel's progress narrative, now a labelled block\n instead of four unexplained pills. */}\n <div className={`px-3 py-2 ${PREVIEW_CHROME_DIVIDER}`}>\n <h3 className=\"mb-1 text-[11px] font-semibold uppercase tracking-wide text-text-muted\">Build progress</h3>\n <StepRail steps={steps} />\n </div>\n\n {/* Tabs — each shows the real content of the built entity */}\n <Tabs value={tab} onValueChange={(value) => setTab(value as PreviewStepKey)}>\n {/* fe-libs TabsList is `w-fit inline-flex` with nowrap triggers and no\n overflow handling, inside a panel that is `overflow-hidden` — below\n ~430px the fourth tab was clipped instead of reachable. */}\n <div className=\"min-w-0 overflow-x-auto px-3 pt-2\">\n <TabsList className=\"w-max\">\n <TabsTrigger value=\"datasink\" data-testid=\"ai-preview-tab-datasink\">\n {t('bigconsole.widget.aiPreview.tab.dataSink', 'Data Sink')}\n </TabsTrigger>\n <TabsTrigger value=\"dashboard\" data-testid=\"ai-preview-tab-dashboard\">\n {t('bigconsole.widget.aiPreview.tab.dashboard', 'Dashboard')}\n </TabsTrigger>\n <TabsTrigger value=\"parser\" data-testid=\"ai-preview-tab-parser\">\n {t('bigconsole.widget.aiPreview.tab.parser', 'Parser')}\n </TabsTrigger>\n <TabsTrigger value=\"widget\" data-testid=\"ai-preview-tab-widget\">\n {t('bigconsole.widget.aiPreview.tab.widget', 'Widget')}\n </TabsTrigger>\n </TabsList>\n </div>\n\n {/* The hard 440px cap ate most of the fold on a short laptop screen. */}\n <div className=\"min-w-0 overflow-y-auto p-3\" style={{ maxHeight: 'min(440px, 60vh)' }}>\n <TabsContent value=\"datasink\">\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title=\"Data sink rows\"\n description=\"The rows the assistant just landed, straight from the sink.\"\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 </TabsContent>\n\n <TabsContent value=\"dashboard\">\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title={t('bigconsole.widget.aiPreview.tab.dashboard', 'Dashboard')}\n description=\"The live board, rendered exactly as the real page renders it.\"\n actions={\n entityIds.dashboard ? (\n <OpenEntityButton onOpen={() => navigate(`/dashboards/${entityIds.dashboard ?? ''}`)} />\n ) : undefined\n }\n >\n <DashboardContent dashboardId={entityIds.dashboard} />\n </Section>\n </TabsContent>\n\n <TabsContent value=\"parser\">\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title=\"Parser output\"\n description=\"The rows after the parser's transform, so you can check the shape.\"\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 </TabsContent>\n\n <TabsContent value=\"widget\">\n <Section\n emphasis=\"standard\"\n padding=\"compact\"\n headingLevel={3}\n title=\"Widgets\"\n description=\"Every widget on the new dashboard, rendered live.\"\n actions={\n dashboardId ? <OpenEntityButton onOpen={() => navigate(`/dashboards/${dashboardId}`)} /> : undefined\n }\n >\n <WidgetsContent dashboardId={dashboardId} live={isRunning} />\n </Section>\n </TabsContent>\n </div>\n </Tabs>\n\n {/* A failed run used to be 12px red text in a bare footer — the quietest\n treatment on the panel for its most important message. */}\n {error ? (\n <div\n role=\"alert\"\n className=\"border-t border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] px-3 py-2\"\n >\n <h3 className=\"text-[11px] font-semibold uppercase tracking-wide text-[var(--color-status-error-text)]\">\n Build stopped\n </h3>\n <p\n className=\"mt-0.5 flex items-start gap-icon-gap text-xs text-[var(--color-status-error-text)]\"\n data-testid=\"ai-preview-error\"\n >\n <AlertCircle className=\"mt-0.5 size-3.5 shrink-0\" aria-hidden=\"true\" />\n <span className=\"min-w-0 break-words\">{error}</span>\n </p>\n </div>\n ) : null}\n </section>\n );\n}\n\nexport default AiPreviewPanel;\n"],"mappings":"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"}
1
+ {"version":3,"file":"AiPreviewPanel.js","names":[],"sources":["../../../../src/bigconsole/components/preview/AiPreviewPanel.tsx"],"sourcesContent":["/**\n * AI Preview — watch the assistant build, with the real content inline.\n *\n * Opens by itself the moment a prompt starts. A step rail (driven by the agent's\n * own narration) shows DataSink → Dashboard → Parser → Widget filling in, and\n * the four tabs show the ACTUAL content of each entity as it lands — the data\n * sink's rows, the live dashboard, the parser's transformed output, the rendered\n * widget charts — not a list of links. Each tab has an \"Open\" button that jumps\n * to the real page.\n *\n * The content renderers need Apollo + the BigConsole providers, which is why\n * this full panel lives inside BigConsoleRoot; the store-only app-shell mini\n * panel (AiPreviewMiniPanel) keeps the lighter link view for other pages.\n *\n * ★★★ THE SHELL, RAIL AND TABS ARE fe-libs' NOW (`AssistantEntityPreview`).\n * What stays here is what is actually BigConsole's: which entities exist, how to\n * render each one, and where the panel sits relative to the assistant sidebar.\n * The shared surface brought three things this file could not have on its own:\n *\n * - a viewer's tab choice is HONOURED. The local `useEffect` re-selected a tab\n * on every store tick, so clicking one held for about 1.5 seconds before the\n * next progress update took it back — you could not read a tab while the\n * build continued.\n * - the rail can show a FAILED step instead of drawing a failure as \"not\n * started\" (the store still cannot produce one — see `assistantRunStore` —\n * but the surface no longer loses it if it ever does).\n * - keyboard navigation across the tabs, which the Radix tablist here never\n * had overflow handling for below ~430px.\n */\nimport { useEffect, useRef, useState, type ReactNode } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport {\n AssistantEntityPreview,\n type AssistantPreviewStep,\n type AssistantPreviewTab,\n} from '@burdenoff/fe-libs/shared/assistant';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowUpRight, Loader2, Sparkles, X } from 'lucide-react';\n\nimport { PREVIEW_STEPS, useAssistantRunStore } from '../../assistant/assistantRunStore';\nimport { useBigConsoleNavigate } from '../../context';\nimport { Section } from '../common';\nimport { PreviewMeta, STEP_LABEL, formatAgo, previewLabels, useAssistantSidebarWidth } from './previewShared';\nimport { DashboardContent, DataSinkContent, ParserContent, WidgetsContent } from './AiPreviewContent';\n\n/**\n * The scroll box each tab's content sits in.\n *\n * ★★ The cap is NOT cosmetic and does not belong in fe-libs: the shared surface\n * is laid out by whatever contains it, and this panel is injected ABOVE a\n * routed page — so without a cap a long data-sink preview pushes the whole page\n * down. `min(440px, 60vh)` is the original value; the hard 440px alone ate most\n * of the fold on a short laptop screen.\n */\nfunction TabBody({ children }: { children: ReactNode }) {\n return (\n <div className=\"min-w-0 overflow-y-auto p-3\" style={{ maxHeight: 'min(440px, 60vh)' }}>\n {children}\n </div>\n );\n}\n\n/** Jump-to-the-real-page button, shared by all four tabs. */\nfunction OpenEntityButton({ onOpen }: { onOpen: () => void }) {\n const { t } = useI18n();\n return (\n <Button size=\"sm\" variant=\"outline\" onClick={onOpen} data-testid=\"ai-preview-open-entity\">\n {t('bigconsole.widget.aiPreview.open', 'Open')}\n <ArrowUpRight className=\"ml-1 size-3.5\" />\n </Button>\n );\n}\n\nexport function AiPreviewPanel() {\n const { t } = useI18n();\n const navigate = useBigConsoleNavigate();\n\n const isRunning = useAssistantRunStore((state) => state.isRunning);\n const previewOpen = useAssistantRunStore((state) => state.previewOpen);\n const hasRun = useAssistantRunStore((state) => state.hasRun);\n const steps = useAssistantRunStore((state) => state.steps);\n const entityIds = useAssistantRunStore((state) => state.entityIds);\n const caption = useAssistantRunStore((state) => state.caption);\n const error = useAssistantRunStore((state) => state.error);\n const updatedAt = useAssistantRunStore((state) => state.updatedAt);\n const openPreview = useAssistantRunStore((state) => state.openPreview);\n const closePreview = useAssistantRunStore((state) => state.closePreview);\n const setRichMounted = useAssistantRunStore((state) => state.setRichMounted);\n\n useEffect(() => {\n setRichMounted(true);\n return () => {\n setRichMounted(false);\n };\n }, [setRichMounted]);\n\n const [now, setNow] = useState(() => Date.now());\n /**\n * ★★ Ticks while the panel is OPEN, not only while a turn is running.\n *\n * Gating on `isRunning` froze the \"updated …\" strip the instant a build\n * finished: a panel left open for an hour still claimed it was updated\n * \"just now\", which is the one moment the reading is least trustworthy.\n */\n useEffect(() => {\n if (!previewOpen) return;\n // ★ Re-read on OPEN, not only on the first tick. `now` still holds the\n // PREVIOUS open's value, so without this a panel reopened after an hour\n // reads \"just now\" for a second before snapping to \"60m ago\" — the same\n // frozen-timestamp bug this effect exists to fix, narrowed to the reopen.\n // This trips the cascading-render lint warning; reading the clock in render\n // instead is an ESLint *error* (Date.now is impure), so the warning is the\n // cheaper of the two. One extra render on open is exactly what is wanted.\n setNow(Date.now());\n const intervalId = window.setInterval(() => setNow(Date.now()), 1000);\n return () => window.clearInterval(intervalId);\n }, [previewOpen]);\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 // Keep clear of the assistant.\n //\n // The assistant sidebar is `position: fixed` — it OVERLAYS the page instead of\n // reflowing it. A panel that spans the full content width therefore runs\n // underneath it, and everything in that overlapped strip (tabs, Open buttons)\n // silently stops taking clicks. Measure the real element rather than hardcoding\n // 560px: the widget is responsive, and it can be closed mid-run.\n // ★ Gated: no build, no poll. See the hook's docblock.\n const assistantWidth = useAssistantSidebarWidth(hasRun);\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: assistantWidth }}\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 agoSeconds = updatedAt ? Math.max(0, Math.floor((now - updatedAt) / 1000)) : 0;\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 — margin against the assistant\n // sidebar, page gutters, and the testid BigConsole already had. Everything\n // visual belongs to the shared surface.\n <div\n data-testid=\"ai-preview-panel\"\n style={{ marginRight: assistantWidth ? assistantWidth + 16 : undefined }}\n className=\"mx-4 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.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 ago={formatAgo(agoSeconds, 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,69 +1,80 @@
1
- import { SECTION_MOTION as e } from "../common/sectionStyles.js";
2
- import "../common/index.js";
3
- import { PREVIEW_STEPS as t, PREVIEW_STEP_LABEL as n } from "../../assistant/assistantRunStore.js";
4
- import { useEffect as r, useState as i } from "react";
5
- import { Check as a, ChevronRight as o, Loader2 as s, Minus as c } from "lucide-react";
6
- import { jsx as l, jsxs as u } from "react/jsx-runtime";
1
+ import { useEffect as e, useState as t } from "react";
2
+ import { jsx as n, jsxs as r } from "react/jsx-runtime";
7
3
  //#region src/bigconsole/components/preview/previewShared.tsx
8
- var d = [
9
- "min-w-0 overflow-hidden rounded-card border border-[var(--color-border-subtle)]",
10
- "bg-bg-sunken shadow-[var(--shadow-elevation-1)]",
11
- e
12
- ].join(" "), f = "border-b border-[var(--color-border-subtle)]";
13
- function p({ status: e }) {
14
- return e === "done" ? /* @__PURE__ */ l(a, { className: "size-3.5 text-status-success-text" }) : e === "running" ? /* @__PURE__ */ l(s, { className: "size-3.5 animate-spin text-action-primary-bg" }) : e === "skipped" ? /* @__PURE__ */ l(c, { className: "size-3.5 text-text-muted" }) : /* @__PURE__ */ l("span", { className: "size-1.5 rounded-full bg-[var(--color-border-strong)]" });
4
+ function i(e, t) {
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) });
15
6
  }
16
- function m(e) {
17
- let t = "inline-flex shrink-0 items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs";
18
- return e === "done" ? `${t} border-status-success-border bg-status-success-bg font-medium text-status-success-text` : e === "running" ? `${t} border-[var(--color-action-primary-bg)] bg-bg-surface font-semibold text-text-primary shadow-[var(--shadow-elevation-1)]` : `${t} border-[var(--color-border-subtle)] font-medium text-text-muted`;
19
- }
20
- function h({ steps: e }) {
21
- return /* @__PURE__ */ l("div", {
22
- role: "list",
23
- className: "flex min-w-0 items-center gap-1 overflow-x-auto pb-0.5",
24
- "data-testid": "ai-preview-step-rail",
25
- "aria-label": "Build steps",
26
- children: t.map((r, i) => /* @__PURE__ */ u("div", {
27
- role: "listitem",
28
- className: "flex shrink-0 items-center gap-1",
29
- children: [/* @__PURE__ */ u("span", {
30
- "data-testid": `ai-preview-step-${r}`,
31
- "data-status": e[r],
32
- "aria-current": e[r] === "running" ? "step" : void 0,
33
- className: m(e[r]),
34
- children: [
35
- /* @__PURE__ */ l(p, { status: e[r] }),
36
- n[r],
37
- e[r] === "skipped" ? /* @__PURE__ */ l("span", {
38
- className: "text-[10px]",
39
- children: "skipped"
40
- }) : null
41
- ]
42
- }), i < t.length - 1 ? /* @__PURE__ */ l(o, {
43
- className: "size-3.5 shrink-0 text-text-muted",
44
- "aria-hidden": "true"
45
- }) : null]
46
- }, r))
47
- });
48
- }
49
- function g(e) {
50
- return e < 5 ? "just now" : e < 60 ? `${String(e)}s ago` : `${String(Math.floor(e / 60))}m ago`;
51
- }
52
- function _() {
53
- let [e, t] = i(0);
54
- return r(() => {
7
+ function a(n) {
8
+ let [r, i] = t(0);
9
+ return e(() => {
10
+ if (!n) {
11
+ i(0);
12
+ return;
13
+ }
55
14
  let e = () => {
56
- let e = document.querySelector("[data-testid=\"assistant-widget-window\"][data-layout=\"sidebar\"]")?.getBoundingClientRect(), n = e && e.width < window.innerWidth * .75 ? Math.round(e.width) : 0;
57
- t((e) => e === n ? e : n);
15
+ let e = document.querySelector("[data-testid=\"assistant-widget-window\"][data-layout=\"sidebar\"]")?.getBoundingClientRect(), t = e && e.width < window.innerWidth * .75 ? Math.round(e.width) : 0;
16
+ i((e) => e === t ? e : t);
58
17
  };
59
18
  e();
60
- let n = window.setInterval(e, 500);
19
+ let t = window.setInterval(e, 500);
61
20
  return window.addEventListener("resize", e), () => {
62
- window.clearInterval(n), window.removeEventListener("resize", e);
21
+ window.clearInterval(t), window.removeEventListener("resize", e);
63
22
  };
64
- }, []), e;
23
+ }, [n]), r;
24
+ }
25
+ var o = {
26
+ datasink: {
27
+ key: "bigconsole.widget.aiPreview.tab.dataSink",
28
+ fallback: "Data Sink"
29
+ },
30
+ dashboard: {
31
+ key: "bigconsole.widget.aiPreview.tab.dashboard",
32
+ fallback: "Dashboard"
33
+ },
34
+ parser: {
35
+ key: "bigconsole.widget.aiPreview.tab.parser",
36
+ fallback: "Parser"
37
+ },
38
+ widget: {
39
+ key: "bigconsole.widget.aiPreview.tab.widget",
40
+ fallback: "Widget"
41
+ }
42
+ };
43
+ function s(e) {
44
+ let t = e("bigconsole.widget.aiPreview.buildProgress", "Build progress"), n = e("bigconsole.widget.aiPreview.stepSkipped", "skipped");
45
+ return {
46
+ steps: t,
47
+ stepsHeading: t,
48
+ skipped: n,
49
+ stepStatus: {
50
+ pending: e("bigconsole.widget.aiPreview.stepPending", "not started"),
51
+ running: e("bigconsole.widget.aiPreview.stepRunning", "running"),
52
+ done: e("bigconsole.widget.aiPreview.stepDone", "completed"),
53
+ failed: e("bigconsole.widget.aiPreview.stepFailed", "failed"),
54
+ skipped: n
55
+ }
56
+ };
57
+ }
58
+ function c({ state: e, stateLabel: t, updatedLabel: i, ago: a }) {
59
+ let o = "inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide";
60
+ return /* @__PURE__ */ r("span", {
61
+ className: "inline-flex items-center gap-2",
62
+ "data-testid": "ai-preview-ago",
63
+ children: [e !== "idle" && t ? /* @__PURE__ */ r("span", {
64
+ className: e === "running" ? `${o} bg-[var(--color-status-success-bgSubtle)] text-status-success-text` : `${o} bg-[var(--color-status-error-bgSubtle)] text-status-error-text`,
65
+ "data-testid": e === "running" ? "ai-preview-live" : "ai-preview-stopped",
66
+ children: [e === "running" ? /* @__PURE__ */ r("span", {
67
+ className: "relative flex size-2",
68
+ children: [/* @__PURE__ */ n("span", { className: "absolute inline-flex size-full rounded-full bg-status-success-text opacity-75 motion-safe:animate-ping" }), /* @__PURE__ */ n("span", { className: "relative inline-flex size-2 rounded-full bg-status-success-text" })]
69
+ }) : /* @__PURE__ */ n("span", { className: "relative inline-flex size-2 rounded-full bg-status-error-text" }), t]
70
+ }) : null, /* @__PURE__ */ r("span", { children: [
71
+ i,
72
+ " ",
73
+ a
74
+ ] })]
75
+ });
65
76
  }
66
77
  //#endregion
67
- export { f as PREVIEW_CHROME_DIVIDER, d as PREVIEW_PANEL_SHELL, h as StepRail, g as formatAgo, _ as useAssistantSidebarWidth };
78
+ export { c as PreviewMeta, o as STEP_LABEL, i as formatAgo, s as previewLabels, a as useAssistantSidebarWidth };
68
79
 
69
80
  //# sourceMappingURL=previewShared.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"previewShared.js","names":[],"sources":["../../../../src/bigconsole/components/preview/previewShared.tsx"],"sourcesContent":["/**\n * Presentational bits shared by the two AI-preview surfaces:\n * - AiPreviewPanel — the full panel with live entity tabs, mounted inside\n * BigConsoleRoot (needs the BigConsole Apollo context).\n * - AiPreviewMiniPanel — the lightweight, store-only panel mounted by the app\n * shell so the build stays visible on ANY page.\n *\n * Nothing here touches Apollo, React Query, or BigConsole context — it is pure\n * store-shape + DOM, which is exactly why the mini panel can live in the app\n * shell without dragging the data layer along.\n */\nimport { useEffect, useState } from 'react';\nimport { Check, ChevronRight, Loader2, Minus } from 'lucide-react';\n\nimport {\n PREVIEW_STEP_LABEL,\n PREVIEW_STEPS,\n type PreviewStepKey,\n type PreviewStepStatus,\n} from '../../assistant/assistantRunStore';\nimport { SECTION_MOTION } from '../common';\n\n/**\n * The panel shell, shared by both preview surfaces.\n *\n * `rounded-lg` + `border-border-default` used to make this a fourth surface\n * recipe in a product that otherwise uses `rounded-card` and the theme-aware\n * `--color-border-subtle` — and the plain `border-*` utilities are registered\n * as frozen light hex in this shell, so they do not follow dark mode. Both\n * panels now read the recipe from here, so they cannot drift apart.\n */\nexport const PREVIEW_PANEL_SHELL = [\n 'min-w-0 overflow-hidden rounded-card border border-[var(--color-border-subtle)]',\n 'bg-bg-sunken shadow-[var(--shadow-elevation-1)]',\n SECTION_MOTION,\n].join(' ');\n\n/** Chrome strips (header, progress, tabs) share one recessed treatment. */\nexport const PREVIEW_CHROME_DIVIDER = 'border-b border-[var(--color-border-subtle)]';\n\nexport function StepPip({ status }: { status: PreviewStepStatus }) {\n if (status === 'done') return <Check className=\"size-3.5 text-status-success-text\" />;\n if (status === 'running') return <Loader2 className=\"size-3.5 animate-spin text-action-primary-bg\" />;\n if (status === 'skipped') return <Minus className=\"size-3.5 text-text-muted\" />;\n // `bg-border-strong` is registered as a frozen light hex in this shell, so the\n // not-started pip stayed light grey in dark mode.\n return <span className=\"size-1.5 rounded-full bg-[var(--color-border-strong)]\" />;\n}\n\n/**\n * Per-step chip treatment. The step you are ON has to out-rank a not-started\n * step at a glance — they used to differ only by border shade, which is the\n * weakest possible signal for the panel's only progress narrative.\n */\nfunction stepChipClasses(status: PreviewStepStatus): string {\n // `rounded-full` rather than `rounded-pill`: this rail also renders inside the\n // app shell's own Tailwind build, and `rounded-full` needs no token registry.\n const base = 'inline-flex shrink-0 items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs';\n if (status === 'done') {\n return `${base} border-status-success-border bg-status-success-bg font-medium text-status-success-text`;\n }\n if (status === 'running') {\n return `${base} border-[var(--color-action-primary-bg)] bg-bg-surface font-semibold text-text-primary shadow-[var(--shadow-elevation-1)]`;\n }\n return `${base} border-[var(--color-border-subtle)] font-medium text-text-muted`;\n}\n\nexport function StepRail({ steps }: { steps: Record<PreviewStepKey, PreviewStepStatus> }) {\n return (\n // One scrolling line rather than `flex-wrap`: wrapping left chevrons\n // dangling at the end of a line, pointing at nothing.\n //\n // `role=\"list\"` is what makes the `aria-label` count: an aria-label on a\n // role-less <div> is dropped by every screen reader, so the rail would have\n // been announced as four unrelated strings.\n <div\n role=\"list\"\n className=\"flex min-w-0 items-center gap-1 overflow-x-auto pb-0.5\"\n data-testid=\"ai-preview-step-rail\"\n aria-label=\"Build steps\"\n >\n {PREVIEW_STEPS.map((key, index) => (\n <div key={key} role=\"listitem\" className=\"flex shrink-0 items-center gap-1\">\n <span\n data-testid={`ai-preview-step-${key}`}\n data-status={steps[key]}\n aria-current={steps[key] === 'running' ? 'step' : undefined}\n className={stepChipClasses(steps[key])}\n >\n <StepPip status={steps[key]} />\n {PREVIEW_STEP_LABEL[key]}\n {steps[key] === 'skipped' ? <span className=\"text-[10px]\">skipped</span> : null}\n </span>\n {index < PREVIEW_STEPS.length - 1 ? (\n <ChevronRight className=\"size-3.5 shrink-0 text-text-muted\" aria-hidden=\"true\" />\n ) : null}\n </div>\n ))}\n </div>\n );\n}\n\nexport function formatAgo(seconds: number): string {\n if (seconds < 5) return 'just now';\n if (seconds < 60) return `${String(seconds)}s ago`;\n return `${String(Math.floor(seconds / 60))}m ago`;\n}\n\n/**\n * Width of the assistant sidebar, or 0 when it is closed.\n *\n * Polled rather than subscribed: the widget lives in fe-libs, is mounted by the\n * app shell, and emits nothing when it opens or closes. Reading the DOM is the\n * only honest signal available, and it is cheap.\n */\nexport function useAssistantSidebarWidth(): number {\n const [width, setWidth] = useState(0);\n\n useEffect(() => {\n const measure = () => {\n const el = document.querySelector('[data-testid=\"assistant-widget-window\"][data-layout=\"sidebar\"]');\n const rect = el?.getBoundingClientRect();\n // Below `sm` the sidebar is full-width; reserving that would leave no room\n // for the preview at all, so on small screens let it stack underneath.\n const next = rect && rect.width < window.innerWidth * 0.75 ? Math.round(rect.width) : 0;\n setWidth((current) => (current === next ? current : next));\n };\n measure();\n const intervalId = window.setInterval(measure, 500);\n window.addEventListener('resize', measure);\n return () => {\n window.clearInterval(intervalId);\n window.removeEventListener('resize', measure);\n };\n }, []);\n\n return width;\n}\n"],"mappings":";;;;;;;AA+BA,IAAa,IAAsB;CACjC;CACA;CACA;CACD,CAAC,KAAK,IAAI,EAGE,IAAyB;AAEtC,SAAgB,EAAQ,EAAE,aAAyC;AAMjE,QALI,MAAW,SAAe,kBAAC,GAAD,EAAO,WAAU,qCAAsC,CAAA,GACjF,MAAW,YAAkB,kBAAC,GAAD,EAAS,WAAU,gDAAiD,CAAA,GACjG,MAAW,YAAkB,kBAAC,GAAD,EAAO,WAAU,4BAA6B,CAAA,GAGxE,kBAAC,QAAD,EAAM,WAAU,yDAA0D,CAAA;;AAQnF,SAAS,EAAgB,GAAmC;CAG1D,IAAM,IAAO;AAOb,QANI,MAAW,SACN,GAAG,EAAK,2FAEb,MAAW,YACN,GAAG,EAAK,6HAEV,GAAG,EAAK;;AAGjB,SAAgB,EAAS,EAAE,YAA+D;AACxF,QAOE,kBAAC,OAAD;EACE,MAAK;EACL,WAAU;EACV,eAAY;EACZ,cAAW;YAEV,EAAc,KAAK,GAAK,MACvB,kBAAC,OAAD;GAAe,MAAK;GAAW,WAAU;aAAzC,CACE,kBAAC,QAAD;IACE,eAAa,mBAAmB;IAChC,eAAa,EAAM;IACnB,gBAAc,EAAM,OAAS,YAAY,SAAS,KAAA;IAClD,WAAW,EAAgB,EAAM,GAAK;cAJxC;KAME,kBAAC,GAAD,EAAS,QAAQ,EAAM,IAAQ,CAAA;KAC9B,EAAmB;KACnB,EAAM,OAAS,YAAY,kBAAC,QAAD;MAAM,WAAU;gBAAc;MAAc,CAAA,GAAG;KACtE;OACN,IAAQ,EAAc,SAAS,IAC9B,kBAAC,GAAD;IAAc,WAAU;IAAoC,eAAY;IAAS,CAAA,GAC/E,KACA;KAdI,EAcJ,CACN;EACE,CAAA;;AAIV,SAAgB,EAAU,GAAyB;AAGjD,QAFI,IAAU,IAAU,aACpB,IAAU,KAAW,GAAG,OAAO,EAAQ,CAAC,SACrC,GAAG,OAAO,KAAK,MAAM,IAAU,GAAG,CAAC,CAAC;;AAU7C,SAAgB,IAAmC;CACjD,IAAM,CAAC,GAAO,KAAY,EAAS,EAAE;AAoBrC,QAlBA,QAAgB;EACd,IAAM,UAAgB;GAEpB,IAAM,IADK,SAAS,cAAc,qEAAiE,EAClF,uBAAuB,EAGlC,IAAO,KAAQ,EAAK,QAAQ,OAAO,aAAa,MAAO,KAAK,MAAM,EAAK,MAAM,GAAG;AACtF,MAAU,MAAa,MAAY,IAAO,IAAU,EAAM;;AAE5D,KAAS;EACT,IAAM,IAAa,OAAO,YAAY,GAAS,IAAI;AAEnD,SADA,OAAO,iBAAiB,UAAU,EAAQ,QAC7B;AAEX,GADA,OAAO,cAAc,EAAW,EAChC,OAAO,oBAAoB,UAAU,EAAQ;;IAE9C,EAAE,CAAC,EAEC"}
1
+ {"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 * sidebar, and how it words a relative timestamp.\n *\n * Nothing here touches Apollo, React Query, or BigConsole context — it is pure\n * DOM + formatting, which is why the mini panel can live in the app shell\n * 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 * Width of the assistant sidebar, or 0 when it is closed.\n *\n * Polled rather than subscribed: the widget lives in fe-libs, is mounted by the\n * app shell, and emits nothing when it opens or closes. Reading the DOM is the\n * only honest signal available, and it is cheap.\n *\n * ★★★ `enabled` IS NOT AN OPTIMISATION, IT IS THE FIX. The mini panel is\n * mounted by the app shell on every page, and a hook cannot be called below an\n * early return — so this 500ms poll ran for the lifetime of the tab on every\n * route, for every user, whether or not the assistant had ever been opened.\n * Moving the call site was impossible; making the hook do nothing was not.\n */\nexport function useAssistantSidebarWidth(enabled: boolean): number {\n const [width, setWidth] = useState(0);\n\n useEffect(() => {\n if (!enabled) {\n setWidth(0);\n return;\n }\n const measure = () => {\n const el = document.querySelector('[data-testid=\"assistant-widget-window\"][data-layout=\"sidebar\"]');\n const rect = el?.getBoundingClientRect();\n // Below `sm` the sidebar is full-width; reserving that would leave no room\n // for the preview at all, so on small screens let it stack underneath.\n const next = rect && rect.width < window.innerWidth * 0.75 ? Math.round(rect.width) : 0;\n setWidth((current) => (current === next ? current : next));\n };\n measure();\n const intervalId = window.setInterval(measure, 500);\n window.addEventListener('resize', measure);\n return () => {\n window.clearInterval(intervalId);\n window.removeEventListener('resize', measure);\n };\n }, [enabled]);\n\n return width;\n}\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 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 ago,\n}: {\n state: 'running' | 'stopped' | 'idle';\n stateLabel: string | null;\n updatedLabel: string;\n ago: string;\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}\n </span>\n </span>\n );\n}\n"],"mappings":";;;AAiCA,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;;AAgBnG,SAAgB,EAAyB,GAA0B;CACjE,IAAM,CAAC,GAAO,KAAY,EAAS,EAAE;AAwBrC,QAtBA,QAAgB;AACd,MAAI,CAAC,GAAS;AACZ,KAAS,EAAE;AACX;;EAEF,IAAM,UAAgB;GAEpB,IAAM,IADK,SAAS,cAAc,qEAAiE,EAClF,uBAAuB,EAGlC,IAAO,KAAQ,EAAK,QAAQ,OAAO,aAAa,MAAO,KAAK,MAAM,EAAK,MAAM,GAAG;AACtF,MAAU,MAAa,MAAY,IAAO,IAAU,EAAM;;AAE5D,KAAS;EACT,IAAM,IAAa,OAAO,YAAY,GAAS,IAAI;AAEnD,SADA,OAAO,iBAAiB,UAAU,EAAQ,QAC7B;AAEX,GADA,OAAO,cAAc,EAAW,EAChC,OAAO,oBAAoB,UAAU,EAAQ;;IAE9C,CAAC,EAAQ,CAAC,EAEN;;AAcT,IAAa,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;EACL,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,UAMC;CACD,IAAM,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;GACX,EAAA,CAAA,CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-bigconsole",
3
- "version": "2026.830.1",
3
+ "version": "2026.912.1",
4
4
  "description": "BigConsole - AI-powered analytics and dashboard platform",
5
5
  "type": "module",
6
6
  "files": [
@@ -48,7 +48,7 @@
48
48
  },
49
49
  "peerDependencies": {
50
50
  "@apollo/client": "^4.0.11",
51
- "@burdenoff/fe-libs": "2026.801.1",
51
+ "@burdenoff/fe-libs": "2026.912.2",
52
52
  "@burdenoff/fluidgrids-fe-sdk": "2026.731.4",
53
53
  "@iconify/react": "^6.0.2",
54
54
  "@monaco-editor/react": "^4.7.0",
@@ -83,6 +83,7 @@
83
83
  "zustand": "^5.0.3"
84
84
  },
85
85
  "devDependencies": {
86
+ "@burdenoff/fe-libs": "2026.912.2",
86
87
  "@burdenoff/fluidgrids-fe-sdk": "2026.731.4",
87
88
  "@eslint/js": "^10.0.1",
88
89
  "@graphql-codegen/add": "^5.0.0",
@@ -92,12 +93,6 @@
92
93
  "@graphql-codegen/typescript": "^4.0.0",
93
94
  "@graphql-codegen/typescript-operations": "^4.0.0",
94
95
  "@graphql-codegen/typescript-react-apollo": "^4.0.0",
95
- "@types/react": "^19.0.2",
96
- "@types/react-dom": "^19.0.2",
97
- "@types/leaflet": "^1.9.21",
98
- "@types/react-grid-layout": "^1.3.5",
99
- "@types/uuid": "^11.0.0",
100
- "@vitejs/plugin-react": "^4.3.4",
101
96
  "@storybook/addon-essentials": "^8.6.0",
102
97
  "@storybook/addon-interactions": "^8.6.0",
103
98
  "@storybook/react": "^8.6.0",
@@ -105,6 +100,12 @@
105
100
  "@storybook/test": "^8.6.0",
106
101
  "@storybook/test-runner": "0.22.0",
107
102
  "@tailwindcss/vite": "^4.3.2",
103
+ "@types/leaflet": "^1.9.21",
104
+ "@types/react": "^19.0.2",
105
+ "@types/react-dom": "^19.0.2",
106
+ "@types/react-grid-layout": "^1.3.5",
107
+ "@types/uuid": "^11.0.0",
108
+ "@vitejs/plugin-react": "^4.3.4",
108
109
  "autoprefixer": "^10.4.20",
109
110
  "eslint": "^9.18.0",
110
111
  "eslint-plugin-react": "^7.37.5",