@copilotkit/vue 2.0.0-rc.1 → 2.0.0-rc.10

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 (317) hide show
  1. package/PARITY.md +13 -12
  2. package/README.md +1 -2
  3. package/dist/components/copilot-provider/CopilotKit.vue.d.ts +3 -1
  4. package/dist/components/copilot-provider/CopilotKit.vue.d.ts.map +1 -1
  5. package/dist/hooks/use-copilot-action.d.ts.map +1 -1
  6. package/dist/hooks/use-frontend-tool.d.ts +1 -1
  7. package/dist/hooks/use-frontend-tool.d.ts.map +1 -1
  8. package/dist/hooks/zod-parameters.d.ts +14 -0
  9. package/dist/hooks/zod-parameters.d.ts.map +1 -0
  10. package/dist/index.cjs +1 -76
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.mjs +384 -14275
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/use-render-activity-message-CTR_ziWV.js +18097 -0
  15. package/dist/use-render-activity-message-CTR_ziWV.js.map +1 -0
  16. package/dist/use-render-activity-message-Dky9_GRG.cjs +92 -0
  17. package/dist/use-render-activity-message-Dky9_GRG.cjs.map +1 -0
  18. package/dist/v2/components/A2UIMessageRenderer.d.ts +7 -0
  19. package/dist/v2/components/A2UIMessageRenderer.d.ts.map +1 -1
  20. package/dist/v2/components/A2UISurfaceActivityRenderer.vue.d.ts.map +1 -1
  21. package/dist/v2/components/LicenseWarningBanner.vue.d.ts.map +1 -1
  22. package/dist/v2/components/OpenGenerativeUIRenderer.d.ts +13 -45
  23. package/dist/v2/components/OpenGenerativeUIRenderer.d.ts.map +1 -1
  24. package/dist/v2/components/a2ui/A2UIBuiltInToolCallRenderer.d.ts +11 -2
  25. package/dist/v2/components/a2ui/A2UIBuiltInToolCallRenderer.d.ts.map +1 -1
  26. package/dist/v2/components/a2ui/A2UICatalogContext.d.ts +1 -1
  27. package/dist/v2/components/a2ui/A2UICatalogContext.d.ts.map +1 -1
  28. package/dist/v2/components/a2ui/A2UIRecoveryStates.d.ts +144 -0
  29. package/dist/v2/components/a2ui/A2UIRecoveryStates.d.ts.map +1 -0
  30. package/dist/v2/components/a2ui.d.ts +3 -7
  31. package/dist/v2/components/a2ui.d.ts.map +1 -1
  32. package/dist/v2/components/chat/CopilotChat.vue.d.ts +1 -0
  33. package/dist/v2/components/chat/CopilotChat.vue.d.ts.map +1 -1
  34. package/dist/v2/components/chat/CopilotChatAssistantMessage.vue.d.ts +3 -1
  35. package/dist/v2/components/chat/CopilotChatAssistantMessage.vue.d.ts.map +1 -1
  36. package/dist/v2/components/chat/CopilotChatAssistantToolbarButton.vue.d.ts +16 -0
  37. package/dist/v2/components/chat/CopilotChatAssistantToolbarButton.vue.d.ts.map +1 -0
  38. package/dist/v2/components/chat/CopilotChatAttachmentQueue.vue.d.ts.map +1 -1
  39. package/dist/v2/components/chat/CopilotChatAttachmentRenderer.vue.d.ts.map +1 -1
  40. package/dist/v2/components/chat/CopilotChatAudioRecorder.vue.d.ts.map +1 -1
  41. package/dist/v2/components/chat/CopilotChatFeedbackCommentPopover.vue.d.ts +22 -0
  42. package/dist/v2/components/chat/CopilotChatFeedbackCommentPopover.vue.d.ts.map +1 -0
  43. package/dist/v2/components/chat/CopilotChatInput.vue.d.ts +21 -232
  44. package/dist/v2/components/chat/CopilotChatInput.vue.d.ts.map +1 -1
  45. package/dist/v2/components/chat/CopilotChatInspectorButton.vue.d.ts +12 -0
  46. package/dist/v2/components/chat/CopilotChatInspectorButton.vue.d.ts.map +1 -0
  47. package/dist/v2/components/chat/CopilotChatLightbox.vue.d.ts +22 -0
  48. package/dist/v2/components/chat/CopilotChatLightbox.vue.d.ts.map +1 -0
  49. package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts +20 -0
  50. package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts.map +1 -1
  51. package/dist/v2/components/chat/CopilotChatReasoningMessage.vue.d.ts.map +1 -1
  52. package/dist/v2/components/chat/CopilotChatSuggestionPill.vue.d.ts.map +1 -1
  53. package/dist/v2/components/chat/CopilotChatSuggestionView.vue.d.ts.map +1 -1
  54. package/dist/v2/components/chat/CopilotChatToggleButton.vue.d.ts.map +1 -1
  55. package/dist/v2/components/chat/CopilotChatToolCallGroup.vue.d.ts +32 -0
  56. package/dist/v2/components/chat/CopilotChatToolCallGroup.vue.d.ts.map +1 -0
  57. package/dist/v2/components/chat/CopilotChatToolCallItem.vue.d.ts.map +1 -1
  58. package/dist/v2/components/chat/CopilotChatToolCallsView.vue.d.ts +11 -1
  59. package/dist/v2/components/chat/CopilotChatToolCallsView.vue.d.ts.map +1 -1
  60. package/dist/v2/components/chat/CopilotChatUserMessage.vue.d.ts.map +1 -1
  61. package/dist/v2/components/chat/CopilotChatView.vue.d.ts +1 -0
  62. package/dist/v2/components/chat/CopilotChatView.vue.d.ts.map +1 -1
  63. package/dist/v2/components/chat/CopilotChatWorkSummary.vue.d.ts +5 -0
  64. package/dist/v2/components/chat/CopilotChatWorkSummary.vue.d.ts.map +1 -0
  65. package/dist/v2/components/chat/CopilotModalHeader.vue.d.ts.map +1 -1
  66. package/dist/v2/components/chat/CopilotModalHeaderCloseButton.d.ts.map +1 -1
  67. package/dist/v2/components/chat/CopilotModalHeaderTitle.d.ts.map +1 -1
  68. package/dist/v2/components/chat/CopilotPopup.vue.d.ts +1 -0
  69. package/dist/v2/components/chat/CopilotPopup.vue.d.ts.map +1 -1
  70. package/dist/v2/components/chat/CopilotPopupView.vue.d.ts +1 -0
  71. package/dist/v2/components/chat/CopilotPopupView.vue.d.ts.map +1 -1
  72. package/dist/v2/components/chat/CopilotPopupViewInternal.vue.d.ts +1 -0
  73. package/dist/v2/components/chat/CopilotPopupViewInternal.vue.d.ts.map +1 -1
  74. package/dist/v2/components/chat/CopilotPopupWelcomeScreen.vue.d.ts.map +1 -1
  75. package/dist/v2/components/chat/CopilotSidebar.vue.d.ts +2 -0
  76. package/dist/v2/components/chat/CopilotSidebar.vue.d.ts.map +1 -1
  77. package/dist/v2/components/chat/CopilotSidebarView.vue.d.ts +2 -0
  78. package/dist/v2/components/chat/CopilotSidebarView.vue.d.ts.map +1 -1
  79. package/dist/v2/components/chat/CopilotSidebarViewInternal.vue.d.ts +2 -0
  80. package/dist/v2/components/chat/CopilotSidebarViewInternal.vue.d.ts.map +1 -1
  81. package/dist/v2/components/chat/CopilotSidebarWelcomeScreen.vue.d.ts.map +1 -1
  82. package/dist/v2/components/chat/StreamingMarkdownDefault.vue.d.ts.map +1 -1
  83. package/dist/v2/components/chat/copilotChatToolCallItemRender.d.ts +0 -1
  84. package/dist/v2/components/chat/copilotChatToolCallItemRender.d.ts.map +1 -1
  85. package/dist/v2/components/chat/index.d.ts +17 -1
  86. package/dist/v2/components/chat/index.d.ts.map +1 -1
  87. package/dist/v2/components/chat/inspector-shortcuts.d.ts +12 -0
  88. package/dist/v2/components/chat/inspector-shortcuts.d.ts.map +1 -0
  89. package/dist/v2/components/chat/message-feedback.d.ts +89 -0
  90. package/dist/v2/components/chat/message-feedback.d.ts.map +1 -0
  91. package/dist/v2/components/chat/tool-call-capture.d.ts +39 -0
  92. package/dist/v2/components/chat/tool-call-capture.d.ts.map +1 -0
  93. package/dist/v2/components/chat/tool-call-display-context.d.ts +22 -0
  94. package/dist/v2/components/chat/tool-call-display-context.d.ts.map +1 -0
  95. package/dist/v2/components/chat/tool-call-group-parts.d.ts +131 -0
  96. package/dist/v2/components/chat/tool-call-group-parts.d.ts.map +1 -0
  97. package/dist/v2/components/chat/turn-work.d.ts +33 -0
  98. package/dist/v2/components/chat/turn-work.d.ts.map +1 -0
  99. package/dist/v2/components/chat/types.d.ts +104 -5
  100. package/dist/v2/components/chat/types.d.ts.map +1 -1
  101. package/dist/v2/components/chat/use-chat-labels.d.ts +8 -0
  102. package/dist/v2/components/chat/use-chat-labels.d.ts.map +1 -0
  103. package/dist/v2/components/chat/use-lightbox.d.ts +18 -0
  104. package/dist/v2/components/chat/use-lightbox.d.ts.map +1 -0
  105. package/dist/v2/components/icons/index.d.ts +968 -1
  106. package/dist/v2/components/icons/index.d.ts.map +1 -1
  107. package/dist/v2/components/ui/CopilotDropdownMenuContent.vue.d.ts +55 -0
  108. package/dist/v2/components/ui/CopilotDropdownMenuContent.vue.d.ts.map +1 -0
  109. package/dist/v2/components/ui/CopilotPopover.vue.d.ts +40 -0
  110. package/dist/v2/components/ui/CopilotPopover.vue.d.ts.map +1 -0
  111. package/dist/v2/components/ui/CopilotTooltip.vue.d.ts +37 -0
  112. package/dist/v2/components/ui/CopilotTooltip.vue.d.ts.map +1 -0
  113. package/dist/v2/components/ui/dropdown-menu.d.ts +14 -0
  114. package/dist/v2/components/ui/dropdown-menu.d.ts.map +1 -0
  115. package/dist/v2/components/ui/floating.d.ts +84 -0
  116. package/dist/v2/components/ui/floating.d.ts.map +1 -0
  117. package/dist/v2/components/ui/presence.d.ts +9 -0
  118. package/dist/v2/components/ui/presence.d.ts.map +1 -0
  119. package/dist/v2/components/ui/use-id.d.ts +9 -0
  120. package/dist/v2/components/ui/use-id.d.ts.map +1 -0
  121. package/dist/v2/hooks/use-default-render-tool.d.ts +6 -0
  122. package/dist/v2/hooks/use-default-render-tool.d.ts.map +1 -1
  123. package/dist/v2/hooks/use-frontend-tool.d.ts +5 -0
  124. package/dist/v2/hooks/use-frontend-tool.d.ts.map +1 -1
  125. package/dist/v2/hooks/use-render-activity-message.d.ts +13 -9
  126. package/dist/v2/hooks/use-render-activity-message.d.ts.map +1 -1
  127. package/dist/v2/hooks/use-render-custom-messages.d.ts +5 -7
  128. package/dist/v2/hooks/use-render-custom-messages.d.ts.map +1 -1
  129. package/dist/v2/hooks/use-render-tool.d.ts +4 -0
  130. package/dist/v2/hooks/use-render-tool.d.ts.map +1 -1
  131. package/dist/v2/index.cjs +1 -1
  132. package/dist/v2/index.d.cts +3 -0
  133. package/dist/v2/index.d.mts +3 -0
  134. package/dist/v2/index.d.ts +3 -0
  135. package/dist/v2/index.d.ts.map +1 -1
  136. package/dist/v2/index.mjs +197 -197
  137. package/dist/v2/index.mjs.map +1 -1
  138. package/dist/v2/lib/message-feedback.d.ts +33 -0
  139. package/dist/v2/lib/message-feedback.d.ts.map +1 -0
  140. package/dist/v2/lib/processPartialHtml.d.ts +1 -2
  141. package/dist/v2/lib/processPartialHtml.d.ts.map +1 -1
  142. package/dist/v2/lib/vue-core.d.ts +0 -1
  143. package/dist/v2/lib/vue-core.d.ts.map +1 -1
  144. package/dist/v2/providers/CopilotChatConfigurationProvider.vue.d.ts.map +1 -1
  145. package/dist/v2/providers/CopilotKitProvider.types.d.ts +56 -6
  146. package/dist/v2/providers/CopilotKitProvider.types.d.ts.map +1 -1
  147. package/dist/v2/providers/CopilotKitProvider.vue.d.ts +6 -2
  148. package/dist/v2/providers/CopilotKitProvider.vue.d.ts.map +1 -1
  149. package/dist/v2/providers/index.d.ts +1 -1
  150. package/dist/v2/providers/index.d.ts.map +1 -1
  151. package/dist/v2/providers/keys.d.ts +8 -0
  152. package/dist/v2/providers/keys.d.ts.map +1 -1
  153. package/dist/v2/providers/types.d.ts +3 -37
  154. package/dist/v2/providers/types.d.ts.map +1 -1
  155. package/dist/v2/styles/CopilotKitStyles.d.ts +18 -0
  156. package/dist/v2/styles/CopilotKitStyles.d.ts.map +1 -0
  157. package/dist/v2/types/defineToolCallRenderer.d.ts +2 -0
  158. package/dist/v2/types/defineToolCallRenderer.d.ts.map +1 -1
  159. package/dist/v2/types/human-in-the-loop.d.ts +8 -29
  160. package/dist/v2/types/human-in-the-loop.d.ts.map +1 -1
  161. package/dist/v2/types/interrupt.d.ts +1 -22
  162. package/dist/v2/types/interrupt.d.ts.map +1 -1
  163. package/dist/v2/types/vue-activity-message-renderer.d.ts +16 -15
  164. package/dist/v2/types/vue-activity-message-renderer.d.ts.map +1 -1
  165. package/dist/v2/types/vue-custom-message-renderer.d.ts +7 -16
  166. package/dist/v2/types/vue-custom-message-renderer.d.ts.map +1 -1
  167. package/dist/v2/types/vue-tool-call-renderer.d.ts +16 -28
  168. package/dist/v2/types/vue-tool-call-renderer.d.ts.map +1 -1
  169. package/package.json +14 -27
  170. package/src/__tests__/build-entries.test.ts +27 -0
  171. package/src/components/copilot-provider/CopilotKit.vue +4 -1
  172. package/src/hooks/__tests__/zod-parameters.test.ts +520 -0
  173. package/src/hooks/use-copilot-action.ts +2 -1
  174. package/src/hooks/use-frontend-tool.ts +3 -6
  175. package/src/hooks/zod-parameters.ts +162 -0
  176. package/src/v2/__tests__/exports.test.ts +4 -2
  177. package/src/v2/components/A2UIMessageRenderer.ts +56 -68
  178. package/src/v2/components/A2UISurfaceActivityRenderer.vue +11 -23
  179. package/src/v2/components/LicenseWarningBanner.vue +31 -15
  180. package/src/v2/components/OpenGenerativeUIRenderer.ts +51 -373
  181. package/src/v2/components/__tests__/A2UIBasicCatalogColors.test.ts +8 -9
  182. package/src/v2/components/__tests__/A2UICatalogContext.test.ts +1 -1
  183. package/src/v2/components/__tests__/A2UIMessageRenderer.test.ts +63 -36
  184. package/src/v2/components/__tests__/LicenseWarningBanner.test.ts +20 -0
  185. package/src/v2/components/__tests__/OpenGenerativeUIRenderer.test.ts +55 -6
  186. package/src/v2/components/a2ui/A2UIBuiltInToolCallRenderer.ts +15 -258
  187. package/src/v2/components/a2ui/A2UICatalogContext.ts +10 -102
  188. package/src/v2/components/a2ui/A2UIRecoveryStates.ts +444 -0
  189. package/src/v2/components/a2ui/__tests__/A2UIRecoveryStates.test.ts +175 -0
  190. package/src/v2/components/a2ui.ts +1 -1
  191. package/src/v2/components/chat/CopilotChat.vue +26 -40
  192. package/src/v2/components/chat/CopilotChatAssistantMessage.vue +267 -67
  193. package/src/v2/components/chat/CopilotChatAssistantToolbarButton.vue +46 -0
  194. package/src/v2/components/chat/CopilotChatAttachmentQueue.vue +150 -173
  195. package/src/v2/components/chat/CopilotChatAttachmentRenderer.vue +77 -20
  196. package/src/v2/components/chat/CopilotChatAudioRecorder.vue +2 -2
  197. package/src/v2/components/chat/CopilotChatFeedbackCommentPopover.vue +240 -0
  198. package/src/v2/components/chat/CopilotChatInput.vue +250 -191
  199. package/src/v2/components/chat/CopilotChatInspectorButton.vue +331 -0
  200. package/src/v2/components/chat/CopilotChatLightbox.vue +105 -0
  201. package/src/v2/components/chat/CopilotChatMessageView.vue +262 -87
  202. package/src/v2/components/chat/CopilotChatReasoningMessage.vue +52 -35
  203. package/src/v2/components/chat/CopilotChatSuggestionPill.vue +5 -14
  204. package/src/v2/components/chat/CopilotChatSuggestionView.vue +1 -4
  205. package/src/v2/components/chat/CopilotChatToggleButton.vue +3 -9
  206. package/src/v2/components/chat/CopilotChatToggleButtonCloseIcon.ts +1 -1
  207. package/src/v2/components/chat/CopilotChatToggleButtonOpenIcon.ts +1 -1
  208. package/src/v2/components/chat/CopilotChatToolCallGroup.vue +353 -0
  209. package/src/v2/components/chat/CopilotChatToolCallItem.vue +24 -14
  210. package/src/v2/components/chat/CopilotChatToolCallsView.vue +194 -21
  211. package/src/v2/components/chat/CopilotChatUserMessage.vue +105 -42
  212. package/src/v2/components/chat/CopilotChatView.vue +37 -42
  213. package/src/v2/components/chat/CopilotChatWorkSummary.vue +111 -0
  214. package/src/v2/components/chat/CopilotModalHeader.vue +10 -8
  215. package/src/v2/components/chat/CopilotModalHeaderCloseButton.ts +7 -6
  216. package/src/v2/components/chat/CopilotModalHeaderTitle.ts +1 -4
  217. package/src/v2/components/chat/CopilotPopup.vue +3 -0
  218. package/src/v2/components/chat/CopilotPopupView.vue +1 -0
  219. package/src/v2/components/chat/CopilotPopupViewInternal.vue +9 -19
  220. package/src/v2/components/chat/CopilotPopupWelcomeScreen.vue +5 -8
  221. package/src/v2/components/chat/CopilotSidebar.vue +5 -0
  222. package/src/v2/components/chat/CopilotSidebarView.vue +2 -0
  223. package/src/v2/components/chat/CopilotSidebarViewInternal.vue +24 -17
  224. package/src/v2/components/chat/CopilotSidebarWelcomeScreen.vue +7 -10
  225. package/src/v2/components/chat/StreamingMarkdownDefault.vue +3 -5
  226. package/src/v2/components/chat/__tests__/CopilotChat.labels.test.ts +19 -4
  227. package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.ts +26 -9
  228. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.inspector.test.ts +269 -0
  229. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.learningFeedback.test.ts +760 -0
  230. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.slots.e2e.test.ts +3 -5
  231. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.test.ts +2 -2
  232. package/src/v2/components/chat/__tests__/CopilotChatInput.addMenu.test.ts +328 -0
  233. package/src/v2/components/chat/__tests__/CopilotChatInput.slots.e2e.test.ts +6 -3
  234. package/src/v2/components/chat/__tests__/CopilotChatInput.test.ts +55 -14
  235. package/src/v2/components/chat/__tests__/CopilotChatLightbox.test.ts +160 -0
  236. package/src/v2/components/chat/__tests__/CopilotChatMessageView.test.ts +247 -403
  237. package/src/v2/components/chat/__tests__/CopilotChatMessageView.toolCallDisplay.test.ts +637 -0
  238. package/src/v2/components/chat/__tests__/CopilotChatSuggestionPill.test.ts +1 -1
  239. package/src/v2/components/chat/__tests__/CopilotChatSuggestionView.slots.e2e.test.ts +3 -1
  240. package/src/v2/components/chat/__tests__/CopilotChatToolCallsView.learning.test.ts +75 -0
  241. package/src/v2/components/chat/__tests__/CopilotChatUserMessage.attachments.test.ts +143 -0
  242. package/src/v2/components/chat/__tests__/CopilotChatView.inputOverlay.test.ts +5 -4
  243. package/src/v2/components/chat/__tests__/CopilotChatView.rootWidth.test.ts +7 -33
  244. package/src/v2/components/chat/__tests__/CopilotModalHeader.slots.e2e.test.ts +1 -1
  245. package/src/v2/components/chat/__tests__/CopilotPopupView.slots.e2e.test.ts +3 -3
  246. package/src/v2/components/chat/__tests__/CopilotSidebarView.position.test.ts +126 -0
  247. package/src/v2/components/chat/__tests__/messageFeedbackDefaults.test.ts +33 -0
  248. package/src/v2/components/chat/__tests__/modalChatDisplayProps.test.ts +44 -0
  249. package/src/v2/components/chat/copilotChatToolCallItemRender.ts +3 -19
  250. package/src/v2/components/chat/index.ts +2 -1
  251. package/src/v2/components/chat/inspector-shortcuts.ts +34 -0
  252. package/src/v2/components/chat/message-feedback.ts +291 -0
  253. package/src/v2/components/chat/tool-call-capture.ts +55 -0
  254. package/src/v2/components/chat/tool-call-display-context.ts +28 -0
  255. package/src/v2/components/chat/tool-call-group-parts.ts +251 -0
  256. package/src/v2/components/chat/turn-work.ts +52 -0
  257. package/src/v2/components/chat/types.ts +114 -4
  258. package/src/v2/components/chat/use-chat-labels.ts +16 -0
  259. package/src/v2/components/chat/use-lightbox.ts +64 -0
  260. package/src/v2/components/icons/__tests__/icons.test.ts +72 -68
  261. package/src/v2/components/icons/index.ts +109 -23
  262. package/src/v2/components/ui/CopilotDropdownMenuContent.vue +465 -0
  263. package/src/v2/components/ui/CopilotPopover.vue +138 -0
  264. package/src/v2/components/ui/CopilotTooltip.vue +237 -0
  265. package/src/v2/components/ui/__tests__/CopilotTooltip.test.ts +168 -0
  266. package/src/v2/components/ui/dropdown-menu.ts +15 -0
  267. package/src/v2/components/ui/floating.ts +321 -0
  268. package/src/v2/components/ui/presence.ts +54 -0
  269. package/src/v2/components/ui/use-id.ts +16 -0
  270. package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.ts +62 -1
  271. package/src/v2/hooks/__tests__/use-render-activity-message.test.ts +105 -0
  272. package/src/v2/hooks/__tests__/use-render-tool.test.ts +3 -6
  273. package/src/v2/hooks/__tests__/zod-regression.test.ts +5 -4
  274. package/src/v2/hooks/use-default-render-tool.ts +123 -62
  275. package/src/v2/hooks/use-frontend-tool.ts +10 -10
  276. package/src/v2/hooks/use-render-activity-message.ts +27 -48
  277. package/src/v2/hooks/use-render-custom-messages.ts +14 -35
  278. package/src/v2/hooks/use-render-tool.ts +8 -0
  279. package/src/v2/index.ts +3 -0
  280. package/src/v2/lib/__tests__/message-feedback.test.ts +152 -0
  281. package/src/v2/lib/message-feedback.ts +120 -0
  282. package/src/v2/lib/processPartialHtml.ts +6 -21
  283. package/src/v2/lib/vue-core.ts +3 -5
  284. package/src/v2/providers/CopilotChatConfigurationProvider.vue +29 -2
  285. package/src/v2/providers/CopilotKitProvider.types.ts +57 -5
  286. package/src/v2/providers/CopilotKitProvider.vue +177 -114
  287. package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.openThread.test.ts +69 -0
  288. package/src/v2/providers/__tests__/CopilotKitProvider.a2ui.test.ts +1 -1
  289. package/src/v2/providers/__tests__/CopilotKitProvider.headersAtSendTime.test.ts +3 -3
  290. package/src/v2/providers/__tests__/CopilotKitProvider.learning.test.ts +536 -0
  291. package/src/v2/providers/__tests__/CopilotKitProvider.test.ts +17 -33
  292. package/src/v2/providers/index.ts +4 -1
  293. package/src/v2/providers/keys.ts +12 -0
  294. package/src/v2/providers/types.ts +3 -38
  295. package/src/v2/styles/CopilotKitStyles.ts +95 -0
  296. package/src/v2/styles/__tests__/CopilotKitStyles.client.test.ts +149 -0
  297. package/src/v2/styles/__tests__/CopilotKitStyles.ssr.test.ts +71 -0
  298. package/src/v2/types/__tests__/defineToolCallRenderer.test.ts +28 -33
  299. package/src/v2/types/defineToolCallRenderer.ts +9 -4
  300. package/src/v2/types/human-in-the-loop.ts +8 -32
  301. package/src/v2/types/interrupt.ts +7 -31
  302. package/src/v2/types/vue-activity-message-renderer.ts +24 -15
  303. package/src/v2/types/vue-custom-message-renderer.ts +12 -16
  304. package/src/v2/types/vue-tool-call-renderer.ts +23 -31
  305. package/vite.config.ts +5 -5
  306. package/dist/styles.css +0 -2
  307. package/dist/v2/components/chat/BasicMarkdown.vue.d.ts +0 -9
  308. package/dist/v2/components/chat/BasicMarkdown.vue.d.ts.map +0 -1
  309. package/dist/v2/lib/transcription-client.d.ts +0 -19
  310. package/dist/v2/lib/transcription-client.d.ts.map +0 -1
  311. package/scripts/scope-preflight.mjs +0 -100
  312. package/src/styles/globals.css +0 -233
  313. package/src/v2/components/chat/BasicMarkdown.vue +0 -158
  314. package/src/v2/components/chat/__tests__/BasicMarkdown.test.ts +0 -69
  315. package/src/v2/lib/__tests__/processPartialHtml.test.ts +0 -84
  316. package/src/v2/lib/__tests__/transcription-client.test.ts +0 -67
  317. package/src/v2/lib/transcription-client.ts +0 -151
