@burdenoff/microfe-bigconsole 2026.812.2 → 2026.827.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/bigconsole/components/builder/inspector/LayoutSection.js +71 -0
  2. package/dist/bigconsole/components/builder/inspector/LayoutSection.js.map +1 -0
  3. package/dist/bigconsole/components/builder/inspector/index.js +1 -0
  4. package/dist/bigconsole/components/dashboard/DashboardCanvas.js +137 -121
  5. package/dist/bigconsole/components/dashboard/DashboardCanvas.js.map +1 -1
  6. package/dist/bigconsole/components/dashboard/ExportDashboardDialog.js +204 -93
  7. package/dist/bigconsole/components/dashboard/ExportDashboardDialog.js.map +1 -1
  8. package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.js +259 -133
  9. package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.js.map +1 -1
  10. package/dist/bigconsole/components/dashboard/StoreTemplatePicker.js +87 -39
  11. package/dist/bigconsole/components/dashboard/StoreTemplatePicker.js.map +1 -1
  12. package/dist/bigconsole/components/datasink/DataSinkCard.js +88 -68
  13. package/dist/bigconsole/components/datasink/DataSinkCard.js.map +1 -1
  14. package/dist/bigconsole/components/widgets/PropertiesPanel.js +213 -199
  15. package/dist/bigconsole/components/widgets/PropertiesPanel.js.map +1 -1
  16. package/dist/bigconsole/components/workflow/WorkflowCard.js +46 -27
  17. package/dist/bigconsole/components/workflow/WorkflowCard.js.map +1 -1
  18. package/dist/bigconsole/customWidget/CustomWidgetBuilderSheet.js +44 -47
  19. package/dist/bigconsole/customWidget/CustomWidgetBuilderSheet.js.map +1 -1
  20. package/dist/bigconsole/customWidget/proposalSource.js +31 -24
  21. package/dist/bigconsole/customWidget/proposalSource.js.map +1 -1
  22. package/dist/bigconsole/hooks/useDashboardOperations.js +74 -64
  23. package/dist/bigconsole/hooks/useDashboardOperations.js.map +1 -1
  24. package/dist/bigconsole/hooks/useDataSinkOperations.js +2 -2
  25. package/dist/bigconsole/hooks/useDataSinkOperations.js.map +1 -1
  26. package/dist/bigconsole/pages/DashboardBuilderPage.js +1 -1
  27. package/dist/bigconsole/pages/DashboardsPage.js +386 -316
  28. package/dist/bigconsole/pages/DashboardsPage.js.map +1 -1
  29. package/dist/bigconsole/pages/DataSinksPage.js +251 -206
  30. package/dist/bigconsole/pages/DataSinksPage.js.map +1 -1
  31. package/dist/bigconsole/pages/InsightsPage.js +646 -378
  32. package/dist/bigconsole/pages/InsightsPage.js.map +1 -1
  33. package/dist/bigconsole/pages/WorkflowsPage.js +153 -108
  34. package/dist/bigconsole/pages/WorkflowsPage.js.map +1 -1
  35. package/dist/bigconsole/utils/format.js +18 -0
  36. package/dist/bigconsole/utils/format.js.map +1 -0
  37. package/dist/bigconsole/utils/widgetPositioning.js +1 -1
  38. package/dist/generated/wspace-operations.js +1 -0
  39. package/dist/generated/wspace-operations.js.map +1 -1
  40. package/package.json +1 -1
@@ -65,25 +65,31 @@ var s = ({ className: e }) => /* @__PURE__ */ i("svg", {
65
65
  strokeWidth: 2,
66
66
  d: "M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
67
67
  })]
