@miragon/mcp-toolkit-ui 2.7.0 → 2.8.0

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 (387) hide show
  1. package/README.md +7 -4
  2. package/dist/app/adapt-data-widget.d.ts +1 -22
  3. package/dist/app/adapt-data-widget.d.ts.map +1 -1
  4. package/dist/app/adapt-data-widget.js +1 -35
  5. package/dist/app/adapt-data-widget.js.map +1 -1
  6. package/dist/app/builder/BuilderBanners.js +1 -1
  7. package/dist/app/builder/BuilderBanners.js.map +1 -1
  8. package/dist/app/builder/CanvasRow.d.ts +4 -2
  9. package/dist/app/builder/CanvasRow.d.ts.map +1 -1
  10. package/dist/app/builder/CanvasRow.js +21 -7
  11. package/dist/app/builder/CanvasRow.js.map +1 -1
  12. package/dist/app/builder/CatalogueSheet.d.ts.map +1 -1
  13. package/dist/app/builder/CatalogueSheet.js +11 -4
  14. package/dist/app/builder/CatalogueSheet.js.map +1 -1
  15. package/dist/app/builder/CellPropsSheet.d.ts +7 -3
  16. package/dist/app/builder/CellPropsSheet.d.ts.map +1 -1
  17. package/dist/app/builder/CellPropsSheet.js +10 -3
  18. package/dist/app/builder/CellPropsSheet.js.map +1 -1
  19. package/dist/app/builder/DiscardDialog.d.ts +18 -0
  20. package/dist/app/builder/DiscardDialog.d.ts.map +1 -0
  21. package/dist/app/builder/DiscardDialog.js +18 -0
  22. package/dist/app/builder/DiscardDialog.js.map +1 -0
  23. package/dist/app/builder/LayoutSurface.d.ts +33 -9
  24. package/dist/app/builder/LayoutSurface.d.ts.map +1 -1
  25. package/dist/app/builder/LayoutSurface.js +74 -23
  26. package/dist/app/builder/LayoutSurface.js.map +1 -1
  27. package/dist/app/builder/PipelineStrip.d.ts +2 -1
  28. package/dist/app/builder/PipelineStrip.d.ts.map +1 -1
  29. package/dist/app/builder/PipelineStrip.js +26 -13
  30. package/dist/app/builder/PipelineStrip.js.map +1 -1
  31. package/dist/app/builder/SaveDialog.d.ts +9 -1
  32. package/dist/app/builder/SaveDialog.d.ts.map +1 -1
  33. package/dist/app/builder/SaveDialog.js +8 -2
  34. package/dist/app/builder/SaveDialog.js.map +1 -1
  35. package/dist/app/builder/Toolbar.d.ts +12 -4
  36. package/dist/app/builder/Toolbar.d.ts.map +1 -1
  37. package/dist/app/builder/Toolbar.js +18 -7
  38. package/dist/app/builder/Toolbar.js.map +1 -1
  39. package/dist/app/builder/Workspace.d.ts +23 -3
  40. package/dist/app/builder/Workspace.d.ts.map +1 -1
  41. package/dist/app/builder/Workspace.js +58 -8
  42. package/dist/app/builder/Workspace.js.map +1 -1
  43. package/dist/app/builder/builder-model.d.ts +143 -1
  44. package/dist/app/builder/builder-model.d.ts.map +1 -1
  45. package/dist/app/builder/builder-model.js +191 -1
  46. package/dist/app/builder/builder-model.js.map +1 -1
  47. package/dist/app/builder/reveal.d.ts +10 -0
  48. package/dist/app/builder/reveal.d.ts.map +1 -0
  49. package/dist/app/builder/reveal.js +10 -0
  50. package/dist/app/builder/reveal.js.map +1 -0
  51. package/dist/app/builder/use-builder-catalogue.d.ts +4 -1
  52. package/dist/app/builder/use-builder-catalogue.d.ts.map +1 -1
  53. package/dist/app/builder/use-builder-catalogue.js +44 -17
  54. package/dist/app/builder/use-builder-catalogue.js.map +1 -1
  55. package/dist/app/builder/use-builder-session.d.ts +30 -0
  56. package/dist/app/builder/use-builder-session.d.ts.map +1 -0
  57. package/dist/app/builder/use-builder-session.js +58 -0
  58. package/dist/app/builder/use-builder-session.js.map +1 -0
  59. package/dist/app/builder/use-display-resolver.d.ts +17 -0
  60. package/dist/app/builder/use-display-resolver.d.ts.map +1 -0
  61. package/dist/app/builder/use-display-resolver.js +16 -0
  62. package/dist/app/builder/use-display-resolver.js.map +1 -0
  63. package/dist/app/index.d.ts +8 -3
  64. package/dist/app/index.d.ts.map +1 -1
  65. package/dist/app/index.js +8 -2
  66. package/dist/app/index.js.map +1 -1
  67. package/dist/app/layout-builder.d.ts +13 -25
  68. package/dist/app/layout-builder.d.ts.map +1 -1
  69. package/dist/app/layout-builder.js +75 -81
  70. package/dist/app/layout-builder.js.map +1 -1
  71. package/dist/app/mcp-app-view-chrome.d.ts +12 -3
  72. package/dist/app/mcp-app-view-chrome.d.ts.map +1 -1
  73. package/dist/app/mcp-app-view-chrome.js +21 -5
  74. package/dist/app/mcp-app-view-chrome.js.map +1 -1
  75. package/dist/app/mcp-app-view-states.d.ts +11 -3
  76. package/dist/app/mcp-app-view-states.d.ts.map +1 -1
  77. package/dist/app/mcp-app-view-states.js +29 -8
  78. package/dist/app/mcp-app-view-states.js.map +1 -1
  79. package/dist/app/mcp-app-view.d.ts +33 -1
  80. package/dist/app/mcp-app-view.d.ts.map +1 -1
  81. package/dist/app/mcp-app-view.js +181 -127
  82. package/dist/app/mcp-app-view.js.map +1 -1
  83. package/dist/app/mcp-toolkit-app.d.ts +6 -1
  84. package/dist/app/mcp-toolkit-app.d.ts.map +1 -1
  85. package/dist/app/mcp-toolkit-app.js +8 -1
  86. package/dist/app/mcp-toolkit-app.js.map +1 -1
  87. package/dist/app/mcp-use-host-bridge.d.ts +15 -6
  88. package/dist/app/mcp-use-host-bridge.d.ts.map +1 -1
  89. package/dist/app/mcp-use-host-bridge.js +51 -22
  90. package/dist/app/mcp-use-host-bridge.js.map +1 -1
  91. package/dist/app/model-context.d.ts +71 -0
  92. package/dist/app/model-context.d.ts.map +1 -0
  93. package/dist/app/model-context.js +101 -0
  94. package/dist/app/model-context.js.map +1 -0
  95. package/dist/app/use-build-mode-focus.d.ts +33 -0
  96. package/dist/app/use-build-mode-focus.d.ts.map +1 -0
  97. package/dist/app/use-build-mode-focus.js +59 -0
  98. package/dist/app/use-build-mode-focus.js.map +1 -0
  99. package/dist/app/use-view-actions.d.ts +31 -0
  100. package/dist/app/use-view-actions.d.ts.map +1 -0
  101. package/dist/app/use-view-actions.js +81 -0
  102. package/dist/app/use-view-actions.js.map +1 -0
  103. package/dist/app/use-view-lifecycle.d.ts +4 -0
  104. package/dist/app/use-view-lifecycle.d.ts.map +1 -1
  105. package/dist/app/use-view-lifecycle.js +2 -0
  106. package/dist/app/use-view-lifecycle.js.map +1 -1
  107. package/dist/app/view-lifecycle.d.ts +80 -4
  108. package/dist/app/view-lifecycle.d.ts.map +1 -1
  109. package/dist/app/view-lifecycle.js +90 -3
  110. package/dist/app/view-lifecycle.js.map +1 -1
  111. package/dist/app/widget-fixture.d.ts +10 -3
  112. package/dist/app/widget-fixture.d.ts.map +1 -1
  113. package/dist/app/widget-fixture.js +21 -8
  114. package/dist/app/widget-fixture.js.map +1 -1
  115. package/dist/app/widget-props-sheet.d.ts +3 -1
  116. package/dist/app/widget-props-sheet.d.ts.map +1 -1
  117. package/dist/app/widget-props-sheet.js +22 -10
  118. package/dist/app/widget-props-sheet.js.map +1 -1
  119. package/dist/app/widget-renderer.js +1 -1
  120. package/dist/app/widget-renderer.js.map +1 -1
  121. package/dist/components/DrillButton.d.ts +9 -6
  122. package/dist/components/DrillButton.d.ts.map +1 -1
  123. package/dist/components/DrillButton.js +8 -6
  124. package/dist/components/DrillButton.js.map +1 -1
  125. package/dist/components/EmptyState.d.ts.map +1 -1
  126. package/dist/components/EmptyState.js +4 -2
  127. package/dist/components/EmptyState.js.map +1 -1
  128. package/dist/components/ErrorState.d.ts +5 -2
  129. package/dist/components/ErrorState.d.ts.map +1 -1
  130. package/dist/components/ErrorState.js +25 -8
  131. package/dist/components/ErrorState.js.map +1 -1
  132. package/dist/components/FilterBar.d.ts +15 -4
  133. package/dist/components/FilterBar.d.ts.map +1 -1
  134. package/dist/components/FilterBar.js +11 -6
  135. package/dist/components/FilterBar.js.map +1 -1
  136. package/dist/components/KpiGrid.d.ts +66 -21
  137. package/dist/components/KpiGrid.d.ts.map +1 -1
  138. package/dist/components/KpiGrid.js +101 -34
  139. package/dist/components/KpiGrid.js.map +1 -1
  140. package/dist/components/ListFooter.d.ts +31 -4
  141. package/dist/components/ListFooter.d.ts.map +1 -1
  142. package/dist/components/ListFooter.js +30 -11
  143. package/dist/components/ListFooter.js.map +1 -1
  144. package/dist/components/PagedListFooter.d.ts +57 -0
  145. package/dist/components/PagedListFooter.d.ts.map +1 -0
  146. package/dist/components/PagedListFooter.js +27 -0
  147. package/dist/components/PagedListFooter.js.map +1 -0
  148. package/dist/components/Pills.d.ts +27 -7
  149. package/dist/components/Pills.d.ts.map +1 -1
  150. package/dist/components/Pills.js +21 -6
  151. package/dist/components/Pills.js.map +1 -1
  152. package/dist/components/SectionHeading.d.ts +3 -1
  153. package/dist/components/SectionHeading.d.ts.map +1 -1
  154. package/dist/components/SectionHeading.js +6 -2
  155. package/dist/components/SectionHeading.js.map +1 -1
  156. package/dist/components/Toast.d.ts +9 -3
  157. package/dist/components/Toast.d.ts.map +1 -1
  158. package/dist/components/Toast.js +31 -16
  159. package/dist/components/Toast.js.map +1 -1
  160. package/dist/components/WidgetHeader.d.ts +34 -10
  161. package/dist/components/WidgetHeader.d.ts.map +1 -1
  162. package/dist/components/WidgetHeader.js +11 -6
  163. package/dist/components/WidgetHeader.js.map +1 -1
  164. package/dist/hooks/index.d.ts +4 -4
  165. package/dist/hooks/index.d.ts.map +1 -1
  166. package/dist/hooks/index.js +1 -1
  167. package/dist/hooks/index.js.map +1 -1
  168. package/dist/hooks/tool-result-recovery-model.d.ts +155 -0
  169. package/dist/hooks/tool-result-recovery-model.d.ts.map +1 -0
  170. package/dist/hooks/tool-result-recovery-model.js +273 -0
  171. package/dist/hooks/tool-result-recovery-model.js.map +1 -0
  172. package/dist/hooks/use-tool-query.d.ts +56 -14
  173. package/dist/hooks/use-tool-query.d.ts.map +1 -1
  174. package/dist/hooks/use-tool-query.js +102 -25
  175. package/dist/hooks/use-tool-query.js.map +1 -1
  176. package/dist/hooks/use-tool-result-recovery.d.ts +47 -52
  177. package/dist/hooks/use-tool-result-recovery.d.ts.map +1 -1
  178. package/dist/hooks/use-tool-result-recovery.js +58 -120
  179. package/dist/hooks/use-tool-result-recovery.js.map +1 -1
  180. package/dist/hooks/use-view-data.d.ts +52 -10
  181. package/dist/hooks/use-view-data.d.ts.map +1 -1
  182. package/dist/hooks/use-view-data.js +81 -16
  183. package/dist/hooks/use-view-data.js.map +1 -1
  184. package/dist/hooks/use-view-tool-query.d.ts +12 -7
  185. package/dist/hooks/use-view-tool-query.d.ts.map +1 -1
  186. package/dist/hooks/use-view-tool-query.js +15 -14
  187. package/dist/hooks/use-view-tool-query.js.map +1 -1
  188. package/dist/index.d.ts +17 -10
  189. package/dist/index.d.ts.map +1 -1
  190. package/dist/index.js +18 -6
  191. package/dist/index.js.map +1 -1
  192. package/dist/lib/focus-return.d.ts +61 -0
  193. package/dist/lib/focus-return.d.ts.map +1 -0
  194. package/dist/lib/focus-return.js +46 -0
  195. package/dist/lib/focus-return.js.map +1 -0
  196. package/dist/lib/parse-tool-result.d.ts +23 -1
  197. package/dist/lib/parse-tool-result.d.ts.map +1 -1
  198. package/dist/lib/parse-tool-result.js +38 -2
  199. package/dist/lib/parse-tool-result.js.map +1 -1
  200. package/dist/lib/toast-model.d.ts +41 -1
  201. package/dist/lib/toast-model.d.ts.map +1 -1
  202. package/dist/lib/toast-model.js +67 -1
  203. package/dist/lib/toast-model.js.map +1 -1
  204. package/dist/lib/tone-utils.d.ts +58 -6
  205. package/dist/lib/tone-utils.d.ts.map +1 -1
  206. package/dist/lib/tone-utils.js +66 -4
  207. package/dist/lib/tone-utils.js.map +1 -1
  208. package/dist/lib/tool-query.d.ts +107 -0
  209. package/dist/lib/tool-query.d.ts.map +1 -0
  210. package/dist/lib/tool-query.js +182 -0
  211. package/dist/lib/tool-query.js.map +1 -0
  212. package/dist/lib/tool-result-observer.d.ts +108 -0
  213. package/dist/lib/tool-result-observer.d.ts.map +1 -0
  214. package/dist/lib/tool-result-observer.js +158 -0
  215. package/dist/lib/tool-result-observer.js.map +1 -0
  216. package/dist/lib/typography.d.ts +37 -0
  217. package/dist/lib/typography.d.ts.map +1 -0
  218. package/dist/lib/typography.js +41 -0
  219. package/dist/lib/typography.js.map +1 -0
  220. package/dist/lib/utils.d.ts.map +1 -1
  221. package/dist/lib/utils.js +11 -1
  222. package/dist/lib/utils.js.map +1 -1
  223. package/dist/locale/index.d.ts +1 -1
  224. package/dist/locale/index.d.ts.map +1 -1
  225. package/dist/locale/message-types.d.ts +117 -4
  226. package/dist/locale/message-types.d.ts.map +1 -1
  227. package/dist/locale/messages-de.d.ts.map +1 -1
  228. package/dist/locale/messages-de.js +44 -10
  229. package/dist/locale/messages-de.js.map +1 -1
  230. package/dist/locale/messages-en.d.ts.map +1 -1
  231. package/dist/locale/messages-en.js +44 -10
  232. package/dist/locale/messages-en.js.map +1 -1
  233. package/dist/locale/toolkit-messages.d.ts +19 -1
  234. package/dist/locale/toolkit-messages.d.ts.map +1 -1
  235. package/dist/locale/toolkit-messages.js +54 -1
  236. package/dist/locale/toolkit-messages.js.map +1 -1
  237. package/dist/primitives/alert.d.ts +1 -1
  238. package/dist/primitives/alert.d.ts.map +1 -1
  239. package/dist/primitives/alert.js +2 -0
  240. package/dist/primitives/alert.js.map +1 -1
  241. package/dist/primitives/badge.d.ts +1 -1
  242. package/dist/primitives/badge.d.ts.map +1 -1
  243. package/dist/primitives/badge.js +9 -4
  244. package/dist/primitives/badge.js.map +1 -1
  245. package/dist/primitives/button.d.ts +1 -1
  246. package/dist/primitives/button.js +1 -1
  247. package/dist/primitives/button.js.map +1 -1
  248. package/dist/primitives/dialog.d.ts +1 -1
  249. package/dist/primitives/dialog.d.ts.map +1 -1
  250. package/dist/primitives/dialog.js +10 -3
  251. package/dist/primitives/dialog.js.map +1 -1
  252. package/dist/primitives/select.js +1 -1
  253. package/dist/primitives/select.js.map +1 -1
  254. package/dist/primitives/sheet.d.ts +1 -1
  255. package/dist/primitives/sheet.d.ts.map +1 -1
  256. package/dist/primitives/sheet.js +6 -2
  257. package/dist/primitives/sheet.js.map +1 -1
  258. package/dist/providers/query-provider.d.ts +32 -1
  259. package/dist/providers/query-provider.d.ts.map +1 -1
  260. package/dist/providers/query-provider.js +53 -4
  261. package/dist/providers/query-provider.js.map +1 -1
  262. package/dist/render-cases-primitives.js +1 -1
  263. package/dist/render-cases-primitives.js.map +1 -1
  264. package/dist/render-cases.d.ts.map +1 -1
  265. package/dist/render-cases.js +30 -6
  266. package/dist/render-cases.js.map +1 -1
  267. package/dist/render-effects.d.ts +42 -0
  268. package/dist/render-effects.d.ts.map +1 -0
  269. package/dist/render-effects.js +81 -0
  270. package/dist/render-effects.js.map +1 -0
  271. package/dist/theme/create-theme.d.ts +19 -1
  272. package/dist/theme/create-theme.d.ts.map +1 -1
  273. package/dist/theme/create-theme.js +15 -0
  274. package/dist/theme/create-theme.js.map +1 -1
  275. package/package.json +5 -4
  276. package/src/app/adapt-data-widget.tsx +2 -42
  277. package/src/app/builder/BuilderBanners.tsx +1 -1
  278. package/src/app/builder/CanvasRow.tsx +63 -17
  279. package/src/app/builder/CatalogueSheet.tsx +50 -29
  280. package/src/app/builder/CellPropsSheet.tsx +17 -2
  281. package/src/app/builder/DiscardDialog.tsx +59 -0
  282. package/src/app/builder/LayoutSurface.tsx +206 -66
  283. package/src/app/builder/PipelineStrip.tsx +253 -153
  284. package/src/app/builder/SaveDialog.tsx +14 -2
  285. package/src/app/builder/Toolbar.tsx +144 -59
  286. package/src/app/builder/Workspace.tsx +226 -58
  287. package/src/app/builder/builder-model.ts +341 -2
  288. package/src/app/builder/builder-model.ux.test.ts +456 -0
  289. package/src/app/builder/builder-ux.test.tsx +844 -0
  290. package/src/app/builder/reveal.ts +10 -0
  291. package/src/app/builder/use-builder-catalogue.ts +56 -19
  292. package/src/app/builder/use-builder-session.ts +68 -0
  293. package/src/app/builder/use-display-resolver.ts +24 -0
  294. package/src/app/index.ts +12 -2
  295. package/src/app/layout-builder.tsx +178 -191
  296. package/src/app/mcp-app-view-chrome.test.tsx +141 -1
  297. package/src/app/mcp-app-view-chrome.tsx +56 -6
  298. package/src/app/mcp-app-view-states.tsx +51 -11
  299. package/src/app/mcp-app-view.effects.test.tsx +292 -0
  300. package/src/app/mcp-app-view.test.tsx +222 -2
  301. package/src/app/mcp-app-view.tsx +267 -117
  302. package/src/app/mcp-toolkit-app.test.tsx +40 -0
  303. package/src/app/mcp-toolkit-app.tsx +8 -1
  304. package/src/app/mcp-use-host-bridge.test.tsx +180 -0
  305. package/src/app/mcp-use-host-bridge.tsx +67 -24
  306. package/src/app/model-context.test.tsx +116 -0
  307. package/src/app/model-context.tsx +125 -0
  308. package/src/app/use-build-mode-focus.ts +62 -0
  309. package/src/app/use-view-actions.ts +89 -0
  310. package/src/app/use-view-lifecycle.ts +6 -0
  311. package/src/app/view-lifecycle.test.ts +216 -0
  312. package/src/app/view-lifecycle.ts +165 -5
  313. package/src/app/widget-fixture.effects.test.tsx +51 -0
  314. package/src/app/widget-fixture.render.test.tsx +81 -0
  315. package/src/app/widget-fixture.tsx +34 -11
  316. package/src/app/widget-props-sheet.tsx +57 -16
  317. package/src/app/widget-renderer.tsx +1 -1
  318. package/src/components/DrillButton.tsx +23 -10
  319. package/src/components/EmptyState.tsx +6 -4
  320. package/src/components/ErrorState.tsx +31 -9
  321. package/src/components/FilterBar.test.tsx +82 -0
  322. package/src/components/FilterBar.tsx +52 -26
  323. package/src/components/KpiGrid.test.tsx +279 -0
  324. package/src/components/KpiGrid.tsx +297 -90
  325. package/src/components/ListFooter.test.tsx +116 -0
  326. package/src/components/ListFooter.tsx +102 -15
  327. package/src/components/PagedListFooter.test.tsx +140 -0
  328. package/src/components/PagedListFooter.tsx +107 -0
  329. package/src/components/Pills.test.tsx +77 -0
  330. package/src/components/Pills.tsx +63 -10
  331. package/src/components/SectionHeading.tsx +9 -3
  332. package/src/components/Toast.tsx +37 -19
  333. package/src/components/WidgetHeader.test.tsx +78 -0
  334. package/src/components/WidgetHeader.tsx +63 -16
  335. package/src/components/feedback-states.test.tsx +98 -9
  336. package/src/components/tone-figures.test.tsx +8 -4
  337. package/src/hooks/index.ts +6 -3
  338. package/src/hooks/tool-result-recovery-model.test.ts +744 -0
  339. package/src/hooks/tool-result-recovery-model.ts +418 -0
  340. package/src/hooks/use-tool-query.effects.test.tsx +205 -0
  341. package/src/hooks/use-tool-query.ts +156 -33
  342. package/src/hooks/use-tool-result-recovery.effects.test.tsx +141 -0
  343. package/src/hooks/use-tool-result-recovery.test.ts +3 -4
  344. package/src/hooks/use-tool-result-recovery.ts +84 -142
  345. package/src/hooks/use-view-data.test.tsx +118 -9
  346. package/src/hooks/use-view-data.ts +128 -18
  347. package/src/hooks/use-view-tool-query.ts +25 -19
  348. package/src/i18n-coverage.test.tsx +104 -2
  349. package/src/index.ts +48 -11
  350. package/src/lib/focus-return.test.ts +288 -0
  351. package/src/lib/focus-return.ts +96 -0
  352. package/src/lib/parse-tool-result.test.ts +46 -1
  353. package/src/lib/parse-tool-result.ts +40 -2
  354. package/src/lib/toast-model.test.ts +166 -1
  355. package/src/lib/toast-model.ts +104 -2
  356. package/src/lib/tone-utils.test.ts +86 -10
  357. package/src/lib/tone-utils.ts +89 -8
  358. package/src/lib/tool-query.test.ts +569 -0
  359. package/src/lib/tool-query.ts +253 -0
  360. package/src/lib/tool-result-observer.test.ts +296 -0
  361. package/src/lib/tool-result-observer.ts +211 -0
  362. package/src/lib/typography.test.ts +45 -0
  363. package/src/lib/typography.ts +47 -0
  364. package/src/lib/utils.ts +12 -1
  365. package/src/locale/index.ts +1 -0
  366. package/src/locale/message-types.ts +118 -2
  367. package/src/locale/messages-de.ts +49 -10
  368. package/src/locale/messages-en.ts +49 -10
  369. package/src/locale/toolkit-messages.test.ts +133 -0
  370. package/src/locale/toolkit-messages.ts +63 -2
  371. package/src/primitives/alert.tsx +3 -0
  372. package/src/primitives/badge.tsx +9 -4
  373. package/src/primitives/button.tsx +1 -1
  374. package/src/primitives/dialog.tsx +10 -1
  375. package/src/primitives/select.tsx +1 -1
  376. package/src/primitives/sheet.tsx +7 -0
  377. package/src/providers/query-provider.test.tsx +154 -0
  378. package/src/providers/query-provider.tsx +66 -5
  379. package/src/render-cases-primitives.tsx +1 -1
  380. package/src/render-cases.tsx +38 -6
  381. package/src/render-effects.tsx +96 -0
  382. package/src/theme/cd-tokens.test.ts +3 -1
  383. package/src/theme/create-theme.test.ts +10 -0
  384. package/src/theme/create-theme.ts +28 -0
  385. package/src/theme/theme-css.test.ts +51 -0
  386. package/src/theme.css +31 -1
  387. package/ui-catalog.json +448 -41
