@elabs-ai/components-ui 4.0.0 → 4.2.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 (339) hide show
  1. package/README.md +10 -8
  2. package/dist/chunk-FCH4ZN6G.js +57 -0
  3. package/dist/chunk-FCH4ZN6G.js.map +1 -0
  4. package/dist/form.d.ts +28 -0
  5. package/dist/form.js +123 -0
  6. package/dist/form.js.map +1 -0
  7. package/dist/index.d.ts +2597 -188
  8. package/dist/index.js +12855 -3578
  9. package/dist/index.js.map +1 -1
  10. package/dist/lib/cn.js +10 -1
  11. package/dist/lib/cn.js.map +1 -1
  12. package/package.json +21 -7
  13. package/src/__contract__/attribution-panel.contract.test.tsx +49 -0
  14. package/src/__contract__/badge.contract.test.tsx +49 -0
  15. package/src/__contract__/bounded-number.contract.test.tsx +49 -0
  16. package/src/__contract__/button.contract.test.tsx +49 -0
  17. package/src/__contract__/color-picker.contract.test.tsx +49 -0
  18. package/src/__contract__/combobox.contract.test.tsx +49 -0
  19. package/src/__contract__/command-trigger.contract.test.tsx +49 -0
  20. package/src/__contract__/copyable-value.contract.test.tsx +49 -0
  21. package/src/__contract__/date-picker.contract.test.tsx +49 -0
  22. package/src/__contract__/date-range-picker.contract.test.tsx +49 -0
  23. package/src/__contract__/empty-state.contract.test.tsx +49 -0
  24. package/src/__contract__/error-state.contract.test.tsx +49 -0
  25. package/src/__contract__/icon-button.contract.test.tsx +49 -0
  26. package/src/__contract__/input.contract.test.tsx +49 -0
  27. package/src/__contract__/key-value-editor.contract.test.tsx +49 -0
  28. package/src/__contract__/list-editor.contract.test.tsx +49 -0
  29. package/src/__contract__/loading-state.contract.test.tsx +49 -0
  30. package/src/__contract__/metric-card.contract.test.tsx +49 -0
  31. package/src/__contract__/model-picker.contract.test.tsx +49 -0
  32. package/src/__contract__/nav-notifications.contract.test.tsx +49 -0
  33. package/src/__contract__/number-input.contract.test.tsx +49 -0
  34. package/src/__contract__/rating.contract.test.tsx +49 -0
  35. package/src/__contract__/schema-form.contract.test.tsx +49 -0
  36. package/src/__contract__/section-header.contract.test.tsx +49 -0
  37. package/src/__contract__/segmented-field.contract.test.tsx +49 -0
  38. package/src/__contract__/slider-number.contract.test.tsx +49 -0
  39. package/src/__contract__/status-badge.contract.test.tsx +49 -0
  40. package/src/__contract__/tag-input.contract.test.tsx +49 -0
  41. package/src/__contract__/text.contract.test.tsx +49 -0
  42. package/src/__contract__/textarea.contract.test.tsx +49 -0
  43. package/src/__contract__/timeline.contract.test.tsx +49 -0
  44. package/src/__contract__/toggle.contract.test.tsx +49 -0
  45. package/src/__contract__/transfer.contract.test.tsx +49 -0
  46. package/src/__contract__/tree-select.contract.test.tsx +49 -0
  47. package/src/__contract__/tree.contract.test.tsx +49 -0
  48. package/src/__contract__/virtual-select.contract.test.tsx +49 -0
  49. package/src/__contract__/workspace-picker.contract.test.tsx +49 -0
  50. package/src/_peer-deps.test.ts +59 -0
  51. package/src/blocks-comparison-table.stories.tsx +2 -2
  52. package/src/components/accordion/accordion.tsx +13 -4
  53. package/src/components/advanced-group/advanced-group.tsx +1 -1
  54. package/src/components/alert/alert.tsx +15 -4
  55. package/src/components/alert-dialog/alert-dialog.test.tsx +32 -0
  56. package/src/components/alert-dialog/alert-dialog.tsx +28 -6
  57. package/src/components/app-shell/app-shell.stories.tsx +44 -2
  58. package/src/components/app-shell/app-shell.test.tsx +99 -0
  59. package/src/components/app-shell/app-shell.tsx +56 -4
  60. package/src/components/app-sidebar/app-sidebar.stories.tsx +9 -1
  61. package/src/components/attribution-panel/attribution-panel.tsx +3 -1
  62. package/src/components/attribution-panel/attributions.generated.ts +83 -79
  63. package/src/components/avatar/avatar.tsx +4 -1
  64. package/src/components/badge/badge.stories.tsx +8 -0
  65. package/src/components/badge/badge.test.tsx +8 -0
  66. package/src/components/badge/badge.tsx +15 -5
  67. package/src/components/bento-grid/bento-grid.test.tsx +24 -3
  68. package/src/components/bento-grid/bento-grid.tsx +11 -1
  69. package/src/components/bounded-number/bounded-number.test.tsx +1 -1
  70. package/src/components/bounded-number/bounded-number.tsx +78 -75
  71. package/src/components/breadcrumb/breadcrumb.stories.tsx +38 -0
  72. package/src/components/breadcrumb/breadcrumb.test.tsx +24 -1
  73. package/src/components/breadcrumb/breadcrumb.tsx +10 -4
  74. package/src/components/button/button.stories.tsx +116 -8
  75. package/src/components/button/button.tsx +18 -12
  76. package/src/components/button-group/button-group.tsx +1 -1
  77. package/src/components/calendar/calendar.tsx +3 -2
  78. package/src/components/card/card.stories.tsx +11 -6
  79. package/src/components/card/card.test.tsx +24 -2
  80. package/src/components/card/card.tsx +48 -7
  81. package/src/components/carousel/carousel.test.tsx +169 -0
  82. package/src/components/carousel/carousel.tsx +93 -32
  83. package/src/components/change-review/change-review.stories.tsx +145 -1
  84. package/src/components/change-review/change-review.test.tsx +143 -0
  85. package/src/components/change-review/change-review.tsx +214 -24
  86. package/src/components/checkbox/checkbox.tsx +2 -1
  87. package/src/components/collapsible-panel/use-collapsible-panel.ts +0 -3
  88. package/src/components/color-picker/color-picker.tsx +22 -15
  89. package/src/components/combobox/combobox.stories.tsx +10 -0
  90. package/src/components/combobox/combobox.test.tsx +54 -0
  91. package/src/components/combobox/combobox.tsx +25 -11
  92. package/src/components/command/command.stories.tsx +16 -6
  93. package/src/components/command/command.test.tsx +88 -0
  94. package/src/components/command/command.tsx +38 -13
  95. package/src/components/command/index.ts +1 -0
  96. package/src/components/command-trigger/command-trigger.stories.tsx +39 -0
  97. package/src/components/command-trigger/command-trigger.test.tsx +18 -0
  98. package/src/components/command-trigger/command-trigger.tsx +70 -0
  99. package/src/components/command-trigger/index.ts +1 -0
  100. package/src/components/context-menu/context-menu.tsx +16 -7
  101. package/src/components/context-rail/context-rail.stories.tsx +285 -0
  102. package/src/components/context-rail/context-rail.test.tsx +263 -0
  103. package/src/components/context-rail/context-rail.tsx +678 -0
  104. package/src/components/context-rail/index.ts +1 -0
  105. package/src/components/copyable-value/copyable-value.stories.tsx +10 -0
  106. package/src/components/copyable-value/copyable-value.test.tsx +28 -1
  107. package/src/components/copyable-value/copyable-value.tsx +13 -2
  108. package/src/components/date-picker/date-picker.stories.tsx +9 -0
  109. package/src/components/date-picker/date-picker.test.tsx +52 -0
  110. package/src/components/date-picker/date-picker.tsx +13 -15
  111. package/src/components/date-range-picker/date-range-picker.stories.tsx +9 -0
  112. package/src/components/date-range-picker/date-range-picker.test.tsx +76 -1
  113. package/src/components/date-range-picker/date-range-picker.tsx +39 -28
  114. package/src/components/descriptions/descriptions.tsx +1 -1
  115. package/src/components/dialog/dialog.test.tsx +8 -3
  116. package/src/components/dialog/dialog.tsx +14 -6
  117. package/src/components/drawer/drawer.tsx +23 -6
  118. package/src/components/dropdown-menu/dropdown-menu.stories.tsx +1 -1
  119. package/src/components/dropdown-menu/dropdown-menu.tsx +4 -4
  120. package/src/components/empty-state/empty-state.test.tsx +26 -0
  121. package/src/components/empty-state/empty-state.tsx +6 -1
  122. package/src/components/expand-dialog/expand-dialog.tsx +1 -2
  123. package/src/components/field/field-context.ts +59 -0
  124. package/src/components/field/field.stories.tsx +142 -0
  125. package/src/components/field/field.test.tsx +415 -0
  126. package/src/components/field/field.tsx +342 -0
  127. package/src/components/field/index.ts +9 -0
  128. package/src/components/field-row/field-row.stories.tsx +85 -1
  129. package/src/components/field-row/field-row.test.tsx +145 -0
  130. package/src/components/field-row/field-row.tsx +50 -6
  131. package/src/components/file-upload/file-upload.stories.tsx +16 -0
  132. package/src/components/file-upload/file-upload.test.tsx +90 -12
  133. package/src/components/file-upload/file-upload.tsx +164 -31
  134. package/src/components/form/form.tsx +8 -3
  135. package/src/components/hover-card/hover-card.tsx +1 -0
  136. package/src/components/icon-button/icon-button.test.tsx +1 -1
  137. package/src/components/icon-button/icon-button.tsx +3 -3
  138. package/src/components/input/input.stories.tsx +121 -0
  139. package/src/components/input/input.test.tsx +13 -0
  140. package/src/components/input/input.tsx +8 -4
  141. package/src/components/input-group/input-group.stories.tsx +74 -0
  142. package/src/components/input-group/input-group.tsx +18 -9
  143. package/src/components/input-otp/input-otp.tsx +8 -2
  144. package/src/components/keyboard-shortcuts/index.ts +6 -0
  145. package/src/components/keyboard-shortcuts/keyboard-shortcuts.stories.tsx +177 -0
  146. package/src/components/keyboard-shortcuts/keyboard-shortcuts.test.tsx +73 -0
  147. package/src/components/keyboard-shortcuts/keyboard-shortcuts.tsx +203 -0
  148. package/src/components/label/label.tsx +1 -1
  149. package/src/components/link-preview/link-preview.tsx +1 -1
  150. package/src/components/list-editor/list-editor.test.tsx +24 -0
  151. package/src/components/list-editor/list-editor.tsx +126 -52
  152. package/src/components/locale-provider/index.ts +6 -1
  153. package/src/components/locale-provider/locale-keys.test.ts +134 -0
  154. package/src/components/locale-provider/locale-provider.stories.tsx +89 -1
  155. package/src/components/locale-provider/locale-provider.test.tsx +257 -1
  156. package/src/components/locale-provider/locale-provider.tsx +63 -8
  157. package/src/components/locale-provider/messages.ts +822 -2
  158. package/src/components/match-highlight/match-highlight.stories.tsx +1 -1
  159. package/src/components/mention-input/mention-input-mirror.stories.tsx +9 -1
  160. package/src/components/mention-input/mention-input.stories.tsx +3 -3
  161. package/src/components/mention-input/mention-input.test.tsx +51 -1
  162. package/src/components/mention-input/mention-input.tsx +66 -46
  163. package/src/components/mention-input/mention-value.ts +8 -2
  164. package/src/components/menubar/menubar.tsx +16 -6
  165. package/src/components/metric-card/metric-card.stories.tsx +5 -3
  166. package/src/components/metric-card/metric-card.tsx +21 -2
  167. package/src/components/model-picker/model-picker.stories.tsx +63 -3
  168. package/src/components/model-picker/model-picker.test.tsx +63 -0
  169. package/src/components/model-picker/model-picker.tsx +62 -36
  170. package/src/components/nav-main/nav-main.tsx +8 -4
  171. package/src/components/nav-notifications/nav-notifications.stories.tsx +110 -0
  172. package/src/components/nav-notifications/nav-notifications.test.tsx +11 -0
  173. package/src/components/nav-notifications/nav-notifications.tsx +67 -9
  174. package/src/components/nav-user/nav-user.tsx +5 -5
  175. package/src/components/navigation-menu/navigation-menu.stories.tsx +22 -0
  176. package/src/components/navigation-menu/navigation-menu.test.tsx +174 -0
  177. package/src/components/navigation-menu/navigation-menu.tsx +204 -15
  178. package/src/components/number-input/number-input.stories.tsx +7 -3
  179. package/src/components/number-input/number-input.test.tsx +47 -0
  180. package/src/components/number-input/number-input.tsx +65 -24
  181. package/src/components/page-shell/page-shell.stories.tsx +163 -0
  182. package/src/components/page-shell/page-shell.test.tsx +229 -0
  183. package/src/components/page-shell/page-shell.tsx +108 -25
  184. package/src/components/pagination/pagination.test.tsx +109 -0
  185. package/src/components/pagination/pagination.tsx +27 -19
  186. package/src/components/popover/popover.tsx +1 -0
  187. package/src/components/progress/progress.tsx +2 -0
  188. package/src/components/radio-group/radio-group.tsx +1 -1
  189. package/src/components/rating/rating.tsx +3 -1
  190. package/src/components/resizable/resizable.stories.tsx +114 -2
  191. package/src/components/resizable/resizable.test.tsx +76 -0
  192. package/src/components/resizable/resizable.tsx +32 -6
  193. package/src/components/revision-timeline/revision-timeline.stories.tsx +8 -1
  194. package/src/components/revision-timeline/revision-timeline.test.tsx +26 -0
  195. package/src/components/revision-timeline/revision-timeline.tsx +53 -5
  196. package/src/components/schema-form/from-json-schema.test.ts +390 -0
  197. package/src/components/schema-form/from-json-schema.ts +375 -0
  198. package/src/components/schema-form/index.ts +81 -0
  199. package/src/components/schema-form/schema-form-spec.ts +698 -0
  200. package/src/components/schema-form/schema-form-store.ts +506 -0
  201. package/src/components/schema-form/schema-form.stories.tsx +566 -0
  202. package/src/components/schema-form/schema-form.test.tsx +1021 -0
  203. package/src/components/schema-form/schema-form.tsx +1381 -0
  204. package/src/components/section-header/section-header.stories.tsx +37 -0
  205. package/src/components/section-header/section-header.tsx +14 -3
  206. package/src/components/select/select.tsx +13 -7
  207. package/src/components/separator/separator.tsx +1 -0
  208. package/src/components/sheet/sheet.test.tsx +39 -0
  209. package/src/components/sheet/sheet.tsx +40 -9
  210. package/src/components/side-dock/index.ts +1 -0
  211. package/src/components/side-dock/side-dock.stories.tsx +137 -0
  212. package/src/components/side-dock/side-dock.test.tsx +299 -0
  213. package/src/components/side-dock/side-dock.tsx +515 -0
  214. package/src/components/sidebar/index.ts +1 -0
  215. package/src/components/sidebar/sidebar-frame.test.tsx +162 -0
  216. package/src/components/sidebar/sidebar.stories.tsx +421 -1
  217. package/src/components/sidebar/sidebar.test.tsx +71 -2
  218. package/src/components/sidebar/sidebar.tsx +264 -38
  219. package/src/components/skeleton/skeleton.tsx +1 -0
  220. package/src/components/skip-link/index.ts +1 -0
  221. package/src/components/skip-link/skip-link.stories.tsx +106 -0
  222. package/src/components/skip-link/skip-link.test.tsx +20 -0
  223. package/src/components/skip-link/skip-link.tsx +34 -0
  224. package/src/components/slider/slider.tsx +1 -1
  225. package/src/components/slider-number/slider-number.stories.tsx +4 -1
  226. package/src/components/slider-number/slider-number.test.tsx +1 -1
  227. package/src/components/sonner/sonner.tsx +2 -0
  228. package/src/components/spinner/spinner.test.tsx +18 -0
  229. package/src/components/spinner/spinner.tsx +14 -4
  230. package/src/components/split-panel/index.ts +6 -1
  231. package/src/components/split-panel/split-panel.stories.tsx +18 -2
  232. package/src/components/split-panel/split-panel.test.tsx +20 -0
  233. package/src/components/split-panel/split-panel.tsx +28 -8
  234. package/src/components/state-panel/state-panel.stories.tsx +52 -0
  235. package/src/components/state-panel/state-panel.test.tsx +125 -0
  236. package/src/components/state-panel/state-panel.tsx +110 -24
  237. package/src/components/status-badge/status-badge.stories.tsx +8 -0
  238. package/src/components/switch/switch.tsx +3 -1
  239. package/src/components/table/table.stories.tsx +64 -0
  240. package/src/components/table/table.test.tsx +149 -1
  241. package/src/components/table/table.tsx +71 -2
  242. package/src/components/tabs/index.ts +10 -1
  243. package/src/components/tabs/tabs.stories.tsx +48 -0
  244. package/src/components/tabs/tabs.test.tsx +67 -0
  245. package/src/components/tabs/tabs.tsx +176 -72
  246. package/src/components/tag-input/tag-input.tsx +6 -3
  247. package/src/components/team-switcher/team-switcher.tsx +35 -6
  248. package/src/components/textarea/textarea.tsx +2 -2
  249. package/src/components/theme-switcher/theme-switcher.stories.tsx +76 -0
  250. package/src/components/theme-switcher/theme-switcher.test.tsx +182 -0
  251. package/src/components/theme-switcher/theme-switcher.tsx +140 -12
  252. package/src/components/theme-switcher/use-theme-transition.ts +2 -2
  253. package/src/components/timeline/timeline.stories.tsx +27 -0
  254. package/src/components/timeline/timeline.tsx +10 -3
  255. package/src/components/toggle/toggle.tsx +7 -2
  256. package/src/components/toggle-group/toggle-group.tsx +2 -0
  257. package/src/components/toolbar/toolbar.stories.tsx +10 -2
  258. package/src/components/tooltip/tooltip.tsx +2 -1
  259. package/src/components/top-nav/top-nav.tsx +1 -1
  260. package/src/components/transfer/transfer.tsx +35 -30
  261. package/src/components/tree/tree.stories.tsx +2 -2
  262. package/src/components/tree/tree.test.tsx +41 -0
  263. package/src/components/tree/tree.tsx +91 -31
  264. package/src/components/tree/use-tree-keyboard.ts +37 -14
  265. package/src/components/tree-select/tree-select.stories.tsx +9 -0
  266. package/src/components/tree-select/tree-select.test.tsx +5 -0
  267. package/src/components/tree-select/tree-select.tsx +11 -14
  268. package/src/components/typography/prose.test.tsx +3 -2
  269. package/src/components/typography/prose.tsx +3 -3
  270. package/src/components/typography/typography.stories.tsx +228 -0
  271. package/src/components/view-toolbar/view-toolbar.stories.tsx +41 -1
  272. package/src/components/view-toolbar/view-toolbar.test.tsx +33 -0
  273. package/src/components/view-toolbar/view-toolbar.tsx +37 -4
  274. package/src/components/virtual-select/virtual-select.stories.tsx +9 -0
  275. package/src/components/virtual-select/virtual-select.test.tsx +29 -3
  276. package/src/components/virtual-select/virtual-select.tsx +56 -18
  277. package/src/components/wizard/wizard.test.tsx +9 -0
  278. package/src/components/wizard/wizard.tsx +13 -5
  279. package/src/components/workspace-picker/index.ts +3 -0
  280. package/src/components/workspace-picker/workspace-picker-state.ts +50 -0
  281. package/src/components/workspace-picker/workspace-picker.stories.tsx +126 -0
  282. package/src/components/workspace-picker/workspace-picker.test.tsx +207 -0
  283. package/src/components/workspace-picker/workspace-picker.tsx +172 -0
  284. package/src/illustrations/empty-list-illustration.tsx +26 -0
  285. package/src/illustrations/error-illustration.tsx +60 -0
  286. package/src/illustrations/first-run-illustration.tsx +27 -0
  287. package/src/illustrations/illustration-base.tsx +108 -0
  288. package/src/illustrations/illustrations.stories.tsx +94 -0
  289. package/src/illustrations/illustrations.test.tsx +98 -0
  290. package/src/illustrations/index.ts +16 -0
  291. package/src/illustrations/no-access-illustration.tsx +45 -0
  292. package/src/illustrations/no-results-illustration.tsx +46 -0
  293. package/src/illustrations/offline-illustration.tsx +37 -0
  294. package/src/illustrations/success-illustration.tsx +40 -0
  295. package/src/index.ts +80 -2
  296. package/src/lib/agent-event-model.test.ts +12 -0
  297. package/src/lib/agent-event-model.ts +42 -0
  298. package/src/lib/approval-option.test.ts +17 -0
  299. package/src/lib/approval-option.ts +53 -0
  300. package/src/lib/check-result.ts +33 -0
  301. package/src/lib/cn.ts +9 -0
  302. package/src/lib/csv.ts +48 -0
  303. package/src/lib/diff-line.test.ts +36 -0
  304. package/src/lib/diff-line.ts +63 -0
  305. package/src/lib/diff-rows.test.ts +76 -0
  306. package/src/lib/diff-rows.ts +94 -0
  307. package/src/lib/format-duration.test.ts +22 -0
  308. package/src/lib/format-duration.ts +30 -0
  309. package/src/lib/has-renderable-content.test.ts +49 -0
  310. package/src/lib/has-renderable-content.ts +32 -0
  311. package/src/lib/merge-refs.ts +2 -0
  312. package/src/lib/operating-mode.test.ts +36 -0
  313. package/src/lib/operating-mode.ts +81 -0
  314. package/src/lib/optional-peer.ts +59 -0
  315. package/src/lib/session-launch.ts +37 -0
  316. package/src/lib/slash-command.test.ts +44 -0
  317. package/src/lib/slash-command.ts +45 -0
  318. package/src/lib/trigger-query.test.ts +94 -0
  319. package/src/lib/trigger-query.ts +88 -0
  320. package/src/lib/use-controllable-state.ts +47 -0
  321. package/src/lib/use-mobile.test.tsx +47 -0
  322. package/src/lib/use-mobile.ts +34 -12
  323. package/src/motion.stories.tsx +6 -0
  324. package/src/templates-object-detail-hub.stories.tsx +2 -2
  325. package/src/templates-screen-states.stories.tsx +5 -5
  326. package/src/templates-settings.stories.tsx +1 -1
  327. package/src/blocks/sidebar-02/app-sidebar.tsx +0 -160
  328. package/src/blocks/sidebar-02/logo.tsx +0 -18
  329. package/src/blocks/sidebar-02/nav-main.tsx +0 -13
  330. package/src/blocks/sidebar-02/nav-notifications.tsx +0 -7
  331. package/src/blocks/sidebar-02/sidebar-02.stories.tsx +0 -23
  332. package/src/blocks/sidebar-02/team-switcher.tsx +0 -7
  333. package/src/blocks/sidebar-04/app-sidebar.tsx +0 -236
  334. package/src/blocks/sidebar-04/mail-context.tsx +0 -29
  335. package/src/blocks/sidebar-04/nav-user.tsx +0 -6
  336. package/src/blocks/sidebar-04/sidebar-04.stories.tsx +0 -150
  337. package/src/blocks/sidebar-05/app-sidebar.tsx +0 -375
  338. package/src/blocks/sidebar-05/sidebar-05.stories.tsx +0 -18
  339. package/src/blocks/sidebar-05/team-switcher.tsx +0 -6