68
- }), f = n(function({ workflow: n, onView: f, onEdit: p, onDelete: m }) {
69
- let h = n.dataSinkLinks?.length ?? n.linkedDataSinkCount ?? 0, g = r((e) => {
68
+ }), f = n(function({ workflow: n, onView: f, onEdit: p, onDelete: m, onRun: h, isRunning: g = !1 }) {
69
+ let _ = n.dataSinkLinks?.length ?? n.linkedDataSinkCount ?? 0, v = r((e) => {
70
70
  if (!e) return "Never";
71
71
  try {
72
72
  return o(new Date(e), { addSuffix: !0 });
73
73
  } catch {
74
74
  return "Unknown";
75
75
  }
76
- }, []), _ = r(() => {
76
+ }, []), y = r(() => {
77
77
  f?.(n.id);
78
- }, [f, n.id]), v = r((e) => {
78
+ }, [f, n.id]), b = r((e) => {
79
79
  e.stopPropagation(), p?.(n.id);
80
- }, [p, n.id]), y = r((e) => {
80
+ }, [p, n.id]), x = r((e) => {
81
81
  e.stopPropagation(), m?.(n.id);
82
- }, [m, n.id]), b = r((e) => {
82
+ }, [m, n.id]), S = r((e) => {
83
+ e.stopPropagation(), !g && h?.(n.id);
84
+ }, [
85
+ g,
86
+ h,
87
+ n.id
88
+ ]), C = r((e) => {
83
89
  e.stopPropagation(), f?.(n.id);
84
90
  }, [f, n.id]);
85
91
  return /* @__PURE__ */ a("div", {
86
- onClick: _,
92
+ onClick: y,
87
93
  className: "min-w-0 cursor-pointer overflow-hidden rounded-card border border-[var(--color-border-subtle)] bg-bg-elevated motion-safe:transition-[box-shadow,border-color] focus-within:border-action-primary-bg hover:border-action-primary-bg hover:shadow-[var(--shadow-elevation-1)]",
88
94
  children: [/* @__PURE__ */ a("div", {
89
95
  className: "flex items-center justify-between gap-icon-gap border-b border-[var(--color-border-subtle)] bg-bg-sunken px-3 py-2",
@@ -108,7 +114,7 @@ var s = ({ className: e }) => /* @__PURE__ */ i("svg", {
108
114
  className: "text-sm font-semibold text-text-primary",
109
115
  children: /* @__PURE__ */ i("button", {
110
116
  type: "button",
111
- onClick: b,
117
+ onClick: C,
112
118
  "aria-label": `Open workflow ${n.name}`,
113
119
  className: `line-clamp-2 rounded-button text-left ${e}`,
114
120
  children: n.name
@@ -119,21 +125,34 @@ var s = ({ className: e }) => /* @__PURE__ */ i("svg", {
119
125
  })]
120
126
  }), /* @__PURE__ */ a("div", {
121
127
  className: "flex shrink-0 items-center gap-1",
122
- children: [p && /* @__PURE__ */ i("button", {
123
- type: "button",
124
- onClick: v,
125
- className: "inline-flex h-control-md w-[var(--size-controlHeight-md)] items-center justify-center rounded-button text-text-secondary motion-safe:transition-colors hover:bg-bg-sunken hover:text-text-primary sm:h-control-sm sm:w-[var(--size-controlHeight-sm)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-bg-surface",
126
- title: "Edit Workflow",
127
- "aria-label": `Edit workflow ${n.name}`,
128
- children: /* @__PURE__ */ i(c, { className: "w-4 h-4" })
129
- }), m && /* @__PURE__ */ i("button", {
130
- type: "button",
131
- onClick: y,
132
- className: "inline-flex h-control-md w-[var(--size-controlHeight-md)] items-center justify-center rounded-button text-text-secondary motion-safe:transition-colors hover:bg-status-error-bg/10 hover:text-status-error-text sm:h-control-sm sm:w-[var(--size-controlHeight-sm)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-bg-surface",
133
- title: "Delete Workflow",
134
- "aria-label": `Delete workflow ${n.name}`,
135
- children: /* @__PURE__ */ i(l, { className: "w-4 h-4" })
136
- })]
128
+ children: [
129
+ h && /* @__PURE__ */ i("button", {
130
+ type: "button",
131
+ onClick: S,
132
+ disabled: g,
133
+ "aria-busy": g,
134
+ className: "inline-flex h-control-md w-[var(--size-controlHeight-md)] items-center justify-center rounded-button text-text-secondary motion-safe:transition-colors hover:bg-bg-sunken hover:text-text-primary disabled:cursor-not-allowed disabled:opacity-60 sm:h-control-sm sm:w-[var(--size-controlHeight-sm)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-bg-surface",
135
+ title: g ? "Running…" : "Run Workflow",
136
+ "aria-label": g ? `Running workflow ${n.name}` : `Run workflow ${n.name}`,
137
+ children: /* @__PURE__ */ i(d, { className: `w-4 h-4 ${g ? "motion-safe:animate-pulse" : ""}` })
138
+ }),
139
+ p && /* @__PURE__ */ i("button", {
140
+ type: "button",
141
+ onClick: b,
142
+ className: "inline-flex h-control-md w-[var(--size-controlHeight-md)] items-center justify-center rounded-button text-text-secondary motion-safe:transition-colors hover:bg-bg-sunken hover:text-text-primary sm:h-control-sm sm:w-[var(--size-controlHeight-sm)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-bg-surface",
143
+ title: "Edit Workflow",
144
+ "aria-label": `Edit workflow ${n.name}`,
145
+ children: /* @__PURE__ */ i(c, { className: "w-4 h-4" })
146
+ }),
147
+ m && /* @__PURE__ */ i("button", {
148
+ type: "button",
149
+ onClick: x,
150
+ className: "inline-flex h-control-md w-[var(--size-controlHeight-md)] items-center justify-center rounded-button text-text-secondary motion-safe:transition-colors hover:bg-status-error-bg/10 hover:text-status-error-text sm:h-control-sm sm:w-[var(--size-controlHeight-sm)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-bg-surface",
151
+ title: "Delete Workflow",
152
+ "aria-label": `Delete workflow ${n.name}`,
153
+ children: /* @__PURE__ */ i(l, { className: "w-4 h-4" })
154
+ })
155
+ ]
137
156
  })]
138
157
  }),
139
158
  n.description && /* @__PURE__ */ i("p", {
@@ -146,12 +165,12 @@ var s = ({ className: e }) => /* @__PURE__ */ i("svg", {
146
165
  className: "flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1",
147
166
  children: [/* @__PURE__ */ a("span", {
148
167
  className: "flex items-center gap-1",
149
- title: `${h} linked DataSinks`,
168
+ title: `${_} linked DataSinks`,
150
169
  children: [
151
170
  /* @__PURE__ */ i(u, { className: "w-3.5 h-3.5 shrink-0" }),
152
- h,
171
+ _,
153
172
  " DataSink",
154
- h === 1 ? "" : "s"
173
+ _ === 1 ? "" : "s"
155
174
  ]
156
175
  }), /* @__PURE__ */ a("span", {
157
176
  className: "flex items-center gap-1",
@@ -160,7 +179,7 @@ var s = ({ className: e }) => /* @__PURE__ */ i("svg", {
160
179
  })]
161
180
  }), /* @__PURE__ */ i("span", {
162
181
  title: n.lastExecutedAt ? `Last executed: ${new Date(n.lastExecutedAt).toLocaleString()}` : "Never executed",
163
- children: g(n.lastExecutedAt)
182
+ children: v(n.lastExecutedAt)
164
183
  })]
165
184
  })
166
185
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"WorkflowCard.js","names":[],"sources":["../../../../src/bigconsole/components/workflow/WorkflowCard.tsx"],"sourcesContent":["/**\n * WorkflowCard Component\n *\n * Card component displaying a FluidGrids Workflow summary.\n * Shows name, status, linked DataSinks count, and execution stats.\n *\n * Emphasis (BOFF-5634): the card is a tile inside the list page's `primary`\n * results section, so it sits on `bg-bg-elevated` — one step above the section\n * surface — rather than repeating the generic panel treatment used by every\n * other block in the module. The old 96px empty media band carried no\n * information and out-weighed the title; it is now a compact identity strip.\n */\n\nimport { memo, useCallback } from 'react';\nimport { formatDistanceToNow } from 'date-fns';\nimport { WorkflowStatusBadge } from './WorkflowStatusBadge';\nimport { SECTION_FOCUS_RING } from '../common';\nimport type { FluidGridsWorkflow } from '../../types';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface WorkflowCardProps {\n workflow: FluidGridsWorkflow;\n onView?: (id: string) => void;\n onEdit?: (id: string) => void;\n onDelete?: (id: string) => void;\n}\n\n// ============================================================================\n// ICONS\n// ============================================================================\n\nconst WorkflowIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={1.5} d=\"M13 10V3L4 14h7v7l9-11h-7z\" />\n </svg>\n);\n\nconst EditIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z\"\n />\n </svg>\n);\n\nconst DeleteIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\"\n />\n </svg>\n);\n\nconst DataSinkIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4\"\n />\n </svg>\n);\n\nconst PlayIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z\"\n />\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M21 12a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n);\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport const WorkflowCard = memo(function WorkflowCard({ workflow, onView, onEdit, onDelete }: WorkflowCardProps) {\n // Calculate linked DataSinks count\n const linkedDataSinkCount = workflow.dataSinkLinks?.length ?? workflow.linkedDataSinkCount ?? 0;\n\n // Format last executed time\n const formatLastExecuted = useCallback((dateStr: string | null): string => {\n if (!dateStr) return 'Never';\n try {\n const date = new Date(dateStr);\n return formatDistanceToNow(date, { addSuffix: true });\n } catch {\n return 'Unknown';\n }\n }, []);\n\n // Event handlers\n const handleCardClick = useCallback(() => {\n onView?.(workflow.id);\n }, [onView, workflow.id]);\n\n const handleEditClick = useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation();\n onEdit?.(workflow.id);\n },\n [onEdit, workflow.id]\n );\n\n const handleDeleteClick = useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation();\n onDelete?.(workflow.id);\n },\n [onDelete, workflow.id]\n );\n\n // The title is the real \"open this workflow\" control. The tile itself must NOT\n // carry role=\"button\": that role makes its subtree presentational, which would\n // drop the Edit/Delete buttons and the status badge out of the accessibility\n // tree. The tile keeps a plain click handler as a pointer convenience only.\n const handleTitleClick = useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation();\n onView?.(workflow.id);\n },\n [onView, workflow.id]\n );\n\n return (\n <div\n onClick={handleCardClick}\n className=\"min-w-0 cursor-pointer overflow-hidden rounded-card border border-[var(--color-border-subtle)] bg-bg-elevated motion-safe:transition-[box-shadow,border-color] focus-within:border-action-primary-bg hover:border-action-primary-bg hover:shadow-[var(--shadow-elevation-1)]\"\n >\n {/* Identity strip: what kind of thing this is + its status, at a glance */}\n <div className=\"flex items-center justify-between gap-icon-gap border-b border-[var(--color-border-subtle)] bg-bg-sunken px-3 py-2\">\n <span className=\"flex min-w-0 items-center gap-icon-gap text-xs font-medium text-text-secondary\">\n <WorkflowIcon className=\"w-4 h-4 shrink-0\" />\n <span className=\"truncate\">Workflow</span>\n </span>\n <WorkflowStatusBadge status={workflow.status} size=\"sm\" />\n </div>\n\n {/* Content */}\n <div className=\"p-card-padding\">\n {/* Title and Actions */}\n <div className=\"mb-2 flex items-start justify-between gap-icon-gap\">\n <div className=\"min-w-0 flex-1\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n <button\n type=\"button\"\n onClick={handleTitleClick}\n aria-label={`Open workflow ${workflow.name}`}\n className={`line-clamp-2 rounded-button text-left ${SECTION_FOCUS_RING}`}\n >\n {workflow.name}\n </button>\n </h3>\n <p className=\"mt-0.5 truncate font-mono text-xs text-text-secondary\">{workflow.key}</p>\n </div>\n\n <div className=\"flex shrink-0 items-center gap-1\">\n {onEdit && (\n <button\n type=\"button\"\n onClick={handleEditClick}\n className={`inline-flex h-control-md w-[var(--size-controlHeight-md)] items-center justify-center rounded-button text-text-secondary motion-safe:transition-colors hover:bg-bg-sunken hover:text-text-primary sm:h-control-sm sm:w-[var(--size-controlHeight-sm)] ${SECTION_FOCUS_RING}`}\n title=\"Edit Workflow\"\n aria-label={`Edit workflow ${workflow.name}`}\n >\n <EditIcon className=\"w-4 h-4\" />\n </button>\n )}\n {onDelete && (\n <button\n type=\"button\"\n onClick={handleDeleteClick}\n className={`inline-flex h-control-md w-[var(--size-controlHeight-md)] items-center justify-center rounded-button text-text-secondary motion-safe:transition-colors hover:bg-status-error-bg/10 hover:text-status-error-text sm:h-control-sm sm:w-[var(--size-controlHeight-sm)] ${SECTION_FOCUS_RING}`}\n title=\"Delete Workflow\"\n aria-label={`Delete workflow ${workflow.name}`}\n >\n <DeleteIcon className=\"w-4 h-4\" />\n </button>\n )}\n </div>\n </div>\n\n {/* Description */}\n {workflow.description && (\n <p className=\"mb-3 line-clamp-2 text-xs text-text-secondary\">{workflow.description}</p>\n )}\n\n {/* Stats Footer */}\n <div className=\"flex flex-wrap items-center justify-between gap-x-3 gap-y-1 text-xs text-text-muted\">\n <div className=\"flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1\">\n {/* DataSinks Count */}\n <span className=\"flex items-center gap-1\" title={`${linkedDataSinkCount} linked DataSinks`}>\n <DataSinkIcon className=\"w-3.5 h-3.5 shrink-0\" />\n {linkedDataSinkCount} DataSink{linkedDataSinkCount !== 1 ? 's' : ''}\n </span>\n\n {/* Execution Count */}\n <span className=\"flex items-center gap-1\" title={`${workflow.executionCount} executions`}>\n <PlayIcon className=\"w-3.5 h-3.5 shrink-0\" />\n {workflow.executionCount}\n </span>\n </div>\n\n {/* Last Executed */}\n <span\n title={\n workflow.lastExecutedAt\n ? `Last executed: ${new Date(workflow.lastExecutedAt).toLocaleString()}`\n : 'Never executed'\n }\n >\n {formatLastExecuted(workflow.lastExecutedAt)}\n </span>\n </div>\n </div>\n </div>\n );\n});\n\nexport default WorkflowCard;\n"],"mappings":"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"}
1
+ {"version":3,"file":"WorkflowCard.js","names":[],"sources":["../../../../src/bigconsole/components/workflow/WorkflowCard.tsx"],"sourcesContent":["/**\n * WorkflowCard Component\n *\n * Card component displaying a FluidGrids Workflow summary.\n * Shows name, status, linked DataSinks count, and execution stats.\n *\n * Emphasis (BOFF-5634): the card is a tile inside the list page's `primary`\n * results section, so it sits on `bg-bg-elevated` — one step above the section\n * surface — rather than repeating the generic panel treatment used by every\n * other block in the module. The old 96px empty media band carried no\n * information and out-weighed the title; it is now a compact identity strip.\n */\n\nimport { memo, useCallback } from 'react';\nimport { formatDistanceToNow } from 'date-fns';\nimport { WorkflowStatusBadge } from './WorkflowStatusBadge';\nimport { SECTION_FOCUS_RING } from '../common';\nimport type { FluidGridsWorkflow } from '../../types';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface WorkflowCardProps {\n workflow: FluidGridsWorkflow;\n onView?: (id: string) => void;\n onEdit?: (id: string) => void;\n onDelete?: (id: string) => void;\n /**\n * Trigger a run. Omitted when the actor lacks `bigconsole-workflow:execute`,\n * so the affordance is absent rather than present-and-refused.\n */\n onRun?: (id: string) => void;\n /** True while this workflow's run request is in flight. */\n isRunning?: boolean;\n}\n\n// ============================================================================\n// ICONS\n// ============================================================================\n\nconst WorkflowIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={1.5} d=\"M13 10V3L4 14h7v7l9-11h-7z\" />\n </svg>\n);\n\nconst EditIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z\"\n />\n </svg>\n);\n\nconst DeleteIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\"\n />\n </svg>\n);\n\nconst DataSinkIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4\"\n />\n </svg>\n);\n\nconst PlayIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z\"\n />\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M21 12a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n);\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport const WorkflowCard = memo(function WorkflowCard({\n workflow,\n onView,\n onEdit,\n onDelete,\n onRun,\n isRunning = false,\n}: WorkflowCardProps) {\n // Calculate linked DataSinks count\n const linkedDataSinkCount = workflow.dataSinkLinks?.length ?? workflow.linkedDataSinkCount ?? 0;\n\n // Format last executed time\n const formatLastExecuted = useCallback((dateStr: string | null): string => {\n if (!dateStr) return 'Never';\n try {\n const date = new Date(dateStr);\n return formatDistanceToNow(date, { addSuffix: true });\n } catch {\n return 'Unknown';\n }\n }, []);\n\n // Event handlers\n const handleCardClick = useCallback(() => {\n onView?.(workflow.id);\n }, [onView, workflow.id]);\n\n const handleEditClick = useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation();\n onEdit?.(workflow.id);\n },\n [onEdit, workflow.id]\n );\n\n const handleDeleteClick = useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation();\n onDelete?.(workflow.id);\n },\n [onDelete, workflow.id]\n );\n\n const handleRunClick = useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation();\n if (isRunning) return;\n onRun?.(workflow.id);\n },\n [isRunning, onRun, workflow.id]\n );\n\n // The title is the real \"open this workflow\" control. The tile itself must NOT\n // carry role=\"button\": that role makes its subtree presentational, which would\n // drop the Edit/Delete buttons and the status badge out of the accessibility\n // tree. The tile keeps a plain click handler as a pointer convenience only.\n const handleTitleClick = useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation();\n onView?.(workflow.id);\n },\n [onView, workflow.id]\n );\n\n return (\n <div\n onClick={handleCardClick}\n className=\"min-w-0 cursor-pointer overflow-hidden rounded-card border border-[var(--color-border-subtle)] bg-bg-elevated motion-safe:transition-[box-shadow,border-color] focus-within:border-action-primary-bg hover:border-action-primary-bg hover:shadow-[var(--shadow-elevation-1)]\"\n >\n {/* Identity strip: what kind of thing this is + its status, at a glance */}\n <div className=\"flex items-center justify-between gap-icon-gap border-b border-[var(--color-border-subtle)] bg-bg-sunken px-3 py-2\">\n <span className=\"flex min-w-0 items-center gap-icon-gap text-xs font-medium text-text-secondary\">\n <WorkflowIcon className=\"w-4 h-4 shrink-0\" />\n <span className=\"truncate\">Workflow</span>\n </span>\n <WorkflowStatusBadge status={workflow.status} size=\"sm\" />\n </div>\n\n {/* Content */}\n <div className=\"p-card-padding\">\n {/* Title and Actions */}\n <div className=\"mb-2 flex items-start justify-between gap-icon-gap\">\n <div className=\"min-w-0 flex-1\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n <button\n type=\"button\"\n onClick={handleTitleClick}\n aria-label={`Open workflow ${workflow.name}`}\n className={`line-clamp-2 rounded-button text-left ${SECTION_FOCUS_RING}`}\n >\n {workflow.name}\n </button>\n </h3>\n <p className=\"mt-0.5 truncate font-mono text-xs text-text-secondary\">{workflow.key}</p>\n </div>\n\n <div className=\"flex shrink-0 items-center gap-1\">\n {onRun && (\n <button\n type=\"button\"\n onClick={handleRunClick}\n disabled={isRunning}\n aria-busy={isRunning}\n className={`inline-flex h-control-md w-[var(--size-controlHeight-md)] items-center justify-center rounded-button text-text-secondary motion-safe:transition-colors hover:bg-bg-sunken hover:text-text-primary disabled:cursor-not-allowed disabled:opacity-60 sm:h-control-sm sm:w-[var(--size-controlHeight-sm)] ${SECTION_FOCUS_RING}`}\n title={isRunning ? 'Running…' : 'Run Workflow'}\n aria-label={isRunning ? `Running workflow ${workflow.name}` : `Run workflow ${workflow.name}`}\n >\n <PlayIcon className={`w-4 h-4 ${isRunning ? 'motion-safe:animate-pulse' : ''}`} />\n </button>\n )}\n {onEdit && (\n <button\n type=\"button\"\n onClick={handleEditClick}\n className={`inline-flex h-control-md w-[var(--size-controlHeight-md)] items-center justify-center rounded-button text-text-secondary motion-safe:transition-colors hover:bg-bg-sunken hover:text-text-primary sm:h-control-sm sm:w-[var(--size-controlHeight-sm)] ${SECTION_FOCUS_RING}`}\n title=\"Edit Workflow\"\n aria-label={`Edit workflow ${workflow.name}`}\n >\n <EditIcon className=\"w-4 h-4\" />\n </button>\n )}\n {onDelete && (\n <button\n type=\"button\"\n onClick={handleDeleteClick}\n className={`inline-flex h-control-md w-[var(--size-controlHeight-md)] items-center justify-center rounded-button text-text-secondary motion-safe:transition-colors hover:bg-status-error-bg/10 hover:text-status-error-text sm:h-control-sm sm:w-[var(--size-controlHeight-sm)] ${SECTION_FOCUS_RING}`}\n title=\"Delete Workflow\"\n aria-label={`Delete workflow ${workflow.name}`}\n >\n <DeleteIcon className=\"w-4 h-4\" />\n </button>\n )}\n </div>\n </div>\n\n {/* Description */}\n {workflow.description && (\n <p className=\"mb-3 line-clamp-2 text-xs text-text-secondary\">{workflow.description}</p>\n )}\n\n {/* Stats Footer */}\n <div className=\"flex flex-wrap items-center justify-between gap-x-3 gap-y-1 text-xs text-text-muted\">\n <div className=\"flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1\">\n {/* DataSinks Count */}\n <span className=\"flex items-center gap-1\" title={`${linkedDataSinkCount} linked DataSinks`}>\n <DataSinkIcon className=\"w-3.5 h-3.5 shrink-0\" />\n {linkedDataSinkCount} DataSink{linkedDataSinkCount !== 1 ? 's' : ''}\n </span>\n\n {/* Execution Count */}\n <span className=\"flex items-center gap-1\" title={`${workflow.executionCount} executions`}>\n <PlayIcon className=\"w-3.5 h-3.5 shrink-0\" />\n {workflow.executionCount}\n </span>\n </div>\n\n {/* Last Executed */}\n <span\n title={\n workflow.lastExecutedAt\n ? `Last executed: ${new Date(workflow.lastExecutedAt).toLocaleString()}`\n : 'Never executed'\n }\n >\n {formatLastExecuted(workflow.lastExecutedAt)}\n </span>\n </div>\n </div>\n </div>\n );\n});\n\nexport default WorkflowCard;\n"],"mappings":"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"}
@@ -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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"}