@miragon/mcp-toolkit-ui 2.6.0 → 2.7.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 (348) hide show
  1. package/README.md +7 -1
  2. package/dist/app/builder/BuilderBanners.d.ts +7 -2
  3. package/dist/app/builder/BuilderBanners.d.ts.map +1 -1
  4. package/dist/app/builder/BuilderBanners.js +8 -3
  5. package/dist/app/builder/BuilderBanners.js.map +1 -1
  6. package/dist/app/builder/CanvasRow.d.ts.map +1 -1
  7. package/dist/app/builder/CanvasRow.js +12 -14
  8. package/dist/app/builder/CanvasRow.js.map +1 -1
  9. package/dist/app/builder/CatalogueSheet.d.ts.map +1 -1
  10. package/dist/app/builder/CatalogueSheet.js +6 -5
  11. package/dist/app/builder/CatalogueSheet.js.map +1 -1
  12. package/dist/app/builder/EmptyHint.d.ts +4 -0
  13. package/dist/app/builder/EmptyHint.d.ts.map +1 -1
  14. package/dist/app/builder/EmptyHint.js +6 -1
  15. package/dist/app/builder/EmptyHint.js.map +1 -1
  16. package/dist/app/builder/LayoutSurface.js +3 -3
  17. package/dist/app/builder/LayoutSurface.js.map +1 -1
  18. package/dist/app/builder/PipelineStrip.d.ts +3 -1
  19. package/dist/app/builder/PipelineStrip.d.ts.map +1 -1
  20. package/dist/app/builder/PipelineStrip.js +11 -6
  21. package/dist/app/builder/PipelineStrip.js.map +1 -1
  22. package/dist/app/builder/SaveDialog.d.ts.map +1 -1
  23. package/dist/app/builder/SaveDialog.js +2 -2
  24. package/dist/app/builder/SaveDialog.js.map +1 -1
  25. package/dist/app/builder/Toolbar.d.ts +3 -1
  26. package/dist/app/builder/Toolbar.d.ts.map +1 -1
  27. package/dist/app/builder/Toolbar.js +9 -3
  28. package/dist/app/builder/Toolbar.js.map +1 -1
  29. package/dist/app/builder/Workspace.js +2 -2
  30. package/dist/app/builder/Workspace.js.map +1 -1
  31. package/dist/app/builder/builder-model.d.ts +3 -2
  32. package/dist/app/builder/builder-model.d.ts.map +1 -1
  33. package/dist/app/builder/builder-model.js +5 -4
  34. package/dist/app/builder/builder-model.js.map +1 -1
  35. package/dist/app/builder/labels.d.ts +13 -62
  36. package/dist/app/builder/labels.d.ts.map +1 -1
  37. package/dist/app/builder/labels.js +9 -58
  38. package/dist/app/builder/labels.js.map +1 -1
  39. package/dist/app/builder/status-chip.d.ts.map +1 -1
  40. package/dist/app/builder/status-chip.js +3 -2
  41. package/dist/app/builder/status-chip.js.map +1 -1
  42. package/dist/app/builder/use-builder-catalogue.d.ts +1 -0
  43. package/dist/app/builder/use-builder-catalogue.d.ts.map +1 -1
  44. package/dist/app/builder/use-builder-catalogue.js +6 -0
  45. package/dist/app/builder/use-builder-catalogue.js.map +1 -1
  46. package/dist/app/builder/use-save-dashboard.d.ts +3 -1
  47. package/dist/app/builder/use-save-dashboard.d.ts.map +1 -1
  48. package/dist/app/builder/use-save-dashboard.js +3 -1
  49. package/dist/app/builder/use-save-dashboard.js.map +1 -1
  50. package/dist/app/index.d.ts +1 -1
  51. package/dist/app/index.d.ts.map +1 -1
  52. package/dist/app/index.js.map +1 -1
  53. package/dist/app/layout-builder.d.ts +7 -2
  54. package/dist/app/layout-builder.d.ts.map +1 -1
  55. package/dist/app/layout-builder.js +17 -5
  56. package/dist/app/layout-builder.js.map +1 -1
  57. package/dist/app/mcp-app-view-chrome.d.ts +94 -20
  58. package/dist/app/mcp-app-view-chrome.d.ts.map +1 -1
  59. package/dist/app/mcp-app-view-chrome.js +109 -40
  60. package/dist/app/mcp-app-view-chrome.js.map +1 -1
  61. package/dist/app/mcp-app-view-states.d.ts +15 -0
  62. package/dist/app/mcp-app-view-states.d.ts.map +1 -0
  63. package/dist/app/mcp-app-view-states.js +34 -0
  64. package/dist/app/mcp-app-view-states.js.map +1 -0
  65. package/dist/app/mcp-app-view.d.ts +40 -5
  66. package/dist/app/mcp-app-view.d.ts.map +1 -1
  67. package/dist/app/mcp-app-view.js +174 -128
  68. package/dist/app/mcp-app-view.js.map +1 -1
  69. package/dist/app/mcp-toolkit-app.d.ts +2 -1
  70. package/dist/app/mcp-toolkit-app.d.ts.map +1 -1
  71. package/dist/app/mcp-toolkit-app.js +25 -3
  72. package/dist/app/mcp-toolkit-app.js.map +1 -1
  73. package/dist/app/use-view-lifecycle.d.ts +38 -0
  74. package/dist/app/use-view-lifecycle.d.ts.map +1 -0
  75. package/dist/app/use-view-lifecycle.js +79 -0
  76. package/dist/app/use-view-lifecycle.js.map +1 -0
  77. package/dist/app/view-lifecycle.d.ts +109 -0
  78. package/dist/app/view-lifecycle.d.ts.map +1 -0
  79. package/dist/app/view-lifecycle.js +120 -0
  80. package/dist/app/view-lifecycle.js.map +1 -0
  81. package/dist/app/widget-props-sheet.d.ts +3 -9
  82. package/dist/app/widget-props-sheet.d.ts.map +1 -1
  83. package/dist/app/widget-props-sheet.js +6 -13
  84. package/dist/app/widget-props-sheet.js.map +1 -1
  85. package/dist/app/widget-renderer.d.ts +41 -1
  86. package/dist/app/widget-renderer.d.ts.map +1 -1
  87. package/dist/app/widget-renderer.js +82 -31
  88. package/dist/app/widget-renderer.js.map +1 -1
  89. package/dist/components/DrillButton.d.ts.map +1 -1
  90. package/dist/components/DrillButton.js +2 -1
  91. package/dist/components/DrillButton.js.map +1 -1
  92. package/dist/components/EmptyState.d.ts +33 -0
  93. package/dist/components/EmptyState.d.ts.map +1 -0
  94. package/dist/components/EmptyState.js +17 -0
  95. package/dist/components/EmptyState.js.map +1 -0
  96. package/dist/components/ErrorState.d.ts +50 -0
  97. package/dist/components/ErrorState.d.ts.map +1 -0
  98. package/dist/components/ErrorState.js +60 -0
  99. package/dist/components/ErrorState.js.map +1 -0
  100. package/dist/components/FilterBar.d.ts +1 -1
  101. package/dist/components/FilterBar.d.ts.map +1 -1
  102. package/dist/components/FilterBar.js +7 -4
  103. package/dist/components/FilterBar.js.map +1 -1
  104. package/dist/components/GroupCard.d.ts.map +1 -1
  105. package/dist/components/GroupCard.js +2 -1
  106. package/dist/components/GroupCard.js.map +1 -1
  107. package/dist/components/KpiGrid.d.ts +10 -3
  108. package/dist/components/KpiGrid.d.ts.map +1 -1
  109. package/dist/components/KpiGrid.js +31 -9
  110. package/dist/components/KpiGrid.js.map +1 -1
  111. package/dist/components/ListFooter.d.ts +3 -2
  112. package/dist/components/ListFooter.d.ts.map +1 -1
  113. package/dist/components/ListFooter.js +14 -4
  114. package/dist/components/ListFooter.js.map +1 -1
  115. package/dist/components/Pills.d.ts +10 -6
  116. package/dist/components/Pills.d.ts.map +1 -1
  117. package/dist/components/Pills.js +12 -6
  118. package/dist/components/Pills.js.map +1 -1
  119. package/dist/components/Toast.d.ts +37 -0
  120. package/dist/components/Toast.d.ts.map +1 -0
  121. package/dist/components/Toast.js +91 -0
  122. package/dist/components/Toast.js.map +1 -0
  123. package/dist/components/WidgetHeader.js +1 -1
  124. package/dist/hooks/use-tool-result-recovery.d.ts +23 -1
  125. package/dist/hooks/use-tool-result-recovery.d.ts.map +1 -1
  126. package/dist/hooks/use-tool-result-recovery.js +21 -7
  127. package/dist/hooks/use-tool-result-recovery.js.map +1 -1
  128. package/dist/hooks/use-view-data.d.ts +6 -0
  129. package/dist/hooks/use-view-data.d.ts.map +1 -1
  130. package/dist/hooks/use-view-data.js +6 -1
  131. package/dist/hooks/use-view-data.js.map +1 -1
  132. package/dist/index.d.ts +12 -5
  133. package/dist/index.d.ts.map +1 -1
  134. package/dist/index.js +14 -4
  135. package/dist/index.js.map +1 -1
  136. package/dist/lib/focus-ring.d.ts +18 -0
  137. package/dist/lib/focus-ring.d.ts.map +1 -0
  138. package/dist/lib/focus-ring.js +18 -0
  139. package/dist/lib/focus-ring.js.map +1 -0
  140. package/dist/lib/icon-stroke.d.ts +12 -0
  141. package/dist/lib/icon-stroke.d.ts.map +1 -0
  142. package/dist/lib/icon-stroke.js +12 -0
  143. package/dist/lib/icon-stroke.js.map +1 -0
  144. package/dist/lib/toast-model.d.ts +46 -0
  145. package/dist/lib/toast-model.d.ts.map +1 -0
  146. package/dist/lib/toast-model.js +36 -0
  147. package/dist/lib/toast-model.js.map +1 -0
  148. package/dist/lib/tone-utils.d.ts +43 -9
  149. package/dist/lib/tone-utils.d.ts.map +1 -1
  150. package/dist/lib/tone-utils.js +36 -7
  151. package/dist/lib/tone-utils.js.map +1 -1
  152. package/dist/locale/index.d.ts +3 -1
  153. package/dist/locale/index.d.ts.map +1 -1
  154. package/dist/locale/index.js +2 -1
  155. package/dist/locale/index.js.map +1 -1
  156. package/dist/locale/locale-provider.d.ts +42 -1
  157. package/dist/locale/locale-provider.d.ts.map +1 -1
  158. package/dist/locale/locale-provider.js +44 -3
  159. package/dist/locale/locale-provider.js.map +1 -1
  160. package/dist/locale/message-types.d.ts +223 -0
  161. package/dist/locale/message-types.d.ts.map +1 -0
  162. package/dist/locale/message-types.js +7 -0
  163. package/dist/locale/message-types.js.map +1 -0
  164. package/dist/locale/messages-de.d.ts +9 -0
  165. package/dist/locale/messages-de.d.ts.map +1 -0
  166. package/dist/locale/messages-de.js +151 -0
  167. package/dist/locale/messages-de.js.map +1 -0
  168. package/dist/locale/messages-en.d.ts +8 -0
  169. package/dist/locale/messages-en.d.ts.map +1 -0
  170. package/dist/locale/messages-en.js +150 -0
  171. package/dist/locale/messages-en.js.map +1 -0
  172. package/dist/locale/toolkit-messages.d.ts +39 -0
  173. package/dist/locale/toolkit-messages.d.ts.map +1 -0
  174. package/dist/locale/toolkit-messages.js +50 -0
  175. package/dist/locale/toolkit-messages.js.map +1 -0
  176. package/dist/primitives/alert.d.ts.map +1 -1
  177. package/dist/primitives/alert.js +4 -2
  178. package/dist/primitives/alert.js.map +1 -1
  179. package/dist/primitives/badge.d.ts +1 -1
  180. package/dist/primitives/badge.d.ts.map +1 -1
  181. package/dist/primitives/badge.js +14 -6
  182. package/dist/primitives/badge.js.map +1 -1
  183. package/dist/primitives/button.d.ts.map +1 -1
  184. package/dist/primitives/button.js +12 -8
  185. package/dist/primitives/button.js.map +1 -1
  186. package/dist/primitives/card.js +4 -4
  187. package/dist/primitives/card.js.map +1 -1
  188. package/dist/primitives/dialog.d.ts +7 -3
  189. package/dist/primitives/dialog.d.ts.map +1 -1
  190. package/dist/primitives/dialog.js +14 -7
  191. package/dist/primitives/dialog.js.map +1 -1
  192. package/dist/primitives/input.d.ts.map +1 -1
  193. package/dist/primitives/input.js +4 -1
  194. package/dist/primitives/input.js.map +1 -1
  195. package/dist/primitives/scroll-area.js +1 -1
  196. package/dist/primitives/scroll-area.js.map +1 -1
  197. package/dist/primitives/select.d.ts.map +1 -1
  198. package/dist/primitives/select.js +5 -2
  199. package/dist/primitives/select.js.map +1 -1
  200. package/dist/primitives/sheet.d.ts +3 -1
  201. package/dist/primitives/sheet.d.ts.map +1 -1
  202. package/dist/primitives/sheet.js +12 -6
  203. package/dist/primitives/sheet.js.map +1 -1
  204. package/dist/primitives/skeleton.js +1 -1
  205. package/dist/primitives/skeleton.js.map +1 -1
  206. package/dist/primitives/switch.d.ts.map +1 -1
  207. package/dist/primitives/switch.js +2 -1
  208. package/dist/primitives/switch.js.map +1 -1
  209. package/dist/primitives/tabs.d.ts.map +1 -1
  210. package/dist/primitives/tabs.js +5 -3
  211. package/dist/primitives/tabs.js.map +1 -1
  212. package/dist/render-cases.d.ts.map +1 -1
  213. package/dist/render-cases.js +20 -2
  214. package/dist/render-cases.js.map +1 -1
  215. package/dist/render-scan.d.ts +25 -0
  216. package/dist/render-scan.d.ts.map +1 -0
  217. package/dist/render-scan.js +66 -0
  218. package/dist/render-scan.js.map +1 -0
  219. package/dist/theme/cd-tokens.d.ts +66 -0
  220. package/dist/theme/cd-tokens.d.ts.map +1 -0
  221. package/dist/theme/cd-tokens.js +64 -0
  222. package/dist/theme/cd-tokens.js.map +1 -0
  223. package/dist/theme/create-theme.d.ts +196 -39
  224. package/dist/theme/create-theme.d.ts.map +1 -1
  225. package/dist/theme/create-theme.js +189 -13
  226. package/dist/theme/create-theme.js.map +1 -1
  227. package/dist/theme/fonts.d.ts +38 -0
  228. package/dist/theme/fonts.d.ts.map +1 -0
  229. package/dist/theme/fonts.js +56 -0
  230. package/dist/theme/fonts.js.map +1 -0
  231. package/dist/theme/host-fonts.d.ts +39 -0
  232. package/dist/theme/host-fonts.d.ts.map +1 -0
  233. package/dist/theme/host-fonts.js +44 -0
  234. package/dist/theme/host-fonts.js.map +1 -0
  235. package/dist/theme/index.d.ts +8 -1
  236. package/dist/theme/index.d.ts.map +1 -1
  237. package/dist/theme/index.js +5 -1
  238. package/dist/theme/index.js.map +1 -1
  239. package/dist/theme/portal-container.d.ts +16 -0
  240. package/dist/theme/portal-container.d.ts.map +1 -0
  241. package/dist/theme/portal-container.js +19 -0
  242. package/dist/theme/portal-container.js.map +1 -0
  243. package/dist/theme/presets.d.ts +24 -9
  244. package/dist/theme/presets.d.ts.map +1 -1
  245. package/dist/theme/presets.js +44 -11
  246. package/dist/theme/presets.js.map +1 -1
  247. package/dist/theme/theme-provider.d.ts +27 -9
  248. package/dist/theme/theme-provider.d.ts.map +1 -1
  249. package/dist/theme/theme-provider.js +42 -20
  250. package/dist/theme/theme-provider.js.map +1 -1
  251. package/package.json +9 -4
  252. package/src/app/builder/BuilderBanners.tsx +26 -13
  253. package/src/app/builder/CanvasRow.tsx +22 -21
  254. package/src/app/builder/CatalogueSheet.tsx +16 -14
  255. package/src/app/builder/EmptyHint.tsx +16 -11
  256. package/src/app/builder/LayoutSurface.tsx +7 -7
  257. package/src/app/builder/PipelineStrip.tsx +26 -17
  258. package/src/app/builder/SaveDialog.tsx +11 -12
  259. package/src/app/builder/Toolbar.tsx +19 -7
  260. package/src/app/builder/Workspace.tsx +5 -3
  261. package/src/app/builder/builder-feedback.test.tsx +75 -0
  262. package/src/app/builder/builder-model.test.ts +5 -0
  263. package/src/app/builder/builder-model.ts +5 -4
  264. package/src/app/builder/labels.ts +15 -121
  265. package/src/app/builder/status-chip.tsx +8 -4
  266. package/src/app/builder/use-builder-catalogue.ts +6 -0
  267. package/src/app/builder/use-save-dashboard.ts +5 -0
  268. package/src/app/index.ts +8 -1
  269. package/src/app/layout-builder.tsx +23 -3
  270. package/src/app/mcp-app-view-chrome.test.tsx +347 -14
  271. package/src/app/mcp-app-view-chrome.tsx +278 -100
  272. package/src/app/mcp-app-view-states.tsx +100 -0
  273. package/src/app/mcp-app-view.test.tsx +258 -0
  274. package/src/app/mcp-app-view.tsx +292 -185
  275. package/src/app/mcp-toolkit-app.tsx +31 -7
  276. package/src/app/use-view-lifecycle.ts +116 -0
  277. package/src/app/view-lifecycle.test.ts +227 -0
  278. package/src/app/view-lifecycle.ts +174 -0
  279. package/src/app/widget-props-sheet.tsx +13 -26
  280. package/src/app/widget-renderer.render.test.tsx +131 -0
  281. package/src/app/widget-renderer.tsx +136 -38
  282. package/src/components/DrillButton.tsx +2 -1
  283. package/src/components/EmptyState.tsx +81 -0
  284. package/src/components/ErrorState.tsx +182 -0
  285. package/src/components/FilterBar.tsx +9 -6
  286. package/src/components/GroupCard.tsx +3 -2
  287. package/src/components/KpiGrid.tsx +75 -44
  288. package/src/components/ListFooter.tsx +17 -6
  289. package/src/components/Pills.tsx +20 -11
  290. package/src/components/Toast.tsx +188 -0
  291. package/src/components/WidgetHeader.tsx +1 -1
  292. package/src/components/feedback-states.test.tsx +225 -0
  293. package/src/components/tone-figures.test.tsx +54 -0
  294. package/src/fonts.css +19 -0
  295. package/src/globals.css +9 -165
  296. package/src/hooks/use-tool-result-recovery.test.ts +25 -1
  297. package/src/hooks/use-tool-result-recovery.ts +34 -6
  298. package/src/hooks/use-view-data.test.tsx +60 -0
  299. package/src/hooks/use-view-data.ts +12 -1
  300. package/src/i18n-coverage.test.tsx +323 -0
  301. package/src/index.ts +57 -5
  302. package/src/lib/focus-ring.ts +20 -0
  303. package/src/lib/icon-stroke.test.ts +59 -0
  304. package/src/lib/icon-stroke.ts +13 -0
  305. package/src/lib/toast-model.test.ts +68 -0
  306. package/src/lib/toast-model.ts +63 -0
  307. package/src/lib/tone-utils.test.ts +47 -14
  308. package/src/lib/tone-utils.ts +61 -13
  309. package/src/locale/index.ts +13 -1
  310. package/src/locale/locale-provider.test.tsx +87 -0
  311. package/src/locale/locale-provider.tsx +90 -3
  312. package/src/locale/message-types.ts +221 -0
  313. package/src/locale/messages-de.ts +159 -0
  314. package/src/locale/messages-en.ts +157 -0
  315. package/src/locale/toolkit-messages.test.ts +216 -0
  316. package/src/locale/toolkit-messages.ts +82 -0
  317. package/src/no-hardcoded-copy.test.ts +432 -0
  318. package/src/primitives/alert.tsx +4 -2
  319. package/src/primitives/badge.tsx +14 -7
  320. package/src/primitives/button.tsx +13 -8
  321. package/src/primitives/card.tsx +4 -4
  322. package/src/primitives/dialog.tsx +32 -7
  323. package/src/primitives/input.tsx +5 -3
  324. package/src/primitives/scroll-area.tsx +1 -1
  325. package/src/primitives/select.tsx +6 -3
  326. package/src/primitives/sheet.tsx +26 -6
  327. package/src/primitives/skeleton.tsx +1 -1
  328. package/src/primitives/switch.tsx +4 -2
  329. package/src/primitives/tabs.tsx +9 -6
  330. package/src/render-cases.tsx +45 -1
  331. package/src/render-scan.ts +75 -0
  332. package/src/theme/cd-tokens.generated.css +48 -0
  333. package/src/theme/cd-tokens.test.ts +107 -0
  334. package/src/theme/cd-tokens.ts +67 -0
  335. package/src/theme/create-theme.test.ts +243 -5
  336. package/src/theme/create-theme.ts +313 -41
  337. package/src/theme/fonts.test.ts +85 -0
  338. package/src/theme/fonts.ts +72 -0
  339. package/src/theme/host-fonts.test.ts +135 -0
  340. package/src/theme/host-fonts.ts +52 -0
  341. package/src/theme/index.ts +15 -1
  342. package/src/theme/portal-container.ts +20 -0
  343. package/src/theme/presets.ts +44 -11
  344. package/src/theme/theme-css.test.ts +568 -0
  345. package/src/theme/theme-provider.test.tsx +82 -0
  346. package/src/theme/theme-provider.tsx +60 -24
  347. package/src/theme.css +314 -0
  348. package/ui-catalog.json +344 -17
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miragon/mcp-toolkit-ui",
3
- "version": "2.6.0",
3
+ "version": "2.7.0",
4
4
  "description": "React UI for MCP servers: shadcn primitives, composite components, TanStack Query hooks, and the MCP App shell (McpAppView + WidgetRenderer).",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,7 +26,10 @@
