@burdenoff/microfe-bigconsole 2026.812.2 → 2026.825.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,44 +2,41 @@ import e from "../hooks/useWidgetOperations.js";
2
2
  import t from "../hooks/useDataSinkOperations.js";
3
3
  import n from "../hooks/useParserOperations.js";
4
4
  import "../hooks/index.js";
5
- import r from "./CustomWidgetProposalPanel.js";
6
- import { memo as i, useCallback as a, useMemo as o } from "react";
7
- import { useI18n as s } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
8
- import { Sheet as c, SheetContent as l, SheetDescription as u, SheetHeader as d, SheetTitle as f } from "@burdenoff/fe-libs/ui";
9
- import { jsx as p, jsxs as m } from "react/jsx-runtime";
5
+ import { selectActiveProposalSinks as r } from "./proposalSource.js";
6
+ import i from "./CustomWidgetProposalPanel.js";
7
+ import { memo as a, useCallback as o, useMemo as s } from "react";
8
+ import { useI18n as c } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
9
+ import { Sheet as l, SheetContent as u, SheetDescription as d, SheetHeader as f, SheetTitle as p } from "@burdenoff/fe-libs/ui";
10
+ import { jsx as m, jsxs as h } from "react/jsx-runtime";
10
11
  //#region src/bigconsole/customWidget/CustomWidgetBuilderSheet.tsx