@@ -1,7 +1,16 @@
1
1
  import { useCallback, useMemo, useReducer, useState } from "react"
2
2
  import { Eye, Layers, SlidersHorizontal } from "lucide-react"
3
3
  import type { LayoutConfig, PipelineStepRef, RowDef, WidgetProps } from "@miragon/mcp-toolkit-core"
4
- import { draftToLayout, entriesToKeys, initDraft, sizeToSpan } from "./builder/builder-model.js"
4
+ import {
5
+ builderLeave,
6
+ builderSnapshot,
7
+ draftHasCells,
8
+ initDraft,
9
+ sizeToSpan,
10
+ toExitCommit,
11
+ type BuilderExitCommit,
12
+ type BuilderView,
13
+ } from "./builder/builder-model.js"
5
14
  import {
6
15
  draftReducer,
7
16
  initBuilderState,
@@ -12,17 +21,20 @@ import { Toolbar } from "./builder/Toolbar.js"
12
21
  import { PipelineStrip } from "./builder/PipelineStrip.js"
13
22
  import { CatalogueSheet } from "./builder/CatalogueSheet.js"
14
23
  import { SaveDialog } from "./builder/SaveDialog.js"
24
+ import { DiscardDialog } from "./builder/DiscardDialog.js"
15
25
  import { PreviewPane } from "./builder/PreviewPane.js"
16
26
  import { LayoutSurface } from "./builder/LayoutSurface.js"
17
27
  import { BuilderBanners } from "./builder/BuilderBanners.js"
18
28
  import { EmptyHint } from "./builder/EmptyHint.js"
19
29
  import { CellPropsSheet } from "./builder/CellPropsSheet.js"
20
30
  import { useBuilderCatalogue } from "./builder/use-builder-catalogue.js"
31
+ import { useBuilderMode, useDraftStatus, useRenameFeedback } from "./builder/use-builder-session.js"
21
32
  import { useKeyStepEntries } from "./builder/use-key-step-entries.js"
22
33
  import { useSaveDashboard } from "./builder/use-save-dashboard.js"
23
34
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "../primitives/tabs.js"
24
35
  import { ToastProvider, useToast } from "../components/Toast.js"
25
36
  import { useToolkitMessages } from "../locale/locale-provider.js"
37
+ import { builderMountFocusTarget, useFocusOnMount } from "./use-build-mode-focus.js"
26
38
  import { type WidgetComponent } from "./widget-renderer.js"
27
39
 
28
40
  // -------------------------------------------------------------------------- //
@@ -50,25 +62,13 @@ export interface LayoutBuilderProps {
50
62
  labels?: LayoutBuilderLabels
51
63
  onSaved?: (result: { id: string; name: string }) => void
52
64
  /**
53
- * Called when the user clicks "Done" / exits the builder. Receives
54
- * the final draft so the parent can swap into the rendered view with
55
- * the just-built layout. Omit `commit` to leave parent state untouched.
65
+ * Leaves the builder. "Apply" calls it WITH the final draft (`commit`) so
66
+ * the parent swaps the just-built layout into its view; "Discard" calls it
67
+ * WITHOUT one, so the parent keeps the view as it was before editing (after
68
+ * a confirmation when the draft changed it). Without `onExit` the builder
69
+ * has no Apply, and Discard resets the draft in place.
56
70
  */
57
- onExit?: (commit?: {
58
- layout: LayoutConfig
59
- keys?: Record<string, unknown>
60
- steps?: PipelineStepRef[]
61
- title?: string
62
- context?: {
63
- keys: Record<string, unknown>
64
- stepIds: string[]
65
- stepData: Record<
66
- string,
67
- { data: unknown; keys: Record<string, unknown>; _app: string; _dataType: string }
68
- >
69
- errors: { stepId: string; reason: string }[]
70
- }
71
- }) => void
71
+ onExit?: (commit?: BuilderExitCommit) => void
72
72
  }
73
73
 
74
74
  // -------------------------------------------------------------------------- //
@@ -76,18 +76,47 @@ export interface LayoutBuilderProps {
76
76
  // -------------------------------------------------------------------------- //
77
77
 
78
78
  /**
79
- * The visual view builder. Confirms a successful save with a toast (it mounts
80
- * a `ToastProvider` when none is above it — inside `McpAppView` it shares the
79
+ * The visual view builder. People see widgets and steps by their titles
80
+ * (`title`/`titleKey` of the definitions, else the id); the data sources, the
81
+ * catalogue and the technical ids sit behind "Advanced". "Apply" shows the
82
+ * draft in the view, "Save as dashboard" stores it, "Discard" restores the
83
+ * view as it was. Confirms a successful save with a toast (it mounts a
84
+ * `ToastProvider` when none is above it — inside `McpAppView` it shares the
81
85
  * view's). Every caption follows the active locale (`labels` overrides).
82
86
  */
83
87
  export function LayoutBuilder(props: LayoutBuilderProps) {
88
+ // Discard without an `onExit` restores the initial props by remounting.
89
+ const [generation, setGeneration] = useState(0)
90
+ const reset = useCallback(() => setGeneration((g) => g + 1), [])
84
91
  return (
85
92
  <ToastProvider>
86
- <LayoutBuilderImpl {...props} />
93
+ <LayoutBuilderImpl
94
+ key={generation}
95
+ {...props}
96
+ onReset={reset}
97
+ mountFocus={builderMountFocusTarget(generation)}
98
+ />
87
99
  </ToastProvider>
88
100
  )
89
101
  }
90
102
 
103
+ /**
104
+ * The catalog strings, overrides merged; a `labels` override of the deprecated
105
+ * `done` still labels Apply (a catalog override of it already did, in
106
+ * `resolveToolkitMessages`).
107
+ */
108
+ function useBuilderLabels(labels: LayoutBuilderLabels | undefined): Required<LayoutBuilderLabels> {
109
+ const messages = useToolkitMessages()
110
+ return useMemo(
111
+ () => ({
112
+ ...messages.builder,
113
+ ...labels,
114
+ apply: labels?.apply ?? labels?.done ?? messages.builder.apply,
115
+ }),
116
+ [messages.builder, labels],
117
+ )
118
+ }
119
+
91
120
  function LayoutBuilderImpl({
92
121
  initialLayout,
93
122
  title,
@@ -101,16 +130,16 @@ function LayoutBuilderImpl({
101
130
  labels,
102
131
  onSaved,
103
132
  onExit,
104
- }: LayoutBuilderProps) {
105
- const messages = useToolkitMessages()
106
- const L = useMemo(() => ({ ...messages.builder, ...labels }), [messages.builder, labels])
133
+ onReset,
134
+ mountFocus,
135
+ }: LayoutBuilderProps & { onReset: () => void; mountFocus: string | null }) {
136
+ const L = useBuilderLabels(labels)
107
137
  const toast = useToast()
108
138
 
139
+ // A reset remounts the builder: the element that had the focus is gone.
140
+ const rootRef = useFocusOnMount<HTMLDivElement>(mountFocus)
141
+
109
142
  // ── Draft layout (pure reducer) ─────────────────────────────────────────
110
- // The draft plus the UI indices coupled to draft mutations
111
- // (activeTabIndex / focusedRowIndex / editingTabIdx) live in a single pure
112
- // reducer (see ./builder/builder-reducer.ts) so every mutation is testable
113
- // and the old setState-in-updater pattern is gone.
114
143
  const [builderState, dispatch] = useReducer(
115
144
  draftReducer,
116
145
  initialLayout,
@@ -128,64 +157,28 @@ function LayoutBuilderImpl({
128
157
  const setEditingTabIdx = useCallback((idx: number | null) => {
129
158
  dispatch(idx === null ? { type: "stopEditingTab" } : { type: "startEditingTab", tabIdx: idx })
130
159
  }, [])
131
- const [view, setView] = useState<"layout" | "pipeline" | "preview">("layout")
160
+ const { advanced, view, setView, toggleAdvanced, openDataSources } = useBuilderMode()
132
161
  const [catalogueOpen, setCatalogueOpen] = useState(false)
133
-
134
- // ── Inline rename feedback ──────────────────────────────────────────────
135
- // The reducer rejects duplicate tab labels and bumps `renameRejectedAt`
136
- // (Phase 1 signal). Mirror each bump into a transient flag so the rename
137
- // input can show "name already exists" inline (Finding [3]). Tracked via
138
- // the render-phase setState pattern used elsewhere in this codebase; cleared
139
- // when the inline editor closes (editingTabIdx → null).
140
- const [renameRejected, setRenameRejected] = useState(false)
141
- const [prevRenameRejectedAt, setPrevRenameRejectedAt] = useState(renameRejectedAt)
142
- if (renameRejectedAt !== prevRenameRejectedAt) {
143
- setPrevRenameRejectedAt(renameRejectedAt)
144
- setRenameRejected(true)
145
- }
146
- const [prevEditingTabIdx, setPrevEditingTabIdx] = useState(editingTabIdx)
147
- if (editingTabIdx !== prevEditingTabIdx) {
148
- setPrevEditingTabIdx(editingTabIdx)
149
- if (editingTabIdx === null) setRenameRejected(false)
150
- }
162
+ const [discardOpen, setDiscardOpen] = useState(false)
163
+ const renameRejected = useRenameFeedback(renameRejectedAt, editingTabIdx)
151
164
 
152
165
  // ── Keys + steps editor state ───────────────────────────────────────────
153
166
  const { keyEntries, stepEntries, addKey, updateKey, removeKey, addStep, updateStep, removeStep } =
154
167
  useKeyStepEntries({ initialKeys, initialSteps })
155
168
 
169
+ // ── Draft status: what Apply / Discard / the unsaved marker compare ─────
170
+ const snapshot = useMemo(
171
+ () => builderSnapshot(draft, keyEntries, stepEntries),
172
+ [draft, keyEntries, stepEntries],
173
+ )
174
+ const draftState = useDraftStatus(snapshot)
175
+
156
176
  // ── Catalogue fetch (mount + on key/step edits) ─────────────────────────
157
- // The builder fetches its own catalogue from the app-only
158
- // `get-builder-catalogue` tool; the LLM never sees this data.
159
- const {
160
- liveReachable,
161
- liveUnreachable,
162
- liveCatalog,
163
- liveSteps,
164
- liveContext,
165
- validationIssues,
166
- status,
167
- refreshError,
168
- retryCatalogue,
169
- widgetById,
170
- propsSchemaByWidgetId,
171
- paletteByApp,
172
- liveKeyCount,
173
- stepsByApp,
174
- } = useBuilderCatalogue({ callTool, catalogueToolName, keyEntries, stepEntries })
177
+ const catalogue = useBuilderCatalogue({ callTool, catalogueToolName, keyEntries, stepEntries })
178
+ const { liveReachable, liveCatalog, liveContext, widgetById } = catalogue
175
179
 
176
180
  // ── Save dialog ─────────────────────────────────────────────────────────
177
- const {
178
- isBusy,
179
- saveOpen,
180
- setSaveOpen,
181
- saveName,
182
- setSaveName,
183
- saveDescription,
184
- setSaveDescription,
185
- saveError,
186
- setSaveError,
187
- saveDraft,
188
- } = useSaveDashboard({
181
+ const save = useSaveDashboard({
189
182
  callTool,
190
183
  saveToolName,
191
184
  dashboardId,
@@ -194,17 +187,14 @@ function LayoutBuilderImpl({
194
187
  stepEntries,
195
188
  draft,
196
189
  onSaved,
197
- onSuccess: (name) => toast.show({ tone: "success", title: L.saved(name) }),
190
+ onSuccess: (name) => {
191
+ draftState.markSaved(snapshot)
192
+ toast.show({ tone: "success", title: L.saved(name) })
193
+ },
198
194
  })
199
195
 
200
- // ── DnD ──────────────────────────────────────────────────────────────────
196
+ // ── DnD + the cell whose props are being configured ─────────────────────
201
197
  const [dragOverRow, setDragOverRow] = useState<number | null>(null)
202
-
203
- // Which cell (if any) is having its props configured. The
204
- // WidgetPropsSheet is mounted once at the LayoutBuilder root so that
205
- // exactly one editor can be open at a time across the entire workspace
206
- // — including across tabs. Stored as `{ rowIdx, cellIdx }` against the
207
- // currently active draft surface (rows or active tab's rows).
208
198
  const [configuringCell, setConfiguringCell] = useState<{
209
199
  rowIdx: number
210
200
  cellIdx: number
@@ -227,47 +217,34 @@ function LayoutBuilderImpl({
227
217
  },
228
218
  [widgetById],
229
219
  )
230
-
231
220
  const addRow = useCallback(() => dispatch({ type: "addRow" }), [])
232
-
233
221
  const removeRow = useCallback((rowIdx: number) => dispatch({ type: "removeRow", rowIdx }), [])
234
-
235
222
  const moveRow = useCallback(
236
223
  (rowIdx: number, direction: -1 | 1) => dispatch({ type: "moveRow", rowIdx, direction }),
237
224
  [],
238
225
  )
239
-
240
226
  const setCellSpan = useCallback(
241
227
  (rowIdx: number, cellIdx: number, span: number) =>
242
228
  dispatch({ type: "setCellSpan", rowIdx, cellIdx, span }),
243
229
  [],
244
230
  )
245
-
246
231
  const removeCell = useCallback(
247
232
  (rowIdx: number, cellIdx: number) => dispatch({ type: "removeCell", rowIdx, cellIdx }),
248
233
  [],
249
234
  )
250
-
251
- // Sets (or clears, when `next` is undefined) the per-instance `props` on
252
- // a cell. Triggered by the WidgetPropsSheet's Apply button. Storing
253
- // `undefined` rather than `{}` keeps the saved layout's wire format
254
- // identical to one a hand-coded LLM call would produce — the field is
255
- // simply absent.
235
+ // Sets (or clears, when `next` is undefined) a cell's per-instance `props`
236
+ // from the props sheet's Apply; `undefined` keeps the field absent on the wire.
256
237
  const setCellProps = useCallback(
257
238
  (rowIdx: number, cellIdx: number, next: Record<string, unknown> | undefined) =>
258
239
  dispatch({ type: "setCellProps", rowIdx, cellIdx, next }),
259
240
  [],
260
241
  )
261
-
262
242
  const addTab = useCallback(() => dispatch({ type: "addTab" }), [])
263
-
264
- // Commit an inline tab rename and decide whether to close the editor.
265
- // Dispatches the rename, then keeps the input open when the trimmed label
266
- // collides with another tab so the reducer's rejection (`renameRejectedAt`)
267
- // can drive the inline "name already exists" feedback instead of the editor
268
- // silently snapping shut on the old label.
243
+ // Commit an inline tab rename. A label that collides with another tab keeps
244
+ // the editor open (the reducer's rejection drives the inline error); the
245
+ // return value tells the field whether it closed.
269
246
  const commitRename = useCallback(
270
- (tabIdx: number, label: string) => {
247
+ (tabIdx: number, label: string): boolean => {
271
248
  dispatch({ type: "renameTab", tabIdx, label })
272
249
  const trimmed = label.trim()
273
250
  const isDuplicate =
@@ -275,10 +252,10 @@ function LayoutBuilderImpl({
275
252
  trimmed.length > 0 &&
276
253
  draft.tabs.some((t, i) => i !== tabIdx && t.label === trimmed)
277
254
  if (!isDuplicate) setEditingTabIdx(null)
255
+ return !isDuplicate
278
256
  },
279
257
  [draft, setEditingTabIdx],
280
258
  )
281
-
282
259
  const removeTab = useCallback((tabIdx: number) => dispatch({ type: "removeTab", tabIdx }), [])
283
260
 
284
261
  // ── Smart action: add a producing step for a missing key ────────────────
@@ -291,87 +268,77 @@ function LayoutBuilderImpl({
291
268
  [addStep, liveCatalog],
292
269
  )
293
270
 
271
+ // ── Leaving: Apply commits, Discard restores (asking first when the view loses changes) ─
272
+ const leave = builderLeave({
273
+ changed: draftState.changed,
274
+ onExit,
275
+ onReset,
276
+ commit: () => toExitCommit(draft, keyEntries, stepEntries, title, liveContext),
277
+ confirm: () => setDiscardOpen(true),
278
+ })
279
+
294
280
  const widgetProps: WidgetProps = useMemo(
295
281
  () => ({ keys: liveContext.keys, context: liveContext }),
296
282
  [liveContext],
297
283
  )
298
- const hasAnyCell = activeRows.some((r) => r.row.length > 0)
299
- const liveKeysMap = liveContext.keys
284
+ const hasAnyCell = draftHasCells(draft)
300
285
 
301
286
  return (
302
- <div className="flex min-h-[60vh] flex-col gap-4 p-1">
287
+ <div ref={rootRef} className="flex min-h-[60vh] flex-col gap-4 p-1">
303
288
  <Toolbar
304
289
  L={L}
305
290
  previewMode={view === "preview"}
306
- isBusy={isBusy}
307
- status={status}
308
- liveKeys={liveKeyCount}
291
+ isBusy={save.isBusy}
292
+ status={catalogue.status}
293
+ liveKeys={catalogue.liveKeyCount}
309
294
  reachableCount={liveReachable.length}
310
- unreachableCount={liveUnreachable.length}
295
+ unreachableCount={catalogue.liveUnreachable.length}
296
+ advanced={advanced}
297
+ onToggleAdvanced={toggleAdvanced}
298
+ unsaved={draftState.unsaved}
311
299
  onOpenCatalogue={() => setCatalogueOpen(true)}
312
- onOpenSave={() => setSaveOpen(true)}
313
- onDone={
314
- onExit
315
- ? () =>
316
- onExit({
317
- layout: draftToLayout(draft),
318
- keys: entriesToKeys(keyEntries),
319
- steps: stepEntries.filter((s) => s.step.trim().length > 0),
320
- title,
321
- context: {
322
- keys: liveContext.keys,
323
- stepIds: Object.keys(liveContext.steps),
324
- stepData: Object.fromEntries(
325
- Object.entries(liveContext.steps).map(([id, result]) => [
326
- id,
327
- {
328
- data: result.data,
329
- keys: result.keys,
330
- _app: result._app,
331
- _dataType: result._dataType,
332
- },
333
- ]),
334
- ),
335
- errors: liveContext.errors,
336
- },
337
- })
338
- : undefined
339
- }
300
+ onOpenSave={() => save.setSaveOpen(true)}
301
+ onDiscard={leave.discard}
302
+ onApply={leave.apply}
340
303
  hasAnyCell={hasAnyCell}
341
304
  />
342
305
 
343
306
  <BuilderBanners
344
307
  L={L}
345
308
  catalogueToolName={catalogueToolName}
346
- refreshError={refreshError}
347
- validationIssues={validationIssues}
348
- onRetry={retryCatalogue}
309
+ refreshError={catalogue.refreshError}
310
+ validationIssues={catalogue.validationIssues}
311
+ onRetry={catalogue.retryCatalogue}
349
312
  />
350
313
 
351
- <Tabs value={view} onValueChange={(v) => setView(v as typeof view)}>
314
+ <Tabs value={view} onValueChange={(v) => setView(v as BuilderView)}>
352
315
  <TabsList>
353
316
  <TabsTrigger value="layout">
354
- <Layers className="size-3.5" />
317
+ <Layers aria-hidden="true" className="size-3.5" />
355
318
  {L.viewLayoutTab}
356
319
  </TabsTrigger>
357
- <TabsTrigger value="pipeline">
358
- <SlidersHorizontal className="size-3.5" />
359
- {L.viewPipelineTab}
360
- {(keyEntries.length > 0 || stepEntries.length > 0) && (
361
- <span className="bg-muted text-muted-foreground ml-1 rounded px-1 font-mono text-[10px]">
362
- {keyEntries.length}/{stepEntries.length}
363
- </span>
364
- )}
365
- </TabsTrigger>
320
+ {advanced && (
321
+ <TabsTrigger value="pipeline">
322
+ <SlidersHorizontal aria-hidden="true" className="size-3.5" />
323
+ {L.viewPipelineTab}
324
+ {(keyEntries.length > 0 || stepEntries.length > 0) && (
325
+ <span className="bg-muted text-muted-foreground text-small ml-1 rounded px-1 tabular-nums">
326
+ {keyEntries.length}/{stepEntries.length}
327
+ </span>
328
+ )}
329
+ </TabsTrigger>
330
+ )}
366
331
  <TabsTrigger value="preview" disabled={!hasAnyCell}>
367
- <Eye className="size-3.5" />
332
+ <Eye aria-hidden="true" className="size-3.5" />
368
333
  {L.viewPreviewTab}
369
334
  </TabsTrigger>
370
335
  </TabsList>
371
336
 
372
337
  <TabsContent value="layout" className="mt-4">
373
- {liveReachable.length === 0 && !hasAnyCell ? (
374
- <EmptyHint L={L} onGotoPipeline={() => setView("pipeline")} />
338
+ {/* Until the catalogue answers, the palette shows a loading state
339
+ (an empty state now would flash on every open). */}
340
+ {catalogue.loaded && liveReachable.length === 0 && !hasAnyCell ? (
341
+ <EmptyHint L={L} onGotoPipeline={openDataSources} />
375
342
  ) : (
376
343
  <LayoutSurface
377
344
  L={L}
@@ -386,8 +353,11 @@ function LayoutBuilderImpl({
386
353
  onRemoveTab={removeTab}
387
354
  workspace={{
388
355
  L,
389
- paletteByApp,
356
+ paletteByApp: catalogue.paletteByApp,
357
+ appDisplayByName: catalogue.appDisplayByName,
390
358
  paletteCount: liveReachable.length,
359
+ paletteLoading: !catalogue.loaded,
360
+ onOpenDataSources: openDataSources,
391
361
  activeRows,
392
362
  focusedRowIndex,
393
363
  setFocusedRowIndex,
@@ -400,7 +370,9 @@ function LayoutBuilderImpl({
400
370
  onRemoveCell: removeCell,
401
371
  onAddWidget: addWidgetToRow,
402
372
  onConfigureCell: (rowIdx, cellIdx) => setConfiguringCell({ rowIdx, cellIdx }),
403
- propsSchemaByWidgetId,
373
+ propsSchemaByWidgetId: catalogue.propsSchemaByWidgetId,
374
+ displayByWidgetId: catalogue.displayByWidgetId,
375
+ advanced,
404
376
  widgets,
405
377
  widgetProps,
406
378
  }}
@@ -408,24 +380,26 @@ function LayoutBuilderImpl({
408
380
  )}
409
381
  </TabsContent>
410
382
 
411
- <TabsContent value="pipeline" className="mt-4">
412
- <PipelineStrip
413
- L={L}
414
- keyEntries={keyEntries}
415
- stepEntries={stepEntries}
416
- keyCatalog={liveCatalog}
417
- availableSteps={liveSteps}
418
- stepsByApp={stepsByApp}
419
- liveKeys={liveKeysMap}
420
- onAddKey={addKey}
421
- onUpdateKey={updateKey}
422
- onRemoveKey={removeKey}
423
- onAddStep={addStep}
424
- onUpdateStep={updateStep}
425
- onRemoveStep={removeStep}
426
- status={status}
427
- />
428
- </TabsContent>
383
+ {advanced && (
384
+ <TabsContent value="pipeline" className="mt-4">
385
+ <PipelineStrip
386
+ L={L}
387
+ keyEntries={keyEntries}
388
+ stepEntries={stepEntries}
389
+ keyCatalog={liveCatalog}
390
+ availableSteps={catalogue.liveSteps}
391
+ stepsByApp={catalogue.stepsByApp}
392
+ liveKeys={liveContext.keys}
393
+ onAddKey={addKey}
394
+ onUpdateKey={updateKey}
395
+ onRemoveKey={removeKey}
396
+ onAddStep={addStep}
397
+ onUpdateStep={updateStep}
398
+ onRemoveStep={removeStep}
399
+ status={catalogue.status}
400
+ />
401
+ </TabsContent>
402
+ )}
429
403
 
430
404
  <TabsContent value="preview" className="mt-4">
431
405
  <PreviewPane
@@ -443,8 +417,8 @@ function LayoutBuilderImpl({
443
417
  open={catalogueOpen}
444
418
  onOpenChange={setCatalogueOpen}
445
419
  keyCatalog={liveCatalog}
446
- availableSteps={liveSteps}
447
- unreachableWidgets={liveUnreachable}
420
+ availableSteps={catalogue.liveSteps}
421
+ unreachableWidgets={catalogue.liveUnreachable}
448
422
  onSeedKey={(name) => {
449
423
  addKey(name)
450
424
  setCatalogueOpen(false)
@@ -459,26 +433,39 @@ function LayoutBuilderImpl({
459
433
 
460
434
  <SaveDialog
461
435
  L={L}
462
- open={saveOpen}
436
+ open={save.saveOpen}
437
+ canApply={onExit !== undefined}
463
438
  onOpenChange={(open) => {
464
439
  // Clear any stale save error whenever the dialog is opened or
465
440
  // dismissed so a fresh attempt starts clean.
466
- setSaveError(null)
467
- setSaveOpen(open)
441
+ save.setSaveError(null)
442
+ save.setSaveOpen(open)
443
+ }}
444
+ name={save.saveName}
445
+ setName={save.setSaveName}
446
+ description={save.saveDescription}
447
+ setDescription={save.setSaveDescription}
448
+ onSubmit={save.saveDraft}
449
+ busy={save.isBusy}
450
+ error={save.saveError}
451
+ />
452
+
453
+ <DiscardDialog
454
+ L={L}
455
+ open={discardOpen}
456
+ onOpenChange={setDiscardOpen}
457
+ changesSaved={!draftState.unsaved}
458
+ onConfirm={() => {
459
+ setDiscardOpen(false)
460
+ leave.discardNow()
468
461
  }}
469
- name={saveName}
470
- setName={setSaveName}
471
- description={saveDescription}
472
- setDescription={setSaveDescription}
473
- onSubmit={saveDraft}
474
- busy={isBusy}
475
- error={saveError}
476
462
  />
477
463
 
478
464
  <CellPropsSheet
479
465
  configuringCell={configuringCell}
480
466
  activeRows={activeRows}
481
- propsSchemaByWidgetId={propsSchemaByWidgetId}
467
+ propsSchemaByWidgetId={catalogue.propsSchemaByWidgetId}
468
+ displayByWidgetId={catalogue.displayByWidgetId}
482
469
  onApply={setCellProps}
483
470
  onClose={() => setConfiguringCell(null)}
484
471
  />