@@ -649,7 +649,7 @@ describe("CopilotChatAssistantMessage Slot System E2E Tests", () => {
649
649
  });
650
650
 
651
651
  describe("5. className Override with Tailwind Strings", () => {
652
- it("should override root className while preserving default prose classes", () => {
652
+ it("should override root className while preserving default markdown classes", () => {
653
653
  const Host = defineComponent({
654
654
  components: { CopilotChatAssistantMessage },
655
655
  setup() {
@@ -664,10 +664,8 @@ describe("CopilotChatAssistantMessage Slot System E2E Tests", () => {
664
664
  const { container } = renderInWrapper(Host);
665
665
  const root = container.querySelector(".custom-root-class");
666
666
  expect(root).toBeDefined();
667
- const proseDiv =
668
- root?.classList.contains("cpk:prose") ||
669
- !!root?.querySelector(".cpk\\:prose");
670
- expect(proseDiv).toBe(true);
667
+ expect(root?.classList.contains("copilotKitAssistantMessage")).toBe(true);
668
+ expect(root?.querySelector(".cpk-markdown")).not.toBeNull();
671
669
  });
672
670
 
673
671
  it("should allow tailwind utilities to override default styles", () => {
@@ -907,7 +907,7 @@ describe("CopilotChatAssistantMessage", () => {
907
907
  <CopilotChatConfigurationProvider :thread-id="TEST_THREAD_ID">
908
908
  <CopilotChatAssistantMessage :message="basicMessage" :toolbar-visible="false">
909
909
  <template #layout="{ toolbar, toolbarVisible, message }">
910
- <span class="cpk:hidden">{{ childrenSpy(toolbar, toolbarVisible, message) }}</span>
910
+ <span hidden>{{ childrenSpy(toolbar, toolbarVisible, message) }}</span>
911
911
  <div data-testid="children-render" />
912
912
  </template>
913
913
  </CopilotChatAssistantMessage>
@@ -941,7 +941,7 @@ describe("CopilotChatAssistantMessage", () => {
941
941
  <CopilotChatConfigurationProvider :thread-id="TEST_THREAD_ID">
942
942
  <CopilotChatAssistantMessage :message="basicMessage">
943
943
  <template #layout="{ toolbar, toolbarVisible, message }">
944
- <span class="cpk:hidden">{{ childrenSpy(toolbar, toolbarVisible, message) }}</span>
944
+ <span hidden>{{ childrenSpy(toolbar, toolbarVisible, message) }}</span>
945
945
  <div />
946
946
  </template>
947
947
  </CopilotChatAssistantMessage>
@@ -0,0 +1,328 @@
1
+ import { defineComponent, h, nextTick } from "vue";
2
+ import { fireEvent, render, screen, waitFor } from "@testing-library/vue";
3
+ import { afterEach, describe, expect, it, vi } from "vitest";
4
+ import CopilotChatConfigurationProvider from "../../../providers/CopilotChatConfigurationProvider.vue";
5
+ import CopilotChatInput from "../CopilotChatInput.vue";
6
+ import type { ToolsMenuItem } from "../types";
7
+
8
+ const getAddButton = () =>
9
+ screen.getByTestId("copilot-chat-input-add") as HTMLButtonElement;
10
+
11
+ const pointer = (type: string, init: Record<string, unknown> = {}) =>
12
+ Object.assign(new Event(type, { bubbles: true, cancelable: true }), {
13
+ pointerType: "mouse",
14
+ button: 0,
15
+ ctrlKey: false,
16
+ ...init,
17
+ });
18
+
19
+ function renderInput(
20
+ props: Record<string, unknown> = {},
21
+ labels?: Record<string, string>,
22
+ ) {
23
+ return render(
24
+ defineComponent({
25
+ setup() {
26
+ return () =>
27
+ h(
28
+ CopilotChatConfigurationProvider,
29
+ { threadId: "test-thread", labels },
30
+ {
31
+ default: () =>
32
+ h(CopilotChatInput, {
33
+ autoFocus: false,
34
+ onSubmitMessage: () => {},
35
+ ...props,
36
+ }),
37
+ },
38
+ );
39
+ },
40
+ }),
41
+ );
42
+ }
43
+
44
+ const toolsMenu = (
45
+ spies: Record<string, () => void>,
46
+ ): (ToolsMenuItem | "-")[] => [
47
+ { label: "Insert date", action: spies.date! },
48
+ "-",
49
+ {
50
+ label: "Templates",
51
+ items: [
52
+ { label: "Bug report", action: spies.bug! },
53
+ { label: "Feature request", action: spies.feature! },
54
+ ],
55
+ },
56
+ { label: "Clear", action: spies.clear! },
57
+ ];
58
+
59
+ function spies() {
60
+ return {
61
+ date: vi.fn(),
62
+ bug: vi.fn(),
63
+ feature: vi.fn(),
64
+ clear: vi.fn(),
65
+ };
66
+ }
67
+
68
+ describe("CopilotChatInput add menu", () => {
69
+ afterEach(() => {
70
+ vi.useRealTimers();
71
+ document.body.innerHTML = "";
72
+ });
73
+
74
+ it("marks the trigger as a menu button and points it at the open menu", async () => {
75
+ renderInput({ toolsMenu: toolsMenu(spies()) });
76
+ const button = getAddButton();
77
+ expect(button.getAttribute("aria-haspopup")).toBe("menu");
78
+ expect(button.getAttribute("aria-expanded")).toBe("false");
79
+ expect(button.getAttribute("data-state")).toBe("closed");
80
+
81
+ await fireEvent.click(button);
82
+ const content = screen.getByRole("menu");
83
+ expect(button.getAttribute("aria-expanded")).toBe("true");
84
+ expect(button.getAttribute("data-state")).toBe("open");
85
+ expect(button.getAttribute("aria-controls")).toBe(content.id);
86
+ expect(content.getAttribute("aria-labelledby")).toBe(button.id);
87
+ expect(content.classList.contains("cpk-dropdown-menu-content")).toBe(true);
88
+ expect(content.getAttribute("data-slot")).toBe("dropdown-menu-content");
89
+ expect(content.hasAttribute("data-copilotkit")).toBe(true);
90
+ // Teleported out of the input, as Radix portals it.
91
+ expect(
92
+ screen.getByTestId("copilot-chat-input-container").contains(content),
93
+ ).toBe(false);
94
+ });
95
+
96
+ it("renders a submenu trigger instead of a flattened labelled group", async () => {
97
+ renderInput({ toolsMenu: toolsMenu(spies()) });
98
+ await fireEvent.click(getAddButton());
99
+
100
+ const subTrigger = screen.getByRole("menuitem", { name: "Templates" });
101
+ expect(subTrigger.getAttribute("aria-haspopup")).toBe("menu");
102
+ expect(subTrigger.getAttribute("aria-expanded")).toBe("false");
103
+ expect(subTrigger.classList.contains("cpk-dropdown-menu-sub-trigger")).toBe(
104
+ true,
105
+ );
106
+ expect(
107
+ subTrigger.querySelector(".cpk-dropdown-menu-sub-trigger-icon"),
108
+ ).not.toBeNull();
109
+ // The submenu's items are not in the menu until it opens.
110
+ expect(screen.queryByRole("menuitem", { name: "Bug report" })).toBeNull();
111
+ expect(document.querySelector(".cpk-dropdown-menu-label")).toBeNull();
112
+ expect(screen.getAllByRole("separator")).toHaveLength(1);
113
+ });
114
+
115
+ it("opens from the keyboard on its first item, and ArrowUp opens on the last", async () => {
116
+ renderInput({ toolsMenu: toolsMenu(spies()) });
117
+ const button = getAddButton();
118
+
119
+ await fireEvent.keyDown(button, { key: "ArrowDown" });
120
+ await waitFor(() =>
121
+ expect(document.activeElement?.textContent?.trim()).toBe("Insert date"),
122
+ );
123
+
124
+ await fireEvent.keyDown(document.activeElement!, { key: "Escape" });
125
+ await waitFor(() => expect(document.activeElement).toBe(button));
126
+ expect(button.getAttribute("aria-expanded")).toBe("false");
127
+
128
+ await fireEvent.keyDown(button, { key: "ArrowUp" });
129
+ await waitFor(() =>
130
+ expect(document.activeElement?.textContent?.trim()).toBe("Clear"),
131
+ );
132
+ });
133
+
134
+ it("moves focus with the arrow keys, Home, End, and type-ahead", async () => {
135
+ renderInput({ toolsMenu: toolsMenu(spies()) });
136
+ await fireEvent.keyDown(getAddButton(), { key: "Enter" });
137
+ await waitFor(() =>
138
+ expect(document.activeElement?.textContent?.trim()).toBe("Insert date"),
139
+ );
140
+ const focused = () => document.activeElement?.textContent?.trim();
141
+
142
+ await fireEvent.keyDown(document.activeElement!, { key: "ArrowDown" });
143
+ expect(focused()).toBe("Templates");
144
+ await fireEvent.keyDown(document.activeElement!, { key: "ArrowDown" });
145
+ expect(focused()).toBe("Clear");
146
+ // Radix menus do not wrap by default.
147
+ await fireEvent.keyDown(document.activeElement!, { key: "ArrowDown" });
148
+ expect(focused()).toBe("Clear");
149
+ await fireEvent.keyDown(document.activeElement!, { key: "Home" });
150
+ expect(focused()).toBe("Insert date");
151
+ await fireEvent.keyDown(document.activeElement!, { key: "End" });
152
+ expect(focused()).toBe("Clear");
153
+ await fireEvent.keyDown(document.activeElement!, { key: "ArrowUp" });
154
+ expect(focused()).toBe("Templates");
155
+ await fireEvent.keyDown(document.activeElement!, { key: "i" });
156
+ expect(focused()).toBe("Insert date");
157
+ });
158
+
159
+ it("runs an item on Enter, closes, and returns focus to the trigger", async () => {
160
+ const actions = spies();
161
+ renderInput({ toolsMenu: toolsMenu(actions) });
162
+ const button = getAddButton();
163
+ await fireEvent.keyDown(button, { key: "Enter" });
164
+ await waitFor(() =>
165
+ expect(document.activeElement?.textContent?.trim()).toBe("Insert date"),
166
+ );
167
+
168
+ await fireEvent.keyDown(document.activeElement!, { key: "Enter" });
169
+ expect(actions.date).toHaveBeenCalledTimes(1);
170
+ expect(button.getAttribute("aria-expanded")).toBe("false");
171
+ expect(document.activeElement).toBe(button);
172
+ });
173
+
174
+ it("opens a submenu with ArrowRight on its first item, and ArrowLeft closes it", async () => {
175
+ const actions = spies();
176
+ renderInput({ toolsMenu: toolsMenu(actions) });
177
+ await fireEvent.keyDown(getAddButton(), { key: "Enter" });
178
+ await waitFor(() =>
179
+ expect(document.activeElement?.textContent?.trim()).toBe("Insert date"),
180
+ );
181
+ await fireEvent.keyDown(document.activeElement!, { key: "ArrowDown" });
182
+ const subTrigger = screen.getByRole("menuitem", { name: "Templates" });
183
+ expect(document.activeElement).toBe(subTrigger);
184
+
185
+ await fireEvent.keyDown(subTrigger, { key: "ArrowRight" });
186
+ const bug = await screen.findByRole("menuitem", { name: "Bug report" });
187
+ await waitFor(() => expect(document.activeElement).toBe(bug));
188
+ expect(subTrigger.getAttribute("aria-expanded")).toBe("true");
189
+ const subContent = bug.closest('[role="menu"]') as HTMLElement;
190
+ expect(subContent.classList.contains("cpk-dropdown-menu-sub-content")).toBe(
191
+ true,
192
+ );
193
+ expect(subTrigger.getAttribute("aria-controls")).toBe(subContent.id);
194
+
195
+ await fireEvent.keyDown(bug, { key: "ArrowLeft" });
196
+ await waitFor(() => expect(document.activeElement).toBe(subTrigger));
197
+ expect(subTrigger.getAttribute("aria-expanded")).toBe("false");
198
+
199
+ // Enter on the submenu trigger opens it too; Enter on an item runs it.
200
+ await fireEvent.keyDown(subTrigger, { key: "Enter" });
201
+ const feature = await screen.findByRole("menuitem", {
202
+ name: "Feature request",
203
+ });
204
+ await waitFor(() =>
205
+ expect(document.activeElement?.textContent?.trim()).toBe("Bug report"),
206
+ );
207
+ await fireEvent.keyDown(document.activeElement!, { key: "ArrowDown" });
208
+ expect(document.activeElement).toBe(feature);
209
+ await fireEvent.keyDown(feature, { key: "Enter" });
210
+ expect(actions.feature).toHaveBeenCalledTimes(1);
211
+ await waitFor(() => expect(screen.queryAllByRole("menu")).toHaveLength(0));
212
+ expect(document.activeElement).toBe(getAddButton());
213
+ });
214
+
215
+ it("closes the whole menu on Escape in a submenu without reaching outer Escape listeners", async () => {
216
+ renderInput({ toolsMenu: toolsMenu(spies()) });
217
+ const windowEscape = vi.fn();
218
+ const onWindowKeyDown = (event: KeyboardEvent) => {
219
+ if (event.key === "Escape") windowEscape();
220
+ };
221
+ window.addEventListener("keydown", onWindowKeyDown);
222
+ try {
223
+ await fireEvent.keyDown(getAddButton(), { key: "ArrowDown" });
224
+ await waitFor(() =>
225
+ expect(document.activeElement?.textContent?.trim()).toBe("Insert date"),
226
+ );
227
+ await fireEvent.keyDown(document.activeElement!, { key: "ArrowDown" });
228
+ await fireEvent.keyDown(document.activeElement!, { key: "ArrowRight" });
229
+ const bug = await screen.findByRole("menuitem", { name: "Bug report" });
230
+ await waitFor(() => expect(document.activeElement).toBe(bug));
231
+
232
+ await fireEvent.keyDown(bug, { key: "Escape" });
233
+ await waitFor(() =>
234
+ expect(getAddButton().getAttribute("aria-expanded")).toBe("false"),
235
+ );
236
+ expect(document.activeElement).toBe(getAddButton());
237
+ expect(windowEscape).not.toHaveBeenCalled();
238
+ } finally {
239
+ window.removeEventListener("keydown", onWindowKeyDown);
240
+ }
241
+ });
242
+
243
+ it("closes on Tab and returns focus to the trigger", async () => {
244
+ renderInput({ toolsMenu: toolsMenu(spies()) });
245
+ await fireEvent.keyDown(getAddButton(), { key: "ArrowDown" });
246
+ await waitFor(() =>
247
+ expect(document.activeElement?.textContent?.trim()).toBe("Insert date"),
248
+ );
249
+ await fireEvent.keyDown(document.activeElement!, { key: "Tab" });
250
+ expect(getAddButton().getAttribute("aria-expanded")).toBe("false");
251
+ expect(document.activeElement).toBe(getAddButton());
252
+ });
253
+
254
+ it("opens on pointer down, and a pointer down outside closes it", async () => {
255
+ renderInput({ toolsMenu: toolsMenu(spies()) });
256
+ const button = getAddButton();
257
+ button.dispatchEvent(pointer("pointerdown"));
258
+ // The click that follows the pointer down does not toggle it again.
259
+ await fireEvent.click(button, { detail: 1 });
260
+ await nextTick();
261
+ expect(button.getAttribute("aria-expanded")).toBe("true");
262
+ const content = screen.getByRole("menu");
263
+ await waitFor(() => expect(document.activeElement).toBe(content));
264
+
265
+ document.body.dispatchEvent(pointer("pointerdown"));
266
+ await nextTick();
267
+ expect(button.getAttribute("aria-expanded")).toBe("false");
268
+ });
269
+
270
+ it("opens a submenu on hover and focuses the hovered item", async () => {
271
+ vi.useFakeTimers();
272
+ const actions = spies();
273
+ renderInput({ toolsMenu: toolsMenu(actions) });
274
+ await fireEvent.click(getAddButton(), { detail: 1 });
275
+ await nextTick();
276
+ const subTrigger = screen.getByRole("menuitem", { name: "Templates" });
277
+
278
+ subTrigger.dispatchEvent(pointer("pointermove"));
279
+ expect(document.activeElement).toBe(subTrigger);
280
+ expect(screen.queryByRole("menuitem", { name: "Bug report" })).toBeNull();
281
+ await vi.advanceTimersByTimeAsync(150);
282
+ const bug = screen.getByRole("menuitem", { name: "Bug report" });
283
+ // Opening on hover leaves focus on the trigger.
284
+ expect(document.activeElement).toBe(subTrigger);
285
+
286
+ await fireEvent.click(bug);
287
+ expect(actions.bug).toHaveBeenCalledTimes(1);
288
+ await vi.advanceTimersByTimeAsync(500);
289
+ expect(screen.queryAllByRole("menu")).toHaveLength(0);
290
+ });
291
+
292
+ it("shows the add button label and the / shortcut in its tooltip", async () => {
293
+ renderInput(
294
+ { onAddFile: vi.fn() },
295
+ { chatInputToolbarAddButtonLabel: "Upload attachment" },
296
+ );
297
+ const button = getAddButton();
298
+ await fireEvent.focus(button);
299
+ const tooltip = screen.getByRole("tooltip");
300
+ expect(tooltip.textContent).toContain("Upload attachment");
301
+ expect(tooltip.querySelector("code")?.textContent?.trim()).toBe("/");
302
+ expect(
303
+ tooltip.querySelector(".cpk-chat-input-add-menu-tooltip"),
304
+ ).not.toBeNull();
305
+ expect(button.getAttribute("aria-describedby")).toBe(tooltip.id);
306
+ });
307
+
308
+ it("hides the add button tooltip while the menu is open", async () => {
309
+ renderInput({ onAddFile: vi.fn() });
310
+ const button = getAddButton();
311
+ await fireEvent.focus(button);
312
+ expect(screen.getByRole("tooltip")).toBeTruthy();
313
+
314
+ await fireEvent.keyDown(button, { key: "Enter" });
315
+ await waitFor(() => expect(screen.queryByRole("tooltip")).toBeNull());
316
+ });
317
+
318
+ it("gives the transcribe buttons a tooltip with their label instead of a title", async () => {
319
+ renderInput({ onStartTranscribe: vi.fn() });
320
+ const start = screen.getByTestId("copilot-chat-input-start-transcribe");
321
+ expect(start.hasAttribute("title")).toBe(false);
322
+ await fireEvent.focus(start);
323
+ const tooltip = screen.getByRole("tooltip");
324
+ expect(tooltip.textContent?.trim()).toBe("Transcribe");
325
+ expect(tooltip.getAttribute("data-side")).toBe("bottom");
326
+ expect(start.getAttribute("aria-describedby")).toBe(tooltip.id);
327
+ });
328
+ });
@@ -992,7 +992,9 @@ describe("CopilotChatInput Slot System E2E Tests", () => {
992
992
  "[data-testid='input-container']",
993
993
  ) as HTMLElement;
994
994
  expect(inputContainer).not.toBeNull();
995
- expect(inputContainer.classList.contains("cpk:absolute")).toBe(false);
995
+ expect(inputContainer.classList.contains("cpk-chat-input-absolute")).toBe(
996
+ false,
997
+ );
996
998
  });
997
999
 
998
1000
  it("should render absolute positioning when positioning='absolute'", () => {
@@ -1008,8 +1010,9 @@ describe("CopilotChatInput Slot System E2E Tests", () => {
1008
1010
  "[data-testid='absolute-input']",
1009
1011
  ) as HTMLElement;
1010
1012
  expect(inputContainer).not.toBeNull();
1011
- expect(inputContainer.classList.contains("cpk:absolute")).toBe(true);
1012
- expect(inputContainer.classList.contains("cpk:bottom-0")).toBe(true);
1013
+ expect(inputContainer.classList.contains("cpk-chat-input-absolute")).toBe(
1014
+ true,
1015
+ );
1013
1016
  });
1014
1017
 
1015
1018
  it("should apply keyboard height transform", () => {
@@ -35,7 +35,9 @@ const mockLayoutMetrics = (
35
35
  container: HTMLElement,
36
36
  options?: { gridWidth?: number; addWidth?: number; actionsWidth?: number },
37
37
  ) => {
38
- const grid = container.querySelector("div.cpk\\:grid") as HTMLElement | null;
38
+ const grid = container.querySelector(
39
+ ".cpk-chat-input-grid",
40
+ ) as HTMLElement | null;
39
41
  if (!grid) {
40
42
  throw new Error("Grid container not found in CopilotChatInput layout");
41
43
  }
@@ -557,6 +559,38 @@ describe("CopilotChatInput", () => {
557
559
  expect(screen.getByRole("button", { name: "Stop" })).toBe(getSendButton());
558
560
  });
559
561
 
562
+ describe("Enter while the agent runs", () => {
563
+ async function pressEnterWhileRunning(modelValue: string) {
564
+ const onStop = vi.fn();
565
+ renderWithProvider({
566
+ props: { modelValue, isRunning: true },
567
+ listeners: { onSubmitMessage: mockOnSubmitMessage, onStop },
568
+ });
569
+ await fireEvent.keyDown(
570
+ screen.getByPlaceholderText("Type a message..."),
571
+ {
572
+ key: "Enter",
573
+ shiftKey: false,
574
+ },
575
+ );
576
+ return onStop;
577
+ }
578
+
579
+ it("sends the typed text and does not stop the run", async () => {
580
+ const onStop = await pressEnterWhileRunning(" next question ");
581
+
582
+ expect(mockOnSubmitMessage).toHaveBeenCalledWith("next question");
583
+ expect(onStop).not.toHaveBeenCalled();
584
+ });
585
+
586
+ it("stops the run when the input is empty", async () => {
587
+ const onStop = await pressEnterWhileRunning("");
588
+
589
+ expect(onStop).toHaveBeenCalledOnce();
590
+ expect(mockOnSubmitMessage).not.toHaveBeenCalled();
591
+ });
592
+ });
593
+
560
594
  it("names the send button with the configured labels", () => {
561
595
  renderWithProvider({
562
596
  template: `
@@ -694,9 +728,8 @@ describe("CopilotChatInput", () => {
694
728
  const layoutCell = textarea.parentElement as HTMLElement;
695
729
  const gridContainer = layoutCell.parentElement as HTMLElement;
696
730
 
697
- expect(layoutCell.className).toContain("col-start-2");
698
- expect(layoutCell.className).not.toContain("col-span-3");
699
- expect(gridContainer.className).toContain("items-center");
731
+ expect(layoutCell.className).toContain("cpk-chat-input-field");
732
+ expect(gridContainer.getAttribute("data-layout")).toBe("compact");
700
733
  });
701
734
 
702
735
  it("toggles textarea padding based on multiline state", async () => {
@@ -706,8 +739,10 @@ describe("CopilotChatInput", () => {
706
739
  mockLayoutMetrics(container);
707
740
 
708
741
  const textarea = screen.getByRole("textbox");
709
- expect(textarea.className).toContain("pr-5");
710
- expect(textarea.className).not.toContain("px-5");
742
+ expect(textarea.className).toContain("cpk-chat-input-textarea-compact");
743
+ expect(textarea.className).not.toContain(
744
+ "cpk-chat-input-textarea-expanded",
745
+ );
711
746
 
712
747
  await fireEvent.input(textarea, {
713
748
  target: {
@@ -717,8 +752,10 @@ describe("CopilotChatInput", () => {
717
752
  });
718
753
 
719
754
  await waitFor(() => {
720
- expect(textarea.className).toContain("px-5");
721
- expect(textarea.className).not.toContain("pr-5");
755
+ expect(textarea.className).toContain("cpk-chat-input-textarea-expanded");
756
+ expect(textarea.className).not.toContain(
757
+ "cpk-chat-input-textarea-compact",
758
+ );
722
759
  });
723
760
  });
724
761
 
@@ -738,13 +775,16 @@ describe("CopilotChatInput", () => {
738
775
  },
739
776
  });
740
777
  await waitFor(() => {
741
- expect(layoutCell.className).toContain("col-span-3");
778
+ expect(layoutCell.parentElement?.getAttribute("data-layout")).toBe(
779
+ "expanded",
780
+ );
742
781
  });
743
782
 
744
783
  await fireEvent.input(textarea, { target: { value: "short" } });
745
784
  await waitFor(() => {
746
- expect(layoutCell.className).toContain("col-start-2");
747
- expect(layoutCell.className).not.toContain("col-span-3");
785
+ expect(layoutCell.parentElement?.getAttribute("data-layout")).toBe(
786
+ "compact",
787
+ );
748
788
  });
749
789
  });
750
790
 
@@ -760,8 +800,9 @@ describe("CopilotChatInput", () => {
760
800
 
761
801
  await waitFor(() => {
762
802
  const layoutCell = textarea.parentElement as HTMLElement;
763
- expect(layoutCell.className).toContain("col-span-3");
764
- expect(layoutCell.className).not.toContain("col-start-2");
803
+ expect(layoutCell.parentElement?.getAttribute("data-layout")).toBe(
804
+ "expanded",
805
+ );
765
806
  });
766
807
  });
767
808
 
@@ -790,7 +831,7 @@ describe("CopilotChatInput", () => {
790
831
 
791
832
  await fireEvent.click(addButton);
792
833
  const menuItem = await screen.findByRole("menuitem", {
793
- name: "Add photos or files",
834
+ name: "Add attachments",
794
835
  });
795
836
  await fireEvent.click(menuItem);
796
837
  expect(handleAddFile).toHaveBeenCalledTimes(1);
@@ -0,0 +1,160 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import { nextTick } from "vue";
3
+ import { fireEvent, render, screen } from "@testing-library/vue";
4
+ import CopilotChatAttachmentRenderer from "../CopilotChatAttachmentRenderer.vue";
5
+
6
+ // Ported from react-core's lightbox and attachment renderer behavior.
7
+
8
+ const image = {
9
+ type: "image" as const,
10
+ source: { type: "url" as const, value: "https://example.com/cat.png" },
11
+ };
12
+
13
+ async function openLightbox() {
14
+ const result = render(CopilotChatAttachmentRenderer, { props: image });
15
+ const thumbnail = screen.getByRole("button", { name: "Image attachment" });
16
+ thumbnail.focus();
17
+ await fireEvent.click(thumbnail);
18
+ await nextTick();
19
+ return { ...result, thumbnail };
20
+ }
21
+
22
+ describe("CopilotChatAttachmentRenderer lightbox", () => {
23
+ afterEach(() => {
24
+ document.body.innerHTML = "";
25
+ vi.restoreAllMocks();
26
+ });
27
+
28
+ it("opens the image full size in a lightbox portalled to the body", async () => {
29
+ const { container } = await openLightbox();
30
+
31
+ const dialog = screen.getByRole("dialog", { name: "Image attachment" });
32
+ expect(dialog.classList.contains("cpk-attachment-lightbox")).toBe(true);
33
+ expect(container.contains(dialog)).toBe(false);
34
+ const full = dialog.querySelector("img");
35
+ expect(full?.getAttribute("src")).toBe("https://example.com/cat.png");
36
+ expect(
37
+ full?.classList.contains("cpk-attachment-renderer-lightbox-image"),
38
+ ).toBe(true);
39
+ });
40
+
41
+ it("names the close button and moves focus to it", async () => {
42
+ await openLightbox();
43
+ const close = screen.getByRole("button", { name: "Close preview" });
44
+ expect(
45
+ close.classList.contains("cpk-attachment-lightbox-close-button"),
46
+ ).toBe(true);
47
+ expect(document.activeElement).toBe(close);
48
+ });
49
+
50
+ it("closes on Escape and returns focus to the thumbnail", async () => {
51
+ const { thumbnail } = await openLightbox();
52
+ await fireEvent.keyDown(document, { key: "Escape" });
53
+ await nextTick();
54
+ expect(screen.queryByRole("dialog")).toBeNull();
55
+ expect(document.activeElement).toBe(thumbnail);
56
+ });
57
+
58
+ it("closes on the close button and on a backdrop click, not a content click", async () => {
59
+ await openLightbox();
60
+ await fireEvent.click(screen.getByRole("dialog").querySelector("img")!);
61
+ expect(screen.queryByRole("dialog")).not.toBeNull();
62
+
63
+ await fireEvent.click(screen.getByRole("dialog"));
64
+ expect(screen.queryByRole("dialog")).toBeNull();
65
+
66
+ await fireEvent.click(
67
+ screen.getByRole("button", { name: "Image attachment" }),
68
+ );
69
+ await fireEvent.click(
70
+ screen.getByRole("button", { name: "Close preview" }),
71
+ );
72
+ expect(screen.queryByRole("dialog")).toBeNull();
73
+ });
74
+
75
+ it("keeps Tab inside the lightbox", async () => {
76
+ await openLightbox();
77
+ const close = screen.getByRole("button", { name: "Close preview" });
78
+ const event = new KeyboardEvent("keydown", {
79
+ key: "Tab",
80
+ bubbles: true,
81
+ cancelable: true,
82
+ });
83
+ close.dispatchEvent(event);
84
+ expect(event.defaultPrevented).toBe(true);
85
+ expect(document.activeElement).toBe(close);
86
+ });
87
+
88
+ it("morphs the thumbnail with a view transition when the browser has one", async () => {
89
+ const names: string[] = [];
90
+ const startViewTransition = vi.fn((callback: () => Promise<void>) => {
91
+ const thumbnail = document.querySelector<HTMLElement>(
92
+ ".cpk-attachment-renderer-image",
93
+ )!;
94
+ names.push(thumbnail.style.viewTransitionName);
95
+ const done = callback();
96
+ return { finished: done.then(() => undefined) };
97
+ });
98
+ Object.defineProperty(document, "startViewTransition", {
99
+ value: startViewTransition,
100
+ configurable: true,
101
+ });
102
+ try {
103
+ await openLightbox();
104
+ await nextTick();
105
+ expect(startViewTransition).toHaveBeenCalledTimes(1);
106
+ // The name is on the thumbnail before the transition, then on the
107
+ // lightbox image.
108
+ expect(names[0]).toMatch(/^cpk-lightbox-/);
109
+ const full = screen.getByRole("dialog").querySelector("img")!;
110
+ expect(full.style.viewTransitionName).toBe(names[0]);
111
+ expect(
112
+ document.querySelector<HTMLElement>(".cpk-attachment-renderer-image")!
113
+ .style.viewTransitionName,
114
+ ).toBe("");
115
+ } finally {
116
+ delete (document as { startViewTransition?: unknown })
117
+ .startViewTransition;
118
+ }
119
+ });
120
+ });
121
+
122
+ describe("CopilotChatAttachmentRenderer document link", () => {
123
+ it("links a document attachment to its resolved source URL", () => {
124
+ const href =
125
+ "https://files.example.test/download/document.pdf?signature=preserved";
126
+ render(CopilotChatAttachmentRenderer, {
127
+ props: {
128
+ type: "document",
129
+ source: { type: "url", value: href, mimeType: "application/pdf" },
130
+ filename: "document.pdf",
131
+ },
132
+ });
133
+
134
+ const link = screen.getByRole("link", { name: "document.pdf" });
135
+ expect(link.getAttribute("href")).toBe(href);
136
+ expect(link.getAttribute("download")).toBe("document.pdf");
137
+ expect(link.getAttribute("target")).toBe("_blank");
138
+ expect(link.getAttribute("rel")).toBe("noopener noreferrer");
139
+ expect(link.textContent).toContain("document.pdf");
140
+ });
141
+
142
+ it("keeps a data URL downloadable when no filename is known", () => {
143
+ render(CopilotChatAttachmentRenderer, {
144
+ props: {
145
+ type: "document",
146
+ source: {
147
+ type: "data",
148
+ value: "JVBERi0=",
149
+ mimeType: "application/pdf",
150
+ },
151
+ },
152
+ });
153
+
154
+ const link = screen.getByRole("link", { name: "application/pdf" });
155
+ expect(link.getAttribute("href")).toBe(
156
+ "data:application/pdf;base64,JVBERi0=",
157
+ );
158
+ expect(link.getAttribute("download")).toBe("");
159
+ });
160
+ });