@burdenoff/microfe-bigconsole 2026.805.3 → 2026.812.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.
Files changed (247) hide show
  1. package/dist/bigconsole/components/DrilldownDashboardRenderer.js +118 -106
  2. package/dist/bigconsole/components/DrilldownDashboardRenderer.js.map +1 -1
  3. package/dist/bigconsole/components/DrilldownModal.js +54 -50
  4. package/dist/bigconsole/components/DrilldownModal.js.map +1 -1
  5. package/dist/bigconsole/components/builder/BuilderCanvas.js +2 -2
  6. package/dist/bigconsole/components/builder/BuilderCanvas.js.map +1 -1
  7. package/dist/bigconsole/components/builder/BuilderTopbar.js +3 -3
  8. package/dist/bigconsole/components/builder/BuilderTopbar.js.map +1 -1
  9. package/dist/bigconsole/components/builder/inspector/AdvancedSection.js +24 -25
  10. package/dist/bigconsole/components/builder/inspector/AdvancedSection.js.map +1 -1
  11. package/dist/bigconsole/components/builder/inspector/AppearanceSection.js +40 -29
  12. package/dist/bigconsole/components/builder/inspector/AppearanceSection.js.map +1 -1
  13. package/dist/bigconsole/components/builder/inspector/DataBindingSection.js +135 -97
  14. package/dist/bigconsole/components/builder/inspector/DataBindingSection.js.map +1 -1
  15. package/dist/bigconsole/components/builder/inspector/GeneralSection.js +58 -44
  16. package/dist/bigconsole/components/builder/inspector/GeneralSection.js.map +1 -1
  17. package/dist/bigconsole/components/common/Section.js +70 -0
  18. package/dist/bigconsole/components/common/Section.js.map +1 -0
  19. package/dist/bigconsole/components/common/index.js +2 -0
  20. package/dist/bigconsole/components/common/sectionStyles.js +42 -0
  21. package/dist/bigconsole/components/common/sectionStyles.js.map +1 -0
  22. package/dist/bigconsole/components/dashboard/CreateDashboardDialog.js +209 -193
  23. package/dist/bigconsole/components/dashboard/CreateDashboardDialog.js.map +1 -1
  24. package/dist/bigconsole/components/dashboard/DashboardCanvas.js +213 -231
  25. package/dist/bigconsole/components/dashboard/DashboardCanvas.js.map +1 -1
  26. package/dist/bigconsole/components/dashboard/DashboardGrid.js +12 -18
  27. package/dist/bigconsole/components/dashboard/DashboardGrid.js.map +1 -1
  28. package/dist/bigconsole/components/dashboard/DashboardToolbar.js +109 -102
  29. package/dist/bigconsole/components/dashboard/DashboardToolbar.js.map +1 -1
  30. package/dist/bigconsole/components/dashboard/DrilldownBreadcrumb.js +81 -70
  31. package/dist/bigconsole/components/dashboard/DrilldownBreadcrumb.js.map +1 -1
  32. package/dist/bigconsole/components/dashboard/ExportDashboardDialog.js +125 -159
  33. package/dist/bigconsole/components/dashboard/ExportDashboardDialog.js.map +1 -1
  34. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterChip.js +25 -23
  35. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterChip.js.map +1 -1
  36. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterEditor.js +3 -3
  37. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterEditor.js.map +1 -1
  38. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterPresetSelector.js +5 -3
  39. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterPresetSelector.js.map +1 -1
  40. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/GlobalFiltersBar.js +170 -284
  41. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/GlobalFiltersBar.js.map +1 -1
  42. package/dist/bigconsole/components/dashboard/ImportDashboardDialog.js +147 -151
  43. package/dist/bigconsole/components/dashboard/ImportDashboardDialog.js.map +1 -1
  44. package/dist/bigconsole/components/dashboard/LiveBoardBanner.js +45 -53
  45. package/dist/bigconsole/components/dashboard/LiveBoardBanner.js.map +1 -1
  46. package/dist/bigconsole/components/dashboard/PageTabsManager/PageTabsManager.js +157 -148
  47. package/dist/bigconsole/components/dashboard/PageTabsManager/PageTabsManager.js.map +1 -1
  48. package/dist/bigconsole/components/dashboard/SavedViewsDropdown/SavedViewsDropdown.js +17 -8
  49. package/dist/bigconsole/components/dashboard/SavedViewsDropdown/SavedViewsDropdown.js.map +1 -1
  50. package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.js +101 -95
  51. package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.js.map +1 -1
  52. package/dist/bigconsole/components/dashboard/StoreTemplatePicker.js +20 -18
  53. package/dist/bigconsole/components/dashboard/StoreTemplatePicker.js.map +1 -1
  54. package/dist/bigconsole/components/dashboard/TemplateSetupBanner.js +26 -58
  55. package/dist/bigconsole/components/dashboard/TemplateSetupBanner.js.map +1 -1
  56. package/dist/bigconsole/components/dashboard/WidgetPalette.js +307 -231
  57. package/dist/bigconsole/components/dashboard/WidgetPalette.js.map +1 -1
  58. package/dist/bigconsole/components/datasink/CacheTTLSelector.js +29 -29
  59. package/dist/bigconsole/components/datasink/CacheTTLSelector.js.map +1 -1
  60. package/dist/bigconsole/components/datasink/DataSinkCard.js +78 -79
  61. package/dist/bigconsole/components/datasink/DataSinkCard.js.map +1 -1
  62. package/dist/bigconsole/components/datasink/DataSinkDataViewer.js +33 -34
  63. package/dist/bigconsole/components/datasink/DataSinkDataViewer.js.map +1 -1
  64. package/dist/bigconsole/components/datasink/DataSinkDeleteDialog.js +79 -56
  65. package/dist/bigconsole/components/datasink/DataSinkDeleteDialog.js.map +1 -1
  66. package/dist/bigconsole/components/datasink/DataSinkRefreshDialog.js +75 -65
  67. package/dist/bigconsole/components/datasink/DataSinkRefreshDialog.js.map +1 -1
  68. package/dist/bigconsole/components/datasink/DataSinkStatusBadge.js +1 -1
  69. package/dist/bigconsole/components/datasink/DataSinkStatusBadge.js.map +1 -1
  70. package/dist/bigconsole/components/datasink/DataSinkTableViewer.js +20 -19
  71. package/dist/bigconsole/components/datasink/DataSinkTableViewer.js.map +1 -1
  72. package/dist/bigconsole/components/datasink/ExportDataSinkDialog.js +127 -171
  73. package/dist/bigconsole/components/datasink/ExportDataSinkDialog.js.map +1 -1
  74. package/dist/bigconsole/components/datasink/ImportDataSinkDialog.js +263 -243
  75. package/dist/bigconsole/components/datasink/ImportDataSinkDialog.js.map +1 -1
  76. package/dist/bigconsole/components/datasink/LinkedParsersPanel.js +65 -66
  77. package/dist/bigconsole/components/datasink/LinkedParsersPanel.js.map +1 -1
  78. package/dist/bigconsole/components/datasink/LinkedWorkflowsPanel.js +121 -127
  79. package/dist/bigconsole/components/datasink/LinkedWorkflowsPanel.js.map +1 -1
  80. package/dist/bigconsole/components/datasink/RefreshTypeSelector.js +33 -26
  81. package/dist/bigconsole/components/datasink/RefreshTypeSelector.js.map +1 -1
  82. package/dist/bigconsole/components/datasink/SetupStateBadge.js +3 -3
  83. package/dist/bigconsole/components/datasink/SetupStateBadge.js.map +1 -1
  84. package/dist/bigconsole/components/datasink/WorkflowStatusPanel.js +95 -95
  85. package/dist/bigconsole/components/datasink/WorkflowStatusPanel.js.map +1 -1
  86. package/dist/bigconsole/components/parser/DataSinkSelector.js +154 -123
  87. package/dist/bigconsole/components/parser/DataSinkSelector.js.map +1 -1
  88. package/dist/bigconsole/components/parser/ExpressionEditor.js +3 -3
  89. package/dist/bigconsole/components/parser/ExpressionEditor.js.map +1 -1
  90. package/dist/bigconsole/components/parser/ImportParserDialog.js +235 -275
  91. package/dist/bigconsole/components/parser/ImportParserDialog.js.map +1 -1
  92. package/dist/bigconsole/components/parser/ParserCard.js +133 -114
  93. package/dist/bigconsole/components/parser/ParserCard.js.map +1 -1
  94. package/dist/bigconsole/components/parser/ParserSelector.js +191 -174
  95. package/dist/bigconsole/components/parser/ParserSelector.js.map +1 -1
  96. package/dist/bigconsole/components/parser/ParserStatusBadge.js +2 -2
  97. package/dist/bigconsole/components/parser/ParserStatusBadge.js.map +1 -1
  98. package/dist/bigconsole/components/parser/ParserTypeBadge.js +4 -4
  99. package/dist/bigconsole/components/parser/ParserTypeBadge.js.map +1 -1
  100. package/dist/bigconsole/components/pipeline/PipelineCard.js +134 -89
  101. package/dist/bigconsole/components/pipeline/PipelineCard.js.map +1 -1
  102. package/dist/bigconsole/components/pipeline/PipelineDeleteDialog.js +77 -64
  103. package/dist/bigconsole/components/pipeline/PipelineDeleteDialog.js.map +1 -1
  104. package/dist/bigconsole/components/pipeline/PipelineStatusBadge.js +7 -10
  105. package/dist/bigconsole/components/pipeline/PipelineStatusBadge.js.map +1 -1
  106. package/dist/bigconsole/components/preview/AiPreviewContent.js +31 -32
  107. package/dist/bigconsole/components/preview/AiPreviewContent.js.map +1 -1
  108. package/dist/bigconsole/components/preview/AiPreviewMiniPanel.js +152 -106
  109. package/dist/bigconsole/components/preview/AiPreviewMiniPanel.js.map +1 -1
  110. package/dist/bigconsole/components/preview/AiPreviewPanel.js +193 -152
  111. package/dist/bigconsole/components/preview/AiPreviewPanel.js.map +1 -1
  112. package/dist/bigconsole/components/preview/previewShared.js +41 -23
  113. package/dist/bigconsole/components/preview/previewShared.js.map +1 -1
  114. package/dist/bigconsole/components/widgets/ActionConfigDialog.js +342 -308
  115. package/dist/bigconsole/components/widgets/ActionConfigDialog.js.map +1 -1
  116. package/dist/bigconsole/components/widgets/DrilldownConfigPanel.js +262 -238
  117. package/dist/bigconsole/components/widgets/DrilldownConfigPanel.js.map +1 -1
  118. package/dist/bigconsole/components/widgets/PropertiesPanel.js +783 -867
  119. package/dist/bigconsole/components/widgets/PropertiesPanel.js.map +1 -1
  120. package/dist/bigconsole/components/widgets/WidgetErrorBoundary.js +7 -5
  121. package/dist/bigconsole/components/widgets/WidgetErrorBoundary.js.map +1 -1
  122. package/dist/bigconsole/components/widgets/WidgetHeader.js +7 -8
  123. package/dist/bigconsole/components/widgets/WidgetHeader.js.map +1 -1
  124. package/dist/bigconsole/components/widgets/WidgetRegistry.js +4 -4
  125. package/dist/bigconsole/components/widgets/WidgetRegistry.js.map +1 -1
  126. package/dist/bigconsole/components/widgets/WidgetWrapper.js +8 -7
  127. package/dist/bigconsole/components/widgets/WidgetWrapper.js.map +1 -1
  128. package/dist/bigconsole/components/widgets/adaptive-card/AdaptiveCardWidget.js +1 -1
  129. package/dist/bigconsole/components/widgets/adaptive-card/AdaptiveCardWidget.js.map +1 -1
  130. package/dist/bigconsole/components/widgets/calendar/CalendarWidget.js +2 -2
  131. package/dist/bigconsole/components/widgets/calendar/CalendarWidget.js.map +1 -1
  132. package/dist/bigconsole/components/widgets/chart/ChartConfig.js +11 -5
  133. package/dist/bigconsole/components/widgets/chart/ChartConfig.js.map +1 -1
  134. package/dist/bigconsole/components/widgets/comments/WidgetComments.js +19 -15
  135. package/dist/bigconsole/components/widgets/comments/WidgetComments.js.map +1 -1
  136. package/dist/bigconsole/components/widgets/event-logs/EventLogViewer.js +154 -102
  137. package/dist/bigconsole/components/widgets/event-logs/EventLogViewer.js.map +1 -1
  138. package/dist/bigconsole/components/widgets/gauge/CircularGauge.js +36 -32
  139. package/dist/bigconsole/components/widgets/gauge/CircularGauge.js.map +1 -1
  140. package/dist/bigconsole/components/widgets/gauge/GaugeConfig.js +85 -60
  141. package/dist/bigconsole/components/widgets/gauge/GaugeConfig.js.map +1 -1
  142. package/dist/bigconsole/components/widgets/gauge/GaugeWidget.js +64 -35
  143. package/dist/bigconsole/components/widgets/gauge/GaugeWidget.js.map +1 -1
  144. package/dist/bigconsole/components/widgets/gauge/LinearGauge.js +46 -45
  145. package/dist/bigconsole/components/widgets/gauge/LinearGauge.js.map +1 -1
  146. package/dist/bigconsole/components/widgets/heatmap/HeatmapConfig.js +35 -24
  147. package/dist/bigconsole/components/widgets/heatmap/HeatmapConfig.js.map +1 -1
  148. package/dist/bigconsole/components/widgets/iframe-widget/IframeWidget.js +2 -2
  149. package/dist/bigconsole/components/widgets/iframe-widget/IframeWidget.js.map +1 -1
  150. package/dist/bigconsole/components/widgets/kanban/KanbanWidget.js +4 -4
  151. package/dist/bigconsole/components/widgets/kanban/KanbanWidget.js.map +1 -1
  152. package/dist/bigconsole/components/widgets/kpi-comparison/KPIComparisonWidget.js +153 -90
  153. package/dist/bigconsole/components/widgets/kpi-comparison/KPIComparisonWidget.js.map +1 -1
  154. package/dist/bigconsole/components/widgets/list/ListConfig.js +8 -2
  155. package/dist/bigconsole/components/widgets/list/ListConfig.js.map +1 -1
  156. package/dist/bigconsole/components/widgets/list/ListItem.js +1 -1
  157. package/dist/bigconsole/components/widgets/list/ListItem.js.map +1 -1
  158. package/dist/bigconsole/components/widgets/map-widget/MapWidget.js +1 -1
  159. package/dist/bigconsole/components/widgets/map-widget/MapWidget.js.map +1 -1
  160. package/dist/bigconsole/components/widgets/metric-card/MetricCardConfig.js +14 -4
  161. package/dist/bigconsole/components/widgets/metric-card/MetricCardConfig.js.map +1 -1
  162. package/dist/bigconsole/components/widgets/metric-card/MetricCardWidget.js +112 -89
  163. package/dist/bigconsole/components/widgets/metric-card/MetricCardWidget.js.map +1 -1
  164. package/dist/bigconsole/components/widgets/metric-card/SparklineChart.js +21 -13
  165. package/dist/bigconsole/components/widgets/metric-card/SparklineChart.js.map +1 -1
  166. package/dist/bigconsole/components/widgets/metric-card/index.js +4 -4
  167. package/dist/bigconsole/components/widgets/pivot-table/PivotTableWidget.js +3 -3
  168. package/dist/bigconsole/components/widgets/pivot-table/PivotTableWidget.js.map +1 -1
  169. package/dist/bigconsole/components/widgets/progress/ProgressConfig.js +34 -25
  170. package/dist/bigconsole/components/widgets/progress/ProgressConfig.js.map +1 -1
  171. package/dist/bigconsole/components/widgets/progress/ProgressWidget.js +129 -114
  172. package/dist/bigconsole/components/widgets/progress/ProgressWidget.js.map +1 -1
  173. package/dist/bigconsole/components/widgets/retention/RetentionWidget.js +3 -3
  174. package/dist/bigconsole/components/widgets/retention/RetentionWidget.js.map +1 -1
  175. package/dist/bigconsole/components/widgets/states/EmptyState.js +6 -4
  176. package/dist/bigconsole/components/widgets/states/EmptyState.js.map +1 -1
  177. package/dist/bigconsole/components/widgets/states/ErrorState.js +7 -5
  178. package/dist/bigconsole/components/widgets/states/ErrorState.js.map +1 -1
  179. package/dist/bigconsole/components/widgets/states/LoadingState.js +5 -4
  180. package/dist/bigconsole/components/widgets/states/LoadingState.js.map +1 -1
  181. package/dist/bigconsole/components/widgets/table/TableConfig.js +10 -4
  182. package/dist/bigconsole/components/widgets/table/TableConfig.js.map +1 -1
  183. package/dist/bigconsole/components/widgets/table/TableHeader.js +1 -1
  184. package/dist/bigconsole/components/widgets/table/TableHeader.js.map +1 -1
  185. package/dist/bigconsole/components/widgets/table/TableWidget.js +1 -1
  186. package/dist/bigconsole/components/widgets/table/TableWidget.js.map +1 -1
  187. package/dist/bigconsole/components/widgets/text-widget/TextWidget.js +1 -1
  188. package/dist/bigconsole/components/widgets/text-widget/TextWidget.js.map +1 -1
  189. package/dist/bigconsole/components/widgets/timeline/TimelineWidget.js +3 -3
  190. package/dist/bigconsole/components/widgets/timeline/TimelineWidget.js.map +1 -1
  191. package/dist/bigconsole/components/widgets/utils/conditionalFormat.js +2 -2
  192. package/dist/bigconsole/components/widgets/utils/conditionalFormat.js.map +1 -1
  193. package/dist/bigconsole/components/widgets/utils/targetProgress.js +13 -0
  194. package/dist/bigconsole/components/widgets/utils/targetProgress.js.map +1 -0
  195. package/dist/bigconsole/components/widgets/webhook-config/WebhookConfigPanel.js +112 -85
  196. package/dist/bigconsole/components/widgets/webhook-config/WebhookConfigPanel.js.map +1 -1
  197. package/dist/bigconsole/components/widgets/widget-actions/WidgetActions.js +28 -20
  198. package/dist/bigconsole/components/widgets/widget-actions/WidgetActions.js.map +1 -1
  199. package/dist/bigconsole/components/workflow/WorkflowCard.js +92 -77
  200. package/dist/bigconsole/components/workflow/WorkflowCard.js.map +1 -1
  201. package/dist/bigconsole/components/workflow/WorkflowDeleteDialog.js +70 -51
  202. package/dist/bigconsole/components/workflow/WorkflowDeleteDialog.js.map +1 -1
  203. package/dist/bigconsole/components/workflow/WorkflowStatusBadge.js +16 -17
  204. package/dist/bigconsole/components/workflow/WorkflowStatusBadge.js.map +1 -1
  205. package/dist/bigconsole/customWidget/CustomWidgetBuilderSheet.js +6 -4
  206. package/dist/bigconsole/customWidget/CustomWidgetBuilderSheet.js.map +1 -1
  207. package/dist/bigconsole/customWidget/CustomWidgetProposalPanel.js +360 -338
  208. package/dist/bigconsole/customWidget/CustomWidgetProposalPanel.js.map +1 -1
  209. package/dist/bigconsole/hooks/useModalFocusTrap.js +53 -0
  210. package/dist/bigconsole/hooks/useModalFocusTrap.js.map +1 -0
  211. package/dist/bigconsole/pages/DashboardBuilderPage.js +209 -203
  212. package/dist/bigconsole/pages/DashboardBuilderPage.js.map +1 -1
  213. package/dist/bigconsole/pages/DashboardViewPage.js +126 -120
  214. package/dist/bigconsole/pages/DashboardViewPage.js.map +1 -1
  215. package/dist/bigconsole/pages/DashboardsPage.js +838 -800
  216. package/dist/bigconsole/pages/DashboardsPage.js.map +1 -1
  217. package/dist/bigconsole/pages/DataParsersPage.js +263 -315
  218. package/dist/bigconsole/pages/DataParsersPage.js.map +1 -1
  219. package/dist/bigconsole/pages/DataSinkBuilderPage.js +301 -341
  220. package/dist/bigconsole/pages/DataSinkBuilderPage.js.map +1 -1
  221. package/dist/bigconsole/pages/DataSinkViewPage.js +346 -339
  222. package/dist/bigconsole/pages/DataSinkViewPage.js.map +1 -1
  223. package/dist/bigconsole/pages/DataSinksPage.js +356 -458
  224. package/dist/bigconsole/pages/DataSinksPage.js.map +1 -1
  225. package/dist/bigconsole/pages/DataSourcesPage.js +7 -2
  226. package/dist/bigconsole/pages/DataSourcesPage.js.map +1 -1
  227. package/dist/bigconsole/pages/HomePage.js +186 -193
  228. package/dist/bigconsole/pages/HomePage.js.map +1 -1
  229. package/dist/bigconsole/pages/InsightsPage.js +887 -740
  230. package/dist/bigconsole/pages/InsightsPage.js.map +1 -1
  231. package/dist/bigconsole/pages/ParserBuilderPage.js +679 -637
  232. package/dist/bigconsole/pages/ParserBuilderPage.js.map +1 -1
  233. package/dist/bigconsole/pages/ParserViewPage.js +368 -342
  234. package/dist/bigconsole/pages/ParserViewPage.js.map +1 -1
  235. package/dist/bigconsole/pages/PipelineBuilderPage.js +537 -444
  236. package/dist/bigconsole/pages/PipelineBuilderPage.js.map +1 -1
  237. package/dist/bigconsole/pages/PipelineViewPage.js +412 -377
  238. package/dist/bigconsole/pages/PipelineViewPage.js.map +1 -1
  239. package/dist/bigconsole/pages/PipelinesPage.js +248 -298
  240. package/dist/bigconsole/pages/PipelinesPage.js.map +1 -1
  241. package/dist/bigconsole/pages/WidgetDemoPage.js +251 -153
  242. package/dist/bigconsole/pages/WidgetDemoPage.js.map +1 -1
  243. package/dist/bigconsole/pages/WorkflowViewPage.js +381 -371
  244. package/dist/bigconsole/pages/WorkflowViewPage.js.map +1 -1
  245. package/dist/bigconsole/pages/WorkflowsPage.js +171 -286
  246. package/dist/bigconsole/pages/WorkflowsPage.js.map +1 -1
  247. package/package.json +1 -1