@@ -0,0 +1,169 @@
1
+ import { createRef } from "react";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { fireEvent, render, screen } from "@testing-library/react";
4
+ import { DirectionProvider } from "@radix-ui/react-direction";
5
+ import {
6
+ Carousel,
7
+ CarouselContent,
8
+ CarouselItem,
9
+ CarouselNext,
10
+ CarouselPrevious,
11
+ } from "./carousel";
12
+
13
+ function renderCarousel(props: Partial<React.ComponentProps<typeof Carousel>> = {}) {
14
+ return render(
15
+ <Carousel {...props}>
16
+ <CarouselContent>
17
+ <CarouselItem>
18
+ <input aria-label="slide input" defaultValue="abc" />
19
+ </CarouselItem>
20
+ <CarouselItem>Slide 2</CarouselItem>
21
+ </CarouselContent>
22
+ <CarouselPrevious />
23
+ <CarouselNext />
24
+ </Carousel>,
25
+ );
26
+ }
27
+
28
+ describe("Carousel keyboard handling", () => {
29
+ it("does not intercept ArrowLeft/ArrowRight when the target is a text input", () => {
30
+ renderCarousel();
31
+ const input = screen.getByLabelText("slide input") as HTMLInputElement;
32
+ input.focus();
33
+ const event = fireEvent.keyDown(input, { key: "ArrowRight", bubbles: true });
34
+ // `fireEvent` returns `false` from `dispatchEvent` only when
35
+ // `preventDefault()` was called — the carousel must leave the caret event
36
+ // alone so it reaches the input's own default behavior.
37
+ expect(event).toBe(true);
38
+ });
39
+
40
+ it("still intercepts ArrowLeft/ArrowRight outside of an editable target", () => {
41
+ renderCarousel();
42
+ const region = screen.getByRole("region");
43
+ const event = fireEvent.keyDown(region, { key: "ArrowRight", bubbles: true });
44
+ expect(event).toBe(false);
45
+ });
46
+ });
47
+
48
+ describe("Carousel RTL keyboard mapping", () => {
49
+ it("flips ArrowLeft/ArrowRight to the visually-adjacent slide under dir=rtl", () => {
50
+ let scrollPrevSpy: ReturnType<typeof vi.spyOn> | undefined;
51
+ let scrollNextSpy: ReturnType<typeof vi.spyOn> | undefined;
52
+ render(
53
+ <DirectionProvider dir="rtl">
54
+ <Carousel
55
+ setApi={(api) => {
56
+ if (!api) return;
57
+ scrollPrevSpy = vi.spyOn(api, "scrollPrev");
58
+ scrollNextSpy = vi.spyOn(api, "scrollNext");
59
+ }}
60
+ >
61
+ <CarouselContent>
62
+ <CarouselItem>Slide 1</CarouselItem>
63
+ <CarouselItem>Slide 2</CarouselItem>
64
+ </CarouselContent>
65
+ </Carousel>
66
+ </DirectionProvider>,
67
+ );
68
+ const region = screen.getByRole("region");
69
+ // In RTL, ArrowLeft is the visually-forward direction — it must trigger
70
+ // the SAME embla call the "Next slide" button does, not "Previous".
71
+ fireEvent.keyDown(region, { key: "ArrowLeft" });
72
+ expect(scrollNextSpy).toHaveBeenCalledTimes(1);
73
+ expect(scrollPrevSpy).not.toHaveBeenCalled();
74
+
75
+ fireEvent.keyDown(region, { key: "ArrowRight" });
76
+ expect(scrollPrevSpy).toHaveBeenCalledTimes(1);
77
+ });
78
+
79
+ it("keeps the physical mapping under dir=ltr (default)", () => {
80
+ let scrollPrevSpy: ReturnType<typeof vi.spyOn> | undefined;
81
+ let scrollNextSpy: ReturnType<typeof vi.spyOn> | undefined;
82
+ render(
83
+ <Carousel
84
+ setApi={(api) => {
85
+ if (!api) return;
86
+ scrollPrevSpy = vi.spyOn(api, "scrollPrev");
87
+ scrollNextSpy = vi.spyOn(api, "scrollNext");
88
+ }}
89
+ >
90
+ <CarouselContent>
91
+ <CarouselItem>Slide 1</CarouselItem>
92
+ <CarouselItem>Slide 2</CarouselItem>
93
+ </CarouselContent>
94
+ </Carousel>,
95
+ );
96
+ const region = screen.getByRole("region");
97
+ fireEvent.keyDown(region, { key: "ArrowRight" });
98
+ expect(scrollNextSpy).toHaveBeenCalledTimes(1);
99
+ fireEvent.keyDown(region, { key: "ArrowLeft" });
100
+ expect(scrollPrevSpy).toHaveBeenCalledTimes(1);
101
+ });
102
+
103
+ it("uses ArrowUp/ArrowDown instead of ArrowLeft/ArrowRight for a vertical carousel", () => {
104
+ let scrollPrevSpy: ReturnType<typeof vi.spyOn> | undefined;
105
+ let scrollNextSpy: ReturnType<typeof vi.spyOn> | undefined;
106
+ render(
107
+ <Carousel
108
+ orientation="vertical"
109
+ setApi={(api) => {
110
+ if (!api) return;
111
+ scrollPrevSpy = vi.spyOn(api, "scrollPrev");
112
+ scrollNextSpy = vi.spyOn(api, "scrollNext");
113
+ }}
114
+ >
115
+ <CarouselContent>
116
+ <CarouselItem>Slide 1</CarouselItem>
117
+ <CarouselItem>Slide 2</CarouselItem>
118
+ </CarouselContent>
119
+ </Carousel>,
120
+ );
121
+ const region = screen.getByRole("region");
122
+ fireEvent.keyDown(region, { key: "ArrowRight" });
123
+ expect(scrollNextSpy).not.toHaveBeenCalled();
124
+ fireEvent.keyDown(region, { key: "ArrowDown" });
125
+ expect(scrollNextSpy).toHaveBeenCalledTimes(1);
126
+ fireEvent.keyDown(region, { key: "ArrowUp" });
127
+ expect(scrollPrevSpy).toHaveBeenCalledTimes(1);
128
+ });
129
+ });
130
+
131
+ describe("CarouselPrevious / CarouselNext", () => {
132
+ it("forward a ref to the underlying button", () => {
133
+ const prevRef = createRef<HTMLButtonElement>();
134
+ const nextRef = createRef<HTMLButtonElement>();
135
+ renderCarousel();
136
+ render(
137
+ <Carousel>
138
+ <CarouselContent>
139
+ <CarouselItem>1</CarouselItem>
140
+ </CarouselContent>
141
+ <CarouselPrevious ref={prevRef} />
142
+ <CarouselNext ref={nextRef} />
143
+ </Carousel>,
144
+ );
145
+ expect(prevRef.current).toBeInstanceOf(HTMLButtonElement);
146
+ expect(nextRef.current).toBeInstanceOf(HTMLButtonElement);
147
+ });
148
+
149
+ it("keeps the buttons inset on narrow viewports (no unconditional negative offset)", () => {
150
+ render(
151
+ <Carousel>
152
+ <CarouselContent>
153
+ <CarouselItem>1</CarouselItem>
154
+ </CarouselContent>
155
+ <CarouselPrevious />
156
+ <CarouselNext />
157
+ </Carousel>,
158
+ );
159
+ const prev = screen.getByRole("button", { name: "Previous slide" });
160
+ const next = screen.getByRole("button", { name: "Next slide" });
161
+ // The negative offset that pushes the button outside the carousel box
162
+ // only applies from `sm:` up; unconditionally offsetting it caused
163
+ // horizontal overflow on narrow phone screens.
164
+ expect(prev.className).not.toMatch(/(?<!sm:)-start-12/);
165
+ expect(next.className).not.toMatch(/(?<!sm:)-end-12/);
166
+ expect(prev.className).toContain("sm:-start-12");
167
+ expect(next.className).toContain("sm:-end-12");
168
+ });
169
+ });
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import {
2
4
  createContext,
3
5
  forwardRef,
@@ -6,13 +8,29 @@ import {
6
8
  useEffect,
7
9
  useState,
8
10
  type ComponentProps,
11
+ type ElementRef,
9
12
  type HTMLAttributes,
10
13
  type KeyboardEvent,
11
14
  } from "react";
15
+ import { useDirection } from "@radix-ui/react-direction";
12
16
  import useEmblaCarousel, { type UseEmblaCarouselType } from "embla-carousel-react";
13
17
  import { ArrowLeft, ArrowRight } from "lucide-react";
14
18
  import { cn } from "../../lib/cn";
15
19
  import { Button } from "../button";
20
+ import { useLocale } from "../locale-provider";
21
+
22
+ /**
23
+ * True when the keyboard event's target already owns arrow-key semantics of
24
+ * its own (a text field's caret, a contenteditable's selection) — the
25
+ * carousel must not steal ArrowLeft/ArrowRight from a form control that
26
+ * happens to live inside the active slide.
27
+ */
28
+ function isEditableTarget(target: EventTarget | null): boolean {
29
+ if (!(target instanceof HTMLElement)) return false;
30
+ if (target.isContentEditable) return true;
31
+ const tag = target.tagName;
32
+ return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT";
33
+ }
16
34
 
17
35
  type CarouselApi = UseEmblaCarouselType[1];
18
36
  type CarouselOptions = NonNullable<Parameters<typeof useEmblaCarousel>[0]>;
@@ -62,17 +80,20 @@ export const Carousel = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement
62
80
  // aria-roledescription changes the announcement, it does not name the
63
81
  // landmark. Default is overridable; pass a specific label ("Product
64
82
  // photos") when the carousel's purpose isn't generic. Issue #279.
65
- "aria-label": ariaLabel = "Carousel",
83
+ "aria-label": ariaLabel,
66
84
  ...props
67
85
  },