11
- var h = {
12
+ var g = {
12
13
  x: 0,
13
14
  y: 0,
14
15
  width: 6,
15
16
  height: 4
16
- }, g = "default", _ = i(function({ isOpen: i, onClose: _, dashboardId: v, pageId: y, source: b }) {
17
- let { t: x } = s(), { dataSinks: S } = t({ skipFetch: !i }), { createWidget: C } = e(y, v, { skipFetch: !0 }), w = v === g, { parsers: T, executeParser: E } = n({
18
- consoleId: v,
19
- skipFetch: !i
20
- }), { parsers: D } = n({
21
- consoleId: g,
22
- skipFetch: !i || w
23
- }), O = o(() => S.map((e) => ({
24
- id: e.id,
25
- key: e.key,
26
- name: e.name
27
- })), [S]), k = o(() => {
28
- let e = new Set(T.map((e) => e.id));
29
- return [...T, ...D.filter((t) => !e.has(t.id))].map((e) => ({
17
+ }, _ = "default", v = a(function({ isOpen: a, onClose: v, dashboardId: y, pageId: b, source: x }) {
18
+ let { t: S } = c(), { dataSinks: C } = t({ skipFetch: !a }), { createWidget: w } = e(b, y, { skipFetch: !0 }), T = y === _, { parsers: E, executeParser: D } = n({
19
+ consoleId: y,
20
+ skipFetch: !a
21
+ }), { parsers: O } = n({
22
+ consoleId: _,
23
+ skipFetch: !a || T
24
+ }), k = s(() => r(C), [C]), A = s(() => {
25
+ let e = new Set(E.map((e) => e.id));
26
+ return [...E, ...O.filter((t) => !e.has(t.id))].map((e) => ({
30
27
  id: e.id,
31
28
  name: e.name,
32
29
  inputSinkKey: e.inputSinkKey
33
30
  }));
34
- }, [T, D]), A = a(async (e) => {
31
+ }, [E, O]), j = o(async (e) => {
35
32
  if (!e.binding.parserId) return;
36
- let t = await E(e.binding.parserId);
37
- if (!t || !t.success) throw Error(t?.error || x("bigconsole.widget.customWidget.error.couldNotResolveBinding", "Could not resolve the binding."));
33
+ let t = await D(e.binding.parserId);
34
+ if (!t || !t.success) throw Error(t?.error || S("bigconsole.widget.customWidget.error.couldNotResolveBinding", "Could not resolve the binding."));
38
35
  return t.output;
39
- }, [E, x]), j = a(async (e) => {
40
- if (!await C({
41
- pageId: y,
42
- dashboardId: v,
36
+ }, [D, S]), M = o(async (e) => {
37
+ if (!await w({
38
+ pageId: b,
39
+ dashboardId: y,
43
40
  type: e.widgetType,
44
41
  title: e.presentation.title,
45
42
  description: e.presentation.description,
@@ -52,18 +49,18 @@ var h = {
52
49
  sourcePrompt: e.sourcePrompt,
53
50
  proposalVersion: e.version
54
51
  },
55
- position: { ...h }
56
- })) throw Error(x("bigconsole.widget.customWidget.error.widgetNotCreated", "The widget could not be created."));
52
+ position: { ...g }
53
+ })) throw Error(S("bigconsole.widget.customWidget.error.widgetNotCreated", "The widget could not be created."));
57
54
  }, [
58
- C,
55
+ w,
56
+ b,
59
57
  y,
60
- v,
61
- x
58
+ S
62
59
  ]);
63
- return /* @__PURE__ */ p(c, {
64
- open: i,
65
- onOpenChange: (e) => !e && _(),
66
- children: /* @__PURE__ */ m(l, {
60
+ return /* @__PURE__ */ m(l, {
61
+ open: a,
62
+ onOpenChange: (e) => !e && v(),
63
+ children: /* @__PURE__ */ h(u, {
67
64
  style: {
68
65
  width: "min(480px, 100vw)",
69
66
  maxWidth: "100vw",
@@ -71,32 +68,32 @@ var h = {
71
68
  display: "flex",
72
69
  flexDirection: "column"
73
70
  },
74
- children: [/* @__PURE__ */ m(d, {
71
+ children: [/* @__PURE__ */ h(f, {
75
72
  style: {
76
73
  padding: "var(--space-cardPadding)",
77
74
  borderBottom: "1px solid var(--color-border-subtle)",
78
75
  backgroundColor: "var(--color-bg-surface)",
79
76
  flexShrink: 0
80
77
  },
81
- children: [/* @__PURE__ */ p(f, { children: x("bigconsole.widget.customWidget.sheet.title", "AI custom widget") }), /* @__PURE__ */ p(u, { children: x("bigconsole.widget.customWidget.sheet.description", "Describe a widget, review the proposal, then approve it onto the dashboard.") })]
82
- }), /* @__PURE__ */ p("div", {
78
+ children: [/* @__PURE__ */ m(p, { children: S("bigconsole.widget.customWidget.sheet.title", "AI custom widget") }), /* @__PURE__ */ m(d, { children: S("bigconsole.widget.customWidget.sheet.description", "Describe a widget, review the proposal, then approve it onto the dashboard.") })]
79
+ }), /* @__PURE__ */ m("div", {
83
80
  style: {
84
81
  flex: 1,
85
82
  minHeight: 0
86
83
  },
87
- children: /* @__PURE__ */ p(r, {
88
- sinks: O,
89
- parsers: k,
90
- resolvePreviewData: A,
91
- onApprove: j,
92
- onClose: _,
93
- source: b
84
+ children: /* @__PURE__ */ m(i, {
85
+ sinks: k,
86
+ parsers: A,
87
+ resolvePreviewData: j,
88
+ onApprove: M,
89
+ onClose: v,
90
+ source: x
94
91
  })
95
92
  })]
96
93
  })
97
94
  });
98
95
  });
99
96
  //#endregion
100
- export { _ as default };
97
+ export { v as default };
101
98
 
102
99
  //# sourceMappingURL=CustomWidgetBuilderSheet.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CustomWidgetBuilderSheet.js","names":[],"sources":["../../../src/bigconsole/customWidget/CustomWidgetBuilderSheet.tsx"],"sourcesContent":["/**\n * Container that wires the custom-widget proposal panel to real workspace data\n * (BOFF-5532).\n *\n * Supplies the panel with the user's actual permitted DataSinks and parsers,\n * resolves previews by running the real parser, and persists an approved\n * proposal through the normal `createWidget` path — so an approved widget is an\n * ordinary widget, editable afterwards through the standard inspector.\n */\n\nimport { type FC, memo, useCallback, useMemo } from 'react';\nimport { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nimport { useDataSinkOperations, useParserOperations, useWidgetOperations } from '../hooks';\nimport { CustomWidgetProposalPanel } from './CustomWidgetProposalPanel';\nimport type { CustomWidgetProposal } from './proposalContract';\nimport type { CustomWidgetProposalSource, ProposalCandidateParser, ProposalCandidateSink } from './proposalSource';\n\nexport interface CustomWidgetBuilderSheetProps {\n isOpen: boolean;\n onClose: () => void;\n dashboardId: string;\n pageId: string;\n /** Swap for the GraphQL-backed source once BOFF-3021's backend half lands. */\n source?: CustomWidgetProposalSource;\n}\n\n/** Grid slot for an approved widget; the reviewer can move it afterwards. */\nconst DEFAULT_POSITION = { x: 0, y: 0, width: 6, height: 4 } as const;\n\n/** Sentinel consoleId under which workspace-global parsers are stored. */\nconst GLOBAL_CONSOLE_ID = 'default';\n\nexport const CustomWidgetBuilderSheet: FC<CustomWidgetBuilderSheetProps> = memo(function CustomWidgetBuilderSheet({\n isOpen,\n onClose,\n dashboardId,\n pageId,\n source,\n}) {\n const { t } = useI18n();\n const { dataSinks } = useDataSinkOperations({ skipFetch: !isOpen });\n const { createWidget } = useWidgetOperations(pageId, dashboardId, { skipFetch: true });\n\n // Parsers are either dashboard-scoped or workspace-global (`consoleId: 'default'`),\n // and the widget inspector offers both. Fetch both here too, otherwise a proposal\n // can never bind to a global parser — which is what the parser builder creates by\n // default, so the picker would look empty in a typical workspace.\n const isGlobalConsole = dashboardId === GLOBAL_CONSOLE_ID;\n const { parsers: dashboardParsers, executeParser } = useParserOperations({\n consoleId: dashboardId,\n skipFetch: !isOpen,\n });\n const { parsers: globalParsers } = useParserOperations({\n consoleId: GLOBAL_CONSOLE_ID,\n skipFetch: !isOpen || isGlobalConsole,\n });\n\n const sinks: ProposalCandidateSink[] = useMemo(\n () => dataSinks.map((sink) => ({ id: sink.id, key: sink.key, name: sink.name })),\n [dataSinks]\n );\n\n const parserCandidates: ProposalCandidateParser[] = useMemo(() => {\n const seen = new Set(dashboardParsers.map((parser) => parser.id));\n const merged = [...dashboardParsers, ...globalParsers.filter((parser) => !seen.has(parser.id))];\n return merged.map((parser) => ({ id: parser.id, name: parser.name, inputSinkKey: parser.inputSinkKey }));\n }, [dashboardParsers, globalParsers]);\n\n /**\n * Resolves what the binding actually produces. Rejects on failure so the\n * panel can distinguish \"no data\" from \"you cannot read this\".\n */\n const resolvePreviewData = useCallback(\n async (proposal: CustomWidgetProposal): Promise<unknown> => {\n if (!proposal.binding.parserId) return undefined;\n const execution = await executeParser(proposal.binding.parserId);\n if (!execution || !execution.success) {\n throw new Error(\n execution?.error ||\n t('bigconsole.widget.customWidget.error.couldNotResolveBinding', 'Could not resolve the binding.')\n );\n }\n return execution.output;\n },\n [executeParser, t]\n );\n\n const handleApprove = useCallback(\n async (proposal: CustomWidgetProposal) => {\n const widget = await createWidget({\n pageId,\n dashboardId,\n type: proposal.widgetType,\n title: proposal.presentation.title,\n description: proposal.presentation.description,\n dataSinkId: proposal.binding.dataSinkId || undefined,\n parserId: proposal.binding.parserId,\n config: {\n ...proposal.presentation.config,\n ...proposal.fieldMapping,\n // Provenance, so an approved widget can be traced back to its prompt\n // and survives export/import and clone with its origin intact.\n generatedBy: proposal.generatedBy,\n sourcePrompt: proposal.sourcePrompt,\n proposalVersion: proposal.version,\n },\n position: { ...DEFAULT_POSITION },\n });\n\n if (!widget)\n throw new Error(t('bigconsole.widget.customWidget.error.widgetNotCreated', 'The widget could not be created.'));\n },\n [createWidget, pageId, dashboardId, t]\n );\n\n return (\n <Sheet open={isOpen} onOpenChange={(open) => !open && onClose()}>\n {/*\n `min(480px, 100vw)` rather than a fixed 480px + 95vw cap, and header\n padding on the `--space-*` scale rather than raw pixels, so this sheet\n follows the same rules as the widget inspector (BOFF-5636).\n */}\n <SheetContent\n style={{\n width: 'min(480px, 100vw)',\n maxWidth: '100vw',\n padding: 0,\n display: 'flex',\n flexDirection: 'column',\n }}\n >\n <SheetHeader\n style={{\n padding: 'var(--space-cardPadding)',\n borderBottom: '1px solid var(--color-border-subtle)',\n backgroundColor: 'var(--color-bg-surface)',\n flexShrink: 0,\n }}\n >\n <SheetTitle>{t('bigconsole.widget.customWidget.sheet.title', 'AI custom widget')}</SheetTitle>\n <SheetDescription>\n {t(\n 'bigconsole.widget.customWidget.sheet.description',\n 'Describe a widget, review the proposal, then approve it onto the dashboard.'\n )}\n </SheetDescription>\n </SheetHeader>\n\n <div style={{ flex: 1, minHeight: 0 }}>\n <CustomWidgetProposalPanel\n sinks={sinks}\n parsers={parserCandidates}\n resolvePreviewData={resolvePreviewData}\n onApprove={handleApprove}\n onClose={onClose}\n source={source}\n />\n </div>\n </SheetContent>\n </Sheet>\n );\n});\n\nexport default CustomWidgetBuilderSheet;\n"],"mappings":";;;;;;;;;;AA6BA,IAAM,IAAmB;CAAE,GAAG;CAAG,GAAG;CAAG,OAAO;CAAG,QAAQ;CAAG,EAGtD,IAAoB,WAEb,IAA8D,EAAK,SAAkC,EAChH,WACA,YACA,gBACA,WACA,aACC;CACD,IAAM,EAAE,SAAM,GAAS,EACjB,EAAE,iBAAc,EAAsB,EAAE,WAAW,CAAC,GAAQ,CAAC,EAC7D,EAAE,oBAAiB,EAAoB,GAAQ,GAAa,EAAE,WAAW,IAAM,CAAC,EAMhF,IAAkB,MAAgB,GAClC,EAAE,SAAS,GAAkB,qBAAkB,EAAoB;EACvE,WAAW;EACX,WAAW,CAAC;EACb,CAAC,EACI,EAAE,SAAS,MAAkB,EAAoB;EACrD,WAAW;EACX,WAAW,CAAC,KAAU;EACvB,CAAC,EAEI,IAAiC,QAC/B,EAAU,KAAK,OAAU;EAAE,IAAI,EAAK;EAAI,KAAK,EAAK;EAAK,MAAM,EAAK;EAAM,EAAE,EAChF,CAAC,EAAU,CACZ,EAEK,IAA8C,QAAc;EAChE,IAAM,IAAO,IAAI,IAAI,EAAiB,KAAK,MAAW,EAAO,GAAG,CAAC;AAEjE,SADe,CAAC,GAAG,GAAkB,GAAG,EAAc,QAAQ,MAAW,CAAC,EAAK,IAAI,EAAO,GAAG,CAAC,CAAC,CACjF,KAAK,OAAY;GAAE,IAAI,EAAO;GAAI,MAAM,EAAO;GAAM,cAAc,EAAO;GAAc,EAAE;IACvG,CAAC,GAAkB,EAAc,CAAC,EAM/B,IAAqB,EACzB,OAAO,MAAqD;AAC1D,MAAI,CAAC,EAAS,QAAQ,SAAU;EAChC,IAAM,IAAY,MAAM,EAAc,EAAS,QAAQ,SAAS;AAChE,MAAI,CAAC,KAAa,CAAC,EAAU,QAC3B,OAAU,MACR,GAAW,SACT,EAAE,+DAA+D,iCAAiC,CACrG;AAEH,SAAO,EAAU;IAEnB,CAAC,GAAe,EAAE,CACnB,EAEK,IAAgB,EACpB,OAAO,MAAmC;AAqBxC,MAAI,CApBW,MAAM,EAAa;GAChC;GACA;GACA,MAAM,EAAS;GACf,OAAO,EAAS,aAAa;GAC7B,aAAa,EAAS,aAAa;GACnC,YAAY,EAAS,QAAQ,cAAc,KAAA;GAC3C,UAAU,EAAS,QAAQ;GAC3B,QAAQ;IACN,GAAG,EAAS,aAAa;IACzB,GAAG,EAAS;IAGZ,aAAa,EAAS;IACtB,cAAc,EAAS;IACvB,iBAAiB,EAAS;IAC3B;GACD,UAAU,EAAE,GAAG,GAAkB;GAClC,CAAC,CAGA,OAAU,MAAM,EAAE,yDAAyD,mCAAmC,CAAC;IAEnH;EAAC;EAAc;EAAQ;EAAa;EAAE,CACvC;AAED,QACE,kBAAC,GAAD;EAAO,MAAM;EAAQ,eAAe,MAAS,CAAC,KAAQ,GAAS;YAM7D,kBAAC,GAAD;GACE,OAAO;IACL,OAAO;IACP,UAAU;IACV,SAAS;IACT,SAAS;IACT,eAAe;IAChB;aAPH,CASE,kBAAC,GAAD;IACE,OAAO;KACL,SAAS;KACT,cAAc;KACd,iBAAiB;KACjB,YAAY;KACb;cANH,CAQE,kBAAC,GAAD,EAAA,UAAa,EAAE,8CAA8C,mBAAmB,EAAc,CAAA,EAC9F,kBAAC,GAAD,EAAA,UACG,EACC,oDACA,8EACD,EACgB,CAAA,CACP;OAEd,kBAAC,OAAD;IAAK,OAAO;KAAE,MAAM;KAAG,WAAW;KAAG;cACnC,kBAAC,GAAD;KACS;KACP,SAAS;KACW;KACpB,WAAW;KACF;KACD;KACR,CAAA;IACE,CAAA,CACO;;EACT,CAAA;EAEV"}
1
+ {"version":3,"file":"CustomWidgetBuilderSheet.js","names":[],"sources":["../../../src/bigconsole/customWidget/CustomWidgetBuilderSheet.tsx"],"sourcesContent":["/**\n * Container that wires the custom-widget proposal panel to real workspace data\n * (BOFF-5532).\n *\n * Supplies the panel with the user's actual permitted DataSinks and parsers,\n * resolves previews by running the real parser, and persists an approved\n * proposal through the normal `createWidget` path — so an approved widget is an\n * ordinary widget, editable afterwards through the standard inspector.\n */\n\nimport { type FC, memo, useCallback, useMemo } from 'react';\nimport { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nimport { useDataSinkOperations, useParserOperations, useWidgetOperations } from '../hooks';\nimport { CustomWidgetProposalPanel } from './CustomWidgetProposalPanel';\nimport type { CustomWidgetProposal } from './proposalContract';\nimport {\n selectActiveProposalSinks,\n type CustomWidgetProposalSource,\n type ProposalCandidateParser,\n type ProposalCandidateSink,\n} from './proposalSource';\n\nexport interface CustomWidgetBuilderSheetProps {\n isOpen: boolean;\n onClose: () => void;\n dashboardId: string;\n pageId: string;\n /** Swap for the GraphQL-backed source once BOFF-3021's backend half lands. */\n source?: CustomWidgetProposalSource;\n}\n\n/** Grid slot for an approved widget; the reviewer can move it afterwards. */\nconst DEFAULT_POSITION = { x: 0, y: 0, width: 6, height: 4 } as const;\n\n/** Sentinel consoleId under which workspace-global parsers are stored. */\nconst GLOBAL_CONSOLE_ID = 'default';\n\nexport const CustomWidgetBuilderSheet: FC<CustomWidgetBuilderSheetProps> = memo(function CustomWidgetBuilderSheet({\n isOpen,\n onClose,\n dashboardId,\n pageId,\n source,\n}) {\n const { t } = useI18n();\n const { dataSinks } = useDataSinkOperations({ skipFetch: !isOpen });\n const { createWidget } = useWidgetOperations(pageId, dashboardId, { skipFetch: true });\n\n // Parsers are either dashboard-scoped or workspace-global (`consoleId: 'default'`),\n // and the widget inspector offers both. Fetch both here too, otherwise a proposal\n // can never bind to a global parser — which is what the parser builder creates by\n // default, so the picker would look empty in a typical workspace.\n const isGlobalConsole = dashboardId === GLOBAL_CONSOLE_ID;\n const { parsers: dashboardParsers, executeParser } = useParserOperations({\n consoleId: dashboardId,\n skipFetch: !isOpen,\n });\n const { parsers: globalParsers } = useParserOperations({\n consoleId: GLOBAL_CONSOLE_ID,\n skipFetch: !isOpen || isGlobalConsole,\n });\n\n const sinks: ProposalCandidateSink[] = useMemo(() => selectActiveProposalSinks(dataSinks), [dataSinks]);\n\n const parserCandidates: ProposalCandidateParser[] = useMemo(() => {\n const seen = new Set(dashboardParsers.map((parser) => parser.id));\n const merged = [...dashboardParsers, ...globalParsers.filter((parser) => !seen.has(parser.id))];\n return merged.map((parser) => ({ id: parser.id, name: parser.name, inputSinkKey: parser.inputSinkKey }));\n }, [dashboardParsers, globalParsers]);\n\n /**\n * Resolves what the binding actually produces. Rejects on failure so the\n * panel can distinguish \"no data\" from \"you cannot read this\".\n */\n const resolvePreviewData = useCallback(\n async (proposal: CustomWidgetProposal): Promise<unknown> => {\n if (!proposal.binding.parserId) return undefined;\n const execution = await executeParser(proposal.binding.parserId);\n if (!execution || !execution.success) {\n throw new Error(\n execution?.error ||\n t('bigconsole.widget.customWidget.error.couldNotResolveBinding', 'Could not resolve the binding.')\n );\n }\n return execution.output;\n },\n [executeParser, t]\n );\n\n const handleApprove = useCallback(\n async (proposal: CustomWidgetProposal) => {\n const widget = await createWidget({\n pageId,\n dashboardId,\n type: proposal.widgetType,\n title: proposal.presentation.title,\n description: proposal.presentation.description,\n dataSinkId: proposal.binding.dataSinkId || undefined,\n parserId: proposal.binding.parserId,\n config: {\n ...proposal.presentation.config,\n ...proposal.fieldMapping,\n // Provenance, so an approved widget can be traced back to its prompt\n // and survives export/import and clone with its origin intact.\n generatedBy: proposal.generatedBy,\n sourcePrompt: proposal.sourcePrompt,\n proposalVersion: proposal.version,\n },\n position: { ...DEFAULT_POSITION },\n });\n\n if (!widget)\n throw new Error(t('bigconsole.widget.customWidget.error.widgetNotCreated', 'The widget could not be created.'));\n },\n [createWidget, pageId, dashboardId, t]\n );\n\n return (\n <Sheet open={isOpen} onOpenChange={(open) => !open && onClose()}>\n {/*\n `min(480px, 100vw)` rather than a fixed 480px + 95vw cap, and header\n padding on the `--space-*` scale rather than raw pixels, so this sheet\n follows the same rules as the widget inspector (BOFF-5636).\n */}\n <SheetContent\n style={{\n width: 'min(480px, 100vw)',\n maxWidth: '100vw',\n padding: 0,\n display: 'flex',\n flexDirection: 'column',\n }}\n >\n <SheetHeader\n style={{\n padding: 'var(--space-cardPadding)',\n borderBottom: '1px solid var(--color-border-subtle)',\n backgroundColor: 'var(--color-bg-surface)',\n flexShrink: 0,\n }}\n >\n <SheetTitle>{t('bigconsole.widget.customWidget.sheet.title', 'AI custom widget')}</SheetTitle>\n <SheetDescription>\n {t(\n 'bigconsole.widget.customWidget.sheet.description',\n 'Describe a widget, review the proposal, then approve it onto the dashboard.'\n )}\n </SheetDescription>\n </SheetHeader>\n\n <div style={{ flex: 1, minHeight: 0 }}>\n <CustomWidgetProposalPanel\n sinks={sinks}\n parsers={parserCandidates}\n resolvePreviewData={resolvePreviewData}\n onApprove={handleApprove}\n onClose={onClose}\n source={source}\n />\n </div>\n </SheetContent>\n </Sheet>\n );\n});\n\nexport default CustomWidgetBuilderSheet;\n"],"mappings":";;;;;;;;;;;AAkCA,IAAM,IAAmB;CAAE,GAAG;CAAG,GAAG;CAAG,OAAO;CAAG,QAAQ;CAAG,EAGtD,IAAoB,WAEb,IAA8D,EAAK,SAAkC,EAChH,WACA,YACA,gBACA,WACA,aACC;CACD,IAAM,EAAE,SAAM,GAAS,EACjB,EAAE,iBAAc,EAAsB,EAAE,WAAW,CAAC,GAAQ,CAAC,EAC7D,EAAE,oBAAiB,EAAoB,GAAQ,GAAa,EAAE,WAAW,IAAM,CAAC,EAMhF,IAAkB,MAAgB,GAClC,EAAE,SAAS,GAAkB,qBAAkB,EAAoB;EACvE,WAAW;EACX,WAAW,CAAC;EACb,CAAC,EACI,EAAE,SAAS,MAAkB,EAAoB;EACrD,WAAW;EACX,WAAW,CAAC,KAAU;EACvB,CAAC,EAEI,IAAiC,QAAc,EAA0B,EAAU,EAAE,CAAC,EAAU,CAAC,EAEjG,IAA8C,QAAc;EAChE,IAAM,IAAO,IAAI,IAAI,EAAiB,KAAK,MAAW,EAAO,GAAG,CAAC;AAEjE,SADe,CAAC,GAAG,GAAkB,GAAG,EAAc,QAAQ,MAAW,CAAC,EAAK,IAAI,EAAO,GAAG,CAAC,CAAC,CACjF,KAAK,OAAY;GAAE,IAAI,EAAO;GAAI,MAAM,EAAO;GAAM,cAAc,EAAO;GAAc,EAAE;IACvG,CAAC,GAAkB,EAAc,CAAC,EAM/B,IAAqB,EACzB,OAAO,MAAqD;AAC1D,MAAI,CAAC,EAAS,QAAQ,SAAU;EAChC,IAAM,IAAY,MAAM,EAAc,EAAS,QAAQ,SAAS;AAChE,MAAI,CAAC,KAAa,CAAC,EAAU,QAC3B,OAAU,MACR,GAAW,SACT,EAAE,+DAA+D,iCAAiC,CACrG;AAEH,SAAO,EAAU;IAEnB,CAAC,GAAe,EAAE,CACnB,EAEK,IAAgB,EACpB,OAAO,MAAmC;AAqBxC,MAAI,CApBW,MAAM,EAAa;GAChC;GACA;GACA,MAAM,EAAS;GACf,OAAO,EAAS,aAAa;GAC7B,aAAa,EAAS,aAAa;GACnC,YAAY,EAAS,QAAQ,cAAc,KAAA;GAC3C,UAAU,EAAS,QAAQ;GAC3B,QAAQ;IACN,GAAG,EAAS,aAAa;IACzB,GAAG,EAAS;IAGZ,aAAa,EAAS;IACtB,cAAc,EAAS;IACvB,iBAAiB,EAAS;IAC3B;GACD,UAAU,EAAE,GAAG,GAAkB;GAClC,CAAC,CAGA,OAAU,MAAM,EAAE,yDAAyD,mCAAmC,CAAC;IAEnH;EAAC;EAAc;EAAQ;EAAa;EAAE,CACvC;AAED,QACE,kBAAC,GAAD;EAAO,MAAM;EAAQ,eAAe,MAAS,CAAC,KAAQ,GAAS;YAM7D,kBAAC,GAAD;GACE,OAAO;IACL,OAAO;IACP,UAAU;IACV,SAAS;IACT,SAAS;IACT,eAAe;IAChB;aAPH,CASE,kBAAC,GAAD;IACE,OAAO;KACL,SAAS;KACT,cAAc;KACd,iBAAiB;KACjB,YAAY;KACb;cANH,CAQE,kBAAC,GAAD,EAAA,UAAa,EAAE,8CAA8C,mBAAmB,EAAc,CAAA,EAC9F,kBAAC,GAAD,EAAA,UACG,EACC,oDACA,8EACD,EACgB,CAAA,CACP;OAEd,kBAAC,OAAD;IAAK,OAAO;KAAE,MAAM;KAAG,WAAW;KAAG;cACnC,kBAAC,GAAD;KACS;KACP,SAAS;KACW;KACpB,WAAW;KACF;KACD;KACR,CAAA;IACE,CAAA,CACO;;EACT,CAAA;EAEV"}
@@ -1,5 +1,12 @@
1
1
  //#region src/bigconsole/customWidget/proposalSource.ts
2
- var e = [
2
+ function e(e) {
3
+ return e.filter((e) => !e.deletedAt).map(({ id: e, key: t, name: n }) => ({
4
+ id: e,
5
+ key: t,
6
+ name: n
7
+ }));
8
+ }
9
+ var t = [
3
10
  {
4
11
  type: "funnel_chart",
5
12
  words: [
@@ -58,7 +65,7 @@ var e = [
58
65
  "graph"
59
66
  ]
60
67
  }
61
- ], t = [
68
+ ], n = [
62
69
  {
63
70
  chartType: "bar",
64
71
  words: ["bar"]
@@ -85,17 +92,17 @@ var e = [
85
92
  ]
86
93
  }
87
94
  ];
88
- function n(t) {
89
- let n = t.toLowerCase();
90
- for (let { type: t, words: r } of e) if (r.some((e) => n.includes(e))) return t;
91
- return "chart";
92
- }
93
95
  function r(e) {
94
96
  let n = e.toLowerCase();
95
- for (let { chartType: e, words: r } of t) if (r.some((e) => n.includes(e))) return e;
97
+ for (let { type: e, words: r } of t) if (r.some((e) => n.includes(e))) return e;
98
+ return "chart";
99
+ }
100
+ function i(e) {
101
+ let t = e.toLowerCase();
102
+ for (let { chartType: e, words: r } of n) if (r.some((e) => t.includes(e))) return e;
96
103
  return "line";
97
104
  }
98
- function i(e, t) {
105
+ function a(e, t) {
99
106
  if (t.length === 0) return;
100
107
  let n = e.toLowerCase().split(/[^a-z0-9]+/).filter((e) => e.length > 2), r;
101
108
  for (let e of t) {
@@ -107,7 +114,7 @@ function i(e, t) {
107
114
  }
108
115
  return r?.sink ?? t[0];
109
116
  }
110
- function a(e) {
117
+ function o(e) {
111
118
  switch (e) {
112
119
  case "heatmap": return {
113
120
  xAxisField: "column",
@@ -131,7 +138,7 @@ function a(e) {
131
138
  };
132
139
  }
133
140
  }
134
- function o(e, t) {
141
+ function s(e, t) {
135
142
  let n = {
136
143
  showLegend: !0,
137
144
  showTooltip: !0
@@ -139,7 +146,7 @@ function o(e, t) {
139
146
  switch (e) {
140
147
  case "chart": return {
141
148
  ...n,
142
- chartType: r(t),
149
+ chartType: i(t),
143
150
  showGrid: !0
144
151
  };
145
152
  case "funnel_chart": return {
@@ -166,39 +173,39 @@ function o(e, t) {
166
173
  default: return n;
167
174
  }
168
175
  }
169
- function s(e) {
176
+ function c(e) {
170
177
  let t = e.trim().replace(/^(please\s+)?(create|build|make|add|show me|show|give me)\s+(an?\s+|the\s+)?/i, "").split(/[.;\n]/)[0].trim();
171
178
  if (!t) return "Custom widget";
172
179
  let n = t.charAt(0).toUpperCase() + t.slice(1);
173
180
  return n.length > 60 ? `${n.slice(0, 57)}…` : n;
174
181
  }
175
- var c = 0, l = {
182
+ var l = 0, u = {
176
183
  id: "local-heuristic-v1",
177
- async propose({ prompt: e, sinks: t, parsers: r }) {
178
- let u = n(e), d = i(e, t), f = d ? r.find((e) => e.inputSinkKey === d.key) : void 0;
179
- return c += 1, {
184
+ async propose({ prompt: e, sinks: t, parsers: n }) {
185
+ let i = r(e), d = a(e, t), f = d ? n.find((e) => e.inputSinkKey === d.key) : void 0;
186
+ return l += 1, {
180
187
  version: "1.0",
181
- id: `proposal-${c}`,
188
+ id: `proposal-${l}`,
182
189
  sourcePrompt: e,
183
- generatedBy: l.id,
184
- widgetType: u,
190
+ generatedBy: u.id,
191
+ widgetType: i,
185
192
  binding: {
186
193
  dataSinkId: d?.id ?? "",
187
194
  dataSinkKey: d?.key,
188
195
  parserId: f?.id,
189
196
  parserName: f?.name
190
197
  },
191
- fieldMapping: a(u),
198
+ fieldMapping: o(i),
192
199
  presentation: {
193
- title: s(e),
200
+ title: c(e),
194
201
  description: `Generated from: "${e}"`,
195
- config: o(u, e)
202
+ config: s(i, e)
196
203
  },
197
204
  rationale: d ? `Matched DataSink "${d.key}"${f ? ` and parser "${f.name}"` : " (no parser found for this sink)"}.` : "No DataSink is available in this workspace — create one before saving this widget."
198
205
  };
199
206
  }
200
207
  };
201
208
  //#endregion
202
- export { l as localProposalSource };
209
+ export { u as localProposalSource, e as selectActiveProposalSinks };
203
210
 
204
211
  //# sourceMappingURL=proposalSource.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"proposalSource.js","names":[],"sources":["../../../src/bigconsole/customWidget/proposalSource.ts"],"sourcesContent":["/**\n * Proposal source — the seam between the review UI and whatever produces\n * proposals (BOFF-5532 / BOFF-3021).\n *\n * The UI only ever talks to `CustomWidgetProposalSource`. Today the default\n * implementation derives a proposal locally from the prompt and the workspace's\n * real sinks and parsers, because the backend operation that will emit typed\n * proposals is owned by BOFF-3021's backend half and does not exist yet.\n *\n * When it lands, add a `graphqlProposalSource` here that calls it and swap the\n * value passed to the panel. Nothing in the review, edit, approve or persist\n * path changes: those already run against real sinks, real parsers and the real\n * `createBigConsoleWidget` mutation.\n */\n\nimport {\n CUSTOM_WIDGET_PROPOSAL_VERSION,\n type CustomWidgetProposal,\n type ProposableWidgetType,\n type ProposalFieldMapping,\n} from './proposalContract';\n\n/** A sink the current user may bind to, as the panel knows it. */\nexport interface ProposalCandidateSink {\n id: string;\n key: string;\n name?: string | null;\n}\n\n/** A parser the current user may bind to. */\nexport interface ProposalCandidateParser {\n id: string;\n name: string;\n inputSinkKey: string;\n}\n\nexport interface ProposalRequest {\n prompt: string;\n sinks: readonly ProposalCandidateSink[];\n parsers: readonly ProposalCandidateParser[];\n}\n\nexport interface CustomWidgetProposalSource {\n /** Stable id, surfaced in the UI so reviewers know what produced a proposal. */\n readonly id: string;\n propose(request: ProposalRequest): Promise<CustomWidgetProposal>;\n}\n\n// ============================================================================\n// Local heuristic source\n// ============================================================================\n\n/**\n * Keyword → widget type. Ordered most-specific first so \"conversion funnel\"\n * matches funnel before a generic \"chart\" would win.\n */\nconst TYPE_KEYWORDS: ReadonlyArray<{ type: ProposableWidgetType; words: readonly string[] }> = [\n { type: 'funnel_chart', words: ['funnel', 'conversion', 'drop-off', 'dropoff', 'stage'] },\n { type: 'heatmap', words: ['heatmap', 'heat map', 'matrix', 'intensity', 'by hour', 'density'] },\n { type: 'retention', words: ['retention', 'cohort'] },\n { type: 'table', words: ['table', 'list of', 'rows', 'breakdown table', 'grid'] },\n { type: 'metric_card', words: ['kpi', 'single number', 'metric card', 'scorecard', 'total'] },\n { type: 'chart', words: ['chart', 'trend', 'over time', 'line', 'bar', 'area', 'pie', 'graph'] },\n];\n\nconst CHART_SUBTYPE_KEYWORDS: ReadonlyArray<{ chartType: string; words: readonly string[] }> = [\n { chartType: 'bar', words: ['bar'] },\n { chartType: 'area', words: ['area', 'cumulative'] },\n { chartType: 'pie', words: ['pie', 'composition', 'share', 'mix'] },\n { chartType: 'line', words: ['line', 'trend', 'over time'] },\n];\n\nfunction detectWidgetType(prompt: string): ProposableWidgetType {\n const lower = prompt.toLowerCase();\n for (const { type, words } of TYPE_KEYWORDS) {\n if (words.some((word) => lower.includes(word))) return type;\n }\n return 'chart';\n}\n\nfunction detectChartSubtype(prompt: string): string {\n const lower = prompt.toLowerCase();\n for (const { chartType, words } of CHART_SUBTYPE_KEYWORDS) {\n if (words.some((word) => lower.includes(word))) return chartType;\n }\n return 'line';\n}\n\n/**\n * Picks the sink whose key/name shares the most word-stems with the prompt.\n * Falls back to the first permitted sink so a proposal is always reviewable\n * rather than failing outright.\n */\nfunction pickSink(prompt: string, sinks: readonly ProposalCandidateSink[]): ProposalCandidateSink | undefined {\n if (sinks.length === 0) return undefined;\n const tokens = prompt\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter((token) => token.length > 2);\n\n let best: { sink: ProposalCandidateSink; score: number } | undefined;\n for (const sink of sinks) {\n const haystack = `${sink.key} ${sink.name ?? ''}`.toLowerCase();\n const score = tokens.reduce((acc, token) => (haystack.includes(token) ? acc + 1 : acc), 0);\n if (!best || score > best.score) best = { sink, score };\n }\n return best?.sink ?? sinks[0];\n}\n\n/** Default field mapping per widget type, using the renderers' conventions. */\nfunction defaultFieldMapping(type: ProposableWidgetType): ProposalFieldMapping {\n switch (type) {\n case 'heatmap':\n return { xAxisField: 'column', yAxisField: 'row', valueField: 'value' };\n case 'funnel_chart':\n return { labelField: 'name', valueField: 'value' };\n case 'metric_card':\n return { valueField: 'value' };\n case 'table':\n return {};\n case 'retention':\n return { cohortField: 'cohort', valueField: 'value' };\n case 'chart':\n default:\n return { xAxisField: 'name', labelField: 'name', valueField: 'value' };\n }\n}\n\nfunction defaultConfig(type: ProposableWidgetType, prompt: string): Record<string, unknown> {\n const base: Record<string, unknown> = { showLegend: true, showTooltip: true };\n switch (type) {\n case 'chart':\n return { ...base, chartType: detectChartSubtype(prompt), showGrid: true };\n case 'funnel_chart':\n return { ...base, chartType: 'funnel', showLabels: true, showPercentages: true, orientation: 'vertical' };\n case 'heatmap':\n return { ...base, colorScheme: 'blue', showValues: true };\n case 'table':\n return { ...base, pageSize: 10, sortable: true };\n case 'metric_card':\n return { ...base, showTrend: true };\n default:\n return base;\n }\n}\n\n/** Turns the prompt's first clause into a widget title. */\nfunction deriveTitle(prompt: string): string {\n const cleaned = prompt\n .trim()\n .replace(/^(please\\s+)?(create|build|make|add|show me|show|give me)\\s+(an?\\s+|the\\s+)?/i, '')\n .split(/[.;\\n]/)[0]\n .trim();\n if (!cleaned) return 'Custom widget';\n const titled = cleaned.charAt(0).toUpperCase() + cleaned.slice(1);\n return titled.length > 60 ? `${titled.slice(0, 57)}…` : titled;\n}\n\nlet proposalCounter = 0;\n\n/**\n * Derives a proposal from the prompt and the workspace's real, permitted sinks\n * and parsers. Deterministic, so the review UI and its tests behave predictably.\n */\nexport const localProposalSource: CustomWidgetProposalSource = {\n id: 'local-heuristic-v1',\n\n async propose({ prompt, sinks, parsers }: ProposalRequest): Promise<CustomWidgetProposal> {\n const widgetType = detectWidgetType(prompt);\n const sink = pickSink(prompt, sinks);\n const parser = sink ? parsers.find((candidate) => candidate.inputSinkKey === sink.key) : undefined;\n\n proposalCounter += 1;\n\n return {\n version: CUSTOM_WIDGET_PROPOSAL_VERSION,\n id: `proposal-${proposalCounter}`,\n sourcePrompt: prompt,\n generatedBy: localProposalSource.id,\n widgetType,\n binding: {\n dataSinkId: sink?.id ?? '',\n dataSinkKey: sink?.key,\n parserId: parser?.id,\n parserName: parser?.name,\n },\n fieldMapping: defaultFieldMapping(widgetType),\n presentation: {\n title: deriveTitle(prompt),\n description: `Generated from: \"${prompt}\"`,\n config: defaultConfig(widgetType, prompt),\n },\n rationale: sink\n ? `Matched DataSink \"${sink.key}\"${parser ? ` and parser \"${parser.name}\"` : ' (no parser found for this sink)'}.`\n : 'No DataSink is available in this workspace — create one before saving this widget.',\n };\n },\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"proposalSource.js","names":[],"sources":["../../../src/bigconsole/customWidget/proposalSource.ts"],"sourcesContent":["/**\n * Proposal source — the seam between the review UI and whatever produces\n * proposals (BOFF-5532 / BOFF-3021).\n *\n * The UI only ever talks to `CustomWidgetProposalSource`. Today the default\n * implementation derives a proposal locally from the prompt and the workspace's\n * real sinks and parsers, because the backend operation that will emit typed\n * proposals is owned by BOFF-3021's backend half and does not exist yet.\n *\n * When it lands, add a `graphqlProposalSource` here that calls it and swap the\n * value passed to the panel. Nothing in the review, edit, approve or persist\n * path changes: those already run against real sinks, real parsers and the real\n * `createBigConsoleWidget` mutation.\n */\n\nimport {\n CUSTOM_WIDGET_PROPOSAL_VERSION,\n type CustomWidgetProposal,\n type ProposableWidgetType,\n type ProposalFieldMapping,\n} from './proposalContract';\n\n/** A sink the current user may bind to, as the panel knows it. */\nexport interface ProposalCandidateSink {\n id: string;\n key: string;\n name?: string | null;\n}\n\n/**\n * Converts the workspace store into a proposal-safe candidate set. The API\n * excludes soft-deleted sinks, but this second boundary is intentional:\n * persisted client state can outlive an out-of-band deletion.\n */\nexport function selectActiveProposalSinks(\n sinks: ReadonlyArray<ProposalCandidateSink & { deletedAt?: string | null }>\n): ProposalCandidateSink[] {\n return sinks.filter((sink) => !sink.deletedAt).map(({ id, key, name }) => ({ id, key, name }));\n}\n\n/** A parser the current user may bind to. */\nexport interface ProposalCandidateParser {\n id: string;\n name: string;\n inputSinkKey: string;\n}\n\nexport interface ProposalRequest {\n prompt: string;\n sinks: readonly ProposalCandidateSink[];\n parsers: readonly ProposalCandidateParser[];\n}\n\nexport interface CustomWidgetProposalSource {\n /** Stable id, surfaced in the UI so reviewers know what produced a proposal. */\n readonly id: string;\n propose(request: ProposalRequest): Promise<CustomWidgetProposal>;\n}\n\n// ============================================================================\n// Local heuristic source\n// ============================================================================\n\n/**\n * Keyword → widget type. Ordered most-specific first so \"conversion funnel\"\n * matches funnel before a generic \"chart\" would win.\n */\nconst TYPE_KEYWORDS: ReadonlyArray<{ type: ProposableWidgetType; words: readonly string[] }> = [\n { type: 'funnel_chart', words: ['funnel', 'conversion', 'drop-off', 'dropoff', 'stage'] },\n { type: 'heatmap', words: ['heatmap', 'heat map', 'matrix', 'intensity', 'by hour', 'density'] },\n { type: 'retention', words: ['retention', 'cohort'] },\n { type: 'table', words: ['table', 'list of', 'rows', 'breakdown table', 'grid'] },\n { type: 'metric_card', words: ['kpi', 'single number', 'metric card', 'scorecard', 'total'] },\n { type: 'chart', words: ['chart', 'trend', 'over time', 'line', 'bar', 'area', 'pie', 'graph'] },\n];\n\nconst CHART_SUBTYPE_KEYWORDS: ReadonlyArray<{ chartType: string; words: readonly string[] }> = [\n { chartType: 'bar', words: ['bar'] },\n { chartType: 'area', words: ['area', 'cumulative'] },\n { chartType: 'pie', words: ['pie', 'composition', 'share', 'mix'] },\n { chartType: 'line', words: ['line', 'trend', 'over time'] },\n];\n\nfunction detectWidgetType(prompt: string): ProposableWidgetType {\n const lower = prompt.toLowerCase();\n for (const { type, words } of TYPE_KEYWORDS) {\n if (words.some((word) => lower.includes(word))) return type;\n }\n return 'chart';\n}\n\nfunction detectChartSubtype(prompt: string): string {\n const lower = prompt.toLowerCase();\n for (const { chartType, words } of CHART_SUBTYPE_KEYWORDS) {\n if (words.some((word) => lower.includes(word))) return chartType;\n }\n return 'line';\n}\n\n/**\n * Picks the sink whose key/name shares the most word-stems with the prompt.\n * Falls back to the first permitted sink so a proposal is always reviewable\n * rather than failing outright.\n */\nfunction pickSink(prompt: string, sinks: readonly ProposalCandidateSink[]): ProposalCandidateSink | undefined {\n if (sinks.length === 0) return undefined;\n const tokens = prompt\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter((token) => token.length > 2);\n\n let best: { sink: ProposalCandidateSink; score: number } | undefined;\n for (const sink of sinks) {\n const haystack = `${sink.key} ${sink.name ?? ''}`.toLowerCase();\n const score = tokens.reduce((acc, token) => (haystack.includes(token) ? acc + 1 : acc), 0);\n if (!best || score > best.score) best = { sink, score };\n }\n return best?.sink ?? sinks[0];\n}\n\n/** Default field mapping per widget type, using the renderers' conventions. */\nfunction defaultFieldMapping(type: ProposableWidgetType): ProposalFieldMapping {\n switch (type) {\n case 'heatmap':\n return { xAxisField: 'column', yAxisField: 'row', valueField: 'value' };\n case 'funnel_chart':\n return { labelField: 'name', valueField: 'value' };\n case 'metric_card':\n return { valueField: 'value' };\n case 'table':\n return {};\n case 'retention':\n return { cohortField: 'cohort', valueField: 'value' };\n case 'chart':\n default:\n return { xAxisField: 'name', labelField: 'name', valueField: 'value' };\n }\n}\n\nfunction defaultConfig(type: ProposableWidgetType, prompt: string): Record<string, unknown> {\n const base: Record<string, unknown> = { showLegend: true, showTooltip: true };\n switch (type) {\n case 'chart':\n return { ...base, chartType: detectChartSubtype(prompt), showGrid: true };\n case 'funnel_chart':\n return { ...base, chartType: 'funnel', showLabels: true, showPercentages: true, orientation: 'vertical' };\n case 'heatmap':\n return { ...base, colorScheme: 'blue', showValues: true };\n case 'table':\n return { ...base, pageSize: 10, sortable: true };\n case 'metric_card':\n return { ...base, showTrend: true };\n default:\n return base;\n }\n}\n\n/** Turns the prompt's first clause into a widget title. */\nfunction deriveTitle(prompt: string): string {\n const cleaned = prompt\n .trim()\n .replace(/^(please\\s+)?(create|build|make|add|show me|show|give me)\\s+(an?\\s+|the\\s+)?/i, '')\n .split(/[.;\\n]/)[0]\n .trim();\n if (!cleaned) return 'Custom widget';\n const titled = cleaned.charAt(0).toUpperCase() + cleaned.slice(1);\n return titled.length > 60 ? `${titled.slice(0, 57)}…` : titled;\n}\n\nlet proposalCounter = 0;\n\n/**\n * Derives a proposal from the prompt and the workspace's real, permitted sinks\n * and parsers. Deterministic, so the review UI and its tests behave predictably.\n */\nexport const localProposalSource: CustomWidgetProposalSource = {\n id: 'local-heuristic-v1',\n\n async propose({ prompt, sinks, parsers }: ProposalRequest): Promise<CustomWidgetProposal> {\n const widgetType = detectWidgetType(prompt);\n const sink = pickSink(prompt, sinks);\n const parser = sink ? parsers.find((candidate) => candidate.inputSinkKey === sink.key) : undefined;\n\n proposalCounter += 1;\n\n return {\n version: CUSTOM_WIDGET_PROPOSAL_VERSION,\n id: `proposal-${proposalCounter}`,\n sourcePrompt: prompt,\n generatedBy: localProposalSource.id,\n widgetType,\n binding: {\n dataSinkId: sink?.id ?? '',\n dataSinkKey: sink?.key,\n parserId: parser?.id,\n parserName: parser?.name,\n },\n fieldMapping: defaultFieldMapping(widgetType),\n presentation: {\n title: deriveTitle(prompt),\n description: `Generated from: \"${prompt}\"`,\n config: defaultConfig(widgetType, prompt),\n },\n rationale: sink\n ? `Matched DataSink \"${sink.key}\"${parser ? ` and parser \"${parser.name}\"` : ' (no parser found for this sink)'}.`\n : 'No DataSink is available in this workspace — create one before saving this widget.',\n };\n },\n};\n"],"mappings":"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"}
@@ -49,7 +49,7 @@ function g(e) {
49
49
  function _(_ = {}) {
50
50
  let { workspaceId: v, skipFetch: y = !1 } = _, [b, x] = m(!1), [S, C] = m(null), w = h(), T = e((e) => e.dataSinks), E = e((e) => e.setDataSinks), D = e((e) => e.addDataSink), O = e((e) => e.updateDataSink), k = e((e) => e.removeDataSink), A = e((e) => e.setDataSinkRefreshing), j = p(() => Array.from(T.values()), [T]), M = d(async (e) => {
51
51
  try {
52
- x(!0), C(null);
52
+ x(!0), C(null), E([]);
53
53
  let t = ((await w.query({
54
54
  query: c,
55
55
  variables: {
@@ -61,7 +61,7 @@ function _(_ = {}) {
61
61
  })).data?.listDataSinks?.edges || []).map((e) => g(e.node));
62
62
  return E(t), t;
63
63
  } catch (e) {
64
- return C(e instanceof Error ? e : /* @__PURE__ */ Error("Failed to fetch DataSinks")), [];
64
+ return C(e instanceof Error ? e : /* @__PURE__ */ Error("Failed to fetch DataSinks")), E([]), [];
65
65
  } finally {
66
66
  x(!1);
67
67
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useDataSinkOperations.js","names":[],"sources":["../../../src/bigconsole/hooks/useDataSinkOperations.ts"],"sourcesContent":["/**\n * DataSink Operations Hook\n *\n * Custom hook for DataSink CRUD operations with Apollo Client.\n * Integrates with the DataSink Zustand store for state management.\n */\n\nimport { useState, useMemo, useCallback, useEffect } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport { useDataSinkStore } from '../store/dataSinkStore';\nimport {\n ListDataSinksDocument,\n GetDataSinkDocument,\n GetDataSinkDataDocument,\n CreateDataSinkDocument,\n UpdateDataSinkDocument,\n DeleteDataSinkDocument,\n RefreshDataSinkDocument,\n InvalidateDataSinkCacheDocument,\n BatchCreateDataSinksDocument,\n BatchDeleteDataSinksDocument,\n} from '../../generated/wspace-operations';\nimport type { DataSink, DataSinkStatus, CreateDataSinkInput, UpdateDataSinkInput } from '../types';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface UseDataSinkOperationsOptions {\n workspaceId?: string;\n skipFetch?: boolean;\n}\n\ninterface DataSinkFilters {\n status?: DataSinkStatus;\n isActive?: boolean;\n search?: string;\n}\n\ninterface RefreshDataSinkInput {\n id: string;\n forceRefresh?: boolean;\n}\n\ninterface InvalidateCacheResult {\n count: number;\n invalidatedIds: string[];\n success: boolean;\n message?: string;\n}\n\ninterface DataSinkDataResult {\n raw: unknown;\n extracted: unknown;\n cached: boolean;\n version: number;\n fetchedAt: string;\n // v3.0: pagination metadata (non-null totalCount only for array payloads).\n totalCount?: number | null;\n limit?: number | null;\n offset?: number | null;\n hasMore?: boolean | null;\n}\n\n// GraphQL Query/Mutation Result Types\ninterface ListDataSinksResult {\n listDataSinks?: {\n edges: Array<{ node: unknown }>;\n pageInfo?: { hasNextPage: boolean; endCursor?: string };\n };\n}\n\ninterface GetDataSinkResult {\n getDataSink?: unknown;\n}\n\ninterface GetDataSinkDataResult {\n getDataSinkData?: DataSinkDataResult;\n}\n\ninterface CreateDataSinkResult {\n createDataSink?: unknown;\n}\n\ninterface UpdateDataSinkResult {\n updateDataSink?: unknown;\n}\n\ninterface DeleteDataSinkResult {\n deleteDataSink?: boolean;\n}\n\ninterface RefreshDataSinkResult {\n refreshDataSink?: unknown;\n}\n\ninterface InvalidateDataSinkCacheResult {\n invalidateDataSinkCache?: InvalidateCacheResult;\n}\n\ninterface BatchCreateDataSinksResult {\n batchCreateDataSinks?: unknown[];\n}\n\ninterface BatchDeleteDataSinksResult {\n batchDeleteDataSinks?: Array<{ id: string; deletedAt: string }>;\n}\n\n// ============================================================================\n// HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Normalize GraphQL response to DataSink type\n */\nfunction normalizeDataSink(data: unknown): DataSink {\n const raw = data as Record<string, unknown>;\n return {\n id: raw.id as string,\n workspaceId: raw.workspaceId as string,\n tenantId: raw.tenantId as string,\n name: (raw.name as string) || null,\n description: (raw.description as string) || null,\n key: raw.key as string,\n fluidGridsWorkflowId: (raw.fluidGridsWorkflowId as string) || null,\n fluidGridsWorkflowExecutionId: (raw.fluidGridsWorkflowExecutionId as string) || null,\n config: (raw.config as Record<string, unknown>) || null,\n data: (raw.data as Record<string, unknown>) || {},\n dataStats: (raw.dataStats as DataSink['dataStats']) || null,\n lastRefreshedAt: (raw.lastRefreshedAt as string) || new Date().toISOString(),\n lastRefreshedBy: (raw.lastRefreshedBy as string) || null,\n expiresAt: (raw.expiresAt as string) || null,\n nextRefreshAt: (raw.nextRefreshAt as string) || null,\n ttl: (raw.ttl as number) ?? 0,\n version: (raw.version as number) ?? 1,\n sizeBytes: (raw.sizeBytes as number) ?? 0,\n isActive: (raw.isActive as boolean) ?? true,\n fieldMaskingEnabled: (raw.fieldMaskingEnabled as boolean) ?? false,\n fieldMaskingRules: (raw.fieldMaskingRules as Record<string, unknown>) || null,\n status: (raw.status as DataSink['status']) || 'ready',\n statusChangedAt: (raw.statusChangedAt as string) || null,\n statusChangedBy: (raw.statusChangedBy as string) || null,\n setupState: (raw.setupState as DataSink['setupState']) || 'not_started',\n refresh: (raw.refresh as DataSink['refresh']) || null,\n exports: (raw.exports as DataSink['exports']) || null,\n alerts: (raw.alerts as DataSink['alerts']) || null,\n quota: (raw.quota as DataSink['quota']) || null,\n workflowLinks: raw.workflowLinks as DataSink['workflowLinks'],\n linkedWorkflowCount: raw.linkedWorkflowCount as number | undefined,\n metadata: (raw.metadata as Record<string, unknown>) || null,\n createdAt: raw.createdAt as string,\n createdBy: (raw.createdBy as string) || '',\n updatedAt: raw.updatedAt as string,\n updatedBy: (raw.updatedBy as string) || '',\n deletedAt: (raw.deletedAt as string) || null,\n deletedBy: (raw.deletedBy as string) || null,\n };\n}\n\n// ============================================================================\n// HOOK\n// ============================================================================\n\nexport function useDataSinkOperations(options: UseDataSinkOperationsOptions = {}) {\n const { workspaceId, skipFetch = false } = options;\n\n // Local state\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n // Apollo client\n const client = useApolloClient();\n\n // Store selectors and actions\n const dataSinksMap = useDataSinkStore((state) => state.dataSinks);\n const setDataSinks = useDataSinkStore((state) => state.setDataSinks);\n const addDataSink = useDataSinkStore((state) => state.addDataSink);\n const updateDataSinkInStore = useDataSinkStore((state) => state.updateDataSink);\n const removeDataSink = useDataSinkStore((state) => state.removeDataSink);\n const setDataSinkRefreshing = useDataSinkStore((state) => state.setDataSinkRefreshing);\n\n // Memoized array of datasinks\n const dataSinks = useMemo(() => Array.from(dataSinksMap.values()), [dataSinksMap]);\n\n // ============================================================================\n // OPERATIONS\n // ============================================================================\n\n /**\n * Fetch all datasinks with optional filters\n */\n const fetchDataSinks = useCallback(\n async (filters?: DataSinkFilters): Promise<DataSink[]> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await client.query<ListDataSinksResult>({\n query: ListDataSinksDocument,\n variables: {\n status: filters?.status,\n isActive: filters?.isActive,\n search: filters?.search,\n },\n fetchPolicy: 'network-only',\n });\n\n const edges = result.data?.listDataSinks?.edges || [];\n const normalized = edges.map((edge: { node: unknown }) => normalizeDataSink(edge.node));\n\n setDataSinks(normalized);\n return normalized;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to fetch DataSinks');\n setError(error);\n return [];\n } finally {\n setLoading(false);\n }\n },\n [client, setDataSinks]\n );\n\n /**\n * Fetch a single datasink by ID\n */\n const fetchDataSink = useCallback(\n async (id: string): Promise<DataSink | null> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await client.query<GetDataSinkResult>({\n query: GetDataSinkDocument,\n variables: { id },\n fetchPolicy: 'network-only',\n });\n\n if (result.data?.getDataSink) {\n const normalized = normalizeDataSink(result.data.getDataSink);\n addDataSink(normalized);\n return normalized;\n }\n\n return null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to fetch DataSink');\n setError(error);\n return null;\n } finally {\n setLoading(false);\n }\n },\n [client, addDataSink]\n );\n\n /**\n * Get datasink data for preview\n */\n const getDataSinkData = useCallback(\n async (\n idOrKey: { id?: string; key?: string },\n options?: {\n dataPath?: string;\n forceRefresh?: boolean;\n // v3.0: server-side pagination + filter for array payloads.\n limit?: number;\n offset?: number;\n filter?: Array<{ field: string; op: string; value: unknown }>;\n }\n ): Promise<DataSinkDataResult | null> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await client.query<GetDataSinkDataResult>({\n query: GetDataSinkDataDocument,\n variables: {\n id: idOrKey.id,\n key: idOrKey.key,\n dataPath: options?.dataPath,\n forceRefresh: options?.forceRefresh,\n limit: options?.limit,\n offset: options?.offset,\n filter: options?.filter,\n },\n fetchPolicy: 'network-only',\n });\n\n return result.data?.getDataSinkData || null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to get DataSink data');\n setError(error);\n return null;\n } finally {\n setLoading(false);\n }\n },\n [client]\n );\n\n /**\n * Create a new datasink\n */\n const createDataSink = useCallback(\n async (input: CreateDataSinkInput): Promise<DataSink | null> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await client.mutate<CreateDataSinkResult>({\n mutation: CreateDataSinkDocument,\n variables: { input },\n });\n\n if (result.data?.createDataSink) {\n const normalized = normalizeDataSink(result.data.createDataSink);\n addDataSink(normalized);\n return normalized;\n }\n\n return null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to create DataSink');\n setError(error);\n return null;\n } finally {\n setLoading(false);\n }\n },\n [client, addDataSink]\n );\n\n /**\n * Update an existing datasink\n */\n const updateDataSink = useCallback(\n async (input: UpdateDataSinkInput): Promise<DataSink | null> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await client.mutate<UpdateDataSinkResult>({\n mutation: UpdateDataSinkDocument,\n variables: { input },\n });\n\n if (result.data?.updateDataSink) {\n const normalized = normalizeDataSink(result.data.updateDataSink);\n updateDataSinkInStore(normalized.id, normalized);\n return normalized;\n }\n\n return null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to update DataSink');\n setError(error);\n return null;\n } finally {\n setLoading(false);\n }\n },\n [client, updateDataSinkInStore]\n );\n\n /**\n * Delete a datasink\n */\n const deleteDataSink = useCallback(\n async (id: string): Promise<boolean> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await client.mutate<DeleteDataSinkResult>({\n mutation: DeleteDataSinkDocument,\n variables: { id },\n });\n\n if (result.data?.deleteDataSink) {\n removeDataSink(id);\n return true;\n }\n\n return false;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to delete DataSink');\n setError(error);\n return false;\n } finally {\n setLoading(false);\n }\n },\n [client, removeDataSink]\n );\n\n /**\n * Refresh a datasink's data\n */\n const refreshDataSink = useCallback(\n async (input: RefreshDataSinkInput): Promise<DataSink | null> => {\n try {\n setLoading(true);\n setError(null);\n\n if (input.id) {\n setDataSinkRefreshing(input.id, true);\n }\n\n const result = await client.mutate<RefreshDataSinkResult>({\n mutation: RefreshDataSinkDocument,\n variables: { input },\n });\n\n if (result.data?.refreshDataSink) {\n const normalized = normalizeDataSink(result.data.refreshDataSink);\n updateDataSinkInStore(normalized.id, normalized);\n setDataSinkRefreshing(normalized.id, false);\n return normalized;\n }\n\n return null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to refresh DataSink');\n setError(error);\n if (input.id) {\n setDataSinkRefreshing(input.id, false);\n }\n return null;\n } finally {\n setLoading(false);\n }\n },\n [client, updateDataSinkInStore, setDataSinkRefreshing]\n );\n\n /**\n * Invalidate datasink cache\n */\n const invalidateCache = useCallback(\n async (id?: string): Promise<InvalidateCacheResult> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await client.mutate<InvalidateDataSinkCacheResult>({\n mutation: InvalidateDataSinkCacheDocument,\n variables: {\n id,\n },\n });\n\n return (\n result.data?.invalidateDataSinkCache || {\n count: 0,\n invalidatedIds: [],\n success: false,\n }\n );\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to invalidate cache');\n setError(error);\n return {\n count: 0,\n invalidatedIds: [],\n success: false,\n message: error.message,\n };\n } finally {\n setLoading(false);\n }\n },\n [client, workspaceId]\n );\n\n /**\n * Batch create datasinks\n */\n const batchCreateDataSinks = useCallback(\n async (inputs: CreateDataSinkInput[]): Promise<DataSink[]> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await client.mutate<BatchCreateDataSinksResult>({\n mutation: BatchCreateDataSinksDocument,\n variables: { inputs },\n });\n\n if (result.data?.batchCreateDataSinks) {\n const normalized = result.data.batchCreateDataSinks.map(normalizeDataSink);\n normalized.forEach(addDataSink);\n return normalized;\n }\n\n return [];\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to batch create DataSinks');\n setError(error);\n return [];\n } finally {\n setLoading(false);\n }\n },\n [client, addDataSink]\n );\n\n /**\n * Batch delete datasinks\n */\n const batchDeleteDataSinks = useCallback(\n async (ids: string[]): Promise<{ id: string; deletedAt: string }[]> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await client.mutate<BatchDeleteDataSinksResult>({\n mutation: BatchDeleteDataSinksDocument,\n variables: { ids },\n });\n\n if (result.data?.batchDeleteDataSinks) {\n ids.forEach(removeDataSink);\n return result.data.batchDeleteDataSinks;\n }\n\n return [];\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to batch delete DataSinks');\n setError(error);\n return [];\n } finally {\n setLoading(false);\n }\n },\n [client, removeDataSink]\n );\n\n /**\n * Refetch all datasinks\n */\n const refetch = useCallback(async () => {\n await fetchDataSinks();\n }, [fetchDataSinks]);\n\n // Auto-fetch on mount if not skipped\n useEffect(() => {\n if (!skipFetch) {\n fetchDataSinks();\n }\n }, [skipFetch, fetchDataSinks]);\n\n return {\n loading,\n error,\n dataSinks,\n fetchDataSinks,\n fetchDataSink,\n getDataSinkData,\n createDataSink,\n updateDataSink,\n deleteDataSink,\n refreshDataSink,\n invalidateCache,\n batchCreateDataSinks,\n batchDeleteDataSinks,\n refetch,\n };\n}\n\nexport default useDataSinkOperations;\n"],"mappings":"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"}
1
+ {"version":3,"file":"useDataSinkOperations.js","names":[],"sources":["../../../src/bigconsole/hooks/useDataSinkOperations.ts"],"sourcesContent":["/**\n * DataSink Operations Hook\n *\n * Custom hook for DataSink CRUD operations with Apollo Client.\n * Integrates with the DataSink Zustand store for state management.\n */\n\nimport { useState, useMemo, useCallback, useEffect } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport { useDataSinkStore } from '../store/dataSinkStore';\nimport {\n ListDataSinksDocument,\n GetDataSinkDocument,\n GetDataSinkDataDocument,\n CreateDataSinkDocument,\n UpdateDataSinkDocument,\n DeleteDataSinkDocument,\n RefreshDataSinkDocument,\n InvalidateDataSinkCacheDocument,\n BatchCreateDataSinksDocument,\n BatchDeleteDataSinksDocument,\n} from '../../generated/wspace-operations';\nimport type { DataSink, DataSinkStatus, CreateDataSinkInput, UpdateDataSinkInput } from '../types';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface UseDataSinkOperationsOptions {\n workspaceId?: string;\n skipFetch?: boolean;\n}\n\ninterface DataSinkFilters {\n status?: DataSinkStatus;\n isActive?: boolean;\n search?: string;\n}\n\ninterface RefreshDataSinkInput {\n id: string;\n forceRefresh?: boolean;\n}\n\ninterface InvalidateCacheResult {\n count: number;\n invalidatedIds: string[];\n success: boolean;\n message?: string;\n}\n\ninterface DataSinkDataResult {\n raw: unknown;\n extracted: unknown;\n cached: boolean;\n version: number;\n fetchedAt: string;\n // v3.0: pagination metadata (non-null totalCount only for array payloads).\n totalCount?: number | null;\n limit?: number | null;\n offset?: number | null;\n hasMore?: boolean | null;\n}\n\n// GraphQL Query/Mutation Result Types\ninterface ListDataSinksResult {\n listDataSinks?: {\n edges: Array<{ node: unknown }>;\n pageInfo?: { hasNextPage: boolean; endCursor?: string };\n };\n}\n\ninterface GetDataSinkResult {\n getDataSink?: unknown;\n}\n\ninterface GetDataSinkDataResult {\n getDataSinkData?: DataSinkDataResult;\n}\n\ninterface CreateDataSinkResult {\n createDataSink?: unknown;\n}\n\ninterface UpdateDataSinkResult {\n updateDataSink?: unknown;\n}\n\ninterface DeleteDataSinkResult {\n deleteDataSink?: boolean;\n}\n\ninterface RefreshDataSinkResult {\n refreshDataSink?: unknown;\n}\n\ninterface InvalidateDataSinkCacheResult {\n invalidateDataSinkCache?: InvalidateCacheResult;\n}\n\ninterface BatchCreateDataSinksResult {\n batchCreateDataSinks?: unknown[];\n}\n\ninterface BatchDeleteDataSinksResult {\n batchDeleteDataSinks?: Array<{ id: string; deletedAt: string }>;\n}\n\n// ============================================================================\n// HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Normalize GraphQL response to DataSink type\n */\nfunction normalizeDataSink(data: unknown): DataSink {\n const raw = data as Record<string, unknown>;\n return {\n id: raw.id as string,\n workspaceId: raw.workspaceId as string,\n tenantId: raw.tenantId as string,\n name: (raw.name as string) || null,\n description: (raw.description as string) || null,\n key: raw.key as string,\n fluidGridsWorkflowId: (raw.fluidGridsWorkflowId as string) || null,\n fluidGridsWorkflowExecutionId: (raw.fluidGridsWorkflowExecutionId as string) || null,\n config: (raw.config as Record<string, unknown>) || null,\n data: (raw.data as Record<string, unknown>) || {},\n dataStats: (raw.dataStats as DataSink['dataStats']) || null,\n lastRefreshedAt: (raw.lastRefreshedAt as string) || new Date().toISOString(),\n lastRefreshedBy: (raw.lastRefreshedBy as string) || null,\n expiresAt: (raw.expiresAt as string) || null,\n nextRefreshAt: (raw.nextRefreshAt as string) || null,\n ttl: (raw.ttl as number) ?? 0,\n version: (raw.version as number) ?? 1,\n sizeBytes: (raw.sizeBytes as number) ?? 0,\n isActive: (raw.isActive as boolean) ?? true,\n fieldMaskingEnabled: (raw.fieldMaskingEnabled as boolean) ?? false,\n fieldMaskingRules: (raw.fieldMaskingRules as Record<string, unknown>) || null,\n status: (raw.status as DataSink['status']) || 'ready',\n statusChangedAt: (raw.statusChangedAt as string) || null,\n statusChangedBy: (raw.statusChangedBy as string) || null,\n setupState: (raw.setupState as DataSink['setupState']) || 'not_started',\n refresh: (raw.refresh as DataSink['refresh']) || null,\n exports: (raw.exports as DataSink['exports']) || null,\n alerts: (raw.alerts as DataSink['alerts']) || null,\n quota: (raw.quota as DataSink['quota']) || null,\n workflowLinks: raw.workflowLinks as DataSink['workflowLinks'],\n linkedWorkflowCount: raw.linkedWorkflowCount as number | undefined,\n metadata: (raw.metadata as Record<string, unknown>) || null,\n createdAt: raw.createdAt as string,\n createdBy: (raw.createdBy as string) || '',\n updatedAt: raw.updatedAt as string,\n updatedBy: (raw.updatedBy as string) || '',\n deletedAt: (raw.deletedAt as string) || null,\n deletedBy: (raw.deletedBy as string) || null,\n };\n}\n\n// ============================================================================\n// HOOK\n// ============================================================================\n\nexport function useDataSinkOperations(options: UseDataSinkOperationsOptions = {}) {\n const { workspaceId, skipFetch = false } = options;\n\n // Local state\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n // Apollo client\n const client = useApolloClient();\n\n // Store selectors and actions\n const dataSinksMap = useDataSinkStore((state) => state.dataSinks);\n const setDataSinks = useDataSinkStore((state) => state.setDataSinks);\n const addDataSink = useDataSinkStore((state) => state.addDataSink);\n const updateDataSinkInStore = useDataSinkStore((state) => state.updateDataSink);\n const removeDataSink = useDataSinkStore((state) => state.removeDataSink);\n const setDataSinkRefreshing = useDataSinkStore((state) => state.setDataSinkRefreshing);\n\n // Memoized array of datasinks\n const dataSinks = useMemo(() => Array.from(dataSinksMap.values()), [dataSinksMap]);\n\n // ============================================================================\n // OPERATIONS\n // ============================================================================\n\n /**\n * Fetch all datasinks with optional filters\n */\n const fetchDataSinks = useCallback(\n async (filters?: DataSinkFilters): Promise<DataSink[]> => {\n try {\n setLoading(true);\n setError(null);\n // Fail closed while the authoritative workspace list is refreshed.\n // A persisted Zustand snapshot may contain sinks deleted by another\n // tab, an API client, or an E2E harness; proposal pickers must never\n // bind to those stale records while this network request is pending.\n setDataSinks([]);\n\n const result = await client.query<ListDataSinksResult>({\n query: ListDataSinksDocument,\n variables: {\n status: filters?.status,\n isActive: filters?.isActive,\n search: filters?.search,\n },\n fetchPolicy: 'network-only',\n });\n\n const edges = result.data?.listDataSinks?.edges || [];\n const normalized = edges.map((edge: { node: unknown }) => normalizeDataSink(edge.node));\n\n setDataSinks(normalized);\n return normalized;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to fetch DataSinks');\n setError(error);\n // Do not retain a previously permitted resource set after an auth,\n // workspace-context, or network failure.\n setDataSinks([]);\n return [];\n } finally {\n setLoading(false);\n }\n },\n [client, setDataSinks]\n );\n\n /**\n * Fetch a single datasink by ID\n */\n const fetchDataSink = useCallback(\n async (id: string): Promise<DataSink | null> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await client.query<GetDataSinkResult>({\n query: GetDataSinkDocument,\n variables: { id },\n fetchPolicy: 'network-only',\n });\n\n if (result.data?.getDataSink) {\n const normalized = normalizeDataSink(result.data.getDataSink);\n addDataSink(normalized);\n return normalized;\n }\n\n return null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to fetch DataSink');\n setError(error);\n return null;\n } finally {\n setLoading(false);\n }\n },\n [client, addDataSink]\n );\n\n /**\n * Get datasink data for preview\n */\n const getDataSinkData = useCallback(\n async (\n idOrKey: { id?: string; key?: string },\n options?: {\n dataPath?: string;\n forceRefresh?: boolean;\n // v3.0: server-side pagination + filter for array payloads.\n limit?: number;\n offset?: number;\n filter?: Array<{ field: string; op: string; value: unknown }>;\n }\n ): Promise<DataSinkDataResult | null> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await client.query<GetDataSinkDataResult>({\n query: GetDataSinkDataDocument,\n variables: {\n id: idOrKey.id,\n key: idOrKey.key,\n dataPath: options?.dataPath,\n forceRefresh: options?.forceRefresh,\n limit: options?.limit,\n offset: options?.offset,\n filter: options?.filter,\n },\n fetchPolicy: 'network-only',\n });\n\n return result.data?.getDataSinkData || null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to get DataSink data');\n setError(error);\n return null;\n } finally {\n setLoading(false);\n }\n },\n [client]\n );\n\n /**\n * Create a new datasink\n */\n const createDataSink = useCallback(\n async (input: CreateDataSinkInput): Promise<DataSink | null> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await client.mutate<CreateDataSinkResult>({\n mutation: CreateDataSinkDocument,\n variables: { input },\n });\n\n if (result.data?.createDataSink) {\n const normalized = normalizeDataSink(result.data.createDataSink);\n addDataSink(normalized);\n return normalized;\n }\n\n return null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to create DataSink');\n setError(error);\n return null;\n } finally {\n setLoading(false);\n }\n },\n [client, addDataSink]\n );\n\n /**\n * Update an existing datasink\n */\n const updateDataSink = useCallback(\n async (input: UpdateDataSinkInput): Promise<DataSink | null> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await client.mutate<UpdateDataSinkResult>({\n mutation: UpdateDataSinkDocument,\n variables: { input },\n });\n\n if (result.data?.updateDataSink) {\n const normalized = normalizeDataSink(result.data.updateDataSink);\n updateDataSinkInStore(normalized.id, normalized);\n return normalized;\n }\n\n return null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to update DataSink');\n setError(error);\n return null;\n } finally {\n setLoading(false);\n }\n },\n [client, updateDataSinkInStore]\n );\n\n /**\n * Delete a datasink\n */\n const deleteDataSink = useCallback(\n async (id: string): Promise<boolean> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await client.mutate<DeleteDataSinkResult>({\n mutation: DeleteDataSinkDocument,\n variables: { id },\n });\n\n if (result.data?.deleteDataSink) {\n removeDataSink(id);\n return true;\n }\n\n return false;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to delete DataSink');\n setError(error);\n return false;\n } finally {\n setLoading(false);\n }\n },\n [client, removeDataSink]\n );\n\n /**\n * Refresh a datasink's data\n */\n const refreshDataSink = useCallback(\n async (input: RefreshDataSinkInput): Promise<DataSink | null> => {\n try {\n setLoading(true);\n setError(null);\n\n if (input.id) {\n setDataSinkRefreshing(input.id, true);\n }\n\n const result = await client.mutate<RefreshDataSinkResult>({\n mutation: RefreshDataSinkDocument,\n variables: { input },\n });\n\n if (result.data?.refreshDataSink) {\n const normalized = normalizeDataSink(result.data.refreshDataSink);\n updateDataSinkInStore(normalized.id, normalized);\n setDataSinkRefreshing(normalized.id, false);\n return normalized;\n }\n\n return null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to refresh DataSink');\n setError(error);\n if (input.id) {\n setDataSinkRefreshing(input.id, false);\n }\n return null;\n } finally {\n setLoading(false);\n }\n },\n [client, updateDataSinkInStore, setDataSinkRefreshing]\n );\n\n /**\n * Invalidate datasink cache\n */\n const invalidateCache = useCallback(\n async (id?: string): Promise<InvalidateCacheResult> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await client.mutate<InvalidateDataSinkCacheResult>({\n mutation: InvalidateDataSinkCacheDocument,\n variables: {\n id,\n },\n });\n\n return (\n result.data?.invalidateDataSinkCache || {\n count: 0,\n invalidatedIds: [],\n success: false,\n }\n );\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to invalidate cache');\n setError(error);\n return {\n count: 0,\n invalidatedIds: [],\n success: false,\n message: error.message,\n };\n } finally {\n setLoading(false);\n }\n },\n [client, workspaceId]\n );\n\n /**\n * Batch create datasinks\n */\n const batchCreateDataSinks = useCallback(\n async (inputs: CreateDataSinkInput[]): Promise<DataSink[]> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await client.mutate<BatchCreateDataSinksResult>({\n mutation: BatchCreateDataSinksDocument,\n variables: { inputs },\n });\n\n if (result.data?.batchCreateDataSinks) {\n const normalized = result.data.batchCreateDataSinks.map(normalizeDataSink);\n normalized.forEach(addDataSink);\n return normalized;\n }\n\n return [];\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to batch create DataSinks');\n setError(error);\n return [];\n } finally {\n setLoading(false);\n }\n },\n [client, addDataSink]\n );\n\n /**\n * Batch delete datasinks\n */\n const batchDeleteDataSinks = useCallback(\n async (ids: string[]): Promise<{ id: string; deletedAt: string }[]> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await client.mutate<BatchDeleteDataSinksResult>({\n mutation: BatchDeleteDataSinksDocument,\n variables: { ids },\n });\n\n if (result.data?.batchDeleteDataSinks) {\n ids.forEach(removeDataSink);\n return result.data.batchDeleteDataSinks;\n }\n\n return [];\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to batch delete DataSinks');\n setError(error);\n return [];\n } finally {\n setLoading(false);\n }\n },\n [client, removeDataSink]\n );\n\n /**\n * Refetch all datasinks\n */\n const refetch = useCallback(async () => {\n await fetchDataSinks();\n }, [fetchDataSinks]);\n\n // Auto-fetch on mount if not skipped\n useEffect(() => {\n if (!skipFetch) {\n fetchDataSinks();\n }\n }, [skipFetch, fetchDataSinks]);\n\n return {\n loading,\n error,\n dataSinks,\n fetchDataSinks,\n fetchDataSink,\n getDataSinkData,\n createDataSink,\n updateDataSink,\n deleteDataSink,\n refreshDataSink,\n invalidateCache,\n batchCreateDataSinks,\n batchDeleteDataSinks,\n refetch,\n };\n}\n\nexport default useDataSinkOperations;\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.812.2",
3
+ "version": "2026.825.1",
4
4
  "description": "BigConsole - AI-powered analytics and dashboard platform",
5
5
  "type": "module",
6
6
  "files": [