26
26
  "types": "./dist/hooks/index.d.ts",
27
27
  "import": "./dist/hooks/index.js"
28
28
  },
29
- "./globals.css": "./src/globals.css"
29
+ "./globals.css": "./src/globals.css",
30
+ "./theme.css": "./src/theme.css",
31
+ "./fonts.css": "./src/fonts.css",
32
+ "./cd-tokens.generated.css": "./src/theme/cd-tokens.generated.css"
30
33
  },
31
34
  "files": [
32
35
  "dist",
@@ -42,11 +45,12 @@
42
45
  "react": "^19.2.0",
43
46
  "react-dom": "^19.2.0",
44
47
  "tailwindcss": "^4.3.0",
45
- "@miragon/mcp-toolkit-core": "~2.6.0"
48
+ "@miragon/mcp-toolkit-core": "~2.7.0"
46
49
  },
47
50
  "dependencies": {
48
51
  "@base-ui/react": "1.8.0",
49
52
  "@fontsource-variable/geist": "5.3.0",
53
+ "@fontsource-variable/geist-mono": "5.3.0",
50
54
  "@tanstack/react-query": "5.103.2",
51
55
  "class-variance-authority": "0.7.1",
52
56
  "clsx": "2.1.1",
@@ -58,6 +62,7 @@
58
62
  "devDependencies": {
59
63
  "@stryker-mutator/core": "10.0.0",
60
64
  "@stryker-mutator/vitest-runner": "10.0.0",
65
+ "@types/node": "26.6.4",
61
66
  "@types/react": "19.3.0",
62
67
  "@types/react-dom": "19.3.0",
63
68
  "@vitest/coverage-v8": "4.1.11",
@@ -68,7 +73,7 @@
68
73
  "tailwindcss": "4.3.3",
69
74
  "typescript": "6.0.3",
70
75
  "vitest": "4.1.11",
71
- "@miragon/mcp-toolkit-core": "2.6.0"
76
+ "@miragon/mcp-toolkit-core": "2.7.0"
72
77
  },
73
78
  "engines": {
74
79
  "node": ">=22.22.2"
@@ -1,4 +1,6 @@
1
1
  import { AlertTriangle } from "lucide-react"
2
+ import { ErrorState } from "../../components/ErrorState.js"
3
+ import { TONE_ICON } from "../../lib/tone-utils.js"
2
4
  import type { LayoutBuilderLabels } from "./labels.js"
3
5
 
4
6
  // -------------------------------------------------------------------------- //
@@ -9,7 +11,10 @@ import type { LayoutBuilderLabels } from "./labels.js"
9
11
  * Inline feedback strip under the toolbar. Surfaces two things the builder
10
12
  * previously swallowed:
11
13
  * - a `get-builder-catalogue` fetch failure (Finding [3]) — only console-
12
- * logged before, so the user couldn't tell the palette was stale, and
14
+ * logged before, so the user couldn't tell the palette was stale. It is
15
+ * the kit's inline `ErrorState`: what happened, what to do, a retry, and
16
+ * the tool name with the raw reason under "Details" (never in the
17
+ * sentence the user reads), and
13
18
  * - pipeline validation issues (Finding [10]/[6]) the catalogue reports
14
19
  * fail-soft but `render-view` would reject fail-hard.
15
20
  *
@@ -21,29 +26,37 @@ export function BuilderBanners({
21
26
  catalogueToolName,
22
27
  refreshError,
23
28
  validationIssues,
29
+ onRetry,
24
30
  }: {
25
31
  L: Required<LayoutBuilderLabels>
26
32
  catalogueToolName: string
27
33
  refreshError: string | null
28
34
  validationIssues: string[]
35
+ /** Fetches the catalogue again; the error state offers it as its retry. */
36
+ onRetry?: () => void
29
37
  }) {
30
38
  if (!refreshError && validationIssues.length === 0) return null
31
39
  return (
32
- <div className="flex flex-col gap-2" role="status" aria-live="polite">
40
+ <div className="flex flex-col gap-2">
33
41
  {refreshError && (
34
- <div className="border-destructive/30 bg-destructive/10 text-destructive flex items-start gap-2 rounded-md border px-3 py-2 text-xs">
35
- <AlertTriangle className="mt-0.5 size-3.5 shrink-0" aria-hidden="true" />
36
- <div className="min-w-0">
37
- <p className="font-medium">{L.catalogueError}</p>
38
- <p className="text-destructive/80 mt-0.5 break-words">
39
- <span className="font-mono">{catalogueToolName}</span>: {refreshError}
40
- </p>
41
- </div>
42
- </div>
42
+ <ErrorState
43
+ variant="inline"
44
+ title={L.catalogueError}
45
+ description={L.catalogueErrorHint}
46
+ details={`${catalogueToolName}: ${refreshError}`}
47
+ onRetry={onRetry}
48
+ />
43
49
  )}
44
50
  {validationIssues.length > 0 && (
45
- <div className="flex items-start gap-2 rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-xs text-amber-700 dark:text-amber-400">
46
- <AlertTriangle className="mt-0.5 size-3.5 shrink-0" aria-hidden="true" />
51
+ <div
52
+ role="status"
53
+ aria-live="polite"
54
+ className="border-warning bg-warning-soft text-foreground flex items-start gap-2 rounded-md border px-3 py-2 text-xs"
55
+ >
56
+ <AlertTriangle
57
+ className={`mt-0.5 size-3.5 shrink-0 ${TONE_ICON.warning}`}
58
+ aria-hidden="true"
59
+ />
47
60
  <div className="min-w-0">
48
61
  <p className="font-medium">{L.validationWarning}</p>
49
62
  <ul className="mt-0.5 list-inside list-disc space-y-0.5">
@@ -82,7 +82,7 @@ function CanvasRowImpl({
82
82
  }
83
83
  }}
84
84
  className={cn(
85
- "group relative rounded-lg border-l-2 px-3 py-2 transition-colors",
85
+ "group relative rounded-md border-l-2 px-3 py-2 transition-colors",
86
86
  focused
87
87
  ? "border-l-primary bg-card"
88
88
  : "bg-background hover:bg-card/60 border-l-transparent",
@@ -91,12 +91,12 @@ function CanvasRowImpl({
91
91
  >
92
92
  <div className="mb-2 flex items-center justify-between">
93
93
  <div className="text-muted-foreground flex items-center gap-2 text-[11px]">
94
- <span className="font-mono">row {rowIdx + 1}</span>
94
+ <span className="font-mono">{L.rowLabel(rowIdx + 1)}</span>
95
95
  <span className="bg-muted/60 rounded px-1 py-0.5 text-[10px]">
96
- {row.row.length} {row.row.length === 1 ? "widget" : "widgets"}
96
+ {L.elementCount(row.row.length)}
97
97
  </span>
98
98
  </div>
99
- <div className="opacity-0 transition-opacity group-hover:opacity-100">
99
+ <div className="opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100">
100
100
  <Button
101
101
  variant="ghost"
102
102
  size="icon-xs"
@@ -105,7 +105,7 @@ function CanvasRowImpl({
105
105
  onMoveRow(rowIdx, -1)
106
106
  }}
107
107
  disabled={isFirst}
108
- aria-label="Move row up"
108
+ aria-label={L.moveRowUp}
109
109
  >
110
110
  <ChevronUp />
111
111
  </Button>
@@ -117,7 +117,7 @@ function CanvasRowImpl({
117
117
  onMoveRow(rowIdx, 1)
118
118
  }}
119
119
  disabled={isLast}
120
- aria-label="Move row down"
120
+ aria-label={L.moveRowDown}
121
121
  >
122
122
  <ChevronDown />
123
123
  </Button>
@@ -139,8 +139,8 @@ function CanvasRowImpl({
139
139
  className={cn(
140
140
  "rounded-md border border-dashed py-6 text-center text-[11px] transition-colors",
141
141
  dragOver
142
- ? "border-primary bg-primary/5 text-primary"
143
- : "border-muted-foreground/30 text-muted-foreground",
142
+ ? "border-focus bg-accent text-accent-foreground"
143
+ : "border-input text-muted-foreground",
144
144
  )}
145
145
  >
146
146
  {dragOver ? L.canvasDropHint : L.canvasEmpty}
@@ -155,20 +155,20 @@ function CanvasRowImpl({
155
155
  const propCount = cell.props ? Object.keys(cell.props).length : 0
156
156
  return (
157
157
  <GridItem key={`${cell.widget}-${cellIdx}`} span={span}>
158
- <div className="border-border bg-card relative overflow-hidden rounded-md border shadow-sm">
158
+ <div className="border-border bg-card shadow-1 relative overflow-hidden rounded-md border">
159
159
  <div className="text-muted-foreground bg-muted/30 flex items-center justify-between gap-2 border-b px-2 py-1 text-[10px]">
160
160
  <span className="truncate font-mono">{cell.widget}</span>
161
161
  <div className="flex shrink-0 items-center gap-2">
162
- <label className="text-muted-foreground/80 inline-flex items-center gap-0.5 font-mono">
163
- <span className="sr-only">Column span</span>
162
+ <label className="text-muted-foreground inline-flex items-center gap-0.5 font-mono">
163
+ <span className="sr-only">{L.columnSpan}</span>
164
164
  <Select
165
165
  value={String(span)}
166
166
  onValueChange={(v) => onSetCellSpan(rowIdx, cellIdx, Number(v))}
167
167
  >
168
168
  <SelectTrigger
169
169
  size="sm"
170
- className="h-5 gap-0.5 border-none bg-transparent px-1 py-0 font-mono text-[10px] shadow-none focus:ring-0"
171
- aria-label="Column span"
170
+ className="h-5 gap-0.5 border-none bg-transparent px-1 py-0 font-mono text-[10px] shadow-none"
171
+ aria-label={L.columnSpan}
172
172
  onClick={(e) => e.stopPropagation()}
173
173
  >
174
174
  <SelectValue />
@@ -191,13 +191,11 @@ function CanvasRowImpl({
191
191
  e.stopPropagation()
192
192
  onConfigureCell(rowIdx, cellIdx)
193
193
  }}
194
- aria-label="Configure widget props"
194
+ aria-label={L.configureElement}
195
195
  title={
196
- propCount > 0
197
- ? `Configure widget props (${propCount} set)`
198
- : "Configure widget props"
196
+ propCount > 0 ? L.configureElementSet(propCount) : L.configureElement
199
197
  }
200
- className={cn(propCount > 0 && "text-primary")}
198
+ className={cn(propCount > 0 && "text-link")}
201
199
  >
202
200
  <SlidersHorizontal />
203
201
  </Button>
@@ -209,7 +207,7 @@ function CanvasRowImpl({
209
207
  e.stopPropagation()
210
208
  onRemoveCell(rowIdx, cellIdx)
211
209
  }}
212
- aria-label="Remove widget"
210
+ aria-label={L.removeElement}
213
211
  >
214
212
  <X />
215
213
  </Button>
@@ -226,6 +224,7 @@ function CanvasRowImpl({
226
224
  />
227
225
  </div>
228
226
  <ResizeHandle
227
+ label={L.resizeElement}
229
228
  rowIdx={rowIdx}
230
229
  cellIdx={cellIdx}
231
230
  span={span}
@@ -250,12 +249,14 @@ export const CanvasRow = memo(CanvasRowImpl)
250
249
  // -------------------------------------------------------------------------- //
251
250
 
252
251
  function ResizeHandle({
252
+ label,
253
253
  rowIdx,
254
254
  cellIdx,
255
255
  span,
256
256
  getGridEl,
257
257
  onSetCellSpan,
258
258
  }: {
259
+ label: string
259
260
  rowIdx: number
260
261
  cellIdx: number
261
262
  span: number
@@ -304,8 +305,8 @@ function ResizeHandle({
304
305
  onClick={(e) => e.stopPropagation()}
305
306
  onKeyDown={onKeyDown}
306
307
  tabIndex={0}
307
- className="hover:bg-primary/40 focus-visible:bg-primary/40 focus-visible:ring-primary/60 absolute top-0 right-0 h-full w-1.5 cursor-col-resize touch-none select-none focus-visible:ring-1 focus-visible:outline-none"
308
- aria-label="Resize widget"
308
+ className="hover:bg-primary/40 focus-visible:bg-primary/40 focus-visible:ring-focus absolute top-0 right-0 h-full w-1.5 cursor-col-resize touch-none select-none focus-visible:ring-2 focus-visible:outline-hidden"
309
+ aria-label={label}
309
310
  role="separator"
310
311
  aria-orientation="vertical"
311
312
  aria-valuenow={span}
@@ -11,6 +11,7 @@ import {
11
11
  SheetHeader,
12
12
  SheetTitle,
13
13
  } from "../../primitives/sheet.js"
14
+ import { TONE_DOT, TONE_TEXT } from "../../lib/tone-utils.js"
14
15
  import { cn } from "../../lib/utils.js"
15
16
  import type { LayoutBuilderLabels } from "./labels.js"
16
17
 
@@ -59,28 +60,29 @@ export function CatalogueSheet({
59
60
  <section className="flex flex-col gap-2">
60
61
  <CatalogueSectionHeader title={L.catalogueKeys} count={keyCatalog.length} />
61
62
  {keyCatalog.length === 0 ? (
62
- <p className="text-muted-foreground text-xs italic">No keys registered.</p>
63
+ <p className="text-muted-foreground text-xs italic">{L.noKeysRegistered}</p>
63
64
  ) : (
64
65
  <ul className="flex flex-col gap-1">
65
66
  {keyCatalog.map((entry) => (
66
67
  <li
67
68
  key={entry.key}
68
- className="hover:bg-accent/40 flex flex-col gap-1 rounded-md border px-2 py-1.5 text-xs"
69
+ className="hover:bg-muted flex flex-col gap-1 rounded-md border px-2 py-1.5 text-xs"
69
70
  >
70
71
  <div className="flex items-start justify-between gap-2">
71
72
  <span className="truncate font-mono">{entry.key}</span>
72
73
  <span
73
74
  className={cn(
74
75
  "inline-flex shrink-0 items-center gap-1 text-[10px]",
75
- entry.inContext
76
- ? "text-emerald-600 dark:text-emerald-400"
77
- : "text-muted-foreground",
76
+ entry.inContext ? TONE_TEXT.success : "text-muted-foreground",
78
77
  )}
79
78
  >
80
79
  <span
80
+ aria-hidden="true"
81
81
  className={cn(
82
82
  "size-1.5 rounded-full",
83
- entry.inContext ? "bg-emerald-500" : "bg-muted-foreground/40",
83
+ entry.inContext
84
+ ? TONE_DOT.success
85
+ : "border-input border bg-transparent",
84
86
  )}
85
87
  />
86
88
  {entry.inContext ? L.catalogueLive : L.catalogueMissing}
@@ -89,7 +91,7 @@ export function CatalogueSheet({
89
91
  {(entry.producedBySteps.length > 0 ||
90
92
  entry.consumedBySteps.length > 0 ||
91
93
  entry.consumedByWidgets.length > 0) && (
92
- <div className="text-muted-foreground/80 flex flex-col gap-0.5 text-[10px]">
94
+ <div className="text-muted-foreground flex flex-col gap-0.5 text-[10px]">
93
95
  {entry.producedBySteps.length > 0 && (
94
96
  <span>
95
97
  {L.catalogueProducedBy}{" "}
@@ -98,13 +100,13 @@ export function CatalogueSheet({
98
100
  )}
99
101
  {entry.consumedBySteps.length > 0 && (
100
102
  <span>
101
- step {L.catalogueConsumedBy}{" "}
103
+ {L.catalogueStepPrefix} {L.catalogueConsumedBy}{" "}
102
104
  <span className="font-mono">{entry.consumedBySteps.join(", ")}</span>
103
105
  </span>
104
106
  )}
105
107
  {entry.consumedByWidgets.length > 0 && (
106
108
  <span>
107
- widget {L.catalogueConsumedBy}{" "}
109
+ {L.catalogueElementPrefix} {L.catalogueConsumedBy}{" "}
108
110
  <span className="font-mono">
109
111
  {entry.consumedByWidgets.join(", ")}
110
112
  </span>
@@ -137,7 +139,7 @@ export function CatalogueSheet({
137
139
  <section className="flex flex-col gap-2">
138
140
  <CatalogueSectionHeader title={L.catalogueSteps} count={availableSteps.length} />
139
141
  {availableSteps.length === 0 ? (
140
- <p className="text-muted-foreground text-xs italic">No steps registered.</p>
142
+ <p className="text-muted-foreground text-xs italic">{L.noStepsRegistered}</p>
141
143
  ) : (
142
144
  <ul className="flex flex-col gap-1">
143
145
  {availableSteps.map((s) => (
@@ -146,12 +148,12 @@ export function CatalogueSheet({
146
148
  className="flex flex-col gap-0.5 rounded-md border px-2 py-1.5 text-xs"
147
149
  >
148
150
  <span className="truncate font-mono">{s.id}</span>
149
- <div className="text-muted-foreground/80 flex flex-wrap gap-x-3 text-[10px]">
151
+ <div className="text-muted-foreground flex flex-wrap gap-x-3 text-[10px]">
150
152
  <span className="font-mono">
151
- ← {s.requires.length ? s.requires.join(", ") : "(no inputs)"}
153
+ ← {s.requires.length ? s.requires.join(", ") : L.noInputs}
152
154
  </span>
153
155
  <span className="font-mono">
154
- → {s.produces.length ? s.produces.join(", ") : "(none)"}
156
+ → {s.produces.length ? s.produces.join(", ") : L.noOutputs}
155
157
  </span>
156
158
  </div>
157
159
  </li>
@@ -181,7 +183,7 @@ export function CatalogueSheet({
181
183
  className="bg-muted/20 flex flex-col gap-1 rounded-md border px-2 py-1.5 text-xs"
182
184
  >
183
185
  <span className="truncate font-mono">{w.id}</span>
184
- <span className="text-muted-foreground/80 text-[10px]">
186
+ <span className="text-muted-foreground text-[10px]">
185
187
  {L.catalogueNeedsKeys}{" "}
186
188
  <span className="font-mono">{w.missingKeys.join(", ")}</span>
187
189
  </span>
@@ -1,4 +1,5 @@
1
1
  import { Layers, SlidersHorizontal } from "lucide-react"
2
+ import { EmptyState } from "../../components/EmptyState.js"
2
3
  import { Button } from "../../primitives/button.js"
3
4
  import type { LayoutBuilderLabels } from "./labels.js"
4
5
 
@@ -6,6 +7,10 @@ import type { LayoutBuilderLabels } from "./labels.js"
6
7
  // Empty hint — shown on the Layout view before any widget is reachable
7
8
  // -------------------------------------------------------------------------- //
8
9
 
10
+ /**
11
+ * The builder's empty state. Its one next step (open the data sources) is the
12
+ * blue primary button: CI §9 "Leer: Empty-State-Karte mit blauem CTA".
13
+ */
9
14
  export function EmptyHint({
10
15
  L,
11
16
  onGotoPipeline,
@@ -14,16 +19,16 @@ export function EmptyHint({
14
19
  onGotoPipeline: () => void
15
20
  }) {
16
21
  return (
17
- <div className="border-muted-foreground/30 text-muted-foreground flex flex-col items-center gap-2 rounded-md border border-dashed p-10 text-sm">
18
- <Layers className="size-5" />
19
- <p>No widgets reachable yet.</p>
20
- <p className="text-xs">
21
- Open the <span className="font-medium">{L.viewPipelineTab}</span> tab to seed keys or add a
22
- pipeline step — the palette unlocks as soon as a widget's contract is satisfied.
23
- </p>
24
- <Button variant="outline" size="sm" onClick={onGotoPipeline} className="mt-1">
25
- <SlidersHorizontal /> Open {L.viewPipelineTab}
26
- </Button>
27
- </div>
22
+ <EmptyState
23
+ className="rounded-md border border-dashed"
24
+ icon={<Layers />}
25
+ title={L.emptyHintTitle}
26
+ description={L.emptyHintBody}
27
+ action={
28
+ <Button size="sm" onClick={onGotoPipeline}>
29
+ <SlidersHorizontal aria-hidden="true" /> {L.openDataSources}
30
+ </Button>
31
+ }
32
+ />
28
33
  )
29
34
  }
@@ -65,7 +65,7 @@ export function LayoutSurface({
65
65
  value={tab.label}
66
66
  className="pr-7"
67
67
  onDoubleClick={() => setEditingTabIdx(idx)}
68
- title="Double-click to rename"
68
+ title={L.renameTitle}
69
69
  >
70
70
  {editingTabIdx === idx ? (
71
71
  <input
@@ -87,8 +87,8 @@ export function LayoutSurface({
87
87
  commitRename(idx, e.currentTarget.value)
88
88
  }}
89
89
  className={cn(
90
- "w-24 border-none bg-transparent p-0 text-inherit outline-none focus:ring-0",
91
- renameRejected && "text-destructive",
90
+ "focus-visible:ring-focus w-24 rounded-sm border-none bg-transparent p-0 text-inherit outline-hidden focus-visible:ring-2",
91
+ renameRejected && "text-danger-ink",
92
92
  )}
93
93
  size={Math.max(tab.label.length + 1, 6)}
94
94
  />
@@ -105,7 +105,7 @@ export function LayoutSurface({
105
105
  e.stopPropagation()
106
106
  onRemoveTab(idx)
107
107
  }}
108
- className="text-muted-foreground/60 hover:text-foreground absolute top-1/2 right-1 -translate-y-1/2 rounded p-0.5 opacity-0 transition-opacity group-hover:opacity-100 data-[state=active]:opacity-100"
108
+ className="text-muted-foreground hover:text-foreground absolute top-1/2 right-1 -translate-y-1/2 rounded-sm p-0.5 opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100 data-[state=active]:opacity-100"
109
109
  >
110
110
  <X className="size-3" />
111
111
  </button>
@@ -121,12 +121,12 @@ export function LayoutSurface({
121
121
  >
122
122
  <Plus />
123
123
  </Button>
124
- <span className="text-muted-foreground/70 ml-2 hidden text-[10px] sm:inline">
125
- double-click a tab to rename
124
+ <span className="text-muted-foreground ml-2 hidden text-[10px] sm:inline">
125
+ {L.renameHint}
126
126
  </span>
127
127
  </div>
128
128
  {renameRejected && editingTabIdx !== null && (
129
- <p className="text-destructive -mt-1 mb-2 text-[11px]" role="alert">
129
+ <p className="text-danger-ink -mt-1 mb-2 text-[11px]" role="alert">
130
130
  {L.renameDuplicate}
131
131
  </p>
132
132
  )}
@@ -1,6 +1,7 @@
1
1
  import { memo, useMemo } from "react"
2
- import { ArrowDownToLine, ArrowRightToLine, Plus, X } from "lucide-react"
2
+ import { ArrowDownToLine, ArrowRightToLine, LucideProvider, Plus, X } from "lucide-react"
3
3
  import type { AvailableStep, KeyCatalogEntry, PipelineStepRef } from "@miragon/mcp-toolkit-core"
4
+ import { DENSE_ICON_STROKE } from "../../lib/icon-stroke.js"
4
5
  import { Badge } from "../../primitives/badge.js"
5
6
  import { Button } from "../../primitives/button.js"
6
7
  import { Input } from "../../primitives/input.js"
@@ -62,7 +63,7 @@ function PipelineStripImpl({
62
63
  <span className="text-muted-foreground text-[10px] font-semibold tracking-[0.12em] uppercase">
63
64
  {L.pipelineHeader}
64
65
  </span>
65
- <span className="text-muted-foreground/60 text-[10px]">· auto-applies on edit</span>
66
+ <span className="text-muted-foreground text-[10px]">· {L.autoApplies}</span>
66
67
  <div className="ml-auto text-[11px]">
67
68
  <StatusChip status={status} L={L} />
68
69
  </div>
@@ -84,7 +85,7 @@ function PipelineStripImpl({
84
85
  ) : (
85
86
  <ul className="flex flex-col gap-1">
86
87
  {keyEntries.map((entry, idx) => {
87
- const livePreview = formatLiveKeyValue(liveKeys[entry.name])
88
+ const livePreview = formatLiveKeyValue(liveKeys[entry.name], L.catalogueLive)
88
89
  const usedNames = new Set(
89
90
  keyEntries.map((e, i) => (i === idx ? "" : e.name)).filter(Boolean),
90
91
  )
@@ -103,7 +104,7 @@ function PipelineStripImpl({
103
104
  <SelectContent>
104
105
  {pickable.length === 0 ? (
105
106
  <div className="text-muted-foreground px-2 py-1.5 text-xs italic">
106
- No more keys available.
107
+ {L.noMoreKeys}
107
108
  </div>
108
109
  ) : (
109
110
  pickable.map((c) => (
@@ -111,7 +112,7 @@ function PipelineStripImpl({
111
112
  <div className="flex flex-col gap-0.5">
112
113
  <span className="font-mono text-xs">{c.key}</span>
113
114
  {c.consumedByWidgets.length + c.consumedBySteps.length > 0 && (
114
- <span className="text-muted-foreground/80 font-mono text-[10px]">
115
+ <span className="text-muted-foreground font-mono text-[10px]">
115
116
  → {[...c.consumedByWidgets, ...c.consumedBySteps].join(", ")}
116
117
  </span>
117
118
  )}
@@ -130,17 +131,17 @@ function PipelineStripImpl({
130
131
  className="h-7 pr-10 font-mono text-xs"
131
132
  />
132
133
  <span
133
- className="text-muted-foreground/70 pointer-events-none absolute top-1/2 right-2 -translate-y-1/2 font-mono text-[10px] tabular-nums"
134
- title="parsed type"
134
+ className="text-muted-foreground pointer-events-none absolute top-1/2 right-2 -translate-y-1/2 font-mono text-[10px] tabular-nums"
135
+ title={L.parsedType}
135
136
  >
136
- {valueTypeLabel(entry.rawValue)}
137
+ {L.valueType(valueTypeLabel(entry.rawValue))}
137
138
  </span>
138
139
  </div>
139
140
  <Button
140
141
  variant="ghost"
141
142
  size="icon-xs"
142
143
  onClick={() => onRemoveKey(idx)}
143
- aria-label="Remove key"
144
+ aria-label={L.removeKey}
144
145
  >
145
146
  <X />
146
147
  </Button>
@@ -204,11 +205,10 @@ function PipelineStripImpl({
204
205
  <span className="font-mono text-xs">{s.id}</span>
205
206
  <div className="text-muted-foreground flex flex-wrap gap-x-2 text-[10px]">
206
207
  <span className="font-mono">
207
- ←{" "}
208
- {s.requires.length ? s.requires.join(", ") : "(no inputs)"}
208
+ ← {s.requires.length ? s.requires.join(", ") : L.noInputs}
209
209
  </span>
210
210
  <span className="font-mono">
211
- → {s.produces.length ? s.produces.join(", ") : "(none)"}
211
+ → {s.produces.length ? s.produces.join(", ") : L.noOutputs}
212
212
  </span>
213
213
  </div>
214
214
  </div>
@@ -222,18 +222,18 @@ function PipelineStripImpl({
222
222
  variant="ghost"
223
223
  size="icon-xs"
224
224
  onClick={() => onRemoveStep(idx)}
225
- aria-label="Remove step"
225
+ aria-label={L.removeStep}
226
226
  >
227
227
  <X />
228
228
  </Button>
229
229
  </div>
230
230
  {meta && (
231
- <div className="text-muted-foreground/80 ml-1 flex flex-wrap gap-x-2 pl-24 text-[10px]">
231
+ <div className="text-muted-foreground ml-1 flex flex-wrap gap-x-2 pl-24 text-[10px]">
232
232
  <span className="font-mono">
233
- ← {meta.requires.length ? meta.requires.join(", ") : "(no inputs)"}
233
+ ← {meta.requires.length ? meta.requires.join(", ") : L.noInputs}
234
234
  </span>
235
235
  <span className="font-mono">
236
- → {meta.produces.length ? meta.produces.join(", ") : "(none)"}
236
+ → {meta.produces.length ? meta.produces.join(", ") : L.noOutputs}
237
237
  </span>
238
238
  </div>
239
239
  )}
@@ -257,4 +257,13 @@ function PipelineStripImpl({
257
257
  )
258
258
  }
259
259
 
260
- export const PipelineStrip = memo(PipelineStripImpl)
260
+ /** A dense chrome bar: its Lucide icons draw at stroke 2 (CI §11), not the view's 2.5. */
261
+ function DensePipelineStrip(props: Parameters<typeof PipelineStripImpl>[0]) {
262
+ return (
263
+ <LucideProvider strokeWidth={DENSE_ICON_STROKE}>
264
+ <PipelineStripImpl {...props} />
265
+ </LucideProvider>
266
+ )
267
+ }
268
+
269
+ export const PipelineStrip = memo(DensePipelineStrip)
@@ -1,4 +1,4 @@
1
- import { AlertTriangle } from "lucide-react"
1
+ import { ErrorState } from "../../components/ErrorState.js"
2
2
  import { Button } from "../../primitives/button.js"
3
3
  import {
4
4
  Dialog,
@@ -52,7 +52,7 @@ export function SaveDialog({
52
52
  <Input
53
53
  value={name}
54
54
  onChange={(e) => setName(e.target.value)}
55
- placeholder="Invoice overview"
55
+ placeholder={L.saveNamePlaceholder}
56
56
  autoFocus
57
57
  />
58
58
  </label>
@@ -61,19 +61,18 @@ export function SaveDialog({
61
61
  <Input
62
62
  value={description}
63
63
  onChange={(e) => setDescription(e.target.value)}
64
- placeholder="Optional"
64
+ placeholder={L.saveDescriptionPlaceholder}
65
65
  />
66
66
  </label>
67
+ {/* What happened and what to do in catalog words; the raw reason
68
+ only under Details. The Save button below is the retry. */}
67
69
  {error && (
68
- <div
69
- className="border-destructive/30 bg-destructive/10 text-destructive flex items-start gap-2 rounded-md border px-3 py-2 text-xs"
70
- role="alert"
71
- >
72
- <AlertTriangle className="mt-0.5 size-3.5 shrink-0" aria-hidden="true" />
73
- <span className="min-w-0 break-words">
74
- <span className="font-medium">{L.saveError}:</span> {error}
75
- </span>
76
- </div>
70
+ <ErrorState
71
+ variant="inline"
72
+ title={L.saveError}
73
+ description={L.saveErrorHint}
74
+ details={error}
75
+ />
77
76
  )}
78
77
  </div>
79
78
  <DialogFooter>