68
86
  ref,
69
87
  ) {
88
+ const { t } = useLocale();
89
+ const resolvedAriaLabel = ariaLabel ?? t("ui.carousel.label");
70
90
  const [carouselRef, api] = useEmblaCarousel(
71
91
  { ...opts, axis: orientation === "horizontal" ? "x" : "y" },
72
92
  plugins,
73
93
  );
74
94
  const [canScrollPrev, setCanScrollPrev] = useState(false);
75
95
  const [canScrollNext, setCanScrollNext] = useState(false);
96
+ const dir = useDirection();
76
97
 
77
98
  const onSelect = useCallback((a: CarouselApi) => {
78
99
  if (!a) return;
@@ -85,15 +106,42 @@ export const Carousel = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement
85
106
 
86
107
  const onKeyDown = useCallback(
87
108
  (e: KeyboardEvent<HTMLDivElement>) => {
109
+ // Don't steal ArrowLeft/ArrowRight from a text field's caret or a
110
+ // contenteditable's selection inside the active slide.
111
+ if (isEditableTarget(e.target)) return;
112
+
113
+ if (orientation === "vertical") {
114
+ if (e.key === "ArrowUp") {
115
+ e.preventDefault();
116
+ scrollPrev();
117
+ } else if (e.key === "ArrowDown") {
118
+ e.preventDefault();
119
+ scrollNext();
120
+ }
121
+ return;
122
+ }
123
+
124
+ // Horizontal: the PHYSICAL key maps to the visually-adjacent slide,
125
+ // which flips with writing direction — in RTL, ArrowLeft moves toward
126
+ // the next slide (visually to the left of the current one), not prev.
127
+ const isRtl = dir === "rtl";
88
128
  if (e.key === "ArrowLeft") {
89
129
  e.preventDefault();
90
- scrollPrev();
130
+ if (isRtl) {
131
+ scrollNext();
132
+ } else {
133
+ scrollPrev();
134
+ }
91
135
  } else if (e.key === "ArrowRight") {
92
136
  e.preventDefault();
93
- scrollNext();
137
+ if (isRtl) {
138
+ scrollPrev();
139
+ } else {
140
+ scrollNext();
141
+ }
94
142
  }
95
143
  },
96
- [scrollPrev, scrollNext],
144
+ [orientation, dir, scrollPrev, scrollNext],
97
145
  );
98
146
 
99
147
  useEffect(() => {
@@ -105,6 +153,7 @@ export const Carousel = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement
105
153
  api.on("reInit", onSelect);
106
154
  api.on("select", onSelect);
107
155
  return () => {
156
+ api.off("reInit", onSelect);
108
157
  api.off("select", onSelect);
109
158
  };
110
159
  }, [api, onSelect]);
@@ -128,7 +177,7 @@ export const Carousel = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement
128
177
  className={cn("relative", className)}
129
178
  role="region"
130
179
  aria-roledescription="carousel"
131
- aria-label={ariaLabel}
180
+ aria-label={resolvedAriaLabel}
132
181
  {...props}
133
182
  >
134
183
  {children}
@@ -176,20 +225,28 @@ export const CarouselItem = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivEle
176
225
  },
177
226
  );
178
227
 
179
- export function CarouselPrevious({ className, ...props }: ComponentProps<typeof Button>) {
228
+ export const CarouselPrevious = forwardRef<
229
+ ElementRef<typeof Button>,
230
+ ComponentProps<typeof Button>
231
+ >(function CarouselPrevious({ className, ...props }, ref) {
180
232
  const { scrollPrev, canScrollPrev, orientation } = useCarousel();
233
+ const { t } = useLocale();
181
234
  return (
182
235
  <Button
236
+ ref={ref}
183
237
  variant="outline"
184
238
  size="icon"
185
239
  disabled={!canScrollPrev}
186
240
  onClick={scrollPrev}
187
- aria-label="Previous slide"
241
+ aria-label={t("previousSlide")}
188
242
  className={cn(
189
243
  "absolute size-8 rounded-full",
244
+ // Inset on narrow screens (no room for the button to sit outside
245
+ // the carousel box without overflowing horizontally); pushed
246
+ // outside the box only from `sm` up, where there's margin for it.
190
247
  orientation === "horizontal"
191
- ? "-start-12 top-1/2 -translate-y-1/2"
192
- : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
248
+ ? "start-2 top-1/2 -translate-y-1/2 sm:-start-12"
249
+ : "top-2 left-1/2 -translate-x-1/2 rotate-90 sm:-top-12",
193
250
  className,
194
251
  )}
195
252
  {...props}
@@ -197,29 +254,33 @@ export function CarouselPrevious({ className, ...props }: ComponentProps<typeof
197
254
  <ArrowLeft className="size-4" data-rtl-flip />
198
255
  </Button>
199
256
  );
200
- }
257
+ });
201
258
 
202
- export function CarouselNext({ className, ...props }: ComponentProps<typeof Button>) {
203
- const { scrollNext, canScrollNext, orientation } = useCarousel();
204
- return (
205
- <Button
206
- variant="outline"
207
- size="icon"
208
- disabled={!canScrollNext}
209
- onClick={scrollNext}
210
- aria-label="Next slide"
211
- className={cn(
212
- "absolute size-8 rounded-full",
213
- orientation === "horizontal"
214
- ? "-end-12 top-1/2 -translate-y-1/2"
215
- : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
216
- className,
217
- )}
218
- {...props}
219
- >
220
- <ArrowRight className="size-4" data-rtl-flip />
221
- </Button>
222
- );
223
- }
259
+ export const CarouselNext = forwardRef<ElementRef<typeof Button>, ComponentProps<typeof Button>>(
260
+ function CarouselNext({ className, ...props }, ref) {
261
+ const { scrollNext, canScrollNext, orientation } = useCarousel();
262
+ const { t } = useLocale();
263
+ return (
264
+ <Button
265
+ ref={ref}
266
+ variant="outline"
267
+ size="icon"
268
+ disabled={!canScrollNext}
269
+ onClick={scrollNext}
270
+ aria-label={t("nextSlide")}
271
+ className={cn(
272
+ "absolute size-8 rounded-full",
273
+ orientation === "horizontal"
274
+ ? "end-2 top-1/2 -translate-y-1/2 sm:-end-12"
275
+ : "bottom-2 left-1/2 -translate-x-1/2 rotate-90 sm:-bottom-12",
276
+ className,
277
+ )}
278
+ {...props}
279
+ >
280
+ <ArrowRight className="size-4" data-rtl-flip />
281
+ </Button>
282
+ );
283
+ },
284
+ );
224
285
 
225
286
  export type { CarouselApi };
@@ -1,6 +1,7 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react-vite";
2
2
  import { expect, userEvent, waitFor, within } from "storybook/test";
3
3
  import { useState } from "react";
4
+ import { type CheckResult } from "../../lib/check-result";
4
5
  import {
5
6
  ChangeReview,
6
7
  ChangeReviewHeader,
@@ -42,6 +43,41 @@ const PROVENANCE = {
42
43
  note: "run-abc123",
43
44
  };
44
45
 
46
+ // ─── Sample hunks with verification checks (#112) ─────────────────────────────
47
+
48
+ const HUNKS_WITH_CHECKS: ChangeHunk[] = [
49
+ {
50
+ id: "checked-1",
51
+ title: "src/api/handler.ts",
52
+ status: "modified",
53
+ before: `function handle(req) { return req.body; }`,
54
+ after: `function handle(req: Request) { return req.body; }`,
55
+ checks: [
56
+ { label: "eslint", ok: true, durationMs: 214 },
57
+ {
58
+ label: "tsc",
59
+ ok: false,
60
+ durationMs: 890,
61
+ detail:
62
+ "src/api/handler.ts:1:31 — Parameter 'req' implicitly has an 'any' type, and the " +
63
+ "surrounding function type annotation does not resolve it. This is a deliberately " +
64
+ "long detail line so it renders behind a collapsed disclosure by default.",
65
+ },
66
+ ],
67
+ },
68
+ {
69
+ id: "checked-2",
70
+ title: "src/api/handler.test.ts",
71
+ status: "added",
72
+ after: `it("handles a request", () => { ... });`,
73
+ checks: [
74
+ { label: "pre_tool_use", ok: true, phase: "before" },
75
+ { label: "eslint", ok: true, phase: "after", durationMs: 120 },
76
+ { label: "vitest", ok: true, phase: "after", detail: "3 passed", durationMs: 1420 },
77
+ ] satisfies CheckResult[],
78
+ },
79
+ ];
80
+
45
81
  // ─── Meta ─────────────────────────────────────────────────────────────────────
46
82
 
47
83
  const meta = {
@@ -53,10 +89,30 @@ const meta = {
53
89
  docs: {
54
90
  description: {
55
91
  component:
92
+ "The ACCEPT/REJECT diff surface; read-only patch lines are `AI/DiffView` and an " +
93
+ "editable side-by-side is `Editor/DiffEditor` — see " +
94
+ "[Choosing between similar components](?path=/docs/docs-choosing-between-similar-components--docs). " +
56
95
  "AI-edit trust gate. When an agent proposes edits, the human reviews them " +
57
96
  "hunk-by-hunk and accepts or rejects each before they apply. Compound " +
58
97
  "component with lifted state — compose parts for custom layouts, or use the " +
59
- "convenience `<ChangeReview>` root.",
98
+ "convenience `<ChangeReview>` root.\n\n" +
99
+ "**Ships from `@elabs-ai/components-ui`**, not from the package this story " +
100
+ "group names. Stories are grouped by what a component is *for*, which " +
101
+ "`docs/STORYBOOK_GUIDELINES.md` names as intentional and cites this component " +
102
+ "as the worked example.\n\n" +
103
+ "**A hunk’s `before` / `after` are prose, not diff lines.** They are ordinary " +
104
+ "`ReactNode`s, drawn as a deliberately quiet no-fill comparison — a struck " +
105
+ "muted “before” above a plain “after”, with the status rail carrying the " +
106
+ "polarity. This component is not a line renderer and is not going to become " +
107
+ "one.\n\n" +
108
+ "**Structured `DiffLine[]` hunks arrive by injection.** The app that depends " +
109
+ "on both packages passes a `<DiffView>` (`@elabs-ai/components-ai`) through " +
110
+ "the `renderHunk` render-prop or a hunk’s `after` slot; neither package " +
111
+ "imports the other. `ChangeHunk` gains no `lines` field and `DiffLine` " +
112
+ "never moves — a ratified seam, recorded in § 3 of the adoption-architecture " +
113
+ "decision. The *Injected hunk renderer* story below shows the shape with a " +
114
+ "stand-in node; the real wiring is *Composed into ChangeReview (ui)* on " +
115
+ "[AI/DiffView](?path=/docs/ai-diffview--docs).",
60
116
  },
61
117
  },
62
118
  },
@@ -239,6 +295,55 @@ export const CustomRenderer: Story = {
239
295
  },
240
296
  };
241
297
 
298
+ // ─── Injected hunk renderer (the DiffView seam) ───────────────────────────────
299
+ //
300
+ // BINDING SEAM — docs/decisions/2026-09-01-brainless-adoption-architecture.md § 3.
301
+ // `ChangeHunk` gains no `lines` field and `DiffLine` never moves into this
302
+ // package. An app that wants structured patch rows inside the trust gate injects
303
+ // a renderer through `renderHunk` (or a hunk's `after` slot); in a real app that
304
+ // node is `<DiffView lines={…} />` from `@elabs-ai/components-ai`.
305
+ //
306
+ // This package has no dependency on that one in EITHER direction — that is the
307
+ // whole point of the seam — so the story stands in for it with a plain node of
308
+ // the same shape. The real composition is the `ChangeReviewComposition` story in
309
+ // `packages/ai/src/diff-view.stories.tsx`, which can import both.
310
+
311
+ function StandInLineRenderer({ file }: { file: string }) {
312
+ return (
313
+ <div
314
+ data-testid={`injected-${file}`}
315
+ className="rounded-md bg-surface-muted p-3 text-meta text-muted-foreground"
316
+ >
317
+ <span className="font-mono">{`<DiffView lines={…} />`}</span> renders the patch rows for{" "}
318
+ <span className="font-medium text-foreground">{file}</span> here.
319
+ </div>
320
+ );
321
+ }
322
+
323
+ export const InjectedHunkRenderer: Story = {
324
+ args: {
325
+ hunks: HUNKS,
326
+ renderHunk: (hunk) => <StandInLineRenderer file={hunk.title ?? hunk.id} />,
327
+ },
328
+ play: async ({ canvasElement }) => {
329
+ const canvas = within(canvasElement);
330
+
331
+ // The injected node replaces the default before/after prose on every hunk…
332
+ await waitFor(() => expect(canvas.getByTestId("injected-config/settings.json")).toBeVisible());
333
+ expect(canvas.getAllByTestId(/^injected-/)).toHaveLength(3);
334
+
335
+ // …and the default prose comparison is gone, because the seam replaces it.
336
+ expect(canvas.queryByText('"timeout": 30')).not.toBeInTheDocument();
337
+
338
+ // Approve/reject still belongs to ChangeReview, not to the injected renderer.
339
+ await userEvent.click(
340
+ canvas.getByRole("button", { name: /approve hunk: config\/settings\.json/i }),
341
+ );
342
+ await waitFor(() => expect(canvas.getByText("1 of 3 approved")).toBeVisible());
343
+ expect(canvas.getByTestId("injected-config/settings.json")).toBeVisible();
344
+ },
345
+ };
346
+
242
347
  // ─── Compound (manual layout) ─────────────────────────────────────────────────
243
348
 
244
349
  export const Compound: Story = {
@@ -286,3 +391,42 @@ edge cases properly and includes proper error boundaries.`,
286
391
  ],
287
392
  },
288
393
  };
394
+
395
+ // ─── With verification checks (#112) ──────────────────────────────────────────
396
+ // Evidence a reviewer needs (lint / types / tests / policy hooks) rendered
397
+ // alongside the diff. A failing check informs the decision — it never blocks
398
+ // approval, so `checked-1` (which carries a failing `tsc` check) can still be
399
+ // approved below.
400
+
401
+ export const WithChecks: Story = {
402
+ args: {
403
+ hunks: HUNKS_WITH_CHECKS,
404
+ },
405
+ play: async ({ canvasElement }) => {
406
+ const canvas = within(canvasElement);
407
+ await waitFor(() => expect(canvas.getByText("src/api/handler.ts")).toBeVisible());
408
+
409
+ // Passing and failing checks are distinguishable by accessible text —
410
+ // never by colour alone.
411
+ await waitFor(() => expect(canvas.getAllByText("Passed").length).toBeGreaterThan(0));
412
+ await waitFor(() => expect(canvas.getAllByText("Failed").length).toBeGreaterThan(0));
413
+
414
+ // The long `tsc` detail is collapsed by default…
415
+ const showDetail = canvas.getByRole("button", { name: /show detail/i });
416
+ expect(canvas.queryByText(/implicitly has an 'any' type/)).not.toBeInTheDocument();
417
+ // …and expands on click.
418
+ await userEvent.click(showDetail);
419
+ await waitFor(() => expect(canvas.getByText(/implicitly has an 'any' type/)).toBeVisible());
420
+
421
+ // Both phases present on the second hunk → grouped "Before" / "After".
422
+ await waitFor(() => expect(canvas.getByText("Before")).toBeVisible());
423
+ await waitFor(() => expect(canvas.getByText("After")).toBeVisible());
424
+
425
+ // A failing check does not block approval — it only informs it.
426
+ const approveFirstHunk = canvas.getByRole("button", {
427
+ name: /approve hunk: src\/api\/handler\.ts/i,
428
+ });
429
+ await userEvent.click(approveFirstHunk);
430
+ await waitFor(() => expect(canvas.getByText("1 of 2 approved")).toBeVisible());
431
+ },
432
+ };