@burdenoff/microfe-bigconsole 2026.727.1 → 2026.727.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,92 @@
1
+ import e from "../hooks/useWidgetOperations.js";
2
+ import t from "../hooks/useDataSinkOperations.js";
3
+ import n from "../hooks/useParserOperations.js";
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 { Sheet as s, SheetContent as c, SheetDescription as l, SheetHeader as u, SheetTitle as d } from "@burdenoff/fe-libs/ui";
8
+ import { jsx as f, jsxs as p } from "react/jsx-runtime";
9
+ //#region src/bigconsole/customWidget/CustomWidgetBuilderSheet.tsx
10
+ var m = {
11
+ x: 0,
12
+ y: 0,
13
+ width: 6,
14
+ height: 4
15
+ }, h = i(function({ isOpen: i, onClose: h, dashboardId: g, pageId: _, source: v }) {
16
+ let { dataSinks: y } = t({ skipFetch: !i }), { parsers: b, executeParser: x } = n({
17
+ consoleId: g,
18
+ skipFetch: !i
19
+ }), { createWidget: S } = e(_, g, { skipFetch: !0 }), C = o(() => y.map((e) => ({
20
+ id: e.id,
21
+ key: e.key,
22
+ name: e.name
23
+ })), [y]), w = o(() => b.map((e) => ({
24
+ id: e.id,
25
+ name: e.name,
26
+ inputSinkKey: e.inputSinkKey
27
+ })), [b]), T = a(async (e) => {
28
+ if (!e.binding.parserId) return;
29
+ let t = await x(e.binding.parserId);
30
+ if (!t || !t.success) throw Error(t?.error || "Could not resolve the binding.");
31
+ return t.output;
32
+ }, [x]), E = a(async (e) => {
33
+ if (!await S({
34
+ pageId: _,
35
+ dashboardId: g,
36
+ type: e.widgetType,
37
+ title: e.presentation.title,
38
+ description: e.presentation.description,
39
+ dataSinkId: e.binding.dataSinkId || void 0,
40
+ parserId: e.binding.parserId,
41
+ config: {
42
+ ...e.presentation.config,
43
+ ...e.fieldMapping,
44
+ generatedBy: e.generatedBy,
45
+ sourcePrompt: e.sourcePrompt,
46
+ proposalVersion: e.version
47
+ },
48
+ position: { ...m }
49
+ })) throw Error("The widget could not be created.");
50
+ }, [
51
+ S,
52
+ _,
53
+ g
54
+ ]);
55
+ return /* @__PURE__ */ f(s, {
56
+ open: i,
57
+ onOpenChange: (e) => !e && h(),
58
+ children: /* @__PURE__ */ p(c, {
59
+ style: {
60
+ width: 480,
61
+ maxWidth: "95vw",
62
+ padding: 0,
63
+ display: "flex",
64
+ flexDirection: "column"
65
+ },
66
+ children: [/* @__PURE__ */ p(u, {
67
+ style: {
68
+ padding: 16,
69
+ paddingBottom: 0
70
+ },
71
+ children: [/* @__PURE__ */ f(d, { children: "AI custom widget" }), /* @__PURE__ */ f(l, { children: "Describe a widget, review the proposal, then approve it onto the dashboard." })]
72
+ }), /* @__PURE__ */ f("div", {
73
+ style: {
74
+ flex: 1,
75
+ minHeight: 0
76
+ },
77
+ children: /* @__PURE__ */ f(r, {
78
+ sinks: C,
79
+ parsers: w,
80
+ resolvePreviewData: T,
81
+ onApprove: E,
82
+ onClose: h,
83
+ source: v
84
+ })
85
+ })]
86
+ })
87
+ });
88
+ });
89
+ //#endregion
90
+ export { h as default };
91
+
92
+ //# sourceMappingURL=CustomWidgetBuilderSheet.js.map
@@ -0,0 +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';\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\nexport const CustomWidgetBuilderSheet: FC<CustomWidgetBuilderSheetProps> = memo(function CustomWidgetBuilderSheet({\n isOpen,\n onClose,\n dashboardId,\n pageId,\n source,\n}) {\n const { dataSinks } = useDataSinkOperations({ skipFetch: !isOpen });\n const { parsers, executeParser } = useParserOperations({ consoleId: dashboardId, skipFetch: !isOpen });\n const { createWidget } = useWidgetOperations(pageId, dashboardId, { skipFetch: true });\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 () => parsers.map((parser) => ({ id: parser.id, name: parser.name, inputSinkKey: parser.inputSinkKey })),\n [parsers]\n );\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(execution?.error || 'Could not resolve the binding.');\n }\n return execution.output;\n },\n [executeParser]\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) throw new Error('The widget could not be created.');\n },\n [createWidget, pageId, dashboardId]\n );\n\n return (\n <Sheet open={isOpen} onOpenChange={(open) => !open && onClose()}>\n <SheetContent style={{ width: 480, maxWidth: '95vw', padding: 0, display: 'flex', flexDirection: 'column' }}>\n <SheetHeader style={{ padding: 16, paddingBottom: 0 }}>\n <SheetTitle>AI custom widget</SheetTitle>\n <SheetDescription>\n Describe a widget, review the proposal, then approve it onto the dashboard.\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":";;;;;;;;;AA4BA,IAAM,IAAmB;CAAE,GAAG;CAAG,GAAG;CAAG,OAAO;CAAG,QAAQ;CAAG,EAE/C,IAA8D,EAAK,SAAkC,EAChH,WACA,YACA,gBACA,WACA,aACC;CACD,IAAM,EAAE,iBAAc,EAAsB,EAAE,WAAW,CAAC,GAAQ,CAAC,EAC7D,EAAE,YAAS,qBAAkB,EAAoB;EAAE,WAAW;EAAa,WAAW,CAAC;EAAQ,CAAC,EAChG,EAAE,oBAAiB,EAAoB,GAAQ,GAAa,EAAE,WAAW,IAAM,CAAC,EAEhF,IAAiC,QAC/B,EAAU,KAAK,OAAU;EAAE,IAAI,EAAK;EAAI,KAAK,EAAK;EAAK,MAAM,EAAK;EAAM,EAAE,EAChF,CAAC,EAAU,CACZ,EAEK,IAA8C,QAC5C,EAAQ,KAAK,OAAY;EAAE,IAAI,EAAO;EAAI,MAAM,EAAO;EAAM,cAAc,EAAO;EAAc,EAAE,EACxG,CAAC,EAAQ,CACV,EAMK,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,MAAM,GAAW,SAAS,iCAAiC;AAEvE,SAAO,EAAU;IAEnB,CAAC,EAAc,CAChB,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,CAEW,OAAU,MAAM,mCAAmC;IAElE;EAAC;EAAc;EAAQ;EAAY,CACpC;AAED,QACE,kBAAC,GAAD;EAAO,MAAM;EAAQ,eAAe,MAAS,CAAC,KAAQ,GAAS;YAC7D,kBAAC,GAAD;GAAc,OAAO;IAAE,OAAO;IAAK,UAAU;IAAQ,SAAS;IAAG,SAAS;IAAQ,eAAe;IAAU;aAA3G,CACE,kBAAC,GAAD;IAAa,OAAO;KAAE,SAAS;KAAI,eAAe;KAAG;cAArD,CACE,kBAAC,GAAD,EAAA,UAAY,oBAA6B,CAAA,EACzC,kBAAC,GAAD,EAAA,UAAkB,+EAEC,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"}
@@ -0,0 +1,449 @@
1
+ import e from "../components/widgets/WidgetErrorBoundary.js";
2
+ import t from "../renderers/adapters/BigConsoleRenderer.js";
3
+ import { PROPOSABLE_WIDGET_TYPES as n, derivePreviewState as ee, validateProposal as r } from "./proposalContract.js";
4
+ import { localProposalSource as i } from "./proposalSource.js";
5
+ import { memo as a, useCallback as o, useEffect as s, useMemo as c, useState as l } from "react";
6
+ import { AlertTriangle as u, Check as d, Loader2 as f, RefreshCw as p, Sparkles as m, X as h } from "lucide-react";
7
+ import { Button as g, Input as _, Label as v, Textarea as te } from "@burdenoff/fe-libs/ui";
8
+ import { Fragment as ne, jsx as y, jsxs as b } from "react/jsx-runtime";
9
+ //#region src/bigconsole/customWidget/CustomWidgetProposalPanel.tsx
10
+ var x = "w-full", S = {
11
+ loading: {
12
+ title: "Building preview…",
13
+ body: "Running the parser against the selected DataSink."
14
+ },
15
+ valid: {
16
+ title: "Preview",
17
+ body: ""
18
+ },
19
+ "no-data": {
20
+ title: "No data returned",
21
+ body: "The binding is valid but the parser returned no rows. The widget will render empty until the sink has data."
22
+ },
23
+ "field-mismatch": {
24
+ title: "Field mismatch",
25
+ body: "Some mapped fields are not present in the parser output. Fix the mapping below before approving."
26
+ },
27
+ "access-error": {
28
+ title: "Cannot read this data",
29
+ body: "You do not have access to the proposed DataSink or parser in this workspace."
30
+ }
31
+ };
32
+ function C(e) {
33
+ let t = (/* @__PURE__ */ new Date(0)).toISOString();
34
+ return {
35
+ id: `preview-${e.id}`,
36
+ pageId: "preview",
37
+ type: e.widgetType,
38
+ title: e.presentation.title || "Untitled widget",
39
+ description: e.presentation.description,
40
+ dataSinkId: e.binding.dataSinkId || null,
41
+ parserId: e.binding.parserId || null,
42
+ config: {
43
+ ...e.presentation.config,
44
+ ...e.fieldMapping
45
+ },
46
+ position: {
47
+ x: 0,
48
+ y: 0,
49
+ width: 6,
50
+ height: 4
51
+ },
52
+ positionX: 0,
53
+ positionY: 0,
54
+ positionWidth: 6,
55
+ positionHeight: 4,
56
+ createdAt: t,
57
+ updatedAt: t
58
+ };
59
+ }
60
+ function w(e) {
61
+ if (Array.isArray(e)) {
62
+ let t = e.find((e) => e && typeof e == "object");
63
+ return t ? Object.keys(t) : [];
64
+ }
65
+ if (e && typeof e == "object") return Object.keys(e);
66
+ }
67
+ function T(e) {
68
+ if (Array.isArray(e)) return e.length;
69
+ if (e && typeof e == "object") {
70
+ let t = e.cells;
71
+ return Array.isArray(t) ? t.length : Object.keys(e).length;
72
+ }
73
+ return 0;
74
+ }
75
+ var E = a(function({ sinks: a, parsers: E, resolvePreviewData: D, onApprove: O, onClose: k, source: A = i }) {
76
+ let [j, re] = l(""), [M, N] = l(null), [P, F] = l(!1), [I, L] = l(!1), [R, z] = l(null), [B, V] = l(void 0), [H, U] = l(!1), [W, G] = l(!1), K = c(() => a.map((e) => e.id), [a]), q = c(() => E.map((e) => e.id), [E]), J = c(() => w(B), [B]), Y = c(() => M ? r(M, {
77
+ permittedDataSinkIds: K,
78
+ permittedParserIds: q,
79
+ availableFields: J
80
+ }) : {
81
+ issues: [],
82
+ canApprove: !1
83
+ }, [
84
+ M,
85
+ K,
86
+ q,
87
+ J
88
+ ]), X = c(() => ee(Y, {
89
+ loading: H,
90
+ accessError: W,
91
+ rowCount: T(B)
92
+ }), [
93
+ Y,
94
+ H,
95
+ W,
96
+ B
97
+ ]);
98
+ s(() => {
99
+ if (!M || !M.binding.dataSinkId) {
100
+ V(void 0);
101
+ return;
102
+ }
103
+ let e = !1;
104
+ return U(!0), G(!1), D(M).then((t) => {
105
+ e || V(t);
106
+ }).catch(() => {
107
+ e || (V(void 0), G(!0));
108
+ }).finally(() => {
109
+ e || U(!1);
110
+ }), () => {
111
+ e = !0;
112
+ };
113
+ }, [M ? `${M.binding.dataSinkId}|${M.binding.parserId ?? ""}|${M.binding.dataPath ?? ""}` : "", D]);
114
+ let ie = o(async () => {
115
+ if (j.trim()) {
116
+ F(!0), z(null);
117
+ try {
118
+ N(await A.propose({
119
+ prompt: j.trim(),
120
+ sinks: a,
121
+ parsers: E
122
+ }));
123
+ } finally {
124
+ F(!1);
125
+ }
126
+ }
127
+ }, [
128
+ j,
129
+ A,
130
+ a,
131
+ E
132
+ ]), Z = o((e) => {
133
+ N((t) => t && e(t));
134
+ }, []), ae = o(() => {
135
+ N(null), V(void 0), G(!1), z(null);
136
+ }, []), oe = o(async () => {
137
+ if (!(!M || !Y.canApprove)) {
138
+ L(!0), z(null);
139
+ try {
140
+ await O(M), k();
141
+ } catch (e) {
142
+ z(e instanceof Error ? e.message : "Could not save the widget.");
143
+ } finally {
144
+ L(!1);
145
+ }
146
+ }
147
+ }, [
148
+ M,
149
+ Y.canApprove,
150
+ O,
151
+ k
152
+ ]), Q = Y.issues.filter((e) => e.severity === "error"), $ = Y.issues.filter((e) => e.severity === "warning");
153
+ return /* @__PURE__ */ b("section", {
154
+ "data-testid": "custom-widget-proposal-panel",
155
+ "aria-label": "AI custom widget builder",
156
+ className: "flex h-full w-full flex-col gap-4 overflow-y-auto p-4",
157
+ children: [/* @__PURE__ */ b("div", {
158
+ className: "flex flex-col gap-2",
159
+ children: [
160
+ /* @__PURE__ */ b(v, {
161
+ htmlFor: "custom-widget-prompt",
162
+ className: "flex items-center gap-2",
163
+ children: [/* @__PURE__ */ y(m, {
164
+ className: "size-4",
165
+ "aria-hidden": "true"
166
+ }), "Describe the widget you need"]
167
+ }),
168
+ /* @__PURE__ */ y(te, {
169
+ id: "custom-widget-prompt",
170
+ "data-testid": "custom-widget-prompt",
171
+ value: j,
172
+ onChange: (e) => re(e.target.value),
173
+ placeholder: "e.g. a funnel of signup conversion stages",
174
+ rows: 3
175
+ }),
176
+ /* @__PURE__ */ b("div", {
177
+ className: "flex flex-wrap gap-2",
178
+ children: [/* @__PURE__ */ b(g, {
179
+ "data-testid": "custom-widget-generate",
180
+ onClick: ie,
181
+ disabled: !j.trim() || P,
182
+ children: [P ? /* @__PURE__ */ y(f, {
183
+ className: "mr-2 size-4 animate-spin",
184
+ "aria-hidden": "true"
185
+ }) : null, M ? "Regenerate" : "Generate proposal"]
186
+ }), M ? /* @__PURE__ */ b(g, {
187
+ variant: "outline",
188
+ "data-testid": "custom-widget-reject",
189
+ onClick: ae,
190
+ children: [/* @__PURE__ */ y(h, {
191
+ className: "mr-1 size-4",
192
+ "aria-hidden": "true"
193
+ }), "Reject"]
194
+ }) : null]
195
+ })
196
+ ]
197
+ }), M ? /* @__PURE__ */ b(ne, { children: [
198
+ /* @__PURE__ */ b("div", {
199
+ className: "flex flex-col gap-2",
200
+ children: [
201
+ /* @__PURE__ */ b("div", {
202
+ className: "flex items-center justify-between gap-2",
203
+ children: [/* @__PURE__ */ y("h3", {
204
+ className: "text-sm font-semibold text-text-primary",
205
+ children: S[X].title
206
+ }), /* @__PURE__ */ y("span", {
207
+ "data-testid": "custom-widget-preview-state",
208
+ "data-state": X,
209
+ className: "rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary",
210
+ children: X
211
+ })]
212
+ }),
213
+ /* @__PURE__ */ y("div", {
214
+ "data-testid": "custom-widget-preview",
215
+ className: "min-h-[220px] rounded-lg border border-border-default bg-bg-surface p-2",
216
+ children: X === "loading" ? /* @__PURE__ */ b("div", {
217
+ className: "flex h-[200px] items-center justify-center text-text-secondary",
218
+ children: [/* @__PURE__ */ y(f, {
219
+ className: "mr-2 size-4 animate-spin",
220
+ "aria-hidden": "true"
221
+ }), /* @__PURE__ */ y("span", {
222
+ className: "text-sm",
223
+ children: S.loading.body
224
+ })]
225
+ }) : X === "valid" ? /* @__PURE__ */ y("div", {
226
+ className: "h-[200px]",
227
+ children: /* @__PURE__ */ y(e, {
228
+ widgetId: `preview-${M.id}`,
229
+ children: /* @__PURE__ */ y(t, {
230
+ widget: C(M),
231
+ data: B
232
+ })
233
+ })
234
+ }) : /* @__PURE__ */ b("div", {
235
+ className: "flex h-[200px] flex-col items-center justify-center gap-2 px-4 text-center",
236
+ children: [
237
+ /* @__PURE__ */ y(u, {
238
+ className: "size-5 text-status-warning-text",
239
+ "aria-hidden": "true"
240
+ }),
241
+ /* @__PURE__ */ y("p", {
242
+ className: "text-sm text-text-secondary",
243
+ children: S[X].body
244
+ }),
245
+ X === "access-error" ? /* @__PURE__ */ b(g, {
246
+ size: "sm",
247
+ variant: "outline",
248
+ onClick: () => Z((e) => ({ ...e })),
249
+ children: [/* @__PURE__ */ y(p, {
250
+ className: "mr-1 size-3.5",
251
+ "aria-hidden": "true"
252
+ }), "Retry"]
253
+ }) : null
254
+ ]
255
+ })
256
+ }),
257
+ M.rationale ? /* @__PURE__ */ y("p", {
258
+ className: "text-xs text-text-secondary",
259
+ "data-testid": "custom-widget-rationale",
260
+ children: M.rationale
261
+ }) : null
262
+ ]
263
+ }),
264
+ Q.length > 0 || $.length > 0 ? /* @__PURE__ */ y("ul", {
265
+ "data-testid": "custom-widget-issues",
266
+ className: "flex flex-col gap-1",
267
+ children: [...Q, ...$].map((e) => /* @__PURE__ */ y("li", {
268
+ "data-severity": e.severity,
269
+ className: `rounded-md px-2 py-1 text-xs ${e.severity === "error" ? "bg-status-error-bg/10 text-status-error-text" : "bg-status-warning-bg/10 text-status-warning-text"}`,
270
+ children: e.message
271
+ }, `${e.code}-${e.path ?? ""}`))
272
+ }) : null,
273
+ /* @__PURE__ */ b("div", {
274
+ className: "flex flex-col gap-3",
275
+ children: [
276
+ /* @__PURE__ */ y("h3", {
277
+ className: "text-sm font-semibold text-text-primary",
278
+ children: "Proposal"
279
+ }),
280
+ /* @__PURE__ */ b("div", {
281
+ className: "flex flex-col gap-1",
282
+ children: [/* @__PURE__ */ y(v, {
283
+ htmlFor: "custom-widget-title",
284
+ children: "Title"
285
+ }), /* @__PURE__ */ y(_, {
286
+ id: "custom-widget-title",
287
+ "data-testid": "custom-widget-title",
288
+ className: x,
289
+ value: M.presentation.title,
290
+ onChange: (e) => Z((t) => ({
291
+ ...t,
292
+ presentation: {
293
+ ...t.presentation,
294
+ title: e.target.value
295
+ }
296
+ }))
297
+ })]
298
+ }),
299
+ /* @__PURE__ */ b("div", {
300
+ className: "flex flex-col gap-1",
301
+ children: [/* @__PURE__ */ y(v, {
302
+ htmlFor: "custom-widget-type",
303
+ children: "Widget type"
304
+ }), /* @__PURE__ */ y("select", {
305
+ id: "custom-widget-type",
306
+ "data-testid": "custom-widget-type",
307
+ value: M.widgetType,
308
+ onChange: (e) => Z((t) => ({
309
+ ...t,
310
+ widgetType: e.target.value
311
+ })),
312
+ className: "w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg",
313
+ children: n.map((e) => /* @__PURE__ */ y("option", {
314
+ value: e,
315
+ children: e
316
+ }, e))
317
+ })]
318
+ }),
319
+ /* @__PURE__ */ b("div", {
320
+ className: "flex flex-col gap-1",
321
+ children: [/* @__PURE__ */ y(v, {
322
+ htmlFor: "custom-widget-sink",
323
+ children: "DataSink"
324
+ }), /* @__PURE__ */ b("select", {
325
+ id: "custom-widget-sink",
326
+ "data-testid": "custom-widget-sink",
327
+ value: M.binding.dataSinkId,
328
+ onChange: (e) => {
329
+ let t = a.find((t) => t.id === e.target.value);
330
+ Z((n) => ({
331
+ ...n,
332
+ binding: {
333
+ ...n.binding,
334
+ dataSinkId: e.target.value,
335
+ dataSinkKey: t?.key
336
+ }
337
+ }));
338
+ },
339
+ className: "w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg",
340
+ children: [/* @__PURE__ */ y("option", {
341
+ value: "",
342
+ children: "Select a DataSink…"
343
+ }), a.map((e) => /* @__PURE__ */ y("option", {
344
+ value: e.id,
345
+ children: e.name || e.key
346
+ }, e.id))]
347
+ })]
348
+ }),
349
+ /* @__PURE__ */ b("div", {
350
+ className: "flex flex-col gap-1",
351
+ children: [/* @__PURE__ */ y(v, {
352
+ htmlFor: "custom-widget-parser",
353
+ children: "Parser"
354
+ }), /* @__PURE__ */ b("select", {
355
+ id: "custom-widget-parser",
356
+ "data-testid": "custom-widget-parser",
357
+ value: M.binding.parserId ?? "",
358
+ onChange: (e) => {
359
+ let t = E.find((t) => t.id === e.target.value);
360
+ Z((n) => ({
361
+ ...n,
362
+ binding: {
363
+ ...n.binding,
364
+ parserId: e.target.value || void 0,
365
+ parserName: t?.name
366
+ }
367
+ }));
368
+ },
369
+ className: "w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg",
370
+ children: [/* @__PURE__ */ y("option", {
371
+ value: "",
372
+ children: "No parser"
373
+ }), E.map((e) => /* @__PURE__ */ y("option", {
374
+ value: e.id,
375
+ children: e.name
376
+ }, e.id))]
377
+ })]
378
+ }),
379
+ Object.keys(M.fieldMapping).length > 0 ? /* @__PURE__ */ b("fieldset", {
380
+ className: "flex flex-col gap-2",
381
+ "data-testid": "custom-widget-field-mapping",
382
+ children: [
383
+ /* @__PURE__ */ y("legend", {
384
+ className: "text-sm font-medium text-text-primary",
385
+ children: "Field mapping"
386
+ }),
387
+ Object.entries(M.fieldMapping).map(([e, t]) => /* @__PURE__ */ b("div", {
388
+ className: "flex flex-col gap-1",
389
+ children: [/* @__PURE__ */ y(v, {
390
+ htmlFor: `custom-widget-field-${e}`,
391
+ children: e
392
+ }), /* @__PURE__ */ y(_, {
393
+ id: `custom-widget-field-${e}`,
394
+ "data-testid": `custom-widget-field-${e}`,
395
+ value: t,
396
+ onChange: (t) => Z((n) => ({
397
+ ...n,
398
+ fieldMapping: {
399
+ ...n.fieldMapping,
400
+ [e]: t.target.value
401
+ }
402
+ }))
403
+ })]
404
+ }, e)),
405
+ J?.length ? /* @__PURE__ */ b("p", {
406
+ className: "text-xs text-text-secondary",
407
+ children: ["Available: ", J.join(", ")]
408
+ }) : null
409
+ ]
410
+ }) : null
411
+ ]
412
+ }),
413
+ R ? /* @__PURE__ */ y("p", {
414
+ role: "alert",
415
+ className: "text-xs text-status-error-text",
416
+ "data-testid": "custom-widget-approve-error",
417
+ children: R
418
+ }) : null,
419
+ /* @__PURE__ */ b("div", {
420
+ className: "flex flex-wrap gap-2 border-t border-border-default pt-3",
421
+ children: [/* @__PURE__ */ b(g, {
422
+ "data-testid": "custom-widget-approve",
423
+ onClick: oe,
424
+ disabled: !Y.canApprove || I,
425
+ children: [I ? /* @__PURE__ */ y(f, {
426
+ className: "mr-2 size-4 animate-spin",
427
+ "aria-hidden": "true"
428
+ }) : /* @__PURE__ */ y(d, {
429
+ className: "mr-1 size-4",
430
+ "aria-hidden": "true"
431
+ }), "Approve & add to dashboard"]
432
+ }), /* @__PURE__ */ y(g, {
433
+ variant: "outline",
434
+ onClick: k,
435
+ "data-testid": "custom-widget-cancel",
436
+ children: "Cancel"
437
+ })]
438
+ })
439
+ ] }) : /* @__PURE__ */ y("p", {
440
+ className: "text-sm text-text-secondary",
441
+ "data-testid": "custom-widget-empty",
442
+ children: "No proposal yet. Describe a widget and the assistant will propose one for you to review — nothing is added to the dashboard until you approve it."
443
+ })]
444
+ });
445
+ });
446
+ //#endregion
447
+ export { E as default };
448
+
449
+ //# sourceMappingURL=CustomWidgetProposalPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CustomWidgetProposalPanel.js","names":[],"sources":["../../../src/bigconsole/customWidget/CustomWidgetProposalPanel.tsx"],"sourcesContent":["/**\n * Custom-widget proposal review panel (BOFF-5532).\n *\n * The authoring surface for the AI custom-widget builder: prompt in, typed\n * proposal out, reviewed and edited by a human, then approved into a real\n * widget. Nothing is written to the dashboard until the reviewer approves.\n *\n * Deliberate choices:\n * - The preview renders through the *real* widget renderer with the *real*\n * parser output, so what the reviewer approves is what they will get.\n * - Bindings are chosen from the user's permitted sinks and parsers only, and\n * the panel says plainly when a proposal points somewhere they cannot reach.\n * - Every proposal field is editable before approval — the AI proposes, the\n * reviewer disposes.\n */\n\nimport { type FC, memo, useCallback, useEffect, useMemo, useState } from 'react';\nimport { Button, Input, Label, Textarea } from '@burdenoff/fe-libs/ui';\nimport { AlertTriangle, Check, Loader2, RefreshCw, Sparkles, X } from 'lucide-react';\n\nimport type { Widget } from '../types';\nimport { BigConsoleRenderer } from '../renderers/adapters/BigConsoleRenderer';\nimport { WidgetErrorBoundary } from '../components/widgets/WidgetErrorBoundary';\nimport {\n PROPOSABLE_WIDGET_TYPES,\n derivePreviewState,\n validateProposal,\n type CustomWidgetProposal,\n type ProposableWidgetType,\n type ProposalPreviewState,\n} from './proposalContract';\nimport {\n localProposalSource,\n type CustomWidgetProposalSource,\n type ProposalCandidateParser,\n type ProposalCandidateSink,\n} from './proposalSource';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface CustomWidgetProposalPanelProps {\n /** Sinks the user may bind to (already RBAC-filtered by the API). */\n sinks: readonly ProposalCandidateSink[];\n /** Parsers the user may bind to. */\n parsers: readonly ProposalCandidateParser[];\n /**\n * Resolves the rows a binding would produce, for the preview. Should reject\n * (or throw) when the user cannot read the binding, so the panel can show the\n * access-failure state.\n */\n resolvePreviewData: (proposal: CustomWidgetProposal) => Promise<unknown>;\n /** Persists an approved proposal. Resolves once the widget exists. */\n onApprove: (proposal: CustomWidgetProposal) => Promise<void>;\n /** Closes the panel without persisting anything. */\n onClose: () => void;\n /** Swap for the GraphQL-backed source once BOFF-3021's backend half lands. */\n source?: CustomWidgetProposalSource;\n}\n\nconst FIELD_CLASSES = 'w-full';\n\nconst PREVIEW_COPY: Record<ProposalPreviewState, { title: string; body: string }> = {\n loading: { title: 'Building preview…', body: 'Running the parser against the selected DataSink.' },\n valid: { title: 'Preview', body: '' },\n 'no-data': {\n title: 'No data returned',\n body: 'The binding is valid but the parser returned no rows. The widget will render empty until the sink has data.',\n },\n 'field-mismatch': {\n title: 'Field mismatch',\n body: 'Some mapped fields are not present in the parser output. Fix the mapping below before approving.',\n },\n 'access-error': {\n title: 'Cannot read this data',\n body: 'You do not have access to the proposed DataSink or parser in this workspace.',\n },\n};\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Builds a throwaway Widget so the preview can use the production renderer. */\nfunction toDraftWidget(proposal: CustomWidgetProposal): Widget {\n const now = new Date(0).toISOString();\n return {\n id: `preview-${proposal.id}`,\n pageId: 'preview',\n type: proposal.widgetType,\n title: proposal.presentation.title || 'Untitled widget',\n description: proposal.presentation.description,\n dataSinkId: proposal.binding.dataSinkId || null,\n parserId: proposal.binding.parserId || null,\n config: { ...proposal.presentation.config, ...proposal.fieldMapping },\n position: { x: 0, y: 0, width: 6, height: 4 },\n positionX: 0,\n positionY: 0,\n positionWidth: 6,\n positionHeight: 4,\n createdAt: now,\n updatedAt: now,\n } as Widget;\n}\n\n/** Field names present in the resolved preview payload, for mismatch checks. */\nfunction extractFields(data: unknown): string[] | undefined {\n if (Array.isArray(data)) {\n const first = data.find((row) => row && typeof row === 'object');\n return first ? Object.keys(first as Record<string, unknown>) : [];\n }\n if (data && typeof data === 'object') return Object.keys(data as Record<string, unknown>);\n return undefined;\n}\n\nfunction countRows(data: unknown): number {\n if (Array.isArray(data)) return data.length;\n if (data && typeof data === 'object') {\n const cells = (data as { cells?: unknown }).cells;\n if (Array.isArray(cells)) return cells.length;\n return Object.keys(data as Record<string, unknown>).length;\n }\n return 0;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const CustomWidgetProposalPanel: FC<CustomWidgetProposalPanelProps> = memo(function CustomWidgetProposalPanel({\n sinks,\n parsers,\n resolvePreviewData,\n onApprove,\n onClose,\n source = localProposalSource,\n}) {\n const [prompt, setPrompt] = useState('');\n const [proposal, setProposal] = useState<CustomWidgetProposal | null>(null);\n const [isProposing, setIsProposing] = useState(false);\n const [isApproving, setIsApproving] = useState(false);\n const [approveError, setApproveError] = useState<string | null>(null);\n\n const [previewData, setPreviewData] = useState<unknown>(undefined);\n const [previewLoading, setPreviewLoading] = useState(false);\n const [previewAccessError, setPreviewAccessError] = useState(false);\n\n const permittedDataSinkIds = useMemo(() => sinks.map((sink) => sink.id), [sinks]);\n const permittedParserIds = useMemo(() => parsers.map((parser) => parser.id), [parsers]);\n const availableFields = useMemo(() => extractFields(previewData), [previewData]);\n\n const validation = useMemo(\n () =>\n proposal\n ? validateProposal(proposal, { permittedDataSinkIds, permittedParserIds, availableFields })\n : { issues: [], canApprove: false },\n [proposal, permittedDataSinkIds, permittedParserIds, availableFields]\n );\n\n const previewState = useMemo(\n () =>\n derivePreviewState(validation, {\n loading: previewLoading,\n accessError: previewAccessError,\n rowCount: countRows(previewData),\n }),\n [validation, previewLoading, previewAccessError, previewData]\n );\n\n // Re-resolve the preview whenever the binding changes.\n const bindingKey = proposal\n ? `${proposal.binding.dataSinkId}|${proposal.binding.parserId ?? ''}|${proposal.binding.dataPath ?? ''}`\n : '';\n\n useEffect(() => {\n if (!proposal || !proposal.binding.dataSinkId) {\n setPreviewData(undefined);\n return;\n }\n let cancelled = false;\n setPreviewLoading(true);\n setPreviewAccessError(false);\n\n resolvePreviewData(proposal)\n .then((data) => {\n if (cancelled) return;\n setPreviewData(data);\n })\n .catch(() => {\n if (cancelled) return;\n setPreviewData(undefined);\n setPreviewAccessError(true);\n })\n .finally(() => {\n if (!cancelled) setPreviewLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n // `bindingKey` captures every input that changes the resolved data.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [bindingKey, resolvePreviewData]);\n\n const handlePropose = useCallback(async () => {\n if (!prompt.trim()) return;\n setIsProposing(true);\n setApproveError(null);\n try {\n const next = await source.propose({ prompt: prompt.trim(), sinks, parsers });\n setProposal(next);\n } finally {\n setIsProposing(false);\n }\n }, [prompt, source, sinks, parsers]);\n\n const patchProposal = useCallback((patch: (current: CustomWidgetProposal) => CustomWidgetProposal) => {\n setProposal((current) => (current ? patch(current) : current));\n }, []);\n\n const handleReject = useCallback(() => {\n setProposal(null);\n setPreviewData(undefined);\n setPreviewAccessError(false);\n setApproveError(null);\n }, []);\n\n const handleApprove = useCallback(async () => {\n if (!proposal || !validation.canApprove) return;\n setIsApproving(true);\n setApproveError(null);\n try {\n await onApprove(proposal);\n onClose();\n } catch (error) {\n setApproveError(error instanceof Error ? error.message : 'Could not save the widget.');\n } finally {\n setIsApproving(false);\n }\n }, [proposal, validation.canApprove, onApprove, onClose]);\n\n const errors = validation.issues.filter((issue) => issue.severity === 'error');\n const warnings = validation.issues.filter((issue) => issue.severity === 'warning');\n\n return (\n <section\n data-testid=\"custom-widget-proposal-panel\"\n aria-label=\"AI custom widget builder\"\n className=\"flex h-full w-full flex-col gap-4 overflow-y-auto p-4\"\n >\n {/* ---------- Prompt ---------- */}\n <div className=\"flex flex-col gap-2\">\n <Label htmlFor=\"custom-widget-prompt\" className=\"flex items-center gap-2\">\n <Sparkles className=\"size-4\" aria-hidden=\"true\" />\n Describe the widget you need\n </Label>\n <Textarea\n id=\"custom-widget-prompt\"\n data-testid=\"custom-widget-prompt\"\n value={prompt}\n onChange={(event) => setPrompt(event.target.value)}\n placeholder=\"e.g. a funnel of signup conversion stages\"\n rows={3}\n />\n <div className=\"flex flex-wrap gap-2\">\n <Button data-testid=\"custom-widget-generate\" onClick={handlePropose} disabled={!prompt.trim() || isProposing}>\n {isProposing ? <Loader2 className=\"mr-2 size-4 animate-spin\" aria-hidden=\"true\" /> : null}\n {proposal ? 'Regenerate' : 'Generate proposal'}\n </Button>\n {proposal ? (\n <Button variant=\"outline\" data-testid=\"custom-widget-reject\" onClick={handleReject}>\n <X className=\"mr-1 size-4\" aria-hidden=\"true\" />\n Reject\n </Button>\n ) : null}\n </div>\n </div>\n\n {!proposal ? (\n <p className=\"text-sm text-text-secondary\" data-testid=\"custom-widget-empty\">\n No proposal yet. Describe a widget and the assistant will propose one for you to review — nothing is added to\n the dashboard until you approve it.\n </p>\n ) : (\n <>\n {/* ---------- Preview ---------- */}\n <div className=\"flex flex-col gap-2\">\n <div className=\"flex items-center justify-between gap-2\">\n <h3 className=\"text-sm font-semibold text-text-primary\">{PREVIEW_COPY[previewState].title}</h3>\n <span\n data-testid=\"custom-widget-preview-state\"\n data-state={previewState}\n className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\"\n >\n {previewState}\n </span>\n </div>\n\n <div\n data-testid=\"custom-widget-preview\"\n className=\"min-h-[220px] rounded-lg border border-border-default bg-bg-surface p-2\"\n >\n {previewState === 'loading' ? (\n <div className=\"flex h-[200px] items-center justify-center text-text-secondary\">\n <Loader2 className=\"mr-2 size-4 animate-spin\" aria-hidden=\"true\" />\n <span className=\"text-sm\">{PREVIEW_COPY.loading.body}</span>\n </div>\n ) : previewState === 'valid' ? (\n <div className=\"h-[200px]\">\n <WidgetErrorBoundary widgetId={`preview-${proposal.id}`}>\n <BigConsoleRenderer\n widget={toDraftWidget(proposal)}\n data={previewData as Record<string, unknown>}\n />\n </WidgetErrorBoundary>\n </div>\n ) : (\n <div className=\"flex h-[200px] flex-col items-center justify-center gap-2 px-4 text-center\">\n <AlertTriangle className=\"size-5 text-status-warning-text\" aria-hidden=\"true\" />\n <p className=\"text-sm text-text-secondary\">{PREVIEW_COPY[previewState].body}</p>\n {previewState === 'access-error' ? (\n <Button size=\"sm\" variant=\"outline\" onClick={() => patchProposal((current) => ({ ...current }))}>\n <RefreshCw className=\"mr-1 size-3.5\" aria-hidden=\"true\" />\n Retry\n </Button>\n ) : null}\n </div>\n )}\n </div>\n\n {proposal.rationale ? (\n <p className=\"text-xs text-text-secondary\" data-testid=\"custom-widget-rationale\">\n {proposal.rationale}\n </p>\n ) : null}\n </div>\n\n {/* ---------- Validation ---------- */}\n {errors.length > 0 || warnings.length > 0 ? (\n <ul data-testid=\"custom-widget-issues\" className=\"flex flex-col gap-1\">\n {[...errors, ...warnings].map((issue) => (\n <li\n key={`${issue.code}-${issue.path ?? ''}`}\n data-severity={issue.severity}\n className={`rounded-md px-2 py-1 text-xs ${\n issue.severity === 'error'\n ? 'bg-status-error-bg/10 text-status-error-text'\n : 'bg-status-warning-bg/10 text-status-warning-text'\n }`}\n >\n {issue.message}\n </li>\n ))}\n </ul>\n ) : null}\n\n {/* ---------- Editable proposal ---------- */}\n <div className=\"flex flex-col gap-3\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Proposal</h3>\n\n <div className=\"flex flex-col gap-1\">\n <Label htmlFor=\"custom-widget-title\">Title</Label>\n <Input\n id=\"custom-widget-title\"\n data-testid=\"custom-widget-title\"\n className={FIELD_CLASSES}\n value={proposal.presentation.title}\n onChange={(event) =>\n patchProposal((current) => ({\n ...current,\n presentation: { ...current.presentation, title: event.target.value },\n }))\n }\n />\n </div>\n\n <div className=\"flex flex-col gap-1\">\n <Label htmlFor=\"custom-widget-type\">Widget type</Label>\n <select\n id=\"custom-widget-type\"\n data-testid=\"custom-widget-type\"\n value={proposal.widgetType}\n onChange={(event) =>\n patchProposal((current) => ({\n ...current,\n widgetType: event.target.value as ProposableWidgetType,\n }))\n }\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n >\n {PROPOSABLE_WIDGET_TYPES.map((type) => (\n <option key={type} value={type}>\n {type}\n </option>\n ))}\n </select>\n </div>\n\n <div className=\"flex flex-col gap-1\">\n <Label htmlFor=\"custom-widget-sink\">DataSink</Label>\n <select\n id=\"custom-widget-sink\"\n data-testid=\"custom-widget-sink\"\n value={proposal.binding.dataSinkId}\n onChange={(event) => {\n const sink = sinks.find((candidate) => candidate.id === event.target.value);\n patchProposal((current) => ({\n ...current,\n binding: { ...current.binding, dataSinkId: event.target.value, dataSinkKey: sink?.key },\n }));\n }}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n >\n <option value=\"\">Select a DataSink…</option>\n {sinks.map((sink) => (\n <option key={sink.id} value={sink.id}>\n {sink.name || sink.key}\n </option>\n ))}\n </select>\n </div>\n\n <div className=\"flex flex-col gap-1\">\n <Label htmlFor=\"custom-widget-parser\">Parser</Label>\n <select\n id=\"custom-widget-parser\"\n data-testid=\"custom-widget-parser\"\n value={proposal.binding.parserId ?? ''}\n onChange={(event) => {\n const parser = parsers.find((candidate) => candidate.id === event.target.value);\n patchProposal((current) => ({\n ...current,\n binding: {\n ...current.binding,\n parserId: event.target.value || undefined,\n parserName: parser?.name,\n },\n }));\n }}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n >\n <option value=\"\">No parser</option>\n {parsers.map((parser) => (\n <option key={parser.id} value={parser.id}>\n {parser.name}\n </option>\n ))}\n </select>\n </div>\n\n {/* Field mapping */}\n {Object.keys(proposal.fieldMapping).length > 0 ? (\n <fieldset className=\"flex flex-col gap-2\" data-testid=\"custom-widget-field-mapping\">\n <legend className=\"text-sm font-medium text-text-primary\">Field mapping</legend>\n {Object.entries(proposal.fieldMapping).map(([role, field]) => (\n <div key={role} className=\"flex flex-col gap-1\">\n <Label htmlFor={`custom-widget-field-${role}`}>{role}</Label>\n <Input\n id={`custom-widget-field-${role}`}\n data-testid={`custom-widget-field-${role}`}\n value={field}\n onChange={(event) =>\n patchProposal((current) => ({\n ...current,\n fieldMapping: { ...current.fieldMapping, [role]: event.target.value },\n }))\n }\n />\n </div>\n ))}\n {availableFields?.length ? (\n <p className=\"text-xs text-text-secondary\">Available: {availableFields.join(', ')}</p>\n ) : null}\n </fieldset>\n ) : null}\n </div>\n\n {/* ---------- Approval ---------- */}\n {approveError ? (\n <p role=\"alert\" className=\"text-xs text-status-error-text\" data-testid=\"custom-widget-approve-error\">\n {approveError}\n </p>\n ) : null}\n\n <div className=\"flex flex-wrap gap-2 border-t border-border-default pt-3\">\n <Button\n data-testid=\"custom-widget-approve\"\n onClick={handleApprove}\n disabled={!validation.canApprove || isApproving}\n >\n {isApproving ? (\n <Loader2 className=\"mr-2 size-4 animate-spin\" aria-hidden=\"true\" />\n ) : (\n <Check className=\"mr-1 size-4\" aria-hidden=\"true\" />\n )}\n Approve &amp; add to dashboard\n </Button>\n <Button variant=\"outline\" onClick={onClose} data-testid=\"custom-widget-cancel\">\n Cancel\n </Button>\n </div>\n </>\n )}\n </section>\n );\n});\n\nexport default CustomWidgetProposalPanel;\n"],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import "./proposalContract.js";
2
+ import "./proposalSource.js";
3
+ import "./CustomWidgetProposalPanel.js";
4
+ import "./CustomWidgetBuilderSheet.js";