@burdenoff/microfe-bigconsole 2026.1001.1 → 2026.1001.3

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.
@@ -7,17 +7,9 @@ export declare function DashboardContent({ dashboardId, live }: {
7
7
  live?: boolean;
8
8
  }): import("react").JSX.Element;
9
9
  /**
10
- * ★★★ BC0 — READ-ONLY. This tab used to call `executeParser`, a mutation, on
11
- * every poll: ~15 server-side re-executions per turn purely to look at output.
12
- *
13
- * A parser writes its transformed rows into a data sink named by its own
14
- * `outputKey` (`ParserCoreFields`), and `getDataSinkData` accepts `{ key }`. So
15
- * the output is fully readable without executing anything: read the parser,
16
- * follow `outputKey`, read the sink.
17
- *
18
- * ★ Apollo is queried directly rather than via `useParserOperations.fetchParser`,
19
- * because that helper calls `addParser()` and would publish into the global
20
- * parser store — trading defect 2's leak for the same leak one store over.
10
+ * Read-only evaluation of the saved expression against up to 50 current source
11
+ * rows. outputKey is a widget binding, not a physical DataSink. The governed
12
+ * query applies masking and returns sampling metadata; it persists no output.
21
13
  */
22
14
  export declare function ParserContent({ parserId, live }: {
23
15
  parserId?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"AiPreviewContent.d.ts","sourceRoot":"","sources":["../../../../src/bigconsole/components/preview/AiPreviewContent.tsx"],"names":[],"mappings":"AA4JA,wBAAgB,eAAe,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,+BAmB3E;AAED,wBAAgB,gBAAgB,CAAC,EAAE,WAAW,EAAE,IAAY,EAAE,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,+BAiBvG;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,+BA+BrF;AA6CD,wBAAgB,cAAc,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,+BA0B5F"}
1
+ {"version":3,"file":"AiPreviewContent.d.ts","sourceRoot":"","sources":["../../../../src/bigconsole/components/preview/AiPreviewContent.tsx"],"names":[],"mappings":"AA6JA,wBAAgB,eAAe,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,+BAmB3E;AAED,wBAAgB,gBAAgB,CAAC,EAAE,WAAW,EAAE,IAAY,EAAE,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,+BAiBvG;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,+BAwBrF;AA6CD,wBAAgB,cAAc,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,+BA0B5F"}
@@ -1,44 +1,45 @@
1
- import { GetParserDocument as e, ListWidgetsByDashboardDocument as t } from "../../../generated/wspace-operations.js";
2
- import { useAssistantRunStore as n } from "../../assistant/assistantRunStore.js";
3
- import r from "../datasink/DataSinkTableViewer.js";
4
- import { normalizeWidget as i } from "../../hooks/useWidgetOperations.js";
5
- import a from "../../hooks/useDataSinkOperations.js";
6
- import o from "../widgets/WidgetWrapper.js";
7
- import s from "../DrilldownDashboardRenderer.js";
8
- import { useEffect as c, useRef as l, useState as u } from "react";
9
- import { Spinner as d } from "@burdenoff/fe-libs/ui";
10
- import { useApolloClient as f } from "@apollo/client/react";
11
- import { jsx as p, jsxs as m } from "react/jsx-runtime";
1
+ import { ListWidgetsByDashboardDocument as e } from "../../../generated/wspace-operations.js";
2
+ import { useAssistantRunStore as t } from "../../assistant/assistantRunStore.js";
3
+ import n from "../datasink/DataSinkTableViewer.js";
4
+ import { normalizeWidget as r } from "../../hooks/useWidgetOperations.js";
5
+ import i from "../../hooks/useDataSinkOperations.js";
6
+ import a from "../widgets/WidgetWrapper.js";
7
+ import o from "../DrilldownDashboardRenderer.js";
8
+ import { loadParserPreview as s, parserSampleCaption as c } from "./parserPreview.js";
9
+ import { useEffect as l, useRef as u, useState as d } from "react";
10
+ import { Spinner as f } from "@burdenoff/fe-libs/ui";
11
+ import { useApolloClient as p } from "@apollo/client/react";
12
+ import { jsx as m, jsxs as h } from "react/jsx-runtime";
12
13
  //#region src/bigconsole/components/preview/AiPreviewContent.tsx
13
- var h = 4e3, g = "flex min-h-[6rem] min-w-0 items-center justify-center gap-2 rounded-card bg-bg-sunken px-3 py-6 text-center text-sm text-text-muted";
14
- function _({ label: e, testId: t }) {
15
- return /* @__PURE__ */ m("div", {
16
- className: g,
14
+ var g = 4e3, _ = "flex min-h-[6rem] min-w-0 items-center justify-center gap-2 rounded-card bg-bg-sunken px-3 py-6 text-center text-sm text-text-muted";
15
+ function v({ label: e, testId: t }) {
16
+ return /* @__PURE__ */ h("div", {
17
+ className: _,
17
18
  "data-testid": t,
18
- children: [/* @__PURE__ */ p(d, { className: "size-4 shrink-0" }), e]
19
+ children: [/* @__PURE__ */ m(f, { className: "size-4 shrink-0" }), e]
19
20
  });
20
21
  }
21
- function v({ text: e, testId: t }) {
22
- return /* @__PURE__ */ p("p", {
23
- className: g,
22
+ function y({ text: e, testId: t }) {
23
+ return /* @__PURE__ */ m("p", {
24
+ className: _,
24
25
  "data-testid": t,
25
26
  children: e
26
27
  });
27
28
  }
28
- function y({ text: e, testId: t }) {
29
- return /* @__PURE__ */ p("p", {
30
- className: `${g} text-status-error-text`,
29
+ function b({ text: e, testId: t }) {
30
+ return /* @__PURE__ */ m("p", {
31
+ className: `${_} text-status-error-text`,
31
32
  "data-testid": t,
32
33
  children: e
33
34
  });
34
35
  }
35
- function b(e, t, r) {
36
- let i = n((e) => e.contentRevision), [a, o] = u(null), [s, d] = u(!!t), [f, p] = u(null), [m, g] = u(t), _ = l(e);
37
- return _.current = e, t !== m && (g(t), o(null), p(null), d(!!t)), c(() => {
38
- if (!t) return;
39
- let e = !1, n = !1, i = async () => {
40
- if (!n) {
41
- n = !0;
36
+ function x(e, n, r) {
37
+ let i = t((e) => e.contentRevision), [a, o] = d(null), [s, c] = d(!!n), [f, p] = d(null), [m, h] = d(n), _ = u(e);
38
+ return _.current = e, n !== m && (h(n), o(null), p(null), c(!!n)), l(() => {
39
+ if (!n) return;
40
+ let e = !1, t = !1, i = async () => {
41
+ if (!t) {
42
+ t = !0;
42
43
  try {
43
44
  let t = await _.current();
44
45
  if (e) return;
@@ -47,19 +48,19 @@ function b(e, t, r) {
47
48
  if (e) return;
48
49
  p(t instanceof Error ? t : /* @__PURE__ */ Error("Failed to load preview content"));
49
50
  } finally {
50
- n = !1, e || d(!1);
51
+ t = !1, e || c(!1);
51
52
  }
52
53
  }
53
54
  };
54
55
  i();
55
56
  let a = window.setInterval(() => {
56
57
  r && (typeof document < "u" && document.visibilityState !== "visible" || i());
57
- }, h);
58
+ }, g);
58
59
  return () => {
59
60
  e = !0, window.clearInterval(a);
60
61
  };
61
62
  }, [
62
- t,
63
+ n,
63
64
  r,
64
65
  i
65
66
  ]), {
@@ -68,121 +69,115 @@ function b(e, t, r) {
68
69
  error: f
69
70
  };
70
71
  }
71
- function x({ id: e, live: t }) {
72
- let { getDataSinkData: n } = a({ skipFetch: !0 }), { data: i, loading: o, error: s } = b(async () => {
73
- let t = await n({ id: e }, { limit: 50 });
72
+ function S({ id: e, live: t }) {
73
+ let { getDataSinkData: r } = i({ skipFetch: !0 }), { data: a, loading: o, error: s } = x(async () => {
74
+ let t = await r({ id: e }, { limit: 50 });
74
75
  return t ? t.raw : null;
75
76
  }, e, t);
76
- return e ? s ? /* @__PURE__ */ p(y, {
77
+ return e ? s ? /* @__PURE__ */ m(b, {
77
78
  text: "Could not load the data sink's rows.",
78
79
  testId: "ai-preview-datasink-error"
79
- }) : o ? /* @__PURE__ */ p(_, {
80
+ }) : o ? /* @__PURE__ */ m(v, {
80
81
  label: "Loading the data sink’s data…",
81
82
  testId: "ai-preview-datasink-loading"
82
- }) : /* @__PURE__ */ p("div", {
83
+ }) : /* @__PURE__ */ m("div", {
83
84
  "data-testid": "ai-preview-content-datasink",
84
85
  className: "min-w-0",
85
- children: /* @__PURE__ */ p(r, {
86
- data: i,
86
+ children: /* @__PURE__ */ m(n, {
87
+ data: a,
87
88
  maxHeight: "300px"
88
89
  })
89
- }) : /* @__PURE__ */ p(v, {
90
+ }) : /* @__PURE__ */ m(y, {
90
91
  text: "Waiting for the data sink to be created…",
91
92
  testId: "ai-preview-datasink-waiting"
92
93
  });
93
94
  }
94
- function S({ dashboardId: e, live: t = !1 }) {
95
- let r = n((e) => e.contentRevision);
96
- return e ? /* @__PURE__ */ p("div", {
95
+ function C({ dashboardId: e, live: n = !1 }) {
96
+ let r = t((e) => e.contentRevision);
97
+ return e ? /* @__PURE__ */ m("div", {
97
98
  "data-testid": "ai-preview-content-dashboard",
98
99
  className: "min-w-0",
99
- children: /* @__PURE__ */ p(s, {
100
+ children: /* @__PURE__ */ m(o, {
100
101
  dashboardId: e,
101
102
  params: {},
102
- live: t,
103
+ live: n,
103
104
  refreshRevision: r
104
105
  }, e)
105
- }) : /* @__PURE__ */ p(v, {
106
+ }) : /* @__PURE__ */ m(y, {
106
107
  text: "Waiting for the dashboard to be created…",
107
108
  testId: "ai-preview-dashboard-waiting"
108
109
  });
109
110
  }
110
- function C({ parserId: t, live: n }) {
111
- let i = f(), { getDataSinkData: o } = a({ skipFetch: !0 }), { data: s, loading: c, error: l } = b(async () => {
112
- if (!t) return null;
113
- let n = (await i.query({
114
- query: e,
115
- variables: { id: t },
116
- fetchPolicy: "network-only"
117
- })).data?.parser?.outputKey;
118
- if (!n) return null;
119
- let r = await o({ key: n }, { limit: 50 });
120
- return r ? r.raw : null;
121
- }, t, n);
122
- return t ? l ? /* @__PURE__ */ p(y, {
111
+ function w({ parserId: e, live: t }) {
112
+ let r = p(), { data: i, loading: a, error: o } = x(() => e ? s(r, e) : Promise.resolve(null), e, t), l = c(i);
113
+ return e ? o ? /* @__PURE__ */ m(b, {
123
114
  text: "Could not read the parser's output.",
124
115
  testId: "ai-preview-parser-error"
125
- }) : c ? /* @__PURE__ */ p(_, {
116
+ }) : a ? /* @__PURE__ */ m(v, {
126
117
  label: "Loading the parser’s output…",
127
118
  testId: "ai-preview-parser-loading"
128
- }) : /* @__PURE__ */ p("div", {
119
+ }) : /* @__PURE__ */ h("div", {
129
120
  "data-testid": "ai-preview-content-parser",
130
121
  className: "min-w-0",
131
- children: /* @__PURE__ */ p(r, {
132
- data: s,
122
+ children: [l && /* @__PURE__ */ m("p", {
123
+ "data-testid": "ai-preview-parser-sample",
124
+ className: "mb-2 text-xs text-text-muted",
125
+ children: l
126
+ }), /* @__PURE__ */ m(n, {
127
+ data: i?.output ?? null,
133
128
  maxHeight: "280px"
134
- })
135
- }) : /* @__PURE__ */ p(v, {
129
+ })]
130
+ }) : /* @__PURE__ */ m(y, {
136
131
  text: "Waiting for the parser to be created…",
137
132
  testId: "ai-preview-parser-waiting"
138
133
  });
139
134
  }
140
- function w(e) {
135
+ function T(e) {
141
136
  let t = e.toUpperCase();
142
137
  return t.includes("METRIC") || t.includes("KPI") || t.includes("CARD") || t.includes("STAT") ? "min(160px, 40vh)" : t.includes("TABLE") || t.includes("LIST") ? "min(280px, 50vh)" : "min(320px, 50vh)";
143
138
  }
144
- function T(e, n) {
145
- let r = f(), { data: a, loading: o, error: s } = b(async () => e ? ((await r.query({
146
- query: t,
147
- variables: { dashboardId: e },
139
+ function E(t, n) {
140
+ let i = p(), { data: a, loading: o, error: s } = x(async () => t ? ((await i.query({
141
+ query: e,
142
+ variables: { dashboardId: t },
148
143
  fetchPolicy: "network-only"
149
- })).data?.listWidgetsByDashboard || []).map(i) : null, e, n);
144
+ })).data?.listWidgetsByDashboard || []).map(r) : null, t, n);
150
145
  return {
151
146
  widgets: a ?? [],
152
147
  loading: o,
153
148
  error: s
154
149
  };
155
150
  }
156
- function E({ dashboardId: e, live: t }) {
157
- let { widgets: n, loading: r, error: i } = T(e, t);
158
- return e ? i ? /* @__PURE__ */ p(y, {
151
+ function D({ dashboardId: e, live: t }) {
152
+ let { widgets: n, loading: r, error: i } = E(e, t);
153
+ return e ? i ? /* @__PURE__ */ m(b, {
159
154
  text: "Could not load this dashboard's widgets.",
160
155
  testId: "ai-preview-widget-error"
161
- }) : r && n.length === 0 ? /* @__PURE__ */ p(_, {
156
+ }) : r && n.length === 0 ? /* @__PURE__ */ m(v, {
162
157
  label: "Loading widgets…",
163
158
  testId: "ai-preview-widget-loading"
164
- }) : n.length === 0 ? /* @__PURE__ */ p(v, {
159
+ }) : n.length === 0 ? /* @__PURE__ */ m(y, {
165
160
  text: "No widgets on this dashboard yet.",
166
161
  testId: "ai-preview-widget-empty"
167
- }) : /* @__PURE__ */ p("div", {
162
+ }) : /* @__PURE__ */ m("div", {
168
163
  "data-testid": "ai-preview-content-widget",
169
164
  className: "flex min-w-0 flex-col gap-list-gap",
170
- children: n.map((e) => /* @__PURE__ */ p("div", {
165
+ children: n.map((e) => /* @__PURE__ */ m("div", {
171
166
  className: "min-w-0 overflow-hidden rounded-card border border-[var(--color-border-subtle)] bg-bg-canvas",
172
- style: { height: w(e.type) },
173
- children: /* @__PURE__ */ p(o, {
167
+ style: { height: T(e.type) },
168
+ children: /* @__PURE__ */ m(a, {
174
169
  widget: e,
175
170
  isLoading: !1,
176
171
  error: null,
177
172
  filterValues: {}
178
173
  })
179
174
  }, e.id))
180
- }) : /* @__PURE__ */ p(v, {
175
+ }) : /* @__PURE__ */ m(y, {
181
176
  text: "Waiting for the dashboard — widgets live on it.",
182
177
  testId: "ai-preview-widget-waiting"
183
178
  });
184
179
  }
185
180
  //#endregion
186
- export { S as DashboardContent, x as DataSinkContent, C as ParserContent, E as WidgetsContent };
181
+ export { C as DashboardContent, S as DataSinkContent, w as ParserContent, D as WidgetsContent };
187
182
 
188
183
  //# sourceMappingURL=AiPreviewContent.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AiPreviewContent.js","names":[],"sources":["../../../../src/bigconsole/components/preview/AiPreviewContent.tsx"],"sourcesContent":["/**\n * Live content renderers for the AI preview tabs.\n *\n * Each tab shows the REAL content of the entity the assistant just built —\n * the data sink's rows, the live dashboard, the parser's transformed output,\n * the rendered widget charts — not a list with an Open button. They reuse the\n * same components the real pages use (DataSinkTableViewer, DrilldownDashboard-\n * Renderer, WidgetWrapper) so what you see in the preview is what you get.\n *\n * These require Apollo + the BigConsole providers, which is why they live only\n * in the full AiPreviewPanel (mounted inside BigConsoleRoot), never in the\n * store-only app-shell mini panel.\n *\n * ── BC0 (BOFF-7333; BOFF-7270, BOFF-7271) ────────────────────────────────────\n * A PREVIEW MUST OBSERVE, NEVER MUTATE, AND NEVER PUBLISH.\n *\n * Three defects made this panel change the system it was previewing:\n *\n * 1. The Parser tab polled `executeParser` — a MUTATION — every 4s for 60s,\n * re-running the transform server-side up to ~15 times per turn. It now\n * reads the parser (query), follows its `outputKey`, and reads that sink\n * read-only. Zero mutations.\n * 2. The Widgets tab went through `useWidgetOperations`, which publishes into\n * the GLOBAL widget store — so opening the preview overwrote the widgets an\n * already-open dashboard was rendering. It now holds widgets in local state.\n * 3. `useLiveContent` never reset on key change, so the PREVIOUS entity's rows\n * stayed on screen as though current, and an un-caught rejection left the\n * tab spinning forever with no error surface.\n *\n * ★ Deliberately NOT changed: the other `executeParser` callers\n * (`ParserViewPage`, `CustomWidgetBuilderSheet`, `PropertiesPanel`). Those are\n * user-initiated \"run this parser\" actions where executing IS the intent. Only\n * the passive preview had no business mutating.\n */\nimport { useEffect, useRef, useState } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\n\nimport { DataSinkTableViewer } from '../datasink/DataSinkTableViewer';\nimport { DrilldownDashboardRenderer } from '../DrilldownDashboardRenderer';\nimport { WidgetWrapper } from '../widgets/WidgetWrapper';\nimport { useDataSinkOperations } from '../../hooks/useDataSinkOperations';\nimport { normalizeWidget } from '../../hooks/useWidgetOperations';\nimport { GetParserDocument, ListWidgetsByDashboardDocument } from '../../../generated/wspace-operations';\nimport type { Parser, Widget } from '../../types';\nimport { useAssistantRunStore } from '../../assistant/assistantRunStore';\n\n/** While the build is live, re-pull content on this cadence so it fills in. */\nconst CONTENT_POLL_MS = 4000;\n\n/**\n * Loading and waiting states share one recessed, height-reserved block so the\n * panel does not jump every time a tab fills in — previously each tab had its\n * own bare paragraph and its own height.\n */\nconst PLACEHOLDER_BOX =\n 'flex min-h-[6rem] min-w-0 items-center justify-center gap-2 rounded-card bg-bg-sunken px-3 py-6 text-center text-sm text-text-muted';\n\nfunction CenteredSpinner({ label, testId }: { label: string; testId?: string }) {\n return (\n <div className={PLACEHOLDER_BOX} data-testid={testId}>\n <Spinner className=\"size-4 shrink-0\" />\n {label}\n </div>\n );\n}\n\nfunction Waiting({ text, testId }: { text: string; testId?: string }) {\n return (\n <p className={PLACEHOLDER_BOX} data-testid={testId}>\n {text}\n </p>\n );\n}\n\n/**\n * ★ BC0: the tab had no error surface at all. An un-caught rejection inside the\n * poller left `loading` true forever, so a failed read was indistinguishable\n * from a slow one — and it surfaced as an unhandled promise rejection.\n */\nfunction ErrorBox({ text, testId }: { text: string; testId?: string }) {\n return (\n <p className={`${PLACEHOLDER_BOX} text-status-error-text`} data-testid={testId}>\n {text}\n </p>\n );\n}\n\n/**\n * Fetch-on-mount and confirmed-result revisions, then re-fetch every CONTENT_POLL_MS while `live`. Returns { data, loading, error }. `fetcher` must be\n * stable-ish; we hold it in a ref so the effect keys only on `key`/`live`.\n *\n * ★★ BC0: RESETS ON KEY CHANGE. Previously `data` survived a key change, so\n * switching to a different entity rendered the PREVIOUS entity's rows as though\n * they were the new one's — with `loading` already false, nothing said otherwise.\n */\nfunction useLiveContent<T>(fetcher: () => Promise<T | null>, key: string | undefined, live: boolean) {\n const revision = useAssistantRunStore((state) => state.contentRevision);\n const [data, setData] = useState<T | null>(null);\n const [loading, setLoading] = useState(Boolean(key));\n const [error, setError] = useState<Error | null>(null);\n const [renderedKey, setRenderedKey] = useState(key);\n const fetcherRef = useRef(fetcher);\n fetcherRef.current = fetcher;\n\n // ★★ The reset happens DURING RENDER, not in an effect. An effect-based reset\n // commits one frame carrying the PREVIOUS entity's rows before clearing them —\n // which is the very defect being fixed here. Adjusting state while rendering is\n // React's supported pattern for \"a prop changed, discard the derived state\": it\n // re-runs this component immediately, before anything reaches the DOM, so the\n // stale rows are never painted. A key going undefined clears the render too.\n if (key !== renderedKey) {\n setRenderedKey(key);\n setData(null);\n setError(null);\n setLoading(Boolean(key));\n }\n\n useEffect(() => {\n if (!key) return;\n\n let cancelled = false;\n let fetching = false;\n const pull = async () => {\n if (fetching) return;\n fetching = true;\n try {\n const result = await fetcherRef.current();\n if (cancelled) return;\n setData(result ?? null);\n setError(null);\n } catch (err) {\n if (cancelled) return;\n // A later successful poll clears this, so a transient blip does not\n // strand the tab on an error.\n setError(err instanceof Error ? err : new Error('Failed to load preview content'));\n } finally {\n fetching = false;\n if (!cancelled) setLoading(false);\n }\n };\n void pull();\n const intervalId = window.setInterval(() => {\n if (!live) return;\n if (typeof document !== 'undefined' && document.visibilityState !== 'visible') return;\n void pull();\n }, CONTENT_POLL_MS);\n return () => {\n cancelled = true;\n window.clearInterval(intervalId);\n };\n }, [key, live, revision]);\n\n return { data, loading, error };\n}\n\nexport function DataSinkContent({ id, live }: { id?: string; live: boolean }) {\n const { getDataSinkData } = useDataSinkOperations({ skipFetch: true });\n const { data, loading, error } = useLiveContent(\n async () => {\n const res = await getDataSinkData({ id }, { limit: 50 });\n return res ? res.raw : null;\n },\n id,\n live\n );\n\n if (!id) return <Waiting text=\"Waiting for the data sink to be created…\" testId=\"ai-preview-datasink-waiting\" />;\n if (error) return <ErrorBox text=\"Could not load the data sink's rows.\" testId=\"ai-preview-datasink-error\" />;\n if (loading) return <CenteredSpinner label=\"Loading the data sink’s data…\" testId=\"ai-preview-datasink-loading\" />;\n return (\n <div data-testid=\"ai-preview-content-datasink\" className=\"min-w-0\">\n <DataSinkTableViewer data={data} maxHeight=\"300px\" />\n </div>\n );\n}\n\nexport function DashboardContent({ dashboardId, live = false }: { dashboardId?: string; live?: boolean }) {\n const revision = useAssistantRunStore((state) => state.contentRevision);\n if (!dashboardId)\n return <Waiting text=\"Waiting for the dashboard to be created…\" testId=\"ai-preview-dashboard-waiting\" />;\n // No inner `max-h + overflow-y-auto`: the panel already owns a vertical\n // scroll, and nesting a second one put two scrollbars in ~440px of space.\n return (\n <div data-testid=\"ai-preview-content-dashboard\" className=\"min-w-0\">\n <DrilldownDashboardRenderer\n key={dashboardId}\n dashboardId={dashboardId}\n params={{}}\n live={live}\n refreshRevision={revision}\n />\n </div>\n );\n}\n\n/**\n * ★★★ BC0 — READ-ONLY. This tab used to call `executeParser`, a mutation, on\n * every poll: ~15 server-side re-executions per turn purely to look at output.\n *\n * A parser writes its transformed rows into a data sink named by its own\n * `outputKey` (`ParserCoreFields`), and `getDataSinkData` accepts `{ key }`. So\n * the output is fully readable without executing anything: read the parser,\n * follow `outputKey`, read the sink.\n *\n * ★ Apollo is queried directly rather than via `useParserOperations.fetchParser`,\n * because that helper calls `addParser()` and would publish into the global\n * parser store — trading defect 2's leak for the same leak one store over.\n */\nexport function ParserContent({ parserId, live }: { parserId?: string; live: boolean }) {\n const client = useApolloClient();\n const { getDataSinkData } = useDataSinkOperations({ skipFetch: true });\n const { data, loading, error } = useLiveContent(\n async () => {\n if (!parserId) return null;\n const res = await client.query<{ parser?: Parser | null }>({\n query: GetParserDocument,\n variables: { id: parserId },\n fetchPolicy: 'network-only',\n });\n const outputKey = res.data?.parser?.outputKey;\n if (!outputKey) return null;\n const sink = await getDataSinkData({ key: outputKey }, { limit: 50 });\n return sink ? sink.raw : null;\n },\n parserId,\n live\n );\n\n if (!parserId) return <Waiting text=\"Waiting for the parser to be created…\" testId=\"ai-preview-parser-waiting\" />;\n if (error) return <ErrorBox text=\"Could not read the parser's output.\" testId=\"ai-preview-parser-error\" />;\n // \"Running the parser…\" would now be a lie — nothing is executed.\n if (loading) return <CenteredSpinner label=\"Loading the parser’s output…\" testId=\"ai-preview-parser-loading\" />;\n // The \"Parser output (transformed rows)\" caption used to sit here; it now\n // lives on the section header, so the tab does not label itself twice.\n return (\n <div data-testid=\"ai-preview-content-parser\" className=\"min-w-0\">\n <DataSinkTableViewer data={data} maxHeight=\"280px\" />\n </div>\n );\n}\n\n/**\n * Recharts needs a definite height or it renders 0px tall, but a hard 320px box\n * eats a short viewport whole. `min(px, vh)` keeps the definite height Recharts\n * needs while letting the box shrink on small screens.\n */\nfunction widgetBoxHeight(type: string): string {\n const t = type.toUpperCase();\n if (t.includes('METRIC') || t.includes('KPI') || t.includes('CARD') || t.includes('STAT')) return 'min(160px, 40vh)';\n if (t.includes('TABLE') || t.includes('LIST')) return 'min(280px, 50vh)';\n return 'min(320px, 50vh)';\n}\n\n/**\n * ★★★ BC0 — COMPONENT-LOCAL widgets.\n *\n * `useWidgetOperations` publishes every fetch into the GLOBAL widget store, so\n * merely opening this preview replaced the widget set an already-open dashboard\n * was rendering. The query and the normalisation are reused — `normalizeWidget`\n * is exported from that hook precisely so the two paths cannot drift — but the\n * result stays here, in this component's state.\n *\n * It also inherits `useLiveContent`'s result refresh and visibility check,\n * replacing the bespoke interval this tab used to run.\n */\nfunction usePreviewWidgets(dashboardId: string | undefined, live: boolean) {\n const client = useApolloClient();\n const { data, loading, error } = useLiveContent<Widget[]>(\n async () => {\n if (!dashboardId) return null;\n const result = await client.query<Record<string, unknown>>({\n query: ListWidgetsByDashboardDocument,\n variables: { dashboardId },\n fetchPolicy: 'network-only',\n });\n const list = (result.data?.listWidgetsByDashboard as Record<string, unknown>[]) || [];\n return list.map(normalizeWidget);\n },\n dashboardId,\n live\n );\n return { widgets: data ?? [], loading, error };\n}\n\nexport function WidgetsContent({ dashboardId, live }: { dashboardId?: string; live: boolean }) {\n const { widgets, loading, error } = usePreviewWidgets(dashboardId, live);\n\n if (!dashboardId)\n return <Waiting text=\"Waiting for the dashboard — widgets live on it.\" testId=\"ai-preview-widget-waiting\" />;\n if (error) return <ErrorBox text=\"Could not load this dashboard's widgets.\" testId=\"ai-preview-widget-error\" />;\n if (loading && widgets.length === 0)\n return <CenteredSpinner label=\"Loading widgets…\" testId=\"ai-preview-widget-loading\" />;\n if (widgets.length === 0)\n return <Waiting text=\"No widgets on this dashboard yet.\" testId=\"ai-preview-widget-empty\" />;\n\n return (\n <div data-testid=\"ai-preview-content-widget\" className=\"flex min-w-0 flex-col gap-list-gap\">\n {widgets.map((widget) => (\n <div\n key={widget.id}\n // `border-border-subtle` is registered as a frozen light hex in this\n // shell, so the box kept a light border in dark mode.\n className=\"min-w-0 overflow-hidden rounded-card border border-[var(--color-border-subtle)] bg-bg-canvas\"\n style={{ height: widgetBoxHeight(widget.type) }}\n >\n <WidgetWrapper widget={widget} isLoading={false} error={null} filterValues={{}} />\n </div>\n ))}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AiPreviewContent.js","names":[],"sources":["../../../../src/bigconsole/components/preview/AiPreviewContent.tsx"],"sourcesContent":["/**\n * Live content renderers for the AI preview tabs.\n *\n * Each tab shows the REAL content of the entity the assistant just built —\n * the data sink's rows, the live dashboard, the parser's transformed output,\n * the rendered widget charts — not a list with an Open button. They reuse the\n * same components the real pages use (DataSinkTableViewer, DrilldownDashboard-\n * Renderer, WidgetWrapper) so what you see in the preview is what you get.\n *\n * These require Apollo + the BigConsole providers, which is why they live only\n * in the full AiPreviewPanel (mounted inside BigConsoleRoot), never in the\n * store-only app-shell mini panel.\n *\n * ── BC0 (BOFF-7333; BOFF-7270, BOFF-7271) ────────────────────────────────────\n * A PREVIEW MUST OBSERVE, NEVER MUTATE, AND NEVER PUBLISH.\n *\n * Three defects made this panel change the system it was previewing:\n *\n * 1. The Parser tab polled `executeParser` — a MUTATION — every 4s for 60s,\n * re-running the transform server-side up to ~15 times per turn. It now\n * reads the parser and evaluates a governed source sample through queries.\n * It does not persist output or call mutations.\n * 2. The Widgets tab went through `useWidgetOperations`, which publishes into\n * the GLOBAL widget store — so opening the preview overwrote the widgets an\n * already-open dashboard was rendering. It now holds widgets in local state.\n * 3. `useLiveContent` never reset on key change, so the PREVIOUS entity's rows\n * stayed on screen as though current, and an un-caught rejection left the\n * tab spinning forever with no error surface.\n *\n * ★ Deliberately NOT changed: the other `executeParser` callers\n * (`ParserViewPage`, `CustomWidgetBuilderSheet`, `PropertiesPanel`). Those are\n * user-initiated \"run this parser\" actions where executing IS the intent. Only\n * the passive preview had no business mutating.\n */\nimport { useEffect, useRef, useState } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\n\nimport { DataSinkTableViewer } from '../datasink/DataSinkTableViewer';\nimport { DrilldownDashboardRenderer } from '../DrilldownDashboardRenderer';\nimport { WidgetWrapper } from '../widgets/WidgetWrapper';\nimport { useDataSinkOperations } from '../../hooks/useDataSinkOperations';\nimport { normalizeWidget } from '../../hooks/useWidgetOperations';\nimport { ListWidgetsByDashboardDocument } from '../../../generated/wspace-operations';\nimport type { Widget } from '../../types';\nimport { useAssistantRunStore } from '../../assistant/assistantRunStore';\nimport { loadParserPreview, parserSampleCaption } from './parserPreview';\n\n/** While the build is live, re-pull content on this cadence so it fills in. */\nconst CONTENT_POLL_MS = 4000;\n\n/**\n * Loading and waiting states share one recessed, height-reserved block so the\n * panel does not jump every time a tab fills in — previously each tab had its\n * own bare paragraph and its own height.\n */\nconst PLACEHOLDER_BOX =\n 'flex min-h-[6rem] min-w-0 items-center justify-center gap-2 rounded-card bg-bg-sunken px-3 py-6 text-center text-sm text-text-muted';\n\nfunction CenteredSpinner({ label, testId }: { label: string; testId?: string }) {\n return (\n <div className={PLACEHOLDER_BOX} data-testid={testId}>\n <Spinner className=\"size-4 shrink-0\" />\n {label}\n </div>\n );\n}\n\nfunction Waiting({ text, testId }: { text: string; testId?: string }) {\n return (\n <p className={PLACEHOLDER_BOX} data-testid={testId}>\n {text}\n </p>\n );\n}\n\n/**\n * ★ BC0: the tab had no error surface at all. An un-caught rejection inside the\n * poller left `loading` true forever, so a failed read was indistinguishable\n * from a slow one — and it surfaced as an unhandled promise rejection.\n */\nfunction ErrorBox({ text, testId }: { text: string; testId?: string }) {\n return (\n <p className={`${PLACEHOLDER_BOX} text-status-error-text`} data-testid={testId}>\n {text}\n </p>\n );\n}\n\n/**\n * Fetch-on-mount and confirmed-result revisions, then re-fetch every CONTENT_POLL_MS while `live`. Returns { data, loading, error }. `fetcher` must be\n * stable-ish; we hold it in a ref so the effect keys only on `key`/`live`.\n *\n * ★★ BC0: RESETS ON KEY CHANGE. Previously `data` survived a key change, so\n * switching to a different entity rendered the PREVIOUS entity's rows as though\n * they were the new one's — with `loading` already false, nothing said otherwise.\n */\nfunction useLiveContent<T>(fetcher: () => Promise<T | null>, key: string | undefined, live: boolean) {\n const revision = useAssistantRunStore((state) => state.contentRevision);\n const [data, setData] = useState<T | null>(null);\n const [loading, setLoading] = useState(Boolean(key));\n const [error, setError] = useState<Error | null>(null);\n const [renderedKey, setRenderedKey] = useState(key);\n const fetcherRef = useRef(fetcher);\n fetcherRef.current = fetcher;\n\n // ★★ The reset happens DURING RENDER, not in an effect. An effect-based reset\n // commits one frame carrying the PREVIOUS entity's rows before clearing them —\n // which is the very defect being fixed here. Adjusting state while rendering is\n // React's supported pattern for \"a prop changed, discard the derived state\": it\n // re-runs this component immediately, before anything reaches the DOM, so the\n // stale rows are never painted. A key going undefined clears the render too.\n if (key !== renderedKey) {\n setRenderedKey(key);\n setData(null);\n setError(null);\n setLoading(Boolean(key));\n }\n\n useEffect(() => {\n if (!key) return;\n\n let cancelled = false;\n let fetching = false;\n const pull = async () => {\n if (fetching) return;\n fetching = true;\n try {\n const result = await fetcherRef.current();\n if (cancelled) return;\n setData(result ?? null);\n setError(null);\n } catch (err) {\n if (cancelled) return;\n // A later successful poll clears this, so a transient blip does not\n // strand the tab on an error.\n setError(err instanceof Error ? err : new Error('Failed to load preview content'));\n } finally {\n fetching = false;\n if (!cancelled) setLoading(false);\n }\n };\n void pull();\n const intervalId = window.setInterval(() => {\n if (!live) return;\n if (typeof document !== 'undefined' && document.visibilityState !== 'visible') return;\n void pull();\n }, CONTENT_POLL_MS);\n return () => {\n cancelled = true;\n window.clearInterval(intervalId);\n };\n }, [key, live, revision]);\n\n return { data, loading, error };\n}\n\nexport function DataSinkContent({ id, live }: { id?: string; live: boolean }) {\n const { getDataSinkData } = useDataSinkOperations({ skipFetch: true });\n const { data, loading, error } = useLiveContent(\n async () => {\n const res = await getDataSinkData({ id }, { limit: 50 });\n return res ? res.raw : null;\n },\n id,\n live\n );\n\n if (!id) return <Waiting text=\"Waiting for the data sink to be created…\" testId=\"ai-preview-datasink-waiting\" />;\n if (error) return <ErrorBox text=\"Could not load the data sink's rows.\" testId=\"ai-preview-datasink-error\" />;\n if (loading) return <CenteredSpinner label=\"Loading the data sink’s data…\" testId=\"ai-preview-datasink-loading\" />;\n return (\n <div data-testid=\"ai-preview-content-datasink\" className=\"min-w-0\">\n <DataSinkTableViewer data={data} maxHeight=\"300px\" />\n </div>\n );\n}\n\nexport function DashboardContent({ dashboardId, live = false }: { dashboardId?: string; live?: boolean }) {\n const revision = useAssistantRunStore((state) => state.contentRevision);\n if (!dashboardId)\n return <Waiting text=\"Waiting for the dashboard to be created…\" testId=\"ai-preview-dashboard-waiting\" />;\n // No inner `max-h + overflow-y-auto`: the panel already owns a vertical\n // scroll, and nesting a second one put two scrollbars in ~440px of space.\n return (\n <div data-testid=\"ai-preview-content-dashboard\" className=\"min-w-0\">\n <DrilldownDashboardRenderer\n key={dashboardId}\n dashboardId={dashboardId}\n params={{}}\n live={live}\n refreshRevision={revision}\n />\n </div>\n );\n}\n\n/**\n * Read-only evaluation of the saved expression against up to 50 current source\n * rows. outputKey is a widget binding, not a physical DataSink. The governed\n * query applies masking and returns sampling metadata; it persists no output.\n */\nexport function ParserContent({ parserId, live }: { parserId?: string; live: boolean }) {\n const client = useApolloClient();\n const { data, loading, error } = useLiveContent(\n () => (parserId ? loadParserPreview(client, parserId) : Promise.resolve(null)),\n parserId,\n live\n );\n const sampleCaption = parserSampleCaption(data);\n\n if (!parserId) return <Waiting text=\"Waiting for the parser to be created…\" testId=\"ai-preview-parser-waiting\" />;\n if (error) return <ErrorBox text=\"Could not read the parser's output.\" testId=\"ai-preview-parser-error\" />;\n if (loading) return <CenteredSpinner label=\"Loading the parser’s output…\" testId=\"ai-preview-parser-loading\" />;\n // The \"Parser output (transformed rows)\" caption used to sit here; it now\n // lives on the section header, so the tab does not label itself twice.\n return (\n <div data-testid=\"ai-preview-content-parser\" className=\"min-w-0\">\n {sampleCaption && (\n <p data-testid=\"ai-preview-parser-sample\" className=\"mb-2 text-xs text-text-muted\">\n {sampleCaption}\n </p>\n )}\n <DataSinkTableViewer data={data?.output ?? null} maxHeight=\"280px\" />\n </div>\n );\n}\n\n/**\n * Recharts needs a definite height or it renders 0px tall, but a hard 320px box\n * eats a short viewport whole. `min(px, vh)` keeps the definite height Recharts\n * needs while letting the box shrink on small screens.\n */\nfunction widgetBoxHeight(type: string): string {\n const t = type.toUpperCase();\n if (t.includes('METRIC') || t.includes('KPI') || t.includes('CARD') || t.includes('STAT')) return 'min(160px, 40vh)';\n if (t.includes('TABLE') || t.includes('LIST')) return 'min(280px, 50vh)';\n return 'min(320px, 50vh)';\n}\n\n/**\n * ★★★ BC0 — COMPONENT-LOCAL widgets.\n *\n * `useWidgetOperations` publishes every fetch into the GLOBAL widget store, so\n * merely opening this preview replaced the widget set an already-open dashboard\n * was rendering. The query and the normalisation are reused — `normalizeWidget`\n * is exported from that hook precisely so the two paths cannot drift — but the\n * result stays here, in this component's state.\n *\n * It also inherits `useLiveContent`'s result refresh and visibility check,\n * replacing the bespoke interval this tab used to run.\n */\nfunction usePreviewWidgets(dashboardId: string | undefined, live: boolean) {\n const client = useApolloClient();\n const { data, loading, error } = useLiveContent<Widget[]>(\n async () => {\n if (!dashboardId) return null;\n const result = await client.query<Record<string, unknown>>({\n query: ListWidgetsByDashboardDocument,\n variables: { dashboardId },\n fetchPolicy: 'network-only',\n });\n const list = (result.data?.listWidgetsByDashboard as Record<string, unknown>[]) || [];\n return list.map(normalizeWidget);\n },\n dashboardId,\n live\n );\n return { widgets: data ?? [], loading, error };\n}\n\nexport function WidgetsContent({ dashboardId, live }: { dashboardId?: string; live: boolean }) {\n const { widgets, loading, error } = usePreviewWidgets(dashboardId, live);\n\n if (!dashboardId)\n return <Waiting text=\"Waiting for the dashboard — widgets live on it.\" testId=\"ai-preview-widget-waiting\" />;\n if (error) return <ErrorBox text=\"Could not load this dashboard's widgets.\" testId=\"ai-preview-widget-error\" />;\n if (loading && widgets.length === 0)\n return <CenteredSpinner label=\"Loading widgets…\" testId=\"ai-preview-widget-loading\" />;\n if (widgets.length === 0)\n return <Waiting text=\"No widgets on this dashboard yet.\" testId=\"ai-preview-widget-empty\" />;\n\n return (\n <div data-testid=\"ai-preview-content-widget\" className=\"flex min-w-0 flex-col gap-list-gap\">\n {widgets.map((widget) => (\n <div\n key={widget.id}\n // `border-border-subtle` is registered as a frozen light hex in this\n // shell, so the box kept a light border in dark mode.\n className=\"min-w-0 overflow-hidden rounded-card border border-[var(--color-border-subtle)] bg-bg-canvas\"\n style={{ height: widgetBoxHeight(widget.type) }}\n >\n <WidgetWrapper widget={widget} isLoading={false} error={null} filterValues={{}} />\n </div>\n ))}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { ApolloClient } from '@apollo/client';
2
+
3
+ /** Evaluate a bounded, governed source sample without persisting parser output. */
4
+ export declare function loadParserPreview(client: Pick<ApolloClient, 'query'>, parserId: string): Promise<{
5
+ __typename?: "ParserTestResult";
6
+ success: boolean;
7
+ output?: Record<string, unknown> | null;
8
+ error?: string | null;
9
+ executionTimeMs?: number | null;
10
+ sampled?: boolean | null;
11
+ sampledRows?: number | null;
12
+ totalRows?: number | null;
13
+ } | null>;
14
+ export declare function parserSampleCaption(preview: {
15
+ sampled?: boolean | null;
16
+ sampledRows?: number | null;
17
+ totalRows?: number | null;
18
+ } | null): string | null;
19
+ //# sourceMappingURL=parserPreview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parserPreview.d.ts","sourceRoot":"","sources":["../../../../src/bigconsole/components/preview/parserPreview.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAQnD,mFAAmF;AACnF,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,EAAE,QAAQ,EAAE,MAAM;;;;;;;;;UAuB5F;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,GACnG,MAAM,GAAG,IAAI,CAMf"}
@@ -0,0 +1,31 @@
1
+ import { GetParserDocument as e, TestParserExpressionDocument as t } from "../../../generated/wspace-operations.js";
2
+ //#region src/bigconsole/components/preview/parserPreview.ts
3
+ async function n(n, r) {
4
+ let i = (await n.query({
5
+ query: e,
6
+ variables: { id: r },
7
+ fetchPolicy: "network-only"
8
+ })).data?.parser;
9
+ if (!i) return null;
10
+ let a = (await n.query({
11
+ query: t,
12
+ variables: { input: {
13
+ type: i.type,
14
+ expression: i.expression,
15
+ dataSinkKey: i.inputSinkKey,
16
+ sampleLimit: 50
17
+ } },
18
+ fetchPolicy: "network-only"
19
+ })).data?.testParserExpression;
20
+ if (!a?.success) throw Error(a?.error || "Could not evaluate the parser preview");
21
+ return a;
22
+ }
23
+ function r(e) {
24
+ if (!e?.sampled) return null;
25
+ let t = e.sampledRows ?? 50;
26
+ return e.totalRows !== null && e.totalRows !== void 0 ? `Preview evaluated ${t} of ${e.totalRows} source rows. Full execution may differ.` : `Preview evaluated ${t} source rows. Full execution may differ.`;
27
+ }
28
+ //#endregion
29
+ export { n as loadParserPreview, r as parserSampleCaption };
30
+
31
+ //# sourceMappingURL=parserPreview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parserPreview.js","names":[],"sources":["../../../../src/bigconsole/components/preview/parserPreview.ts"],"sourcesContent":["import type { ApolloClient } from '@apollo/client';\nimport {\n GetParserDocument,\n TestParserExpressionDocument,\n type GetParserQuery,\n type TestParserExpressionQuery,\n} from '../../../generated/wspace-operations';\n\n/** Evaluate a bounded, governed source sample without persisting parser output. */\nexport async function loadParserPreview(client: Pick<ApolloClient, 'query'>, parserId: string) {\n const parserResult = await client.query<GetParserQuery>({\n query: GetParserDocument,\n variables: { id: parserId },\n fetchPolicy: 'network-only',\n });\n const parser = parserResult.data?.parser;\n if (!parser) return null;\n const result = await client.query<TestParserExpressionQuery>({\n query: TestParserExpressionDocument,\n variables: {\n input: {\n type: parser.type,\n expression: parser.expression,\n dataSinkKey: parser.inputSinkKey,\n sampleLimit: 50,\n },\n },\n fetchPolicy: 'network-only',\n });\n const preview = result.data?.testParserExpression;\n if (!preview?.success) throw new Error(preview?.error || 'Could not evaluate the parser preview');\n return preview;\n}\n\nexport function parserSampleCaption(\n preview: { sampled?: boolean | null; sampledRows?: number | null; totalRows?: number | null } | null\n): string | null {\n if (!preview?.sampled) return null;\n const rows = preview.sampledRows ?? 50;\n return preview.totalRows !== null && preview.totalRows !== undefined\n ? `Preview evaluated ${rows} of ${preview.totalRows} source rows. Full execution may differ.`\n : `Preview evaluated ${rows} source rows. Full execution may differ.`;\n}\n"],"mappings":";;AASA,eAAsB,EAAkB,GAAqC,GAAkB;CAM7F,IAAM,KALe,MAAM,EAAO,MAAsB;EACtD,OAAO;EACP,WAAW,EAAE,IAAI,GAAU;EAC3B,aAAa;EACd,CAAC,EAC0B,MAAM;AAClC,KAAI,CAAC,EAAQ,QAAO;CAapB,IAAM,KAZS,MAAM,EAAO,MAAiC;EAC3D,OAAO;EACP,WAAW,EACT,OAAO;GACL,MAAM,EAAO;GACb,YAAY,EAAO;GACnB,aAAa,EAAO;GACpB,aAAa;GACd,EACF;EACD,aAAa;EACd,CAAC,EACqB,MAAM;AAC7B,KAAI,CAAC,GAAS,QAAS,OAAU,MAAM,GAAS,SAAS,wCAAwC;AACjG,QAAO;;AAGT,SAAgB,EACd,GACe;AACf,KAAI,CAAC,GAAS,QAAS,QAAO;CAC9B,IAAM,IAAO,EAAQ,eAAe;AACpC,QAAO,EAAQ,cAAc,QAAQ,EAAQ,cAAc,KAAA,IACvD,qBAAqB,EAAK,MAAM,EAAQ,UAAU,4CAClD,qBAAqB,EAAK"}
@@ -1,34 +1,20 @@
1
1
  import { FC } from 'react';
2
+ import { KanbanData, KanbanTask, KanbanColumnMove } from './model';
2
3
  import { Widget } from '../../../types';
3
4
  import { WidgetTranslateFn } from '../utils/translate';
4
5
 
6
+ export type { KanbanData, KanbanTask, KanbanColumn } from './model';
5
7
  export interface KanbanViewProps extends KanbanWidgetProps {
6
8
  /** Translator — the app passes useI18n().t; the public embed passes a static fallback translator. */
7
9
  t: WidgetTranslateFn;
10
+ interactive?: boolean;
11
+ onSaveMoves?: (moves: KanbanColumnMove[]) => Promise<boolean>;
8
12
  }
9
13
  export interface KanbanWidgetProps {
10
14
  widget: Widget;
11
15
  data?: KanbanData;
12
16
  onClick?: (task: KanbanTask) => void;
13
17
  }
14
- export interface KanbanColumn {
15
- id: string;
16
- title: string;
17
- color?: string;
18
- }
19
- export interface KanbanTask {
20
- id: string;
21
- title: string;
22
- column: string;
23
- priority?: 'high' | 'medium' | 'low';
24
- assignee?: string;
25
- labels?: string[];
26
- dueDate?: string;
27
- }
28
- export interface KanbanData {
29
- columns?: KanbanColumn[];
30
- tasks?: KanbanTask[];
31
- }
32
18
  export declare const KanbanView: FC<KanbanViewProps>;
33
19
  export default KanbanView;
34
20
  //# sourceMappingURL=KanbanView.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"KanbanView.d.ts","sourceRoot":"","sources":["../../../../../src/bigconsole/components/widgets/kanban/KanbanView.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,EAAE,EAAiB,MAAM,OAAO,CAAC;AAC/C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAM5D,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACxD,qGAAqG;IACrG,CAAC,EAAE,iBAAiB,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;CACtB;AAsID,eAAO,MAAM,UAAU,EAAE,EAAE,CAAC,eAAe,CAwFzC,CAAC;AAEH,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"KanbanView.d.ts","sourceRoot":"","sources":["../../../../../src/bigconsole/components/widgets/kanban/KanbanView.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,EAAE,EAAmC,MAAM,OAAO,CAAC;AACjE,OAAO,EAIL,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,gBAAgB,EACtB,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAM5D,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACxD,qGAAqG;IACrG,CAAC,EAAE,iBAAiB,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;CACtC;AAmBD,eAAO,MAAM,UAAU,EAAE,EAAE,CAAC,eAAe,CAmNzC,CAAC;AAEH,eAAe,UAAU,CAAC"}
@@ -1,7 +1,8 @@
1
- import { memo as e, useMemo as t } from "react";
2
- import { jsx as n, jsxs as r } from "react/jsx-runtime";
1
+ import { applyKanbanMoves as e, normalizeKanban as t, validKanbanMoves as n } from "./model.js";
2
+ import { memo as r, useMemo as i, useRef as a, useState as o } from "react";
3
+ import { jsx as s, jsxs as c } from "react/jsx-runtime";
3
4
  //#region src/bigconsole/components/widgets/kanban/KanbanView.tsx
4
- function i(e) {
5
+ function l(e) {
5
6
  switch (e) {
6
7
  case "high": return "bg-status-error-bg/20 text-status-error-text";
7
8
  case "medium": return "bg-status-warning-bg/20 text-[var(--color-status-warning-text)]";
@@ -9,184 +10,167 @@ function i(e) {
9
10
  default: return "bg-bg-sunken text-text-secondary";
10
11
  }
11
12
  }
12
- var a = [
13
- "status",
14
- "state",
15
- "column",
16
- "stage",
17
- "category",
18
- "phase",
19
- "group"
20
- ], o = [
21
- "title",
22
- "name",
23
- "summary",
24
- "label",
25
- "identifier",
26
- "id"
27
- ];
28
- function s(e) {
29
- return typeof e == "string" && e.length > 0 ? e : void 0;
30
- }
31
- function c(e) {
32
- let t = (e) => e.filter((e) => !!e && typeof e == "object" && !Array.isArray(e));
33
- if (Array.isArray(e)) return t(e);
34
- if (e && typeof e == "object") {
35
- let n = e;
36
- for (let e of [
37
- "tasks",
38
- "rows",
39
- "items",
40
- "data",
41
- "nodes",
42
- "results",
43
- "records"
44
- ]) if (Array.isArray(n[e])) return t(n[e]);
45
- let r = Object.values(n).find((e) => Array.isArray(e));
46
- if (Array.isArray(r)) return t(r);
47
- }
48
- return [];
49
- }
50
- function l(e, t) {
51
- for (let n of t) if (n && n in e) return n;
52
- }
53
- function u(e) {
54
- let t = s(e)?.toLowerCase();
55
- if (t) {
56
- if (t === "urgent" || t === "high") return "high";
57
- if (t === "medium" || t === "normal") return "medium";
58
- if (t === "low") return "low";
59
- }
60
- }
61
- function d(e, t) {
62
- if (e && typeof e == "object" && !Array.isArray(e) && Array.isArray(e.columns)) {
63
- let t = e;
64
- return {
65
- columns: t.columns ?? [],
66
- tasks: t.tasks ?? []
67
- };
68
- }
69
- let n = c(e);
70
- if (n.length === 0) return {
71
- columns: [],
72
- tasks: []
73
- };
74
- let r = n[0], i = l(r, [
75
- s(t?.columnField),
76
- s(t?.groupField),
77
- s(t?.statusField),
78
- ...a,
79
- s(t?.xAxisField),
80
- s(t?.labelField)
81
- ]);
82
- if (!i) return {
83
- columns: [],
84
- tasks: []
85
- };
86
- let d = l(r, [
87
- s(t?.cardTitleField),
88
- s(t?.titleField),
89
- ...o,
90
- s(t?.labelField)
91
- ]), f = {}, p = [], m = n.map((e, t) => {
92
- let n = e[i], r = n == null || n === "" ? "Uncategorized" : String(n);
93
- r in f || (f[r] = r, p.push(r));
94
- let a = e.id ?? e.identifier ?? e.key, o = Array.isArray(e.labels) ? e.labels.filter((e) => typeof e == "string") : void 0;
95
- return {
96
- id: a == null ? `task-${t}` : String(a),
97
- title: d && e[d] != null ? String(e[d]) : `Item ${t + 1}`,
98
- column: r,
99
- assignee: s(e.assignee),
100
- priority: u(e.priority ?? e.priorityLabel),
101
- labels: o && o.length > 0 ? o : void 0
102
- };
103
- });
104
- return {
105
- columns: p.map((e) => ({
106
- id: e,
107
- title: f[e] ?? e
108
- })),
109
- tasks: m
110
- };
111
- }
112
- var f = e(function({ widget: e, data: a, onClick: o, t: s }) {
113
- let c = e.config, { columns: l, tasks: u } = t(() => d(a, c), [a, c]), f = t(() => {
114
- let e = {};
115
- return l.forEach((t) => {
116
- e[t.id] = u.filter((e) => e.column === t.id);
13
+ var u = r(function({ widget: r, data: u, onClick: d, t: f, onSaveMoves: p, interactive: m = !1 }) {
14
+ let h = r.config, { columns: g, tasks: _ } = i(() => t(u, h), [u, h]), [v, y] = o(null), [b, x] = o(!1), [S, C] = o(""), w = a(null), T = v && v.data === u && v.widgetId === r.id ? v.moves : n(h?.kanbanColumnMoves), E = i(() => e(_, g, T), [
15
+ _,
16
+ g,
17
+ T
18
+ ]), D = async (e, t) => {
19
+ if (!m || b || !g.some((e) => e.id === t)) return;
20
+ let i = _.find((t) => t.id === e);
21
+ if (!i || E.find((t) => t.id === e)?.column === t) return;
22
+ let a = n(T), o = [...a.filter((t) => t.taskId !== e), {
23
+ taskId: e,
24
+ sourceColumn: i.column,
25
+ targetColumn: t
26
+ }];
27
+ if (y({
28
+ data: u,
29
+ widgetId: r.id,
30
+ moves: o
31
+ }), !p || !i.stableId) {
32
+ C(f("bigconsole.widget.kanban.move.temporary", "{{title}} moved to {{column}} for this view.", {
33
+ title: i.title,
34
+ column: t
35
+ }));
36
+ return;
37
+ }
38
+ x(!0);
39
+ let s;
40
+ try {
41
+ s = await p(o.filter((e) => _.some((t) => t.id === e.taskId && t.stableId)));
42
+ } catch {
43
+ s = !1;
44
+ }
45
+ x(!1), s ? C(f("bigconsole.widget.kanban.move.saved", "{{title}} moved to {{column}} and saved.", {
46
+ title: i.title,
47
+ column: t
48
+ })) : (y({
49
+ data: u,
50
+ widgetId: r.id,
51
+ moves: a
52
+ }), C(f("bigconsole.widget.kanban.move.failed", "Could not save the move. The card was returned to its previous column.")));
53
+ }, O = i(() => {
54
+ let e = /* @__PURE__ */ new Map();
55
+ return g.forEach((t) => {
56
+ e.set(t.id, E.filter((e) => e.column === t.id));
117
57
  }), e;
118
- }, [l, u]);
119
- return l.length ? /* @__PURE__ */ n("div", {
120
- className: "flex h-full p-2 gap-2 overflow-x-auto",
121
- children: l.map((e) => {
122
- let t = f[e.id] || [];
123
- return /* @__PURE__ */ r("div", {
124
- className: "flex-shrink-0 w-48 flex flex-col bg-bg-sunken/50 rounded-lg",
125
- children: [/* @__PURE__ */ r("div", {
126
- className: "flex items-center gap-2 p-2 border-b border-border-default",
127
- children: [
128
- /* @__PURE__ */ n("div", {
129
- className: "w-2 h-2 rounded-full",
130
- style: { backgroundColor: e.color || "var(--color-text-muted)" }
131
- }),
132
- /* @__PURE__ */ n("span", {
133
- className: "text-xs font-medium text-text-primary truncate",
134
- children: e.title
135
- }),
136
- /* @__PURE__ */ n("span", {
137
- className: "ml-auto text-xs text-text-muted",
138
- children: t.length
139
- })
140
- ]
141
- }), /* @__PURE__ */ r("div", {
142
- className: "flex-1 p-2 space-y-2 overflow-y-auto",
143
- children: [t.map((e) => /* @__PURE__ */ r("div", {
144
- className: `
58
+ }, [g, E]);
59
+ return g.length ? /* @__PURE__ */ c("div", {
60
+ className: "flex h-full flex-col",
61
+ children: [m && /* @__PURE__ */ s("p", {
62
+ role: "status",
63
+ className: "px-2 pt-2 text-xs text-text-secondary",
64
+ children: S || (p ? f("bigconsole.widget.kanban.move.editorHint", "Moves are saved to this widget.") : f("bigconsole.widget.kanban.move.viewerHint", "Moves are temporary in this view."))
65
+ }), /* @__PURE__ */ s("div", {
66
+ className: "flex min-h-0 flex-1 p-2 gap-2 overflow-x-auto",
67
+ children: g.map((e) => {
68
+ let t = O.get(e.id) || [];
69
+ return /* @__PURE__ */ c("div", {
70
+ "aria-label": `Column ${e.title}`,
71
+ className: "flex-shrink-0 w-48 flex flex-col bg-bg-sunken/50 rounded-lg",
72
+ onDragOver: (e) => {
73
+ w.current && !b && (e.preventDefault(), e.dataTransfer.dropEffect = "move");
74
+ },
75
+ onDrop: (t) => {
76
+ t.preventDefault(), t.stopPropagation();
77
+ let n = w.current;
78
+ w.current = null, n && D(n, e.id);
79
+ },
80
+ children: [/* @__PURE__ */ c("div", {
81
+ className: "flex items-center gap-2 p-2 border-b border-border-default",
82
+ children: [
83
+ /* @__PURE__ */ s("div", {
84
+ className: "w-2 h-2 rounded-full",
85
+ style: { backgroundColor: e.color || "var(--color-text-muted)" }
86
+ }),
87
+ /* @__PURE__ */ s("span", {
88
+ className: "text-xs font-medium text-text-primary truncate",
89
+ children: e.title
90
+ }),
91
+ /* @__PURE__ */ s("span", {
92
+ className: "ml-auto text-xs text-text-muted",
93
+ children: t.length
94
+ })
95
+ ]
96
+ }), /* @__PURE__ */ c("div", {
97
+ className: "flex-1 p-2 space-y-2 overflow-y-auto",
98
+ children: [t.map((e) => /* @__PURE__ */ c("div", {
99
+ draggable: m && !b,
100
+ onDragStart: (t) => {
101
+ t.stopPropagation(), w.current = e.id, t.dataTransfer.effectAllowed = "move", t.dataTransfer.setData("text/plain", e.id);
102
+ },
103
+ onDragEnd: () => {
104
+ w.current = null;
105
+ },
106
+ className: `
145
107
  p-2 bg-bg-surface rounded border border-border-default
146
- ${o ? "cursor-pointer hover:border-action-primary-bg" : ""}
108
+ ${d ? "cursor-pointer hover:border-action-primary-bg" : ""}
147
109
  transition-colors
148
110
  `,
149
- onClick: () => o?.(e),
150
- children: [/* @__PURE__ */ n("p", {
151
- className: "text-xs font-medium text-text-primary line-clamp-2",
152
- children: e.title
153
- }), /* @__PURE__ */ r("div", {
154
- className: "flex items-center gap-1 mt-2 flex-wrap",
155
- children: [e.priority && /* @__PURE__ */ n("span", {
156
- className: `px-1.5 py-0.5 text-[10px] rounded ${i(e.priority)}`,
157
- children: e.priority
158
- }), e.assignee && /* @__PURE__ */ n("span", {
159
- className: "px-1.5 py-0.5 text-[10px] rounded bg-action-primary-bg/10 text-action-primary-text",
160
- children: e.assignee
161
- })]
111
+ onClick: () => d?.(e),
112
+ children: [
113
+ /* @__PURE__ */ s("p", {
114
+ className: "text-xs font-medium text-text-primary line-clamp-2",
115
+ children: e.title
116
+ }),
117
+ e.description && /* @__PURE__ */ s("p", {
118
+ className: "mt-1 text-xs text-text-secondary line-clamp-3",
119
+ children: e.description
120
+ }),
121
+ m && /* @__PURE__ */ s("select", {
122
+ "aria-label": `Move ${e.title} to column`,
123
+ value: e.column,
124
+ disabled: b,
125
+ onClick: (e) => e.stopPropagation(),
126
+ onChange: (t) => {
127
+ D(e.id, t.target.value);
128
+ },
129
+ className: "mt-2 w-full rounded border border-border-default bg-bg-surface text-xs text-text-secondary",
130
+ children: g.map((e) => /* @__PURE__ */ s("option", {
131
+ value: e.id,
132
+ children: e.title
133
+ }, e.id))
134
+ }),
135
+ /* @__PURE__ */ c("div", {
136
+ className: "flex items-center gap-1 mt-2 flex-wrap",
137
+ children: [e.priority && /* @__PURE__ */ s("span", {
138
+ className: `px-1.5 py-0.5 text-[10px] rounded ${l(e.priority)}`,
139
+ children: e.priority
140
+ }), e.assignee && /* @__PURE__ */ s("span", {
141
+ className: "px-1.5 py-0.5 text-[10px] rounded bg-action-primary-bg/10 text-action-primary-text",
142
+ children: e.assignee
143
+ })]
144
+ })
145
+ ]
146
+ }, e.id)), t.length === 0 && /* @__PURE__ */ s("p", {
147
+ className: "text-[10px] text-text-muted text-center py-4",
148
+ children: f("bigconsole.widget.kanban.noTasks", "No tasks")
162
149
  })]
163
- }, e.id)), t.length === 0 && /* @__PURE__ */ n("p", {
164
- className: "text-[10px] text-text-muted text-center py-4",
165
- children: s("bigconsole.widget.kanban.noTasks", "No tasks")
166
150
  })]
167
- })]
168
- }, e.id);
169
- })
170
- }) : /* @__PURE__ */ n("div", {
151
+ }, e.id);
152
+ })
153
+ })]
154
+ }) : /* @__PURE__ */ s("div", {
171
155
  className: "flex items-center justify-center h-full text-text-secondary text-sm",
172
- children: /* @__PURE__ */ r("div", {
156
+ children: /* @__PURE__ */ c("div", {
173
157
  className: "text-center",
174
- children: [/* @__PURE__ */ n("svg", {
158
+ children: [/* @__PURE__ */ s("svg", {
175
159
  className: "w-12 h-12 mx-auto mb-2 text-text-muted",
176
160
  fill: "none",
177
161
  stroke: "currentColor",
178
162
  viewBox: "0 0 24 24",
179
- children: /* @__PURE__ */ n("path", {
163
+ children: /* @__PURE__ */ s("path", {
180
164
  strokeLinecap: "round",
181
165
  strokeLinejoin: "round",
182
166
  strokeWidth: 1.5,
183
167
  d: "M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2"
184
168
  })
185
- }), /* @__PURE__ */ n("p", { children: s("bigconsole.widget.kanban.emptyState", "No board data") })]
169
+ }), /* @__PURE__ */ s("p", { children: f("bigconsole.widget.kanban.emptyState", "No board data") })]
186
170
  })
187
171
  });
188
172
  });
189
173
  //#endregion
190
- export { f as default };
174
+ export { u as default };
191
175
 
192
176
  //# sourceMappingURL=KanbanView.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"KanbanView.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/kanban/KanbanView.tsx"],"sourcesContent":["/**\n * KanbanWidget Component\n *\n * Simple kanban board view with columns and tasks.\n */\n\nimport { type FC, memo, useMemo } from 'react';\nimport type { Widget } from '../../../types';\nimport type { WidgetTranslateFn } from '../utils/translate';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface KanbanViewProps extends KanbanWidgetProps {\n /** Translator — the app passes useI18n().t; the public embed passes a static fallback translator. */\n t: WidgetTranslateFn;\n}\n\nexport interface KanbanWidgetProps {\n widget: Widget;\n data?: KanbanData;\n onClick?: (task: KanbanTask) => void;\n}\n\nexport interface KanbanColumn {\n id: string;\n title: string;\n color?: string;\n}\n\nexport interface KanbanTask {\n id: string;\n title: string;\n column: string;\n priority?: 'high' | 'medium' | 'low';\n assignee?: string;\n labels?: string[];\n dueDate?: string;\n}\n\nexport interface KanbanData {\n columns?: KanbanColumn[];\n tasks?: KanbanTask[];\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction getPriorityColor(priority?: string): string {\n switch (priority) {\n case 'high':\n return 'bg-status-error-bg/20 text-status-error-text';\n case 'medium':\n return 'bg-status-warning-bg/20 text-[var(--color-status-warning-text)]';\n case 'low':\n return 'bg-status-success-bg/20 text-status-success-text';\n default:\n return 'bg-bg-sunken text-text-secondary';\n }\n}\n\n// Field names we try, in order, when the widget config doesn't name one\n// explicitly. A DataSink/parser typically emits a flat array of rows (e.g. the\n// Linear \"Kanban board\" parser -> [{ id, title, status, assignee, ... }]), so\n// the board is derived from a status/column field on those rows.\nconst COLUMN_FIELD_CANDIDATES = ['status', 'state', 'column', 'stage', 'category', 'phase', 'group'];\nconst TITLE_FIELD_CANDIDATES = ['title', 'name', 'summary', 'label', 'identifier', 'id'];\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\n/** Coerce parser output into an array of object rows (handles common envelopes). */\nfunction asRows(data: unknown): Record<string, unknown>[] {\n const onlyObjects = (arr: unknown[]): Record<string, unknown>[] =>\n arr.filter((r): r is Record<string, unknown> => !!r && typeof r === 'object' && !Array.isArray(r));\n\n if (Array.isArray(data)) return onlyObjects(data);\n\n if (data && typeof data === 'object') {\n const obj = data as Record<string, unknown>;\n for (const key of ['tasks', 'rows', 'items', 'data', 'nodes', 'results', 'records']) {\n if (Array.isArray(obj[key])) return onlyObjects(obj[key] as unknown[]);\n }\n const firstArray = Object.values(obj).find((v) => Array.isArray(v));\n if (Array.isArray(firstArray)) return onlyObjects(firstArray);\n }\n return [];\n}\n\n/** First key that actually exists on the sample row. */\nfunction firstPresent(row: Record<string, unknown>, keys: (string | undefined)[]): string | undefined {\n for (const key of keys) {\n if (key && key in row) return key;\n }\n return undefined;\n}\n\nfunction normalizePriority(value: unknown): KanbanTask['priority'] {\n const v = asString(value)?.toLowerCase();\n if (!v) return undefined;\n if (v === 'urgent' || v === 'high') return 'high';\n if (v === 'medium' || v === 'normal') return 'medium';\n if (v === 'low') return 'low';\n return undefined;\n}\n\n/**\n * Normalize widget data into the board's { columns, tasks } shape.\n *\n * KanbanWidget previously required the exact canonical shape and rendered\n * \"No board data\" for any DataSink/parser output that was a plain array of\n * rows (which is what parsers actually emit) — BOFF-4110. This adapts a flat\n * array into columns (distinct values of a status/column field, in first-seen\n * order) plus one card per row, honoring config field mappings with fallbacks.\n */\nfunction normalizeKanban(\n data: unknown,\n config?: Record<string, unknown>\n): { columns: KanbanColumn[]; tasks: KanbanTask[] } {\n // Canonical shape passes through untouched (back-compat).\n if (data && typeof data === 'object' && !Array.isArray(data) && Array.isArray((data as KanbanData).columns)) {\n const d = data as KanbanData;\n return { columns: d.columns ?? [], tasks: d.tasks ?? [] };\n }\n\n const rows = asRows(data);\n if (rows.length === 0) return { columns: [], tasks: [] };\n\n const sample = rows[0];\n const columnField = firstPresent(sample, [\n asString(config?.columnField),\n asString(config?.groupField),\n asString(config?.statusField),\n ...COLUMN_FIELD_CANDIDATES,\n asString(config?.xAxisField),\n asString(config?.labelField),\n ]);\n if (!columnField) return { columns: [], tasks: [] };\n\n const titleField = firstPresent(sample, [\n asString(config?.cardTitleField),\n asString(config?.titleField),\n ...TITLE_FIELD_CANDIDATES,\n asString(config?.labelField),\n ]);\n\n const columnTitles: Record<string, string> = {};\n const columnOrder: string[] = [];\n const tasks: KanbanTask[] = rows.map((row, i) => {\n const columnRaw = row[columnField];\n const column = columnRaw == null || columnRaw === '' ? 'Uncategorized' : String(columnRaw);\n if (!(column in columnTitles)) {\n columnTitles[column] = column;\n columnOrder.push(column);\n }\n const idRaw = row.id ?? row.identifier ?? row.key;\n const labels = Array.isArray(row.labels) ? row.labels.filter((l): l is string => typeof l === 'string') : undefined;\n return {\n id: idRaw != null ? String(idRaw) : `task-${i}`,\n title: titleField && row[titleField] != null ? String(row[titleField]) : `Item ${i + 1}`,\n column,\n assignee: asString(row.assignee),\n priority: normalizePriority(row.priority ?? row.priorityLabel),\n labels: labels && labels.length > 0 ? labels : undefined,\n };\n });\n\n const columns: KanbanColumn[] = columnOrder.map((id) => ({ id, title: columnTitles[id] ?? id }));\n return { columns, tasks };\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const KanbanView: FC<KanbanViewProps> = memo(function KanbanView({ widget, data, onClick, t }) {\n const config = widget.config as Record<string, unknown> | undefined;\n const { columns, tasks } = useMemo(() => normalizeKanban(data, config), [data, config]);\n\n // Group tasks by column\n const tasksByColumn = useMemo(() => {\n const grouped: Record<string, KanbanTask[]> = {};\n columns.forEach((col) => {\n grouped[col.id] = tasks.filter((t) => t.column === col.id);\n });\n return grouped;\n }, [columns, tasks]);\n\n if (!columns.length) {\n return (\n <div className=\"flex items-center justify-center h-full text-text-secondary text-sm\">\n <div className=\"text-center\">\n <svg className=\"w-12 h-12 mx-auto mb-2 text-text-muted\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2\"\n />\n </svg>\n <p>{t('bigconsole.widget.kanban.emptyState', 'No board data')}</p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"flex h-full p-2 gap-2 overflow-x-auto\">\n {columns.map((column) => {\n const columnTasks = tasksByColumn[column.id] || [];\n\n return (\n <div key={column.id} className=\"flex-shrink-0 w-48 flex flex-col bg-bg-sunken/50 rounded-lg\">\n {/* Column Header */}\n <div className=\"flex items-center gap-2 p-2 border-b border-border-default\">\n <div\n className=\"w-2 h-2 rounded-full\"\n style={{ backgroundColor: column.color || 'var(--color-text-muted)' }}\n />\n <span className=\"text-xs font-medium text-text-primary truncate\">{column.title}</span>\n <span className=\"ml-auto text-xs text-text-muted\">{columnTasks.length}</span>\n </div>\n\n {/* Tasks */}\n <div className=\"flex-1 p-2 space-y-2 overflow-y-auto\">\n {columnTasks.map((task) => (\n <div\n key={task.id}\n className={`\n p-2 bg-bg-surface rounded border border-border-default\n ${onClick ? 'cursor-pointer hover:border-action-primary-bg' : ''}\n transition-colors\n `}\n onClick={() => onClick?.(task)}\n >\n <p className=\"text-xs font-medium text-text-primary line-clamp-2\">{task.title}</p>\n\n <div className=\"flex items-center gap-1 mt-2 flex-wrap\">\n {task.priority && (\n <span className={`px-1.5 py-0.5 text-[10px] rounded ${getPriorityColor(task.priority)}`}>\n {task.priority}\n </span>\n )}\n {task.assignee && (\n <span className=\"px-1.5 py-0.5 text-[10px] rounded bg-action-primary-bg/10 text-action-primary-text\">\n {task.assignee}\n </span>\n )}\n </div>\n </div>\n ))}\n\n {columnTasks.length === 0 && (\n <p className=\"text-[10px] text-text-muted text-center py-4\">\n {t('bigconsole.widget.kanban.noTasks', 'No tasks')}\n </p>\n )}\n </div>\n </div>\n );\n })}\n </div>\n );\n});\n\nexport default KanbanView;\n"],"mappings":";;;AAkDA,SAAS,EAAiB,GAA2B;AACnD,SAAQ,GAAR;EACE,KAAK,OACH,QAAO;EACT,KAAK,SACH,QAAO;EACT,KAAK,MACH,QAAO;EACT,QACE,QAAO;;;AAQb,IAAM,IAA0B;CAAC;CAAU;CAAS;CAAU;CAAS;CAAY;CAAS;CAAQ,EAC9F,IAAyB;CAAC;CAAS;CAAQ;CAAW;CAAS;CAAc;CAAK;AAExF,SAAS,EAAS,GAAoC;AACpD,QAAO,OAAO,KAAU,YAAY,EAAM,SAAS,IAAI,IAAQ,KAAA;;AAIjE,SAAS,EAAO,GAA0C;CACxD,IAAM,KAAe,MACnB,EAAI,QAAQ,MAAoC,CAAC,CAAC,KAAK,OAAO,KAAM,YAAY,CAAC,MAAM,QAAQ,EAAE,CAAC;AAEpG,KAAI,MAAM,QAAQ,EAAK,CAAE,QAAO,EAAY,EAAK;AAEjD,KAAI,KAAQ,OAAO,KAAS,UAAU;EACpC,IAAM,IAAM;AACZ,OAAK,IAAM,KAAO;GAAC;GAAS;GAAQ;GAAS;GAAQ;GAAS;GAAW;GAAU,CACjF,KAAI,MAAM,QAAQ,EAAI,GAAK,CAAE,QAAO,EAAY,EAAI,GAAkB;EAExE,IAAM,IAAa,OAAO,OAAO,EAAI,CAAC,MAAM,MAAM,MAAM,QAAQ,EAAE,CAAC;AACnE,MAAI,MAAM,QAAQ,EAAW,CAAE,QAAO,EAAY,EAAW;;AAE/D,QAAO,EAAE;;AAIX,SAAS,EAAa,GAA8B,GAAkD;AACpG,MAAK,IAAM,KAAO,EAChB,KAAI,KAAO,KAAO,EAAK,QAAO;;AAKlC,SAAS,EAAkB,GAAwC;CACjE,IAAM,IAAI,EAAS,EAAM,EAAE,aAAa;AACnC,QACL;MAAI,MAAM,YAAY,MAAM,OAAQ,QAAO;AAC3C,MAAI,MAAM,YAAY,MAAM,SAAU,QAAO;AAC7C,MAAI,MAAM,MAAO,QAAO;;;AAa1B,SAAS,EACP,GACA,GACkD;AAElD,KAAI,KAAQ,OAAO,KAAS,YAAY,CAAC,MAAM,QAAQ,EAAK,IAAI,MAAM,QAAS,EAAoB,QAAQ,EAAE;EAC3G,IAAM,IAAI;AACV,SAAO;GAAE,SAAS,EAAE,WAAW,EAAE;GAAE,OAAO,EAAE,SAAS,EAAE;GAAE;;CAG3D,IAAM,IAAO,EAAO,EAAK;AACzB,KAAI,EAAK,WAAW,EAAG,QAAO;EAAE,SAAS,EAAE;EAAE,OAAO,EAAE;EAAE;CAExD,IAAM,IAAS,EAAK,IACd,IAAc,EAAa,GAAQ;EACvC,EAAS,GAAQ,YAAY;EAC7B,EAAS,GAAQ,WAAW;EAC5B,EAAS,GAAQ,YAAY;EAC7B,GAAG;EACH,EAAS,GAAQ,WAAW;EAC5B,EAAS,GAAQ,WAAW;EAC7B,CAAC;AACF,KAAI,CAAC,EAAa,QAAO;EAAE,SAAS,EAAE;EAAE,OAAO,EAAE;EAAE;CAEnD,IAAM,IAAa,EAAa,GAAQ;EACtC,EAAS,GAAQ,eAAe;EAChC,EAAS,GAAQ,WAAW;EAC5B,GAAG;EACH,EAAS,GAAQ,WAAW;EAC7B,CAAC,EAEI,IAAuC,EAAE,EACzC,IAAwB,EAAE,EAC1B,IAAsB,EAAK,KAAK,GAAK,MAAM;EAC/C,IAAM,IAAY,EAAI,IAChB,IAAS,KAAa,QAAQ,MAAc,KAAK,kBAAkB,OAAO,EAAU;AAC1F,EAAM,KAAU,MACd,EAAa,KAAU,GACvB,EAAY,KAAK,EAAO;EAE1B,IAAM,IAAQ,EAAI,MAAM,EAAI,cAAc,EAAI,KACxC,IAAS,MAAM,QAAQ,EAAI,OAAO,GAAG,EAAI,OAAO,QAAQ,MAAmB,OAAO,KAAM,SAAS,GAAG,KAAA;AAC1G,SAAO;GACL,IAAI,KAAS,OAAuB,QAAQ,MAAxB,OAAO,EAAM;GACjC,OAAO,KAAc,EAAI,MAAe,OAAO,OAAO,EAAI,GAAY,GAAG,QAAQ,IAAI;GACrF;GACA,UAAU,EAAS,EAAI,SAAS;GAChC,UAAU,EAAkB,EAAI,YAAY,EAAI,cAAc;GAC9D,QAAQ,KAAU,EAAO,SAAS,IAAI,IAAS,KAAA;GAChD;GACD;AAGF,QAAO;EAAE,SADuB,EAAY,KAAK,OAAQ;GAAE;GAAI,OAAO,EAAa,MAAO;GAAI,EAAE;EAC9E;EAAO;;AAO3B,IAAa,IAAkC,EAAK,SAAoB,EAAE,WAAQ,SAAM,YAAS,QAAK;CACpG,IAAM,IAAS,EAAO,QAChB,EAAE,YAAS,aAAU,QAAc,EAAgB,GAAM,EAAO,EAAE,CAAC,GAAM,EAAO,CAAC,EAGjF,IAAgB,QAAc;EAClC,IAAM,IAAwC,EAAE;AAIhD,SAHA,EAAQ,SAAS,MAAQ;AACvB,KAAQ,EAAI,MAAM,EAAM,QAAQ,MAAM,EAAE,WAAW,EAAI,GAAG;IAC1D,EACK;IACN,CAAC,GAAS,EAAM,CAAC;AAoBpB,QAlBK,EAAQ,SAmBX,kBAAC,OAAD;EAAK,WAAU;YACZ,EAAQ,KAAK,MAAW;GACvB,IAAM,IAAc,EAAc,EAAO,OAAO,EAAE;AAElD,UACE,kBAAC,OAAD;IAAqB,WAAU;cAA/B,CAEE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OACE,WAAU;OACV,OAAO,EAAE,iBAAiB,EAAO,SAAS,2BAA2B;OACrE,CAAA;MACF,kBAAC,QAAD;OAAM,WAAU;iBAAkD,EAAO;OAAa,CAAA;MACtF,kBAAC,QAAD;OAAM,WAAU;iBAAmC,EAAY;OAAc,CAAA;MACzE;QAGN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACG,EAAY,KAAK,MAChB,kBAAC,OAAD;MAEE,WAAW;;wBAEL,IAAU,kDAAkD,GAAG;;;MAGrE,eAAe,IAAU,EAAK;gBAPhC,CASE,kBAAC,KAAD;OAAG,WAAU;iBAAsD,EAAK;OAAU,CAAA,EAElF,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACG,EAAK,YACJ,kBAAC,QAAD;QAAM,WAAW,qCAAqC,EAAiB,EAAK,SAAS;kBAClF,EAAK;QACD,CAAA,EAER,EAAK,YACJ,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAK;QACD,CAAA,CAEL;SACF;QAtBC,EAAK,GAsBN,CACN,EAED,EAAY,WAAW,KACtB,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAE,oCAAoC,WAAW;MAChD,CAAA,CAEF;OACF;MA9CI,EAAO,GA8CX;IAER;EACE,CAAA,GAvEJ,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;IAAyC,MAAK;IAAO,QAAO;IAAe,SAAQ;cAChG,kBAAC,QAAD;KACE,eAAc;KACd,gBAAe;KACf,aAAa;KACb,GAAE;KACF,CAAA;IACE,CAAA,EACN,kBAAC,KAAD,EAAA,UAAI,EAAE,uCAAuC,gBAAgB,EAAK,CAAA,CAC9D;;EACF,CAAA;EA6DV"}
1
+ {"version":3,"file":"KanbanView.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/kanban/KanbanView.tsx"],"sourcesContent":["/**\n * KanbanWidget Component\n *\n * Simple kanban board view with columns and tasks.\n */\n\nimport { type FC, memo, useMemo, useRef, useState } from 'react';\nimport {\n normalizeKanban,\n applyKanbanMoves,\n validKanbanMoves,\n type KanbanData,\n type KanbanTask,\n type KanbanColumnMove,\n} from './model';\nexport type { KanbanData, KanbanTask, KanbanColumn } from './model';\nimport type { Widget } from '../../../types';\nimport type { WidgetTranslateFn } from '../utils/translate';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface KanbanViewProps extends KanbanWidgetProps {\n /** Translator — the app passes useI18n().t; the public embed passes a static fallback translator. */\n t: WidgetTranslateFn;\n interactive?: boolean;\n onSaveMoves?: (moves: KanbanColumnMove[]) => Promise<boolean>;\n}\n\nexport interface KanbanWidgetProps {\n widget: Widget;\n data?: KanbanData;\n onClick?: (task: KanbanTask) => void;\n}\n\nfunction getPriorityColor(priority?: string): string {\n switch (priority) {\n case 'high':\n return 'bg-status-error-bg/20 text-status-error-text';\n case 'medium':\n return 'bg-status-warning-bg/20 text-[var(--color-status-warning-text)]';\n case 'low':\n return 'bg-status-success-bg/20 text-status-success-text';\n default:\n return 'bg-bg-sunken text-text-secondary';\n }\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const KanbanView: FC<KanbanViewProps> = memo(function KanbanView({\n widget,\n data,\n onClick,\n t,\n onSaveMoves,\n interactive = false,\n}) {\n const config = widget.config as Record<string, unknown> | undefined;\n const { columns, tasks: sourceTasks } = useMemo(() => normalizeKanban(data, config), [data, config]);\n const [local, setLocal] = useState<{ data: unknown; widgetId: string; moves: KanbanColumnMove[] } | null>(null);\n const [saving, setSaving] = useState(false);\n const [announcement, setAnnouncement] = useState('');\n const dragging = useRef<string | null>(null);\n const moves =\n local && local.data === data && local.widgetId === widget.id\n ? local.moves\n : validKanbanMoves(config?.kanbanColumnMoves);\n const tasks = useMemo(() => applyKanbanMoves(sourceTasks, columns, moves), [sourceTasks, columns, moves]);\n const moveTask = async (taskId: string, targetColumn: string) => {\n if (!interactive || saving || !columns.some((column) => column.id === targetColumn)) return;\n const task = sourceTasks.find((item) => item.id === taskId);\n if (!task || tasks.find((item) => item.id === taskId)?.column === targetColumn) return;\n const previous = validKanbanMoves(moves);\n const next = [\n ...previous.filter((move) => move.taskId !== taskId),\n { taskId, sourceColumn: task.column, targetColumn },\n ];\n setLocal({ data, widgetId: widget.id, moves: next });\n if (!onSaveMoves || !task.stableId) {\n setAnnouncement(\n t('bigconsole.widget.kanban.move.temporary', '{{title}} moved to {{column}} for this view.', {\n title: task.title,\n column: targetColumn,\n })\n );\n return;\n }\n setSaving(true);\n let saved: boolean;\n try {\n saved = await onSaveMoves(\n next.filter((move) => sourceTasks.some((source) => source.id === move.taskId && source.stableId))\n );\n } catch {\n saved = false;\n }\n setSaving(false);\n if (!saved) {\n setLocal({ data, widgetId: widget.id, moves: previous });\n setAnnouncement(\n t(\n 'bigconsole.widget.kanban.move.failed',\n 'Could not save the move. The card was returned to its previous column.'\n )\n );\n } else {\n setAnnouncement(\n t('bigconsole.widget.kanban.move.saved', '{{title}} moved to {{column}} and saved.', {\n title: task.title,\n column: targetColumn,\n })\n );\n }\n };\n\n // Group tasks by column\n const tasksByColumn = useMemo(() => {\n const grouped = new Map<string, KanbanTask[]>();\n columns.forEach((col) => {\n grouped.set(\n col.id,\n tasks.filter((t) => t.column === col.id)\n );\n });\n return grouped;\n }, [columns, tasks]);\n\n if (!columns.length) {\n return (\n <div className=\"flex items-center justify-center h-full text-text-secondary text-sm\">\n <div className=\"text-center\">\n <svg className=\"w-12 h-12 mx-auto mb-2 text-text-muted\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2\"\n />\n </svg>\n <p>{t('bigconsole.widget.kanban.emptyState', 'No board data')}</p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"flex h-full flex-col\">\n {interactive && (\n <p role=\"status\" className=\"px-2 pt-2 text-xs text-text-secondary\">\n {announcement ||\n (onSaveMoves\n ? t('bigconsole.widget.kanban.move.editorHint', 'Moves are saved to this widget.')\n : t('bigconsole.widget.kanban.move.viewerHint', 'Moves are temporary in this view.'))}\n </p>\n )}\n <div className=\"flex min-h-0 flex-1 p-2 gap-2 overflow-x-auto\">\n {columns.map((column) => {\n const columnTasks = tasksByColumn.get(column.id) || [];\n\n return (\n <div\n key={column.id}\n aria-label={`Column ${column.title}`}\n className=\"flex-shrink-0 w-48 flex flex-col bg-bg-sunken/50 rounded-lg\"\n onDragOver={(event) => {\n if (dragging.current && !saving) {\n event.preventDefault();\n event.dataTransfer.dropEffect = 'move';\n }\n }}\n onDrop={(event) => {\n event.preventDefault();\n event.stopPropagation();\n const id = dragging.current;\n dragging.current = null;\n if (id) void moveTask(id, column.id);\n }}\n >\n {/* Column Header */}\n <div className=\"flex items-center gap-2 p-2 border-b border-border-default\">\n <div\n className=\"w-2 h-2 rounded-full\"\n style={{ backgroundColor: column.color || 'var(--color-text-muted)' }}\n />\n <span className=\"text-xs font-medium text-text-primary truncate\">{column.title}</span>\n <span className=\"ml-auto text-xs text-text-muted\">{columnTasks.length}</span>\n </div>\n\n {/* Tasks */}\n <div className=\"flex-1 p-2 space-y-2 overflow-y-auto\">\n {columnTasks.map((task) => (\n <div\n key={task.id}\n draggable={interactive && !saving}\n onDragStart={(event) => {\n event.stopPropagation();\n dragging.current = task.id;\n event.dataTransfer.effectAllowed = 'move';\n event.dataTransfer.setData('text/plain', task.id);\n }}\n onDragEnd={() => {\n dragging.current = null;\n }}\n className={`\n p-2 bg-bg-surface rounded border border-border-default\n ${onClick ? 'cursor-pointer hover:border-action-primary-bg' : ''}\n transition-colors\n `}\n onClick={() => onClick?.(task)}\n >\n <p className=\"text-xs font-medium text-text-primary line-clamp-2\">{task.title}</p>\n {task.description && (\n <p className=\"mt-1 text-xs text-text-secondary line-clamp-3\">{task.description}</p>\n )}\n {interactive && (\n <select\n aria-label={`Move ${task.title} to column`}\n value={task.column}\n disabled={saving}\n onClick={(event) => event.stopPropagation()}\n onChange={(event) => {\n void moveTask(task.id, event.target.value);\n }}\n className=\"mt-2 w-full rounded border border-border-default bg-bg-surface text-xs text-text-secondary\"\n >\n {columns.map((target) => (\n <option key={target.id} value={target.id}>\n {target.title}\n </option>\n ))}\n </select>\n )}\n\n <div className=\"flex items-center gap-1 mt-2 flex-wrap\">\n {task.priority && (\n <span className={`px-1.5 py-0.5 text-[10px] rounded ${getPriorityColor(task.priority)}`}>\n {task.priority}\n </span>\n )}\n {task.assignee && (\n <span className=\"px-1.5 py-0.5 text-[10px] rounded bg-action-primary-bg/10 text-action-primary-text\">\n {task.assignee}\n </span>\n )}\n </div>\n </div>\n ))}\n\n {columnTasks.length === 0 && (\n <p className=\"text-[10px] text-text-muted text-center py-4\">\n {t('bigconsole.widget.kanban.noTasks', 'No tasks')}\n </p>\n )}\n </div>\n </div>\n );\n })}\n </div>\n </div>\n );\n});\n\nexport default KanbanView;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"KanbanWidget.d.ts","sourceRoot":"","sources":["../../../../../src/bigconsole/components/widgets/kanban/KanbanWidget.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,EAAE,EAAQ,MAAM,OAAO,CAAC;AAEtC,OAAO,EAAc,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAElE,cAAc,cAAc,CAAC;AAE7B,eAAO,MAAM,YAAY,EAAE,EAAE,CAAC,iBAAiB,CAG7C,CAAC;AAEH,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"KanbanWidget.d.ts","sourceRoot":"","sources":["../../../../../src/bigconsole/components/widgets/kanban/KanbanWidget.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,EAAE,EAAQ,MAAM,OAAO,CAAC;AAGtC,OAAO,EAAc,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAElE,cAAc,cAAc,CAAC;AAE7B,eAAO,MAAM,YAAY,EAAE,EAAE,CAAC,iBAAiB,CAW7C,CAAC;AAEH,eAAe,YAAY,CAAC"}
@@ -1,16 +1,19 @@
1
- import e from "./KanbanView.js";
2
- import { memo as t } from "react";
3
- import { jsx as n } from "react/jsx-runtime";
4
- import { useI18n as r } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
1
+ import e from "../../../hooks/useWidgetConfigPersist.js";
2
+ import t from "./KanbanView.js";
3
+ import { memo as n } from "react";
4
+ import { jsx as r } from "react/jsx-runtime";
5
+ import { useI18n as i } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
5
6
  //#region src/bigconsole/components/widgets/kanban/KanbanWidget.tsx
6
- var i = t(function(t) {
7
- let { t: i } = r();
8
- return /* @__PURE__ */ n(e, {
9
- ...t,
10
- t: i
7
+ var a = n(function(n) {
8
+ let { t: a } = i(), { canPersist: o, persistConfig: s } = e(n.widget);
9
+ return /* @__PURE__ */ r(t, {
10
+ ...n,
11
+ t: a,
12
+ interactive: !0,
13
+ onSaveMoves: o ? (e) => s({ kanbanColumnMoves: e }) : void 0
11
14
  });
12
15
  });
13
16
  //#endregion
14
- export { i as default };
17
+ export { a as default };
15
18
 
16
19
  //# sourceMappingURL=KanbanWidget.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"KanbanWidget.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/kanban/KanbanWidget.tsx"],"sourcesContent":["/**\n * KanbanWidget — in-app wrapper around the pure KanbanView (BOFF-7926).\n *\n * The view is i18n-provider-free so the public embed bundle can render it with a\n * static translator (the fe-libs I18nProvider touches localStorage, which throws\n * in third-party iframes when the visitor blocks third-party storage).\n */\n\nimport { type FC, memo } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { KanbanView, type KanbanWidgetProps } from './KanbanView';\n\nexport * from './KanbanView';\n\nexport const KanbanWidget: FC<KanbanWidgetProps> = memo(function KanbanWidget(props) {\n const { t } = useI18n();\n return <KanbanView {...props} t={t} />;\n});\n\nexport default KanbanWidget;\n"],"mappings":";;;;;AAcA,IAAa,IAAsC,EAAK,SAAsB,GAAO;CACnF,IAAM,EAAE,SAAM,GAAS;AACvB,QAAO,kBAAC,GAAD;EAAY,GAAI;EAAU;EAAK,CAAA;EACtC"}
1
+ {"version":3,"file":"KanbanWidget.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/kanban/KanbanWidget.tsx"],"sourcesContent":["/**\n * KanbanWidget — in-app wrapper around the pure KanbanView (BOFF-7926).\n *\n * The view is i18n-provider-free so the public embed bundle can render it with a\n * static translator (the fe-libs I18nProvider touches localStorage, which throws\n * in third-party iframes when the visitor blocks third-party storage).\n */\n\nimport { type FC, memo } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useWidgetConfigPersist } from '../../../hooks/useWidgetConfigPersist';\nimport { KanbanView, type KanbanWidgetProps } from './KanbanView';\n\nexport * from './KanbanView';\n\nexport const KanbanWidget: FC<KanbanWidgetProps> = memo(function KanbanWidget(props) {\n const { t } = useI18n();\n const { canPersist, persistConfig } = useWidgetConfigPersist(props.widget);\n return (\n <KanbanView\n {...props}\n t={t}\n interactive\n onSaveMoves={canPersist ? (moves) => persistConfig({ kanbanColumnMoves: moves }) : undefined}\n />\n );\n});\n\nexport default KanbanWidget;\n"],"mappings":";;;;;;AAeA,IAAa,IAAsC,EAAK,SAAsB,GAAO;CACnF,IAAM,EAAE,SAAM,GAAS,EACjB,EAAE,eAAY,qBAAkB,EAAuB,EAAM,OAAO;AAC1E,QACE,kBAAC,GAAD;EACE,GAAI;EACD;EACH,aAAA;EACA,aAAa,KAAc,MAAU,EAAc,EAAE,mBAAmB,GAAO,CAAC,GAAG,KAAA;EACnF,CAAA;EAEJ"}
@@ -0,0 +1,42 @@
1
+ export interface KanbanColumn {
2
+ id: string;
3
+ title: string;
4
+ color?: string;
5
+ }
6
+ export interface KanbanTask {
7
+ id: string;
8
+ title: string;
9
+ column: string;
10
+ description?: string;
11
+ stableId?: boolean;
12
+ priority?: 'high' | 'medium' | 'low';
13
+ assignee?: string;
14
+ labels?: string[];
15
+ dueDate?: string;
16
+ }
17
+ export interface KanbanData {
18
+ columns?: KanbanColumn[];
19
+ tasks?: KanbanTask[];
20
+ }
21
+ /**
22
+ * Normalize widget data into the board's { columns, tasks } shape.
23
+ *
24
+ * KanbanWidget previously required the exact canonical shape and rendered
25
+ * "No board data" for any DataSink/parser output that was a plain array of
26
+ * rows (which is what parsers actually emit) — BOFF-4110. This adapts a flat
27
+ * array into columns (distinct values of a status/column field, in first-seen
28
+ * order) plus one card per row, honoring config field mappings with fallbacks.
29
+ */
30
+ export declare function normalizeKanban(data: unknown, config?: Record<string, unknown>): {
31
+ columns: KanbanColumn[];
32
+ tasks: KanbanTask[];
33
+ };
34
+ export interface KanbanColumnMove {
35
+ taskId: string;
36
+ sourceColumn: string;
37
+ targetColumn: string;
38
+ }
39
+ export declare function validKanbanMoves(value: unknown): KanbanColumnMove[];
40
+ /** Overrides only affect a current card in its original source column. */
41
+ export declare function applyKanbanMoves(tasks: KanbanTask[], columns: KanbanColumn[], value: unknown): KanbanTask[];
42
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../../../src/bigconsole/components/widgets/kanban/model.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;CACtB;AAqED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,OAAO,EACb,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B;IAAE,OAAO,EAAE,YAAY,EAAE,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAA;CAAE,CAwDlD;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,EAAE,CAWnE;AAED,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,OAAO,GAAG,UAAU,EAAE,CAY3G"}
@@ -0,0 +1,145 @@
1
+ //#region src/bigconsole/components/widgets/kanban/model.ts
2
+ var e = [
3
+ "status",
4
+ "state",
5
+ "column",
6
+ "stage",
7
+ "category",
8
+ "phase",
9
+ "group"
10
+ ], t = [
11
+ "title",
12
+ "name",
13
+ "summary",
14
+ "label",
15
+ "identifier",
16
+ "id"
17
+ ];
18
+ function n(e) {
19
+ return typeof e == "string" && e.length > 0 ? e : void 0;
20
+ }
21
+ function r(e) {
22
+ let t = (e) => e.filter((e) => !!e && typeof e == "object" && !Array.isArray(e));
23
+ if (Array.isArray(e)) return t(e);
24
+ if (e && typeof e == "object") {
25
+ let n = e;
26
+ for (let e of [
27
+ "tasks",
28
+ "rows",
29
+ "items",
30
+ "data",
31
+ "nodes",
32
+ "results",
33
+ "records"
34
+ ]) if (Array.isArray(n[e])) return t(n[e]);
35
+ let r = Object.values(n).find((e) => Array.isArray(e));
36
+ if (Array.isArray(r)) return t(r);
37
+ }
38
+ return [];
39
+ }
40
+ function i(e, t) {
41
+ for (let n of t) if (n && n in e) return n;
42
+ }
43
+ function a(e) {
44
+ let t = n(e)?.toLowerCase();
45
+ if (t) {
46
+ if (t === "urgent" || t === "high") return "high";
47
+ if (t === "medium" || t === "normal") return "medium";
48
+ if (t === "low") return "low";
49
+ }
50
+ }
51
+ function o(e) {
52
+ let t = e.map((e) => typeof e == "string" || typeof e == "number" ? String(e) : ""), n = /* @__PURE__ */ new Map();
53
+ t.forEach((e) => {
54
+ e && n.set(e, (n.get(e) ?? 0) + 1);
55
+ });
56
+ let r = new Set(t);
57
+ return t.map((e, t) => {
58
+ if (e && n.get(e) === 1) return {
59
+ id: e,
60
+ stableId: !0
61
+ };
62
+ let i = `row-${t}`;
63
+ for (; r.has(i);) i += "-local";
64
+ return r.add(i), {
65
+ id: i,
66
+ stableId: !1
67
+ };
68
+ });
69
+ }
70
+ function s(s, c) {
71
+ if (s && typeof s == "object" && !Array.isArray(s) && Array.isArray(s.columns)) {
72
+ let e = s, t = o((e.tasks ?? []).map((e) => e.id));
73
+ return {
74
+ columns: e.columns ?? [],
75
+ tasks: (e.tasks ?? []).map((e, n) => ({
76
+ ...e,
77
+ ...t[n]
78
+ }))
79
+ };
80
+ }
81
+ let l = r(s);
82
+ if (l.length === 0) return {
83
+ columns: [],
84
+ tasks: []
85
+ };
86
+ let u = l[0], d = i(u, [
87
+ n(c?.columnField),
88
+ n(c?.groupField),
89
+ n(c?.statusField),
90
+ ...e,
91
+ n(c?.xAxisField),
92
+ n(c?.labelField)
93
+ ]);
94
+ if (!d) return {
95
+ columns: [],
96
+ tasks: []
97
+ };
98
+ let f = i(u, [
99
+ n(c?.cardTitleField),
100
+ n(c?.titleField),
101
+ ...t,
102
+ n(c?.labelField)
103
+ ]), p = o(l.map((e) => e.id ?? e.identifier ?? e.key)), m = /* @__PURE__ */ new Map(), h = [], g = l.map((e, t) => {
104
+ let r = e[d], i = r == null || r === "" ? "Uncategorized" : String(r);
105
+ m.has(i) || (m.set(i, i), h.push(i));
106
+ let o = Array.isArray(e.labels) ? e.labels.filter((e) => typeof e == "string") : void 0;
107
+ return {
108
+ ...p[t],
109
+ description: n(e[n(c?.cardDescriptionField) ?? "description"]),
110
+ title: f && e[f] != null ? String(e[f]) : `Item ${t + 1}`,
111
+ column: i,
112
+ assignee: n(e.assignee),
113
+ priority: a(e.priority ?? e.priorityLabel),
114
+ labels: o && o.length > 0 ? o : void 0
115
+ };
116
+ });
117
+ return {
118
+ columns: h.map((e) => ({
119
+ id: e,
120
+ title: m.get(e) ?? e
121
+ })),
122
+ tasks: g
123
+ };
124
+ }
125
+ function c(e) {
126
+ return Array.isArray(e) ? e.filter((e) => {
127
+ if (!e || typeof e != "object") return !1;
128
+ let t = e;
129
+ return typeof t.taskId == "string" && typeof t.sourceColumn == "string" && typeof t.targetColumn == "string";
130
+ }) : [];
131
+ }
132
+ function l(e, t, n) {
133
+ let r = c(n), i = new Set(t.map((e) => e.id));
134
+ return e.map((e) => {
135
+ let t = [...r].reverse().find((t) => t.taskId === e.id && t.sourceColumn === e.column && i.has(t.targetColumn));
136
+ return t ? {
137
+ ...e,
138
+ column: t.targetColumn
139
+ } : e;
140
+ });
141
+ }
142
+ //#endregion
143
+ export { l as applyKanbanMoves, s as normalizeKanban, c as validKanbanMoves };
144
+
145
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/kanban/model.ts"],"sourcesContent":["export interface KanbanColumn {\n id: string;\n title: string;\n color?: string;\n}\n\nexport interface KanbanTask {\n id: string;\n title: string;\n column: string;\n description?: string;\n stableId?: boolean;\n priority?: 'high' | 'medium' | 'low';\n assignee?: string;\n labels?: string[];\n dueDate?: string;\n}\n\nexport interface KanbanData {\n columns?: KanbanColumn[];\n tasks?: KanbanTask[];\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n// Field names we try, in order, when the widget config doesn't name one\n// explicitly. A DataSink/parser typically emits a flat array of rows (e.g. the\n// Linear \"Kanban board\" parser -> [{ id, title, status, assignee, ... }]), so\n// the board is derived from a status/column field on those rows.\nconst COLUMN_FIELD_CANDIDATES = ['status', 'state', 'column', 'stage', 'category', 'phase', 'group'];\nconst TITLE_FIELD_CANDIDATES = ['title', 'name', 'summary', 'label', 'identifier', 'id'];\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\n/** Coerce parser output into an array of object rows (handles common envelopes). */\nfunction asRows(data: unknown): Record<string, unknown>[] {\n const onlyObjects = (arr: unknown[]): Record<string, unknown>[] =>\n arr.filter((r): r is Record<string, unknown> => !!r && typeof r === 'object' && !Array.isArray(r));\n\n if (Array.isArray(data)) return onlyObjects(data);\n\n if (data && typeof data === 'object') {\n const obj = data as Record<string, unknown>;\n for (const key of ['tasks', 'rows', 'items', 'data', 'nodes', 'results', 'records']) {\n if (Array.isArray(obj[key])) return onlyObjects(obj[key] as unknown[]);\n }\n const firstArray = Object.values(obj).find((v) => Array.isArray(v));\n if (Array.isArray(firstArray)) return onlyObjects(firstArray);\n }\n return [];\n}\n\n/** First key that actually exists on the sample row. */\nfunction firstPresent(row: Record<string, unknown>, keys: (string | undefined)[]): string | undefined {\n for (const key of keys) {\n if (key && key in row) return key;\n }\n return undefined;\n}\n\nfunction normalizePriority(value: unknown): KanbanTask['priority'] {\n const v = asString(value)?.toLowerCase();\n if (!v) return undefined;\n if (v === 'urgent' || v === 'high') return 'high';\n if (v === 'medium' || v === 'normal') return 'medium';\n if (v === 'low') return 'low';\n return undefined;\n}\n\n/** Index fallbacks are local-only and cannot collide with source IDs. */\nfunction taskIdentities(values: unknown[]): { id: string; stableId: boolean }[] {\n const ids = values.map((value) => (typeof value === 'string' || typeof value === 'number' ? String(value) : ''));\n const counts = new Map<string, number>();\n ids.forEach((id) => {\n if (id) counts.set(id, (counts.get(id) ?? 0) + 1);\n });\n const used = new Set(ids);\n return ids.map((id, index) => {\n if (id && counts.get(id) === 1) return { id, stableId: true };\n let localId = `row-${index}`;\n while (used.has(localId)) localId += '-local';\n used.add(localId);\n return { id: localId, stableId: false };\n });\n}\n\n/**\n * Normalize widget data into the board's { columns, tasks } shape.\n *\n * KanbanWidget previously required the exact canonical shape and rendered\n * \"No board data\" for any DataSink/parser output that was a plain array of\n * rows (which is what parsers actually emit) — BOFF-4110. This adapts a flat\n * array into columns (distinct values of a status/column field, in first-seen\n * order) plus one card per row, honoring config field mappings with fallbacks.\n */\nexport function normalizeKanban(\n data: unknown,\n config?: Record<string, unknown>\n): { columns: KanbanColumn[]; tasks: KanbanTask[] } {\n // Canonical shape passes through untouched (back-compat).\n if (data && typeof data === 'object' && !Array.isArray(data) && Array.isArray((data as KanbanData).columns)) {\n const d = data as KanbanData;\n const identities = taskIdentities((d.tasks ?? []).map((task) => task.id));\n return {\n columns: d.columns ?? [],\n tasks: (d.tasks ?? []).map((task, index) => ({ ...task, ...identities[index] })),\n };\n }\n\n const rows = asRows(data);\n if (rows.length === 0) return { columns: [], tasks: [] };\n\n const sample = rows[0];\n const columnField = firstPresent(sample, [\n asString(config?.columnField),\n asString(config?.groupField),\n asString(config?.statusField),\n ...COLUMN_FIELD_CANDIDATES,\n asString(config?.xAxisField),\n asString(config?.labelField),\n ]);\n if (!columnField) return { columns: [], tasks: [] };\n\n const titleField = firstPresent(sample, [\n asString(config?.cardTitleField),\n asString(config?.titleField),\n ...TITLE_FIELD_CANDIDATES,\n asString(config?.labelField),\n ]);\n\n const identities = taskIdentities(rows.map((row) => row.id ?? row.identifier ?? row.key));\n const columnTitles = new Map<string, string>();\n const columnOrder: string[] = [];\n const tasks: KanbanTask[] = rows.map((row, i) => {\n const columnRaw = row[columnField];\n const column = columnRaw == null || columnRaw === '' ? 'Uncategorized' : String(columnRaw);\n if (!columnTitles.has(column)) {\n columnTitles.set(column, column);\n columnOrder.push(column);\n }\n const labels = Array.isArray(row.labels) ? row.labels.filter((l): l is string => typeof l === 'string') : undefined;\n return {\n ...identities[i],\n description: asString(row[asString(config?.cardDescriptionField) ?? 'description']),\n title: titleField && row[titleField] != null ? String(row[titleField]) : `Item ${i + 1}`,\n column,\n assignee: asString(row.assignee),\n priority: normalizePriority(row.priority ?? row.priorityLabel),\n labels: labels && labels.length > 0 ? labels : undefined,\n };\n });\n\n const columns: KanbanColumn[] = columnOrder.map((id) => ({ id, title: columnTitles.get(id) ?? id }));\n return { columns, tasks };\n}\n\nexport interface KanbanColumnMove {\n taskId: string;\n sourceColumn: string;\n targetColumn: string;\n}\n\nexport function validKanbanMoves(value: unknown): KanbanColumnMove[] {\n if (!Array.isArray(value)) return [];\n return value.filter((move): move is KanbanColumnMove => {\n if (!move || typeof move !== 'object') return false;\n const candidate = move as Partial<KanbanColumnMove>;\n return (\n typeof candidate.taskId === 'string' &&\n typeof candidate.sourceColumn === 'string' &&\n typeof candidate.targetColumn === 'string'\n );\n });\n}\n\n/** Overrides only affect a current card in its original source column. */\nexport function applyKanbanMoves(tasks: KanbanTask[], columns: KanbanColumn[], value: unknown): KanbanTask[] {\n const moves = validKanbanMoves(value);\n const targets = new Set(columns.map((column) => column.id));\n return tasks.map((task) => {\n const move = [...moves]\n .reverse()\n .find(\n (candidate) =>\n candidate.taskId === task.id && candidate.sourceColumn === task.column && targets.has(candidate.targetColumn)\n );\n return move ? { ...task, column: move.targetColumn } : task;\n });\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-bigconsole",
3
- "version": "2026.1001.1",
3
+ "version": "2026.1001.3",
4
4
  "description": "BigConsole - AI-powered analytics and dashboard platform",
5
5
  "type": "module",
6
6
  "files": [