@@ -1 +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 { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\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\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Builds a throwaway Widget so the preview can use the production renderer. */\nfunction toDraftWidget(proposal: CustomWidgetProposal, untitledLabel: string): 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 || untitledLabel,\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 { t } = useI18n();\n\n const previewCopy = useMemo<Record<ProposalPreviewState, { title: string; body: string }>>(\n () => ({\n loading: {\n title: t('bigconsole.widget.customWidget.preview.loading.title', 'Building preview…'),\n body: t(\n 'bigconsole.widget.customWidget.preview.loading.body',\n 'Running the parser against the selected DataSink.'\n ),\n },\n valid: {\n title: t('bigconsole.widget.customWidget.preview.valid.title', 'Preview'),\n body: '',\n },\n 'no-data': {\n title: t('bigconsole.widget.customWidget.preview.noData.title', 'No data returned'),\n body: t(\n 'bigconsole.widget.customWidget.preview.noData.body',\n 'The binding is valid but the parser returned no rows. The widget will render empty until the sink has data.'\n ),\n },\n 'field-mismatch': {\n title: t('bigconsole.widget.customWidget.preview.fieldMismatch.title', 'Field mismatch'),\n body: t(\n 'bigconsole.widget.customWidget.preview.fieldMismatch.body',\n 'Some mapped fields are not present in the parser output. Fix the mapping below before approving.'\n ),\n },\n 'access-error': {\n title: t('bigconsole.widget.customWidget.preview.accessError.title', 'Cannot read this data'),\n body: t(\n 'bigconsole.widget.customWidget.preview.accessError.body',\n 'You do not have access to the proposed DataSink or parser in this workspace.'\n ),\n },\n }),\n [t]\n );\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(\n error instanceof Error\n ? error.message\n : t('bigconsole.widget.customWidget.error.couldNotSaveWidget', 'Could not save the widget.')\n );\n } finally {\n setIsApproving(false);\n }\n }, [proposal, validation.canApprove, onApprove, onClose, t]);\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={t('bigconsole.widget.customWidget.panel.ariaLabel', '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 {t('bigconsole.widget.customWidget.prompt.label', '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={t(\n 'bigconsole.widget.customWidget.prompt.placeholder',\n 'e.g. a funnel of signup conversion stages'\n )}\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\n ? t('bigconsole.widget.customWidget.action.regenerate', 'Regenerate')\n : t('bigconsole.widget.customWidget.action.generateProposal', '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 {t('bigconsole.widget.customWidget.action.reject', '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 {t(\n 'bigconsole.widget.customWidget.empty',\n '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.'\n )}\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\">{previewCopy[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\">{previewCopy.loading.body}</span>\n </div>\n ) : previewState === 'valid' ? (\n <div className=\"h-[200px]\">\n <WidgetErrorBoundary widgetId={`preview-${proposal.id}`}>\n <BigConsoleRenderer\n widget={toDraftWidget(\n proposal,\n t('bigconsole.widget.customWidget.untitledWidget', 'Untitled widget')\n )}\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\">{previewCopy[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 {t('bigconsole.widget.customWidget.action.retry', '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\">\n {t('bigconsole.widget.customWidget.proposal.heading', 'Proposal')}\n </h3>\n\n <div className=\"flex flex-col gap-1\">\n <Label htmlFor=\"custom-widget-title\">{t('bigconsole.widget.customWidget.field.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\">\n {t('bigconsole.widget.customWidget.field.widgetType', 'Widget type')}\n </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\">\n {t('bigconsole.widget.customWidget.field.dataSink', 'DataSink')}\n </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=\"\">\n {t('bigconsole.widget.customWidget.field.selectDataSink', 'Select a DataSink…')}\n </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\">{t('bigconsole.widget.customWidget.field.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=\"\">{t('bigconsole.widget.customWidget.field.noParser', '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\">\n {t('bigconsole.widget.customWidget.fieldMapping.legend', 'Field mapping')}\n </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\">\n {t('bigconsole.widget.customWidget.fieldMapping.available', 'Available: {{fields}}', {\n fields: availableFields.join(', '),\n })}\n </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 {t('bigconsole.widget.customWidget.action.approveAndAdd', 'Approve & add to dashboard')}\n </Button>\n <Button variant=\"outline\" onClick={onClose} data-testid=\"custom-widget-cancel\">\n {t('bigconsole.widget.customWidget.action.cancel', 'Cancel')}\n </Button>\n </div>\n </>\n )}\n </section>\n );\n});\n\nexport default CustomWidgetProposalPanel;\n"],"mappings":"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"}
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 { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\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';\nimport { Section, SectionStack, type SectionTone } from '../components/common';\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\n/**\n * The preview is the thing being judged, so its tone tracks the binding state.\n * The copy itself is translated per render (`previewCopy` below); only the tone\n * ladder is static.\n */\nconst PREVIEW_TONES: Record<ProposalPreviewState, SectionTone> = {\n loading: 'neutral',\n valid: 'neutral',\n 'no-data': 'warning',\n 'field-mismatch': 'warning',\n 'access-error': 'danger',\n};\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Builds a throwaway Widget so the preview can use the production renderer. */\nfunction toDraftWidget(proposal: CustomWidgetProposal, untitledLabel: string): 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 || untitledLabel,\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 { t } = useI18n();\n\n const previewCopy = useMemo<Record<ProposalPreviewState, { title: string; body: string }>>(\n () => ({\n loading: {\n title: t('bigconsole.widget.customWidget.preview.loading.title', 'Building preview…'),\n body: t(\n 'bigconsole.widget.customWidget.preview.loading.body',\n 'Running the parser against the selected DataSink.'\n ),\n },\n valid: {\n title: t('bigconsole.widget.customWidget.preview.valid.title', 'Preview'),\n body: '',\n },\n 'no-data': {\n title: t('bigconsole.widget.customWidget.preview.noData.title', 'No data returned'),\n body: t(\n 'bigconsole.widget.customWidget.preview.noData.body',\n 'The binding is valid but the parser returned no rows. The widget will render empty until the sink has data.'\n ),\n },\n 'field-mismatch': {\n title: t('bigconsole.widget.customWidget.preview.fieldMismatch.title', 'Field mismatch'),\n body: t(\n 'bigconsole.widget.customWidget.preview.fieldMismatch.body',\n 'Some mapped fields are not present in the parser output. Fix the mapping below before approving.'\n ),\n },\n 'access-error': {\n title: t('bigconsole.widget.customWidget.preview.accessError.title', 'Cannot read this data'),\n body: t(\n 'bigconsole.widget.customWidget.preview.accessError.body',\n 'You do not have access to the proposed DataSink or parser in this workspace.'\n ),\n },\n }),\n [t]\n );\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(\n error instanceof Error\n ? error.message\n : t('bigconsole.widget.customWidget.error.couldNotSaveWidget', 'Could not save the widget.')\n );\n } finally {\n setIsApproving(false);\n }\n }, [proposal, validation.canApprove, onApprove, onClose, t]);\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={t('bigconsole.widget.customWidget.panel.ariaLabel', 'AI custom widget builder')}\n className=\"flex h-full w-full min-w-0 flex-col overflow-y-auto p-4\"\n >\n {/*\n Five stages — prompt → preview → validation → editable proposal →\n approval — used to be five bare divs separated by one uniform gap. Each\n is now a labelled section, and the preview (the thing the reviewer is\n actually judging) is the panel's single `primary` block (BOFF-5636).\n */}\n <SectionStack>\n {/* ---------- Prompt ---------- */}\n <Section\n title={t('bigconsole.widget.customWidget.prompt.label', 'Describe the widget you need')}\n description=\"Plain English. The assistant proposes a typed widget you can edit before anything is saved.\"\n icon={<Sparkles className=\"size-4\" aria-hidden=\"true\" />}\n actions={\n <div className=\"flex flex-wrap gap-2\">\n <Button\n data-testid=\"custom-widget-generate\"\n onClick={handlePropose}\n disabled={!prompt.trim() || isProposing}\n >\n {isProposing ? <Loader2 className=\"mr-2 size-4 animate-spin\" aria-hidden=\"true\" /> : null}\n {proposal\n ? t('bigconsole.widget.customWidget.action.regenerate', 'Regenerate')\n : t('bigconsole.widget.customWidget.action.generateProposal', '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 {t('bigconsole.widget.customWidget.action.reject', 'Reject')}\n </Button>\n ) : null}\n </div>\n }\n >\n <div className=\"flex flex-col gap-2\">\n <Label htmlFor=\"custom-widget-prompt\">What should it show?</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={t(\n 'bigconsole.widget.customWidget.prompt.placeholder',\n 'e.g. a funnel of signup conversion stages'\n )}\n rows={3}\n />\n </div>\n </Section>\n\n {!proposal ? (\n <Section emphasis=\"muted\" padding=\"compact\" title=\"No proposal yet\" headingLevel={3}>\n <p className=\"text-sm text-text-secondary\" data-testid=\"custom-widget-empty\">\n {t(\n 'bigconsole.widget.customWidget.empty',\n '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.'\n )}\n </p>\n </Section>\n ) : (\n <>\n {/* ---------- Preview ---------- */}\n <Section\n emphasis=\"primary\"\n tone={PREVIEW_TONES[previewState]}\n title={previewCopy[previewState].title}\n description=\"Rendered by the real widget renderer against the real parser output — what you approve is what you get.\"\n meta={\n <span\n data-testid=\"custom-widget-preview-state\"\n data-state={previewState}\n className=\"rounded-pill bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\"\n >\n {previewState}\n </span>\n }\n >\n <div\n data-testid=\"custom-widget-preview\"\n className=\"min-h-[220px] min-w-0 rounded-card border border-[var(--color-border-subtle)] 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\">{previewCopy.loading.body}</span>\n </div>\n ) : previewState === 'valid' ? (\n <div className=\"h-[200px]\">\n <WidgetErrorBoundary widgetId={`preview-${proposal.id}`}>\n <BigConsoleRenderer\n widget={toDraftWidget(\n proposal,\n t('bigconsole.widget.customWidget.untitledWidget', 'Untitled widget')\n )}\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-[var(--color-status-warning-text)]\" aria-hidden=\"true\" />\n <p className=\"text-sm text-text-secondary\">{previewCopy[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 {t('bigconsole.widget.customWidget.action.retry', 'Retry')}\n </Button>\n ) : null}\n </div>\n )}\n </div>\n\n {proposal.rationale ? (\n <p className=\"mt-2 text-xs text-text-secondary\" data-testid=\"custom-widget-rationale\">\n {proposal.rationale}\n </p>\n ) : null}\n </Section>\n\n {/* ---------- Validation ---------- */}\n {errors.length > 0 || warnings.length > 0 ? (\n <Section\n tone={errors.length > 0 ? 'danger' : 'warning'}\n padding=\"compact\"\n headingLevel={3}\n title={\n errors.length > 0\n ? `${errors.length} ${errors.length === 1 ? 'problem' : 'problems'} to fix before approving`\n : `${warnings.length} ${warnings.length === 1 ? 'warning' : 'warnings'}`\n }\n >\n <ul data-testid=\"custom-widget-issues\" className=\"flex min-w-0 flex-col gap-1\">\n {[...errors, ...warnings].map((issue) => (\n <li\n key={`${issue.code}-${issue.path ?? ''}`}\n data-severity={issue.severity}\n className={`break-words text-xs ${\n issue.severity === 'error'\n ? 'text-[var(--color-status-error-text)]'\n : 'text-[var(--color-status-warning-text)]'\n }`}\n >\n {issue.message}\n </li>\n ))}\n </ul>\n </Section>\n ) : null}\n\n {/* ---------- Editable proposal ---------- */}\n <Section\n title={t('bigconsole.widget.customWidget.proposal.heading', 'Proposal')}\n description=\"Every field is editable — the assistant proposes, you dispose.\"\n data-testid=\"custom-widget-proposal-fields\"\n >\n <div className=\"flex flex-col gap-3\">\n <div className=\"flex flex-col gap-1\">\n <Label htmlFor=\"custom-widget-title\">\n {t('bigconsole.widget.customWidget.field.title', 'Title')}\n </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\">\n {t('bigconsole.widget.customWidget.field.widgetType', 'Widget type')}\n </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-[var(--color-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\">\n {t('bigconsole.widget.customWidget.field.dataSink', 'DataSink')}\n </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-[var(--color-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=\"\">\n {t('bigconsole.widget.customWidget.field.selectDataSink', 'Select a DataSink…')}\n </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\">\n {t('bigconsole.widget.customWidget.field.parser', 'Parser')}\n </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-[var(--color-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=\"\">{t('bigconsole.widget.customWidget.field.noParser', '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\">\n {t('bigconsole.widget.customWidget.fieldMapping.legend', 'Field mapping')}\n </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=\"break-words text-xs text-text-secondary\">\n {t('bigconsole.widget.customWidget.fieldMapping.available', 'Available: {{fields}}', {\n fields: availableFields.join(', '),\n })}\n </p>\n ) : null}\n </fieldset>\n ) : null}\n </div>\n </Section>\n\n {/* ---------- Approval ---------- */}\n {approveError ? (\n <Section tone=\"danger\" padding=\"compact\" headingLevel={3} title=\"Could not add the widget\">\n <p\n role=\"alert\"\n className=\"break-words text-xs text-[var(--color-status-error-text)]\"\n data-testid=\"custom-widget-approve-error\"\n >\n {approveError}\n </p>\n </Section>\n ) : null}\n\n <div className=\"flex flex-wrap gap-2 border-t border-[var(--color-border-subtle)] 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 {t('bigconsole.widget.customWidget.action.approveAndAdd', 'Approve & add to dashboard')}\n </Button>\n <Button variant=\"outline\" onClick={onClose} data-testid=\"custom-widget-cancel\">\n {t('bigconsole.widget.customWidget.action.cancel', 'Cancel')}\n </Button>\n </div>\n </>\n )}\n </SectionStack>\n </section>\n );\n});\n\nexport default CustomWidgetProposalPanel;\n"],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ import { useEffect as e } from "react";
2
+ //#region src/bigconsole/hooks/useModalFocusTrap.ts
3
+ var t = [
4
+ "a[href]",
5
+ "button:not([disabled])",
6
+ "input:not([disabled])",
7
+ "select:not([disabled])",
8
+ "textarea:not([disabled])",
9
+ "[tabindex]:not([tabindex=\"-1\"])"
10
+ ].join(", ");
11
+ function n(e) {
12
+ return Array.from(e.querySelectorAll(t)).filter((e) => e.getAttribute("aria-hidden") !== "true" && e.getClientRects().length > 0);
13
+ }
14
+ function r({ isOpen: t, panelRef: r, onClose: i, initialFocusRef: a }) {
15
+ e(() => {
16
+ if (!t) return;
17
+ let e = (e) => {
18
+ if (e.key === "Escape") {
19
+ i();
20
+ return;
21
+ }
22
+ if (e.key !== "Tab") return;
23
+ let t = r.current;
24
+ if (!t) return;
25
+ let a = n(t), o = a[0], s = a[a.length - 1];
26
+ if (!o || !s) {
27
+ e.preventDefault(), t.focus();
28
+ return;
29
+ }
30
+ let c = document.activeElement, l = !t.contains(c);
31
+ (e.shiftKey ? c === o || l : c === s || l) && (e.preventDefault(), (e.shiftKey ? s : o).focus());
32
+ };
33
+ return document.addEventListener("keydown", e), () => document.removeEventListener("keydown", e);
34
+ }, [
35
+ t,
36
+ i,
37
+ r
38
+ ]), e(() => {
39
+ if (!t) return;
40
+ let e = document.activeElement instanceof HTMLElement ? document.activeElement : null;
41
+ return (a?.current ?? r.current)?.focus(), () => {
42
+ e?.isConnected && e.focus();
43
+ };
44
+ }, [
45
+ t,
46
+ a,
47
+ r
48
+ ]);
49
+ }
50
+ //#endregion
51
+ export { r as useModalFocusTrap };
52
+
53
+ //# sourceMappingURL=useModalFocusTrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useModalFocusTrap.js","names":[],"sources":["../../../src/bigconsole/hooks/useModalFocusTrap.ts"],"sourcesContent":["/**\n * useModalFocusTrap — the keyboard contract for BigConsole's hand-rolled modals.\n *\n * Several dialogs here predate the fe-libs `Dialog`/`AlertDialog` primitives and\n * paint their own scrim + panel. Those primitives wrap Radix and would give this\n * behaviour for free, but swapping them in changes mount/animation semantics, so\n * the dialogs that still hand-roll their overlay need the contract implemented\n * explicitly. Without it a modal is announced as modal while Tab quietly walks\n * out into the page behind the scrim.\n *\n * What this gives a dialog:\n * - focus moves into the panel on open, preferring an explicit entry control\n * (pass the Cancel button for destructive confirmations, so the control\n * under the keyboard is the safe one);\n * - Tab / Shift+Tab cycle inside the panel;\n * - focus returns to whatever opened the dialog when it closes;\n * - Escape closes.\n *\n * Extracted from PipelineDeleteDialog (BOFF-5635) so the sibling dialogs share\n * one implementation instead of three drifting copies.\n */\n\nimport { useEffect, type RefObject } from 'react';\n\nconst FOCUSABLE_SELECTOR = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(', ');\n\n/**\n * Focusable descendants in DOM order, minus anything not actually reachable —\n * `getClientRects()` drops `display:none` / `hidden` subtrees, which matters\n * because these panels render conditional blocks (warnings, previews) that come\n * and go while the dialog is open.\n */\nexport function getFocusableChildren(panel: HTMLElement): HTMLElement[] {\n return Array.from(panel.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter(\n (element) => element.getAttribute('aria-hidden') !== 'true' && element.getClientRects().length > 0\n );\n}\n\nexport interface ModalFocusTrapOptions {\n /** Whether the dialog is currently rendered. */\n isOpen: boolean;\n /** The panel element that owns the modal content. */\n panelRef: RefObject<HTMLElement | null>;\n /** Called on Escape. */\n onClose: () => void;\n /**\n * Control to focus on open. Defaults to the panel itself, which needs\n * `tabIndex={-1}` to be focusable.\n */\n initialFocusRef?: RefObject<HTMLElement | null>;\n}\n\nexport function useModalFocusTrap({ isOpen, panelRef, onClose, initialFocusRef }: ModalFocusTrapOptions): void {\n // Escape closes, Tab stays inside. The listener sits on `document` rather than\n // the panel so a Tab pressed while focus has drifted outside is still pulled\n // back in.\n useEffect(() => {\n if (!isOpen) return;\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n onClose();\n return;\n }\n if (event.key !== 'Tab') return;\n\n const panel = panelRef.current;\n if (!panel) return;\n\n const focusable = getFocusableChildren(panel);\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n\n // Nothing tabbable inside (defensive): park focus on the panel itself\n // rather than letting Tab escape to the page behind the scrim.\n if (!first || !last) {\n event.preventDefault();\n panel.focus();\n return;\n }\n\n const active = document.activeElement;\n const outside = !panel.contains(active);\n\n if (event.shiftKey ? active === first || outside : active === last || outside) {\n event.preventDefault();\n (event.shiftKey ? last : first).focus();\n }\n };\n\n document.addEventListener('keydown', onKeyDown);\n return () => document.removeEventListener('keydown', onKeyDown);\n }, [isOpen, onClose, panelRef]);\n\n // Move focus in on open, hand it back on close.\n useEffect(() => {\n if (!isOpen) return;\n\n const previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n (initialFocusRef?.current ?? panelRef.current)?.focus();\n\n return () => {\n // The trigger can be gone by now — deleting a row unmounts the button that\n // opened the dialog — in which case there is nothing to restore to.\n if (previouslyFocused?.isConnected) {\n previouslyFocused.focus();\n }\n };\n }, [isOpen, initialFocusRef, panelRef]);\n}\n"],"mappings":";;AAwBA,IAAM,IAAqB;CACzB;CACA;CACA;CACA;CACA;CACA;CACD,CAAC,KAAK,KAAK;AAQZ,SAAgB,EAAqB,GAAmC;AACtE,QAAO,MAAM,KAAK,EAAM,iBAA8B,EAAmB,CAAC,CAAC,QACxE,MAAY,EAAQ,aAAa,cAAc,KAAK,UAAU,EAAQ,gBAAgB,CAAC,SAAS,EAClG;;AAiBH,SAAgB,EAAkB,EAAE,WAAQ,aAAU,YAAS,sBAAgD;AA2C7G,CAvCA,QAAgB;AACd,MAAI,CAAC,EAAQ;EAEb,IAAM,KAAa,MAAyB;AAC1C,OAAI,EAAM,QAAQ,UAAU;AAC1B,OAAS;AACT;;AAEF,OAAI,EAAM,QAAQ,MAAO;GAEzB,IAAM,IAAQ,EAAS;AACvB,OAAI,CAAC,EAAO;GAEZ,IAAM,IAAY,EAAqB,EAAM,EACvC,IAAQ,EAAU,IAClB,IAAO,EAAU,EAAU,SAAS;AAI1C,OAAI,CAAC,KAAS,CAAC,GAAM;AAEnB,IADA,EAAM,gBAAgB,EACtB,EAAM,OAAO;AACb;;GAGF,IAAM,IAAS,SAAS,eAClB,IAAU,CAAC,EAAM,SAAS,EAAO;AAEvC,IAAI,EAAM,WAAW,MAAW,KAAS,IAAU,MAAW,KAAQ,OACpE,EAAM,gBAAgB,GACrB,EAAM,WAAW,IAAO,GAAO,OAAO;;AAK3C,SADA,SAAS,iBAAiB,WAAW,EAAU,QAClC,SAAS,oBAAoB,WAAW,EAAU;IAC9D;EAAC;EAAQ;EAAS;EAAS,CAAC,EAG/B,QAAgB;AACd,MAAI,CAAC,EAAQ;EAEb,IAAM,IAAoB,SAAS,yBAAyB,cAAc,SAAS,gBAAgB;AAGnG,UAFC,GAAiB,WAAW,EAAS,UAAU,OAAO,QAE1C;AAGX,GAAI,GAAmB,eACrB,EAAkB,OAAO;;IAG5B;EAAC;EAAQ;EAAiB;EAAS,CAAC"}